-
Notifications
You must be signed in to change notification settings - Fork 90
fix(anolisa): fallback to v1 components.toml when v2 is missing (Fixes #2562) #2564
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| From d8cbf695716fe68fa688a88ecb675577322e4d4b Mon Sep 17 00:00:00 2001 | ||
| From: zhangtaibo <zhangtaibo.ztb@alibaba-inc.com> | ||
| Date: Fri, 14 Aug 2026 19:11:46 +0800 | ||
| Subject: [PATCH] fix(anolisa): add missing Provides: anolisa-component(<name>) | ||
| to os-skills, tokenless, ws-ckpt, agent-sec-core spec files | ||
|
|
||
| Four RPM spec files were missing the 'Provides: anolisa-component(<name>)' | ||
| 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 | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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<String>, | ||
| /// Supported host OS/architecture combinations. | ||
| #[serde(default)] | ||
| pub(crate) targets: Vec<ComponentTarget>, | ||
| /// 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); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the mirror has not published Useful? React with 👍 / 👎. |
||
| 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<anolisa_core::download::DownloadedArtifact, ComponentIndexError> { | ||
| #[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() { | ||
|
Comment on lines
1357
to
1359
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P2] v1/v2 schema 接受范围与测试略有不一致
(位置:src/anolisa/crates/anolisa-cli/src/resolution.rs:473-483, 1357-1364) 🤖 Generated by Qoder • Fix in Qoder |
||
| 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"); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P1] v1/v2 回退逻辑忽略非 404 场景
load_component_index中对 v2components-v2.toml的 fetch 仅在reason.contains("http status 404")时回退到 v1,否则直接返回原始ComponentIndexError::Fetch。这会让如超时、DNS 失败、403 等非 404 错误在 v1 也可用时依然导致命令整体失败,违背“镜像暂未部署 v2 时尽量保持可用”的目标。建议将回退条件扩展为“任何 v2 fetch 失败但 v1 fetch 成功都接受 v1”,并在错误信息中区分“v1 也失败”与“v1 成功”。(位置:src/anolisa/crates/anolisa-cli/src/resolution.rs:748-768)
🤖 Generated by Qoder • Fix in Qoder