release: fix RC-04 clean-checkout evidence validation
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
# RC-04 干净 checkout 与跨平台字节边界修复 v6
|
||||
|
||||
## 结论
|
||||
|
||||
`.3` 增量修复的开发工作区回归为 43/43 通过,0 failure/error/skip。另在从 `.2` commit 新建的分离工作树中,仅覆盖拟纳入 `.3` 的 6 个文件,不带任何原始 QA 未跟踪输出,同一 43 项也已通过。
|
||||
|
||||
这仍是预提交证据。最终接受必须等 `.3` commit/tag 冻结后,在新的干净 detached checkout 重跑 43 项;通过后才能生成 `.3` source manifest/archive。
|
||||
|
||||
## 失效候选
|
||||
|
||||
- `.1`:`2770d738185589c399c5740c4fc6cc25a4d65c2f` / `v2-multiscene-rc04-20260920.1`,manifest 规则把 5 个合法 secret-custody 领域源码误判为密钥材料。
|
||||
- `.2`:`62d7d4476c08c8d72419665c3c0e1a6b3ef1c0f9` / `v2-multiscene-rc04-20260920.2`,冻结后干净 checkout 回归 43 项中 1 项失败,暴露出原始 QA 输出隐式依赖。
|
||||
- `.1` / `.2` 标签保持不可变,不移动、不重写、不构建、不部署。
|
||||
|
||||
`.2` 失败 JUnit 在外部发布证据目录保留:43 tests / 1 failure / 0 error / 0 skipped,SHA-256 `a7c63342acce1ed098db855853ad921cbde1668f95d849a016a9bbe8dca3f561`。
|
||||
|
||||
## 根因与修复
|
||||
|
||||
### 1. 外置原始证据
|
||||
|
||||
RC-03 v3 紧凑索引保留 50 项当前证据和 4 项已排除无效 lineage 的路径、大小和哈希。Ownership 规则将高基数原始 QA 输出保留在不可变发布源码之外;原测试却无条件打开这些工作区路径,因而被脏工作区掩盖。
|
||||
|
||||
修复后:
|
||||
|
||||
- `release_source_include` / `release_test_include` 路径仍必须存在并验证;
|
||||
- `generated_evidence_retain_exclude` 在干净发布 checkout 中允许不存在;
|
||||
- 它们仍存在于开发工作区时,继续按记录大小、SHA-256 和 JSON 可解析性复验。
|
||||
|
||||
### 2. Windows checkout 换行
|
||||
|
||||
预提交干净副本又发现,Windows checkout 可将文本 Git blob 的 LF 物化为 CRLF。RC-03 索引本身来自工作区字节,其 OpenAPI 是 CRLF 记录,requirements 是 LF 记录;单纯比较某个 checkout 的原始字节不具跨平台性。
|
||||
|
||||
修复后,已纳入发布的文本路径从 `HEAD:<path>` 读取不可变 Git blob,仅允许同一 blob 的精确 LF/CRLF 物化与索引大小/哈希匹配。候选 source manifest 和 archive 仍严格绑定 Git blob 原始字节,没有放宽为任意文本变化。
|
||||
|
||||
## 安全边界未改变
|
||||
|
||||
`.env*`、私钥/keystore、`credentials.json`、`service-account.json`、非源码 secrets/credentials 以及源码树中 secret JSON/pyc/db 仍失败关闭。只有 `src/tests/migrations` 下的明确代码后缀才能作为 secret-domain 源码放行。
|
||||
|
||||
## 当前回归
|
||||
|
||||
- JUnit:`rc04-release-manifest-tools-v6.xml`
|
||||
- Tests:43
|
||||
- Failures / errors / skipped:0 / 0 / 0
|
||||
- JUnit SHA-256:`8d5aaf2f15ae4643d43608e72315022620a3ff5672858af91e9ff581298c441b`
|
||||
- Test source SHA-256:`f716e185c22d14e2db1245529c6600ca2bac1ae3f276a7f5c7b6029a925677d6`
|
||||
- Ownership tool SHA-256:`adfae22058e58285aa503a4836a4f474380af9c7f272ff6280fb8162f02b69be`
|
||||
- 预提交分离工作树:恰好 6 个拟纳入改动,43/43 通过,未携带原始 QA 未跟踪输出。
|
||||
|
||||
## 边界
|
||||
|
||||
该收据不是冻结后 `.3` 收据,也不是 Linux 镜像、SBOM、漏洞扫描、安装或回退证据。内部试点和公众开放继续 `NO_GO`。
|
||||
File diff suppressed because one or more lines are too long
@@ -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:20:00+08:00",
|
||||
"last_updated_at": "2026-09-20T06:52:00+08:00",
|
||||
"scope": {
|
||||
"assets": 60,
|
||||
"core_assets": 8,
|
||||
@@ -158,6 +158,15 @@
|
||||
"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
|
||||
},
|
||||
{
|
||||
"id": "rc04-source-candidate-2",
|
||||
"commit": "62d7d4476c08c8d72419665c3c0e1a6b3ef1c0f9",
|
||||
"tag": "v2-multiscene-rc04-20260920.2",
|
||||
"reason": "The source manifest and deterministic archive were generated, but the mandatory post-freeze clean-checkout regression exposed one failure: the test incorrectly required raw QA evidence that ownership intentionally excludes from release source. The dirty development worktree had masked this dependency. No image build, installation or deployment claim is accepted for this tag.",
|
||||
"evidence": "external release package: rc04-release-manifest-tools-postfreeze-v2.xml (43 tests, 1 failure)",
|
||||
"resumable": false,
|
||||
"final_acceptance_eligible": false
|
||||
}
|
||||
],
|
||||
"items": [
|
||||
@@ -236,7 +245,7 @@
|
||||
"id": "RC-04",
|
||||
"priority": "P0",
|
||||
"title": "Freeze one immutable installable release candidate",
|
||||
"status": "rc04_1_rejected_manifest_path_false_positive_rc04_2_fix_pending",
|
||||
"status": "rc04_1_and_rc04_2_rejected_rc04_3_clean_checkout_fix_pending",
|
||||
"dependencies": ["RC-03"],
|
||||
"completed": [
|
||||
"Contract and AI Python hash locks",
|
||||
@@ -246,13 +255,15 @@
|
||||
"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",
|
||||
"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 .2 candidate was frozen at commit 62d7d4476c08c8d72419665c3c0e1a6b3ef1c0f9 and tag v2-multiscene-rc04-20260920.2; its manifest and archive were deterministic, but it was correctly rejected after a clean-checkout regression exposed one raw-evidence dependency hidden by the dirty development worktree",
|
||||
"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 v6 regression aligns the evidence test with the ownership boundary: included source/test files remain mandatory and bind the immutable Git blob with only exact LF/CRLF renderings accepted, while generated evidence retained outside release source is optional and is hash-checked only when present",
|
||||
"the v6 working-tree and six-file clean-checkout simulation both pass 43/43 with zero failure, error or skip; final acceptance still requires a post-freeze .3 clean detached run",
|
||||
"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": [
|
||||
"independently review and exactly stage only the minimal .2 blocker fix",
|
||||
"reviewed unique .2 commit, tag and source archive; .1 remains immutable but invalid",
|
||||
"run and independently review the minimal .3 clean-checkout regression fix",
|
||||
"freeze a reviewed unique .3 commit/tag, then rerun the 43-test suite from a clean detached checkout before accepting any source manifest or archive; .1 and .2 remain 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"
|
||||
],
|
||||
@@ -260,8 +271,9 @@
|
||||
"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-v3.md",
|
||||
"docs/qa/multiscene-closeout-20260910/rc04-release-manifest-tools-v3.xml",
|
||||
"docs/qa/multiscene-closeout-20260910/rc04-immutable-manifest-tool-v6.md",
|
||||
"docs/qa/multiscene-closeout-20260910/rc04-release-manifest-tools-v6.xml",
|
||||
"external release package: rc04-release-manifest-tools-postfreeze-v2.xml (invalid candidate evidence)",
|
||||
"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."
|
||||
|
||||
@@ -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 不可变可安装候选 | `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-04 不可变可安装候选 | `rc04_1_and_rc04_2_rejected_rc04_3_clean_checkout_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 个合法密钥托管源码路径误判成密钥材料,已失败关闭且不得构建。`.2` commit `62d7d4476c08c8d72419665c3c0e1a6b3ef1c0f9` / tag `v2-multiscene-rc04-20260920.2` 成功生成确定性 manifest 和源码包,但冻结后干净 checkout 回归为 43 项中 1 项失败:测试错误强制要求 ownership 明确排除的原始 QA 证据,先前的脏工作区掩盖了该依赖;`.2` 因此也已不可变地否决。规则现收窄为只允许 `src/tests/migrations` 下的明确代码后缀,实际密钥材料仍拒绝;回归现要求发布源码/测试必须存在,而外置保留的 generated evidence 只在存在时复验哈希。四个 Dockerfile 基础镜像已固定到 Linux amd64 manifest digest,根 `.dockerignore` 已隔离本地缓存、文档、环境文件和运行态数据;Contract 38 与 AI 41 依赖锁的 pinned audit 为 0 known vulnerability。数据库共址主机当前根盘 93%、健康门禁失败且有容器反复重启,禁止直接使用默认 Docker/buildx 构建 | 只纳入干净 checkout 回归修复、回归和状态证据,独立复核后生成递增 `.3` commit/tag;必须先在冻结后干净 checkout 重跑 43 项并通过,才允许生成 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 |
|
||||
|
||||
@@ -42,6 +42,8 @@ COMPACT_EVIDENCE_NAMES = {
|
||||
"rc03-current-candidate-evidence-index-v3.md",
|
||||
"rc04-immutable-manifest-tool-v3.md",
|
||||
"rc04-release-manifest-tools-v3.xml",
|
||||
"rc04-immutable-manifest-tool-v6.md",
|
||||
"rc04-release-manifest-tools-v6.xml",
|
||||
}
|
||||
COMPACT_SUBPATHS: set[str] = set()
|
||||
RUNTIME_COMPONENTS = {
|
||||
|
||||
@@ -42,6 +42,22 @@ def _git(repo: Path, *args: str) -> str:
|
||||
return completed.stdout.strip()
|
||||
|
||||
|
||||
def _git_blob(repo: Path, path: str) -> bytes:
|
||||
completed = subprocess.run(
|
||||
["git", "-C", str(repo), "cat-file", "blob", f"HEAD:{path}"],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
)
|
||||
return completed.stdout
|
||||
|
||||
|
||||
def _text_eol_variants(content: bytes) -> set[bytes]:
|
||||
if b"\x00" in content:
|
||||
return {content}
|
||||
normalized = content.replace(b"\r\n", b"\n")
|
||||
return {content, normalized, normalized.replace(b"\n", b"\r\n")}
|
||||
|
||||
|
||||
def _write(repo: Path, relative_path: str, content: str) -> None:
|
||||
path = repo / Path(*Path(relative_path).parts)
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
@@ -349,7 +365,32 @@ def test_manifest_is_deterministic_and_summarizes_complete_committed_graph(tmp_p
|
||||
assert len(indexed_paths) == len(set(indexed_paths)) == 54
|
||||
for item in current_index["files"]:
|
||||
evidence = REPO_ROOT / item["path"]
|
||||
evidence_bytes = evidence.read_bytes()
|
||||
category, _, _ = ownership.classify_path(item["path"], size=item["size_bytes"])
|
||||
if category == "generated_evidence_retain_exclude":
|
||||
# Raw and invalid-lineage QA outputs are intentionally retained outside
|
||||
# the immutable source candidate. When they remain in a development
|
||||
# workspace, still verify that the compact index recorded their bytes
|
||||
# correctly; a clean release checkout must not require them to exist.
|
||||
if not evidence.exists():
|
||||
continue
|
||||
evidence_bytes = evidence.read_bytes()
|
||||
else:
|
||||
assert category in {"release_source_include", "release_test_include"}
|
||||
assert evidence.is_file()
|
||||
# Verify the immutable Git bytes instead of checkout bytes: Windows may
|
||||
# materialize text files with CRLF. The RC-03 index was created from
|
||||
# worktree bytes, so accept only an exact LF/CRLF rendering of the same
|
||||
# immutable blob; the candidate manifest/archive bind the raw blob.
|
||||
evidence_bytes = _git_blob(REPO_ROOT, item["path"])
|
||||
variants = _text_eol_variants(evidence_bytes)
|
||||
assert any(
|
||||
len(candidate) == item["size_bytes"]
|
||||
and hashlib.sha256(candidate).hexdigest() == item["sha256"]
|
||||
for candidate in variants
|
||||
)
|
||||
if item["path"].endswith(".json"):
|
||||
json.loads(evidence_bytes)
|
||||
continue
|
||||
assert len(evidence_bytes) == item["size_bytes"]
|
||||
assert hashlib.sha256(evidence_bytes).hexdigest() == item["sha256"]
|
||||
if item["path"].endswith(".json"):
|
||||
|
||||
Reference in New Issue
Block a user