From 7f118fc8ec0ae36c5a196cc80953085ba215c47e Mon Sep 17 00:00:00 2001 From: wlvh <30534800+wlvh@users.noreply.github.com> Date: Mon, 3 Aug 2026 18:05:21 +0800 Subject: [PATCH] Strengthen workflow evidence contracts --- en/.github/pull_request_template.md | 2 +- en/AGENTS.md | 2 +- en/PR_Checklist.md | 10 + en/TESTING.md | 32 +++- en/capability_contract.json | 14 +- en/docs/development_workflow/README.md | 22 +++ tests/test_workflow_docs_sync.py | 184 ++++++++++++++++++- zh/.github/pull_request_template.md | 2 +- zh/AGENTS.md | 2 +- zh/PR_Checklist.md | 7 + zh/TESTING.md | 21 ++- zh/capability_contract.json | 14 +- zh/docs/development_workflow/decisions.md | 35 ++++ zh/skills/workflow-docs-sync/SKILL.md | 21 ++- zh/skills/workflow-docs-sync/evals/README.md | 57 +++++- 15 files changed, 391 insertions(+), 34 deletions(-) diff --git a/en/.github/pull_request_template.md b/en/.github/pull_request_template.md index 836d62b..81607f2 100644 --- a/en/.github/pull_request_template.md +++ b/en/.github/pull_request_template.md @@ -34,7 +34,7 @@ plans, historical drafts, or local uncommitted content. ## 7. Review / Fix Record - + ## 8. Known Limits, Open Decisions, and Rollback diff --git a/en/AGENTS.md b/en/AGENTS.md index 0c8a03a..2b91472 100644 --- a/en/AGENTS.md +++ b/en/AGENTS.md @@ -92,4 +92,4 @@ session; do not create repository run state, receipts, or temporary process docu ## Project-specific Conventions - + diff --git a/en/PR_Checklist.md b/en/PR_Checklist.md index 1a5e0e8..03cc2c5 100644 --- a/en/PR_Checklist.md +++ b/en/PR_Checklist.md @@ -19,6 +19,8 @@ explicitly requests it. - [ ] Select real commands from `TESTING.md` and current repository configuration; do not infer a runner or service from a template. +- [ ] Ensure the decision to add or not add tests follows `TESTING.md` section 4; with no test diff, cite a + specific covering test and rerun evidence. - [ ] For each test, record exact command, scope, result, not-run reason, actual environment, and isolation method. - [ ] Ensure environment choice follows command side effects, CI capabilities, and project policy, with @@ -33,6 +35,8 @@ explicitly requests it. left unchanged; do not edit every document merely for completeness. - [ ] Keep the authority direction `capability_contract.json → interact.md → business_user_guide.md` for capability changes. User-visible claims have current implementation or test evidence and stable anchors. +- [ ] Reference Markdown anchors through the capability contract's contract-defined protocol without copying + its token here; do not present alignment as sentence-level binding or proof of capability semantics. - [ ] Check architecture impact across entrypoints, module boundaries, data flow, state, error models, external dependencies, artifacts, and side effects. - [ ] Replace or delete every active project-fill marker while preserving valid Markdown and JSON. @@ -43,6 +47,12 @@ explicitly requests it. reviewer identity, scope, and limitations. - [ ] Fix every BLOCKER and actionable WARN that does not require a new product decision. Keep remaining issues in open decisions with evidence and impact. +- [ ] Preserve material rounds, REOPENED findings, and SUPERSEDED candidates or evidence. Reopening keeps the + original finding ID, and finding state is not confused with evidence state. +- [ ] Support a prior miss explanation with evidence or label it `hypothesis` / `unknown`; never invent cause + merely to fill a field. +- [ ] Evaluate every material finding for promotion to a long-term rule or automated gate, and record why when + it is not promoted. - [ ] Rerun affected tests and mechanical checks after fixes, then recheck the final diff and Git state. ## PR Delivery diff --git a/en/TESTING.md b/en/TESTING.md index d25d072..25495f0 100644 --- a/en/TESTING.md +++ b/en/TESTING.md @@ -5,6 +5,10 @@ Derive every exact command from current repository scripts, task configuration, CI, build files, or test framework configuration, and verify it from the repository root or a recorded working directory. Do not infer a language, runner, service, or phase from this template. +Prefer a verified repository-owned unified entrypoint when one exists and record what it actually covers; +otherwise, do not create a wrapper only to satisfy this template. Review a wrapper as a separate project +change only when repeated multi-command orchestration, service lifecycle, or cleanup steps provide independent +maintenance value. @@ -43,17 +47,33 @@ infer a language, runner, service, or phase from this template. An alignment test belongs to the target project's test suite, not the documentation sync checker. It should recursively collect stable `anchor_id` values from every object in `capability_contract.json` and -check uniqueness and Markdown references without hardcoding buckets, JSON paths, array positions, or -requiring every contract entry to appear in the business guide. +use the protocol defined by the contract rules to check uniqueness and Markdown references without +hardcoding buckets, JSON paths, array positions, or requiring every contract entry to appear in the +business guide. -Use `test_anchor: null` with a concrete reason for declarations without automation. Register the real test -anchor when a test exists. Before claiming an alignment test exists, verify its implementation and command -in the target repository. +When `test_anchor: null` is explicit, record both a nonempty, concrete `untested_reason` and a nonempty +`pending_since`; register the real test anchor when a test exists. Anchor alignment proves only mechanical +facts such as a structural reference, a valid ID, and no dangling target. It does not by itself prove +sentence-level binding or that claim semantics are implemented. Before claiming an alignment test exists, +verify its implementation and command in the target repository. ## 4. Change Type to Required Evidence +1. For a safely and deterministically reproducible escaped bug, establish the smallest regression test or + fixture that fails before the fix, then change the implementation. +2. When a test cannot establish a failing test first, preserve pre-fix failure evidence and state why stable + automation is unavailable and what risk remains. +3. For user-visible behavior, public contract, or schema changes, add or update the nearest-boundary contract + or scenario test by default. +4. With no test diff, identify the specific existing test that covers the new risk and provide rerun evidence; + “an existing high-level test” is not sufficient. +5. A behavior-preserving internal refactor may add no test, but rerun affected paths and record the + no-test-change reason. +6. A documentation-only gate proves only the structure, parsing, or alignment it actually checks; it is not + runtime behavior validation. + ## 5. Side Effects and Isolation @@ -82,5 +102,7 @@ test file. Record reusable test-decision rules supported by real failures, not incident chronology or volatile commands. If a failure came from layers passing independently while their combination failed, keep both a minimal regression and a scenario test that crosses the real boundary. +Merge the same failure mode into a more general rule. Retire it only when a stronger test, automated gate, +or authoritative rule fully carries the knowledge, never merely because the example is old. diff --git a/en/capability_contract.json b/en/capability_contract.json index a825c6c..2fdb381 100644 --- a/en/capability_contract.json +++ b/en/capability_contract.json @@ -7,9 +7,11 @@ "Every fill entry must be replaced with verified project content or deleted before validation.", "Every active statement must have evidence in the current implementation, configuration, tests, committed artifacts, or reproducible results.", "Future or proposed behavior must use an explicit non-active status and must not be presented as currently available.", - "Markdown documents reference stable anchor_id values only, never JSON paths, array indexes, or schema-internal paths.", + "Markdown documents use only `` to reference stable anchor_id values; the marker name and ANCHOR_ID are case-sensitive, ANCHOR_ID must match `[A-Za-z0-9_.-]+`, and a target checker may tolerate whitespace differences inside the comment, but a JSON path, array position, or schema-internal path cannot replace the anchor ID.", + "Other anchor, ref, contract, or wiki-link forms are unsupported and do not establish an alignment reference; this generic protocol does not guarantee that any consumer exhaustively detects or rejects unknown aliases.", + "An anchor reference proves only that a registered ID is referenced structurally; it does not by itself prove sentence-level binding, business semantics, or implementation.", "Deprecated anchors remain registered for their original meaning and must never be reused.", - "A contract without automation uses test_anchor: null and records a concrete untested_reason or pending_since value." + "A contract that explicitly uses test_anchor: null must record both a nonempty, concrete untested_reason and a nonempty pending_since value; this registers a coverage gap and does not mean the claim is verified." ], "contracts": { "capabilities": [ @@ -24,7 +26,7 @@ "test_anchor": null, "test_status": "not_automated", "untested_reason": "__PROJECT_FILL__: replace with the reason automation is absent, or replace test_anchor and remove this field", - "pending_since": "__PROJECT_FILL__: replace with the applicable date or remove this field", + "pending_since": "__PROJECT_FILL__: replace with the date the coverage gap was registered; keep this field while test_anchor is null", "since": "__PROJECT_FILL__: replace with the first verified version or date" } ], @@ -40,7 +42,7 @@ "test_anchor": null, "test_status": "not_automated", "untested_reason": "__PROJECT_FILL__: replace with the reason automation is absent, or replace test_anchor and remove this field", - "pending_since": "__PROJECT_FILL__: replace with the applicable date or remove this field", + "pending_since": "__PROJECT_FILL__: replace with the date the coverage gap was registered; keep this field while test_anchor is null", "since": "__PROJECT_FILL__: replace with the first verified version or date" } ], @@ -56,7 +58,7 @@ "test_anchor": null, "test_status": "not_automated", "untested_reason": "__PROJECT_FILL__: replace with the reason automation is absent, or replace test_anchor and remove this field", - "pending_since": "__PROJECT_FILL__: replace with the applicable date or remove this field", + "pending_since": "__PROJECT_FILL__: replace with the date the coverage gap was registered; keep this field while test_anchor is null", "since": "__PROJECT_FILL__: replace with the first verified version or date" } ], @@ -72,7 +74,7 @@ "test_anchor": null, "test_status": "not_automated", "untested_reason": "__PROJECT_FILL__: replace with the reason automation is absent, or replace test_anchor and remove this field", - "pending_since": "__PROJECT_FILL__: replace with the applicable date or remove this field", + "pending_since": "__PROJECT_FILL__: replace with the date the coverage gap was registered; keep this field while test_anchor is null", "since": "__PROJECT_FILL__: replace with the first verified version or date" } ], diff --git a/en/docs/development_workflow/README.md b/en/docs/development_workflow/README.md index 0c3e850..6c321f7 100644 --- a/en/docs/development_workflow/README.md +++ b/en/docs/development_workflow/README.md @@ -72,6 +72,28 @@ external shallow clone, then pins target HEAD and upstream SHA for the entire ru - Keep temporary PR body Markdown outside the repository. Commit, push, and draft-PR creation occur through general GitHub publishing capability only after successful checks and only when the user asks. +### DEC-007 Summary + +[DEC-007](../../../zh/docs/development_workflow/decisions.md) refines DEC-006 without restoring proxy +controls. It classifies descriptive facts, normative policies, personal or session preferences, and mixed +claims by meaning; configuration proves enforcement but does not silently supersede policy intent, and a +task preference persists only after explicit owner adoption in repository authority. + +The capability contract is the single definition point for the canonical anchor form, case-sensitive ID +grammar, and whitespace tolerance. Unsupported forms do not establish alignment, the generic protocol does +not promise exhaustive alias detection, and structural references do not prove claim semantics. An explicit +`test_anchor: null` requires both a nonempty `untested_reason` and `pending_since`; the authoring-rule change +does not raise `schema_version` from `0.1.0`. TESTING defines minimum evidence for escaped bugs, public +contracts, no-test diffs, refactors, and documentation-only gates, while the checklist enforces the +add-or-not-add test decision. +Findings keep stable IDs and first-seen evidence; REOPENED is an event, CURRENT / SUPERSEDED applies only to +candidates or evidence, and prior misses are evidence-backed, labeled hypothesis, or recorded as unknown. + +With the same target base, upstream candidate, language, and round-one document bytes, Case A round two passes +only as `PASS_NOOP`; an added valid correction is `ROUND1_INCOMPLETE`, and unsupported prose drift is +`ROUND2_DRIFT`. Either failure restarts both rounds from a clean target. This decision adds no parser, ledger, +receipt, run state, installer behavior, or `sync_docs.py` / CLI feature. + Key implementation files: - `../../../zh/skills/workflow-docs-sync/SKILL.md`: orchestration contract. diff --git a/tests/test_workflow_docs_sync.py b/tests/test_workflow_docs_sync.py index 16ade70..4782837 100644 --- a/tests/test_workflow_docs_sync.py +++ b/tests/test_workflow_docs_sync.py @@ -1146,7 +1146,36 @@ def test_scenario_5_repository_distribution_contract() -> None: f"{language}/{relative_path}" ) - # 固定文档锚点不得在 marker 清零后偷偷保留泛化测试结论。 + # Anchor 协议必须由 contract 单点发布。 + # 显式 null-test 缺口必须保留完整元数据。 + canonical_anchor = "" + anchor_marker = "capability-anchor:" + anchor_rule_fragments = { + "zh": ( + "大小写敏感", + "[A-Za-z0-9_.-]+", + "空白", + "JSON path", + ), + "en": ( + "case-sensitive", + "[A-Za-z0-9_.-]+", + "whitespace", + "JSON path", + ), + } + boundary_fragments = { + "zh": ( + "不受支持", + "不保证", + "不单独证明", + ), + "en": ( + "unsupported", + "does not guarantee", + "does not by itself prove", + ), + } instructional_metadata = ( "目标项目存在本地 alignment test 时,必须登记其真实测试锚点。", "The target project must register its own local alignment test when one exists.", @@ -1154,6 +1183,51 @@ def test_scenario_5_repository_distribution_contract() -> None: for language in ("zh", "en"): contract_path = REPO_ROOT / language / "capability_contract.json" contract = json.loads(s=contract_path.read_text(encoding="utf-8")) + assert contract["schema_version"] == "0.1.0" + rules_text = "\n".join(contract["rules"]) + anchor_rules = [ + rule for rule in contract["rules"] if canonical_anchor in rule + ] + assert len(anchor_rules) == 1 + assert all( + fragment in anchor_rules[0] + for fragment in anchor_rule_fragments[language] + ) + assert all( + fragment in rules_text for fragment in boundary_fragments[language] + ) + null_test_rules = [ + rule for rule in contract["rules"] if "test_anchor: null" in rule + ] + assert len(null_test_rules) == 1 + assert all( + field in null_test_rules[0] + for field in ("untested_reason", "pending_since") + ) + anchor_publishers = [ + relative_path + for relative_path in CORE_FILES + if anchor_marker + in (REPO_ROOT / language / relative_path).read_text( + encoding="utf-8" + ) + ] + assert anchor_publishers == ["capability_contract.json"] + assert rules_text.count(canonical_anchor) == 1 + assert rules_text.count(anchor_marker) == 1 + + explicit_null_entries = [ + entry + for entries in contract["contracts"].values() + for entry in entries + if "test_anchor" in entry and entry["test_anchor"] is None + ] + assert explicit_null_entries + for entry in explicit_null_entries: + for field in ("untested_reason", "pending_since"): + assert isinstance(entry[field], str) and entry[field].strip() + + # 固定文档锚点不得在 marker 清零后偷偷保留泛化测试结论。 projectized = replace_active_markers(value=contract) serialized = json.dumps(obj=projectized, ensure_ascii=False) assert not any(marker in serialized for marker in ACTIVE_MARKERS) @@ -1166,8 +1240,116 @@ def test_scenario_5_repository_distribution_contract() -> None: "test_anchor", "test_status", "untested_reason", + "pending_since", } & set(by_anchor[anchor]) + # 测试、review 和两轮 eval 直接保护风险,不复制 anchor 定义或 + # 增加 parser。 + downstream_contract_fragments = { + "zh": { + "TESTING.md": ( + "仅为满足模板而创建 wrapper", + "可安全、确定性复现的 escaped bug", + "无法先建立失败测试", + "无 test diff", + "纯内部重构", + "文档-only gate", + ), + "PR_Checklist.md": ( + "TESTING.md` 第 4 节", + "contract-defined protocol", + "REOPENED finding", + "SUPERSEDED candidate", + "长期规则", + ), + ".github/pull_request_template.md": ( + "first-seen", + "REOPENED", + "SUPERSEDED", + "hypothesis", + "unknown", + "Promoted reusable rule", + ), + }, + "en": { + "TESTING.md": ( + "do not create a wrapper only to satisfy this template", + "safely and deterministically reproducible escaped bug", + "cannot establish a failing test first", + "no test diff", + "behavior-preserving internal refactor", + "documentation-only gate", + ), + "PR_Checklist.md": ( + "TESTING.md` section 4", + "contract-defined protocol", + "REOPENED findings", + "SUPERSEDED candidates", + "long-term rule", + ), + ".github/pull_request_template.md": ( + "first-seen", + "REOPENED", + "SUPERSEDED", + "hypothesis", + "unknown", + "Promoted reusable rule", + ), + }, + } + for language, files in downstream_contract_fragments.items(): + for relative_path, fragments in files.items(): + text = (REPO_ROOT / language / relative_path).read_text( + encoding="utf-8" + ) + assert canonical_anchor not in text + assert all(fragment in text for fragment in fragments) + + skill_text = (SKILL_ROOT / "SKILL.md").read_text(encoding="utf-8") + assert canonical_anchor not in skill_text + + decisions = ( + REPO_ROOT / "zh/docs/development_workflow/decisions.md" + ).read_text(encoding="utf-8") + assert "## DEC-007" in decisions + assert "- 状态:accepted" in decisions.split("## DEC-007", maxsplit=1)[1] + for fragment in ( + "描述性事实", + "规范性政策", + "个人/会话偏好", + "schema_version", + "PASS_NOOP", + "不修改 `sync_docs.py`", + ): + assert fragment in decisions.split("## DEC-007", maxsplit=1)[1] + + eval_contract = (SKILL_ROOT / "evals/README.md").read_text( + encoding="utf-8" + ) + for fragment in ( + "三条当前 scoped normative policy", + "ENTRY_STATUSES={active, deprecated}", + "check_capability_contract_alignment.py --base-ref", + "PASS_NOOP", + "ROUND1_INCOMPLETE", + "ROUND2_DRIFT", + ): + assert fragment in eval_contract + + english_workflow = ( + REPO_ROOT / "en/docs/development_workflow/README.md" + ).read_text(encoding="utf-8") + for fragment in ( + "DEC-007 Summary", + "descriptive facts", + "normative policies", + "REOPENED", + "PASS_NOOP", + "ROUND1_INCOMPLETE", + "ROUND2_DRIFT", + ): + assert fragment in english_workflow + # 机械兜底只覆盖本次真实误植过的八份下游模板与五个精确 token。 internal_context_tokens = ( "disposable clone", diff --git a/zh/.github/pull_request_template.md b/zh/.github/pull_request_template.md index ea6bfdd..dfad429 100644 --- a/zh/.github/pull_request_template.md +++ b/zh/.github/pull_request_template.md @@ -34,7 +34,7 @@ ## 7. Review / Fix Record - + ## 8. Known Limits, Open Decisions, and Rollback diff --git a/zh/AGENTS.md b/zh/AGENTS.md index 608e2fb..67c0422 100644 --- a/zh/AGENTS.md +++ b/zh/AGENTS.md @@ -82,4 +82,4 @@ ## Project-specific Conventions - + diff --git a/zh/PR_Checklist.md b/zh/PR_Checklist.md index 4583391..fce9a16 100644 --- a/zh/PR_Checklist.md +++ b/zh/PR_Checklist.md @@ -16,6 +16,7 @@ ## Tests and Evidence - [ ] 按 `TESTING.md` 和当前仓库配置选择真实命令,没有从模板猜测 runner 或服务。 +- [ ] 新增或不新增测试的决策符合 `TESTING.md` 第 4 节;无 test diff 时有具体覆盖测试和重跑证据。 - [ ] 每条测试记录 exact command、scope、result、not-run reason、实际环境和隔离方式。 - [ ] 环境选择与命令副作用、CI 能力和项目政策一致;写入、外部状态、残留和清理结果均有 可核对记录。 @@ -28,6 +29,8 @@ no-update reason,不要求为了齐全而修改所有文档。 - [ ] 能力变化遵循 `capability_contract.json → interact.md → business_user_guide.md` 的权威 方向;用户可见声明有当前实现或测试证据和稳定 anchor。 +- [ ] Markdown anchor 按 capability contract 的 contract-defined protocol 引用,没有在本文复写 + token;alignment 结论没有冒充句子级绑定或能力语义证明。 - [ ] 架构影响已核对入口、模块边界、数据流、状态、错误模型、外部依赖、artifact 和副作用。 - [ ] 所有 active project-fill marker 已替换或删除,Markdown 与 JSON 仍可被严格解析。 @@ -36,6 +39,10 @@ - [ ] 已完成本项目测试与交付政策要求的 review gate,并准确记录 reviewer 身份、范围和限制。 - [ ] 所有 BLOCKER 和不需要新产品决策的 actionable WARN 已修复并复核;其余问题进入 open decisions,包含证据与影响。 +- [ ] Material rounds、REOPENED finding 与 SUPERSEDED candidate/evidence 均保留;重开沿用原 + finding ID,finding 状态没有与证据状态混用。 +- [ ] 上一轮漏检原因有证据,或明确标为 `hypothesis` / `unknown`,没有为填字段编造因果。 +- [ ] 每个 material finding 已评估是否提升为长期规则或自动化 gate;未提升时记录原因。 - [ ] 修复后重跑受影响测试和机械检查,最终 diff 与 Git 状态已再次检查。 ## PR Delivery diff --git a/zh/TESTING.md b/zh/TESTING.md index efaa6ef..3ed0745 100644 --- a/zh/TESTING.md +++ b/zh/TESTING.md @@ -4,6 +4,9 @@ 所有 exact command 必须从当前仓库的脚本、任务配置、CI、构建文件或测试框架配置中提取, 并在仓库根目录或明确记录的工作目录验证。不得从本模板猜测语言、runner、服务或阶段名。 +已有经过验证的 repository-owned 统一入口时优先使用,并记录它实际覆盖的范围;不存在时不应 +仅为满足模板而创建 wrapper。只有多命令编排、服务生命周期或清理步骤长期重复并形成独立 +维护收益时,才把 wrapper 作为单独项目改动审查。 @@ -30,16 +33,26 @@ ## 3. Capability Contract Alignment alignment test 属于目标项目自己的测试套件,不属于文档同步 checker。它应在本地递归收集 -`capability_contract.json` 中所有对象的稳定 `anchor_id`,检查唯一性和 Markdown 引用, +`capability_contract.json` 中所有对象的稳定 `anchor_id`,并按 contract rules 定义的协议检查 +唯一性和 Markdown 引用, 但不硬编码 bucket、JSON path、数组位置或要求所有 contract 条目进入 business guide。 -无法自动化的声明使用 `test_anchor: null` 并记录具体原因;已有测试时登记真实测试锚点。 -文档声明 alignment test 存在之前,必须确认目标仓库确有对应测试实现和可执行命令。 +显式使用 `test_anchor: null` 时同时记录非空、具体的 `untested_reason` 和非空 `pending_since`; +已有测试时登记真实测试锚点。Anchor alignment 只证明结构引用、合法 ID 和非悬空等机械事实, +不单独证明句子级绑定或声明的业务语义已经实现。文档声明 alignment test 存在之前,必须确认 +目标仓库确有对应测试实现和可执行命令。 ## 4. Change Type to Required Evidence +1. 可安全、确定性复现的 escaped bug:先建立修复前失败的最小回归测试或 fixture,再改实现。 +2. 无法先建立失败测试:保留修复前失败证据,说明无法稳定自动化的原因和剩余风险。 +3. 用户可观察行为、公开契约或 schema 变化:默认新增或修改最近边界的 contract/scenario 测试。 +4. 无 test diff:指出具体已有测试如何覆盖本次新风险并提供重跑证据;“已有高层测试”不充分。 +5. 纯内部重构且行为不变:可不新增测试,但须重跑受影响路径并记录 no-test-change reason。 +6. 文档-only gate:只证明实际检查的结构、解析或 alignment 范围,不得冒充运行时行为验证。 + ## 5. Side Effects and Isolation @@ -65,5 +78,7 @@ alignment test 属于目标项目自己的测试套件,不属于文档同步 c 只记录真实缺陷暴露出的可复用测试决策规则,不保存事故编年史或易漂移命令。若一次事故 来自“各层单独通过但组合失败”,同时保留最小回归测试与覆盖真实边界的 scenario 测试。 +同一失效模式合并为更一般的规则;只有知识被更强测试、自动化 gate 或权威规则完整接管时才 +退役,不能只因案例变旧而删除。 diff --git a/zh/capability_contract.json b/zh/capability_contract.json index 15f7df6..5419219 100644 --- a/zh/capability_contract.json +++ b/zh/capability_contract.json @@ -7,9 +7,11 @@ "每个填充项必须在验证前替换为已核实的项目内容,或从契约中删除。", "每条 active 声明必须有当前实现、配置、测试、committed artifacts 或可重复运行结果作为证据。", "future 或 proposed 行为必须使用明确的非 active 状态,不得呈现为当前可用能力。", - "Markdown 文档只引用稳定的 anchor_id,不引用 JSON path、数组位置或 schema 内部路径。", + "Markdown 文档只用 `` 引用稳定 anchor_id;marker 名称与 ANCHOR_ID 大小写敏感,ANCHOR_ID 必须匹配 `[A-Za-z0-9_.-]+`;目标 checker 可容忍 comment 内空白差异,但不得用 JSON path、数组位置或 schema 内部路径代替 anchor ID。", + "其他 anchor、ref、contract 或 wiki link 形式不受支持,不能建立 alignment 引用;本通用协议不保证任何消费者会穷举发现或拒绝未知 alias。", + "Anchor 引用只证明结构上引用了已登记 ID,不单独证明句子级绑定、业务语义或实现已完成。", "deprecated anchor 必须保留原始语义登记,且不得复用于其他含义。", - "没有自动化覆盖的契约使用 test_anchor: null,并记录具体 untested_reason 或 pending_since。" + "显式使用 test_anchor: null 的契约必须同时记录非空、具体的 untested_reason 与非空 pending_since;这只登记覆盖缺口,不表示声明已经验证。" ], "contracts": { "capabilities": [ @@ -24,7 +26,7 @@ "test_anchor": null, "test_status": "not_automated", "untested_reason": "__PROJECT_FILL__: 替换为缺少自动化的原因;如已有测试则填写 test_anchor 并删除本字段", - "pending_since": "__PROJECT_FILL__: 替换为适用日期,或删除本字段", + "pending_since": "__PROJECT_FILL__: 替换为覆盖缺口登记日期;test_anchor 为 null 时必须保留本字段", "since": "__PROJECT_FILL__: 替换为首次验证的版本或日期" } ], @@ -40,7 +42,7 @@ "test_anchor": null, "test_status": "not_automated", "untested_reason": "__PROJECT_FILL__: 替换为缺少自动化的原因;如已有测试则填写 test_anchor 并删除本字段", - "pending_since": "__PROJECT_FILL__: 替换为适用日期,或删除本字段", + "pending_since": "__PROJECT_FILL__: 替换为覆盖缺口登记日期;test_anchor 为 null 时必须保留本字段", "since": "__PROJECT_FILL__: 替换为首次验证的版本或日期" } ], @@ -56,7 +58,7 @@ "test_anchor": null, "test_status": "not_automated", "untested_reason": "__PROJECT_FILL__: 替换为缺少自动化的原因;如已有测试则填写 test_anchor 并删除本字段", - "pending_since": "__PROJECT_FILL__: 替换为适用日期,或删除本字段", + "pending_since": "__PROJECT_FILL__: 替换为覆盖缺口登记日期;test_anchor 为 null 时必须保留本字段", "since": "__PROJECT_FILL__: 替换为首次验证的版本或日期" } ], @@ -72,7 +74,7 @@ "test_anchor": null, "test_status": "not_automated", "untested_reason": "__PROJECT_FILL__: 替换为缺少自动化的原因;如已有测试则填写 test_anchor 并删除本字段", - "pending_since": "__PROJECT_FILL__: 替换为适用日期,或删除本字段", + "pending_since": "__PROJECT_FILL__: 替换为覆盖缺口登记日期;test_anchor 为 null 时必须保留本字段", "since": "__PROJECT_FILL__: 替换为首次验证的版本或日期" } ], diff --git a/zh/docs/development_workflow/decisions.md b/zh/docs/development_workflow/decisions.md index 652599e..6522a2d 100644 --- a/zh/docs/development_workflow/decisions.md +++ b/zh/docs/development_workflow/decisions.md @@ -133,3 +133,38 @@ Case A 对“部分过时旧文档、共同虚构能力、验证层级膨胀” “已验证不存在”或“检测能力已验证”。 - 英文状态:本决策对应的对外说明已同步到英文 README 与 development workflow。 + +## DEC-007:政策证据、Anchor 协议与两轮严格收敛 + +- 状态:accepted +- 日期:2026-08-03 UTC +- 知识分类:描述性事实必须由当前代码、配置、测试、committed artifacts 或可重复运行结果 + 支持;规范性政策可由 scoped repository instruction、accepted decision、团队/项目配置或已 + 持久化的 owner decision 支持;个人/会话偏好默认不进入下游项目。混合语句拆分核验,禁止 + 把事实改写成政策或把政策伪装成实现事实。 +- 政策权威:机器配置证明 enforcement,不自动 supersede 规范意图。配置与政策冲突是治理 + 漂移,必须形成 finding/open decision;同类政策按显式 supersession 和既有 authority/scope + 规则裁决。权威仍不明确时保留原文和已检查来源,不静默删除或永久神圣化。 +- 持久化边界:当前 task instruction 只授权当前任务;只有 owner 明确采纳为长期项目政策并在 + 同一变更中写入 repository authority 或 accepted decision 后,才可跨轮保留。 +- Anchor 协议:canonical authoring form、大小写、ID grammar 和 whitespace tolerance 只在 + `capability_contract.json.rules` 定义。其他形式不受支持,但当前 SEC consumer/checker 不承诺 + 穷举拒绝未知 alias;结构引用不单独证明句子级绑定或业务语义。显式 `test_anchor: null` 同时 + 要求非空、具体的 `untested_reason` 和非空 `pending_since`,只表示覆盖缺口已登记。 +- 版本语义:`schema_version` 继续为 `0.1.0`,只表示 JSON shape 和机器必需字段;纯 authoring + prose 规则变化不升级该字段,未来 shape 或机器必需字段变化再按版本策略处理。 +- 测试决策:跨项目模板保留 escaped-bug、无法先红测、公开契约、无 test diff、纯重构和 + documentation-only gate 的最低证据规则,并由 PR checklist 执行。一个协议只有一个定义点。 +- Finding 闭环:finding 使用 stable ID、severity、first-seen 和 OPEN / CLOSED / DEFERRED; + REOPENED 是保留原 ID 的事件。CURRENT / SUPERSEDED 只描述 candidate/evidence。漏检原因必须 + evidence-backed、标为 hypothesis 或写 unknown;closure 时评估可复用规则提升。 +- Case A:同一 target code/config/test/committed-artifact base、upstream candidate、language 和 + round 1 最终九文档 bytes 下,round 2 只接受 `PASS_NOOP`。冻结事实支持的新增修正为 + `ROUND1_INCOMPLETE`,无新增反证的表达漂移为 `ROUND2_DRIFT`;两者都使 gate 失败,并要求从 + clean target 重跑完整两轮。外部状态或 identity 变化使证据失效,不构成 PASS 例外。 +- 与 DEC-006 的关系:本决策 refine 其 Case A 两轮收敛语义,并补充政策证据、Anchor publisher + 和测试/finding 合同;不恢复 template equality、旧 token blacklist、固定 Agent 拓扑或代理 + parser。 +- 实现边界:不修改 `sync_docs.py`、installer 或 CLI schema,不新增 anchor/policy/Markdown + parser、repository ledger、receipt、run state 或 claim-level binding。 +- 英文状态:双语下游模板已等价同步;canonical Skill、eval 与决策继续以中文路径为权威。 diff --git a/zh/skills/workflow-docs-sync/SKILL.md b/zh/skills/workflow-docs-sync/SKILL.md index f69de2d..028455a 100644 --- a/zh/skills/workflow-docs-sync/SKILL.md +++ b/zh/skills/workflow-docs-sync/SKILL.md @@ -46,6 +46,23 @@ UTF-8、八份非 PR source 至少含一个 active marker;随后只创建缺 committed artifacts、可重复运行结果和必要 Git 历史;不得用旧文档证明旧文档正确,也不得 因为内容未出现在近期 diff 中而跳过。 +按语义而非句式分类待核对声明: + +- 描述性事实说明当前状态、能力、命令、职责、行为、副作用、交付状态或证据强度,必须由 + 当前代码、配置、测试、committed artifacts 或可重复运行结果支持。 +- 规范性政策规定开发者或 Agent 应如何工作,可由当前 scoped repository instruction、accepted + decision、团队/项目配置,或已明确采纳并持久化的 owner decision 支持。 +- 个人或当前会话偏好默认不属于目标项目;只有 owner 明确采纳为长期政策并在同一变更中写入 + repository authority 或 accepted decision 后才可持久化。 +- 混合语句拆分规范意图与现状断言,事实部分仍按描述性事实核验。不得把事实改写成政策来 + 规避证据,也不得把政策写成实现事实。 + +当前 tracked、scoped 治理入口可推定为 active policy authority;明显模板残留、个人偏好、历史 +说明、失效状态或冲突仍须形成 finding。机器配置证明 enforcement,不自动 supersede 规范意图; +两者冲突时登记 finding/open decision,既不破坏硬 gate,也不静默选边。同类政策先按显式 +supersession 和现有 authority/scope 规则裁决;仍有歧义时保留原文并记录已检查来源。对 material +policy 的保留、修改或 no-update reason 必须记录 authority source、scope 和冲突检查结果。 + 确认文档问题后、任何语义编辑前,先在会话中登记 finding:唯一 ID、`BLOCKER` / `WARN` / `NOTE`、代码/配置/测试或 artifact 证据、风险和最小修复边界。不得在编辑完成后根据最终 diff 或 reviewer 结论倒填为“写入前 finding”。同一根因、同一证据链和同一修复边界影响多份文档 @@ -53,7 +70,9 @@ committed artifacts、可重复运行结果和必要 Git 历史;不得用旧 必须在编辑前登记 `BLOCKER`。 - 全量质疑九份文档,只改错误、缺失、失真或失效内容;正确内容保持零 diff。 -- 删除没有当前事实证据的强声明。需要产品判断时记录 open decision,不编造结论。 +- 删除、收窄或降级没有当前证据的描述性强声明;规范性政策按政策权威、作用域和冲突处理。 + 不得把事实改写为政策以规避证据,也不得把政策伪装成实现事实。需要产品判断时记录 open + decision,不编造结论。 - 保持 `capability_contract.json → interact.md → docs/business_user_guide.md` 的权威方向。 - 清除所有 active project-fill marker;不要使用固定写入顺序、完成百分比、KEEP ledger 或 过程状态。 diff --git a/zh/skills/workflow-docs-sync/evals/README.md b/zh/skills/workflow-docs-sync/evals/README.md index 586fb54..b8e0065 100644 --- a/zh/skills/workflow-docs-sync/evals/README.md +++ b/zh/skills/workflow-docs-sync/evals/README.md @@ -12,7 +12,7 @@ receipt、scratch 或 PR body。 `AGENTS.md` 与根 README 导航到中文 maintainer map,并准确找到: - 双语下游模板源及中文语义源; -- canonical Skill、安装器、测试、README、development workflow 与 DEC-006; +- canonical Skill、安装器、测试、README、development workflow、DEC-006 与 DEC-007; - 根 `.github/` 与 `zh/.github/`、`en/.github/` 的不同职责; - 最短测试入口、`py_compile`、Skill quick validation、`git diff --check` 和 CLI help。 @@ -23,35 +23,76 @@ receipt、scratch 或 PR body。 ## Case A:SEC_metrics 真实端到端 从 SEC_metrics 当前真实 Git HEAD 建立隔离目标,按项目命令的副作用与政策选择执行环境;不 -复用旧 eval 结论或旧 shadow 文件。第一次运行 `$workflow-docs-sync` 时: +复用旧 eval 结论或旧 shadow 文件。两轮固定同一 SEC code/config/test/committed-artifact base、 +候选 upstream SHA、language 和 round 1 最终九文档 bytes;live 外部状态或任一 identity 变化都 +使证据失效,必须重新冻结并从 clean target 重跑。 + +### Round 1 mandatory checks + +第一次运行 `$workflow-docs-sync` 时: - 从当前代码、配置、测试和 committed artifacts 重建 pipeline,专门核对 stage 10、11、12 的输入、写入副作用、容错参数和 hard failure; +- 编辑前从真实目标选择并记录三条当前 scoped normative policy(例如 fail-fast、具体异常、 + 显式数据契约)和两条描述性强声明;逐条记录 classification、authority/evidence、scope、 + conflict check 和 expected disposition,不注入人工 fixture; +- 静默删除已分类政策,或把描述性事实改写成祈使句来逃避证据核验,均为 FAIL;修改、保留或 + 不更新 material policy 都记录 authority source; - 主动寻找部分过时旧文档; - 主动寻找多份文档彼此一致、但代码和测试不存在的共同虚构能力; - 主动检查 light、golden、repair validation 是否被写成 full validation; - 没有当前部署配置和运行证据时,不得声称生产调度完成; +- 对照 contract publisher 的 canonical grammar 与 SEC checker 真实 regex,记录 consumer 只 + 支持 canonical form、不穷举 alias;记录 SEC 的 `ENTRY_STATUSES={active, deprecated}` 是项目 + 侧加严,不要求通用模板复制该词表; +- 核对目标 `TESTING.md` 的 bug-first 与 no-test-diff 规则,正确且更具体的内容保持零 diff; - 运行项目真实测试,准确记录验证层级,并完成 review 与最终 `check`。 这些检查是现实抽样。所选 HEAD 初始状态不含相应缺陷时,只能报告“本次未观察到”,不得写 成“已验证不存在”或“检测能力已验证”。若观察到缺陷,记录代码/测试反证、文档声明、修改和 复核结果。 -在同一候选文档上立即进行第二次完整运行。重新调查和检查,但不得依赖第一次的过程说明; -记录前后九份文件 bytes、Git diff 与测试选择。预期是 no-op;只有新增项目事实要求的最小 -改写才可接受,并必须说明触发证据。任何无事实依据的格式抖动、重复改写或新增过程状态均 -失败。 +### Round 2 strict convergence + +以 round 1 最终九文档 bytes 为输入立即进行第二次完整运行,不依赖第一次过程说明,重新调查、 +选择测试、review 并运行最终 `check`。记录前后 bytes、Git diff、测试选择、普通/暂存/untracked/ +ignored 状态,并按以下唯一判定收口: + +- `PASS_NOOP`:九文档相对 round 1 最终候选零 diff,且无 staged、untracked 或 ignored residue。 +- `ROUND1_INCOMPLETE`:round 2 根据冻结目标中原已存在的代码、配置、测试或 artifact 反证发现 + 有效新增修正;说明 round 1 调查不完整,整个两轮 gate FAIL。 +- `ROUND2_DRIFT`:只有措辞、排序、格式或偏好变化,没有新增反证;说明最小改写不稳定,整个 + 两轮 gate FAIL。 + +后两种结果都必须先修复候选或调查协议,再从 clean target 重跑完整 round 1 + round 2;不得 +只补第三轮覆盖早期失败。第二次 `prepare` / `check` no-op 只能作为幂等性附加证据,不能代替 +第二次完整 Case A。 + +### Alignment consumer validation + +SEC checker 要求 evidence path 与 committed HEAD 一致。为不改变 primary Case A target identity: + +1. round 2 获得 `PASS_NOOP` 后冻结九文档 bytes 与 digest; +2. 从同一 SEC base 创建第二个 disposable validation checkout; +3. 应用完全相同的九文档候选并创建 test-only commit; +4. 运行 `python3 tools/check_capability_contract_alignment.py --base-ref `; +5. 记录 derived validation commit、publisher/consumer、exact command、结果、结构证明边界和 + cleanup;不得把 derived commit 冒充 primary target identity 或发布 commit。 Case A 最终记录包含: - SEC_metrics target SHA、候选 upstream SHA 与 language,且两轮固定为同一对身份; - 每轮的 exact prompt、实际 review mode 和 exact commands; +- independent reviewer 的会话/线程身份、启动时间与认知隔离边界;不可用时明确记录 + self-review,不得冒充 independent; - 第一次运行的 files changed / digest、完整文档 diff、测试选择与结果、review findings 和原始 final-check JSON; - 第二次完整重新调查后的 files changed / digest、完整 diff、重新选择的测试与结果、review findings 和原始 final-check JSON; - 对“部分过时旧文档、共同虚构能力、验证层级膨胀”的逐项结果:已观察到并检出,或本次未 观察到; +- policy classification table、Anchor publisher/consumer/grammar/alias limitation、项目侧 status + 加严、derived alignment commit 和 cleanup; +- 正式 Case G/A raw record 的 candidate SHA、record URL 与内容 digest;最终 PASS 不覆盖早期 + failure、REOPENED 或 SUPERSEDED candidate/evidence; - 未运行项、open decisions 和剩余风险。 - -第二次 `prepare` / `check` no-op 只能作为幂等性附加证据,不能代替第二次完整 Case A。