diff --git a/.codegraph/.gitignore b/.codegraph/.gitignore new file mode 100644 index 000000000..d20c0fe4b --- /dev/null +++ b/.codegraph/.gitignore @@ -0,0 +1,5 @@ +# CodeGraph data files — local to each machine, not for committing. +# Ignore everything in .codegraph/ except this file itself, so transient +# files (the database, daemon.pid, sockets, logs) never show up in git. +* +!.gitignore diff --git a/0001-fix-anolisa-add-missing-Provides-anolisa-component-n.patch b/0001-fix-anolisa-add-missing-Provides-anolisa-component-n.patch new file mode 100644 index 000000000..6edff4d35 --- /dev/null +++ b/0001-fix-anolisa-add-missing-Provides-anolisa-component-n.patch @@ -0,0 +1,43 @@ +From d8cbf695716fe68fa688a88ecb675577322e4d4b Mon Sep 17 00:00:00 2001 +From: zhangtaibo +Date: Fri, 14 Aug 2026 19:11:46 +0800 +Subject: [PATCH] fix(anolisa): add missing Provides: anolisa-component() + to os-skills, tokenless, ws-ckpt, agent-sec-core spec files + +Four RPM spec files were missing the 'Provides: anolisa-component()' +virtual provide line that agentsight, copilot-shell, cosh-ng, and skillfs +already declare. When the repo-side components-v2.toml is unavailable +(HTTP 404) and no package_map entry exists in repo.toml, the ANOLISA RPM +resolver falls through all three resolution paths and returns empty +candidates, causing 'INVALID_ARGUMENT: not an ANOLISA RPM component'. + +Fixes #2559 (same root cause, covers os-skills/tokenless/ws-ckpt/sec-core). +Complements PR #2560 (agent-memory spec). + +Verified on ECS: with this patch, rpm-build.sh produces RPMs that include +the anolisa-component provides, and 'anolisa adapter scan' correctly lists +the component adapters after install+adopt. +--- + src/agent-sec-core/agent-sec-core.spec.in | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/src/agent-sec-core/agent-sec-core.spec.in b/src/agent-sec-core/agent-sec-core.spec.in +index 80a7db31..0e676fb5 100644 +--- a/src/agent-sec-core/agent-sec-core.spec.in ++++ b/src/agent-sec-core/agent-sec-core.spec.in +@@ -61,6 +61,12 @@ Requires: agent-sec-hermes-hook = %{version}-%{release} + Requires: agent-sec-qwen-code-hook = %{version}-%{release} + Requires: agent-sec-skills = %{version}-%{release} + ++# Declare ANOLISA component identity so that `anolisa install sec-core --backend rpm` ++# can resolve this package via RPM Provides when the repo-side components-v2.toml ++# is unavailable (404) and no package_map entry exists — the resolver falls through to ++# RPM Provides but finds no anolisa-component(sec-core) capability. ++Provides: anolisa-component(sec-core) ++ + %description + Agent-Sec-Core is an OS-level security baseline and hardening framework for AI Agents. + This metapackage installs all agent-sec-core components including CLI, hooks, and skills. +-- +2.43.7 + diff --git a/src/anolisa/crates/anolisa-cli/src/repo_config.rs b/src/anolisa/crates/anolisa-cli/src/repo_config.rs index 656f7723d..8a17d416f 100644 --- a/src/anolisa/crates/anolisa-cli/src/repo_config.rs +++ b/src/anolisa/crates/anolisa-cli/src/repo_config.rs @@ -694,7 +694,6 @@ pub fn raw_index_v2_url(base_url: &str) -> String { /// /// Released clients continue reading this path, so publishers must preserve its /// schema v1 contents when publishing later component-index generations. -#[cfg(test)] pub fn component_index_url(base_url: &str) -> String { format!("{}/components.toml", raw_root(base_url)) } diff --git a/src/anolisa/crates/anolisa-cli/src/resolution.rs b/src/anolisa/crates/anolisa-cli/src/resolution.rs index 8cba4912a..5f6079580 100644 --- a/src/anolisa/crates/anolisa-cli/src/resolution.rs +++ b/src/anolisa/crates/anolisa-cli/src/resolution.rs @@ -14,7 +14,7 @@ use anolisa_platform::pkg_query::{PackageQuery, PackageQueryError}; use serde::{Deserialize, Serialize}; use thiserror::Error; -use crate::repo_config::{BackendConfig, HostVars, RepoConfig, component_index_v2_url}; +use crate::repo_config::{BackendConfig, HostVars, RepoConfig, component_index_url, component_index_v2_url}; /// On-disk schema version for repo-side `components-v2.toml`. pub(crate) const COMPONENT_INDEX_SCHEMA_VERSION: u32 = 2; @@ -54,6 +54,7 @@ pub(crate) struct ComponentIndexEntry { #[serde(default)] pub(crate) summary: Option, /// Supported host OS/architecture combinations. + #[serde(default)] pub(crate) targets: Vec, /// Backend-native package names for this component. /// @@ -473,7 +474,11 @@ impl ComponentIndex { } fn validate(&self) -> Result<(), ComponentIndexError> { - if self.schema_version != COMPONENT_INDEX_SCHEMA_VERSION { + // Accept schema v1 and v2: v1 rows simply lack `targets`, which + // defaults to an empty vec via `#[serde(default)]`. This lets the + // v2 parser gracefully load v1 `components.toml` when the v2 file + // has not been published to the mirror yet. + if !matches!(self.schema_version, 1 | 2) { return Err(ComponentIndexError::UnsupportedSchema { actual: self.schema_version, expected: COMPONENT_INDEX_SCHEMA_VERSION, @@ -492,7 +497,9 @@ impl ComponentIndex { reason: format!("duplicate component '{name}'"), }); } - if entry.targets.is_empty() { + // v1 indexes predate the `targets` field; skip the non-empty + // check for schema v1 (targets defaults to empty via serde). + if self.schema_version >= 2 && entry.targets.is_empty() { return Err(ComponentIndexError::Invalid { reason: format!("component '{name}' must declare at least one target"), }); @@ -741,18 +748,43 @@ pub(crate) fn load_component_index( let url = component_index_v2_url(&base_url); let cache = DownloadCache::new(layout.cache_dir.clone()); + let downloaded = match fetch_index_url(&cache, &url) { + Ok(art) => art, + Err(ComponentIndexError::Fetch { ref reason }) + if reason.contains("http status 404") => + { + // v2 not published yet — fall back to v1 components.toml. + // The v2 parser accepts v1 rows because `targets` has + // `#[serde(default)]`, so legacy indexes without targets + // still load (components simply appear as "no target info"). + let v1_url = component_index_url(&base_url); + fetch_index_url(&cache, &v1_url).map_err(|err| ComponentIndexError::Fetch { + reason: format!( + "failed to fetch component index (tried v2 then v1): {err}" + ), + })? + } + Err(err) => return Err(err), + }; + ComponentIndex::load(&downloaded.cached_path) +} + +/// Fetch a component index TOML from `url` into `cache`. +fn fetch_index_url( + cache: &DownloadCache, + url: &str, +) -> Result { #[cfg(test)] if !url.starts_with("file://") { return Err(ComponentIndexError::Fetch { reason: format!("test mode: refusing non-file URL {url}"), }); } - let downloaded = cache - .fetch(&url, None) + cache + .fetch(url, None) .map_err(|err| ComponentIndexError::Fetch { reason: format!("failed to fetch {url}: {err}"), - })?; - ComponentIndex::load(&downloaded.cached_path) + }) } /// Best-effort load of repo-side `components-v2.toml`. @@ -1325,7 +1357,10 @@ cosh = "site-copilot" #[test] fn unsupported_schema_is_rejected() { - for actual in [1, 99] { + // Schema v1 and v2 are accepted; anything else is rejected. + // v1 is accepted because `targets` defaults to an empty vec and + // the non-empty-target check is skipped for schema v1. + for actual in [99] { let source = format!("schema_version = {actual}"); let err = ComponentIndex::from_toml_str(&source, "components.toml") .expect_err("unsupported schema");