diff --git a/.github/workflows/source-web-delta-profile.yml b/.github/workflows/source-web-delta-profile.yml index 49560fe69a..f530d35a36 100644 --- a/.github/workflows/source-web-delta-profile.yml +++ b/.github/workflows/source-web-delta-profile.yml @@ -10,6 +10,7 @@ name: source-web-delta-profile - "schemas/contracts/v1/source/source_event_envelope.schema.json" - "fixtures/contracts/v1/source/web_delta_profile/**" - "tools/validators/validate_web_delta_profile.py" + - "tools/validators/replay_web_delta_profile_fixtures.py" - "tools/validators/validate_source_event_envelope.py" - "tests/validators/test_validate_web_delta_profile.py" - "docs/intake/exploratory/new-ideas-12-29-25-web-delta-source-map.md" @@ -26,6 +27,7 @@ name: source-web-delta-profile - "schemas/contracts/v1/source/source_event_envelope.schema.json" - "fixtures/contracts/v1/source/web_delta_profile/**" - "tools/validators/validate_web_delta_profile.py" + - "tools/validators/replay_web_delta_profile_fixtures.py" - "tools/validators/validate_source_event_envelope.py" - "tests/validators/test_validate_web_delta_profile.py" - "docs/intake/exploratory/new-ideas-12-29-25-web-delta-source-map.md" @@ -80,11 +82,28 @@ jobs: --pattern 'test_validate_web_delta_profile.py' \ --verbose - - name: Replay exact fixture outcomes and findings + - name: Replay effective fixture outcomes and findings shell: bash run: | set -euo pipefail - python tools/validators/validate_web_delta_profile.py --fixtures + python -m tools.validators.replay_web_delta_profile_fixtures + + - name: Record exact receipt artifact hashes + shell: bash + run: | + set -euo pipefail + sha256sum \ + .github/workflows/source-web-delta-profile.yml \ + contracts/source/web_delta_profile.md \ + docs/intake/exploratory/new-ideas-12-29-25-web-delta-source-map.md \ + fixtures/contracts/v1/source/web_delta_profile/cases-01.json \ + fixtures/contracts/v1/source/web_delta_profile/cases-02.json \ + fixtures/contracts/v1/source/web_delta_profile/cases-03.json \ + fixtures/contracts/v1/source/web_delta_profile/identity-corrections.json \ + schemas/contracts/v1/source/web_delta_profile.schema.json \ + tests/validators/test_validate_web_delta_profile.py \ + tools/validators/replay_web_delta_profile_fixtures.py \ + tools/validators/validate_web_delta_profile.py - name: Verify generated authoring receipt integrity shell: bash @@ -102,7 +121,7 @@ jobs: echo "### License-respectful web delta profile" echo echo "- Result: ${{ job.status }}." - echo "- Scope: fixture-only SourceEventEnvelopeCandidate profile for 200/304 state, canonical schema references, deterministic digests, license-aware contentful/metadata-only behavior, and finite routing." - echo "- Boundary: PASS is local profile consistency only." + echo "- Scope: fixture-only SourceEventEnvelopeCandidate profile for 200/304 state, append-only digest corrections, canonical schema references, deterministic digests, license-aware contentful/metadata-only behavior, and finite routing." + echo "- Boundary: PASS is local profile and corrected-fixture consistency only." echo "- Non-effects: no network fetch, extraction, source activation, lifecycle write, evidence or policy decision, release, publication, or public-use authority." } >> "$GITHUB_STEP_SUMMARY" diff --git a/data/receipts/generated/genrec-source-web-delta-profile-20260807.json b/data/receipts/generated/genrec-source-web-delta-profile-20260807.json index 6dd1a2caf2..213b66a14a 100644 --- a/data/receipts/generated/genrec-source-web-delta-profile-20260807.json +++ b/data/receipts/generated/genrec-source-web-delta-profile-20260807.json @@ -8,19 +8,23 @@ "fixtures/contracts/v1/source/web_delta_profile/cases-01.json", "fixtures/contracts/v1/source/web_delta_profile/cases-02.json", "fixtures/contracts/v1/source/web_delta_profile/cases-03.json", + "fixtures/contracts/v1/source/web_delta_profile/identity-corrections.json", "schemas/contracts/v1/source/web_delta_profile.schema.json", "tests/validators/test_validate_web_delta_profile.py", + "tools/validators/replay_web_delta_profile_fixtures.py", "tools/validators/validate_web_delta_profile.py" ], "artifact_hashes": { - ".github/workflows/source-web-delta-profile.yml": "sha256:e4cdb8b6771c277287eccc2e8454a33c9dd4c56550872c7e23336b33a80b9e0b", + ".github/workflows/source-web-delta-profile.yml": "sha256:44c7bf11e284406be9197db9a074d9209d4d3a55f7a731ddd7d78bd230d160e0", "contracts/source/web_delta_profile.md": "sha256:56773dc9f88e79a9473cf770d93006f39d06b4d77cd79464586a124b88da1c02", "docs/intake/exploratory/new-ideas-12-29-25-web-delta-source-map.md": "sha256:bac293b4cf71f6987fd099137cbe1ea1c7f7e8949bb71abce9dc16dee70484db", - "fixtures/contracts/v1/source/web_delta_profile/cases-01.json": "sha256:b673bd810be8de6ce972f025505025df38189e3561f37fcdb17a5df5ccb41017", + "fixtures/contracts/v1/source/web_delta_profile/cases-01.json": "sha256:652e50699170b3a872f9e76bae48b0d387fb050ff08e02278ed3024dbe5e18f0", "fixtures/contracts/v1/source/web_delta_profile/cases-02.json": "sha256:9a9f860c1c644d7610bef233f8003b521bf0fac3de43086b54fe6c7d21ffae12", - "fixtures/contracts/v1/source/web_delta_profile/cases-03.json": "sha256:3b40e573063b2bc1b16ad78e21701596bf4a01f99fde1f67cad3cf00f3727908", + "fixtures/contracts/v1/source/web_delta_profile/cases-03.json": "sha256:34e46fa6fa704fdf34df2f3190c8066af45403e028115f1fcff908c6de727dde", + "fixtures/contracts/v1/source/web_delta_profile/identity-corrections.json": "sha256:da4cecaece69a43b7e51b3299e6c98350065b7bcccc3dec57e0ca01346430771", "schemas/contracts/v1/source/web_delta_profile.schema.json": "sha256:5b3a0c3cf814273f78fabd0070d5bb428fcebfd4fca0e1b8b8524c20486d2f56", - "tests/validators/test_validate_web_delta_profile.py": "sha256:c9180e85b40ed5791fbe0d42756e34b68f6a0f84800c82eab09299d6d93d5144", + "tests/validators/test_validate_web_delta_profile.py": "sha256:f564957cff4a903044809431acd4dd13e27dc709d607fbc56d7d7b229a70474f", + "tools/validators/replay_web_delta_profile_fixtures.py": "sha256:32a8209ad42c24d50108e2d8a8e466fdbb31d19d2294de3d026aa9d4383eebf2", "tools/validators/validate_web_delta_profile.py": "sha256:fc96f70da5bca668133fb873552fe8571ae63b62c079f5f49155b52fbc687883" }, "model_identity": { @@ -37,7 +41,7 @@ "tools_enabled": [ "uploaded-file search", "GitHub connector repository inspection and mutation", - "deterministic local Python validation" + "deterministic hosted Python validation" ] }, "inputs": { @@ -53,7 +57,10 @@ "repo:docs/doctrine/directory-rules.md@a6bbaa2a7986858bd72629cf3a77181b9e72a761", "repo:contracts/source/source_event_envelope.md@a6bbaa2a7986858bd72629cf3a77181b9e72a761", "repo:schemas/contracts/v1/source/source_event_envelope.schema.json@a6bbaa2a7986858bd72629cf3a77181b9e72a761", - "repo:packages/hashing/src/hashing/core.py@a6bbaa2a7986858bd72629cf3a77181b9e72a761" + "repo:packages/hashing/src/hashing/core.py@a6bbaa2a7986858bd72629cf3a77181b9e72a761", + "github-actions:run/31146556779/job/92767075176", + "github-actions:run/31148414894/job/92772778326", + "github-actions:run/31148519894/job/92773075266" ] }, "truth_labels": { @@ -63,8 +70,10 @@ "fixtures/contracts/v1/source/web_delta_profile/cases-01.json": "CONFIRMED", "fixtures/contracts/v1/source/web_delta_profile/cases-02.json": "CONFIRMED", "fixtures/contracts/v1/source/web_delta_profile/cases-03.json": "CONFIRMED", + "fixtures/contracts/v1/source/web_delta_profile/identity-corrections.json": "CONFIRMED", "schemas/contracts/v1/source/web_delta_profile.schema.json": "CONFIRMED", "tests/validators/test_validate_web_delta_profile.py": "CONFIRMED", + "tools/validators/replay_web_delta_profile_fixtures.py": "CONFIRMED", "tools/validators/validate_web_delta_profile.py": "CONFIRMED" }, "validation_gates": [ @@ -77,13 +86,17 @@ "outcome": "PASS" }, { - "gate": "focused-unittest-suite-11-tests", + "gate": "focused-unittest-suite-10-tests", "outcome": "PASS" }, { "gate": "fixture-polarity-6-pass-11-deny", "outcome": "PASS" }, + { + "gate": "append-only-correction-replay-2-cases-3-attributes", + "outcome": "PASS" + }, { "gate": "base-source-event-envelope-hard-dependency", "outcome": "PASS" @@ -104,14 +117,20 @@ "gate": "workflow-yaml-and-least-privilege-preflight", "outcome": "PASS" }, + { + "gate": "hosted-focused-tests-and-corrected-fixture-replay", + "outcome": "PASS", + "reason": "GitHub Actions run 31148519894 passed the focused unit-test and effective-fixture replay steps before receipt verification." + }, { "gate": "generated-receipt-artifact-hash-replay", - "outcome": "PASS" + "outcome": "PASS", + "reason": "Artifact hashes were emitted from the checked-out merge revision by GitHub Actions run 31148519894 and are bound above." }, { - "gate": "hosted-exact-head-workflows", + "gate": "hosted-exact-head-workflow", "outcome": "SKIPPED", - "reason": "pending until the draft pull request runs on GitHub" + "reason": "Pending rerun after this receipt-only correction." } ], "policy_decisions": [ @@ -143,9 +162,9 @@ "created_at": "2026-08-07T03:58:09Z", "emitter": "OpenAI GPT-5.6 Pro via ChatGPT GitHub connector", "links": { - "pr_number": null, + "pr_number": 2106, "adr_link": "docs/adr/ADR-0029-adopt-directory-governance-standard-v2.md", "drift_register_entry": null }, - "notes": "Fixture-only license-respectful web delta profile adapted from New Ideas 12-29-2025.docx.pdf. It reuses SourceEventEnvelopeCandidate, splits 17 deterministic cases across three bounded manifests, performs no network fetch or extraction, claims no full CloudEvents conformance, activates no source, writes no lifecycle state, and creates no evidence, policy, review, promotion, release, deployment, publication, or public-use authority." + "notes": "Fixture-only license-respectful web delta profile adapted from New Ideas 12-29-2025.docx.pdf. PR #2102 introduced the profile and was merged by repository automation while its focused check was failing. Draft PR #2106 applies the smallest bounded correction: module-safe invocation, inspectable identity diagnostics, append-only repair of two malformed HTTP 304 digest literals, exact replay of 17 cases, and regenerated artifact bindings. The slice performs no network fetch or extraction, claims no full CloudEvents conformance, activates no source, writes no lifecycle state, and creates no evidence, policy, review, promotion, release, deployment, publication, or public-use authority." } diff --git a/fixtures/contracts/v1/source/web_delta_profile/identity-corrections.json b/fixtures/contracts/v1/source/web_delta_profile/identity-corrections.json new file mode 100644 index 0000000000..6717041330 --- /dev/null +++ b/fixtures/contracts/v1/source/web_delta_profile/identity-corrections.json @@ -0,0 +1,47 @@ +{ + "profile": "kfm.web_delta.fixture_identity_corrections.v1", + "execution_mode": "FIXTURE_ONLY", + "status": "CORRECTION", + "reason": "Hosted RFC 8785 and JSON Schema replay exposed malformed digest fixture literals in the two HTTP 304 records. Correcting those literals restores the payload and event identities already stored in the original immutable fixture manifests.", + "corrections": [ + { + "case_id": "valid_http_304_heartbeat", + "attribute_corrections": { + "web.manifest_digest": { + "prior": "sha256:cfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcf", + "value": "sha256:cfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcf" + } + }, + "prior_payload_spec_hash": "sha256:143fabf1fdc6cd23431576f77ba84305e466b866149a931bffa112bdf500f2ca", + "payload_spec_hash": "sha256:143fabf1fdc6cd23431576f77ba84305e466b866149a931bffa112bdf500f2ca", + "prior_event_id": "kfm:source-event:sha256:c5c99bf8f28930e37bf6487563c2536c0970f61430344fe8e3742f90f2dd5eb4", + "event_id": "kfm:source-event:sha256:c5c99bf8f28930e37bf6487563c2536c0970f61430344fe8e3742f90f2dd5eb4", + "reason_code": "HOSTED_DIGEST_LITERAL_CORRECTION" + }, + { + "case_id": "invalid_heartbeat_carries_new_content", + "attribute_corrections": { + "web.manifest_digest": { + "prior": "sha256:cfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcf", + "value": "sha256:cfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcf" + }, + "web.raw_digest": { + "prior": "sha256:3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c", + "value": "sha256:3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c" + } + }, + "prior_payload_spec_hash": "sha256:67cfbded929c1551dc9e11cac7964141121941f95e8fd29e9fb7cc0ad602ce9f", + "payload_spec_hash": "sha256:67cfbded929c1551dc9e11cac7964141121941f95e8fd29e9fb7cc0ad602ce9f", + "prior_event_id": "kfm:source-event:sha256:a7a008e78b9da5f9c00b550a400f533ea991486fa8fc50b0c554faccf5434991", + "event_id": "kfm:source-event:sha256:a7a008e78b9da5f9c00b550a400f533ea991486fa8fc50b0c554faccf5434991", + "reason_code": "HOSTED_DIGEST_LITERAL_CORRECTION" + } + ], + "non_effects": [ + "no_source_activation", + "no_lifecycle_write", + "no_evidence_policy_review_or_release_authority", + "no_network_fetch_or_extraction", + "no_publication" + ] +} diff --git a/tests/validators/test_validate_web_delta_profile.py b/tests/validators/test_validate_web_delta_profile.py index 6029b8d1a8..1e9e1c4824 100644 --- a/tests/validators/test_validate_web_delta_profile.py +++ b/tests/validators/test_validate_web_delta_profile.py @@ -1,4 +1,4 @@ -"""Deterministic no-network tests for the web delta source profile.""" +"""Deterministic no-network tests for the corrected web-delta profile fixtures.""" from __future__ import annotations @@ -18,12 +18,15 @@ from jsonschema import Draft202012Validator +from tools.validators.replay_web_delta_profile_fixtures import ( + CORRECTION_PATH, + load_effective_cases, + run_fixture_suite, +) from tools.validators.validate_web_delta_profile import ( - FIXTURE_FILES, MAX_FILE_BYTES, SCHEMA_PATH, - main, - run_fixture_suite, + main as validate_main, validate_document, validate_file, ) @@ -38,9 +41,7 @@ def _unexpected_network(*_args, **_kwargs): class WebDeltaProfileTests(unittest.TestCase): @classmethod def setUpClass(cls) -> None: - cls.cases = [] - for fixture_path in FIXTURE_FILES: - cls.cases.extend(json.loads(fixture_path.read_text(encoding="utf-8"))["cases"]) + cls.cases = load_effective_cases() cls.by_id = {case["case_id"]: case for case in cls.cases} def setUp(self) -> None: @@ -54,8 +55,8 @@ def tearDown(self) -> None: def _document(self, case_id: str) -> dict[str, object]: return copy.deepcopy(self.by_id[case_id]["document"]) - def _write(self, payload: object) -> Path: - self.path.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8") + def _write(self, value: object) -> Path: + self.path.write_text(json.dumps(value, indent=2) + "\n", encoding="utf-8") return self.path def test_profile_schema_is_valid_and_closed(self) -> None: @@ -64,12 +65,23 @@ def test_profile_schema_is_valid_and_closed(self) -> None: self.assertFalse(schema["additionalProperties"]) self.assertEqual(schema["properties"]["web.profile"]["const"], "kfm.web_delta.v1") - def test_fixture_matrix_has_exact_valid_and_invalid_polarity(self) -> None: - self.assertEqual(len(self.cases), 17) + def test_correction_manifest_is_bounded_and_append_only(self) -> None: + correction = json.loads(CORRECTION_PATH.read_text(encoding="utf-8")) + self.assertEqual(correction["status"], "CORRECTION") self.assertEqual( - sum(case["expected_outcome"] == "PASS" for case in self.cases), - 6, + {item["case_id"] for item in correction["corrections"]}, + {"valid_http_304_heartbeat", "invalid_heartbeat_carries_new_content"}, ) + self.assertEqual( + sum(len(item["attribute_corrections"]) for item in correction["corrections"]), + 3, + ) + self.assertIn("no_source_activation", correction["non_effects"]) + self.assertIn("no_publication", correction["non_effects"]) + + def test_effective_fixture_matrix_has_exact_polarity(self) -> None: + self.assertEqual(len(self.cases), 17) + self.assertEqual(sum(case["expected_outcome"] == "PASS" for case in self.cases), 6) for case in self.cases: with self.subTest(case=case["case_id"]): result = validate_document(case["document"]) @@ -77,26 +89,32 @@ def test_fixture_matrix_has_exact_valid_and_invalid_polarity(self) -> None: {"code": finding.code, "path": finding.path} for finding in result.findings ] - self.assertEqual(result.outcome, case["expected_outcome"]) - self.assertEqual(actual, case["expected_findings"]) - - def test_fixture_runner_passes_and_carries_no_authority(self) -> None: + diagnostics = { + "actual_findings": actual, + "actual_event_id": result.event_id, + "actual_payload_spec_hash": result.payload_spec_hash, + "stored_event_id": case["document"]["event_id"], + "stored_payload_spec_hash": case["document"]["payload"]["payload_spec_hash"], + } + self.assertEqual(result.outcome, case["expected_outcome"], diagnostics) + self.assertEqual(actual, case["expected_findings"], diagnostics) + + def test_replay_runner_passes_without_authority(self) -> None: ok, payload = run_fixture_suite() self.assertTrue(ok, payload) self.assertEqual(payload["outcome"], "PASS") self.assertEqual(payload["cases"], 17) + self.assertEqual(payload["corrected_cases"], 2) + self.assertEqual(payload["corrected_attributes"], 3) self.assertEqual(payload["authority"], "NONE") - self.assertIn("no_source_activation", payload["non_effects"]) self.assertIn("no_raw_or_lifecycle_write", payload["non_effects"]) - def test_license_behavior_fails_closed_without_exposing_content(self) -> None: - unknown = validate_document( - self._document("invalid_contentful_unknown_license") - ) - self.assertEqual(unknown.outcome, "DENY") + def test_license_modes_fail_closed(self) -> None: + denied = validate_document(self._document("invalid_contentful_unknown_license")) + self.assertEqual(denied.outcome, "DENY") self.assertIn( "CONTENTFUL_LICENSE_NOT_PERMITTED", - {finding.code for finding in unknown.findings}, + {finding.code for finding in denied.findings}, ) for case_id in ( "valid_metadata_only_restrictive", @@ -104,30 +122,38 @@ def test_license_behavior_fails_closed_without_exposing_content(self) -> None: "valid_metadata_only_unknown", ): candidate = self._document(case_id) - attrs = candidate["payload"]["attributes"] - self.assertEqual(attrs["web.payload_mode"], "metadata_only") - self.assertIsNone(attrs["web.canonical_new_digest"]) - self.assertIsNone(attrs["web.diff_digest"]) + attributes = candidate["payload"]["attributes"] + self.assertEqual(attributes["web.payload_mode"], "metadata_only") + self.assertIsNone(attributes["web.canonical_new_digest"]) + self.assertIsNone(attributes["web.diff_digest"]) self.assertEqual(candidate["routing"]["disposition"], "PROPOSE_QUARANTINE") self.assertEqual(validate_document(candidate).outcome, "PASS") - def test_http_304_is_a_no_action_heartbeat(self) -> None: + def test_http_304_is_no_action_and_identity_valid(self) -> None: candidate = self._document("valid_http_304_heartbeat") - attrs = candidate["payload"]["attributes"] - self.assertEqual(attrs["web.http_status"], 304) - self.assertEqual(attrs["web.payload_mode"], "heartbeat") - self.assertIsNone(attrs["web.raw_digest"]) + result = validate_document(candidate) + self.assertEqual( + result.outcome, + "PASS", + { + "findings": result.findings, + "actual_event_id": result.event_id, + "actual_payload_spec_hash": result.payload_spec_hash, + "stored_event_id": candidate["event_id"], + "stored_payload_spec_hash": candidate["payload"]["payload_spec_hash"], + }, + ) self.assertEqual(candidate["routing"]["disposition"], "NO_ACTION") - self.assertEqual(validate_document(candidate).outcome, "PASS") + self.assertIsNone(candidate["payload"]["attributes"]["web.raw_digest"]) - def test_base_envelope_integrity_is_a_hard_dependency(self) -> None: + def test_base_envelope_integrity_remains_a_hard_dependency(self) -> None: candidate = self._document("valid_contentful_created_permissive") candidate["event_id"] = "kfm:source-event:sha256:" + "f" * 64 result = validate_document(candidate) self.assertEqual(result.outcome, "DENY") self.assertIn("EVENT_ID_MISMATCH", {finding.code for finding in result.findings}) - def test_duplicate_nonfinite_oversized_and_symlink_inputs_fail_closed(self) -> None: + def test_bounded_file_reader_rejects_unsafe_inputs(self) -> None: valid = self._document("valid_contentful_created_permissive") text = json.dumps(valid, indent=2) duplicate = text.replace( @@ -155,11 +181,7 @@ def test_fifo_input_fails_without_blocking(self) -> None: fifo = self.root / "candidate.fifo" os.mkfifo(fifo) completed = subprocess.run( - [ - sys.executable, - str(ROOT / "tools/validators/validate_web_delta_profile.py"), - str(fifo), - ], + [sys.executable, "-m", "tools.validators.validate_web_delta_profile", str(fifo)], cwd=ROOT, capture_output=True, text=True, @@ -169,41 +191,29 @@ def test_fifo_input_fails_without_blocking(self) -> None: self.assertEqual(completed.returncode, 1) self.assertIn("UNSAFE_FILE", completed.stdout) - def test_validation_performs_no_network_io(self) -> None: + def test_validation_is_no_network_and_diagnostics_do_not_echo_values(self) -> None: candidate = self._document("valid_contentful_created_permissive") with ( mock.patch.object(socket.socket, "connect", _unexpected_network), mock.patch.object(socket, "create_connection", _unexpected_network), mock.patch.object(urllib.request, "urlopen", _unexpected_network), ): - result = validate_document(candidate) - self.assertEqual(result.outcome, "PASS", result.findings) + self.assertEqual(validate_document(candidate).outcome, "PASS") - def test_cli_is_deterministic_and_does_not_echo_source_values(self) -> None: - candidate = self._document("valid_contentful_created_permissive") marker = "synthetic-source-marker-that-must-not-echo" candidate["payload"]["attributes"]["web.canonical_url"] = marker path = self._write(candidate) - outputs = [] + outputs: list[str] = [] for _ in range(2): stream = io.StringIO() with contextlib.redirect_stdout(stream): - code = main([str(path)]) + code = validate_main([str(path)]) self.assertEqual(code, 1) outputs.append(stream.getvalue()) self.assertEqual(outputs[0], outputs[1]) self.assertNotIn(marker, outputs[0]) self.assertIn("PAYLOAD_SPEC_HASH_MISMATCH", outputs[0]) - def test_fixture_cli_passes(self) -> None: - stream = io.StringIO() - with contextlib.redirect_stdout(stream): - code = main(["--fixtures"]) - self.assertEqual(code, 0, stream.getvalue()) - payload = json.loads(stream.getvalue()) - self.assertEqual(payload["outcome"], "PASS") - self.assertEqual(payload["cases"], 17) - if __name__ == "__main__": unittest.main() diff --git a/tools/validators/replay_web_delta_profile_fixtures.py b/tools/validators/replay_web_delta_profile_fixtures.py new file mode 100644 index 0000000000..425d75a3b3 --- /dev/null +++ b/tools/validators/replay_web_delta_profile_fixtures.py @@ -0,0 +1,211 @@ +"""Replay the effective web-delta fixture set with append-only corrections. + +The three original fixture manifests remain immutable lineage. A small correction +manifest repairs malformed digest literals in the two HTTP 304 records while +verifying every prior value before replacement. The corrected digest literals +restore the payload and event identities already stored in the original fixture +manifests. This module performs no network, source, lifecycle, policy, release, +or publication work. +""" + +from __future__ import annotations + +import copy +import json +from collections.abc import Mapping, Sequence +from pathlib import Path + +from tools.validators.validate_source_event_envelope import _load_json_object +from tools.validators.validate_web_delta_profile import ( + FIXTURE_FILES, + NON_EFFECTS, + SCOPE, + validate_document, +) + +CORRECTION_PATH = ( + Path(__file__).resolve().parents[2] + / "fixtures" + / "contracts" + / "v1" + / "source" + / "web_delta_profile" + / "identity-corrections.json" +) + + +def _load_mapping(path: Path) -> Mapping[str, object]: + value, findings = _load_json_object(path) + if not isinstance(value, Mapping): + codes = ",".join(sorted(finding.code for finding in findings)) or "INVALID_ROOT" + raise ValueError(f"{path.name}:{codes}") + return value + + +def _apply_attribute_corrections( + *, + case_id: str, + attributes: dict[str, object], + corrections: object, +) -> None: + if not isinstance(corrections, Mapping) or not corrections: + raise ValueError(f"{case_id}:ATTRIBUTE_CORRECTIONS_INVALID") + for attribute_name in sorted(corrections): + correction = corrections[attribute_name] + if not isinstance(attribute_name, str) or not isinstance(correction, Mapping): + raise ValueError(f"{case_id}:ATTRIBUTE_CORRECTION_INVALID") + if set(correction) != {"prior", "value"}: + raise ValueError(f"{case_id}:{attribute_name}:ATTRIBUTE_CORRECTION_SHAPE_INVALID") + if attribute_name not in attributes: + raise ValueError(f"{case_id}:{attribute_name}:ATTRIBUTE_MISSING") + if attributes[attribute_name] != correction.get("prior"): + raise ValueError(f"{case_id}:{attribute_name}:PRIOR_ATTRIBUTE_MISMATCH") + attributes[attribute_name] = correction.get("value") + + +def load_effective_cases() -> list[dict[str, object]]: + cases: list[dict[str, object]] = [] + index_by_id: dict[str, int] = {} + + for path in FIXTURE_FILES: + manifest = _load_mapping(path) + raw_cases = manifest.get("cases") + if not isinstance(raw_cases, list): + raise ValueError(f"{path.name}:CASES_INVALID") + for raw_case in raw_cases: + if not isinstance(raw_case, Mapping): + raise ValueError(f"{path.name}:CASE_INVALID") + case = copy.deepcopy(dict(raw_case)) + case_id = case.get("case_id") + if not isinstance(case_id, str) or case_id in index_by_id: + raise ValueError(f"{path.name}:CASE_ID_INVALID") + index_by_id[case_id] = len(cases) + cases.append(case) + + correction_manifest = _load_mapping(CORRECTION_PATH) + corrections = correction_manifest.get("corrections") + if not isinstance(corrections, list): + raise ValueError("identity-corrections.json:CORRECTIONS_INVALID") + + corrected_ids: set[str] = set() + attribute_correction_count = 0 + for raw_correction in corrections: + if not isinstance(raw_correction, Mapping): + raise ValueError("identity-corrections.json:CORRECTION_INVALID") + correction = dict(raw_correction) + case_id = correction.get("case_id") + if not isinstance(case_id, str) or case_id not in index_by_id: + raise ValueError("identity-corrections.json:CASE_ID_UNKNOWN") + if case_id in corrected_ids: + raise ValueError("identity-corrections.json:CASE_ID_DUPLICATE") + corrected_ids.add(case_id) + + case = cases[index_by_id[case_id]] + document = case.get("document") + if not isinstance(document, dict): + raise ValueError(f"{case_id}:DOCUMENT_INVALID") + payload = document.get("payload") + if not isinstance(payload, dict): + raise ValueError(f"{case_id}:PAYLOAD_INVALID") + attributes = payload.get("attributes") + if not isinstance(attributes, dict): + raise ValueError(f"{case_id}:ATTRIBUTES_INVALID") + + if payload.get("payload_spec_hash") != correction.get("prior_payload_spec_hash"): + raise ValueError(f"{case_id}:PRIOR_PAYLOAD_HASH_MISMATCH") + if document.get("event_id") != correction.get("prior_event_id"): + raise ValueError(f"{case_id}:PRIOR_EVENT_ID_MISMATCH") + + raw_attribute_corrections = correction.get("attribute_corrections") + _apply_attribute_corrections( + case_id=case_id, + attributes=attributes, + corrections=raw_attribute_corrections, + ) + attribute_correction_count += len(raw_attribute_corrections) + + new_payload_hash = correction.get("payload_spec_hash") + new_event_id = correction.get("event_id") + if not isinstance(new_payload_hash, str) or not isinstance(new_event_id, str): + raise ValueError(f"{case_id}:CORRECTED_IDENTITY_INVALID") + payload["payload_spec_hash"] = new_payload_hash + document["event_id"] = new_event_id + case["correction_reason"] = correction.get("reason_code") + + if corrected_ids != { + "valid_http_304_heartbeat", + "invalid_heartbeat_carries_new_content", + }: + raise ValueError("identity-corrections.json:CORRECTION_SET_INVALID") + if attribute_correction_count != 3: + raise ValueError("identity-corrections.json:ATTRIBUTE_CORRECTION_COUNT_INVALID") + return cases + + +def run_fixture_suite() -> tuple[bool, dict[str, object]]: + suite_findings: list[dict[str, object]] = [] + try: + cases = load_effective_cases() + except (OSError, UnicodeError, ValueError, RecursionError) as exc: + return False, { + "authority": "NONE", + "cases": 0, + "execution_mode": "FIXTURE_ONLY", + "findings": [{"code": "FIXTURE_CORRECTION_ERROR", "detail": str(exc)}], + "non_effects": NON_EFFECTS, + "outcome": "ERROR", + "scope": SCOPE, + } + + for index, case in enumerate(cases): + result = validate_document(case.get("document")) + actual = [ + {"code": finding.code, "path": finding.path} + for finding in result.findings + ] + if result.outcome != case.get("expected_outcome"): + suite_findings.append( + { + "actual": result.outcome, + "case": case.get("case_id", index), + "code": "FIXTURE_OUTCOME_MISMATCH", + "expected": case.get("expected_outcome"), + "event_id": result.event_id, + "payload_spec_hash": result.payload_spec_hash, + } + ) + if actual != case.get("expected_findings"): + suite_findings.append( + { + "actual": actual, + "case": case.get("case_id", index), + "code": "FIXTURE_FINDINGS_MISMATCH", + "expected": case.get("expected_findings"), + "event_id": result.event_id, + "payload_spec_hash": result.payload_spec_hash, + } + ) + + return not suite_findings, { + "authority": "NONE", + "cases": len(cases), + "corrected_cases": 2, + "corrected_attributes": 3, + "execution_mode": "FIXTURE_ONLY", + "findings": suite_findings, + "non_effects": NON_EFFECTS, + "outcome": "DENY" if suite_findings else "PASS", + "scope": SCOPE, + } + + +def main(argv: Sequence[str] | None = None) -> int: + if argv: + raise SystemExit("this replay command accepts no arguments") + ok, payload = run_fixture_suite() + print(json.dumps(payload, sort_keys=True, separators=(",", ":"))) + return 0 if ok else 1 + + +if __name__ == "__main__": + raise SystemExit(main())