From bcef83ec24fc06bbdc008b5ce37794134a3736e7 Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Wed, 5 Aug 2026 01:34:11 +0800 Subject: [PATCH] fix(reconciliation): make v1 missing-only Co-Authored-By: Codex --- .../scripts/reconciliation_record_contract.py | 158 ++++------------- .../test_reconciliation_record_contract.py | 166 ++++++++++-------- .../qsl-reconciliation-record.v1.schema.json | 105 +---------- 3 files changed, 133 insertions(+), 296 deletions(-) diff --git a/python/scripts/reconciliation_record_contract.py b/python/scripts/reconciliation_record_contract.py index 21a4720..8ef5049 100644 --- a/python/scripts/reconciliation_record_contract.py +++ b/python/scripts/reconciliation_record_contract.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Validate the contract-only QSL ReconciliationRecord v1 consumer contract.""" +"""Validate the MISSING-only QSL ReconciliationRecord v1 contract.""" from __future__ import annotations @@ -19,9 +19,8 @@ SCHEMA_ID = "qsl.reconciliation_record.v1" BUNDLE_SCHEMA_ID = "qsl.deployment_bundle.v1" ACTIVATION_SCHEMA_ID = "qsl.activation.v1" -OBSERVER_SCHEMA_ID = "qsl.reconciliation_observer_receipt.v1" -RECONCILIATION_STATUSES = ("MISSING", "MATCHED", "MISMATCHED") -COMPARISON_FIELDS = ( +RECONCILIATION_STATUS = "MISSING" +IDENTITY_BINDING_FIELDS = ( "deployment_bundle_sha256", "activation_id", "activation_sha256", @@ -40,11 +39,17 @@ _REPOSITORY_PATTERN = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.-]*/[A-Za-z0-9][A-Za-z0-9_.-]*$") _FORBIDDEN_KEY_PATTERN = re.compile( r"credential|secret|token|password|cookie|jwt|private(?:[_-]?key)?|api[_-]?key|access[_-]?key|" - r"provider[_-]?rows?|raw[_-]?provider|account[_-]?(?:number|id|balance)|balance|positions?|orders?|" - r"fills?|capital(?:[_-]?(?:amount|balance|value))?", + r"(?:^|[_-])raw(?:$|[_-])|provider[_-]?rows?|account|balance|positions?|orders?|" + r"fills?|capital(?:[_-]?(?:amount|balance|value))?|observer|observation|observed|comparisons?|" + r"mismatch(?:ed)?(?:[_-]?list)?|max[_-]?age", re.IGNORECASE, ) -_ALLOWED_ASSERTION_KEYS = {"fills_verified", "capital_use_verified"} +_ALLOWED_SECURITY_KEYS = { + "account_alias", + "account_digest_sha256", + "fills_verified", + "capital_use_verified", +} _URL_PATTERN = re.compile(r"[a-z][a-z0-9+.-]*://", re.IGNORECASE) _JWT_PATTERN = re.compile(r"(?:^|\s)[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}(?:$|\s)") _BEARER_PATTERN = re.compile(r"(?:^|\s)bearer\s+\S+", re.IGNORECASE) @@ -52,7 +57,7 @@ _COMMON_FIELDS = { "schema", "reconciliation_id", - "created_at", + "produced_at", "expires_at", "digest_algorithm", "contract_only", @@ -64,7 +69,6 @@ "assertions", "reconciliation_sha256", } -_OBSERVATION_FIELDS = {"observed_identity", "observer_receipt", "comparison"} _BUNDLE_REFERENCE_FIELDS = {"schema", "bundle_id", "bundle_sha256"} _ACTIVATION_REFERENCE_FIELDS = {"schema", "activation_id", "activation_sha256"} _TARGET_FIELDS = { @@ -75,27 +79,12 @@ "account_alias", "account_digest_sha256", } -_EXPECTED_IDENTITY_FIELDS = set(COMPARISON_FIELDS) | {"expected_identity_sha256"} -_OBSERVED_IDENTITY_FIELDS = set(COMPARISON_FIELDS) | { - "producer_id", - "producer_revision", - "artifact_sha256", - "observed_at", - "observed_identity_sha256", -} -_OBSERVER_RECEIPT_FIELDS = { - "schema", - "observer_id", - "observer_revision", - "created_at", - "observed_identity", - "observer_receipt_sha256", -} +_EXPECTED_IDENTITY_FIELDS = set(IDENTITY_BINDING_FIELDS) | {"expected_identity_sha256"} _ASSERTION_FIELDS = { "apply_performed", "config_sync_performed", "runtime_mutation_performed", - "runtime_active", + "runtime_active_verified", "fills_verified", "capital_use_verified", } @@ -127,7 +116,7 @@ def _reject_non_finite_or_null(value: Any, path: str = "reconciliation") -> None def _reject_forbidden_material(value: Any, path: str = "reconciliation") -> None: if isinstance(value, Mapping): for key, child in value.items(): - if key not in _ALLOWED_ASSERTION_KEYS and _FORBIDDEN_KEY_PATTERN.search(key): + if key not in _ALLOWED_SECURITY_KEYS and _FORBIDDEN_KEY_PATTERN.search(key): _fail(f"{path}.{key} is forbidden in a reconciliation contract") _reject_forbidden_material(child, f"{path}.{key}") elif isinstance(value, list): @@ -216,16 +205,6 @@ def calculate_expected_identity_sha256(identity: Mapping[str, Any]) -> str: return hashlib.sha256(canonical.encode("utf-8")).hexdigest() -def calculate_observed_identity_sha256(identity: Mapping[str, Any]) -> str: - canonical = _canonical_without(identity, "observed_identity_sha256", "observed_identity") - return hashlib.sha256(canonical.encode("utf-8")).hexdigest() - - -def calculate_observer_receipt_sha256(receipt: Mapping[str, Any]) -> str: - canonical = _canonical_without(receipt, "observer_receipt_sha256", "observer_receipt") - return hashlib.sha256(canonical.encode("utf-8")).hexdigest() - - def _validate_target(value: Any, path: str = "target") -> Mapping[str, Any]: target = _expect_object(value, path) _expect_exact_keys(target, _TARGET_FIELDS, path) @@ -268,65 +247,6 @@ def _validate_expected_identity(value: Any) -> Mapping[str, Any]: return identity -def _validate_observed_identity(value: Any) -> tuple[Mapping[str, Any], datetime]: - identity = _expect_object(value, "observed_identity") - _expect_exact_keys(identity, _OBSERVED_IDENTITY_FIELDS, "observed_identity") - _expect_sha256(identity["deployment_bundle_sha256"], "observed_identity.deployment_bundle_sha256") - _expect_identity(identity["activation_id"], "observed_identity.activation_id") - _expect_sha256(identity["activation_sha256"], "observed_identity.activation_sha256") - _validate_target({field: identity[field] for field in _TARGET_FIELDS}, "observed_identity") - _expect_identity(identity["producer_id"], "observed_identity.producer_id") - _expect_revision(identity["producer_revision"], "observed_identity.producer_revision") - _expect_sha256(identity["artifact_sha256"], "observed_identity.artifact_sha256") - observed_at = _parse_timestamp(identity["observed_at"], "observed_identity.observed_at") - _expect_sha256(identity["observed_identity_sha256"], "observed_identity.observed_identity_sha256") - if identity["observed_identity_sha256"] != calculate_observed_identity_sha256(identity): - _fail("observed_identity_sha256 mismatch") - return identity, observed_at - - -def _validate_observer_receipt(value: Any) -> tuple[Mapping[str, Any], Mapping[str, Any], datetime]: - receipt = _expect_object(value, "observer_receipt") - _expect_exact_keys(receipt, _OBSERVER_RECEIPT_FIELDS, "observer_receipt") - if receipt["schema"] != OBSERVER_SCHEMA_ID: - _fail(f"observer_receipt.schema must be {OBSERVER_SCHEMA_ID}") - _expect_identity(receipt["observer_id"], "observer_receipt.observer_id") - _expect_revision(receipt["observer_revision"], "observer_receipt.observer_revision") - created_at = _parse_timestamp(receipt["created_at"], "observer_receipt.created_at") - observed, _ = _validate_observed_identity(receipt["observed_identity"]) - if receipt["observer_id"] == observed["producer_id"]: - _fail("observer receipt must be produced by an identity separate from the platform producer") - _expect_sha256(receipt["observer_receipt_sha256"], "observer_receipt.observer_receipt_sha256") - if receipt["observer_receipt_sha256"] != calculate_observer_receipt_sha256(receipt): - _fail("observer_receipt_sha256 mismatch") - return receipt, observed, created_at - - -def _validate_comparison( - value: Any, - expected: Mapping[str, Any], - observed: Mapping[str, Any], -) -> list[str]: - comparison = _expect_object(value, "comparison") - _expect_exact_keys(comparison, {"complete", "fields"}, "comparison") - if comparison["complete"] is not True: - _fail("comparison.complete must be true for an observed reconciliation") - fields = _expect_object(comparison["fields"], "comparison.fields") - _expect_exact_keys(fields, set(COMPARISON_FIELDS), "comparison.fields") - differences = [] - for field in COMPARISON_FIELDS: - entry = _expect_object(fields[field], f"comparison.fields.{field}") - _expect_exact_keys(entry, {"expected", "observed", "equal"}, f"comparison.fields.{field}") - if entry["expected"] != expected[field] or entry["observed"] != observed[field]: - _fail(f"comparison.fields.{field} does not bind the exact expected and observed values") - equality = expected[field] == observed[field] - if entry["equal"] is not equality: - _fail(f"comparison.fields.{field}.equal is inconsistent") - if not equality: - differences.append(field) - return differences - - def _validate_references( root: Mapping[str, Any], bundle: Mapping[str, Any], @@ -376,22 +296,21 @@ def validate_reconciliation_record( expected_activation: Any, as_of: str | None = None, ) -> Mapping[str, Any]: - """Validate exact expected identity and optional independently observed truth, fail closed.""" + """Validate exact expected identity without accepting observed runtime truth.""" _reject_non_finite_or_null(record) - _reject_forbidden_material(record) root = _expect_object(record, "reconciliation") status = root.get("status") - if status not in RECONCILIATION_STATUSES: - _fail("status must be one of MISSING, MATCHED, MISMATCHED") - expected_fields = _COMMON_FIELDS if status == "MISSING" else _COMMON_FIELDS | _OBSERVATION_FIELDS - _expect_exact_keys(root, expected_fields, "reconciliation") + if status != RECONCILIATION_STATUS: + _fail("qsl.reconciliation_record.v1 is MISSING-only") + _reject_forbidden_material(record) + _expect_exact_keys(root, _COMMON_FIELDS, "reconciliation") if root["schema"] != SCHEMA_ID: _fail(f"schema must be {SCHEMA_ID}") _expect_identity(root["reconciliation_id"], "reconciliation_id") - created_at = _parse_timestamp(root["created_at"], "created_at") + produced_at = _parse_timestamp(root["produced_at"], "produced_at") expires_at = _parse_timestamp(root["expires_at"], "expires_at") - if expires_at <= created_at: - _fail("expires_at must be after created_at") + if expires_at <= produced_at: + _fail("expires_at must be after produced_at") if root["digest_algorithm"] != "sha256": _fail("digest_algorithm must be sha256") if root["contract_only"] is not True: @@ -412,41 +331,24 @@ def validate_reconciliation_record( ) except (BundleValidationError, ActivationValidationError) as exc: raise ReconciliationValidationError(f"expected bundle or activation is invalid: {exc}") from exc - expected = _validate_references(root, bundle, activation) + _validate_references(root, bundle, activation) validation_time = datetime.now(UTC).replace(microsecond=0) if as_of is None else _parse_timestamp(as_of, "as_of") - if created_at > validation_time: + if produced_at > validation_time: _fail("reconciliation record was created in the future") if validation_time >= expires_at: _fail("reconciliation record is stale or expired") activation_effective = _parse_timestamp(activation["effective_at"], "expected_activation.effective_at") activation_expires = _parse_timestamp(activation["expires_at"], "expected_activation.expires_at") - if created_at < activation_effective or expires_at > activation_expires: + if produced_at < activation_effective or expires_at > activation_expires: _fail("reconciliation validity window must remain within the activation window") - - if status == "MISSING": - return root - - observed, observed_at = _validate_observed_identity(root["observed_identity"]) - _, receipt_observed, receipt_created_at = _validate_observer_receipt(root["observer_receipt"]) - if receipt_observed != observed: - _fail("observer_receipt does not bind the exact platform-produced observed identity") - if observed_at < activation_effective or observed_at > created_at: - _fail("observed identity is stale or was produced after the reconciliation record") - if receipt_created_at < observed_at or receipt_created_at > created_at: - _fail("observer receipt time must be between observation and reconciliation creation") - differences = _validate_comparison(root["comparison"], expected, observed) - if status == "MATCHED" and differences: - _fail("MATCHED requires every expected and observed identity field to be exactly equal") - if status == "MISMATCHED" and not differences: - _fail("MISMATCHED requires at least one explicit required-field difference") return root def build_missing_record( *, record_id: str, - created_at: str, + produced_at: str, expires_at: str, expected_bundle: Any, expected_activation: Any, @@ -461,7 +363,7 @@ def build_missing_record( record = { "schema": SCHEMA_ID, "reconciliation_id": record_id, - "created_at": created_at, + "produced_at": produced_at, "expires_at": expires_at, "digest_algorithm": "sha256", "contract_only": True, @@ -477,7 +379,7 @@ def build_missing_record( }, "target": dict(activation["target"]), "expected_identity": _expected_identity(bundle, activation), - "status": "MISSING", + "status": RECONCILIATION_STATUS, "assertions": {field: False for field in sorted(_ASSERTION_FIELDS)}, } record["reconciliation_sha256"] = calculate_reconciliation_sha256(record) diff --git a/python/tests/test_reconciliation_record_contract.py b/python/tests/test_reconciliation_record_contract.py index 01d97be..cca343c 100644 --- a/python/tests/test_reconciliation_record_contract.py +++ b/python/tests/test_reconciliation_record_contract.py @@ -1,6 +1,7 @@ from __future__ import annotations import copy +import hashlib import importlib.util import json import math @@ -27,6 +28,18 @@ def _load_module(name: str): class ReconciliationRecordContractTest(unittest.TestCase): + IDENTITY_BINDING_FIELDS = ( + "deployment_bundle_sha256", + "activation_id", + "activation_sha256", + "platform", + "repository", + "revision", + "environment", + "account_alias", + "account_digest_sha256", + ) + @staticmethod def _sha(character: str) -> str: return character * 64 @@ -35,6 +48,13 @@ def _sha(character: str) -> str: def _revision(character: str) -> str: return character * 40 + @staticmethod + def _self_excluded_sha256(value: dict[str, object], excluded_field: str) -> str: + content = dict(value) + content.pop(excluded_field, None) + canonical = json.dumps(content, sort_keys=True, separators=(",", ":"), ensure_ascii=True, allow_nan=False) + return hashlib.sha256(canonical.encode("utf-8")).hexdigest() + def _bundle(self) -> dict[str, object]: bundle: dict[str, object] = { "schema": "qsl.deployment_bundle.v1", @@ -126,7 +146,7 @@ def _activation(self) -> dict[str, object]: def _missing(self) -> dict[str, object]: return reconciliation_record_contract.build_missing_record( record_id="reconciliation.soxl-signal.ibkr-us.20260805", - created_at="2026-08-05T12:00:00Z", + produced_at="2026-08-05T12:00:00Z", expires_at="2026-08-05T13:00:00Z", expected_bundle=self._bundle(), expected_activation=self._activation(), @@ -141,9 +161,7 @@ def _observed(self, expected: dict[str, object]) -> dict[str, object]: "artifact_sha256": self._sha("e"), "observed_at": "2026-08-05T11:55:00Z", } - observed["observed_identity_sha256"] = ( - reconciliation_record_contract.calculate_observed_identity_sha256(observed) - ) + observed["observed_identity_sha256"] = self._self_excluded_sha256(observed, "observed_identity_sha256") return observed def _observer_receipt(self, observed: dict[str, object]) -> dict[str, object]: @@ -154,14 +172,12 @@ def _observer_receipt(self, observed: dict[str, object]) -> dict[str, object]: "created_at": "2026-08-05T11:58:00Z", "observed_identity": copy.deepcopy(observed), } - receipt["observer_receipt_sha256"] = ( - reconciliation_record_contract.calculate_observer_receipt_sha256(receipt) - ) + receipt["observer_receipt_sha256"] = self._self_excluded_sha256(receipt, "observer_receipt_sha256") return receipt def _comparison(self, expected: dict[str, object], observed: dict[str, object]) -> dict[str, object]: fields = {} - for field in reconciliation_record_contract.COMPARISON_FIELDS: + for field in self.IDENTITY_BINDING_FIELDS: fields[field] = { "expected": expected[field], "observed": observed[field], @@ -175,9 +191,7 @@ def _record_with_observation(self, status: str = "MATCHED") -> dict[str, object] observed = self._observed(expected) if status == "MISMATCHED": observed["revision"] = self._revision("0") - observed["observed_identity_sha256"] = ( - reconciliation_record_contract.calculate_observed_identity_sha256(observed) - ) + observed["observed_identity_sha256"] = self._self_excluded_sha256(observed, "observed_identity_sha256") record["status"] = status record["observed_identity"] = observed record["observer_receipt"] = self._observer_receipt(observed) @@ -198,14 +212,19 @@ def test_schema_is_closed_contract_only_and_uses_canonical_statuses(self): self.assertEqual(schema["$id"], "qsl.reconciliation_record.v1") self.assertFalse(schema["additionalProperties"]) self.assertEqual(schema["properties"]["contract_only"], {"const": True}) - self.assertEqual(schema["properties"]["status"]["enum"], ["MISSING", "MATCHED", "MISMATCHED"]) - self.assertIn("does not perform apply", schema["description"]) + self.assertEqual(schema["properties"]["status"], {"const": "MISSING"}) + self.assertNotIn("observed_identity", schema["properties"]) + self.assertNotIn("observer_receipt", schema["properties"]) + self.assertNotIn("comparison", schema["properties"]) + self.assertIn("generation time", schema["description"]) def test_valid_missing_is_default_deterministic_and_binds_exact_inputs(self): record = self._missing() validated = self._validate(record) self.assertEqual(validated["status"], "MISSING") + self.assertEqual(validated["produced_at"], "2026-08-05T12:00:00Z") self.assertNotIn("observed_identity", record) + self.assertFalse(validated["assertions"]["runtime_active_verified"]) self.assertEqual( reconciliation_record_contract.canonical_json(record), reconciliation_record_contract.canonical_json(dict(reversed(record.items()))), @@ -215,11 +234,16 @@ def test_valid_missing_is_default_deterministic_and_binds_exact_inputs(self): reconciliation_record_contract.calculate_reconciliation_sha256(record), ) - def test_missing_rejects_fake_observed_or_matched_claims(self): + def test_missing_rejects_all_observer_comparison_and_freshness_material(self): for field, value in ( + ("observer_id", "self-asserted-observer"), + ("observer_receipt", {"observer_id": "self-asserted-observer"}), + ("observation", {"observed_at": "2026-08-05T11:55:00Z"}), ("observed_identity", self._observed(self._missing()["expected_identity"])), ("comparison", {"complete": False}), - ("runtime_active", True), + ("comparisons", []), + ("mismatch_list", []), + ("max_age_seconds", 300), ): with self.subTest(field=field): record = self._missing() @@ -228,40 +252,47 @@ def test_missing_rejects_fake_observed_or_matched_claims(self): with self.assertRaises(reconciliation_record_contract.ReconciliationValidationError): self._validate(record) - def test_matched_requires_separate_immutable_observer_receipt(self): + nested = self._missing() + nested["expected_identity"]["metadata"] = {"observer_id": "self-asserted-observer"} + nested["reconciliation_sha256"] = reconciliation_record_contract.calculate_reconciliation_sha256(nested) + with self.assertRaisesRegex(reconciliation_record_contract.ReconciliationValidationError, "forbidden"): + self._validate(nested) + + def test_all_legacy_non_missing_statuses_are_rejected(self): + for status in ("MATCHED", "MISMATCHED", "UNKNOWN", "matched", ""): + with self.subTest(status=status): + record = ( + self._record_with_observation(status) if status in {"MATCHED", "MISMATCHED"} else self._missing() + ) + record["status"] = status + record["reconciliation_sha256"] = reconciliation_record_contract.calculate_reconciliation_sha256(record) + with self.assertRaisesRegex( + reconciliation_record_contract.ReconciliationValidationError, "MISSING-only" + ): + self._validate(record) + + def test_missing_only_rejects_self_asserted_observer_forgery(self): record = self._record_with_observation() - record.pop("observer_receipt") + record["observer_receipt"]["observer_id"] = "attacker-asserted-observer" + record["observer_receipt"]["observer_revision"] = self._revision("0") + record["observer_receipt"]["observer_receipt_sha256"] = self._self_excluded_sha256( + record["observer_receipt"], "observer_receipt_sha256" + ) record["reconciliation_sha256"] = reconciliation_record_contract.calculate_reconciliation_sha256(record) - with self.assertRaisesRegex(reconciliation_record_contract.ReconciliationValidationError, "observer_receipt"): + + with self.assertRaisesRegex(reconciliation_record_contract.ReconciliationValidationError, "MISSING-only"): self._validate(record) - def test_same_identity_matched_fixture_validates_without_creating_runtime_truth(self): + def test_missing_only_rejects_fresh_wrapper_around_stale_observation(self): record = self._record_with_observation() - validated = self._validate(record) - self.assertEqual(validated["status"], "MATCHED") - self.assertFalse(validated["assertions"]["runtime_active"]) - self.assertFalse(validated["assertions"]["fills_verified"]) - self.assertFalse(validated["assertions"]["capital_use_verified"]) - - def test_mismatched_requires_at_least_one_explicit_difference(self): - record = self._record_with_observation("MISMATCHED") - self._validate(record) - matched = self._record_with_observation() - matched["status"] = "MISMATCHED" - matched["reconciliation_sha256"] = reconciliation_record_contract.calculate_reconciliation_sha256(matched) - with self.assertRaisesRegex(reconciliation_record_contract.ReconciliationValidationError, "at least one"): - self._validate(matched) - - def test_required_mismatch_cannot_be_labeled_matched(self): - record = self._record_with_observation() - record["observed_identity"]["revision"] = self._revision("0") - record["observed_identity"]["observed_identity_sha256"] = ( - reconciliation_record_contract.calculate_observed_identity_sha256(record["observed_identity"]) + record["observed_identity"]["observed_at"] = "2026-08-05T10:01:00Z" + record["observed_identity"]["observed_identity_sha256"] = self._self_excluded_sha256( + record["observed_identity"], "observed_identity_sha256" ) record["observer_receipt"] = self._observer_receipt(record["observed_identity"]) - record["comparison"] = self._comparison(record["expected_identity"], record["observed_identity"]) record["reconciliation_sha256"] = reconciliation_record_contract.calculate_reconciliation_sha256(record) - with self.assertRaisesRegex(reconciliation_record_contract.ReconciliationValidationError, "MATCHED"): + + with self.assertRaisesRegex(reconciliation_record_contract.ReconciliationValidationError, "MISSING-only"): self._validate(record) def test_stale_expired_future_and_invalid_calendar_times_fail_closed(self): @@ -269,25 +300,13 @@ def test_stale_expired_future_and_invalid_calendar_times_fail_closed(self): self._validate(self._missing(), as_of="2026-08-05T13:00:00Z") future = self._missing() - future["created_at"] = "2026-08-05T12:01:00Z" + future["produced_at"] = "2026-08-05T12:01:00Z" future["reconciliation_sha256"] = reconciliation_record_contract.calculate_reconciliation_sha256(future) with self.assertRaisesRegex(reconciliation_record_contract.ReconciliationValidationError, "future"): self._validate(future) - stale_observation = self._record_with_observation() - stale_observation["observed_identity"]["observed_at"] = "2026-08-05T09:59:59Z" - stale_observation["observed_identity"]["observed_identity_sha256"] = ( - reconciliation_record_contract.calculate_observed_identity_sha256(stale_observation["observed_identity"]) - ) - stale_observation["observer_receipt"] = self._observer_receipt(stale_observation["observed_identity"]) - stale_observation["reconciliation_sha256"] = ( - reconciliation_record_contract.calculate_reconciliation_sha256(stale_observation) - ) - with self.assertRaisesRegex(reconciliation_record_contract.ReconciliationValidationError, "stale"): - self._validate(stale_observation) - for field, timestamp in ( - ("created_at", "2026-02-30T12:00:00Z"), + ("produced_at", "2026-02-30T12:00:00Z"), ("expires_at", "2026-08-05T25:00:00Z"), ): with self.subTest(field=field): @@ -297,21 +316,22 @@ def test_stale_expired_future_and_invalid_calendar_times_fail_closed(self): with self.assertRaisesRegex(reconciliation_record_contract.ReconciliationValidationError, "timestamp"): self._validate(record) - def test_cross_bundle_activation_platform_and_observer_splices_fail_closed(self): + with self.assertRaisesRegex(reconciliation_record_contract.ReconciliationValidationError, "timestamp"): + self._validate(self._missing(), as_of="2026-08-05T12:00:00+00:00") + + def test_cross_bundle_activation_target_and_expected_identity_fail_closed(self): mutations = ( lambda value: value["deployment_bundle"].update({"bundle_sha256": self._sha("0")}), lambda value: value["activation"].update({"activation_id": "activation.other"}), lambda value: value["target"].update({"platform": "binance-platform"}), - lambda value: value["observer_receipt"]["observed_identity"].update( - {"artifact_sha256": self._sha("0")} - ), + lambda value: value["expected_identity"].update({"revision": self._revision("0")}), ) for mutate in mutations: - record = self._record_with_observation() + record = self._missing() mutate(record) - if "observer_receipt" in record: - record["observer_receipt"]["observer_receipt_sha256"] = ( - reconciliation_record_contract.calculate_observer_receipt_sha256(record["observer_receipt"]) + if record["expected_identity"]["revision"] == self._revision("0"): + record["expected_identity"]["expected_identity_sha256"] = ( + reconciliation_record_contract.calculate_expected_identity_sha256(record["expected_identity"]) ) record["reconciliation_sha256"] = reconciliation_record_contract.calculate_reconciliation_sha256(record) with self.assertRaises(reconciliation_record_contract.ReconciliationValidationError): @@ -320,20 +340,26 @@ def test_cross_bundle_activation_platform_and_observer_splices_fail_closed(self) def test_mutation_and_nested_digest_mismatch_fail_closed(self): record = self._missing() record["target"]["environment"] = "ibkr-shadow" - with self.assertRaisesRegex(reconciliation_record_contract.ReconciliationValidationError, "reconciliation_sha256"): + with self.assertRaisesRegex( + reconciliation_record_contract.ReconciliationValidationError, "reconciliation_sha256" + ): self._validate(record) - record = self._record_with_observation() - record["observed_identity"]["artifact_sha256"] = self._sha("0") + record = self._missing() + record["expected_identity"]["activation_sha256"] = self._sha("0") record["reconciliation_sha256"] = reconciliation_record_contract.calculate_reconciliation_sha256(record) - with self.assertRaisesRegex(reconciliation_record_contract.ReconciliationValidationError, "observed_identity_sha256"): + with self.assertRaisesRegex( + reconciliation_record_contract.ReconciliationValidationError, "expected_identity_sha256" + ): self._validate(record) def test_secret_provider_and_financial_material_is_rejected_recursively(self): for key, value in ( ("api_token", "not-a-real-token"), ("credential_url", "https://user:pass@example.invalid"), + ("raw_payload", {"value": "redacted"}), ("provider_rows", [{"close": 1.0}]), + ("account", {"alias": "not-allowed-here"}), ("account_number", "00000000"), ("balance", 100.0), ("positions", [{"symbol": "SPY"}]), @@ -360,11 +386,13 @@ def test_unknown_null_non_finite_duplicate_and_malformed_json_fail_closed(self): self._validate(record) record = self._missing() - record["assertions"]["runtime_active"] = math.nan + record["assertions"]["runtime_active_verified"] = math.nan with self.assertRaises(reconciliation_record_contract.ReconciliationValidationError): self._validate(record) - duplicate = json.dumps(self._missing()).replace('"status": "MISSING"', '"status": "MISSING", "status": "MATCHED"') + duplicate = json.dumps(self._missing()).replace( + '"status": "MISSING"', '"status": "MISSING", "status": "MATCHED"' + ) with self.assertRaisesRegex(reconciliation_record_contract.ReconciliationValidationError, "duplicate JSON key"): reconciliation_record_contract.parse_reconciliation_json( duplicate, diff --git a/schemas/qsl-reconciliation-record.v1.schema.json b/schemas/qsl-reconciliation-record.v1.schema.json index d9384d7..be2d8d7 100644 --- a/schemas/qsl-reconciliation-record.v1.schema.json +++ b/schemas/qsl-reconciliation-record.v1.schema.json @@ -2,13 +2,13 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "qsl.reconciliation_record.v1", "title": "QSL ReconciliationRecord v1", - "description": "Contract-only consumer record for exact expected identity and separately platform-produced observed identity. It does not perform apply, config sync, runtime mutation, or prove active runtime, fills, or capital use.", + "description": "MISSING-only contract record for exact expected identity. produced_at is only the record generation time and does not represent observation freshness. This contract does not accept observed runtime material or prove active runtime, fills, or capital use. Any non-MISSING observer and freshness contract requires a separate future version.", "type": "object", "additionalProperties": false, "required": [ "schema", "reconciliation_id", - "created_at", + "produced_at", "expires_at", "digest_algorithm", "contract_only", @@ -23,7 +23,7 @@ "properties": { "schema": {"const": "qsl.reconciliation_record.v1"}, "reconciliation_id": {"$ref": "#/$defs/identity"}, - "created_at": {"$ref": "#/$defs/timestamp"}, + "produced_at": {"$ref": "#/$defs/timestamp"}, "expires_at": {"$ref": "#/$defs/timestamp"}, "digest_algorithm": {"const": "sha256"}, "contract_only": {"const": true}, @@ -31,29 +31,10 @@ "activation": {"$ref": "#/$defs/activationReference"}, "target": {"$ref": "#/$defs/target"}, "expected_identity": {"$ref": "#/$defs/expectedIdentity"}, - "status": {"enum": ["MISSING", "MATCHED", "MISMATCHED"]}, - "observed_identity": {"$ref": "#/$defs/observedIdentity"}, - "observer_receipt": {"$ref": "#/$defs/observerReceipt"}, - "comparison": {"$ref": "#/$defs/comparison"}, + "status": {"const": "MISSING"}, "assertions": {"$ref": "#/$defs/assertions"}, "reconciliation_sha256": {"$ref": "#/$defs/sha256"} }, - "oneOf": [ - { - "properties": {"status": {"const": "MISSING"}}, - "not": { - "anyOf": [ - {"required": ["observed_identity"]}, - {"required": ["observer_receipt"]}, - {"required": ["comparison"]} - ] - } - }, - { - "properties": {"status": {"enum": ["MATCHED", "MISMATCHED"]}}, - "required": ["observed_identity", "observer_receipt", "comparison"] - } - ], "$defs": { "identity": {"type": "string", "pattern": "^[a-z][a-z0-9]*(?:[._-][a-z0-9]+)*$"}, "revision": {"type": "string", "pattern": "^[0-9a-f]{40}$"}, @@ -136,89 +117,15 @@ "account_digest_sha256": {"$ref": "#/$defs/sha256"} } }, - "observedIdentity": { - "allOf": [ - {"$ref": "#/$defs/comparableIdentity"}, - { - "type": "object", - "required": ["producer_id", "producer_revision", "artifact_sha256", "observed_at", "observed_identity_sha256"], - "properties": { - "producer_id": {"$ref": "#/$defs/identity"}, - "producer_revision": {"$ref": "#/$defs/revision"}, - "artifact_sha256": {"$ref": "#/$defs/sha256"}, - "observed_at": {"$ref": "#/$defs/timestamp"}, - "observed_identity_sha256": {"$ref": "#/$defs/sha256"} - } - } - ], - "unevaluatedProperties": false - }, - "observerReceipt": { - "type": "object", - "additionalProperties": false, - "required": ["schema", "observer_id", "observer_revision", "created_at", "observed_identity", "observer_receipt_sha256"], - "properties": { - "schema": {"const": "qsl.reconciliation_observer_receipt.v1"}, - "observer_id": {"$ref": "#/$defs/identity"}, - "observer_revision": {"$ref": "#/$defs/revision"}, - "created_at": {"$ref": "#/$defs/timestamp"}, - "observed_identity": {"$ref": "#/$defs/observedIdentity"}, - "observer_receipt_sha256": {"$ref": "#/$defs/sha256"} - } - }, - "comparisonEntry": { - "type": "object", - "additionalProperties": false, - "required": ["expected", "observed", "equal"], - "properties": { - "expected": {"type": "string", "minLength": 1}, - "observed": {"type": "string", "minLength": 1}, - "equal": {"type": "boolean"} - } - }, - "comparison": { - "type": "object", - "additionalProperties": false, - "required": ["complete", "fields"], - "properties": { - "complete": {"const": true}, - "fields": { - "type": "object", - "additionalProperties": false, - "required": [ - "deployment_bundle_sha256", - "activation_id", - "activation_sha256", - "platform", - "repository", - "revision", - "environment", - "account_alias", - "account_digest_sha256" - ], - "properties": { - "deployment_bundle_sha256": {"$ref": "#/$defs/comparisonEntry"}, - "activation_id": {"$ref": "#/$defs/comparisonEntry"}, - "activation_sha256": {"$ref": "#/$defs/comparisonEntry"}, - "platform": {"$ref": "#/$defs/comparisonEntry"}, - "repository": {"$ref": "#/$defs/comparisonEntry"}, - "revision": {"$ref": "#/$defs/comparisonEntry"}, - "environment": {"$ref": "#/$defs/comparisonEntry"}, - "account_alias": {"$ref": "#/$defs/comparisonEntry"}, - "account_digest_sha256": {"$ref": "#/$defs/comparisonEntry"} - } - } - } - }, "assertions": { "type": "object", "additionalProperties": false, - "required": ["apply_performed", "config_sync_performed", "runtime_mutation_performed", "runtime_active", "fills_verified", "capital_use_verified"], + "required": ["apply_performed", "config_sync_performed", "runtime_mutation_performed", "runtime_active_verified", "fills_verified", "capital_use_verified"], "properties": { "apply_performed": {"const": false}, "config_sync_performed": {"const": false}, "runtime_mutation_performed": {"const": false}, - "runtime_active": {"const": false}, + "runtime_active_verified": {"const": false}, "fills_verified": {"const": false}, "capital_use_verified": {"const": false} }