test(onboarding): 托管模板指纹测试,改内容未升版本即失败
All checks were successful
Push & PR Check / check (push) Successful in 1m3s
All checks were successful
Push & PR Check / check (push) Successful in 1m3s
全托管文件(envoy.md / SKILL.md)按内容覆盖,但 verify_onboarding 只凭 capability-packs.json 版本判落后——只改内容忘升 BASE_ONBOARDING_PACK_VERSION 时 verify 永远 ready,子项目收不到更新。升版规则此前只有文档约束。 - 新增 managed_template_fingerprints_track_versions:「版本号 → 摘要」登记表 TEMPLATE_FINGERPRINTS 钉住 resources/onboarding/**(↔ BASE 版本)与 AGENTS / git-workflow 管理块(↔ 块首 version);摘要前 CRLF→LF 归一化 - capability-packs.json 与 BASE 相等复用既有断言,补可读报错 - onboarding-sync.md 说明该测试 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
f948c4c802
commit
7595c37d22
@ -6,7 +6,7 @@ description: >
|
||||
项目自身保留业务规则、测试、部署声明与交付决策。炼境本身亦被纳入其中,
|
||||
形成可自我进化的开发智能闭环。
|
||||
iteration: 1
|
||||
updated: 2026-10-02
|
||||
updated: 2026-10-03
|
||||
agent_button_carrier: tauri
|
||||
|
||||
|
||||
@ -376,7 +376,7 @@ modules:
|
||||
progress: 100
|
||||
note: "体系分发主线全卡完成:…/V1/U3/B4/U1 ✅(2026-07-15 择升环三断点修复收官:U3 规则校验 / B4 沉默项目信号 / U1 全托管升级+批量重分发)。U1 批量模式 MCP 实调待重建安装后终验,通过后模块收官 done"
|
||||
position: [3500, 0]
|
||||
updated: 2026-07-15
|
||||
updated: 2026-10-03
|
||||
|
||||
- id: gitea-integration
|
||||
name: Gitea 集成
|
||||
|
||||
@ -168,6 +168,14 @@
|
||||
- acceptance: agent_verify: [文件] AGENTS.md.tmpl「agent 按钮与验证路径」节含「同源同边界」「操作序列 agent 皆可复刻」「三分定位」表述,MANAGED 版本 1.6.1→1.7.0;炼境自身 AGENTS.md 块内同步;cargo test onboarding 通过
|
||||
- notes: 2026-08-23 多角度辩证收敛(v3→v3.1 外科手术版):真实增量仅三个半(先本体后投影时序/复刻+复现路径/三分定位启发式/飞轮转译轻写一句),其余为已有六条重述不重复注入;三分定位从"定律"降级"启发式"(crypto.randomUUID 环境层实锤:本体投影都没病,病在环境);原型探索豁免时序约束;飞轮转译不承诺机制(采集通道挂 governance-feedback-loop 缺口①)
|
||||
|
||||
### ✅ F1. 托管模板指纹测试:改内容必须升版本(文档约束 → 机器校验)
|
||||
- status: done
|
||||
- done_note: 2026-10-03 完成。tests 新增 managed_template_fingerprints_track_versions + 「版本号 → 摘要」登记表 TEMPLATE_FINGERPRINTS 三项:resources/onboarding/** 全部资源(递归遍历,新增模板文件自动纳入)↔ BASE_ONBOARDING_PACK_VERSION;AGENTS / git-workflow 管理块 ↔ 块首 version。摘要前 CRLF→LF,段间 \0 分隔;Python 直接对 git index LF blob 独立复算三项摘要与 Windows CRLF 工作区结果一致。报错区分「内容变了版本没升」与「版本已升待更新指纹」,并给出可直接粘贴的新登记行。BASE 与模板 capability-packs.json 相等复用既有 check_capability_packs 断言(补可读报错)。自验:cargo test --locked onboarding 22 全过;envoy.md 改一字 → 仅 pack 项失败(其余 21 测仍全过,即旧测试抓不到该漏洞);AGENTS 块内改一字 → pack + AGENTS 两项失败;均已还原(envoy.md sha256 前后一致)后全绿。侦察佐证:dbc1b06(2026-09-23)改了 git-workflow 管理块内容未升 1.7.0,文档规则在 07-06 之后仍被漏过。**合入前实证更新流程**:rebase 到并行合入的 PR #28(envoy/SKILL/AGENTS 模板改动,规范升 BASE 1.7.0、AGENTS 块 1.10.0)后测试按设计报「版本已升,更新指纹常量即可」并给出新登记行(git-workflow 未动不报),照报错更新登记表后 22 全过、全量 cargo test 267 过
|
||||
- complexity: S
|
||||
- files: src-tauri/src/commands/onboarding.rs, .claude/rules/onboarding-sync.md
|
||||
- acceptance: agent_verify: [cargo test] `cd src-tauri && cargo test --locked onboarding` 全绿;临时改 resources/onboarding/.claude/commands/envoy.md 一个字 → 指纹测试失败且报错含「升 BASE_ONBOARDING_PACK_VERSION 与 capability-packs.json 版本,并更新指纹常量」,还原后通过;摘要前 CRLF→LF 归一化(工作区 autocrlf=CRLF 与 CI LF 同摘要)
|
||||
- notes: 全托管文件(envoy.md / SKILL.md)按内容覆盖,但 verify 只看 capability-packs 版本(check_skill 仅子串检查)——只改内容忘升 BASE_ONBOARDING_PACK_VERSION 时 verify 永远 ready、无人重跑分发,子项目收不到更新。onboarding-sync.md 的升版规则此前只有文档约束(2026-07-06 块版本漏升实锤)。指纹表同时钉住 AGENTS / git-workflow 的 ONBOARDING_MANAGED 块版本(同类漏洞:块版本不升 → dispatch_versioned 全部 skipped)
|
||||
|
||||
## 复盘笔记
|
||||
|
||||
【复盘】U1 全托管文件升级通道 + 批量重分发(2026-07-15)
|
||||
|
||||
@ -5,4 +5,6 @@ globs: src-tauri/resources/onboarding/**
|
||||
|
||||
**改模板内容必须同步升级 `ONBOARDING_MANAGED_START version="x.y.z"` 版本号**——版本是分发引擎唯一的升级信号,内容变了版本不动,批量分发会全部 skipped(2026-07-06 实测踩坑)。模板改完后跑 `cargo test onboarding` 确认版本相关测试通过。
|
||||
|
||||
该规则已由指纹测试 `managed_template_fingerprints_track_versions`(`onboarding.rs`)机器校验:`resources/onboarding/**` 任何内容变化须升 `BASE_ONBOARDING_PACK_VERSION` 与模板 `capability-packs.json` 版本,管理块内容变化另须升块首 version,再按测试报错把新摘要填回 `TEMPLATE_FINGERPRINTS`。
|
||||
|
||||
分发到子项目需要**重建安装炼境**(模板打包在二进制资源里),再调 `apply_onboarding_pack`。
|
||||
|
||||
@ -1263,7 +1263,12 @@ mod tests {
|
||||
assert_eq!(unavailable.state, VerifyState::Unavailable);
|
||||
|
||||
let pack_ready = check_capability_packs(Some(TMPL_CAPABILITY_PACKS));
|
||||
assert_eq!(pack_ready.state, VerifyState::Ready);
|
||||
assert_eq!(
|
||||
pack_ready.state,
|
||||
VerifyState::Ready,
|
||||
"模板 capability-packs.json 的版本须等于 BASE_ONBOARDING_PACK_VERSION / TOOLCHAIN_PACK_VERSION:{}",
|
||||
pack_ready.detail
|
||||
);
|
||||
let pack_old = check_capability_packs(Some(r#"{"schema_version":0,"packs":[]}"#));
|
||||
assert_eq!(pack_old.state, VerifyState::VersionMismatch);
|
||||
|
||||
@ -1442,6 +1447,131 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
// ── 托管模板指纹:改内容必须升版本(onboarding-sync.md 升版规则的机器校验)──
|
||||
|
||||
/// 版本号 → 摘要登记表:(指纹对象, 登记版本, sha256)。
|
||||
/// - `resources/onboarding/**`:全部接入包资源,对应 BASE_ONBOARDING_PACK_VERSION——
|
||||
/// verify 只凭 capability-packs.json 的版本判落后,全托管文件(envoy/SKILL)的内容不参与比对
|
||||
/// - `<模板>#ONBOARDING_MANAGED`:版本化块,对应块首 version——dispatch_versioned 唯一的升级信号
|
||||
///
|
||||
/// 改了模板:按测试报错升对应版本,再把报错给出的新摘要填回这里。
|
||||
const TEMPLATE_FINGERPRINTS: [(&str, &str, &str); 3] = [
|
||||
(
|
||||
"resources/onboarding/**",
|
||||
"1.7.0",
|
||||
"0c39acd7c0513a1063ea0c2de5da06ba2baa69b7eb85b167499859ab6ad0eb45",
|
||||
),
|
||||
(
|
||||
"AGENTS.md.tmpl#ONBOARDING_MANAGED",
|
||||
"1.10.0",
|
||||
"59b29e99c1b2184732dbf828eb3f2c8d3bb5afdfdbd30fd5d967daced82313e5",
|
||||
),
|
||||
(
|
||||
"git-workflow.md.tmpl#ONBOARDING_MANAGED",
|
||||
"1.7.0",
|
||||
"9d52231fd725b786ad4d2bf874e21c155150b4df5a115e3b8facf23b6c23c255",
|
||||
),
|
||||
];
|
||||
|
||||
/// sha256 hex。先把 CRLF 归一化为 LF:Windows autocrlf 工作区是 CRLF,CI 检出是 LF。
|
||||
/// 段间加 \0 分隔,内容在相邻段之间挪动也会改变摘要。
|
||||
fn lf_sha256<'a>(parts: impl IntoIterator<Item = &'a str>) -> String {
|
||||
use sha2::Digest;
|
||||
let mut hasher = sha2::Sha256::new();
|
||||
for part in parts {
|
||||
hasher.update(part.replace("\r\n", "\n").as_bytes());
|
||||
hasher.update([0u8]);
|
||||
}
|
||||
hasher
|
||||
.finalize()
|
||||
.iter()
|
||||
.map(|byte| format!("{byte:02x}"))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// 递归收集 (相对路径, 内容)——新增模板文件无需登记,自动纳入指纹。
|
||||
fn collect_resource_files(root: &Path, dir: &Path, out: &mut Vec<(String, String)>) {
|
||||
for entry in std::fs::read_dir(dir).unwrap() {
|
||||
let path = entry.unwrap().path();
|
||||
if path.is_dir() {
|
||||
collect_resource_files(root, &path, out);
|
||||
continue;
|
||||
}
|
||||
let relative = path
|
||||
.strip_prefix(root)
|
||||
.unwrap()
|
||||
.to_string_lossy()
|
||||
.replace('\\', "/");
|
||||
let content = std::fs::read_to_string(&path)
|
||||
.unwrap_or_else(|e| panic!("读取模板 {relative} 失败: {e}"));
|
||||
out.push((relative, content));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn managed_template_fingerprints_track_versions() {
|
||||
let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("resources/onboarding");
|
||||
let mut files = Vec::new();
|
||||
collect_resource_files(&root, &root, &mut files);
|
||||
files.sort();
|
||||
let pack_digest = lf_sha256(
|
||||
files
|
||||
.iter()
|
||||
.flat_map(|(path, content)| [path.as_str(), content.as_str()]),
|
||||
);
|
||||
let block = |template: &'static str| {
|
||||
let block = extract_managed_block(template).expect("模板须含 ONBOARDING_MANAGED 块");
|
||||
(
|
||||
block_version(block).expect("管理块首须有 version"),
|
||||
lf_sha256([block]),
|
||||
)
|
||||
};
|
||||
let (agents_version, agents_digest) = block(TMPL_AGENTS);
|
||||
let (workflow_version, workflow_digest) = block(TMPL_GIT_WORKFLOW);
|
||||
let actual = [
|
||||
(
|
||||
BASE_ONBOARDING_PACK_VERSION,
|
||||
pack_digest,
|
||||
"改了托管模板请升 BASE_ONBOARDING_PACK_VERSION 与 capability-packs.json 版本,并更新指纹常量",
|
||||
),
|
||||
(
|
||||
agents_version,
|
||||
agents_digest,
|
||||
"改了 AGENTS.md.tmpl 管理块请升块首 version(不升则存量项目重跑接入包全部 skipped),并更新指纹常量",
|
||||
),
|
||||
(
|
||||
workflow_version,
|
||||
workflow_digest,
|
||||
"改了 git-workflow.md.tmpl 管理块请升块首 version(不升则存量项目重跑接入包全部 skipped),并更新指纹常量",
|
||||
),
|
||||
];
|
||||
|
||||
let stale: Vec<String> = TEMPLATE_FINGERPRINTS
|
||||
.iter()
|
||||
.zip(&actual)
|
||||
.filter(|((_, version, digest), (now_version, now_digest, _))| {
|
||||
*version != *now_version || *digest != *now_digest
|
||||
})
|
||||
.map(|((name, version, _), (now_version, now_digest, hint))| {
|
||||
let (advice, next_version) = if version == now_version {
|
||||
(*hint, "<新版本>")
|
||||
} else {
|
||||
("版本已升,更新指纹常量即可", *now_version)
|
||||
};
|
||||
format!(
|
||||
"- {name}:登记 v{version},当前 v{now_version}——{advice}。\n TEMPLATE_FINGERPRINTS 改为 (\"{name}\", \"{next_version}\", \"{now_digest}\")"
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
let hashed: Vec<&str> = files.iter().map(|(path, _)| path.as_str()).collect();
|
||||
assert!(
|
||||
stale.is_empty(),
|
||||
"托管模板内容与登记版本不符:\n{}\n(resources/onboarding/** 参与摘要的文件:{})",
|
||||
stale.join("\n"),
|
||||
hashed.join(", ")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skill_is_projected_to_codex_and_claude_from_one_source() {
|
||||
let base = std::env::temp_dir().join(format!(
|
||||
|
||||
Loading…
Reference in New Issue
Block a user