diff --git a/Makefile b/Makefile index f495401..9040897 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ -.PHONY: validate validate-resource-contract validate-measurement validate-value-type validate-source-locator validate-sourceos-repo-manifest validate-mesh-action-registry validate-control-plane-examples validate-nlboot-examples validate-lattice-data-governai-examples validate-ops-history-examples validate-runtime-observability-examples validate-interpretability-examples validate-lifecycle-boundary-examples validate-svf-contracts validate-sync-cycle-receipts validate-onboarding-examples validate-runtime-causality-examples validate-agentic-os-examples validate-triparty-examples validate-labor-market-examples validate-supply-chain-risk-examples validate-reasoning-examples validate-mpcc-event-examples validate-knowledge-nugget-examples validate-semantic-action-examples validate-epistemic-kernel-examples validate-ab-update-examples validate-device-service-examples validate-duplicate-schema-ids validate-lawful-dispatch-receipt validate-architectural-building-block validate-agent-passport-examples validate-seam-definition-examples validate-agent-system-vocabulary validate-genesis-inception-examples validate-measurement validate-world-model-examples validate-eval-item-examples validate-ingestion-pipeline-examples validate-data-acquisition-examples validate-glossary-alignment-examples validate-data-class-examples validate-table-keys validate-dag-loop validate-vocab-currency-loop vocab-currency-dogfood validate-glossary-promotion validate-agreement validate-stopword-analysis stopword-analysis-live validate-kgram-differential validate-learned-dictionary train-dataclass-classifiers validate-schema-references +.PHONY: validate validate-resource-contract validate-measurement validate-value-type validate-source-locator validate-sourceos-repo-manifest validate-mesh-action-registry validate-control-plane-examples validate-nlboot-examples validate-lattice-data-governai-examples validate-ops-history-examples validate-runtime-observability-examples validate-interpretability-examples validate-lifecycle-boundary-examples validate-svf-contracts validate-sync-cycle-receipts validate-onboarding-examples validate-runtime-causality-examples validate-agentic-os-examples validate-triparty-examples validate-labor-market-examples validate-supply-chain-risk-examples validate-reasoning-examples validate-mpcc-event-examples validate-knowledge-nugget-examples validate-semantic-action-examples validate-epistemic-kernel-examples validate-ab-update-examples validate-device-service-examples validate-duplicate-schema-ids validate-lawful-dispatch-receipt validate-architectural-building-block validate-agent-passport-examples validate-seam-definition-examples validate-agent-system-vocabulary validate-genesis-inception-examples validate-measurement validate-world-model-examples validate-eval-item-examples validate-ingestion-pipeline-examples validate-data-acquisition-examples validate-glossary-alignment-examples validate-data-class-examples validate-table-keys validate-dag-loop validate-vocab-currency-loop vocab-currency-dogfood validate-glossary-promotion validate-agreement validate-stopword-analysis stopword-analysis-live validate-kgram-differential validate-learned-dictionary train-dataclass-classifiers validate-schema-references validate-capture-receipt-examples -validate: validate-data-class-examples validate-glossary-alignment-examples validate-data-acquisition-examples validate-ingestion-pipeline-examples validate-control-plane-examples validate-nlboot-examples validate-lattice-data-governai-examples validate-ops-history-examples validate-runtime-observability-examples validate-interpretability-examples validate-lifecycle-boundary-examples validate-svf-contracts validate-sync-cycle-receipts validate-onboarding-examples validate-runtime-causality-examples validate-agentic-os-examples validate-triparty-examples validate-labor-market-examples validate-supply-chain-risk-examples validate-reasoning-examples validate-mpcc-event-examples validate-knowledge-nugget-examples validate-semantic-action-examples validate-epistemic-kernel-examples validate-ab-update-examples validate-device-service-examples validate-duplicate-schema-ids validate-value-type validate-source-locator validate-sourceos-repo-manifest validate-mesh-action-registry validate-lawful-dispatch-receipt validate-architectural-building-block validate-agent-passport-examples validate-seam-definition-examples validate-agent-system-vocabulary validate-genesis-inception-examples validate-measurement validate-world-model-examples validate-eval-item-examples validate-resource-contract validate-table-keys validate-dag-loop validate-vocab-currency-loop validate-glossary-promotion validate-agreement validate-stopword-analysis validate-kgram-differential validate-learned-dictionary validate-trained-classifiers validate-multiverseal-twin-examples validate-schema-references +validate: validate-data-class-examples validate-glossary-alignment-examples validate-data-acquisition-examples validate-ingestion-pipeline-examples validate-control-plane-examples validate-nlboot-examples validate-lattice-data-governai-examples validate-ops-history-examples validate-runtime-observability-examples validate-interpretability-examples validate-lifecycle-boundary-examples validate-svf-contracts validate-sync-cycle-receipts validate-onboarding-examples validate-runtime-causality-examples validate-agentic-os-examples validate-triparty-examples validate-labor-market-examples validate-supply-chain-risk-examples validate-reasoning-examples validate-mpcc-event-examples validate-knowledge-nugget-examples validate-semantic-action-examples validate-epistemic-kernel-examples validate-ab-update-examples validate-device-service-examples validate-duplicate-schema-ids validate-value-type validate-source-locator validate-sourceos-repo-manifest validate-mesh-action-registry validate-lawful-dispatch-receipt validate-architectural-building-block validate-agent-passport-examples validate-seam-definition-examples validate-agent-system-vocabulary validate-genesis-inception-examples validate-measurement validate-world-model-examples validate-eval-item-examples validate-resource-contract validate-table-keys validate-dag-loop validate-vocab-currency-loop validate-glossary-promotion validate-agreement validate-stopword-analysis validate-kgram-differential validate-learned-dictionary validate-trained-classifiers validate-multiverseal-twin-examples validate-schema-references validate-capture-receipt-examples @echo "OK: validate" # General referential-integrity gate: every schema is a valid JSON Schema and every @@ -226,3 +226,7 @@ validate-value-type: validate-multiverseal-twin-examples: python3 -m pip install --user jsonschema >/dev/null python3 tools/validate_multiverseal_twin_examples.py + +validate-capture-receipt-examples: + python3 -m pip install --user jsonschema >/dev/null + python3 tools/validate_capture_receipt_examples.py diff --git a/docs/adr/0019-capture-receipt.md b/docs/adr/0019-capture-receipt.md new file mode 100644 index 0000000..e75543a --- /dev/null +++ b/docs/adr/0019-capture-receipt.md @@ -0,0 +1,36 @@ +# ADR-0019: CaptureReceipt — the universal bind-purpose-before-f() primitive + +Status: Accepted +Date: 2026-08-03 +Relates-to: ADR-0014 (multiverseal twin identity) + +## Context + +Epoch E13 / workstream WS-B. `TwinAttestation.envelope.authorization` already binds a +declared purpose + proof at ingest, but only inside the twin family. The reidentification- +economy result makes that binding a universal necessity, not a twin-local nicety: purpose is +provably **unrecoverable** from the signal — the mutual information `I(purpose; Q)` between a +purpose bit and a quasi-identifier-bearing signal `Q` is `0`. So purpose cannot be inferred +after the fact; it has to be bound BEFORE the transform `f()` runs, at capture, or it is gone. +We need one domain-agnostic object that seals it — for any capture, not just twins. + +## Decisions + +1. **`CaptureReceipt` generalizes the twin authorization** into a standalone contract: + `declaredPurpose` + `authorizationProof` + `boundBeforeTransform: true`, gating a named + `transform` (the `f()`), optionally bound against a `contextRef` (reference-at-ingest / VRF). +2. **Fail-closed.** `disposition ∈ {admitted, refused, inert}`. A receipt with a missing/empty + purpose or proof MUST be `refused` or `inert` — you cannot be `admitted` without a bound + purpose+proof. Enforced in-schema (required fields) and machine-checked by the validator. +3. **Ties to WS-D.** `reversibilityFloorBits` records the reversibility-distance floor this + capture re-establishes, linking WS-B to `ReversibilityDistance`. +4. Canonical examples (`capture_receipt.json` admitted, `capture_receipt_refused.json` refused) + and an invariant validator wired into `make validate`. + +## Consequences + +- A single primitive now carries the capture-time purpose bit across every domain; the twin's + `envelope.authorization` becomes one specialization of it rather than the only home. +- The purpose bit is sealed where it is the only place it can live — at capture — closing the + post-hoc-inference gap the reidentification result proves is otherwise unrecoverable. +- Downstream consumers can gate `f()` on a resolvable receipt and refuse fail-closed. diff --git a/examples/capture_receipt.json b/examples/capture_receipt.json new file mode 100644 index 0000000..288efa0 --- /dev/null +++ b/examples/capture_receipt.json @@ -0,0 +1,20 @@ +{ + "id": "urn:srcos:capture-receipt:abc123:qie:1", + "type": "CaptureReceipt", + "specVersion": "2.0.0", + "capturedAt": "2026-08-03T00:00:00Z", + "subjectRef": "pseudo:abc123", + "transform": { + "name": "quasi-identifier-extraction", + "inputRef": "urn:srcos:capture-input:abc123:raw" + }, + "contextRef": "urn:srcos:context-ref:abc123:math-commons", + "declaredPurpose": "research-cohort-linkage-under-consent", + "authorizationProof": "sig:consent:abc123:0xdeadbeef", + "boundBeforeTransform": true, + "evidenceRefs": [ + "urn:srcos:reasoning-receipt:abc123:consent-check:1" + ], + "disposition": "admitted", + "reversibilityFloorBits": 12.5 +} diff --git a/examples/capture_receipt_refused.json b/examples/capture_receipt_refused.json new file mode 100644 index 0000000..9369ed2 --- /dev/null +++ b/examples/capture_receipt_refused.json @@ -0,0 +1,15 @@ +{ + "id": "urn:srcos:capture-receipt:def456:qie:1", + "type": "CaptureReceipt", + "specVersion": "2.0.0", + "capturedAt": "2026-08-03T00:00:00Z", + "subjectRef": "pseudo:def456", + "transform": { + "name": "quasi-identifier-extraction" + }, + "contextRef": "urn:srcos:context-ref:def456:unknown", + "declaredPurpose": "", + "authorizationProof": "", + "boundBeforeTransform": true, + "disposition": "refused" +} diff --git a/schemas/CaptureReceipt.json b/schemas/CaptureReceipt.json new file mode 100644 index 0000000..41d4663 --- /dev/null +++ b/schemas/CaptureReceipt.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.srcos.ai/v2/CaptureReceipt.json", + "title": "CaptureReceipt", + "description": "The universal capture-time receipt: the primitive that binds a declared purpose + authorization to a datum BEFORE the transform f() that would consume it runs. It generalizes TwinAttestation.envelope.authorization into a domain-agnostic object. The reidentification-economy result is the reason it must exist: purpose is provably unrecoverable from the signal — the mutual information I(purpose; Q) between a purpose bit and a quasi-identifier-bearing signal Q is 0 — so purpose CANNOT be inferred post-hoc from the captured data. It has to be bound at capture, sealed into a receipt, or it is gone forever. CaptureReceipt is that seal: it is fail-closed (no valid purpose+proof ⇒ not admitted) and it re-establishes a reversibility-distance floor tying capture to WS-D (ReversibilityDistance).", + "type": "object", + "additionalProperties": false, + "required": ["id", "type", "specVersion", "capturedAt", "declaredPurpose", "authorizationProof", "boundBeforeTransform", "disposition"], + "properties": { + "id": { "type": "string", "pattern": "^urn:srcos:capture-receipt:", "description": "Stable URN for this capture receipt." }, + "type": { "const": "CaptureReceipt", "description": "Type discriminator; always 'CaptureReceipt'." }, + "specVersion": { "type": "string", "description": "Semantic version of the contract." }, + "capturedAt": { "type": "string", "format": "date-time", "description": "When capture occurred and this receipt was sealed — necessarily BEFORE the transform f() ran." }, + "subjectRef": { "type": "string", "description": "Pseudonymous, agent-centric reference to whom/what is captured. Never a real-world identifier." }, + "transform": { + "type": "object", + "additionalProperties": false, + "required": ["name"], + "description": "The transform f() this receipt gates — the operation the capture authorizes and precedes.", + "properties": { + "name": { "type": "string", "description": "The f() this receipt gates, e.g. 'quasi-identifier-extraction'." }, + "inputRef": { "type": "string", "description": "Optional reference to the input the transform will consume." } + } + }, + "contextRef": { "type": "string", "description": "The reference the capture is bound against (ties to reference-at-ingest / the VRF-derived context reference). Optional but recommended." }, + "declaredPurpose": { "type": "string", "description": "The purpose under which capture is authorized. Bound at capture because it is provably unrecoverable afterward (I(purpose; Q) = 0)." }, + "authorizationProof": { "type": "string", "description": "Proof of authorization for the declared purpose." }, + "boundBeforeTransform": { "const": true, "description": "Asserts the receipt was bound BEFORE f() ran. Always true — the purpose bit cannot be recovered post-hoc, so it must be sealed at capture." }, + "evidenceRefs": { + "type": "array", + "description": "Works-receipts on the evidence fabric supporting this capture.", + "items": { "type": "string", "pattern": "^urn:srcos:reasoning-receipt:" } + }, + "disposition": { + "type": "string", + "enum": ["admitted", "refused", "inert"], + "description": "admitted = capture proceeds; refused = no valid authorization, capture blocked (fail-closed); inert = retained but never summed/used." + }, + "reversibilityFloorBits": { "type": "number", "description": "The reversibility-distance floor (bits) this capture re-establishes; ties WS-B to WS-D (ReversibilityDistance)." } + } +} diff --git a/tools/validate_capture_receipt_examples.py b/tools/validate_capture_receipt_examples.py new file mode 100644 index 0000000..eebbc81 --- /dev/null +++ b/tools/validate_capture_receipt_examples.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +"""Validate CaptureReceipt examples + the invariants that make the receipt safe. + +CaptureReceipt (Epoch E13 / WS-B) is the universal bind-purpose-before-f() primitive: +it seals a declared purpose + authorization onto a datum BEFORE the transform that would +consume it runs. It generalizes TwinAttestation.envelope.authorization. The reason it must +exist at capture — not later — is the reidentification-economy result that purpose is +provably unrecoverable from the signal (I(purpose; Q) = 0). + +Beyond JSON Schema: + * URN prefix per contract (urn:srcos:capture-receipt:); + * boundBeforeTransform is exactly true (the whole point — sealed before f()); + * fail-closed: missing/empty declaredPurpose OR authorizationProof ⇒ disposition + MUST be 'refused' or 'inert' (you cannot be 'admitted' without a bound purpose+proof); + * disposition ∈ {admitted, refused, inert}. +""" +from __future__ import annotations +import json, sys +from pathlib import Path +import jsonschema + +ROOT = Path(__file__).resolve().parents[1] +PAIRS = [ + ("schemas/CaptureReceipt.json", "examples/capture_receipt.json"), + ("schemas/CaptureReceipt.json", "examples/capture_receipt_refused.json"), +] +EXPECTED_ID_PREFIX = "urn:srcos:capture-receipt:" +VALID_DISPOSITIONS = {"admitted", "refused", "inert"} + + +def main() -> int: + errors: list[str] = [] + for schema_rel, ex_rel in PAIRS: + schema = json.loads((ROOT / schema_rel).read_text()) + doc = json.loads((ROOT / ex_rel).read_text()) + try: + jsonschema.validate(doc, schema, cls=jsonschema.Draft202012Validator) + except jsonschema.ValidationError as e: + errors.append(f"{ex_rel}: schema invalid: {e.message} @ {list(e.absolute_path)}") + continue + + # 1. URN prefix. + if not str(doc.get("id", "")).startswith(EXPECTED_ID_PREFIX): + errors.append(f"{ex_rel}: id must start with '{EXPECTED_ID_PREFIX}'") + + # 2. boundBeforeTransform is exactly true. + if doc.get("boundBeforeTransform") is not True: + errors.append(f"{ex_rel}: boundBeforeTransform must be exactly true (sealed before f())") + + # 4. disposition in the closed set. + disp = doc.get("disposition") + if disp not in VALID_DISPOSITIONS: + errors.append(f"{ex_rel}: disposition must be one of {sorted(VALID_DISPOSITIONS)}") + + # 3. Fail-closed: no bound purpose+proof ⇒ cannot be admitted. + purpose = str(doc.get("declaredPurpose", "")).strip() + proof = str(doc.get("authorizationProof", "")).strip() + if (not purpose or not proof) and disp == "admitted": + errors.append( + f"{ex_rel}: fail-closed violation: disposition 'admitted' requires a " + f"non-empty declaredPurpose AND authorizationProof" + ) + + if not any(e.startswith(ex_rel) for e in errors): + print(f"OK {ex_rel}") + + if errors: + print("\nVALIDATION FAILED:") + for e in errors: + print(f" - {e}") + return 1 + print("\nAll CaptureReceipt examples valid (schema + invariants).") + return 0 + + +if __name__ == "__main__": + sys.exit(main())