From 62d7d4476c08c8d72419665c3c0e1a6b3ef1c0f9 Mon Sep 17 00:00:00 2001 From: yuqei Date: Sun, 20 Sep 2026 06:32:40 +0800 Subject: [PATCH] release: fix RC-04 manifest source-path validation --- .../rc04-immutable-manifest-tool-v3.md | 21 ++++++++++++++ .../rc04-release-manifest-tools-v3.xml | 1 + .../remaining-closure-register-20260919.json | 26 ++++++++++++----- ...台-多场景完整交付与开发收口方案-2026-09-10.md | 2 +- .../build-release-candidate-manifest.py | 20 ++++++++++++- .../build-release-worktree-ownership.py | 4 +-- .../test_release_candidate_manifest_tools.py | 29 ++++++++++++++++++- 7 files changed, 90 insertions(+), 13 deletions(-) create mode 100644 docs/qa/multiscene-closeout-20260910/rc04-immutable-manifest-tool-v3.md create mode 100644 docs/qa/multiscene-closeout-20260910/rc04-release-manifest-tools-v3.xml diff --git a/docs/qa/multiscene-closeout-20260910/rc04-immutable-manifest-tool-v3.md b/docs/qa/multiscene-closeout-20260910/rc04-immutable-manifest-tool-v3.md new file mode 100644 index 00000000..870de9e4 --- /dev/null +++ b/docs/qa/multiscene-closeout-20260910/rc04-immutable-manifest-tool-v3.md @@ -0,0 +1,21 @@ +# RC-04 不可变源码 Manifest 工具(阻断修复 v3) + +## 结果 + +首次冻结的 commit `2770d738185589c399c5740c4fc6cc25a4d65c2f` 与 tag `v2-multiscene-rc04-20260920.1` 已保持不可变,但 manifest 实跑失败关闭,因此该候选不得进入构建或安装。失败原因是路径规则把合法的密钥托管源码目录及 `secret_refs.py` 误判为密钥材料;没有发现实际密钥。 + +本轮只修复该发布工具阻断:名称含 `secret` 或 `credential` 的路径,只有在 `src`、`tests` 或 `migrations` 源码树内且后缀属于明确的代码类型时才可通过。`.env`、证书/私钥、keystore、`credentials.json`、`service-account.json`、运行库、缓存、本地二进制,以及非源码树中的 secret/credential 路径仍失败关闭。 + +## 验证 + +- 当前测试:**43/43 passed**,0 failures、0 errors、0 skipped; +- JUnit:`docs/qa/multiscene-closeout-20260910/rc04-release-manifest-tools-v3.xml`;SHA256 `cb6c0d78ed433c07c77cd8aca90af3714c5e4bb9ae5ca15e69d7cea054bcb70e`; +- manifest 工具 SHA256:`d542ce43459ff3c83e67d225cddf470feb76147ec8760715a4b3ce8a57f91421`; +- ownership 工具 SHA256:`d6b1e9cd24aa2f0ac4dfe32be300bae70850ce092d663ff8c19bdb3cea7e88bc`; +- 测试文件 SHA256:`66b97077c87fabb1313e096938c717cca87abcb5e122086ca195c67b0f695c2e`。 + +新增回归同时证明:密钥托管实现源码及测试路径允许进入候选;`deploy/secrets/production.yaml`、源码树内的 `secrets/production.json`、credential 目录文本和 `credentials.json` 继续被拒绝。既有干净 HEAD、完整 commit OID、仓库外输出、60 资产、14 Prompt、依赖 hash lock、迁移 head、基础镜像 digest 和 `.dockerignore` 断言继续通过。 + +## 当前边界 + +`.1` 只是保留的失败冻结点,不是可安装候选。必须以本修复形成新的递增 commit/tag,并对新提交重新生成完整 source manifest 与 archive。Linux 镜像、SBOM、漏洞扫描、启动健康检查及安装/回退包仍未完成;内部试点和公众开放继续保持 `NO_GO`。 diff --git a/docs/qa/multiscene-closeout-20260910/rc04-release-manifest-tools-v3.xml b/docs/qa/multiscene-closeout-20260910/rc04-release-manifest-tools-v3.xml new file mode 100644 index 00000000..2ce27e31 --- /dev/null +++ b/docs/qa/multiscene-closeout-20260910/rc04-release-manifest-tools-v3.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/qa/multiscene-closeout-20260910/remaining-closure-register-20260919.json b/docs/qa/multiscene-closeout-20260910/remaining-closure-register-20260919.json index 6b3c1baf..a082522f 100644 --- a/docs/qa/multiscene-closeout-20260910/remaining-closure-register-20260919.json +++ b/docs/qa/multiscene-closeout-20260910/remaining-closure-register-20260919.json @@ -1,7 +1,7 @@ { "schema_version": "multiscene-remaining-closure-register.v2", "captured_at": "2026-09-20T01:47:00+08:00", - "last_updated_at": "2026-09-20T06:10:00+08:00", + "last_updated_at": "2026-09-20T06:20:00+08:00", "scope": { "assets": 60, "core_assets": 8, @@ -99,7 +99,7 @@ "id": "worktree-ownership-pre-freeze-v2", "path": "external release package: rc04-worktree-ownership-reviewed-v2.json", "report_path": "external release package: rc04-worktree-ownership-reviewed-v2.md", - "result": "pre-freeze classification currently covers 9358 paths: source 325, tests 165, compact evidence 10, generated retain/exclude 8853 and local exclude 5; secret/runtime 0, manual review 0, include gaps 0, missing anchors 0 and ownership blockers 0; classifier and manifest tests 40/40", + "result": "the full pre-freeze review covered 9358 paths: source 325, tests 165, compact evidence 10, generated retain/exclude 8853 and local exclude 5; secret/runtime 0, manual review 0, include gaps 0, missing anchors 0 and ownership blockers 0; the first immutable attempt was rejected by a manifest path false positive and a minimal v3 rule fix is pending a new review", "boundary": "the reviewed snapshot is regenerated outside the repository after all RC-04 edits and immediately before exact path staging; it is not itself an immutable candidate" }, { @@ -150,6 +150,14 @@ "evidence": "docs/qa/multiscene-closeout-20260910/rc03-contract-sdk-openapi-permission-recovery-v4-invalid-c-temp.xml", "resumable": false, "final_acceptance_eligible": false + }, + { + "id": "rc04-source-candidate-1", + "commit": "2770d738185589c399c5740c4fc6cc25a4d65c2f", + "tag": "v2-multiscene-rc04-20260920.1", + "reason": "The source freeze itself is immutable, but the manifest failed closed because five legitimate secret-domain source paths were classified as secret material. No source manifest, image build, installation or deployment claim is accepted for this tag.", + "resumable": false, + "final_acceptance_eligible": false } ], "items": [ @@ -228,7 +236,7 @@ "id": "RC-04", "priority": "P0", "title": "Freeze one immutable installable release candidate", - "status": "ownership_repaired_pending_final_reviewed_commit_and_linux_artifacts", + "status": "rc04_1_rejected_manifest_path_false_positive_rc04_2_fix_pending", "dependencies": ["RC-03"], "completed": [ "Contract and AI Python hash locks", @@ -237,12 +245,14 @@ "Contract 38 and AI 41 dependency locks have zero currently known vulnerabilities in the pinned pip-audit 2.10.1 OSV scan", "Dockerfiles use only-binary and require-hashes, and their default Linux amd64 base images are pinned by manifest digest", "the root .dockerignore excludes local caches, dependency/build output, evidence documents, non-template environment files and runtime data from build contexts", - "ownership and deterministic manifest tooling implemented with the current 40/40 formal manifest-tool JUnit receipt", + "the full .1 candidate was frozen at commit 2770d738185589c399c5740c4fc6cc25a4d65c2f and tag v2-multiscene-rc04-20260920.1, then correctly rejected when the manifest path validator produced five false positives", + "the path validator now permits only explicit code suffixes under src/tests/migrations for secret-domain source names while continuing to reject secret material paths", + "the v3 ownership and manifest-tool regression is 43/43 passed with zero failure, error or skip", "the repaired pre-freeze draft closes secret/runtime blockers, manual review, include gaps and missing required anchors to zero while preserving exclusions rather than deleting user files" ], "open_findings": [ - "regenerate the external ownership snapshot after all RC-04 edits, verify its git porcelain fingerprint immediately before staging and independently review every include/exclude decision", - "reviewed unique commit, tag and source archive", + "independently review and exactly stage only the minimal .2 blocker fix", + "reviewed unique .2 commit, tag and source archive; .1 remains immutable but invalid", "Linux image builds, SBOM, image scans, health checks and immutable digests on a builder that does not write the database host's existing /var/lib/docker", "install, upgrade and rollback package bound to the exact candidate" ], @@ -250,8 +260,8 @@ "docs/qa/multiscene-closeout-20260910/rc04-python-dependency-lock-v3.md", "docs/qa/multiscene-closeout-20260910/rc04-python-dependency-lock-v3.json", "docs/qa/multiscene-closeout-20260910/rc03-current-candidate-evidence-index-v3.json", - "docs/qa/multiscene-closeout-20260910/rc04-immutable-manifest-tool-v2.md", - "docs/qa/multiscene-closeout-20260910/rc04-release-manifest-tools-v2.xml", + "docs/qa/multiscene-closeout-20260910/rc04-immutable-manifest-tool-v3.md", + "docs/qa/multiscene-closeout-20260910/rc04-release-manifest-tools-v3.xml", "external release package: rc04-worktree-ownership-reviewed-v2.json/.md" ], "exit": "A reviewed clean commit/tag and deterministic path/mode/size/SHA256 manifest close the dependency, OpenAPI, migration, config, runtime, 60-asset and 14-Prompt graph; a Linux x86_64 builder produces Web plus three Python images with base/image digests, SBOM, scans and health checks; install and rollback instructions are executable." diff --git a/project-docs/AI合同平台-多场景完整交付与开发收口方案-2026-09-10.md b/project-docs/AI合同平台-多场景完整交付与开发收口方案-2026-09-10.md index bc5b491b..f5eea969 100644 --- a/project-docs/AI合同平台-多场景完整交付与开发收口方案-2026-09-10.md +++ b/project-docs/AI合同平台-多场景完整交付与开发收口方案-2026-09-10.md @@ -407,7 +407,7 @@ Gate A 没有启动服务、访问实时数据库、调用真实模型、部署 | RC-01 已知代码与发布安全 | `local_same_candidate_closed_pending_rc06_target_reverification` | 已知 Contract API 身份伪造写入口均已修复,独立复扫未发现剩余 P0/P1;专项 26/26、actor/report 30/30,且已并入 54 文件当前候选组合。尚缺的是目标 PostgreSQL/反向代理上下文复验,不是新的 RC-01 源码 | RC-06 验证 PostgreSQL 与实际代理上下文。AI Platform 的 `X-User-Id` 只允许由持有 service key 的受信内部调用方断言;若未来允许不完全可信客户端直连,必须先升级为不可伪造的签名身份上下文 | | RC-02 产品 Golden | `technical_closed_current_source_pending_rc06_legal_publication` | fresh v4 当前源码技术 31/31、相关测试 32/32;仍为 0/31 法律批准,且没有 HTTP/SQL/线上发布 | 相关输入/hash 不变时复用 fresh v4;改变则只重跑受影响项。法律批准或内部限时风险接受、持久化发布和在线 hash 继续由 RC-06 处理,不伪造 | | RC-03 同候选确定性质量 | `closed_current_candidate` | Windows:synthetic 144/144、Python 1,210 pass+3 platform skip、扩展 319/319、Web 192/192、Golden 31/31+32/32、OpenAPI fresh/tool pass;Linux:同一 ledger 实现和测试字节的 output/ledger/attempts 3/3 pass、0 skip。补验后 452 source pins missing 0、mismatch 0,自包含 v3 聚合索引已绑定收据。失效的 synthetic v1~v4 C-TEMP 及两个 invalid-c-temp 仍单独保留,未拼接 | 当前字节 RC-03 退出条件已满足,严格转入 RC-04。若后续源码、安全实现、测试或冻结输入发生变化,则只作废并重跑受影响门禁,同时递增索引版本;不得改写原始 Windows/Linux JUnit | -| RC-04 不可变可安装候选 | `ownership_repaired_pending_final_reviewed_commit_and_linux_artifacts` | 独立审阅发现并关闭旧草案误纳入失败/过期证据的问题;当前预冻结归属覆盖 9,358 条路径:source 325、test 165、compact evidence 10、generated retain/exclude 8,853、local exclude 5,secret/runtime=0、manual=0、include gap=0、missing anchor=0、ownership blocker=0。manifest/ownership 当前测试 40/40 通过;四个 Dockerfile 默认基础镜像已固定到 Linux amd64 manifest digest,根 `.dockerignore` 已隔离本地缓存、文档、环境文件和运行态数据。Contract v3 38/38 与 AI 41 依赖锁及 0-known-vulnerability 审计已完成。仍无唯一 commit/tag/source archive、Linux 应用镜像、SBOM、scan/health、安装及回退包;数据库共址主机当前根盘 92% 且健康门禁失败,禁止直接使用默认 Docker/buildx 构建 | 所有 RC-04 文档修改完成后在仓库外重建最终 ownership 并独立复核;形成唯一 clean commit/tag 和 path/mode/size/SHA256 manifest。Linux 构建仅允许在独立 builder,或在维护窗口使用 `/home` 独立 BuildKit/containerd root、串行和严格资源限额,绝不写入既有 `/var/lib/docker`;随后记录 image/base digest、SBOM、扫描、import/startup/health 及安装/回退收据 | +| RC-04 不可变可安装候选 | `rc04_1_rejected_manifest_path_false_positive_rc04_2_fix_pending` | 已完成 9,358 条预冻结路径的独立审阅:500 纳入、8,858 保留排除,secret/runtime、manual、gap、missing anchor、ownership blocker 均为 0。`.1` commit `2770d738185589c399c5740c4fc6cc25a4d65c2f` / tag `v2-multiscene-rc04-20260920.1` 保持不可变,但 manifest 实跑把 5 个合法密钥托管源码路径误判成密钥材料,已失败关闭且不得构建。规则现收窄为只允许 `src/tests/migrations` 下的明确代码后缀,实际密钥材料仍拒绝;当前工具回归已以 v3 JUnit 固定,43/43 通过。四个 Dockerfile 基础镜像已固定到 Linux amd64 manifest digest,根 `.dockerignore` 已隔离本地缓存、文档、环境文件和运行态数据;Contract 38 与 AI 41 依赖锁的 pinned audit 为 0 known vulnerability。数据库共址主机当前根盘 92%、健康门禁失败且有容器反复重启,禁止直接使用默认 Docker/buildx 构建 | 只纳入 manifest 规则修复、回归和状态证据,生成递增 `.2` commit/tag 后重新运行 source manifest/archive。Linux 构建仅允许在独立 builder,或在健康门禁恢复后的维护窗口使用 `/home` 独立 BuildKit/containerd root、串行和严格资源限额,绝不写入既有 `/var/lib/docker`;随后记录 image/base digest、SBOM、扫描、import/startup/health 及安装/回退收据 | | RC-05 真实模型质量与耗时 | `blocked_by_rc04_not_started_full_real_matrix` | 当前 RC 没有真实矩阵;历史真实尝试 2 次已计入,总预算最多剩 278 次;尚未验证最终候选 `codex_cli / gpt-5.6-sol / medium / 300 / fallback=false` | 冻结 144 个任务,代表探针属于矩阵首批而不是额外调用;基础 264 次,最多 14 次真实修复余量;0 unknown;失败/取消/降级/接管全进分母;关键主体、金额、日期、事实无静默丢失,正常链 schema/终态无阻断;按 operation 报 p50/p95/超时,未达三分钟只披露异步体验 | | RC-06 部署、发布与双工作区 UAT | `waiting_for_rc04_rc05_and_external_uat_inputs` | 未完成目标备份、两库 current、迁移、RC 镜像部署、60资产/14 Prompt 批次批准和 apply/hash;没有管理员+2 普通成员+2 工作区的完整证据;31 项资产仍为 `needs_legal_review` | 获得目标主机、备份/迁移/部署授权、发布负责人和两个普通账号;具名批次批准必须逐项记录法律批准,或仅对内部受控试点记录有 owner/期限/范围/补偿控制的风险接受和使用限制,未处置项不得进入 RC-07 内部 Go;每个资产走适用正常 API 链,8 核心走 UI;验证跨租户、取消/幂等/关页续接/worker restart/人工接管、下载及原稿不覆盖。法律文书不强迫走不适用的签署/撤回 | | RC-07 恢复与内部放行 | `waiting_for_rc06` | 旧恢复证据不绑定当前 RC;没有当前候选的隔离恢复、RPO/RTO、rollback/forward、操作包、证据索引和内部 Go/No-Go | 在隔离 namespace 恢复当前候选,实测 RPO/RTO并核验版本/hash/关键记录;完成 rollback/forward;交付安装、升级、运维、回退、发布清单、已知限制和证据索引;单独作出内部受控 Go/No-Go | diff --git a/yuqei-infra/scripts/build-release-candidate-manifest.py b/yuqei-infra/scripts/build-release-candidate-manifest.py index 79e6f6ec..6e02e67c 100644 --- a/yuqei-infra/scripts/build-release-candidate-manifest.py +++ b/yuqei-infra/scripts/build-release-candidate-manifest.py @@ -86,6 +86,19 @@ REQUIRED_PREFIXES = ( "yuqei-ai-platform/services/ai-platform-api/migrations/versions/", "yuqei-contract-platform/apps/contract-web/src/app/api/", ) +SECRET_NAMED_SOURCE_SUFFIXES = { + ".cjs", + ".go", + ".java", + ".js", + ".mjs", + ".py", + ".pyi", + ".rs", + ".ts", + ".tsx", +} +SOURCE_TREE_COMPONENTS = {"src", "tests", "migrations"} class ManifestError(RuntimeError): @@ -208,7 +221,12 @@ def forbidden_candidate_path(path: str) -> str | None: if (name == ".env" or name.startswith(".env.")) and not safe_template: return "non_template_environment_file" if re.search(r"(?:^|/)(?:secrets?|credentials?)(?:[._-]|/)", lowered) and not safe_template: - return "secret_like_path" + source_named_for_secret_domain = ( + pure.suffix.casefold() in SECRET_NAMED_SOURCE_SUFFIXES + and bool(SOURCE_TREE_COMPONENTS.intersection(parts)) + ) + if not source_named_for_secret_domain: + return "secret_like_path" return None diff --git a/yuqei-infra/scripts/build-release-worktree-ownership.py b/yuqei-infra/scripts/build-release-worktree-ownership.py index 1b84b2b4..b2467f30 100644 --- a/yuqei-infra/scripts/build-release-worktree-ownership.py +++ b/yuqei-infra/scripts/build-release-worktree-ownership.py @@ -40,8 +40,8 @@ COMPACT_EVIDENCE_NAMES = { "remaining-closure-register-20260919.json", "rc03-current-candidate-evidence-index-v3.json", "rc03-current-candidate-evidence-index-v3.md", - "rc04-immutable-manifest-tool-v2.md", - "rc04-release-manifest-tools-v2.xml", + "rc04-immutable-manifest-tool-v3.md", + "rc04-release-manifest-tools-v3.xml", } COMPACT_SUBPATHS: set[str] = set() RUNTIME_COMPONENTS = { diff --git a/yuqei-infra/tests/test_release_candidate_manifest_tools.py b/yuqei-infra/tests/test_release_candidate_manifest_tools.py index 0ba8103d..5f592c2c 100644 --- a/yuqei-infra/tests/test_release_candidate_manifest_tools.py +++ b/yuqei-infra/tests/test_release_candidate_manifest_tools.py @@ -240,10 +240,18 @@ RUN python -c "import platform; name, version = platform.libc_ver(); minimum=(2, ), ( "docs/qa/multiscene-closeout-20260910/rc04-immutable-manifest-tool-v2.md", - "release_evidence_compact_include", + "generated_evidence_retain_exclude", ), ( "docs/qa/multiscene-closeout-20260910/rc04-release-manifest-tools-v2.xml", + "generated_evidence_retain_exclude", + ), + ( + "docs/qa/multiscene-closeout-20260910/rc04-immutable-manifest-tool-v3.md", + "release_evidence_compact_include", + ), + ( + "docs/qa/multiscene-closeout-20260910/rc04-release-manifest-tools-v3.xml", "release_evidence_compact_include", ), ( @@ -395,3 +403,22 @@ def test_manifest_refuses_ambiguous_dirty_missing_and_secret_candidates(tmp_path secret_commit = _git(repo, "rev-parse", "HEAD") with pytest.raises(manifest.ManifestError, match="non_template_environment_file"): manifest.build_manifest(repo, secret_commit, external_output) + + +def test_manifest_distinguishes_secret_domain_source_from_secret_material() -> None: + safe_source_paths = ( + "yuqei-ai-platform/services/ai-platform-api/src/yuqei_ai_platform_api/secret_refs.py", + "yuqei-contract-platform/apps/contract-web/src/app/api/secret-custody/status/route.ts", + "yuqei-contract-platform/services/contract-api/tests/test_secret_custody.py", + ) + for path in safe_source_paths: + assert manifest.forbidden_candidate_path(path) is None + + blocked_material_paths = ( + "deploy/secrets/production.yaml", + "yuqei-contract-platform/services/contract-api/src/secrets/production.json", + "credentials/service-account.txt", + "credentials.json", + ) + for path in blocked_material_paths: + assert manifest.forbidden_candidate_path(path) in {"secret_like_path", "credential_path"}