From 590c052c558663308cb5f002b35589fc60615e97 Mon Sep 17 00:00:00 2001 From: estelledc <150260202+estelledc@users.noreply.github.com> Date: Fri, 31 Jul 2026 11:58:12 +0800 Subject: [PATCH 01/28] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20TraceFetch=20?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E9=A9=BE=E9=A9=B6=E4=BB=BB=E5=8A=A1=EF=BC=9A?= =?UTF-8?q?=E7=BB=91=E5=AE=9A=E7=8A=B6=E6=80=81=E6=A0=A1=E9=AA=8C=E7=BC=BA?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + autopilot/project.json | 66 +++++++++++++++++++++++++++++ autopilot/tasks/SDW-AUTO-901.json | 70 +++++++++++++++++++++++++++++++ 3 files changed, 137 insertions(+) create mode 100644 autopilot/project.json create mode 100644 autopilot/tasks/SDW-AUTO-901.json diff --git a/.gitignore b/.gitignore index 55d4dba..f2a1e30 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ dist/ htmlcov/ *.egg-info/ runs/ +.autopilot/ diff --git a/autopilot/project.json b/autopilot/project.json new file mode 100644 index 0000000..a4adf2f --- /dev/null +++ b/autopilot/project.json @@ -0,0 +1,66 @@ +{ + "schemaVersion": "system-design-workbench.autopilot-project.v1", + "taskSchemaVersion": "system-design-workbench.autopilot-task.v1", + "taskDirectory": "autopilot/tasks", + "runtimeDirectory": ".autopilot", + "hardLimits": { + "maxMinutes": 45, + "maxChangedFiles": 4, + "maxVerificationAttempts": 2, + "maxPatchBytes": 65536 + }, + "producer": { + "id": "codex-exec-v1", + "cliVersion": "0.144.1", + "executableSha256": "134063e133f0b4244fa3b251acf973d4fe4b4aeeacbdc135211bf480f59f1477", + "maxSeconds": 600, + "maxOutputBytes": 4194304 + }, + "protectedPaths": [ + ".git", + ".autopilot", + ".github/workflows", + ".gitignore", + "autopilot", + "CHANGELOG.md", + "LICENSE", + "Makefile", + "README.md", + "pyproject.toml", + "schemas", + "scripts", + "uv.lock" + ], + "requiredStopConditions": [ + "budget-exhausted", + "verification-attempts-exhausted", + "scope-expansion-required", + "protected-path-change-required", + "dirty-working-tree", + "task-input-changed", + "new-authority-required" + ], + "checks": [ + { + "id": "crawl-test", + "kind": "argv-v1", + "timeoutMs": 120000, + "argv": [ + "uv", + "run", + "--locked", + "pytest", + "tests/test_crawl.py" + ] + }, + { + "id": "project-check", + "kind": "argv-v1", + "timeoutMs": 300000, + "argv": [ + "make", + "check" + ] + } + ] +} diff --git a/autopilot/tasks/SDW-AUTO-901.json b/autopilot/tasks/SDW-AUTO-901.json new file mode 100644 index 0000000..1f4fd56 --- /dev/null +++ b/autopilot/tasks/SDW-AUTO-901.json @@ -0,0 +1,70 @@ +{ + "schemaVersion": "system-design-workbench.autopilot-task.v1", + "id": "SDW-AUTO-901", + "status": "DRAFT", + "execution": { + "kind": "codex-patch-intent-v1", + "patchSha256": null, + "patchBytes": null + }, + "objective": "Make `verify_crawl_bundle` reject a crawl receipt whose top-level `status` differs from the deterministic status derived by `_receipt_from_store` from `pages` and `max_pages`. Preserve all valid crawl receipts, reuse the producer's exact precedence for pending, complete, failed, and blocked page combinations, and add a regression that changes only a valid complete receipt's top-level status to `failed` and proves verification rejects it with a stable, specific failure.", + "problemEvidence": [ + "`_receipt_from_store` deterministically derives the top-level crawl status from page statuses, terminal page count, and max_pages.", + "`verify_crawl_bundle` recomputes failures_by_code and validates the SQLite projection, but it never recomputes or compares the top-level crawl status.", + "On source HEAD c57b1bcb788d6228bbade186667c3bed46ef1996, a valid one-page complete crawl remains `valid: true` after changing only `crawl-receipt.json.status` from `complete` to `failed`.", + "The existing crawl test file passes before the fix, so the new regression must fail on the source behavior and pass only when the verifier enforces the producer's status contract.", + "The fix belongs in verification and its direct tests; changing crawl production, public models, build configuration, or schemas is outside this task." + ], + "allowedPaths": [ + "src/tracefetch/verify.py", + "tests/test_crawl.py" + ], + "forbiddenPaths": [ + ".github", + ".gitignore", + "CHANGELOG.md", + "LICENSE", + "Makefile", + "README.md", + "autopilot", + "pyproject.toml", + "schemas", + "scripts", + "src/tracefetch/crawl.py", + "src/tracefetch/contracts.py", + "uv.lock" + ], + "baselineChecks": [ + "crawl-test" + ], + "acceptanceChecks": [ + "crawl-test", + "project-check" + ], + "budget": { + "maxMinutes": 30, + "maxChangedFiles": 2, + "maxVerificationAttempts": 1 + }, + "authority": { + "write": true, + "invokeAgent": true, + "applyPatch": false, + "commit": false, + "push": false, + "release": false, + "network": true + }, + "externalOutcome": "An independent reviewer accepts a frozen two-file candidate that makes `verify_crawl_bundle` reject a top-level status inconsistent with the producer's deterministic page projection, while the targeted crawl tests and the full TraceFetch project check pass without changing valid crawl behavior.", + "stopConditions": [ + "budget-exhausted", + "verification-attempts-exhausted", + "scope-expansion-required", + "protected-path-change-required", + "dirty-working-tree", + "task-input-changed", + "new-authority-required", + "crawl-status-contract-requires-behavior-change" + ], + "requiresChanges": true +} From ad98560072e5938c2814f045ddd2ac680d7dc583 Mon Sep 17 00:00:00 2001 From: estelledc <150260202+estelledc@users.noreply.github.com> Date: Fri, 31 Jul 2026 12:02:43 +0800 Subject: [PATCH 02/28] =?UTF-8?q?=E6=94=B6=E7=B4=A7=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=E4=BB=BB=E5=8A=A1=EF=BC=9A=E7=BA=B3=E5=85=A5?= =?UTF-8?q?=20Ruff=20=E6=A0=BC=E5=BC=8F=E9=97=A8=E7=A6=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- autopilot/tasks/SDW-AUTO-901.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/autopilot/tasks/SDW-AUTO-901.json b/autopilot/tasks/SDW-AUTO-901.json index 1f4fd56..73d4ea0 100644 --- a/autopilot/tasks/SDW-AUTO-901.json +++ b/autopilot/tasks/SDW-AUTO-901.json @@ -7,13 +7,14 @@ "patchSha256": null, "patchBytes": null }, - "objective": "Make `verify_crawl_bundle` reject a crawl receipt whose top-level `status` differs from the deterministic status derived by `_receipt_from_store` from `pages` and `max_pages`. Preserve all valid crawl receipts, reuse the producer's exact precedence for pending, complete, failed, and blocked page combinations, and add a regression that changes only a valid complete receipt's top-level status to `failed` and proves verification rejects it with a stable, specific failure.", + "objective": "Make `verify_crawl_bundle` reject a crawl receipt whose top-level `status` differs from the deterministic status derived by `_receipt_from_store` from `pages` and `max_pages`. Preserve all valid crawl receipts, reuse the producer's exact precedence for pending, complete, failed, and blocked page combinations, add a regression that changes only a valid complete receipt's top-level status to `failed` and proves verification rejects it with a stable, specific failure, and keep both changed files compliant with the repository's current Ruff formatting contract.", "problemEvidence": [ "`_receipt_from_store` deterministically derives the top-level crawl status from page statuses, terminal page count, and max_pages.", "`verify_crawl_bundle` recomputes failures_by_code and validates the SQLite projection, but it never recomputes or compares the top-level crawl status.", "On source HEAD c57b1bcb788d6228bbade186667c3bed46ef1996, a valid one-page complete crawl remains `valid: true` after changing only `crawl-receipt.json.status` from `complete` to `failed`.", "The existing crawl test file passes before the fix, so the new regression must fail on the source behavior and pass only when the verifier enforces the producer's status contract.", - "The fix belongs in verification and its direct tests; changing crawl production, public models, build configuration, or schemas is outside this task." + "The fix belongs in verification and its direct tests; changing crawl production, public models, build configuration, or schemas is outside this task.", + "The first isolated candidate passed all 12 crawl tests and Ruff lint, but `ruff format --check` rejected its multiline `processed_count` sum; that candidate is not eligible for TaskSpec installation, and the replacement must satisfy the existing formatter without a later manual patch." ], "allowedPaths": [ "src/tracefetch/verify.py", From 3f3b2080386ecd21455cdc51ccef5a18c54b7e48 Mon Sep 17 00:00:00 2001 From: estelledc <150260202+estelledc@users.noreply.github.com> Date: Fri, 31 Jul 2026 12:07:09 +0800 Subject: [PATCH 03/28] =?UTF-8?q?=E5=AE=89=E8=A3=85=E5=AE=A1=E6=9F=A5?= =?UTF-8?q?=E5=90=8E=E7=9A=84=20TraceFetch=20=E5=80=99=E9=80=89=E7=BB=91?= =?UTF-8?q?=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- autopilot/tasks/SDW-AUTO-901.json | 60 +++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 7 deletions(-) diff --git a/autopilot/tasks/SDW-AUTO-901.json b/autopilot/tasks/SDW-AUTO-901.json index 73d4ea0..758ea8d 100644 --- a/autopilot/tasks/SDW-AUTO-901.json +++ b/autopilot/tasks/SDW-AUTO-901.json @@ -1,11 +1,57 @@ { "schemaVersion": "system-design-workbench.autopilot-task.v1", "id": "SDW-AUTO-901", - "status": "DRAFT", + "status": "READY", "execution": { - "kind": "codex-patch-intent-v1", - "patchSha256": null, - "patchBytes": null + "kind": "prepared-bound-patch-v1", + "patchSha256": "78508dd8d7e36e6b172dec2d659130119b5275ae1c9cc96c3e7cc5932c7a8c16", + "patchBytes": 2454, + "preparation": { + "sourceHead": "ad98560072e5938c2814f045ddd2ac680d7dc583", + "project": { + "path": "autopilot/project.json", + "fileSha256": "1687980d1f6ff5c4f330cc385c22138e1e18ea01ccb3628f3ee549b56be4753a", + "gitBlobOid": "a4adf2f2ec76ab0e4297d6da52a85db3ff32809b", + "canonicalSha256": "aa4e0a90a916fd297aac585b19a43ae6feec951b1226424fc8936fe585b6acbf" + }, + "task": { + "path": "autopilot/tasks/SDW-AUTO-901.json", + "fileSha256": "e11baa1d3c0775c9aec5098183a7bb7c8d12ce52124a2a5850559a13254295d5", + "gitBlobOid": "73d4ea0c5d5ef022f146a817c8945f4c54bb16a4", + "canonicalSha256": "3d08463bf3c304c3c0cd163834eaacaf368c443cf6b55051ce8d04b40819909b" + }, + "candidateSnapshotSha256": "be883bbcbf58d84bcccb3c059c27543fc1dcfdd8516b6660bea81c9f17891f49", + "producer": { + "schemaVersion": "system-design-workbench.codex-producer-receipt.v1", + "id": "codex-exec-v1", + "cliVersion": "0.144.1", + "executableSha256": "134063e133f0b4244fa3b251acf973d4fe4b4aeeacbdc135211bf480f59f1477", + "argvLabel": "codex exec fixed-contract-v1", + "sourceBindingSha256": "76b7f0fb9a030699c7414b7bf6e0da880b680954d62702e307fec4f75ce93c07", + "promptSha256": "004e4f069911910c9db5acc36ea123f1e578671375e91e1e6f23088e6668364f", + "exitCode": 0, + "signal": null, + "timedOut": false, + "outputLimitExceeded": false, + "durationMs": 72416, + "stdout": { + "bytes": 44792, + "sha256": "242bbda2d8f0a65d9c8f32f393649e4abdd4eb862c5f655e63ce94671c0c3ffe" + }, + "stderr": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "events": { + "lineCount": 23, + "turnCompletedCount": 1, + "errorCount": 0 + }, + "patchSha256": "78508dd8d7e36e6b172dec2d659130119b5275ae1c9cc96c3e7cc5932c7a8c16", + "patchBytes": 2454, + "candidateSnapshotSha256": "be883bbcbf58d84bcccb3c059c27543fc1dcfdd8516b6660bea81c9f17891f49" + } + } }, "objective": "Make `verify_crawl_bundle` reject a crawl receipt whose top-level `status` differs from the deterministic status derived by `_receipt_from_store` from `pages` and `max_pages`. Preserve all valid crawl receipts, reuse the producer's exact precedence for pending, complete, failed, and blocked page combinations, add a regression that changes only a valid complete receipt's top-level status to `failed` and proves verification rejects it with a stable, specific failure, and keep both changed files compliant with the repository's current Ruff formatting contract.", "problemEvidence": [ @@ -49,12 +95,12 @@ }, "authority": { "write": true, - "invokeAgent": true, - "applyPatch": false, + "invokeAgent": false, + "applyPatch": true, "commit": false, "push": false, "release": false, - "network": true + "network": false }, "externalOutcome": "An independent reviewer accepts a frozen two-file candidate that makes `verify_crawl_bundle` reject a top-level status inconsistent with the producer's deterministic page projection, while the targeted crawl tests and the full TraceFetch project check pass without changing valid crawl behavior.", "stopConditions": [ From e52d1fd8681323eacd3205677a7b3b54aec70aff Mon Sep 17 00:00:00 2001 From: estelledc <150260202+estelledc@users.noreply.github.com> Date: Fri, 31 Jul 2026 12:10:27 +0800 Subject: [PATCH 04/28] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=8A=93=E5=8F=96?= =?UTF-8?q?=E6=94=B6=E6=8D=AE=E9=AA=8C=E8=AF=81=EF=BC=9A=E6=A0=A1=E9=AA=8C?= =?UTF-8?q?=E9=A1=B6=E5=B1=82=E7=8A=B6=E6=80=81=E6=8A=95=E5=BD=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tracefetch/verify.py | 14 ++++++++++++++ tests/test_crawl.py | 15 +++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/tracefetch/verify.py b/src/tracefetch/verify.py index 59d14e1..f3a68e7 100644 --- a/src/tracefetch/verify.py +++ b/src/tracefetch/verify.py @@ -258,6 +258,20 @@ def verify_crawl_bundle(root_dir: Path) -> list[str]: failures.append(f"non-complete crawl page has bundle_path: {page.url}") if dict(sorted(failures_by_code.items())) != dict(sorted(receipt.failures_by_code.items())): failures.append("failures_by_code does not match page records") + statuses = Counter(page.status for page in receipt.pages) + processed_count = statuses["complete"] + statuses["failed"] + statuses["blocked"] + if statuses["pending"] and processed_count >= receipt.max_pages: + expected_status = "partial" + elif statuses["pending"]: + expected_status = "running" + elif statuses["complete"] and (statuses["failed"] or statuses["blocked"]): + expected_status = "partial" + elif statuses["complete"]: + expected_status = "complete" + else: + expected_status = "failed" + if receipt.status != expected_status: + failures.append("crawl status does not match page records") failures.extend(_verify_crawl_state(receipt, state_path)) return failures diff --git a/tests/test_crawl.py b/tests/test_crawl.py index ee9bdfe..ba9c379 100644 --- a/tests/test_crawl.py +++ b/tests/test_crawl.py @@ -234,3 +234,18 @@ def test_failed_page_counts_are_recomputed_from_page_records( receipt["failures_by_code"] = {"fabricated": 1} receipt_path.write_text(json.dumps(receipt), encoding="utf-8") assert "failures_by_code does not match page records" in verify_crawl_bundle(output) + + +def test_crawl_status_is_recomputed_from_page_records( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + output = tmp_path / "crawl" + install_fake_fetch(monkeypatch, {ROOT: []}) + receipt = crawl_site(ROOT, output, reader="direct", policy=policy(), resume=False) + assert receipt.status == "complete" + receipt_path = output / "crawl-receipt.json" + payload = json.loads(receipt_path.read_text(encoding="utf-8")) + payload["status"] = "failed" + receipt_path.write_text(json.dumps(payload), encoding="utf-8") + + assert verify_crawl_bundle(output) == ["crawl status does not match page records"] From 7bdeb0a0520a29ac8a7d01f0bdf0e782a241e726 Mon Sep 17 00:00:00 2001 From: estelledc <150260202+estelledc@users.noreply.github.com> Date: Fri, 31 Jul 2026 19:29:30 +0800 Subject: [PATCH 05/28] =?UTF-8?q?=E6=8E=A5=E5=85=A5=20TraceFetch=20transac?= =?UTF-8?q?tion=20=E5=B9=B6=E7=99=BB=E8=AE=B0=E6=94=B6=E6=8D=AE=E9=AA=8C?= =?UTF-8?q?=E8=AF=81=E4=BB=BB=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- autopilot/project.json | 24 +++++++++- autopilot/tasks/SDW-AUTO-902.json | 78 +++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 autopilot/tasks/SDW-AUTO-902.json diff --git a/autopilot/project.json b/autopilot/project.json index a4adf2f..2ba9ecb 100644 --- a/autopilot/project.json +++ b/autopilot/project.json @@ -1,5 +1,5 @@ { - "schemaVersion": "system-design-workbench.autopilot-project.v1", + "schemaVersion": "system-design-workbench.autopilot-project.v2", "taskSchemaVersion": "system-design-workbench.autopilot-task.v1", "taskDirectory": "autopilot/tasks", "runtimeDirectory": ".autopilot", @@ -41,6 +41,18 @@ "new-authority-required" ], "checks": [ + { + "id": "cli-test", + "kind": "argv-v1", + "timeoutMs": 120000, + "argv": [ + "uv", + "run", + "--locked", + "pytest", + "tests/test_cli.py" + ] + }, { "id": "crawl-test", "kind": "argv-v1", @@ -62,5 +74,13 @@ "check" ] } - ] + ], + "reviewPolicy": { + "kind": "record-before-close-v1" + }, + "controllerSourceManifest": { + "toolVersion": "1.0.0", + "manifestSha256": "685a54e02f7070c6b427b5bcf1094bddb0bed378fe7a582092792edb004e7e51", + "sourceSetSha256": "a54277217f12396a00d955d63f577f54e6b19c29b2fb874311fda3e6b48a8f43" + } } diff --git a/autopilot/tasks/SDW-AUTO-902.json b/autopilot/tasks/SDW-AUTO-902.json new file mode 100644 index 0000000..dedf5f4 --- /dev/null +++ b/autopilot/tasks/SDW-AUTO-902.json @@ -0,0 +1,78 @@ +{ + "schemaVersion": "system-design-workbench.autopilot-task.v1", + "id": "SDW-AUTO-902", + "status": "DRAFT", + "execution": { + "kind": "codex-patch-intent-v1", + "patchSha256": null, + "patchBytes": null + }, + "objective": "Make `tracefetch verify ` preserve the published machine-error contract when `crawl-receipt.json` is syntactically invalid, invalid UTF-8, or schema-invalid. `verify_crawl_bundle` already translates those receipt failures into a failure list, so `crawl_verification_payload` must not reparse the same invalid receipt without a failure boundary. The CLI must exit 6, write no stdout, and write exactly one `tracefetch.error.v1` JSON object to stderr with `error=verification_failed`, `retryable=false`, and a non-empty bounded `details.failures` list; it must not expose a Python traceback or an uncaught Pydantic exception. Preserve the exact successful crawl-verification payload for valid receipts and keep ordinary evidence-bundle verification unchanged. Fix the shared verification layer rather than adding a CLI-only catch or duplicating receipt validation logic.", + "problemEvidence": [ + "On committed source HEAD e52d1fd8681323eacd3205677a7b3b54aec70aff, a crawl directory containing only `crawl-receipt.json` with `{}` reaches `verify_crawl_bundle`, then `crawl_verification_payload` reparses the same file and raises an uncaught Pydantic `ValidationError` with 11 missing-field errors.", + "The reproduced CLI process exits 1 and prints a Python traceback, contradicting README's contract that errors are one `tracefetch.error.v1` object on stderr with stable exit codes.", + "`verify_crawl_bundle` already catches `OSError`, `UnicodeError`, and `ValidationError` while reading the crawl receipt and returns an `invalid crawl receipt` failure; the second unguarded parse in `crawl_verification_payload` is the root divergence.", + "The existing `tests/test_cli.py` suite passes 7 tests on the source behavior because it covers tampered ordinary evidence but not malformed crawl receipts.", + "Regression coverage must call the public CLI entry point for malformed JSON, invalid UTF-8, and a valid JSON object missing required receipt fields; each case must assert exit 6, empty stdout, one parseable error envelope, and no `Traceback`, `pydantic_core`, or `ValidationError` text outside the bounded failure details.", + "A direct verification-layer regression must prove `crawl_verification_payload` returns `valid=false` with zero verified pages and the failure list instead of throwing when the receipt cannot be modeled.", + "Valid crawl receipts must retain the existing `tracefetch.crawl-verification.v1` fields, crawl ID, verified-page count, and `valid=true`; ordinary `receipt.json` verification is outside the changed control path and must remain unchanged.", + "The fix must reuse the result of the existing receipt failure boundary or a single shared parse result; adding a broad catch only in `cli.py` would leave `crawl_verification_payload` unsafe for its other caller and is not sufficient." + ], + "allowedPaths": [ + "src/tracefetch/verify.py", + "tests/test_cli.py", + "tests/test_crawl.py" + ], + "forbiddenPaths": [ + ".github", + ".gitignore", + "CHANGELOG.md", + "LICENSE", + "Makefile", + "README.md", + "autopilot", + "pyproject.toml", + "schemas", + "scripts", + "src/tracefetch/cli.py", + "src/tracefetch/contracts.py", + "src/tracefetch/crawl.py", + "src/tracefetch/errors.py", + "uv.lock" + ], + "baselineChecks": [ + "cli-test", + "crawl-test" + ], + "acceptanceChecks": [ + "cli-test", + "crawl-test", + "project-check" + ], + "budget": { + "maxMinutes": 30, + "maxChangedFiles": 3, + "maxVerificationAttempts": 1 + }, + "authority": { + "write": true, + "invokeAgent": true, + "applyPatch": false, + "commit": false, + "push": false, + "release": false, + "network": true + }, + "externalOutcome": "An independent reviewer accepts a frozen candidate that converts malformed crawl-receipt verification into the documented JSON error and exit-6 contract while valid crawl and ordinary evidence verification remain unchanged; after the frozen lineage closes, the exact product commit is pushed on its lineage branch without claiming PR, merge, release, or production acceptance.", + "stopConditions": [ + "budget-exhausted", + "verification-attempts-exhausted", + "scope-expansion-required", + "protected-path-change-required", + "dirty-working-tree", + "task-input-changed", + "new-authority-required", + "malformed-crawl-receipt-contract-requires-behavior-change" + ], + "requiresChanges": true +} From d89f2222afa87c767efefd814ce60595313acde7 Mon Sep 17 00:00:00 2001 From: estelledc <150260202+estelledc@users.noreply.github.com> Date: Fri, 31 Jul 2026 19:33:35 +0800 Subject: [PATCH 06/28] =?UTF-8?q?=E8=A1=A5=E5=85=85=20TraceFetch=20Ruff=20?= =?UTF-8?q?=E5=80=99=E9=80=89=E9=97=A8=E7=A6=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- autopilot/tasks/SDW-AUTO-902.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/autopilot/tasks/SDW-AUTO-902.json b/autopilot/tasks/SDW-AUTO-902.json index dedf5f4..52db058 100644 --- a/autopilot/tasks/SDW-AUTO-902.json +++ b/autopilot/tasks/SDW-AUTO-902.json @@ -7,7 +7,7 @@ "patchSha256": null, "patchBytes": null }, - "objective": "Make `tracefetch verify ` preserve the published machine-error contract when `crawl-receipt.json` is syntactically invalid, invalid UTF-8, or schema-invalid. `verify_crawl_bundle` already translates those receipt failures into a failure list, so `crawl_verification_payload` must not reparse the same invalid receipt without a failure boundary. The CLI must exit 6, write no stdout, and write exactly one `tracefetch.error.v1` JSON object to stderr with `error=verification_failed`, `retryable=false`, and a non-empty bounded `details.failures` list; it must not expose a Python traceback or an uncaught Pydantic exception. Preserve the exact successful crawl-verification payload for valid receipts and keep ordinary evidence-bundle verification unchanged. Fix the shared verification layer rather than adding a CLI-only catch or duplicating receipt validation logic.", + "objective": "Make `tracefetch verify ` preserve the published machine-error contract when `crawl-receipt.json` is syntactically invalid, invalid UTF-8, or schema-invalid. `verify_crawl_bundle` already translates those receipt failures into a failure list, so `crawl_verification_payload` must not reparse the same invalid receipt without a failure boundary. The CLI must exit 6, write no stdout, and write exactly one `tracefetch.error.v1` JSON object to stderr with `error=verification_failed`, `retryable=false`, and a non-empty bounded `details.failures` list; it must not expose a Python traceback or an uncaught Pydantic exception. Preserve the exact successful crawl-verification payload for valid receipts and keep ordinary evidence-bundle verification unchanged. Fix the shared verification layer rather than adding a CLI-only catch or duplicating receipt validation logic. Every changed Python file must already match the repository's committed Ruff formatter output; a candidate that needs a later manual formatting patch is not eligible.", "problemEvidence": [ "On committed source HEAD e52d1fd8681323eacd3205677a7b3b54aec70aff, a crawl directory containing only `crawl-receipt.json` with `{}` reaches `verify_crawl_bundle`, then `crawl_verification_payload` reparses the same file and raises an uncaught Pydantic `ValidationError` with 11 missing-field errors.", "The reproduced CLI process exits 1 and prints a Python traceback, contradicting README's contract that errors are one `tracefetch.error.v1` object on stderr with stable exit codes.", @@ -16,7 +16,8 @@ "Regression coverage must call the public CLI entry point for malformed JSON, invalid UTF-8, and a valid JSON object missing required receipt fields; each case must assert exit 6, empty stdout, one parseable error envelope, and no `Traceback`, `pydantic_core`, or `ValidationError` text outside the bounded failure details.", "A direct verification-layer regression must prove `crawl_verification_payload` returns `valid=false` with zero verified pages and the failure list instead of throwing when the receipt cannot be modeled.", "Valid crawl receipts must retain the existing `tracefetch.crawl-verification.v1` fields, crawl ID, verified-page count, and `valid=true`; ordinary `receipt.json` verification is outside the changed control path and must remain unchanged.", - "The fix must reuse the result of the existing receipt failure boundary or a single shared parse result; adding a broad catch only in `cli.py` would leave `crawl_verification_payload` unsafe for its other caller and is not sufficient." + "The fix must reuse the result of the existing receipt failure boundary or a single shared parse result; adding a broad catch only in `cli.py` would leave `crawl_verification_payload` unsafe for its other caller and is not sufficient.", + "Transaction a2433ac0-fa05-4b99-87a8-3c46b0354cdf produced a semantically plausible three-file candidate whose focused CLI/crawl tests passed 25/25, but `ruff format --check .` rejected multiline expressions in `src/tracefetch/verify.py` and `tests/test_crawl.py`; that candidate was formally rejected as `ruff-format-contract-failed` and must not be installed or repaired manually." ], "allowedPaths": [ "src/tracefetch/verify.py", From 979accf6a7795010121cfaafbbe62eace9a92067 Mon Sep 17 00:00:00 2001 From: estelledc <150260202+estelledc@users.noreply.github.com> Date: Fri, 31 Jul 2026 19:37:43 +0800 Subject: [PATCH 07/28] =?UTF-8?q?=E6=94=B6=E7=B4=A7=20TraceFetch=20?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E8=BE=B9=E7=95=8C=E4=B8=8E=E5=80=99=E9=80=89?= =?UTF-8?q?=E6=B4=81=E5=87=80=E9=97=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- autopilot/tasks/SDW-AUTO-902.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/autopilot/tasks/SDW-AUTO-902.json b/autopilot/tasks/SDW-AUTO-902.json index 52db058..c4adc83 100644 --- a/autopilot/tasks/SDW-AUTO-902.json +++ b/autopilot/tasks/SDW-AUTO-902.json @@ -7,7 +7,7 @@ "patchSha256": null, "patchBytes": null }, - "objective": "Make `tracefetch verify ` preserve the published machine-error contract when `crawl-receipt.json` is syntactically invalid, invalid UTF-8, or schema-invalid. `verify_crawl_bundle` already translates those receipt failures into a failure list, so `crawl_verification_payload` must not reparse the same invalid receipt without a failure boundary. The CLI must exit 6, write no stdout, and write exactly one `tracefetch.error.v1` JSON object to stderr with `error=verification_failed`, `retryable=false`, and a non-empty bounded `details.failures` list; it must not expose a Python traceback or an uncaught Pydantic exception. Preserve the exact successful crawl-verification payload for valid receipts and keep ordinary evidence-bundle verification unchanged. Fix the shared verification layer rather than adding a CLI-only catch or duplicating receipt validation logic. Every changed Python file must already match the repository's committed Ruff formatter output; a candidate that needs a later manual formatting patch is not eligible.", + "objective": "Make `tracefetch verify ` preserve the published machine-error contract when `crawl-receipt.json` is syntactically invalid, invalid UTF-8, or schema-invalid. Parse the receipt through one shared bounded boundary: malformed or schema-invalid JSON must become the exact fixed failure `crawl receipt failed schema validation`, invalid UTF-8 must become `crawl receipt is not valid UTF-8`, and an unreadable receipt must become `crawl receipt is unreadable`; raw Pydantic, filesystem, input-value, receipt-content, and machine-path text must never enter a failure string. `crawl_verification_payload` must reuse the parsed receipt and failure result rather than reparsing. The CLI must exit 6, write no stdout, and write exactly one `tracefetch.error.v1` JSON object to stderr with `error=verification_failed`, `retryable=false`, and a non-empty `details.failures` list; for adversarial schema-invalid input its complete stderr must remain at most 4096 bytes and must not reflect any receipt sentinel. If the receipt itself is valid but crawl state is a symlink or otherwise invalid, the failure payload must retain that receipt's actual crawl ID and complete-page count. Preserve the exact successful crawl-verification payload for valid receipts and keep ordinary evidence-bundle verification unchanged. Fix the shared verification layer rather than adding a CLI-only catch or duplicating receipt validation logic. Every changed Python file must already match the repository's committed Ruff formatter output; a candidate that needs a later manual formatting patch is not eligible. Producer verification must leave no ignored cache, coverage, virtualenv, build, or distribution artifact in its isolated candidate clone.", "problemEvidence": [ "On committed source HEAD e52d1fd8681323eacd3205677a7b3b54aec70aff, a crawl directory containing only `crawl-receipt.json` with `{}` reaches `verify_crawl_bundle`, then `crawl_verification_payload` reparses the same file and raises an uncaught Pydantic `ValidationError` with 11 missing-field errors.", "The reproduced CLI process exits 1 and prints a Python traceback, contradicting README's contract that errors are one `tracefetch.error.v1` object on stderr with stable exit codes.", @@ -17,7 +17,10 @@ "A direct verification-layer regression must prove `crawl_verification_payload` returns `valid=false` with zero verified pages and the failure list instead of throwing when the receipt cannot be modeled.", "Valid crawl receipts must retain the existing `tracefetch.crawl-verification.v1` fields, crawl ID, verified-page count, and `valid=true`; ordinary `receipt.json` verification is outside the changed control path and must remain unchanged.", "The fix must reuse the result of the existing receipt failure boundary or a single shared parse result; adding a broad catch only in `cli.py` would leave `crawl_verification_payload` unsafe for its other caller and is not sufficient.", - "Transaction a2433ac0-fa05-4b99-87a8-3c46b0354cdf produced a semantically plausible three-file candidate whose focused CLI/crawl tests passed 25/25, but `ruff format --check .` rejected multiline expressions in `src/tracefetch/verify.py` and `tests/test_crawl.py`; that candidate was formally rejected as `ruff-format-contract-failed` and must not be installed or repaired manually." + "Transaction a2433ac0-fa05-4b99-87a8-3c46b0354cdf produced a semantically plausible three-file candidate whose focused CLI/crawl tests passed 25/25, but `ruff format --check .` rejected multiline expressions in `src/tracefetch/verify.py` and `tests/test_crawl.py`; that candidate was formally rejected as `ruff-format-contract-failed` and must not be installed or repaired manually.", + "Independent review of the rejected candidate showed that directly embedding Pydantic `ValidationError` text is a security and availability failure: a 5000-page invalid receipt produced about 2.4 MB of stderr, a single failure near 2.38 million characters, and reflected `TOP_SECRET_SENTINEL`; fixed portable failure messages and a 4096-byte complete-envelope bound are therefore hard acceptance gates.", + "The rejected helper returned `receipt=None` before parsing whenever `crawl.sqlite3` was a symlink, so a valid one-page receipt's failure payload regressed from its real crawl ID and verified-page count to an empty ID and zero; receipt validity and state validity must remain separate.", + "Transaction eeec5b9e-6560-463d-83ed-54ed5e2b59cd invoked the producer once but candidate audit rejected an ignored generated path; resume correctly ended at `PRODUCER_OUTCOME_UNKNOWN` without invoking the model again. A replacement producer may run checks only if it leaves no `.venv`, `.pytest_cache`, `.coverage`, `build`, `dist`, or other ignored output in the isolated candidate clone." ], "allowedPaths": [ "src/tracefetch/verify.py", From 01098a13169b5511fd5228f69177f9089efd261c Mon Sep 17 00:00:00 2001 From: estelledc <150260202+estelledc@users.noreply.github.com> Date: Fri, 31 Jul 2026 19:48:07 +0800 Subject: [PATCH 08/28] =?UTF-8?q?=E6=A0=A1=E5=87=86=20TraceFetch=20?= =?UTF-8?q?=E6=94=B6=E6=8D=AE=E9=94=99=E8=AF=AF=E8=BE=B9=E7=95=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- autopilot/tasks/SDW-AUTO-902.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/autopilot/tasks/SDW-AUTO-902.json b/autopilot/tasks/SDW-AUTO-902.json index c4adc83..6099e7b 100644 --- a/autopilot/tasks/SDW-AUTO-902.json +++ b/autopilot/tasks/SDW-AUTO-902.json @@ -7,7 +7,7 @@ "patchSha256": null, "patchBytes": null }, - "objective": "Make `tracefetch verify ` preserve the published machine-error contract when `crawl-receipt.json` is syntactically invalid, invalid UTF-8, or schema-invalid. Parse the receipt through one shared bounded boundary: malformed or schema-invalid JSON must become the exact fixed failure `crawl receipt failed schema validation`, invalid UTF-8 must become `crawl receipt is not valid UTF-8`, and an unreadable receipt must become `crawl receipt is unreadable`; raw Pydantic, filesystem, input-value, receipt-content, and machine-path text must never enter a failure string. `crawl_verification_payload` must reuse the parsed receipt and failure result rather than reparsing. The CLI must exit 6, write no stdout, and write exactly one `tracefetch.error.v1` JSON object to stderr with `error=verification_failed`, `retryable=false`, and a non-empty `details.failures` list; for adversarial schema-invalid input its complete stderr must remain at most 4096 bytes and must not reflect any receipt sentinel. If the receipt itself is valid but crawl state is a symlink or otherwise invalid, the failure payload must retain that receipt's actual crawl ID and complete-page count. Preserve the exact successful crawl-verification payload for valid receipts and keep ordinary evidence-bundle verification unchanged. Fix the shared verification layer rather than adding a CLI-only catch or duplicating receipt validation logic. Every changed Python file must already match the repository's committed Ruff formatter output; a candidate that needs a later manual formatting patch is not eligible. Producer verification must leave no ignored cache, coverage, virtualenv, build, or distribution artifact in its isolated candidate clone.", + "objective": "Make `tracefetch verify ` preserve the published machine-error contract when `crawl-receipt.json` is syntactically invalid, invalid UTF-8, or schema-invalid. Parse the receipt through one shared load/decode/schema failure boundary: malformed or schema-invalid JSON must become the exact fixed failure `crawl receipt failed schema validation`, invalid UTF-8 must become `crawl receipt is not valid UTF-8`, and an unreadable receipt must become `crawl receipt is unreadable`; raw Pydantic, filesystem, input-value, receipt-content, and machine-path text from that load boundary must never enter those three failures. Existing diagnostics after a receipt has been successfully modeled—including page URL and bundle-path diagnostics—are outside this task and must not be rewritten. `crawl_verification_payload` must reuse the parsed receipt and failure result rather than reparsing. The CLI must exit 6, write no stdout, and write exactly one `tracefetch.error.v1` JSON object to stderr with `error=verification_failed`, `retryable=false`, and a non-empty `details.failures` list; for adversarial schema-invalid input its complete stderr must remain at most 4096 bytes and must not reflect any receipt sentinel. If the receipt itself is valid but crawl state is missing, corrupt, a symlink, or otherwise invalid, the failure payload must retain that receipt's actual crawl ID and complete-page count. Preserve the exact successful crawl-verification payload for valid receipts and keep ordinary evidence-bundle verification unchanged. Fix the shared verification layer rather than adding a CLI-only catch or duplicating receipt validation logic. Every changed Python file must already match the repository's committed Ruff formatter output; a candidate that needs a later manual formatting patch is not eligible. Producer verification must leave no ignored cache, coverage, virtualenv, build, or distribution artifact in its isolated candidate clone.", "problemEvidence": [ "On committed source HEAD e52d1fd8681323eacd3205677a7b3b54aec70aff, a crawl directory containing only `crawl-receipt.json` with `{}` reaches `verify_crawl_bundle`, then `crawl_verification_payload` reparses the same file and raises an uncaught Pydantic `ValidationError` with 11 missing-field errors.", "The reproduced CLI process exits 1 and prints a Python traceback, contradicting README's contract that errors are one `tracefetch.error.v1` object on stderr with stable exit codes.", @@ -20,7 +20,9 @@ "Transaction a2433ac0-fa05-4b99-87a8-3c46b0354cdf produced a semantically plausible three-file candidate whose focused CLI/crawl tests passed 25/25, but `ruff format --check .` rejected multiline expressions in `src/tracefetch/verify.py` and `tests/test_crawl.py`; that candidate was formally rejected as `ruff-format-contract-failed` and must not be installed or repaired manually.", "Independent review of the rejected candidate showed that directly embedding Pydantic `ValidationError` text is a security and availability failure: a 5000-page invalid receipt produced about 2.4 MB of stderr, a single failure near 2.38 million characters, and reflected `TOP_SECRET_SENTINEL`; fixed portable failure messages and a 4096-byte complete-envelope bound are therefore hard acceptance gates.", "The rejected helper returned `receipt=None` before parsing whenever `crawl.sqlite3` was a symlink, so a valid one-page receipt's failure payload regressed from its real crawl ID and verified-page count to an empty ID and zero; receipt validity and state validity must remain separate.", - "Transaction eeec5b9e-6560-463d-83ed-54ed5e2b59cd invoked the producer once but candidate audit rejected an ignored generated path; resume correctly ended at `PRODUCER_OUTCOME_UNKNOWN` without invoking the model again. A replacement producer may run checks only if it leaves no `.venv`, `.pytest_cache`, `.coverage`, `build`, `dist`, or other ignored output in the isolated candidate clone." + "Transaction eeec5b9e-6560-463d-83ed-54ed5e2b59cd invoked the producer once but candidate audit rejected an ignored generated path; resume correctly ended at `PRODUCER_OUTCOME_UNKNOWN` without invoking the model again. A replacement producer may run checks only if it leaves no `.venv`, `.pytest_cache`, `.coverage`, `build`, `dist`, or other ignored output in the isolated candidate clone.", + "Transaction 451a4c03-f2e6-45b8-a09b-8e1b7f660cc6 produced a formatted candidate that passed focused 27/27 and full 117/117 checks, bounded a 5000-page schema-invalid sentinel case to 242 bytes, and preserved valid receipt summaries for missing, corrupt, and symlinked state. It was formally rejected after a probe placed a sentinel in a schema-valid page URL and bundle path. Independent review showed that probe exercised existing downstream diagnostics rather than the malformed receipt load boundary; broad redaction would change public behavior and exceed this task, so the rejected candidate cannot be reused but the replacement must retain its narrower load-boundary semantics.", + "An explicit input-byte ceiling and open-handle protection against local concurrent replacement are not defined by the current TraceFetch receipt contract. They remain separate follow-up risks; this task must not invent a new size threshold or claim TOCTOU protection while fixing the confirmed exception and output-reflection path." ], "allowedPaths": [ "src/tracefetch/verify.py", From 128f74272a4eb3ee698c11370d1c5fd3f2a7402c Mon Sep 17 00:00:00 2001 From: estelledc <150260202+estelledc@users.noreply.github.com> Date: Fri, 31 Jul 2026 19:52:31 +0800 Subject: [PATCH 09/28] =?UTF-8?q?=E5=9B=BA=E5=AE=9A=20TraceFetch=20Ruff=20?= =?UTF-8?q?=E5=8D=95=E8=A1=8C=E5=90=88=E5=90=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- autopilot/tasks/SDW-AUTO-902.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/autopilot/tasks/SDW-AUTO-902.json b/autopilot/tasks/SDW-AUTO-902.json index 6099e7b..1d23209 100644 --- a/autopilot/tasks/SDW-AUTO-902.json +++ b/autopilot/tasks/SDW-AUTO-902.json @@ -22,7 +22,8 @@ "The rejected helper returned `receipt=None` before parsing whenever `crawl.sqlite3` was a symlink, so a valid one-page receipt's failure payload regressed from its real crawl ID and verified-page count to an empty ID and zero; receipt validity and state validity must remain separate.", "Transaction eeec5b9e-6560-463d-83ed-54ed5e2b59cd invoked the producer once but candidate audit rejected an ignored generated path; resume correctly ended at `PRODUCER_OUTCOME_UNKNOWN` without invoking the model again. A replacement producer may run checks only if it leaves no `.venv`, `.pytest_cache`, `.coverage`, `build`, `dist`, or other ignored output in the isolated candidate clone.", "Transaction 451a4c03-f2e6-45b8-a09b-8e1b7f660cc6 produced a formatted candidate that passed focused 27/27 and full 117/117 checks, bounded a 5000-page schema-invalid sentinel case to 242 bytes, and preserved valid receipt summaries for missing, corrupt, and symlinked state. It was formally rejected after a probe placed a sentinel in a schema-valid page URL and bundle path. Independent review showed that probe exercised existing downstream diagnostics rather than the malformed receipt load boundary; broad redaction would change public behavior and exceed this task, so the rejected candidate cannot be reused but the replacement must retain its narrower load-boundary semantics.", - "An explicit input-byte ceiling and open-handle protection against local concurrent replacement are not defined by the current TraceFetch receipt contract. They remain separate follow-up risks; this task must not invent a new size threshold or claim TOCTOU protection while fixing the confirmed exception and output-reflection path." + "An explicit input-byte ceiling and open-handle protection against local concurrent replacement are not defined by the current TraceFetch receipt contract. They remain separate follow-up risks; this task must not invent a new size threshold or claim TOCTOU protection while fixing the confirmed exception and output-reflection path.", + "Transaction a6bd4c8a-8fc3-4c88-9c54-503e899e2a00 produced the calibrated load-boundary behavior and passed 29 focused tests, but `ruff format --check .` rejected one multiline call in `tests/test_cli.py`: `(output / \"crawl-receipt.json\").write_text(json.dumps({\"pages\": pages}), encoding=\"utf-8\")` must remain on the formatter's single line. That candidate was formally rejected as `ruff-format-contract-failed`; the replacement must use the exact committed Ruff style without relying on a later formatting patch." ], "allowedPaths": [ "src/tracefetch/verify.py", From d17b48da03865609f04460757b633517b795b212 Mon Sep 17 00:00:00 2001 From: estelledc <150260202+estelledc@users.noreply.github.com> Date: Fri, 31 Jul 2026 20:02:00 +0800 Subject: [PATCH 10/28] =?UTF-8?q?=E8=A1=A5=E9=BD=90=20TraceFetch=20SQLite?= =?UTF-8?q?=20=E6=8D=9F=E5=9D=8F=E8=BE=B9=E7=95=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- autopilot/tasks/SDW-AUTO-902.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/autopilot/tasks/SDW-AUTO-902.json b/autopilot/tasks/SDW-AUTO-902.json index 1d23209..7a234ba 100644 --- a/autopilot/tasks/SDW-AUTO-902.json +++ b/autopilot/tasks/SDW-AUTO-902.json @@ -7,7 +7,7 @@ "patchSha256": null, "patchBytes": null }, - "objective": "Make `tracefetch verify ` preserve the published machine-error contract when `crawl-receipt.json` is syntactically invalid, invalid UTF-8, or schema-invalid. Parse the receipt through one shared load/decode/schema failure boundary: malformed or schema-invalid JSON must become the exact fixed failure `crawl receipt failed schema validation`, invalid UTF-8 must become `crawl receipt is not valid UTF-8`, and an unreadable receipt must become `crawl receipt is unreadable`; raw Pydantic, filesystem, input-value, receipt-content, and machine-path text from that load boundary must never enter those three failures. Existing diagnostics after a receipt has been successfully modeled—including page URL and bundle-path diagnostics—are outside this task and must not be rewritten. `crawl_verification_payload` must reuse the parsed receipt and failure result rather than reparsing. The CLI must exit 6, write no stdout, and write exactly one `tracefetch.error.v1` JSON object to stderr with `error=verification_failed`, `retryable=false`, and a non-empty `details.failures` list; for adversarial schema-invalid input its complete stderr must remain at most 4096 bytes and must not reflect any receipt sentinel. If the receipt itself is valid but crawl state is missing, corrupt, a symlink, or otherwise invalid, the failure payload must retain that receipt's actual crawl ID and complete-page count. Preserve the exact successful crawl-verification payload for valid receipts and keep ordinary evidence-bundle verification unchanged. Fix the shared verification layer rather than adding a CLI-only catch or duplicating receipt validation logic. Every changed Python file must already match the repository's committed Ruff formatter output; a candidate that needs a later manual formatting patch is not eligible. Producer verification must leave no ignored cache, coverage, virtualenv, build, or distribution artifact in its isolated candidate clone.", + "objective": "Make `tracefetch verify ` preserve its JSON error contract for malformed crawl receipts and corrupted crawl state. Use one shared receipt parse result. Map malformed/schema-invalid JSON, invalid UTF-8, and unreadable receipts to the exact fixed failures `crawl receipt failed schema validation`, `crawl receipt is not valid UTF-8`, and `crawl receipt is unreadable` without reflecting load-boundary exception or input text. CLI failures must exit 6 with empty stdout and one `tracefetch.error.v1` stderr object; a 5000-page schema-invalid sentinel case must stay within 4096 bytes and not echo the sentinel. A successfully modeled receipt must retain its crawl ID and complete-page count when state is missing, corrupt, or a symlink. SQLite query and row-projection `sqlite3.Error`, `TypeError`, `ValueError`, or `OverflowError` must become `crawl SQLite state is invalid`, never a traceback. Preserve valid crawl payloads, ordinary evidence verification, and existing downstream page diagnostics. Fix the shared verification layer, keep all changed Python files Ruff-formatted, and leave no ignored producer artifact.", "problemEvidence": [ "On committed source HEAD e52d1fd8681323eacd3205677a7b3b54aec70aff, a crawl directory containing only `crawl-receipt.json` with `{}` reaches `verify_crawl_bundle`, then `crawl_verification_payload` reparses the same file and raises an uncaught Pydantic `ValidationError` with 11 missing-field errors.", "The reproduced CLI process exits 1 and prints a Python traceback, contradicting README's contract that errors are one `tracefetch.error.v1` object on stderr with stable exit codes.", @@ -23,7 +23,8 @@ "Transaction eeec5b9e-6560-463d-83ed-54ed5e2b59cd invoked the producer once but candidate audit rejected an ignored generated path; resume correctly ended at `PRODUCER_OUTCOME_UNKNOWN` without invoking the model again. A replacement producer may run checks only if it leaves no `.venv`, `.pytest_cache`, `.coverage`, `build`, `dist`, or other ignored output in the isolated candidate clone.", "Transaction 451a4c03-f2e6-45b8-a09b-8e1b7f660cc6 produced a formatted candidate that passed focused 27/27 and full 117/117 checks, bounded a 5000-page schema-invalid sentinel case to 242 bytes, and preserved valid receipt summaries for missing, corrupt, and symlinked state. It was formally rejected after a probe placed a sentinel in a schema-valid page URL and bundle path. Independent review showed that probe exercised existing downstream diagnostics rather than the malformed receipt load boundary; broad redaction would change public behavior and exceed this task, so the rejected candidate cannot be reused but the replacement must retain its narrower load-boundary semantics.", "An explicit input-byte ceiling and open-handle protection against local concurrent replacement are not defined by the current TraceFetch receipt contract. They remain separate follow-up risks; this task must not invent a new size threshold or claim TOCTOU protection while fixing the confirmed exception and output-reflection path.", - "Transaction a6bd4c8a-8fc3-4c88-9c54-503e899e2a00 produced the calibrated load-boundary behavior and passed 29 focused tests, but `ruff format --check .` rejected one multiline call in `tests/test_cli.py`: `(output / \"crawl-receipt.json\").write_text(json.dumps({\"pages\": pages}), encoding=\"utf-8\")` must remain on the formatter's single line. That candidate was formally rejected as `ruff-format-contract-failed`; the replacement must use the exact committed Ruff style without relying on a later formatting patch." + "Transaction a6bd4c8a-8fc3-4c88-9c54-503e899e2a00 produced the calibrated load-boundary behavior and passed 29 focused tests, but `ruff format --check .` rejected one multiline call in `tests/test_cli.py`: `(output / \"crawl-receipt.json\").write_text(json.dumps({\"pages\": pages}), encoding=\"utf-8\")` must remain on the formatter's single line. That candidate was formally rejected as `ruff-format-contract-failed`; the replacement must use the exact committed Ruff style without relying on a later formatting patch.", + "Transaction 94c87198-b226-4f21-a7dc-6d333bd0e194 fixed the formatter issue but left `_verify_crawl_state` row conversion outside its `sqlite3.Error` boundary. After updating `pages.depth` to the string `not-an-integer`, the public CLI still exited 1 with an uncaught `ValueError` traceback. That candidate was formally rejected as `corrupt-state-traceback`; regression coverage must tamper a schema-valid crawl SQLite row, assert the internal payload retains crawl ID and complete-page count with `crawl SQLite state is invalid`, and assert the CLI still exits 6 with one JSON error." ], "allowedPaths": [ "src/tracefetch/verify.py", From 4a5a7c75c30e4ea296a44bd8f472e04cd34df792 Mon Sep 17 00:00:00 2001 From: estelledc <150260202+estelledc@users.noreply.github.com> Date: Fri, 31 Jul 2026 20:07:28 +0800 Subject: [PATCH 11/28] =?UTF-8?q?=E5=9B=BA=E5=AE=9A=20TraceFetch=20SQLite?= =?UTF-8?q?=20=E5=9B=9E=E5=BD=92=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- autopilot/tasks/SDW-AUTO-902.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/autopilot/tasks/SDW-AUTO-902.json b/autopilot/tasks/SDW-AUTO-902.json index 7a234ba..b09d80c 100644 --- a/autopilot/tasks/SDW-AUTO-902.json +++ b/autopilot/tasks/SDW-AUTO-902.json @@ -24,7 +24,8 @@ "Transaction 451a4c03-f2e6-45b8-a09b-8e1b7f660cc6 produced a formatted candidate that passed focused 27/27 and full 117/117 checks, bounded a 5000-page schema-invalid sentinel case to 242 bytes, and preserved valid receipt summaries for missing, corrupt, and symlinked state. It was formally rejected after a probe placed a sentinel in a schema-valid page URL and bundle path. Independent review showed that probe exercised existing downstream diagnostics rather than the malformed receipt load boundary; broad redaction would change public behavior and exceed this task, so the rejected candidate cannot be reused but the replacement must retain its narrower load-boundary semantics.", "An explicit input-byte ceiling and open-handle protection against local concurrent replacement are not defined by the current TraceFetch receipt contract. They remain separate follow-up risks; this task must not invent a new size threshold or claim TOCTOU protection while fixing the confirmed exception and output-reflection path.", "Transaction a6bd4c8a-8fc3-4c88-9c54-503e899e2a00 produced the calibrated load-boundary behavior and passed 29 focused tests, but `ruff format --check .` rejected one multiline call in `tests/test_cli.py`: `(output / \"crawl-receipt.json\").write_text(json.dumps({\"pages\": pages}), encoding=\"utf-8\")` must remain on the formatter's single line. That candidate was formally rejected as `ruff-format-contract-failed`; the replacement must use the exact committed Ruff style without relying on a later formatting patch.", - "Transaction 94c87198-b226-4f21-a7dc-6d333bd0e194 fixed the formatter issue but left `_verify_crawl_state` row conversion outside its `sqlite3.Error` boundary. After updating `pages.depth` to the string `not-an-integer`, the public CLI still exited 1 with an uncaught `ValueError` traceback. That candidate was formally rejected as `corrupt-state-traceback`; regression coverage must tamper a schema-valid crawl SQLite row, assert the internal payload retains crawl ID and complete-page count with `crawl SQLite state is invalid`, and assert the CLI still exits 6 with one JSON error." + "Transaction 94c87198-b226-4f21-a7dc-6d333bd0e194 fixed the formatter issue but left `_verify_crawl_state` row conversion outside its `sqlite3.Error` boundary. After updating `pages.depth` to the string `not-an-integer`, the public CLI still exited 1 with an uncaught `ValueError` traceback. That candidate was formally rejected as `corrupt-state-traceback`; regression coverage must tamper a schema-valid crawl SQLite row, assert the internal payload retains crawl ID and complete-page count with `crawl SQLite state is invalid`, and assert the CLI still exits 6 with one JSON error.", + "Transaction 5b1625c6-77da-448e-94a5-d19d1e6da0ad implemented the SQLite conversion boundary and passed 28 focused tests, but Ruff rejected two multiline forms. The replacement must keep the verified-pages conditional expression on one formatter line, and keep `assert json.loads(captured.err)[\"details\"][\"failures\"][-1] == (\"crawl SQLite state is invalid\")` on one line; that candidate was formally rejected as `ruff-format-contract-failed` and cannot be post-formatted or reused." ], "allowedPaths": [ "src/tracefetch/verify.py", From ca8d6169fae235f84ad3663ce5345dd024798d8e Mon Sep 17 00:00:00 2001 From: estelledc <150260202+estelledc@users.noreply.github.com> Date: Fri, 31 Jul 2026 20:12:57 +0800 Subject: [PATCH 12/28] =?UTF-8?q?=E8=AE=B0=E5=BD=95=20TraceFetch=20?= =?UTF-8?q?=E7=AC=AC=E4=B8=83=E5=80=99=E9=80=89=E5=81=9C=E6=AD=A2=E6=9D=A1?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- autopilot/tasks/SDW-AUTO-902.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/autopilot/tasks/SDW-AUTO-902.json b/autopilot/tasks/SDW-AUTO-902.json index b09d80c..58afdaf 100644 --- a/autopilot/tasks/SDW-AUTO-902.json +++ b/autopilot/tasks/SDW-AUTO-902.json @@ -25,7 +25,8 @@ "An explicit input-byte ceiling and open-handle protection against local concurrent replacement are not defined by the current TraceFetch receipt contract. They remain separate follow-up risks; this task must not invent a new size threshold or claim TOCTOU protection while fixing the confirmed exception and output-reflection path.", "Transaction a6bd4c8a-8fc3-4c88-9c54-503e899e2a00 produced the calibrated load-boundary behavior and passed 29 focused tests, but `ruff format --check .` rejected one multiline call in `tests/test_cli.py`: `(output / \"crawl-receipt.json\").write_text(json.dumps({\"pages\": pages}), encoding=\"utf-8\")` must remain on the formatter's single line. That candidate was formally rejected as `ruff-format-contract-failed`; the replacement must use the exact committed Ruff style without relying on a later formatting patch.", "Transaction 94c87198-b226-4f21-a7dc-6d333bd0e194 fixed the formatter issue but left `_verify_crawl_state` row conversion outside its `sqlite3.Error` boundary. After updating `pages.depth` to the string `not-an-integer`, the public CLI still exited 1 with an uncaught `ValueError` traceback. That candidate was formally rejected as `corrupt-state-traceback`; regression coverage must tamper a schema-valid crawl SQLite row, assert the internal payload retains crawl ID and complete-page count with `crawl SQLite state is invalid`, and assert the CLI still exits 6 with one JSON error.", - "Transaction 5b1625c6-77da-448e-94a5-d19d1e6da0ad implemented the SQLite conversion boundary and passed 28 focused tests, but Ruff rejected two multiline forms. The replacement must keep the verified-pages conditional expression on one formatter line, and keep `assert json.loads(captured.err)[\"details\"][\"failures\"][-1] == (\"crawl SQLite state is invalid\")` on one line; that candidate was formally rejected as `ruff-format-contract-failed` and cannot be post-formatted or reused." + "Transaction 5b1625c6-77da-448e-94a5-d19d1e6da0ad implemented the SQLite conversion boundary and passed 28 focused tests, but Ruff rejected two multiline forms. The replacement must keep `assert json.loads(captured.err)[\"details\"][\"failures\"][-1] == (\"crawl SQLite state is invalid\")` on one line; that candidate was formally rejected as `ruff-format-contract-failed` and cannot be post-formatted or reused.", + "Transaction 8c95c95c-3d1b-4509-8c0f-6f841a7c8afd covered the corrupt-row contract and passed 31 focused tests, but Ruff rejected an inline verified-pages conditional in the result dictionary; the replacement should compute a local `verified_pages` value before constructing the dictionary so the formatter owns layout. It also mapped a receipt symlink to the legacy combined symlink message instead of the required exact `crawl receipt is unreadable`. The candidate was formally rejected as `receipt-symlink-contract-mismatch` plus `ruff-format-contract-failed`." ], "allowedPaths": [ "src/tracefetch/verify.py", From ec5b729035052ae604576e2dbdd6aa95b476f60f Mon Sep 17 00:00:00 2001 From: estelledc <150260202+estelledc@users.noreply.github.com> Date: Fri, 31 Jul 2026 20:33:04 +0800 Subject: [PATCH 13/28] =?UTF-8?q?=E7=BB=91=E5=AE=9A=20TraceFetch=20produce?= =?UTF-8?q?r=20v2=20=E6=8E=A7=E5=88=B6=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- autopilot/project.json | 7 ++++--- autopilot/tasks/SDW-AUTO-902.json | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/autopilot/project.json b/autopilot/project.json index 2ba9ecb..e31b776 100644 --- a/autopilot/project.json +++ b/autopilot/project.json @@ -14,7 +14,8 @@ "cliVersion": "0.144.1", "executableSha256": "134063e133f0b4244fa3b251acf973d4fe4b4aeeacbdc135211bf480f59f1477", "maxSeconds": 600, - "maxOutputBytes": 4194304 + "maxOutputBytes": 4194304, + "promptContract": "system-design-workbench.codex-producer-prompt.v2" }, "protectedPaths": [ ".git", @@ -80,7 +81,7 @@ }, "controllerSourceManifest": { "toolVersion": "1.0.0", - "manifestSha256": "685a54e02f7070c6b427b5bcf1094bddb0bed378fe7a582092792edb004e7e51", - "sourceSetSha256": "a54277217f12396a00d955d63f577f54e6b19c29b2fb874311fda3e6b48a8f43" + "manifestSha256": "fd4e40d860f06a24d9732ea152d124efab472efe49920552458d408a7e9f402f", + "sourceSetSha256": "843089a34bb3d0b3a5c2503538c5b02474e6e1b13d039638f57f47545d95f1f5" } } diff --git a/autopilot/tasks/SDW-AUTO-902.json b/autopilot/tasks/SDW-AUTO-902.json index 58afdaf..516eb6c 100644 --- a/autopilot/tasks/SDW-AUTO-902.json +++ b/autopilot/tasks/SDW-AUTO-902.json @@ -26,7 +26,8 @@ "Transaction a6bd4c8a-8fc3-4c88-9c54-503e899e2a00 produced the calibrated load-boundary behavior and passed 29 focused tests, but `ruff format --check .` rejected one multiline call in `tests/test_cli.py`: `(output / \"crawl-receipt.json\").write_text(json.dumps({\"pages\": pages}), encoding=\"utf-8\")` must remain on the formatter's single line. That candidate was formally rejected as `ruff-format-contract-failed`; the replacement must use the exact committed Ruff style without relying on a later formatting patch.", "Transaction 94c87198-b226-4f21-a7dc-6d333bd0e194 fixed the formatter issue but left `_verify_crawl_state` row conversion outside its `sqlite3.Error` boundary. After updating `pages.depth` to the string `not-an-integer`, the public CLI still exited 1 with an uncaught `ValueError` traceback. That candidate was formally rejected as `corrupt-state-traceback`; regression coverage must tamper a schema-valid crawl SQLite row, assert the internal payload retains crawl ID and complete-page count with `crawl SQLite state is invalid`, and assert the CLI still exits 6 with one JSON error.", "Transaction 5b1625c6-77da-448e-94a5-d19d1e6da0ad implemented the SQLite conversion boundary and passed 28 focused tests, but Ruff rejected two multiline forms. The replacement must keep `assert json.loads(captured.err)[\"details\"][\"failures\"][-1] == (\"crawl SQLite state is invalid\")` on one line; that candidate was formally rejected as `ruff-format-contract-failed` and cannot be post-formatted or reused.", - "Transaction 8c95c95c-3d1b-4509-8c0f-6f841a7c8afd covered the corrupt-row contract and passed 31 focused tests, but Ruff rejected an inline verified-pages conditional in the result dictionary; the replacement should compute a local `verified_pages` value before constructing the dictionary so the formatter owns layout. It also mapped a receipt symlink to the legacy combined symlink message instead of the required exact `crawl receipt is unreadable`. The candidate was formally rejected as `receipt-symlink-contract-mismatch` plus `ruff-format-contract-failed`." + "Transaction 8c95c95c-3d1b-4509-8c0f-6f841a7c8afd covered the corrupt-row contract and passed 31 focused tests, but Ruff rejected an inline verified-pages conditional in the result dictionary; the replacement should compute a local `verified_pages` value before constructing the dictionary so the formatter owns layout. It also mapped a receipt symlink to the legacy combined symlink message instead of the required exact `crawl receipt is unreadable`. The candidate was formally rejected as `receipt-symlink-contract-mismatch` plus `ruff-format-contract-failed`.", + "After the seventh rejected batch, the operator paused new TraceFetch transactions and changed the source-owned controller instead of retrying the same producer contract. Pushed controller commit 69f75e573e5b077878152dae75f534ed1615c33d adds prompt contract v2, which requires the producer to run declared baseline checks before editing, rerun every baseline and acceptance recipe before finishing, and remove generated ignored artifacts. The new controller source set 843089a34bb3d0b3a5c2503538c5b02474e6e1b13d039638f57f47545d95f1f5 is a real workflow delta; it authorizes exactly one fresh bounded transaction, not an automatic retry loop." ], "allowedPaths": [ "src/tracefetch/verify.py", From cff9587db73c7a31d5e864b2792c6440b54af02f Mon Sep 17 00:00:00 2001 From: estelledc <150260202+estelledc@users.noreply.github.com> Date: Fri, 31 Jul 2026 20:44:37 +0800 Subject: [PATCH 14/28] =?UTF-8?q?=E5=88=87=E6=8D=A2=20TraceFetch=20?= =?UTF-8?q?=E6=89=8B=E5=B7=A5=E5=80=99=E9=80=89=20lineage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- autopilot/tasks/SDW-AUTO-902.json | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/autopilot/tasks/SDW-AUTO-902.json b/autopilot/tasks/SDW-AUTO-902.json index 516eb6c..776e885 100644 --- a/autopilot/tasks/SDW-AUTO-902.json +++ b/autopilot/tasks/SDW-AUTO-902.json @@ -3,7 +3,7 @@ "id": "SDW-AUTO-902", "status": "DRAFT", "execution": { - "kind": "codex-patch-intent-v1", + "kind": "bound-patch-intent-v1", "patchSha256": null, "patchBytes": null }, @@ -27,7 +27,8 @@ "Transaction 94c87198-b226-4f21-a7dc-6d333bd0e194 fixed the formatter issue but left `_verify_crawl_state` row conversion outside its `sqlite3.Error` boundary. After updating `pages.depth` to the string `not-an-integer`, the public CLI still exited 1 with an uncaught `ValueError` traceback. That candidate was formally rejected as `corrupt-state-traceback`; regression coverage must tamper a schema-valid crawl SQLite row, assert the internal payload retains crawl ID and complete-page count with `crawl SQLite state is invalid`, and assert the CLI still exits 6 with one JSON error.", "Transaction 5b1625c6-77da-448e-94a5-d19d1e6da0ad implemented the SQLite conversion boundary and passed 28 focused tests, but Ruff rejected two multiline forms. The replacement must keep `assert json.loads(captured.err)[\"details\"][\"failures\"][-1] == (\"crawl SQLite state is invalid\")` on one line; that candidate was formally rejected as `ruff-format-contract-failed` and cannot be post-formatted or reused.", "Transaction 8c95c95c-3d1b-4509-8c0f-6f841a7c8afd covered the corrupt-row contract and passed 31 focused tests, but Ruff rejected an inline verified-pages conditional in the result dictionary; the replacement should compute a local `verified_pages` value before constructing the dictionary so the formatter owns layout. It also mapped a receipt symlink to the legacy combined symlink message instead of the required exact `crawl receipt is unreadable`. The candidate was formally rejected as `receipt-symlink-contract-mismatch` plus `ruff-format-contract-failed`.", - "After the seventh rejected batch, the operator paused new TraceFetch transactions and changed the source-owned controller instead of retrying the same producer contract. Pushed controller commit 69f75e573e5b077878152dae75f534ed1615c33d adds prompt contract v2, which requires the producer to run declared baseline checks before editing, rerun every baseline and acceptance recipe before finishing, and remove generated ignored artifacts. The new controller source set 843089a34bb3d0b3a5c2503538c5b02474e6e1b13d039638f57f47545d95f1f5 is a real workflow delta; it authorizes exactly one fresh bounded transaction, not an automatic retry loop." + "After the seventh rejected batch, the operator paused new TraceFetch transactions and changed the source-owned controller instead of retrying the same producer contract. Pushed controller commit 69f75e573e5b077878152dae75f534ed1615c33d adds prompt contract v2, which requires the producer to run declared baseline checks before editing, rerun every baseline and acceptance recipe before finishing, and remove generated ignored artifacts. The new controller source set 843089a34bb3d0b3a5c2503538c5b02474e6e1b13d039638f57f47545d95f1f5 is a real workflow delta; it authorizes exactly one fresh bounded transaction, not an automatic retry loop.", + "Transaction ac575b5c-4441-4f68-a037-c725097dba7f ran once under prompt contract v2 and returned `candidate-empty`; resume moved the persisted latch to terminal `PRODUCER_OUTCOME_UNKNOWN` without a second model invocation, candidate installation, product write, commit, or push. The operator therefore stopped producer retries and selected the source-owned manual prepared-patch path. The independently authored three-file patch is 11818 bytes with SHA-256 152fd43e9de0efdd87b9b8dc5fcfa154a4616e5f37cc444d4547247ff81edb08; it passed 31 focused tests and full `make check` with 121 tests and 82.74% coverage before binding." ], "allowedPaths": [ "src/tracefetch/verify.py", @@ -67,12 +68,12 @@ }, "authority": { "write": true, - "invokeAgent": true, + "invokeAgent": false, "applyPatch": false, "commit": false, "push": false, "release": false, - "network": true + "network": false }, "externalOutcome": "An independent reviewer accepts a frozen candidate that converts malformed crawl-receipt verification into the documented JSON error and exit-6 contract while valid crawl and ordinary evidence verification remain unchanged; after the frozen lineage closes, the exact product commit is pushed on its lineage branch without claiming PR, merge, release, or production acceptance.", "stopConditions": [ From ae23d7125fdb330b7bfd1c0f764094b79debcd5c Mon Sep 17 00:00:00 2001 From: estelledc <150260202+estelledc@users.noreply.github.com> Date: Fri, 31 Jul 2026 20:46:06 +0800 Subject: [PATCH 15/28] =?UTF-8?q?=E5=AE=89=E8=A3=85=20SDW-AUTO-902=20?= =?UTF-8?q?=E6=89=8B=E5=B7=A5=E5=80=99=E9=80=89=E7=BB=91=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- autopilot/tasks/SDW-AUTO-902.json | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/autopilot/tasks/SDW-AUTO-902.json b/autopilot/tasks/SDW-AUTO-902.json index 776e885..8c010d1 100644 --- a/autopilot/tasks/SDW-AUTO-902.json +++ b/autopilot/tasks/SDW-AUTO-902.json @@ -1,11 +1,28 @@ { "schemaVersion": "system-design-workbench.autopilot-task.v1", "id": "SDW-AUTO-902", - "status": "DRAFT", + "status": "READY", "execution": { - "kind": "bound-patch-intent-v1", - "patchSha256": null, - "patchBytes": null + "kind": "prepared-bound-patch-v1", + "patchSha256": "152fd43e9de0efdd87b9b8dc5fcfa154a4616e5f37cc444d4547247ff81edb08", + "patchBytes": 11818, + "preparation": { + "sourceHead": "cff9587db73c7a31d5e864b2792c6440b54af02f", + "project": { + "path": "autopilot/project.json", + "fileSha256": "4729153e18f731cc6c3f1450320eab479d44147140cdd9a1c63b7949b209e412", + "gitBlobOid": "e31b776e55cee1136758764ced3af1f60b6ff04b", + "canonicalSha256": "942ec6ad09ddc509953064ce3be3bea397c818875ad6138cefccf0de778b65d8" + }, + "task": { + "path": "autopilot/tasks/SDW-AUTO-902.json", + "fileSha256": "417f5e634d62b8decbe2df0b911724ef032b8bd67aa863cecaef9de153647fad", + "gitBlobOid": "776e8857d300c2a7afa2d24b268a142c1e145eed", + "canonicalSha256": "e57c2d195c215a940ffa3be19ba92200000ab26993555fec563171d0e3e197a3" + }, + "candidateSnapshotSha256": "cb7dc6e8f230db839bc2f58fa8dc1d114392afafe13d7a6f52870c66a31ef808", + "producer": null + } }, "objective": "Make `tracefetch verify ` preserve its JSON error contract for malformed crawl receipts and corrupted crawl state. Use one shared receipt parse result. Map malformed/schema-invalid JSON, invalid UTF-8, and unreadable receipts to the exact fixed failures `crawl receipt failed schema validation`, `crawl receipt is not valid UTF-8`, and `crawl receipt is unreadable` without reflecting load-boundary exception or input text. CLI failures must exit 6 with empty stdout and one `tracefetch.error.v1` stderr object; a 5000-page schema-invalid sentinel case must stay within 4096 bytes and not echo the sentinel. A successfully modeled receipt must retain its crawl ID and complete-page count when state is missing, corrupt, or a symlink. SQLite query and row-projection `sqlite3.Error`, `TypeError`, `ValueError`, or `OverflowError` must become `crawl SQLite state is invalid`, never a traceback. Preserve valid crawl payloads, ordinary evidence verification, and existing downstream page diagnostics. Fix the shared verification layer, keep all changed Python files Ruff-formatted, and leave no ignored producer artifact.", "problemEvidence": [ @@ -69,7 +86,7 @@ "authority": { "write": true, "invokeAgent": false, - "applyPatch": false, + "applyPatch": true, "commit": false, "push": false, "release": false, From 6279a0323730aeee707e07e1ac2d6fc82796cf0d Mon Sep 17 00:00:00 2001 From: estelledc <150260202+estelledc@users.noreply.github.com> Date: Fri, 31 Jul 2026 20:54:02 +0800 Subject: [PATCH 16/28] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=8A=93=E5=8F=96?= =?UTF-8?q?=E6=94=B6=E6=8D=AE=E4=B8=8E=E7=8A=B6=E6=80=81=E9=AA=8C=E8=AF=81?= =?UTF-8?q?=E8=BE=B9=E7=95=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tracefetch/verify.py | 83 ++++++++++++++++++++++++------------ tests/test_cli.py | 56 ++++++++++++++++++++++++ tests/test_crawl.py | 92 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 203 insertions(+), 28 deletions(-) diff --git a/src/tracefetch/verify.py b/src/tracefetch/verify.py index f3a68e7..a5d8c4a 100644 --- a/src/tracefetch/verify.py +++ b/src/tracefetch/verify.py @@ -207,17 +207,31 @@ def verification_payload(bundle_dir: Path) -> dict[str, object]: } -def verify_crawl_bundle(root_dir: Path) -> list[str]: +def _load_crawl_receipt(receipt_path: Path) -> tuple[CrawlReceipt | None, list[str]]: + if receipt_path.is_symlink(): + return None, ["crawl receipt is unreadable"] + try: + source = receipt_path.read_text(encoding="utf-8") + except UnicodeError: + return None, ["crawl receipt is not valid UTF-8"] + except OSError: + return None, ["crawl receipt is unreadable"] + try: + return CrawlReceipt.model_validate_json(source), [] + except ValidationError: + return None, ["crawl receipt failed schema validation"] + + +def _crawl_verification_result(root_dir: Path) -> tuple[CrawlReceipt | None, list[str]]: failures: list[str] = [] root = root_dir.resolve() receipt_path = root_dir / "crawl-receipt.json" state_path = root_dir / "crawl.sqlite3" - if receipt_path.is_symlink() or state_path.is_symlink(): - return ["crawl receipt and state must not be symlinks"] - try: - receipt = CrawlReceipt.model_validate_json(receipt_path.read_text(encoding="utf-8")) - except (OSError, UnicodeError, ValidationError) as exc: - return [f"invalid crawl receipt: {exc}"] + receipt, receipt_failures = _load_crawl_receipt(receipt_path) + if receipt is None: + return None, receipt_failures + if state_path.is_symlink(): + return receipt, ["crawl receipt and state must not be symlinks"] try: state_digest = _sha256_file(state_path) except OSError as exc: @@ -273,25 +287,33 @@ def verify_crawl_bundle(root_dir: Path) -> list[str]: if receipt.status != expected_status: failures.append("crawl status does not match page records") failures.extend(_verify_crawl_state(receipt, state_path)) - return failures + return receipt, failures + + +def verify_crawl_bundle(root_dir: Path) -> list[str]: + return _crawl_verification_result(root_dir)[1] def crawl_verification_payload(root_dir: Path) -> dict[str, object]: - failures = verify_crawl_bundle(root_dir) - receipt = CrawlReceipt.model_validate_json( - (root_dir / "crawl-receipt.json").read_text(encoding="utf-8") + receipt, failures = _crawl_verification_result(root_dir) + verified_pages = ( + 0 if receipt is None else sum(page.status == "complete" for page in receipt.pages) ) return { "schema_version": "tracefetch.crawl-verification.v1", - "crawl_id": receipt.crawl_id, + "crawl_id": "" if receipt is None else receipt.crawl_id, "valid": not failures, - "verified_pages": sum(page.status == "complete" for page in receipt.pages), + "verified_pages": verified_pages, "failures": failures, } def _verify_crawl_state(receipt: CrawlReceipt, state_path: Path) -> list[str]: failures: list[str] = [] + connection: sqlite3.Connection | None = None + metadata: dict[str, str] = {} + state_pages: list[dict[str, object]] = [] + invalid_state = False try: connection = sqlite3.connect(f"file:{state_path}?mode=ro", uri=True) connection.row_factory = sqlite3.Row @@ -305,11 +327,27 @@ def _verify_crawl_state(receipt: CrawlReceipt, state_path: Path) -> list[str]: "from pages order by depth, added_at" ) ) - except sqlite3.Error as exc: - return [f"invalid crawl SQLite state: {exc}"] + state_pages = [ + { + "url": str(row["url"]), + "depth": int(row["depth"]), + "status": "pending" if row["status"] == "running" else str(row["status"]), + "bundle_path": row["bundle_path"], + "error_code": row["error_code"], + "error_message": row["error_message"], + } + for row in rows + ] + except (sqlite3.Error, TypeError, ValueError, OverflowError): + invalid_state = True finally: - if "connection" in locals(): - connection.close() + if connection is not None: + try: + connection.close() + except sqlite3.Error: + invalid_state = True + if invalid_state: + return ["crawl SQLite state is invalid"] expected_metadata = { "crawl_id": receipt.crawl_id, "root_url": receipt.root_url, @@ -321,17 +359,6 @@ def _verify_crawl_state(receipt: CrawlReceipt, state_path: Path) -> list[str]: for key, expected in expected_metadata.items(): if metadata.get(key) != expected: failures.append(f"crawl state metadata mismatch: {key}") - state_pages = [ - { - "url": str(row["url"]), - "depth": int(row["depth"]), - "status": "pending" if row["status"] == "running" else str(row["status"]), - "bundle_path": row["bundle_path"], - "error_code": row["error_code"], - "error_message": row["error_message"], - } - for row in rows - ] receipt_pages = [ page.model_dump( mode="json", diff --git a/tests/test_cli.py b/tests/test_cli.py index a61bec4..de65a63 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -49,6 +49,62 @@ def test_cli_verification_error_has_stable_json_exit( assert error["retryable"] is False +@pytest.mark.parametrize( + ("contents", "failure"), + [ + (b"{", "crawl receipt failed schema validation"), + (b"{}", "crawl receipt failed schema validation"), + (b"\xff", "crawl receipt is not valid UTF-8"), + ], +) +def test_cli_crawl_receipt_load_errors_are_fixed_and_bounded( + tmp_path: Path, + capsys: pytest.CaptureFixture[str], + contents: bytes, + failure: str, +) -> None: + output = tmp_path / "crawl" + output.mkdir() + (output / "crawl-receipt.json").write_bytes(contents) + + with pytest.raises(SystemExit) as exit_info: + main(["verify", str(output), "--json"]) + + captured = capsys.readouterr() + assert exit_info.value.code == 6 + assert captured.out == "" + error = json.loads(captured.err) + assert error["schema_version"] == "tracefetch.error.v1" + assert error["error"] == "verification_failed" + assert error["details"]["failures"] == [failure] + assert len(captured.err.encode("utf-8")) <= 4096 + assert "Traceback" not in captured.err + assert "pydantic_core" not in captured.err + assert "ValidationError" not in captured.err + + +def test_cli_large_invalid_crawl_receipt_does_not_reflect_input( + tmp_path: Path, capsys: pytest.CaptureFixture[str] +) -> None: + sentinel = "TOP_SECRET_SENTINEL" + output = tmp_path / "crawl" + output.mkdir() + pages = [{"url": f"https://example.test/{index}/{sentinel}"} for index in range(5000)] + (output / "crawl-receipt.json").write_text(json.dumps({"pages": pages}), encoding="utf-8") + + with pytest.raises(SystemExit) as exit_info: + main(["verify", str(output), "--json"]) + + captured = capsys.readouterr() + assert exit_info.value.code == 6 + assert captured.out == "" + assert len(captured.err.encode("utf-8")) <= 4096 + assert sentinel not in captured.err + assert json.loads(captured.err)["details"]["failures"] == [ + "crawl receipt failed schema validation" + ] + + def test_cli_schema_and_doctor_emit_machine_readable_json( capsys: pytest.CaptureFixture[str], ) -> None: diff --git a/tests/test_crawl.py b/tests/test_crawl.py index ba9c379..12557b7 100644 --- a/tests/test_crawl.py +++ b/tests/test_crawl.py @@ -9,6 +9,7 @@ from tracefetch.adapters.base import ReaderResult from tracefetch.bundle import BundleResult, write_bundle +from tracefetch.cli import main from tracefetch.config import Policy from tracefetch.contracts import Attempt, LinkRecord from tracefetch.crawl import CrawlStore, crawl_site, policy_sha256 @@ -223,6 +224,97 @@ def test_crawl_sqlite_tamper_is_rejected(tmp_path: Path, monkeypatch: pytest.Mon assert "crawl state metadata mismatch: max_depth" in failures +@pytest.mark.parametrize( + ("contents", "failure"), + [ + (b"{", "crawl receipt failed schema validation"), + (b"{}", "crawl receipt failed schema validation"), + (b"\xff", "crawl receipt is not valid UTF-8"), + ], +) +def test_crawl_payload_keeps_receipt_load_failures_inside_the_contract( + tmp_path: Path, contents: bytes, failure: str +) -> None: + output = tmp_path / "crawl" + output.mkdir() + (output / "crawl-receipt.json").write_bytes(contents) + + assert crawl_verification_payload(output) == { + "schema_version": "tracefetch.crawl-verification.v1", + "crawl_id": "", + "valid": False, + "verified_pages": 0, + "failures": [failure], + } + + +def test_crawl_receipt_symlink_is_a_fixed_unreadable_failure(tmp_path: Path) -> None: + output = tmp_path / "crawl" + output.mkdir() + outside = tmp_path / "outside-receipt.json" + outside.write_text("{}", encoding="utf-8") + (output / "crawl-receipt.json").symlink_to(outside) + + assert crawl_verification_payload(output)["failures"] == ["crawl receipt is unreadable"] + + +@pytest.mark.parametrize("state_kind", ["missing", "corrupt", "symlink"]) +def test_valid_receipt_summary_survives_invalid_crawl_state( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, + state_kind: str, +) -> None: + output = tmp_path / "crawl" + install_fake_fetch(monkeypatch, {ROOT: []}) + receipt = crawl_site(ROOT, output, reader="direct", policy=policy(), resume=False) + state_path = output / "crawl.sqlite3" + if state_kind == "missing": + state_path.unlink() + elif state_kind == "corrupt": + state_path.write_bytes(b"not a SQLite database") + else: + outside = tmp_path / "outside.sqlite3" + outside.write_bytes(state_path.read_bytes()) + state_path.unlink() + state_path.symlink_to(outside) + + payload = crawl_verification_payload(output) + + assert payload["valid"] is False + assert payload["crawl_id"] == receipt.crawl_id + assert payload["verified_pages"] == 1 + if state_kind == "symlink": + assert payload["failures"] == ["crawl receipt and state must not be symlinks"] + + +def test_invalid_sqlite_row_is_fixed_for_payload_and_cli( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, + capsys: pytest.CaptureFixture[str], +) -> None: + output = tmp_path / "crawl" + install_fake_fetch(monkeypatch, {ROOT: []}) + receipt = crawl_site(ROOT, output, reader="direct", policy=policy(), resume=False) + connection = sqlite3.connect(output / "crawl.sqlite3") + connection.execute("update pages set depth = 'not-an-integer'") + connection.commit() + connection.close() + + payload = crawl_verification_payload(output) + assert payload["crawl_id"] == receipt.crawl_id + assert payload["verified_pages"] == 1 + assert payload["failures"][-1] == "crawl SQLite state is invalid" + + with pytest.raises(SystemExit) as exit_info: + main(["verify", str(output), "--json"]) + + captured = capsys.readouterr() + assert exit_info.value.code == 6 + assert captured.out == "" + assert json.loads(captured.err)["details"]["failures"][-1] == "crawl SQLite state is invalid" + assert "Traceback" not in captured.err + + def test_failed_page_counts_are_recomputed_from_page_records( tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: From a7fae409056aecba042acf5320cbc858da3df7a0 Mon Sep 17 00:00:00 2001 From: estelledc <150260202+estelledc@users.noreply.github.com> Date: Fri, 31 Jul 2026 22:26:26 +0800 Subject: [PATCH 17/28] =?UTF-8?q?=E7=99=BB=E8=AE=B0=20903=20=E6=9C=89?= =?UTF-8?q?=E7=95=8C=E8=AF=BB=E5=8F=96=E4=BB=BB=E5=8A=A1=E5=B9=B6=E7=BB=91?= =?UTF-8?q?=E5=AE=9A=E6=96=B0=E6=8E=A7=E5=88=B6=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- autopilot/project.json | 4 +- autopilot/tasks/SDW-AUTO-903.json | 79 +++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 autopilot/tasks/SDW-AUTO-903.json diff --git a/autopilot/project.json b/autopilot/project.json index e31b776..80388aa 100644 --- a/autopilot/project.json +++ b/autopilot/project.json @@ -81,7 +81,7 @@ }, "controllerSourceManifest": { "toolVersion": "1.0.0", - "manifestSha256": "fd4e40d860f06a24d9732ea152d124efab472efe49920552458d408a7e9f402f", - "sourceSetSha256": "843089a34bb3d0b3a5c2503538c5b02474e6e1b13d039638f57f47545d95f1f5" + "manifestSha256": "0fbb587d6a940ebfdd11f01a1927dee86f169d22c6d5247ec91ad2a2312932f7", + "sourceSetSha256": "fede99b03f5d876ae2128086f674dfe4e2cc687098cfdbf37b8ea9531a38af24" } } diff --git a/autopilot/tasks/SDW-AUTO-903.json b/autopilot/tasks/SDW-AUTO-903.json new file mode 100644 index 0000000..8ba2689 --- /dev/null +++ b/autopilot/tasks/SDW-AUTO-903.json @@ -0,0 +1,79 @@ +{ + "schemaVersion": "system-design-workbench.autopilot-task.v1", + "id": "SDW-AUTO-903", + "status": "DRAFT", + "execution": { + "kind": "codex-patch-intent-v1", + "patchSha256": null, + "patchBytes": null + }, + "objective": "Add a bounded open-handle crawl-receipt read so `tracefetch verify ` cannot allocate unbounded memory from `crawl-receipt.json` and cannot parse a locally replaced file. The shared verification layer must open the receipt with `O_NOFOLLOW`, keep one file descriptor, fstat before and after, read in bounded chunks with a short-read check, and require identical device, inode, and byte size. A receipt larger than 16 MiB maps to the exact fixed failure `crawl receipt exceeds the size limit`; a receipt that changes while being read maps to `crawl receipt changed during verification`; symlink, unreadable, invalid UTF-8, and schema-invalid receipts keep the existing exact messages `crawl receipt is unreadable`, `crawl receipt is not valid UTF-8`, and `crawl receipt failed schema validation` without reflecting exception text, receipt content, or local paths. CLI failures must exit 6 with empty stdout and one bounded `tracefetch.error.v1` stderr object under 4096 bytes. A valid receipt must retain crawl ID, complete-page count, and `valid=true`; missing, corrupt, or symlinked SQLite state summaries from SDW-AUTO-902 must not regress. Preserve ordinary `receipt.json` verification and downstream page diagnostics, keep all changed Python files Ruff-formatted, and leave no ignored producer artifact.", + "problemEvidence": [ + "The SDW-AUTO-902 lineage explicitly deferred this risk: an explicit input-byte ceiling and open-handle protection against local concurrent replacement are not defined by the current TraceFetch receipt contract, and the 902 fix was forbidden from inventing them.", + "`_load_crawl_receipt` currently calls `Path.is_symlink()` and then `Path.read_text(encoding=\"utf-8\")`, which fully allocates the decoded file before validation; an arbitrarily large `crawl-receipt.json` therefore drives unbounded memory use with no fixed failure boundary.", + "The symlink check and the open are separate path operations, so a local concurrent writer can replace `crawl-receipt.json` between them and verification parses a different file than the one that was checked.", + "The existing fixed load-boundary messages, the 5000-page sentinel bounds, and the shared single-parse result were accepted in SDW-AUTO-902 and must remain byte-for-byte unchanged for schema-invalid, invalid UTF-8, and unreadable receipts.", + "Regression coverage must call the public CLI entry point for an oversized receipt and for a mid-read truncation, asserting exit 6, empty stdout, one parseable error envelope, total stderr no larger than 4096 bytes, and no receipt-content sentinel reflection.", + "Direct verification-layer regressions must assert the exact failure list for the two new fixed messages and must prove a valid one-page receipt still yields its real crawl ID and complete-page count when the SQLite state is missing, corrupt, or a symlink.", + "The fix must live in the shared verification layer used by both `verify_crawl_bundle` and `crawl_verification_payload`; catching the failure only in `cli.py` leaves the payload caller unsafe.", + "The candidate must keep all changed Python files Ruff-formatted and must not leave `.venv`, `.pytest_cache`, `.coverage`, `build`, `dist`, or other ignored artifacts in the isolated candidate clone.", + "This task authorizes exactly one fresh bounded transaction, not an automatic retry loop; if the producer returns `candidate-empty`, the operator will fail it closed and use the source-owned manual prepared-patch path as in SDW-AUTO-902." + ], + "allowedPaths": [ + "src/tracefetch/verify.py", + "tests/test_cli.py", + "tests/test_crawl.py" + ], + "forbiddenPaths": [ + ".github", + ".gitignore", + "CHANGELOG.md", + "LICENSE", + "Makefile", + "README.md", + "autopilot", + "pyproject.toml", + "schemas", + "scripts", + "src/tracefetch/cli.py", + "src/tracefetch/contracts.py", + "src/tracefetch/crawl.py", + "src/tracefetch/errors.py", + "uv.lock" + ], + "baselineChecks": [ + "cli-test", + "crawl-test" + ], + "acceptanceChecks": [ + "cli-test", + "crawl-test", + "project-check" + ], + "budget": { + "maxMinutes": 30, + "maxChangedFiles": 3, + "maxVerificationAttempts": 1 + }, + "authority": { + "write": true, + "invokeAgent": true, + "applyPatch": false, + "commit": false, + "push": false, + "release": false, + "network": true + }, + "externalOutcome": "An independent reviewer accepts a frozen candidate that adds the bounded open-handle crawl-receipt read with the two fixed failure messages while valid crawl and ordinary evidence verification remain unchanged; after the frozen lineage closes, the exact product commit is pushed on its lineage branch without claiming PR, merge, release, or production acceptance.", + "stopConditions": [ + "budget-exhausted", + "verification-attempts-exhausted", + "scope-expansion-required", + "protected-path-change-required", + "dirty-working-tree", + "task-input-changed", + "new-authority-required", + "receipt-boundary-contract-requires-behavior-change" + ], + "requiresChanges": true +} From e53c5db7ea0c25c71617e95dfacf89d052fead57 Mon Sep 17 00:00:00 2001 From: estelledc <150260202+estelledc@users.noreply.github.com> Date: Fri, 31 Jul 2026 22:28:58 +0800 Subject: [PATCH 18/28] =?UTF-8?q?=E5=88=87=E6=8D=A2=20903=20=E6=89=8B?= =?UTF-8?q?=E5=B7=A5=E5=80=99=E9=80=89=20lineage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- autopilot/tasks/SDW-AUTO-903.json | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/autopilot/tasks/SDW-AUTO-903.json b/autopilot/tasks/SDW-AUTO-903.json index 8ba2689..787eb00 100644 --- a/autopilot/tasks/SDW-AUTO-903.json +++ b/autopilot/tasks/SDW-AUTO-903.json @@ -3,7 +3,7 @@ "id": "SDW-AUTO-903", "status": "DRAFT", "execution": { - "kind": "codex-patch-intent-v1", + "kind": "bound-patch-intent-v1", "patchSha256": null, "patchBytes": null }, @@ -17,7 +17,8 @@ "Direct verification-layer regressions must assert the exact failure list for the two new fixed messages and must prove a valid one-page receipt still yields its real crawl ID and complete-page count when the SQLite state is missing, corrupt, or a symlink.", "The fix must live in the shared verification layer used by both `verify_crawl_bundle` and `crawl_verification_payload`; catching the failure only in `cli.py` leaves the payload caller unsafe.", "The candidate must keep all changed Python files Ruff-formatted and must not leave `.venv`, `.pytest_cache`, `.coverage`, `build`, `dist`, or other ignored artifacts in the isolated candidate clone.", - "This task authorizes exactly one fresh bounded transaction, not an automatic retry loop; if the producer returns `candidate-empty`, the operator will fail it closed and use the source-owned manual prepared-patch path as in SDW-AUTO-902." + "This task authorizes exactly one fresh bounded transaction, not an automatic retry loop; if the producer returns `candidate-empty`, the operator will fail it closed and use the source-owned manual prepared-patch path as in SDW-AUTO-902.", + "Transaction 4ad9cb8b-dd52-41ea-afa6-02398e528229 ran once under prompt contract v2 and failed at the Codex process boundary with `codex-exec-failed` before installing any candidate or receipt; resume moved the persisted latch to terminal `PRODUCER_OUTCOME_UNKNOWN` without a second model invocation, product write, commit, or push. The operator therefore stopped producer retries and selected the source-owned manual prepared-patch path." ], "allowedPaths": [ "src/tracefetch/verify.py", @@ -57,12 +58,12 @@ }, "authority": { "write": true, - "invokeAgent": true, + "invokeAgent": false, "applyPatch": false, "commit": false, "push": false, "release": false, - "network": true + "network": false }, "externalOutcome": "An independent reviewer accepts a frozen candidate that adds the bounded open-handle crawl-receipt read with the two fixed failure messages while valid crawl and ordinary evidence verification remain unchanged; after the frozen lineage closes, the exact product commit is pushed on its lineage branch without claiming PR, merge, release, or production acceptance.", "stopConditions": [ From 4e34d2292a4b5315b8d1595c49b16f32ca136b09 Mon Sep 17 00:00:00 2001 From: estelledc <150260202+estelledc@users.noreply.github.com> Date: Fri, 31 Jul 2026 22:29:22 +0800 Subject: [PATCH 19/28] =?UTF-8?q?=E5=AE=89=E8=A3=85=20SDW-AUTO-903=20?= =?UTF-8?q?=E6=89=8B=E5=B7=A5=E5=80=99=E9=80=89=E7=BB=91=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- autopilot/tasks/SDW-AUTO-903.json | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/autopilot/tasks/SDW-AUTO-903.json b/autopilot/tasks/SDW-AUTO-903.json index 787eb00..b79ff9d 100644 --- a/autopilot/tasks/SDW-AUTO-903.json +++ b/autopilot/tasks/SDW-AUTO-903.json @@ -1,11 +1,28 @@ { "schemaVersion": "system-design-workbench.autopilot-task.v1", "id": "SDW-AUTO-903", - "status": "DRAFT", + "status": "READY", "execution": { - "kind": "bound-patch-intent-v1", - "patchSha256": null, - "patchBytes": null + "kind": "prepared-bound-patch-v1", + "patchSha256": "db891b31290487c4c84aa101517c5d3e4dc89a0926c6cfe5fe5956cbe162f980", + "patchBytes": 7991, + "preparation": { + "sourceHead": "e53c5db7ea0c25c71617e95dfacf89d052fead57", + "project": { + "path": "autopilot/project.json", + "fileSha256": "d2f1e89e8857477de0c0bc0ff146520929ef542ce8d60eb04deffecad2117ba4", + "gitBlobOid": "80388aaf9961622a139398df38326c39d1619b4f", + "canonicalSha256": "75ee3877eee92352025bd212299b68918be915999834e2407d2f2a2f1351d295" + }, + "task": { + "path": "autopilot/tasks/SDW-AUTO-903.json", + "fileSha256": "c064da6df401043cf0218bf56ff3fe933bff6674023338caf241dccf52a1bcda", + "gitBlobOid": "787eb00ce96ad6c57e4de092fbef34bdeb0739ba", + "canonicalSha256": "d9c1638fe487f4a89123f9f0938701d103b26b8c4c9bc07cf647eccccb5561db" + }, + "candidateSnapshotSha256": "7b95b0e62b766074ed49ef063aa620ce1ba3bb545197bb4c000f933706a8cc47", + "producer": null + } }, "objective": "Add a bounded open-handle crawl-receipt read so `tracefetch verify ` cannot allocate unbounded memory from `crawl-receipt.json` and cannot parse a locally replaced file. The shared verification layer must open the receipt with `O_NOFOLLOW`, keep one file descriptor, fstat before and after, read in bounded chunks with a short-read check, and require identical device, inode, and byte size. A receipt larger than 16 MiB maps to the exact fixed failure `crawl receipt exceeds the size limit`; a receipt that changes while being read maps to `crawl receipt changed during verification`; symlink, unreadable, invalid UTF-8, and schema-invalid receipts keep the existing exact messages `crawl receipt is unreadable`, `crawl receipt is not valid UTF-8`, and `crawl receipt failed schema validation` without reflecting exception text, receipt content, or local paths. CLI failures must exit 6 with empty stdout and one bounded `tracefetch.error.v1` stderr object under 4096 bytes. A valid receipt must retain crawl ID, complete-page count, and `valid=true`; missing, corrupt, or symlinked SQLite state summaries from SDW-AUTO-902 must not regress. Preserve ordinary `receipt.json` verification and downstream page diagnostics, keep all changed Python files Ruff-formatted, and leave no ignored producer artifact.", "problemEvidence": [ @@ -59,7 +76,7 @@ "authority": { "write": true, "invokeAgent": false, - "applyPatch": false, + "applyPatch": true, "commit": false, "push": false, "release": false, From b7e14ee304dfa2859b7cb115533fc22f4dccc35f Mon Sep 17 00:00:00 2001 From: zhouxun Date: Sat, 1 Aug 2026 11:26:18 +0800 Subject: [PATCH 20/28] =?UTF-8?q?=E7=99=BB=E8=AE=B0=20904=20=E6=94=B6?= =?UTF-8?q?=E6=8D=AE=E8=AF=BB=E5=8F=96=E5=AE=A1=E6=9F=A5=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- autopilot/tasks/SDW-AUTO-904.json | 75 +++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 autopilot/tasks/SDW-AUTO-904.json diff --git a/autopilot/tasks/SDW-AUTO-904.json b/autopilot/tasks/SDW-AUTO-904.json new file mode 100644 index 0000000..ac6c783 --- /dev/null +++ b/autopilot/tasks/SDW-AUTO-904.json @@ -0,0 +1,75 @@ +{ + "schemaVersion": "system-design-workbench.autopilot-task.v1", + "id": "SDW-AUTO-904", + "status": "DRAFT", + "execution": { + "kind": "bound-patch-intent-v1", + "patchSha256": null, + "patchBytes": null + }, + "objective": "Replace the rejected SDW-AUTO-903 crawl-receipt reader with a bounded open-handle implementation that satisfies the full read contract. Keep one O_NOFOLLOW file descriptor, fstat before and after, reject files larger than 16 MiB with `crawl receipt exceeds the size limit`, classify any non-full chunk as `crawl receipt changed during verification`, and map read OSError to the existing fixed `crawl receipt is unreadable` failure. Preserve fixed invalid UTF-8 and schema messages, valid crawl summaries, ordinary receipt verification, SQLite-state diagnostics, CLI exit 6, empty stdout, bounded non-reflective JSON stderr, and Ruff formatting.", + "problemEvidence": [ + "Independent review of frozen SDW-AUTO-903 snapshot 387526abb2a0adc618b4fcc357ba6ddd936b394f0c0b41de521f31dd9a2c4c1d rejected it with `read-error-unhandled` and `short-read-undetected`; the v2 review decision was recorded and that one-shot lineage terminated without product commit or push.", + "A local injected `_read_receipt_chunk` OSError escaped `_load_crawl_receipt` instead of returning `crawl receipt is unreadable`, violating the fixed shared-layer failure contract and allowing a public CLI traceback.", + "A local injected partial read returned fewer bytes than requested but non-empty data; the candidate continued reading and later reported schema validation rather than `crawl receipt changed during verification`.", + "The minimal fix is at the shared chunk loop: catch OSError and require every read to return exactly the requested byte count. Regression tests must exercise both direct payload behavior and public CLI bounded output.", + "The complete candidate also retains the original 903 oversized, mid-read truncation, exact-size, valid-summary, ordinary-receipt, and downstream diagnostics coverage because the rejected patch was never committed as a product change." + ], + "allowedPaths": [ + "src/tracefetch/verify.py", + "tests/test_cli.py", + "tests/test_crawl.py" + ], + "forbiddenPaths": [ + ".github", + ".gitignore", + "CHANGELOG.md", + "LICENSE", + "Makefile", + "README.md", + "autopilot", + "pyproject.toml", + "schemas", + "scripts", + "src/tracefetch/cli.py", + "src/tracefetch/contracts.py", + "src/tracefetch/crawl.py", + "src/tracefetch/errors.py", + "uv.lock" + ], + "baselineChecks": [ + "cli-test", + "crawl-test" + ], + "acceptanceChecks": [ + "cli-test", + "crawl-test", + "project-check" + ], + "budget": { + "maxMinutes": 30, + "maxChangedFiles": 3, + "maxVerificationAttempts": 1 + }, + "authority": { + "write": true, + "invokeAgent": false, + "applyPatch": false, + "commit": false, + "push": false, + "release": false, + "network": false + }, + "externalOutcome": "An independent reviewer accepts a frozen successor candidate that closes both concrete SDW-AUTO-903 review findings while preserving the full bounded crawl-receipt contract; after close, the exact product commit is pushed on its lineage branch and a delivery ref-match observation is exported without claiming PR, merge, release, or production acceptance.", + "stopConditions": [ + "budget-exhausted", + "verification-attempts-exhausted", + "scope-expansion-required", + "protected-path-change-required", + "dirty-working-tree", + "task-input-changed", + "new-authority-required", + "receipt-boundary-contract-requires-behavior-change" + ], + "requiresChanges": true +} From bcb19834fc442a16e2a4d6a50527de0bbd408a9b Mon Sep 17 00:00:00 2001 From: zhouxun Date: Sat, 1 Aug 2026 11:26:31 +0800 Subject: [PATCH 21/28] =?UTF-8?q?=E5=AE=89=E8=A3=85=20SDW-AUTO-904=20?= =?UTF-8?q?=E6=89=8B=E5=B7=A5=E5=80=99=E9=80=89=E7=BB=91=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- autopilot/tasks/SDW-AUTO-904.json | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/autopilot/tasks/SDW-AUTO-904.json b/autopilot/tasks/SDW-AUTO-904.json index ac6c783..ad84103 100644 --- a/autopilot/tasks/SDW-AUTO-904.json +++ b/autopilot/tasks/SDW-AUTO-904.json @@ -1,11 +1,28 @@ { "schemaVersion": "system-design-workbench.autopilot-task.v1", "id": "SDW-AUTO-904", - "status": "DRAFT", + "status": "READY", "execution": { - "kind": "bound-patch-intent-v1", - "patchSha256": null, - "patchBytes": null + "kind": "prepared-bound-patch-v1", + "patchSha256": "3887749bbeed11f02680d5c5073349b33ec78a83b59caba363781b4a1850eece", + "patchBytes": 10372, + "preparation": { + "sourceHead": "b7e14ee304dfa2859b7cb115533fc22f4dccc35f", + "project": { + "path": "autopilot/project.json", + "fileSha256": "d2f1e89e8857477de0c0bc0ff146520929ef542ce8d60eb04deffecad2117ba4", + "gitBlobOid": "80388aaf9961622a139398df38326c39d1619b4f", + "canonicalSha256": "75ee3877eee92352025bd212299b68918be915999834e2407d2f2a2f1351d295" + }, + "task": { + "path": "autopilot/tasks/SDW-AUTO-904.json", + "fileSha256": "bda6da5ad8bd842d45a5b9af2b91711a53915a20830ea17505fd12691caff13f", + "gitBlobOid": "ac6c7838eb5c9bab2c543eb735a74ce399837a0f", + "canonicalSha256": "c93247bcd2869b7f846617dcb14fc990096dcaee0474e7a0a50fbf7876180fc7" + }, + "candidateSnapshotSha256": "8ea0347a42089ffda61728fb367080ba13be59d3437bb67a57139b7cad00a581", + "producer": null + } }, "objective": "Replace the rejected SDW-AUTO-903 crawl-receipt reader with a bounded open-handle implementation that satisfies the full read contract. Keep one O_NOFOLLOW file descriptor, fstat before and after, reject files larger than 16 MiB with `crawl receipt exceeds the size limit`, classify any non-full chunk as `crawl receipt changed during verification`, and map read OSError to the existing fixed `crawl receipt is unreadable` failure. Preserve fixed invalid UTF-8 and schema messages, valid crawl summaries, ordinary receipt verification, SQLite-state diagnostics, CLI exit 6, empty stdout, bounded non-reflective JSON stderr, and Ruff formatting.", "problemEvidence": [ @@ -54,7 +71,7 @@ "authority": { "write": true, "invokeAgent": false, - "applyPatch": false, + "applyPatch": true, "commit": false, "push": false, "release": false, From e8bb6b4b8635b6566442d11c8e7650d3b6ed2e22 Mon Sep 17 00:00:00 2001 From: zhouxun Date: Sat, 1 Aug 2026 11:27:58 +0800 Subject: [PATCH 22/28] =?UTF-8?q?=E7=99=BB=E8=AE=B0=20905=20=E6=94=B6?= =?UTF-8?q?=E6=8D=AE=E8=AF=BB=E5=8F=96=E5=AE=8C=E6=95=B4=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- autopilot/tasks/SDW-AUTO-905.json | 75 +++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 autopilot/tasks/SDW-AUTO-905.json diff --git a/autopilot/tasks/SDW-AUTO-905.json b/autopilot/tasks/SDW-AUTO-905.json new file mode 100644 index 0000000..15fa985 --- /dev/null +++ b/autopilot/tasks/SDW-AUTO-905.json @@ -0,0 +1,75 @@ +{ + "schemaVersion": "system-design-workbench.autopilot-task.v1", + "id": "SDW-AUTO-905", + "status": "DRAFT", + "execution": { + "kind": "bound-patch-intent-v1", + "patchSha256": null, + "patchBytes": null + }, + "objective": "Implement the complete bounded open-handle crawl-receipt read after the rejected 903 candidate and the environment-only failed 904 claim. Keep one O_NOFOLLOW file descriptor; classify open, fstat, read, and close boundary failures without traceback; fstat before and after; reject more than 16 MiB with `crawl receipt exceeds the size limit`; classify every non-full read as `crawl receipt changed during verification`; and map read or metadata OSError to `crawl receipt is unreadable`. Preserve exact invalid UTF-8/schema failures, valid crawl summaries, ordinary receipt verification, SQLite diagnostics, CLI exit 6, empty stdout, bounded non-reflective JSON stderr, and Ruff formatting.", + "problemEvidence": [ + "Independent review of frozen SDW-AUTO-903 snapshot 387526abb2a0adc618b4fcc357ba6ddd936b394f0c0b41de521f31dd9a2c4c1d rejected it with recorded reason codes `read-error-unhandled` and `short-read-undetected`; that lineage terminated without product commit or push.", + "Injected read OSError escaped the shared verification layer, while an injected non-empty partial read was misclassified as schema-invalid instead of changed-during-verification.", + "Additional review showed fstat and close are OSError boundaries too: fstat must map to the fixed unreadable result, while close must never replace the already-determined fixed verification result with an exception.", + "SDW-AUTO-904 never applied a product patch: its one claim terminated at generation 2 because the isolated clone lacked the locked dev extra and both declared baseline commands could not spawn pytest. The failed runtime was archived outside the target, the dev environment was installed from uv.lock, and both unmodified baseline files then passed (11 CLI tests and 20 crawl tests).", + "The successor candidate retains the complete 903 scope and adds direct and public-CLI regressions for short read, read OSError, and metadata OSError. Only the three original product/test paths are required." + ], + "allowedPaths": [ + "src/tracefetch/verify.py", + "tests/test_cli.py", + "tests/test_crawl.py" + ], + "forbiddenPaths": [ + ".github", + ".gitignore", + "CHANGELOG.md", + "LICENSE", + "Makefile", + "README.md", + "autopilot", + "pyproject.toml", + "schemas", + "scripts", + "src/tracefetch/cli.py", + "src/tracefetch/contracts.py", + "src/tracefetch/crawl.py", + "src/tracefetch/errors.py", + "uv.lock" + ], + "baselineChecks": [ + "cli-test", + "crawl-test" + ], + "acceptanceChecks": [ + "cli-test", + "crawl-test", + "project-check" + ], + "budget": { + "maxMinutes": 30, + "maxChangedFiles": 3, + "maxVerificationAttempts": 1 + }, + "authority": { + "write": true, + "invokeAgent": false, + "applyPatch": false, + "commit": false, + "push": false, + "release": false, + "network": false + }, + "externalOutcome": "An independent reviewer accepts a frozen successor candidate that closes all concrete 903 review findings and the additional metadata/close error boundary while preserving the full bounded receipt contract; after close, the exact product commit is pushed on its lineage branch and a delivery ref-match observation is exported without claiming PR, merge, release, or production acceptance.", + "stopConditions": [ + "budget-exhausted", + "verification-attempts-exhausted", + "scope-expansion-required", + "protected-path-change-required", + "dirty-working-tree", + "task-input-changed", + "new-authority-required", + "receipt-boundary-contract-requires-behavior-change" + ], + "requiresChanges": true +} From 2764ca54b9fc4871fb8654791068df023accd6f7 Mon Sep 17 00:00:00 2001 From: zhouxun Date: Sat, 1 Aug 2026 11:28:09 +0800 Subject: [PATCH 23/28] =?UTF-8?q?=E5=AE=89=E8=A3=85=20SDW-AUTO-905=20?= =?UTF-8?q?=E6=89=8B=E5=B7=A5=E5=80=99=E9=80=89=E7=BB=91=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- autopilot/tasks/SDW-AUTO-905.json | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/autopilot/tasks/SDW-AUTO-905.json b/autopilot/tasks/SDW-AUTO-905.json index 15fa985..0fcde86 100644 --- a/autopilot/tasks/SDW-AUTO-905.json +++ b/autopilot/tasks/SDW-AUTO-905.json @@ -1,11 +1,28 @@ { "schemaVersion": "system-design-workbench.autopilot-task.v1", "id": "SDW-AUTO-905", - "status": "DRAFT", + "status": "READY", "execution": { - "kind": "bound-patch-intent-v1", - "patchSha256": null, - "patchBytes": null + "kind": "prepared-bound-patch-v1", + "patchSha256": "38d9126043109ebf2388139d6841e82d19f17efe884d620d6d3f961de4cf7f77", + "patchBytes": 11144, + "preparation": { + "sourceHead": "e8bb6b4b8635b6566442d11c8e7650d3b6ed2e22", + "project": { + "path": "autopilot/project.json", + "fileSha256": "d2f1e89e8857477de0c0bc0ff146520929ef542ce8d60eb04deffecad2117ba4", + "gitBlobOid": "80388aaf9961622a139398df38326c39d1619b4f", + "canonicalSha256": "75ee3877eee92352025bd212299b68918be915999834e2407d2f2a2f1351d295" + }, + "task": { + "path": "autopilot/tasks/SDW-AUTO-905.json", + "fileSha256": "3438d131269f78304a39dde7fca369e3218509eecdb56e3e49e6da5329631be7", + "gitBlobOid": "15fa9852e72ade6306c358b8914801bcf16503f4", + "canonicalSha256": "8be4bab909f00e3db8102ddf9462e43d1b7ff0948e3065e3b71c3f1e2cfb4216" + }, + "candidateSnapshotSha256": "85c904004f94f2c62ad814760bac480ce8bbfa29312c8f0f4cfa820aac5f4ef9", + "producer": null + } }, "objective": "Implement the complete bounded open-handle crawl-receipt read after the rejected 903 candidate and the environment-only failed 904 claim. Keep one O_NOFOLLOW file descriptor; classify open, fstat, read, and close boundary failures without traceback; fstat before and after; reject more than 16 MiB with `crawl receipt exceeds the size limit`; classify every non-full read as `crawl receipt changed during verification`; and map read or metadata OSError to `crawl receipt is unreadable`. Preserve exact invalid UTF-8/schema failures, valid crawl summaries, ordinary receipt verification, SQLite diagnostics, CLI exit 6, empty stdout, bounded non-reflective JSON stderr, and Ruff formatting.", "problemEvidence": [ @@ -54,7 +71,7 @@ "authority": { "write": true, "invokeAgent": false, - "applyPatch": false, + "applyPatch": true, "commit": false, "push": false, "release": false, From 726c0f279eeb2029cb3f8600c66478d7a292520c Mon Sep 17 00:00:00 2001 From: zhouxun Date: Sat, 1 Aug 2026 11:29:21 +0800 Subject: [PATCH 24/28] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=8A=93=E5=8F=96?= =?UTF-8?q?=E6=94=B6=E6=8D=AE=E6=9C=89=E7=95=8C=E8=AF=BB=E5=8F=96=E8=BE=B9?= =?UTF-8?q?=E7=95=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tracefetch/verify.py | 58 ++++++++++++++++++--- tests/test_cli.py | 84 ++++++++++++++++++++++++++++++ tests/test_crawl.py | 107 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 242 insertions(+), 7 deletions(-) diff --git a/src/tracefetch/verify.py b/src/tracefetch/verify.py index a5d8c4a..cafc3a4 100644 --- a/src/tracefetch/verify.py +++ b/src/tracefetch/verify.py @@ -2,8 +2,11 @@ import hashlib import json +import os import sqlite3 +import stat from collections import Counter +from contextlib import suppress from pathlib import Path from typing import Any @@ -18,6 +21,8 @@ LinkRecord, ) +MAX_CRAWL_RECEIPT_BYTES = 16 * 1024 * 1024 + def verify_bundle(bundle_dir: Path) -> list[str]: failures: list[str] = [] @@ -207,15 +212,56 @@ def verification_payload(bundle_dir: Path) -> dict[str, object]: } +def _read_receipt_chunk(descriptor: int, size: int) -> bytes: + return os.read(descriptor, size) + + +def _read_crawl_receipt_bytes(receipt_path: Path) -> tuple[bytes | None, str | None]: + try: + descriptor = os.open(receipt_path, os.O_RDONLY | os.O_NOFOLLOW) + except OSError: + return None, "crawl receipt is unreadable" + try: + before = os.fstat(descriptor) + if not stat.S_ISREG(before.st_mode): + return None, "crawl receipt is unreadable" + if before.st_size > MAX_CRAWL_RECEIPT_BYTES: + return None, "crawl receipt exceeds the size limit" + chunks: list[bytes] = [] + remaining = before.st_size + while remaining > 0: + requested = min(65536, remaining) + try: + chunk = _read_receipt_chunk(descriptor, requested) + except OSError: + return None, "crawl receipt is unreadable" + if len(chunk) != requested: + return None, "crawl receipt changed during verification" + chunks.append(chunk) + remaining -= len(chunk) + after = os.fstat(descriptor) + if (before.st_dev, before.st_ino, before.st_size) != ( + after.st_dev, + after.st_ino, + after.st_size, + ): + return None, "crawl receipt changed during verification" + return b"".join(chunks), None + except OSError: + return None, "crawl receipt is unreadable" + finally: + with suppress(OSError): + os.close(descriptor) + + def _load_crawl_receipt(receipt_path: Path) -> tuple[CrawlReceipt | None, list[str]]: - if receipt_path.is_symlink(): - return None, ["crawl receipt is unreadable"] + received, failure = _read_crawl_receipt_bytes(receipt_path) + if received is None: + return None, [failure or "crawl receipt is unreadable"] try: - source = receipt_path.read_text(encoding="utf-8") - except UnicodeError: + source = received.decode("utf-8") + except UnicodeDecodeError: return None, ["crawl receipt is not valid UTF-8"] - except OSError: - return None, ["crawl receipt is unreadable"] try: return CrawlReceipt.model_validate_json(source), [] except ValidationError: diff --git a/tests/test_cli.py b/tests/test_cli.py index de65a63..0200999 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -5,12 +5,14 @@ import pytest +import tracefetch.verify as verify_module from tracefetch.cli import main from tracefetch.contracts import ( SearchResultAttempt, SearchResultCandidate, SearchResultsEnvelope, ) +from tracefetch.verify import MAX_CRAWL_RECEIPT_BYTES def test_cli_ingest_and_verify_round_trip( @@ -105,6 +107,88 @@ def test_cli_large_invalid_crawl_receipt_does_not_reflect_input( ] +def test_cli_oversized_crawl_receipt_stays_bounded_and_silent( + tmp_path: Path, + capsys: pytest.CaptureFixture[str], +) -> None: + sentinel = "TOP_SECRET_SENTINEL" + output = tmp_path / "crawl" + output.mkdir() + (output / "crawl-receipt.json").write_bytes( + b"x" * (MAX_CRAWL_RECEIPT_BYTES + 1) + sentinel.encode() + ) + + with pytest.raises(SystemExit) as exit_info: + main(["verify", str(output), "--json"]) + + captured = capsys.readouterr() + assert exit_info.value.code == 6 + assert captured.out == "" + assert len(captured.err.encode("utf-8")) <= 4096 + assert sentinel not in captured.err + assert json.loads(captured.err)["details"]["failures"] == [ + "crawl receipt exceeds the size limit" + ] + + +def test_cli_crawl_receipt_truncated_mid_read_fails_closed( + tmp_path: Path, + capsys: pytest.CaptureFixture[str], + monkeypatch: pytest.MonkeyPatch, +) -> None: + output = tmp_path / "crawl" + output.mkdir() + (output / "crawl-receipt.json").write_bytes(b"x" * 70000) + real = verify_module._read_receipt_chunk + calls = 0 + + def fake(descriptor: int, size: int) -> bytes: + nonlocal calls + calls += 1 + if calls >= 2: + return b"" + return real(descriptor, size) + + monkeypatch.setattr(verify_module, "_read_receipt_chunk", fake) + + with pytest.raises(SystemExit) as exit_info: + main(["verify", str(output), "--json"]) + + captured = capsys.readouterr() + assert exit_info.value.code == 6 + assert captured.out == "" + assert json.loads(captured.err)["details"]["failures"] == [ + "crawl receipt changed during verification" + ] + assert "Traceback" not in captured.err + + +def test_cli_crawl_receipt_read_error_is_fixed_and_bounded( + tmp_path: Path, + capsys: pytest.CaptureFixture[str], + monkeypatch: pytest.MonkeyPatch, +) -> None: + output = tmp_path / "crawl" + output.mkdir() + (output / "crawl-receipt.json").write_bytes(b"TOP_SECRET_SENTINEL") + + def fake(descriptor: int, size: int) -> bytes: + raise OSError("injected read failure") + + monkeypatch.setattr(verify_module, "_read_receipt_chunk", fake) + + with pytest.raises(SystemExit) as exit_info: + main(["verify", str(output), "--json"]) + + captured = capsys.readouterr() + assert exit_info.value.code == 6 + assert captured.out == "" + assert len(captured.err.encode("utf-8")) <= 4096 + assert "TOP_SECRET_SENTINEL" not in captured.err + assert json.loads(captured.err)["details"]["failures"] == ["crawl receipt is unreadable"] + assert "Traceback" not in captured.err + + def test_cli_schema_and_doctor_emit_machine_readable_json( capsys: pytest.CaptureFixture[str], ) -> None: diff --git a/tests/test_crawl.py b/tests/test_crawl.py index 12557b7..fbb9a67 100644 --- a/tests/test_crawl.py +++ b/tests/test_crawl.py @@ -1,12 +1,14 @@ from __future__ import annotations import json +import os import sqlite3 from datetime import UTC, datetime from pathlib import Path import pytest +import tracefetch.verify as verify_module from tracefetch.adapters.base import ReaderResult from tracefetch.bundle import BundleResult, write_bundle from tracefetch.cli import main @@ -15,7 +17,11 @@ from tracefetch.crawl import CrawlStore, crawl_site, policy_sha256 from tracefetch.errors import InvalidInputError from tracefetch.normalize import NormalizedDocument -from tracefetch.verify import crawl_verification_payload, verify_crawl_bundle +from tracefetch.verify import ( + MAX_CRAWL_RECEIPT_BYTES, + crawl_verification_payload, + verify_crawl_bundle, +) NOW = datetime(2026, 7, 24, tzinfo=UTC) ROOT = "https://example.test/" @@ -258,6 +264,105 @@ def test_crawl_receipt_symlink_is_a_fixed_unreadable_failure(tmp_path: Path) -> assert crawl_verification_payload(output)["failures"] == ["crawl receipt is unreadable"] +def test_oversized_crawl_receipt_is_a_fixed_bounded_failure(tmp_path: Path) -> None: + output = tmp_path / "crawl" + output.mkdir() + (output / "crawl-receipt.json").write_bytes(b"x" * (MAX_CRAWL_RECEIPT_BYTES + 1)) + + assert crawl_verification_payload(output) == { + "schema_version": "tracefetch.crawl-verification.v1", + "crawl_id": "", + "valid": False, + "verified_pages": 0, + "failures": ["crawl receipt exceeds the size limit"], + } + + +def test_crawl_receipt_at_the_size_limit_is_not_rejected_as_oversized(tmp_path: Path) -> None: + output = tmp_path / "crawl" + output.mkdir() + (output / "crawl-receipt.json").write_bytes(b"x" * MAX_CRAWL_RECEIPT_BYTES) + + assert crawl_verification_payload(output)["failures"] == [ + "crawl receipt failed schema validation" + ] + + +def test_crawl_receipt_truncated_mid_read_fails_closed( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + output = tmp_path / "crawl" + output.mkdir() + (output / "crawl-receipt.json").write_bytes(b"x" * 70000) + real = verify_module._read_receipt_chunk + calls = 0 + + def fake(descriptor: int, size: int) -> bytes: + nonlocal calls + calls += 1 + if calls >= 2: + return b"" + return real(descriptor, size) + + monkeypatch.setattr(verify_module, "_read_receipt_chunk", fake) + + assert crawl_verification_payload(output)["failures"] == [ + "crawl receipt changed during verification" + ] + + +def test_crawl_receipt_short_read_fails_closed( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + output = tmp_path / "crawl" + output.mkdir() + (output / "crawl-receipt.json").write_bytes(b"{}") + real = verify_module._read_receipt_chunk + + def fake(descriptor: int, size: int) -> bytes: + return real(descriptor, size - 1) + + monkeypatch.setattr(verify_module, "_read_receipt_chunk", fake) + + assert crawl_verification_payload(output)["failures"] == [ + "crawl receipt changed during verification" + ] + + +def test_crawl_receipt_read_error_is_a_fixed_unreadable_failure( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + output = tmp_path / "crawl" + output.mkdir() + (output / "crawl-receipt.json").write_bytes(b"{}") + + def fake(descriptor: int, size: int) -> bytes: + raise OSError("injected read failure") + + monkeypatch.setattr(verify_module, "_read_receipt_chunk", fake) + + assert crawl_verification_payload(output)["failures"] == ["crawl receipt is unreadable"] + + +def test_crawl_receipt_fstat_error_is_a_fixed_unreadable_failure( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + output = tmp_path / "crawl" + output.mkdir() + (output / "crawl-receipt.json").write_bytes(b"{}") + + def fake(descriptor: int) -> os.stat_result: + raise OSError("injected fstat failure") + + monkeypatch.setattr(verify_module.os, "fstat", fake) + + assert crawl_verification_payload(output)["failures"] == ["crawl receipt is unreadable"] + + @pytest.mark.parametrize("state_kind", ["missing", "corrupt", "symlink"]) def test_valid_receipt_summary_survives_invalid_crawl_state( tmp_path: Path, From e3c0b5d8ed3e10d95e1edb850a0303acab027859 Mon Sep 17 00:00:00 2001 From: zhouxun Date: Sat, 1 Aug 2026 15:36:26 +0800 Subject: [PATCH 25/28] =?UTF-8?q?=E7=99=BB=E8=AE=B0=20906=20=E6=94=B6?= =?UTF-8?q?=E6=8D=AE=E5=AE=89=E5=85=A8=E6=89=93=E5=BC=80=E4=BB=BB=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- autopilot/tasks/SDW-AUTO-906.json | 75 +++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 autopilot/tasks/SDW-AUTO-906.json diff --git a/autopilot/tasks/SDW-AUTO-906.json b/autopilot/tasks/SDW-AUTO-906.json new file mode 100644 index 0000000..1388411 --- /dev/null +++ b/autopilot/tasks/SDW-AUTO-906.json @@ -0,0 +1,75 @@ +{ + "schemaVersion": "system-design-workbench.autopilot-task.v1", + "id": "SDW-AUTO-906", + "status": "DRAFT", + "execution": { + "kind": "bound-patch-intent-v1", + "patchSha256": null, + "patchBytes": null + }, + "objective": "Make the bounded crawl-receipt open itself non-blocking and fail closed when the required safe-open primitives are unavailable. The shared verification layer must require both `os.O_NOFOLLOW` and `os.O_NONBLOCK`, open with `O_RDONLY | O_NOFOLLOW | O_NONBLOCK`, and map a missing flag or any open/fstat/read boundary failure to the fixed `crawl receipt is unreadable` result without traceback. A FIFO, socket, device, directory, or other non-regular target must never block verification and must remain unreadable. Preserve the accepted SDW-AUTO-905 16 MiB limit, exact full-chunk rule, pre/post fstat identity and size check, valid crawl summaries, malformed UTF-8/schema failures, CLI exit 6/empty stdout/bounded non-reflective JSON stderr, ordinary evidence verification, and all existing diagnostics.", + "problemEvidence": [ + "Post-push review of product commit 726c0f279eeb2029cb3f8600c66478d7a292520c found that `_read_crawl_receipt_bytes` calls `os.open` with `O_RDONLY | O_NOFOLLOW` and only rejects non-regular files after open. Opening a FIFO read-only can block before fstat, so a local special-file replacement can hang `tracefetch verify` despite the later regular-file check.", + "The source-owned Autopilot controller already uses `O_NOFOLLOW | O_NONBLOCK` for untrusted local file reads. The receipt reader should apply the same primitive rather than claiming a bounded verification path while open itself remains unbounded in time.", + "A local capability probe confirmed Darwin exposes both flags. A deterministic monkeypatch deleting `os.O_NOFOLLOW` made the current shared loader escape `AttributeError` instead of returning the fixed unreadable failure. Platforms lacking either primitive must fail closed; silently dropping no-follow or non-blocking semantics would weaken the stated security contract.", + "Regression coverage must exercise the public payload/CLI contract for missing safe-open flags without globally mutating unrelated os users, and a POSIX-only real FIFO subprocess probe must demonstrate verification returns promptly with exit 6, empty stdout, one bounded JSON error, and no traceback.", + "The final change belongs only in `src/tracefetch/verify.py`, `tests/test_cli.py`, and `tests/test_crawl.py`; no public schema or producer behavior change is required." + ], + "allowedPaths": [ + "src/tracefetch/verify.py", + "tests/test_cli.py", + "tests/test_crawl.py" + ], + "forbiddenPaths": [ + ".github", + ".gitignore", + "CHANGELOG.md", + "LICENSE", + "Makefile", + "README.md", + "autopilot", + "pyproject.toml", + "schemas", + "scripts", + "src/tracefetch/cli.py", + "src/tracefetch/contracts.py", + "src/tracefetch/crawl.py", + "src/tracefetch/errors.py", + "uv.lock" + ], + "baselineChecks": [ + "cli-test", + "crawl-test" + ], + "acceptanceChecks": [ + "cli-test", + "crawl-test", + "project-check" + ], + "budget": { + "maxMinutes": 30, + "maxChangedFiles": 3, + "maxVerificationAttempts": 1 + }, + "authority": { + "write": true, + "invokeAgent": false, + "applyPatch": false, + "commit": false, + "push": false, + "release": false, + "network": false + }, + "externalOutcome": "An independent reviewer accepts a frozen successor candidate proving crawl receipt verification cannot block on a FIFO and cannot weaken safe-open semantics when O_NOFOLLOW or O_NONBLOCK is unavailable; after close, the exact direct-successor product commit is pushed and a fresh delivery ref-match observation is exported without claiming PR, merge, release, or production acceptance.", + "stopConditions": [ + "budget-exhausted", + "verification-attempts-exhausted", + "scope-expansion-required", + "protected-path-change-required", + "dirty-working-tree", + "task-input-changed", + "new-authority-required", + "safe-open-contract-requires-platform-fallback" + ], + "requiresChanges": true +} From cfda888e5ec20e7b37a273849430a2c15233c42b Mon Sep 17 00:00:00 2001 From: zhouxun Date: Sat, 1 Aug 2026 15:38:04 +0800 Subject: [PATCH 26/28] =?UTF-8?q?=E6=A0=A1=E5=87=86=20906=20FIFO=20?= =?UTF-8?q?=E9=AA=8C=E6=94=B6=E8=BE=B9=E7=95=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- autopilot/tasks/SDW-AUTO-906.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autopilot/tasks/SDW-AUTO-906.json b/autopilot/tasks/SDW-AUTO-906.json index 1388411..1d5eac3 100644 --- a/autopilot/tasks/SDW-AUTO-906.json +++ b/autopilot/tasks/SDW-AUTO-906.json @@ -12,7 +12,7 @@ "Post-push review of product commit 726c0f279eeb2029cb3f8600c66478d7a292520c found that `_read_crawl_receipt_bytes` calls `os.open` with `O_RDONLY | O_NOFOLLOW` and only rejects non-regular files after open. Opening a FIFO read-only can block before fstat, so a local special-file replacement can hang `tracefetch verify` despite the later regular-file check.", "The source-owned Autopilot controller already uses `O_NOFOLLOW | O_NONBLOCK` for untrusted local file reads. The receipt reader should apply the same primitive rather than claiming a bounded verification path while open itself remains unbounded in time.", "A local capability probe confirmed Darwin exposes both flags. A deterministic monkeypatch deleting `os.O_NOFOLLOW` made the current shared loader escape `AttributeError` instead of returning the fixed unreadable failure. Platforms lacking either primitive must fail closed; silently dropping no-follow or non-blocking semantics would weaken the stated security contract.", - "Regression coverage must exercise the public payload/CLI contract for missing safe-open flags without globally mutating unrelated os users, and a POSIX-only real FIFO subprocess probe must demonstrate verification returns promptly with exit 6, empty stdout, one bounded JSON error, and no traceback.", + "Regression coverage must exercise the public payload/CLI contract for missing safe-open flags without globally mutating unrelated os users. A POSIX-only real FIFO subprocess probe must call the shared verification layer directly and demonstrate it returns the fixed unreadable payload promptly without traceback; the existing CLI target classifier may reject a FIFO earlier as an invalid path and is outside this three-file task.", "The final change belongs only in `src/tracefetch/verify.py`, `tests/test_cli.py`, and `tests/test_crawl.py`; no public schema or producer behavior change is required." ], "allowedPaths": [ From f3ea48072ee36b96d98be7d067b49e4c5f4c0643 Mon Sep 17 00:00:00 2001 From: zhouxun Date: Sat, 1 Aug 2026 15:39:02 +0800 Subject: [PATCH 27/28] =?UTF-8?q?=E5=AE=89=E8=A3=85=20SDW-AUTO-906=20?= =?UTF-8?q?=E6=89=8B=E5=B7=A5=E5=80=99=E9=80=89=E7=BB=91=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- autopilot/tasks/SDW-AUTO-906.json | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/autopilot/tasks/SDW-AUTO-906.json b/autopilot/tasks/SDW-AUTO-906.json index 1d5eac3..d787ff5 100644 --- a/autopilot/tasks/SDW-AUTO-906.json +++ b/autopilot/tasks/SDW-AUTO-906.json @@ -1,11 +1,28 @@ { "schemaVersion": "system-design-workbench.autopilot-task.v1", "id": "SDW-AUTO-906", - "status": "DRAFT", + "status": "READY", "execution": { - "kind": "bound-patch-intent-v1", - "patchSha256": null, - "patchBytes": null + "kind": "prepared-bound-patch-v1", + "patchSha256": "0ae0b5557ae6df2ce43018b62fa88df781d8bf09a4ca87c5ea38e2b2cf944cf8", + "patchBytes": 5016, + "preparation": { + "sourceHead": "cfda888e5ec20e7b37a273849430a2c15233c42b", + "project": { + "path": "autopilot/project.json", + "fileSha256": "d2f1e89e8857477de0c0bc0ff146520929ef542ce8d60eb04deffecad2117ba4", + "gitBlobOid": "80388aaf9961622a139398df38326c39d1619b4f", + "canonicalSha256": "75ee3877eee92352025bd212299b68918be915999834e2407d2f2a2f1351d295" + }, + "task": { + "path": "autopilot/tasks/SDW-AUTO-906.json", + "fileSha256": "39d6f4e707d2f5bdb2aff8cfe954a9bc7728b71d200eef069f2ce230d047277d", + "gitBlobOid": "1d5eac3b0dccf7a3121ca7c12daafd66825bb75d", + "canonicalSha256": "0005df509781c4166992d39b2e6ac44c50407ed9d31276075c050f4b4874f177" + }, + "candidateSnapshotSha256": "cc0dd3c84e2c44d130246b081918377c310116056d8c6deefeba8f8af4cbe31f", + "producer": null + } }, "objective": "Make the bounded crawl-receipt open itself non-blocking and fail closed when the required safe-open primitives are unavailable. The shared verification layer must require both `os.O_NOFOLLOW` and `os.O_NONBLOCK`, open with `O_RDONLY | O_NOFOLLOW | O_NONBLOCK`, and map a missing flag or any open/fstat/read boundary failure to the fixed `crawl receipt is unreadable` result without traceback. A FIFO, socket, device, directory, or other non-regular target must never block verification and must remain unreadable. Preserve the accepted SDW-AUTO-905 16 MiB limit, exact full-chunk rule, pre/post fstat identity and size check, valid crawl summaries, malformed UTF-8/schema failures, CLI exit 6/empty stdout/bounded non-reflective JSON stderr, ordinary evidence verification, and all existing diagnostics.", "problemEvidence": [ @@ -54,7 +71,7 @@ "authority": { "write": true, "invokeAgent": false, - "applyPatch": false, + "applyPatch": true, "commit": false, "push": false, "release": false, From 49e0fd036ff302f9edb53a6cd87e6de52d66ec6f Mon Sep 17 00:00:00 2001 From: zhouxun Date: Sat, 1 Aug 2026 15:40:16 +0800 Subject: [PATCH 28/28] =?UTF-8?q?=E6=94=B6=E7=B4=A7=E6=8A=93=E5=8F=96?= =?UTF-8?q?=E6=94=B6=E6=8D=AE=E5=AE=89=E5=85=A8=E6=89=93=E5=BC=80=E8=BE=B9?= =?UTF-8?q?=E7=95=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tracefetch/verify.py | 6 ++++- tests/test_cli.py | 31 +++++++++++++++++++++++ tests/test_crawl.py | 53 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 1 deletion(-) diff --git a/src/tracefetch/verify.py b/src/tracefetch/verify.py index cafc3a4..a0847c5 100644 --- a/src/tracefetch/verify.py +++ b/src/tracefetch/verify.py @@ -217,8 +217,12 @@ def _read_receipt_chunk(descriptor: int, size: int) -> bytes: def _read_crawl_receipt_bytes(receipt_path: Path) -> tuple[bytes | None, str | None]: + nofollow = getattr(os, "O_NOFOLLOW", None) + nonblock = getattr(os, "O_NONBLOCK", None) + if not isinstance(nofollow, int) or not isinstance(nonblock, int): + return None, "crawl receipt is unreadable" try: - descriptor = os.open(receipt_path, os.O_RDONLY | os.O_NOFOLLOW) + descriptor = os.open(receipt_path, os.O_RDONLY | nofollow | nonblock) except OSError: return None, "crawl receipt is unreadable" try: diff --git a/tests/test_cli.py b/tests/test_cli.py index 0200999..cfd376d 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -2,6 +2,7 @@ import json from pathlib import Path +from types import SimpleNamespace import pytest @@ -189,6 +190,36 @@ def fake(descriptor: int, size: int) -> bytes: assert "Traceback" not in captured.err +@pytest.mark.parametrize("missing_flag", ["O_NOFOLLOW", "O_NONBLOCK"]) +def test_cli_crawl_receipt_requires_safe_open_flags( + tmp_path: Path, + capsys: pytest.CaptureFixture[str], + monkeypatch: pytest.MonkeyPatch, + missing_flag: str, +) -> None: + output = tmp_path / "crawl" + output.mkdir() + (output / "crawl-receipt.json").write_bytes(b"TOP_SECRET_SENTINEL") + available = { + "O_NOFOLLOW": verify_module.os.O_NOFOLLOW, + "O_NONBLOCK": verify_module.os.O_NONBLOCK, + "O_RDONLY": verify_module.os.O_RDONLY, + } + del available[missing_flag] + monkeypatch.setattr(verify_module, "os", SimpleNamespace(**available)) + + with pytest.raises(SystemExit) as exit_info: + main(["verify", str(output), "--json"]) + + captured = capsys.readouterr() + assert exit_info.value.code == 6 + assert captured.out == "" + assert len(captured.err.encode("utf-8")) <= 4096 + assert "TOP_SECRET_SENTINEL" not in captured.err + assert json.loads(captured.err)["details"]["failures"] == ["crawl receipt is unreadable"] + assert "Traceback" not in captured.err + + def test_cli_schema_and_doctor_emit_machine_readable_json( capsys: pytest.CaptureFixture[str], ) -> None: diff --git a/tests/test_crawl.py b/tests/test_crawl.py index fbb9a67..ae003ad 100644 --- a/tests/test_crawl.py +++ b/tests/test_crawl.py @@ -3,8 +3,11 @@ import json import os import sqlite3 +import subprocess +import sys from datetime import UTC, datetime from pathlib import Path +from types import SimpleNamespace import pytest @@ -363,6 +366,56 @@ def fake(descriptor: int) -> os.stat_result: assert crawl_verification_payload(output)["failures"] == ["crawl receipt is unreadable"] +@pytest.mark.parametrize("missing_flag", ["O_NOFOLLOW", "O_NONBLOCK"]) +def test_crawl_receipt_requires_safe_open_flags( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, + missing_flag: str, +) -> None: + output = tmp_path / "crawl" + output.mkdir() + (output / "crawl-receipt.json").write_bytes(b"{}") + available = { + "O_NOFOLLOW": os.O_NOFOLLOW, + "O_NONBLOCK": os.O_NONBLOCK, + "O_RDONLY": os.O_RDONLY, + } + del available[missing_flag] + monkeypatch.setattr(verify_module, "os", SimpleNamespace(**available)) + + assert crawl_verification_payload(output)["failures"] == ["crawl receipt is unreadable"] + + +@pytest.mark.skipif(not hasattr(os, "mkfifo"), reason="requires POSIX FIFO support") +def test_crawl_receipt_fifo_fails_without_blocking(tmp_path: Path) -> None: + output = tmp_path / "crawl" + output.mkdir() + os.mkfifo(output / "crawl-receipt.json") + + completed = subprocess.run( + [ + sys.executable, + "-c", + ( + "import json,sys;" + "from pathlib import Path;" + "from tracefetch.verify import crawl_verification_payload;" + "print(json.dumps(crawl_verification_payload(Path(sys.argv[1]))))" + ), + str(output), + ], + check=False, + capture_output=True, + text=True, + timeout=5, + ) + + assert completed.returncode == 0 + assert json.loads(completed.stdout)["failures"] == ["crawl receipt is unreadable"] + assert completed.stderr == "" + assert "Traceback" not in completed.stderr + + @pytest.mark.parametrize("state_kind", ["missing", "corrupt", "symlink"]) def test_valid_receipt_summary_survives_invalid_crawl_state( tmp_path: Path,