diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f5e5cb7..f775630 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,6 +18,11 @@ jobs: python-version: ["3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 + - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 + with: + repository: nicoechaniz/collective-memory + ref: 3e3b39416917f8e3c2bc5ca69362b20296205938 + path: .collective-memory-contract - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 with: python-version: ${{ matrix.python-version }} @@ -42,14 +47,22 @@ jobs: - run: python -m ruff format --check tools/generate_dm070_vectors.py tests/test_dm070_multihost.py - run: python -m ruff check tools/generate_dm070_vectors.py tests/test_dm070_multihost.py - run: MYPYPATH=src python -m mypy tools/generate_dm070_vectors.py tests/test_dm070_multihost.py + - run: python -m ruff format --check tools/generate_dm036_vectors.py tests/test_dm036_collective_memory.py + - run: python -m ruff check tools/generate_dm036_vectors.py tests/test_dm036_collective_memory.py + - run: MYPYPATH=src python -m mypy tools/generate_dm036_vectors.py tests/test_dm036_collective_memory.py - run: python tools/generate_dm041_vectors.py --check - run: python tools/generate_dm042_vectors.py --check + - run: python tools/generate_dm036_vectors.py --check + env: + COLLECTIVE_MEMORY_CONTRACT_ROOT: ${{ github.workspace }}/.collective-memory-contract - run: python tools/generate_dm061_vectors.py --check - run: python tools/generate_dm070_vectors.py --check - run: python tools/generate_dm081_vectors.py --check - run: python tools/generate_dm082_vectors.py --check - run: python -m compileall -q coordination src tools tests - run: PYTHONPATH=src python -W error::ResourceWarning -m unittest discover -s tests -v + env: + COLLECTIVE_MEMORY_CONTRACT_ROOT: ${{ github.workspace }}/.collective-memory-contract - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 with: repository: nicoechaniz/hermes-memory-kit @@ -112,6 +125,11 @@ jobs: - run: python -m pip install -r requirements-build.txt - run: python tools/reproducible_build.py --output dist - run: python tools/scan_secrets.py . dist/daimon_matrix-0.0.0.tar.gz dist/daimon_matrix-0.0.0-py3-none-any.whl + - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 + with: + repository: nicoechaniz/collective-memory + ref: 3e3b39416917f8e3c2bc5ca69362b20296205938 + path: .collective-memory-contract - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 with: repository: nicoechaniz/hermes-memory-kit @@ -120,6 +138,7 @@ jobs: - name: Smoke-test the installed wheel env: HMK_CONTRACT_ROOT: ${{ github.workspace }}/.hmk-contract + COLLECTIVE_MEMORY_CONTRACT_ROOT: ${{ github.workspace }}/.collective-memory-contract run: | python -m venv "${RUNNER_TEMP}/dm020-wheel" "${RUNNER_TEMP}/dm020-wheel/bin/python" -m pip install -r requirements-vectors.txt @@ -129,6 +148,7 @@ jobs: "${RUNNER_TEMP}/dm020-wheel/bin/python" -c 'import importlib.metadata as m; import daimon_matrix; assert daimon_matrix.__version__ == "0.0.0"; assert m.requires("daimon-matrix") == ["cryptography==50.0.0", "mcp==2.0.0", "wasmtime==45.0.0"]' "${RUNNER_TEMP}/dm020-wheel/bin/python" -c 'from daimon_matrix.identity import create_genesis, verify_genesis; roots=[bytes([n])*32 for n in (1,2,3)]; recovery=[bytes([n])*32 for n in (4,5,6)]; artifact=create_genesis(roots,2,recovery,2,created_at_ms=1); assert verify_genesis(artifact).being_ref.startswith("dm:being:v1:")' "${RUNNER_TEMP}/dm020-wheel/bin/python" -c 'from daimon_matrix.ledger import Ledger; from daimon_matrix.weave import BeingManifest; assert Ledger and BeingManifest' + "${RUNNER_TEMP}/dm020-wheel/bin/python" -c 'from daimon_matrix.collective_memory import create_source_manifest, create_publisher_manifest; assert create_source_manifest()["adapter_id"] != create_publisher_manifest()["adapter_id"]' "${RUNNER_TEMP}/dm020-wheel/bin/python" -c 'from daimon_matrix.sealed import PROFILE; from cryptography.hazmat.primitives.hpke import Suite; assert PROFILE.startswith("HPKE-X25519") and Suite' "${RUNNER_TEMP}/dm020-wheel/bin/python" -W error::ResourceWarning -m unittest tests.test_dm022_ledger -q "${RUNNER_TEMP}/dm020-wheel/bin/python" -W error::ResourceWarning -m unittest tests.test_dm023_sync -q @@ -137,6 +157,7 @@ jobs: "${RUNNER_TEMP}/dm020-wheel/bin/python" -W error::ResourceWarning -m unittest tests.test_dm033_human_review -q "${RUNNER_TEMP}/dm020-wheel/bin/python" -W error::ResourceWarning -m unittest tests.test_dm034_memory_projection -q "${RUNNER_TEMP}/dm020-wheel/bin/python" -W error::ResourceWarning -m unittest tests.test_dm035_publication.DM035PublicContractTests -q + "${RUNNER_TEMP}/dm020-wheel/bin/python" -W error::ResourceWarning -m unittest tests.test_dm036_collective_memory.DM036ContractTests -q "${RUNNER_TEMP}/dm020-wheel/bin/daimon-matrixd" --help "${RUNNER_TEMP}/dm020-wheel/bin/daimon-bootstrap" --help "${RUNNER_TEMP}/dm020-wheel/bin/daimon" --help @@ -191,6 +212,11 @@ jobs: requirements-vectors.txt - run: python -m pip install -r requirements-build.txt -r requirements-vectors.txt - run: python tools/reproducible_build.py --output dist + - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 + with: + repository: nicoechaniz/collective-memory + ref: 3e3b39416917f8e3c2bc5ca69362b20296205938 + path: .collective-memory-contract - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 with: repository: nicoechaniz/hermes-memory-kit @@ -199,6 +225,7 @@ jobs: - name: Run installed deterministic conformance twice env: HMK_CONTRACT_ROOT: ${{ github.workspace }}/.hmk-contract + COLLECTIVE_MEMORY_CONTRACT_ROOT: ${{ github.workspace }}/.collective-memory-contract run: | python -m venv "${RUNNER_TEMP}/dm026-wheel" "${RUNNER_TEMP}/dm026-wheel/bin/python" -m pip install -r requirements-vectors.txt mcp==2.0.0 diff --git a/CONCURRENT-WORK-AUDIT.md b/CONCURRENT-WORK-AUDIT.md index ea16659..3e13a51 100644 --- a/CONCURRENT-WORK-AUDIT.md +++ b/CONCURRENT-WORK-AUDIT.md @@ -114,6 +114,15 @@ credential patterns, and prevent revocation from deleting an untracked target. Inbound collective knowledge is a separate attributed source/quarantine direction and must use the supported API or an atomic snapshot boundary. +DM-036 resolves that pending boundary against the public MIT upstream commit +`3e3b39416917f8e3c2bc5ca69362b20296205938`, with closed schema SHA-256 +`2aad43d1b309ee95108c855fc8dc682a854e5fdf3a1e799ecfca96d3ebf7c5d9`. +The exact repository/tree/blob/license inventory is +`provenance/collective-memory-exchange-v1.json`. No upstream source, database, +WAL, corpus or live state is imported or vendored. Matrix uses two independent +injected contracts: immutable export into quarantine and exact reviewed +publication with fresh effect-truth reconciliation. + ### compaii-state and Wiki Retain hash-pinned generations, staging, conflict checks, classified artifact diff --git a/README.md b/README.md index 0a34b06..535f191 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,15 @@ adopts a shared target and Hermes locally rejects it. The published `dm.local-we.validation/v1` receipt is deterministic and path-free; the card is a synthetic single-host gate, not a Matrix.org, multihost Cluster or CompAII rebirth claim. +DM-036 adds independent inbound and outbound `collective-memory` adapters over +the exact public exchange-v1 contract. Immutable generations enter only an +append-only source log plus `source.imported` quarantine evidence, with offline +catch-up and ledger/source-log rebuild. Publication requires exact current +Matrix source refs, recomputable checkpoints, subject consent, independent +human review, deterministic final-byte secret scanning and fresh upstream +effect truth. The directions have content-addressed DM-018 identities and no +shared credential, store, queue, receipt or authority; the real-I/O lane uses +only isolated synthetic corpus roots. DM-060 adds the root-authorized birth V1 ceremony and installed `daimon-synthetic-birth` acceptance. A parent can offer attributed context but cannot precommit or retain the newborn root; the newborn independently accepts, diff --git a/conformance/registry-v1.json b/conformance/registry-v1.json index 06e40a2..d0f5882 100644 --- a/conformance/registry-v1.json +++ b/conformance/registry-v1.json @@ -129,6 +129,62 @@ "ci_lane": "complete", "required": true }, + { + "id": "collective_exchange_recovery", + "owners": ["DM-022", "DM-036"], + "specifications": ["docs/dm036-collective-memory.md", "specs/source-ancestry.md"], + "setup": "Separate owner-only inbound source-log and outbound publication journals with one pinned immutable collective-memory generation and one reviewed derived target.", + "stimulus": "Crash after inbound prepare, after its Matrix event, after outbound prepare, after provider effect and after Matrix acceptance, then restart each adapter.", + "fault": "Process loss occurs on every side of the authoritative Matrix events and external content-addressed receipts.", + "expected": "Recovery deterministically completes all-old or all-new, authors or discovers exactly one event and never advances a mixed generation or serves blind replay.", + "evidence": ["tests.test_dm036_collective_memory.DM036IntegrationTests.test_inbound_recovers_crash_after_ledger_without_mixed_head", "tests.test_dm036_collective_memory.DM036IntegrationTests.test_inbound_recovers_crash_after_prepare_before_ledger", "tests.test_dm036_collective_memory.DM036IntegrationTests.test_outbound_recovers_after_acceptance_event_before_journal_commit", "tests.test_dm036_collective_memory.DM036IntegrationTests.test_outbound_recovers_after_effect_before_matrix_acceptance", "tests.test_dm036_collective_memory.DM036IntegrationTests.test_outbound_recovers_after_prepare_before_request_event"], + "cleanup": "Close both SQLite stores and discard only fixture-owned source, publication and corpus roots.", + "platform": "linux", + "ci_lane": "complete", + "required": true + }, + { + "id": "collective_publication_review", + "owners": ["DM-015", "DM-018", "DM-033", "DM-036"], + "specifications": ["docs/dm036-collective-memory.md", "specs/adapter-contracts.md", "specs/source-ancestry.md"], + "setup": "Pinned reviewed-publisher boundary, exact current Matrix source checkpoint, subject consent key and distinct current human review key.", + "stimulus": "Publish, retry, advance a successor and tombstone, then substitute evidence, final bytes, source checkpoint, target state and effect truth.", + "fault": "Expired or revoked consent, self-review, final-render secret, stale predecessor, response loss, drift or an inbound credential attempts to authorize publication.", + "expected": "Only exact final reviewed bytes commit monotonically; every retry re-observes effect truth and neither inbound trust nor an external receipt gains Matrix authority.", + "evidence": ["tests.test_dm036_collective_memory.DM036IntegrationTests.test_direction_separation_and_exact_review_fail_closed", "tests.test_dm036_collective_memory.DM036IntegrationTests.test_outbound_real_boundary_publish_response_loss_and_replay", "tests.test_dm036_collective_memory.DM036IntegrationTests.test_outbound_rejects_expired_or_revoked_consent_and_source_drift", "tests.test_dm036_collective_memory.DM036IntegrationTests.test_outbound_rejects_provider_preview_injection_untracked_tombstone_and_drift", "tests.test_dm036_collective_memory.DM036IntegrationTests.test_outbound_successor_and_reviewed_tombstone_are_monotonic"], + "cleanup": "Remove isolated external targets, projection databases, Matrix ledger and publisher journal.", + "platform": "linux", + "ci_lane": "complete", + "required": true + }, + { + "id": "collective_real_io", + "owners": ["DM-015", "DM-036"], + "specifications": ["docs/dm036-collective-memory.md", "schemas/collective-memory/v1/contracts.schema.json"], + "setup": "Real pinned collective-memory export and publication boundaries over an isolated synthetic corpus with FTS SQLite and Atlas projections plus unrelated content.", + "stimulus": "Export, quarantine in Matrix, publish an independently reviewed derivative, verify search and Atlas, then publish a reviewed tombstone and reindex.", + "fault": "A direction shares SQLite, WAL or SHM state, commit exposes a mixed generation, or the transaction mutates unrelated corpus content.", + "expected": "Every view is old-or-new, both databases pass integrity checks, the target converges through tombstone and unrelated content remains exact.", + "evidence": ["tests.test_dm036_collective_memory.DM036IntegrationTests.test_real_export_matrix_quarantine_publish_search_atlas_and_tombstone"], + "cleanup": "Close SQLite handles and remove the complete isolated corpus and both independent Matrix adapter roots.", + "platform": "linux", + "ci_lane": "complete", + "required": true + }, + { + "id": "collective_source_quarantine", + "owners": ["DM-015", "DM-018", "DM-023", "DM-036"], + "specifications": ["docs/dm036-collective-memory.md", "specs/source-ancestry.md"], + "setup": "Pinned read-only export capability, immutable attributed generations, separate append-only Matrix source log and root-authorized ledger.", + "stimulus": "Preview and apply initial, paged, successor, tombstone and offline generations, then rebuild from the authoritative Matrix event and local immutable source log.", + "fault": "Tampered or partial pages, implicit removal, predecessor fork, symlink, timeout or collective text attempts to become autobiographical memory.", + "expected": "Only complete continuous generations advance; every artifact remains attributed and quarantined, prior active truth survives failure and personal-memory assertions remain exactly zero.", + "evidence": ["tests.test_dm036_collective_memory.DM036IntegrationTests.test_inbound_contract_rejects_adversarial_manifests_and_partial_page", "tests.test_dm036_collective_memory.DM036IntegrationTests.test_inbound_initial_retry_successor_and_tombstone_remain_quarantined", "tests.test_dm036_collective_memory.DM036IntegrationTests.test_inbound_offline_catch_up_walks_historical_manifests", "tests.test_dm036_collective_memory.DM036IntegrationTests.test_inbound_paginates_bounded_generation_without_large_ledger_event", "tests.test_dm036_collective_memory.DM036IntegrationTests.test_inbound_rebuilds_active_projection_from_ledger_and_source_log", "tests.test_dm036_collective_memory.DM036IntegrationTests.test_inbound_rejects_artifact_fork_and_symlink_source"], + "cleanup": "Close the inbound source store and ledger, then remove their separate owner-only fixture roots.", + "platform": "linux", + "ci_lane": "complete", + "required": true + }, { "id": "communication_cursor_contiguous", "owners": ["DM-052"], diff --git a/conformance/source-v0-section14.json b/conformance/source-v0-section14.json index 7ba4886..b93323a 100644 --- a/conformance/source-v0-section14.json +++ b/conformance/source-v0-section14.json @@ -1 +1 @@ -{"row_count":84,"rows":[{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_claim_binding_and_evidence_are_closed_and_content_bound","tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-001","index":1,"required_result":"intrinsically valid, locally quarantined","scenario":"valid sequence-zero self-claim with complete evidence"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_paginated_pull_keeps_starting_cursor_until_terminal_page","tests.test_dm081_sources.SourceWireContractTests.test_cyclic_and_disconnected_provenance_is_rejected","tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-002","index":2,"required_result":"reject `false-self`","scenario":"claim author differs from claimant"},{"evidence":["tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-003","index":3,"required_result":"reject","scenario":"root, transport, Tribe, harness, host, model, or adapter signs in place of accepted operational credential"},{"evidence":["tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-004","index":4,"required_result":"reject policy implementation","scenario":"valid signature is treated as true ancestry"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_source_identity_is_byte_exact_and_selector_bound","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-005","index":5,"required_result":"reject","scenario":"source ID does not recompute from exact core"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_source_identity_is_byte_exact_and_selector_bound","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-006","index":6,"required_result":"distinct source or reject mismatch; never merge","scenario":"source alias or normalized URI is substituted"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_source_identity_is_byte_exact_and_selector_bound","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-007","index":7,"required_result":"reject","scenario":"source core embeds credentials, userinfo, whitespace, control, or locator behavior"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-008","index":8,"required_result":"reject","scenario":"relation unknown, empty, unsorted, or duplicated"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_claim_binding_and_evidence_are_closed_and_content_bound","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-009","index":9,"required_result":"reject","scenario":"assert has null evidence or retract has evidence"},{"evidence":["tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-010","index":10,"required_result":"reject","scenario":"sequence gap or wrong predecessor ID/hash"},{"evidence":["tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-011","index":11,"required_result":"idempotent","scenario":"exact event replay"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_same_claim_position_from_two_embodiments_quarantines_series","tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-012","index":12,"required_result":"quarantine series; no winner","scenario":"two successors occupy one claim position"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_same_claim_position_from_two_embodiments_quarantines_series","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-013","index":13,"required_result":"retain and quarantine descendants","scenario":"late sibling arrives below high-water"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_same_claim_position_from_two_embodiments_quarantines_series","tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-014","index":14,"required_result":"reject/fork remains","scenario":"ordinary successor attempts to heal fork"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-015","index":15,"required_result":"exclude claimant; retain history","scenario":"retraction becomes current"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-016","index":16,"required_result":"reject; retraction covers exact current assertion","scenario":"retraction changes only a subset of predecessor relations"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-017","index":17,"required_result":"historical only; no eligibility","scenario":"expired claim replays after a newer head"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_claim_binding_and_evidence_are_closed_and_content_bound","tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-018","index":18,"required_result":"valid new head, initially quarantined","scenario":"assertion after retraction with fresh predecessor/evidence"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_claim_binding_and_evidence_are_closed_and_content_bound","tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-019","index":19,"required_result":"reject","scenario":"evidence manifest binding hash names another claim"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_claim_binding_and_evidence_are_closed_and_content_bound","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-020","index":20,"required_result":"claim incomplete/quarantined, never admitted","scenario":"evidence is missing but well-formed"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_claim_binding_and_evidence_are_closed_and_content_bound","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-021","index":21,"required_result":"discard bytes; remain incomplete","scenario":"evidence bytes mismatch digest"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_claim_binding_and_evidence_are_closed_and_content_bound","tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-022","index":22,"required_result":"reject policy implementation","scenario":"claimant's own evidence or many matching claims are treated as objective quorum"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_cyclic_and_disconnected_provenance_is_rejected","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-023","index":23,"required_result":"downgrade is forbidden; reject manifest","scenario":"cryptographic-author label lacks exact author/content signature"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_claim_starts_quarantined_then_exact_local_assessment_admits","tests.test_dm081_sources.SourceWireContractTests.test_claim_binding_and_evidence_are_closed_and_content_bound","tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-024","index":24,"required_result":"claimant eligible subject to identity/presence/policy","scenario":"valid local assessment admits exact current claim under complete policy/evidence"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_claim_starts_quarantined_then_exact_local_assessment_admits","tests.test_dm081_sources.SourceRegistryTests.test_paginated_pull_keeps_starting_cursor_until_terminal_page","tests.test_dm081_sources.SourceWireContractTests.test_cyclic_and_disconnected_provenance_is_rejected","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-025","index":25,"required_result":"reject","scenario":"assessment author differs from assessor"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_claim_starts_quarantined_then_exact_local_assessment_admits","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-026","index":26,"required_result":"retain as evidence only","scenario":"remote assessment changes local disposition"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_claim_starts_quarantined_then_exact_local_assessment_admits","tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-027","index":27,"required_result":"historical; cannot admit current head","scenario":"assessment cites old claim head"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_assessment_successor_fork_excludes_locally_admitted_claim","tests.test_dm081_sources.SourceRegistryTests.test_claim_starts_quarantined_then_exact_local_assessment_admits","tests.test_dm081_sources.SourceRegistryTests.test_same_claim_position_from_two_embodiments_quarantines_series","tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-028","index":28,"required_result":"exclude claimant locally; quarantine assessment chain","scenario":"two assessment successors fork"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_claim_binding_and_evidence_are_closed_and_content_bound","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-029","index":29,"required_result":"quarantined/incomplete, never admit","scenario":"policy bytes missing or hash mismatch"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_paginated_pull_keeps_starting_cursor_until_terminal_page","tests.test_dm081_sources.SourceWireContractTests.test_claim_binding_and_evidence_are_closed_and_content_bound","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-030","index":30,"required_result":"reject assessment/decision","scenario":"evidence snapshot omits an input, binds another subject/cursor, or changes after decision"},{"evidence":["tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-031","index":31,"required_result":"reject","scenario":"resolver uses search index, route, name, or semantic similarity as membership"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_source_identity_is_byte_exact_and_selector_bound","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-032","index":32,"required_result":"fail closed","scenario":"`/source` operation omits exact selector"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_source_identity_is_byte_exact_and_selector_bound","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-033","index":33,"required_result":"reject","scenario":"unqualified all-sources network query"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-034","index":34,"required_result":"one recipient, evidence retains relations","scenario":"one claimant has multiple relations"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-035","index":35,"required_result":"excluded or resolved-unroutable per DM-012; never replaced","scenario":"admitted claimant is parked or unroutable"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_paginated_pull_keeps_starting_cursor_until_terminal_page","tests.test_dm081_sources.SourceWireContractTests.test_cyclic_and_disconnected_provenance_is_rejected","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-036","index":36,"required_result":"closed denial, no oracle detail","scenario":"unauthorized status/diff distinguishes source existence"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_reviewed_publication_tombstones_without_deleting_history","tests.test_dm081_sources.SourceWireContractTests.test_cyclic_and_disconnected_provenance_is_rejected","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-037","index":37,"required_result":"intrinsically published, receiver quarantine on pull","scenario":"valid first publication with explicit consent and provenance"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_paginated_pull_keeps_starting_cursor_until_terminal_page","tests.test_dm081_sources.SourceRegistryTests.test_reviewed_publication_tombstones_without_deleting_history","tests.test_dm081_sources.SourceWireContractTests.test_cyclic_and_disconnected_provenance_is_rejected","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-038","index":38,"required_result":"reject","scenario":"publication author differs from publisher"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_same_claim_position_from_two_embodiments_quarantines_series","tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-039","index":39,"required_result":"publication incomplete/quarantined; do not offer","scenario":"publisher claim is missing, retracted, expired, or forked"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_reviewed_publication_tombstones_without_deleting_history","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-040","index":40,"required_result":"reject","scenario":"publication ID or sequence does not bind publisher/source URI"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_reviewed_publication_tombstones_without_deleting_history","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-041","index":41,"required_result":"reject V0 export","scenario":"private, implicit-consent, or null-license publication"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_reviewed_publication_tombstones_without_deleting_history","tests.test_dm081_sources.SourceWireContractTests.test_claim_binding_and_evidence_are_closed_and_content_bound","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-042","index":42,"required_result":"cryptographic publication remains a claim; disclosure/promotion denied","scenario":"publisher says explicit consent but required consent evidence is absent"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_reviewed_publication_tombstones_without_deleting_history","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-043","index":43,"required_result":"refuse","scenario":"stable source URI is dereferenced as a locator"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_reviewed_publication_tombstones_without_deleting_history","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-044","index":44,"required_result":"reject","scenario":"publication update names wrong predecessor"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_publication_successor_fork_is_retained_and_never_offered","tests.test_dm081_sources.SourceRegistryTests.test_reviewed_publication_tombstones_without_deleting_history","tests.test_dm081_sources.SourceRegistryTests.test_same_claim_position_from_two_embodiments_quarantines_series","tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-045","index":45,"required_result":"quarantine series; offer neither","scenario":"two publication successors fork"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_fresh_receiver_lands_tombstone_without_withdrawn_content","tests.test_dm081_sources.SourceRegistryTests.test_reviewed_publication_tombstones_without_deleting_history","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-046","index":46,"required_result":"stop offering/projection; retain events and receipts","scenario":"tombstone current"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_reviewed_publication_tombstones_without_deleting_history","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-047","index":47,"required_result":"refuse deletion","scenario":"tombstone deletes untracked or drifted target"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_republish_after_tombstone_creates_new_quarantine_successor","tests.test_dm081_sources.SourceRegistryTests.test_reviewed_publication_tombstones_without_deleting_history","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-048","index":48,"required_result":"new successor and review; old bytes remain history","scenario":"republish after tombstone"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_paginated_pull_keeps_starting_cursor_until_terminal_page","tests.test_dm081_sources.SourceRegistryTests.test_reviewed_publication_tombstones_without_deleting_history","tests.test_dm081_sources.SourceWireContractTests.test_cyclic_and_disconnected_provenance_is_rejected","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-049","index":49,"required_result":"reject","scenario":"provenance output differs from publication content/URI"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_cyclic_and_disconnected_provenance_is_rejected","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-050","index":50,"required_result":"reject","scenario":"provenance graph is cyclic, disconnected, or has derivation root"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_cyclic_and_disconnected_provenance_is_rejected","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-051","index":51,"required_result":"reject","scenario":"summary drops an original node or relabels authorship"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_cyclic_and_disconnected_provenance_is_rejected","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-052","index":52,"required_result":"reject","scenario":"external author represented as cryptographically verified without registered proof"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_cyclic_and_disconnected_provenance_is_rejected","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-053","index":53,"required_result":"reject","scenario":"model/indexer/receiver becomes author merely by projection"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_fresh_receiver_lands_tombstone_without_withdrawn_content","tests.test_dm081_sources.SourceRegistryTests.test_paginated_pull_keeps_starting_cursor_until_terminal_page","tests.test_dm081_sources.SourceRegistryTests.test_reviewed_publication_tombstones_without_deleting_history","tests.test_dm081_sources.SourceRegistryTests.test_same_claim_position_from_two_embodiments_quarantines_series","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-054","index":54,"required_result":"reject","scenario":"diff mutates receiver or offers tombstoned/private/forked content"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_paginated_pull_keeps_starting_cursor_until_terminal_page","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-055","index":55,"required_result":"reject","scenario":"continuation token reused for another requester/responder/cursor"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_paginated_pull_keeps_starting_cursor_until_terminal_page","tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-056","index":56,"required_result":"reject claim; cursor is observer-relative","scenario":"cursor claims global completeness"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_paginated_pull_keeps_starting_cursor_until_terminal_page","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-057","index":57,"required_result":"fail preview","scenario":"incoming writes, fetches, renders, executes, indexes, or advances cursor"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_paginated_pull_keeps_starting_cursor_until_terminal_page","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-058","index":58,"required_result":"stale; recompute","scenario":"incoming starts at changed local cursor"},{"evidence":["tests.test_dm035_publication.DM035PublicationTests.test_final_render_secret_policy_and_unsafe_target","tests.test_dm081_sources.SourceRegistryTests.test_pull_resumes_after_crash_and_never_promotes","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-059","index":59,"required_result":"reject","scenario":"pull copies HMK/collective-memory/ledger SQLite"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_pull_resumes_after_crash_and_never_promotes","tests.test_dm081_sources.SourceWireContractTests.test_claim_binding_and_evidence_are_closed_and_content_bound","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-060","index":60,"required_result":"reject/quarantine","scenario":"pull trusts a remote index row without canonical event/content evidence"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_item_with_transitive_publication_creates_every_import_receipt","tests.test_dm081_sources.SourceRegistryTests.test_pull_resumes_after_crash_and_never_promotes","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-061","index":61,"required_result":"persist canonical evidence and initial quarantine decision only","scenario":"pull receives valid new content"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_pull_resumes_after_crash_and_never_promotes","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-062","index":62,"required_result":"reject configuration/effect","scenario":"configuration asks pull to auto-promote"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_paginated_pull_keeps_starting_cursor_until_terminal_page","tests.test_dm081_sources.SourceRegistryTests.test_pull_resumes_after_crash_and_never_promotes","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-063","index":63,"required_result":"resume idempotently from durable prefix","scenario":"interrupted pull before cursor commit"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_pull_resumes_after_crash_and_never_promotes","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-064","index":64,"required_result":"no duplicate events, blobs, decisions, or projections","scenario":"repeated pull after completion"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_incomplete_item_is_reported_but_not_landed_or_marked_known","tests.test_dm081_sources.SourceRegistryTests.test_malformed_item_is_rejected_while_complete_prefix_lands","tests.test_dm081_sources.SourceWireContractTests.test_cyclic_and_disconnected_provenance_is_rejected","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-065","index":65,"required_result":"keep complete prefix; report incomplete","scenario":"partial bundle lacks predecessor/provenance"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_pull_resumes_after_crash_and_never_promotes","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-066","index":66,"required_result":"reject","scenario":"transport ACK reported as import receipt"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_publication_pull_quarantines_then_separate_promotion_preserves_authors","tests.test_dm081_sources.SourceWireContractTests.test_claim_binding_and_evidence_are_closed_and_content_bound","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-067","index":67,"required_result":"create attributed derived projection","scenario":"promotion is separate and exact policy/evidence/current-head checks pass"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_publication_pull_quarantines_then_separate_promotion_preserves_authors","tests.test_dm081_sources.SourceWireContractTests.test_claim_binding_and_evidence_are_closed_and_content_bound","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-068","index":68,"required_result":"remain quarantined","scenario":"promotion policy or evidence missing"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_fresh_receiver_lands_tombstone_without_withdrawn_content","tests.test_dm081_sources.SourceRegistryTests.test_publication_successor_fork_is_retained_and_never_offered","tests.test_dm081_sources.SourceRegistryTests.test_reviewed_publication_tombstones_without_deleting_history","tests.test_dm081_sources.SourceRegistryTests.test_same_claim_position_from_two_embodiments_quarantines_series","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-069","index":69,"required_result":"reject","scenario":"tombstoned/forked publication is promoted"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_publication_pull_quarantines_then_separate_promotion_preserves_authors","tests.test_dm081_sources.SourceWireContractTests.test_cyclic_and_disconnected_provenance_is_rejected","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-070","index":70,"required_result":"reject","scenario":"promotion rewrites publisher, author, URI, derivation, digest, or decision history"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_publication_pull_quarantines_then_separate_promotion_preserves_authors","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-071","index":71,"required_result":"reject regardless of policy","scenario":"external knowledge promoted as autobiography/body experience"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_publication_pull_quarantines_then_separate_promotion_preserves_authors","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-072","index":72,"required_result":"reject","scenario":"consensus, repetition, embedding score, species, `/we`, or tribe triggers promotion"},{"evidence":["tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-073","index":73,"required_result":"reject; deactivate projection only","scenario":"local rejection deletes canonical history"},{"evidence":["tests.test_dm035_publication.DM035PublicationTests.test_final_render_secret_policy_and_unsafe_target","tests.test_dm081_sources.SourceRegistryTests.test_reviewed_publication_tombstones_without_deleting_history","tests.test_dm081_sources.SourceWireContractTests.test_cyclic_and_disconnected_provenance_is_rejected","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-074","index":74,"required_result":"accept as adapter evidence, not Daimon authority","scenario":"HMK publication maps immutable URI/provenance/receipt/tombstone"},{"evidence":["tests.test_dm035_publication.DM035PublicationTests.test_final_render_secret_policy_and_unsafe_target","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-075","index":75,"required_result":"fail publication review","scenario":"raw HMK body scanned but final rendered artifact is not"},{"evidence":["tests.test_dm035_publication.DM035PublicationTests.test_final_render_secret_policy_and_unsafe_target","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-076","index":76,"required_result":"reject boundary crossing","scenario":"outbound receipt reused as inbound admission"},{"evidence":["tests.test_dm035_publication.DM035PublicationTests.test_final_render_secret_policy_and_unsafe_target","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-077","index":77,"required_result":"reject","scenario":"collective-memory index/Atlas node treated as canonical source event"},{"evidence":["tests.test_dm081_sources.SourceCASTests.test_content_is_inert_without_network_execution_or_archive_expansion","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-078","index":78,"required_result":"refuse before effect","scenario":"content parser receives SSRF target, ambient credential, active content, or archive traversal"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_initial_import_is_quarantine_and_promotion_is_separate","tests.test_dm081_sources.SourceWireContractTests.test_source_graph_depth_accepts_exact_bound_and_rejects_plus_one","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-079","index":79,"required_result":"process normally when otherwise valid","scenario":"exact count/byte/depth bound"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_initial_import_is_quarantine_and_promotion_is_separate","tests.test_dm081_sources.SourceWireContractTests.test_source_graph_depth_accepts_exact_bound_and_rejects_plus_one","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-080","index":80,"required_result":"reject or incomplete before unsafe work","scenario":"any bound plus one"},{"evidence":["tests.test_dm060_synthetic_birth.BirthContractTests.test_distinct_root_birth_first_embodiment_and_empty_memory_activate","tests.test_dm081_sources.SourceWireContractTests.test_cyclic_and_disconnected_provenance_is_rejected","tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-081","index":81,"required_result":"reject","scenario":"source claim used as `/we`, tribe, species, identity, route, disclosure, or birth authority"},{"evidence":["tests.test_dm060_synthetic_birth.BirthContractTests.test_distinct_root_birth_first_embodiment_and_empty_memory_activate","tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-082","index":82,"required_result":"bind contextual reference; claim still locally assessed","scenario":"birth offer references exact current DM-015 claim event ID"},{"evidence":["tests.test_dm060_synthetic_birth.BirthContractTests.test_distinct_root_birth_first_embodiment_and_empty_memory_activate","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-083","index":83,"required_result":"lineage context-incomplete; identity may awaken","scenario":"birth source reference unavailable"},{"evidence":["tests.test_dm060_synthetic_birth.BirthContractTests.test_distinct_root_birth_first_embodiment_and_empty_memory_activate","tests.test_dm081_sources.SourceRegistryTests.test_same_claim_position_from_two_embodiments_quarantines_series","tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-084","index":84,"required_result":"contextual source provenance excluded/quarantined; identity and birth binding remain valid","scenario":"birth source claim later retracts or forks"}],"schema":"dm.source-section14-registry/v0","section":"14","spec_path":"specs/source-ancestry.md","spec_sha256":"2a30c1cbd2d82c461f105f6dd8bd52cd0d7e3a70751081a3a0b54ad82979ac7b"} \ No newline at end of file +{"row_count":84,"rows":[{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_claim_binding_and_evidence_are_closed_and_content_bound","tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-001","index":1,"required_result":"intrinsically valid, locally quarantined","scenario":"valid sequence-zero self-claim with complete evidence"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_paginated_pull_keeps_starting_cursor_until_terminal_page","tests.test_dm081_sources.SourceWireContractTests.test_cyclic_and_disconnected_provenance_is_rejected","tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-002","index":2,"required_result":"reject `false-self`","scenario":"claim author differs from claimant"},{"evidence":["tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-003","index":3,"required_result":"reject","scenario":"root, transport, Tribe, harness, host, model, or adapter signs in place of accepted operational credential"},{"evidence":["tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-004","index":4,"required_result":"reject policy implementation","scenario":"valid signature is treated as true ancestry"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_source_identity_is_byte_exact_and_selector_bound","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-005","index":5,"required_result":"reject","scenario":"source ID does not recompute from exact core"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_source_identity_is_byte_exact_and_selector_bound","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-006","index":6,"required_result":"distinct source or reject mismatch; never merge","scenario":"source alias or normalized URI is substituted"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_source_identity_is_byte_exact_and_selector_bound","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-007","index":7,"required_result":"reject","scenario":"source core embeds credentials, userinfo, whitespace, control, or locator behavior"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-008","index":8,"required_result":"reject","scenario":"relation unknown, empty, unsorted, or duplicated"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_claim_binding_and_evidence_are_closed_and_content_bound","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-009","index":9,"required_result":"reject","scenario":"assert has null evidence or retract has evidence"},{"evidence":["tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-010","index":10,"required_result":"reject","scenario":"sequence gap or wrong predecessor ID/hash"},{"evidence":["tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-011","index":11,"required_result":"idempotent","scenario":"exact event replay"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_same_claim_position_from_two_embodiments_quarantines_series","tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-012","index":12,"required_result":"quarantine series; no winner","scenario":"two successors occupy one claim position"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_same_claim_position_from_two_embodiments_quarantines_series","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-013","index":13,"required_result":"retain and quarantine descendants","scenario":"late sibling arrives below high-water"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_same_claim_position_from_two_embodiments_quarantines_series","tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-014","index":14,"required_result":"reject/fork remains","scenario":"ordinary successor attempts to heal fork"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-015","index":15,"required_result":"exclude claimant; retain history","scenario":"retraction becomes current"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-016","index":16,"required_result":"reject; retraction covers exact current assertion","scenario":"retraction changes only a subset of predecessor relations"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-017","index":17,"required_result":"historical only; no eligibility","scenario":"expired claim replays after a newer head"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_claim_binding_and_evidence_are_closed_and_content_bound","tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-018","index":18,"required_result":"valid new head, initially quarantined","scenario":"assertion after retraction with fresh predecessor/evidence"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_claim_binding_and_evidence_are_closed_and_content_bound","tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-019","index":19,"required_result":"reject","scenario":"evidence manifest binding hash names another claim"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_claim_binding_and_evidence_are_closed_and_content_bound","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-020","index":20,"required_result":"claim incomplete/quarantined, never admitted","scenario":"evidence is missing but well-formed"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_claim_binding_and_evidence_are_closed_and_content_bound","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-021","index":21,"required_result":"discard bytes; remain incomplete","scenario":"evidence bytes mismatch digest"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_claim_binding_and_evidence_are_closed_and_content_bound","tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-022","index":22,"required_result":"reject policy implementation","scenario":"claimant's own evidence or many matching claims are treated as objective quorum"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_cyclic_and_disconnected_provenance_is_rejected","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-023","index":23,"required_result":"downgrade is forbidden; reject manifest","scenario":"cryptographic-author label lacks exact author/content signature"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_claim_starts_quarantined_then_exact_local_assessment_admits","tests.test_dm081_sources.SourceWireContractTests.test_claim_binding_and_evidence_are_closed_and_content_bound","tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-024","index":24,"required_result":"claimant eligible subject to identity/presence/policy","scenario":"valid local assessment admits exact current claim under complete policy/evidence"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_claim_starts_quarantined_then_exact_local_assessment_admits","tests.test_dm081_sources.SourceRegistryTests.test_paginated_pull_keeps_starting_cursor_until_terminal_page","tests.test_dm081_sources.SourceWireContractTests.test_cyclic_and_disconnected_provenance_is_rejected","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-025","index":25,"required_result":"reject","scenario":"assessment author differs from assessor"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_claim_starts_quarantined_then_exact_local_assessment_admits","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-026","index":26,"required_result":"retain as evidence only","scenario":"remote assessment changes local disposition"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_claim_starts_quarantined_then_exact_local_assessment_admits","tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-027","index":27,"required_result":"historical; cannot admit current head","scenario":"assessment cites old claim head"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_assessment_successor_fork_excludes_locally_admitted_claim","tests.test_dm081_sources.SourceRegistryTests.test_claim_starts_quarantined_then_exact_local_assessment_admits","tests.test_dm081_sources.SourceRegistryTests.test_same_claim_position_from_two_embodiments_quarantines_series","tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-028","index":28,"required_result":"exclude claimant locally; quarantine assessment chain","scenario":"two assessment successors fork"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_claim_binding_and_evidence_are_closed_and_content_bound","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-029","index":29,"required_result":"quarantined/incomplete, never admit","scenario":"policy bytes missing or hash mismatch"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_paginated_pull_keeps_starting_cursor_until_terminal_page","tests.test_dm081_sources.SourceWireContractTests.test_claim_binding_and_evidence_are_closed_and_content_bound","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-030","index":30,"required_result":"reject assessment/decision","scenario":"evidence snapshot omits an input, binds another subject/cursor, or changes after decision"},{"evidence":["tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-031","index":31,"required_result":"reject","scenario":"resolver uses search index, route, name, or semantic similarity as membership"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_source_identity_is_byte_exact_and_selector_bound","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-032","index":32,"required_result":"fail closed","scenario":"`/source` operation omits exact selector"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_source_identity_is_byte_exact_and_selector_bound","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-033","index":33,"required_result":"reject","scenario":"unqualified all-sources network query"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-034","index":34,"required_result":"one recipient, evidence retains relations","scenario":"one claimant has multiple relations"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-035","index":35,"required_result":"excluded or resolved-unroutable per DM-012; never replaced","scenario":"admitted claimant is parked or unroutable"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_paginated_pull_keeps_starting_cursor_until_terminal_page","tests.test_dm081_sources.SourceWireContractTests.test_cyclic_and_disconnected_provenance_is_rejected","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-036","index":36,"required_result":"closed denial, no oracle detail","scenario":"unauthorized status/diff distinguishes source existence"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_reviewed_publication_tombstones_without_deleting_history","tests.test_dm081_sources.SourceWireContractTests.test_cyclic_and_disconnected_provenance_is_rejected","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-037","index":37,"required_result":"intrinsically published, receiver quarantine on pull","scenario":"valid first publication with explicit consent and provenance"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_paginated_pull_keeps_starting_cursor_until_terminal_page","tests.test_dm081_sources.SourceRegistryTests.test_reviewed_publication_tombstones_without_deleting_history","tests.test_dm081_sources.SourceWireContractTests.test_cyclic_and_disconnected_provenance_is_rejected","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-038","index":38,"required_result":"reject","scenario":"publication author differs from publisher"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_same_claim_position_from_two_embodiments_quarantines_series","tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-039","index":39,"required_result":"publication incomplete/quarantined; do not offer","scenario":"publisher claim is missing, retracted, expired, or forked"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_reviewed_publication_tombstones_without_deleting_history","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-040","index":40,"required_result":"reject","scenario":"publication ID or sequence does not bind publisher/source URI"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_reviewed_publication_tombstones_without_deleting_history","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-041","index":41,"required_result":"reject V0 export","scenario":"private, implicit-consent, or null-license publication"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_reviewed_publication_tombstones_without_deleting_history","tests.test_dm081_sources.SourceWireContractTests.test_claim_binding_and_evidence_are_closed_and_content_bound","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-042","index":42,"required_result":"cryptographic publication remains a claim; disclosure/promotion denied","scenario":"publisher says explicit consent but required consent evidence is absent"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_reviewed_publication_tombstones_without_deleting_history","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-043","index":43,"required_result":"refuse","scenario":"stable source URI is dereferenced as a locator"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_reviewed_publication_tombstones_without_deleting_history","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-044","index":44,"required_result":"reject","scenario":"publication update names wrong predecessor"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_publication_successor_fork_is_retained_and_never_offered","tests.test_dm081_sources.SourceRegistryTests.test_reviewed_publication_tombstones_without_deleting_history","tests.test_dm081_sources.SourceRegistryTests.test_same_claim_position_from_two_embodiments_quarantines_series","tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-045","index":45,"required_result":"quarantine series; offer neither","scenario":"two publication successors fork"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_fresh_receiver_lands_tombstone_without_withdrawn_content","tests.test_dm081_sources.SourceRegistryTests.test_reviewed_publication_tombstones_without_deleting_history","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-046","index":46,"required_result":"stop offering/projection; retain events and receipts","scenario":"tombstone current"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_reviewed_publication_tombstones_without_deleting_history","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-047","index":47,"required_result":"refuse deletion","scenario":"tombstone deletes untracked or drifted target"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_republish_after_tombstone_creates_new_quarantine_successor","tests.test_dm081_sources.SourceRegistryTests.test_reviewed_publication_tombstones_without_deleting_history","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-048","index":48,"required_result":"new successor and review; old bytes remain history","scenario":"republish after tombstone"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_paginated_pull_keeps_starting_cursor_until_terminal_page","tests.test_dm081_sources.SourceRegistryTests.test_reviewed_publication_tombstones_without_deleting_history","tests.test_dm081_sources.SourceWireContractTests.test_cyclic_and_disconnected_provenance_is_rejected","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-049","index":49,"required_result":"reject","scenario":"provenance output differs from publication content/URI"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_cyclic_and_disconnected_provenance_is_rejected","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-050","index":50,"required_result":"reject","scenario":"provenance graph is cyclic, disconnected, or has derivation root"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_cyclic_and_disconnected_provenance_is_rejected","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-051","index":51,"required_result":"reject","scenario":"summary drops an original node or relabels authorship"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_cyclic_and_disconnected_provenance_is_rejected","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-052","index":52,"required_result":"reject","scenario":"external author represented as cryptographically verified without registered proof"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_cyclic_and_disconnected_provenance_is_rejected","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-053","index":53,"required_result":"reject","scenario":"model/indexer/receiver becomes author merely by projection"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_fresh_receiver_lands_tombstone_without_withdrawn_content","tests.test_dm081_sources.SourceRegistryTests.test_paginated_pull_keeps_starting_cursor_until_terminal_page","tests.test_dm081_sources.SourceRegistryTests.test_reviewed_publication_tombstones_without_deleting_history","tests.test_dm081_sources.SourceRegistryTests.test_same_claim_position_from_two_embodiments_quarantines_series","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-054","index":54,"required_result":"reject","scenario":"diff mutates receiver or offers tombstoned/private/forked content"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_paginated_pull_keeps_starting_cursor_until_terminal_page","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-055","index":55,"required_result":"reject","scenario":"continuation token reused for another requester/responder/cursor"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_paginated_pull_keeps_starting_cursor_until_terminal_page","tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-056","index":56,"required_result":"reject claim; cursor is observer-relative","scenario":"cursor claims global completeness"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_paginated_pull_keeps_starting_cursor_until_terminal_page","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-057","index":57,"required_result":"fail preview","scenario":"incoming writes, fetches, renders, executes, indexes, or advances cursor"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_paginated_pull_keeps_starting_cursor_until_terminal_page","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-058","index":58,"required_result":"stale; recompute","scenario":"incoming starts at changed local cursor"},{"evidence":["tests.test_dm035_publication.DM035PublicationTests.test_final_render_secret_policy_and_unsafe_target","tests.test_dm081_sources.SourceRegistryTests.test_pull_resumes_after_crash_and_never_promotes","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-059","index":59,"required_result":"reject","scenario":"pull copies HMK/collective-memory/ledger SQLite"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_pull_resumes_after_crash_and_never_promotes","tests.test_dm081_sources.SourceWireContractTests.test_claim_binding_and_evidence_are_closed_and_content_bound","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-060","index":60,"required_result":"reject/quarantine","scenario":"pull trusts a remote index row without canonical event/content evidence"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_item_with_transitive_publication_creates_every_import_receipt","tests.test_dm081_sources.SourceRegistryTests.test_pull_resumes_after_crash_and_never_promotes","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-061","index":61,"required_result":"persist canonical evidence and initial quarantine decision only","scenario":"pull receives valid new content"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_pull_resumes_after_crash_and_never_promotes","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-062","index":62,"required_result":"reject configuration/effect","scenario":"configuration asks pull to auto-promote"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_paginated_pull_keeps_starting_cursor_until_terminal_page","tests.test_dm081_sources.SourceRegistryTests.test_pull_resumes_after_crash_and_never_promotes","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-063","index":63,"required_result":"resume idempotently from durable prefix","scenario":"interrupted pull before cursor commit"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_pull_resumes_after_crash_and_never_promotes","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-064","index":64,"required_result":"no duplicate events, blobs, decisions, or projections","scenario":"repeated pull after completion"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_incomplete_item_is_reported_but_not_landed_or_marked_known","tests.test_dm081_sources.SourceRegistryTests.test_malformed_item_is_rejected_while_complete_prefix_lands","tests.test_dm081_sources.SourceWireContractTests.test_cyclic_and_disconnected_provenance_is_rejected","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-065","index":65,"required_result":"keep complete prefix; report incomplete","scenario":"partial bundle lacks predecessor/provenance"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_pull_resumes_after_crash_and_never_promotes","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-066","index":66,"required_result":"reject","scenario":"transport ACK reported as import receipt"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_publication_pull_quarantines_then_separate_promotion_preserves_authors","tests.test_dm081_sources.SourceWireContractTests.test_claim_binding_and_evidence_are_closed_and_content_bound","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-067","index":67,"required_result":"create attributed derived projection","scenario":"promotion is separate and exact policy/evidence/current-head checks pass"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_publication_pull_quarantines_then_separate_promotion_preserves_authors","tests.test_dm081_sources.SourceWireContractTests.test_claim_binding_and_evidence_are_closed_and_content_bound","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-068","index":68,"required_result":"remain quarantined","scenario":"promotion policy or evidence missing"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_fresh_receiver_lands_tombstone_without_withdrawn_content","tests.test_dm081_sources.SourceRegistryTests.test_publication_successor_fork_is_retained_and_never_offered","tests.test_dm081_sources.SourceRegistryTests.test_reviewed_publication_tombstones_without_deleting_history","tests.test_dm081_sources.SourceRegistryTests.test_same_claim_position_from_two_embodiments_quarantines_series","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-069","index":69,"required_result":"reject","scenario":"tombstoned/forked publication is promoted"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_publication_pull_quarantines_then_separate_promotion_preserves_authors","tests.test_dm081_sources.SourceWireContractTests.test_cyclic_and_disconnected_provenance_is_rejected","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-070","index":70,"required_result":"reject","scenario":"promotion rewrites publisher, author, URI, derivation, digest, or decision history"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_publication_pull_quarantines_then_separate_promotion_preserves_authors","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-071","index":71,"required_result":"reject regardless of policy","scenario":"external knowledge promoted as autobiography/body experience"},{"evidence":["tests.test_dm081_sources.SourceRegistryTests.test_publication_pull_quarantines_then_separate_promotion_preserves_authors","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-072","index":72,"required_result":"reject","scenario":"consensus, repetition, embedding score, species, `/we`, or tribe triggers promotion"},{"evidence":["tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-073","index":73,"required_result":"reject; deactivate projection only","scenario":"local rejection deletes canonical history"},{"evidence":["tests.test_dm035_publication.DM035PublicationTests.test_final_render_secret_policy_and_unsafe_target","tests.test_dm081_sources.SourceRegistryTests.test_reviewed_publication_tombstones_without_deleting_history","tests.test_dm081_sources.SourceWireContractTests.test_cyclic_and_disconnected_provenance_is_rejected","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-074","index":74,"required_result":"accept as adapter evidence, not Daimon authority","scenario":"HMK publication maps immutable URI/provenance/receipt/tombstone"},{"evidence":["tests.test_dm035_publication.DM035PublicationTests.test_final_render_secret_policy_and_unsafe_target","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-075","index":75,"required_result":"fail publication review","scenario":"raw HMK body scanned but final rendered artifact is not"},{"evidence":["tests.test_dm035_publication.DM035PublicationTests.test_final_render_secret_policy_and_unsafe_target","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-076","index":76,"required_result":"reject boundary crossing","scenario":"outbound receipt reused as inbound admission"},{"evidence":["tests.test_dm035_publication.DM035PublicationTests.test_final_render_secret_policy_and_unsafe_target","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-077","index":77,"required_result":"reject","scenario":"collective-memory index/Atlas node treated as canonical source event"},{"evidence":["tests.test_dm081_sources.SourceCASTests.test_content_is_inert_without_network_execution_or_archive_expansion","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-078","index":78,"required_result":"refuse before effect","scenario":"content parser receives SSRF target, ambient credential, active content, or archive traversal"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_initial_import_is_quarantine_and_promotion_is_separate","tests.test_dm081_sources.SourceWireContractTests.test_source_graph_depth_accepts_exact_bound_and_rejects_plus_one","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-079","index":79,"required_result":"process normally when otherwise valid","scenario":"exact count/byte/depth bound"},{"evidence":["tests.test_dm081_sources.SourceWireContractTests.test_initial_import_is_quarantine_and_promotion_is_separate","tests.test_dm081_sources.SourceWireContractTests.test_source_graph_depth_accepts_exact_bound_and_rejects_plus_one","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-080","index":80,"required_result":"reject or incomplete before unsafe work","scenario":"any bound plus one"},{"evidence":["tests.test_dm060_synthetic_birth.BirthContractTests.test_distinct_root_birth_first_embodiment_and_empty_memory_activate","tests.test_dm081_sources.SourceWireContractTests.test_cyclic_and_disconnected_provenance_is_rejected","tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-081","index":81,"required_result":"reject","scenario":"source claim used as `/we`, tribe, species, identity, route, disclosure, or birth authority"},{"evidence":["tests.test_dm060_synthetic_birth.BirthContractTests.test_distinct_root_birth_first_embodiment_and_empty_memory_activate","tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-082","index":82,"required_result":"bind contextual reference; claim still locally assessed","scenario":"birth offer references exact current DM-015 claim event ID"},{"evidence":["tests.test_dm060_synthetic_birth.BirthContractTests.test_distinct_root_birth_first_embodiment_and_empty_memory_activate","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-083","index":83,"required_result":"lineage context-incomplete; identity may awaken","scenario":"birth source reference unavailable"},{"evidence":["tests.test_dm060_synthetic_birth.BirthContractTests.test_distinct_root_birth_first_embodiment_and_empty_memory_activate","tests.test_dm081_sources.SourceRegistryTests.test_same_claim_position_from_two_embodiments_quarantines_series","tests.test_dm081_sources.SourceWireContractTests.test_false_self_retraction_and_predecessor_rules_fail_closed","tests.test_dm081_sources.SyntheticSourceJourneyTests.test_two_being_report_is_closed_reproducible_and_secret_free"],"id":"source-s14-084","index":84,"required_result":"contextual source provenance excluded/quarantined; identity and birth binding remain valid","scenario":"birth source claim later retracts or forks"}],"schema":"dm.source-section14-registry/v0","section":"14","spec_path":"specs/source-ancestry.md","spec_sha256":"0734f48d81611e4e5fce53e03087fb14ecce796aea3c7b7fc3100caba7290f25"} \ No newline at end of file diff --git a/docs/dm026-conformance.md b/docs/dm026-conformance.md index 58a89d3..26c880d 100644 --- a/docs/dm026-conformance.md +++ b/docs/dm026-conformance.md @@ -8,7 +8,7 @@ Cluster lifecycle, external-effect, or rebirth certification. ## Closed evidence registry `conformance/registry-v1.json` is the canonical -`dm.conformance.registry/v1`. Its 97 scenario identifiers are closed in both +`dm.conformance.registry/v1`. Its 101 scenario identifiers are closed in both directions: a missing scenario, an unknown scenario, duplicate identifier, unknown field, unregistered evidence reference, or non-required release scenario fails validation. Each scenario names its owner cards, diff --git a/docs/dm036-collective-memory.md b/docs/dm036-collective-memory.md new file mode 100644 index 0000000..4c04750 --- /dev/null +++ b/docs/dm036-collective-memory.md @@ -0,0 +1,180 @@ +# DM-036 collective-memory exchange + +Status: normative for `dm.collective-* /v1`. + +“Matrix” means the `daimon-matrix` component. Matrix.org is not used. The +collective-memory dependency is the exchange boundary at commit +`3e3b39416917f8e3c2bc5ca69362b20296205938`, whose closed schema SHA-256 is +`2aad43d1b309ee95108c855fc8dc682a854e5fdf3a1e799ecfca96d3ebf7c5d9`. +Any other commit, schema, producer, release, policy, scope or target fails +closed. + +## Two directions, no shared authority + +DM-036 is two adapters, not one bidirectional credential: + +| Boundary | Adapter | Owns | Cannot do | +| --- | --- | --- | --- | +| inbound | `dm:adapter:v0:Sh-2fDC4rpFOZz_ddjWqLptoX2SgUUzJPKhe6XQOtj8` | read-only transport identity, immutable source log, quarantine receipts | publish, review, consent, promote, write `/me` | +| outbound | `dm:adapter:v0:Jsug9D2N641xJwE5Q_oLaHDy0wxT5knRJfxzV2ZEOXc` | reviewed request queue, exact provider receipts, effect reconciliation | read exports, trust sources, write `/me`, mint identity | + +Their credentials, transports, capabilities, SQLite files, lock files, +idempotency keys, events and receipts are distinct. Construction rejects the +same path or inode for both stores. Neither receives a host path, URL, command, +SQL statement, database handle, prompt, key or credential through its public +contract. Both DM-018 manifests deny Matrix, identity, presence, membership and +`/me` signing authority. + +Matrix remains authoritative for ledger history, `/me`, classification, +consent, human review and accepted adapter receipts. collective-memory remains +authoritative for its corpus, export generations, publication transaction, +index and Atlas. Each side retains a content-bound receipt for the other; a +receipt never transfers authority. + +## Inbound immutable source intake + +The injected source transport implements only `manifest`, `page` and `object`. +Credentials and endpoints stay inside that transport. Matrix validates before +any local effect: + +1. the exact producer instance/release, policy and public scope; +2. closed canonical manifest identity and state digest; +3. sorted unique artifact/logical IDs, authors and source references; +4. license, explicit scope, classification, media type, predecessors and + tombstones; +5. bounded page/cursor sequence and byte/count totals; and +6. every inert UTF-8 object's declared SHA-256 and length. + +Preview performs remote reads and validation but writes no file, cursor, +receipt, error, ledger event or projection. Apply re-fetches the exact immutable +generation. It prepares all descriptors and content in an owner-only inbound +source-log transaction while the prior generation remains active. A +deterministic `source.imported` event then records one receiver-authored +`quarantined:initial-pull` decision and explicitly commits to +`personal_memory_assertions = 0`. Only after that event exists does the source +log advance its active generation. + +Per-artifact provenance and bytes remain in the append-only source log; the +small canonical ledger event binds its source-log hash, generation, manifest, +counts and decision. This keeps a 4096-artifact import below the Weave event +bound without treating a remote database or index row as canonical evidence. +Tombstones advance only the imported logical head. Historical manifests, +objects, events and decisions remain available. + +### Offline catch-up + +The pinned upstream adds `ExportBoundary.manifest(generation_id)`. Matrix reads +the current manifest, follows `predecessor_generation` backwards to its locally +accepted high-water, rejects cycles/gaps/forks, validates the entire chain and +applies it oldest-first. It never skips an unobserved predecessor or guesses a +winner. A normal single-generation preview still rejects a gap. + +### Inbound recovery + +| Crash/failure | Observable result | +| --- | --- | +| fetch, timeout, invalid page/object or policy mismatch | prior generation remains active; apply records a redacted stable error code | +| before source-log prepare commits | no new source state or ledger event | +| after prepare, before ledger event | recovery authors the deterministic event once | +| after ledger event, before active switch | recovery finds the same event and activates once | +| invalid successor, implicit removal or fork | generation remains inactive and records a closed error | +| receipt/event/source-log drift | effect-truth discrepancy; no cached success | + +`rebuild()` performs no remote read. It validates every stored manifest, +descriptor, inert content byte, predecessor and chained source-log hash, then +uses the authoritative Matrix `source.imported` event prefix to reconstruct +only the derived active/superseded markers and missing crash-window receipt +bindings. A fork, disconnected generation, accepted-after-missing event, +content mismatch or contradictory non-null receipt fails closed. + +## Outbound reviewed publication + +Outbound accepts only a caller-rendered derived Markdown title/body plus exact +current Matrix source event IDs. `collective_checkpoint` binds those IDs and +hashes and the exact being/manifest. Matrix recomputes it before preview, +submit and execute; superseded or missing source events make the request stale. + +Matrix locally reproduces the upstream final Markdown bytes, including closed +frontmatter, and scans title, metadata and body for private-key, bearer, +provider-token, credential-assignment and credential-URL patterns before any +provider plan. The upstream preview must return byte-identical canonical bytes, +hash and length. + +Consent and review are separate Ed25519 evidence envelopes over the exact +preview hash, final content hash, subject, requester, action, logical target, +classification, policy and source checkpoint. Consent must be signed by the +subject. Review must use a current, non-revoked `human:*` principal distinct +from the subject and requester. Inbound trust, a model, Librarian or publisher +receipt cannot satisfy either role. + +The outbound journal stores the full bounded request privately. The canonical +`collective.publication.requested` event stores a content-bound summary rather +than raw body bytes and depends on all Matrix source events and the exact prior +acceptance. Under its own process lock, the adapter asks upstream for a +deterministic plan, applies the supported transaction and validates the complete +content-addressed receipt. It then requires fresh upstream +`reconcile(effect=verified)` before authoring +`collective.publication.receipted`. + +Initial publish, successor and reviewed tombstone are monotonic. A successor +names the current upstream receipt and supersedes the corresponding Matrix +acceptance. Identical retries require fresh effect truth and return the same +event/receipt. Changed bytes under an idempotency key, untracked targets, stale +predecessors and concurrent target writers fail closed. + +### Outbound recovery + +| Failure window | Required retry result | +| --- | --- | +| before request journal prepare | no queue event or external effect | +| after prepare, before request event | recovery authors the deterministic request event | +| provider rejects plan/final bytes | closed rejection; no Matrix acceptance | +| response lost after provider commit | exact apply replay returns the one receipt | +| after provider receipt, before Matrix acceptance | fresh reconcile then one acceptance event | +| after acceptance, before journal completion | journal binds the existing event exactly once | +| target/index/Atlas drift on replay | effect-truth discrepancy; cached success refused | + +## Contracts, vectors and real-I/O gate + +Matrix contracts are in +`schemas/collective-memory/v1/contracts.schema.json`; upstream is pinned by +commit and schema hash rather than copied as executable source. Deterministic +vectors are in `vectors/collective-memory/v1/` and regenerate with: + +```bash +python tools/generate_dm036_vectors.py +python tools/generate_dm036_vectors.py --check +``` + +`tests/test_dm036_collective_memory.py` uses the real pinned upstream +`ExportBoundary` and `PublicationBoundary` in isolated roots. It covers initial +import/retry, 257-item pagination, multi-generation offline catch-up, +successor/tombstone, corrupt/mixed/unavailable refresh, both inbound crash +windows, source-log rebuild, exact review, consent/review revocation and expiry, +secrets, response loss, source/target drift, concurrency and both outbound +acceptance windows. Its real projection lane performs: + +```text +export → Matrix quarantine → reviewed publish → real FTS index + Atlas + → reviewed tombstone → real reindex +``` + +That lane checks both SQLite databases with `PRAGMA integrity_check`, verifies +the published and unrelated documents in index/Atlas, preserves unrelated +corpus content, and proves there is no shared DB, WAL or SHM file. + +## Operation and rollback + +DM-036 has no live deployment authorization. Configure only temporary isolated +roots until a later consented canary card permits a corpus. Source and publisher +database parents must be separate owner-only directories. Start by calling +inbound `recover()` and outbound `recover()`, then reconcile every cached active +receipt before serving it. + +Before the first accepted effect, rollback may disable the adapters and remove +unused local stores. After an import or publication exists, disable the relevant +capability independently and preserve its ledger/source log, high-water and +receipts. Withdraw an outbound artifact only with a reviewed tombstone. Never +delete external data by inference, copy SQLite/WAL files, lower a high-water, +turn collective text into autobiographical memory or use inbound revocation to +activate outbound authority. diff --git a/docs/verification/dm026-invariants.json b/docs/verification/dm026-invariants.json index 286e2fb..02d7224 100644 --- a/docs/verification/dm026-invariants.json +++ b/docs/verification/dm026-invariants.json @@ -4,8 +4,8 @@ "deployment": "not-applicable", "registry_schema": "dm.conformance.registry/v1", "report_schema": "dm.conformance.report/v1", - "required_scenario_count": 97, - "registry_sha256": "ff903ff98b08c273ddea98e6a44bf7ca55834189c8c67c850d83ac02ad63ac20", + "required_scenario_count": 101, + "registry_sha256": "12f01cb1704e9943b7f2069ba41fa3f7f9b153c8cf5a22f506e404ac918b963f", "invariants": { "closed_bidirectional_registry": true, "canonical_registry_digest_pinned": true, @@ -33,7 +33,8 @@ "relationship_three_being_signed_journey": true, "relationship_recipient_intake_and_signed_receipt": true, "relationship_revocation_refuses_stale_carriers": true, - "relationship_scenario_mapping": true + "relationship_scenario_mapping": true, + "collective_memory_bidirectional_exchange": true }, "evidence": [ "conformance/registry-v1.json", @@ -44,6 +45,8 @@ "docs/verification/dm079-invariants.json", "docs/verification/dm033-invariants.json", "docs/verification/dm042-invariants.json", + "docs/verification/dm036-invariants.json", + "docs/dm036-collective-memory.md", "docs/verification/dm061-invariants.json", "docs/verification/dm070-invariants.json", "docs/verification/dm081-invariants.json", diff --git a/docs/verification/dm036-dm083-integration.md b/docs/verification/dm036-dm083-integration.md new file mode 100644 index 0000000..5211d4b --- /dev/null +++ b/docs/verification/dm036-dm083-integration.md @@ -0,0 +1,64 @@ +# DM-036 plus DM-083 integration receipt + +Date: 2026-08-11. Candidate only; not deployed or independently reviewed. + +## Exact boundary + +- Qualified semantic merge: `56f79a1a9128284ef53c4289343162ba84020ad7`. +- DM-083 base: `e45ff577e0594065eb26440caa80cf5377eadb86`. +- DM-036 source branch: `2b8e956f95074c60c537148c10b5bd9533cee509`. +- Collective-memory contract: `3e3b39416917f8e3c2bc5ca69362b20296205938`. +- Hermes Memory Kit contract: `f10fd5c3089c0962920314c97e14bc024feffa7a`. +- Conformance registry: 101 required scenarios, canonical digest + `12f01cb1704e9943b7f2069ba41fa3f7f9b153c8cf5a22f506e404ac918b963f`. + +The merge retains the DM-083 authority epochs, source ancestry, relationships, +Hermes packaging and two-host dogfood contracts. It adds distinct inbound and +outbound collective-memory adapters, separate journals and credentials, +quarantine-only import, exact consent plus independent review for publication, +monotonic successors and tombstones, and crash recovery around both Matrix and +external effects. Upstream provenance was advanced from the historical DM-036 +pin to the hardened public contract above. No upstream database, live corpus, +Matrix runtime or service configuration was changed. + +## Clean automated gate + +All checks used fresh Python 3.13 environments and the exact contract pins. + +```text +DM-036 contract, crash and real-I/O gate: 23 passed +complete Matrix suite: 566 passed, 19 skipped +workflow-equivalent ruff and mypy gates: clean +all checked generators and compileall: clean +reproducible wheel: 18f6524733da7a1592f0e8a762c520ddaaee0117f571ec1af71081ede89c8e32 +reproducible sdist: 58654f643fdbac1ec74d3d0c16a2bbcfb981d4eaa848c4730c62ec0f9f862111 +distribution allowlist and secret scans: clean +installed DM-026 conformance, two byte-identical runs: release_ready=true +conformance transcript: 1e4db1a2609ea995f5512ab14f2206ee9ab56b835e984f909c6652d752b16e81 +conformance report file: cc95be312cd8617fcba98e060c18d9fcc95b72274adf5d3c0aee2da236f53b75 +``` + +The real-I/O scenario exports a synthetic attributed generation, lands it in +Matrix quarantine, publishes separately reviewed derived bytes, verifies FTS +and Atlas projections, then applies a reviewed tombstone. It also checks both +SQLite databases for integrity and proves unrelated corpus content remains +byte-exact. Fault scenarios cover preparation, ledger, external-effect and +acceptance boundaries, response loss, replay, pagination, offline catch-up, +forks, symlinks, drift, expired or revoked consent and self-review. + +## Remote scratch confirmation + +The exact candidate and collective-memory commits were cloned into one unique +temporary root on the authorized `daimonmatrix` host. Vector regeneration and +all 23 DM-036 tests passed there under Python 3.13.5. The bounded cleanup trap +removed the complete scratch root. `clusterd` remained active and +`daimon-matrixd` remained inactive before and after; no `/opt` release or live +state was replaced. + +## Remaining gate + +Publish this stacked candidate for CI and independent review. Do not deploy or +merge it merely from this receipt. A real publication still requires current +source intent, explicit subject consent and an independent human review over +the exact final bytes; this synthetic receipt supplies none of those +authorities. diff --git a/docs/verification/dm036-invariants.json b/docs/verification/dm036-invariants.json new file mode 100644 index 0000000..431eb1a --- /dev/null +++ b/docs/verification/dm036-invariants.json @@ -0,0 +1,34 @@ +{ + "schema": "dm-036-invariant-report/v0", + "scope": "synthetic-isolated-collective-memory-exchange", + "deployment": "not-applicable", + "upstream_contract_commit": "3e3b39416917f8e3c2bc5ca69362b20296205938", + "matrix_contract_id": "https://daimon.network/schemas/collective-memory/v1/contracts.schema.json", + "required_scenario_count": 4, + "invariants": { + "inbound_outbound_adapters_are_distinct": true, + "credentials_stores_queues_receipts_and_authority_are_separate": true, + "inbound_generations_are_complete_continuous_and_content_verified": true, + "all_imported_collective_content_enters_source_quarantine": true, + "collective_content_never_asserts_personal_memory": true, + "offline_catch_up_and_ledger_source_log_rebuild_are_deterministic": true, + "outbound_bytes_require_exact_subject_consent_and_independent_review": true, + "final_render_is_secret_scanned_before_effect": true, + "publication_replay_rechecks_external_effect_truth": true, + "successors_and_tombstones_are_monotonic": true, + "crash_recovery_exposes_only_all_old_or_all_new": true, + "real_isolated_fts_and_atlas_projection_verified": true, + "sqlite_integrity_and_unrelated_corpus_preservation_verified": true, + "no_live_corpus_or_matrix_org_dependency": true + }, + "evidence": [ + "conformance/registry-v1.json", + "docs/dm036-collective-memory.md", + "docs/verification/dm036-dm083-integration.md", + "provenance/collective-memory-exchange-v1.json", + "schemas/collective-memory/v1/contracts.schema.json", + "src/daimon_matrix/collective_memory.py", + "tests/test_dm036_collective_memory.py", + "vectors/collective-memory/v1/index.json" + ] +} diff --git a/provenance/collective-memory-exchange-v1.json b/provenance/collective-memory-exchange-v1.json new file mode 100644 index 0000000..d90fafe --- /dev/null +++ b/provenance/collective-memory-exchange-v1.json @@ -0,0 +1,50 @@ +{ + "schema": "dm.external-contract-provenance/v1", + "name": "collective-memory exchange v1", + "upstream": { + "repository": "https://github.com/Mar-IA-no/collective-memory", + "visibility": "public", + "commit": "3e3b39416917f8e3c2bc5ca69362b20296205938", + "tree": "ef9bc23c6f776ac65df5f05377e40e2cd4c60703", + "commit_date": "2026-08-10T20:02:08-03:00", + "license_expression": "MIT" + }, + "policy": { + "source_imported": false, + "source_vendored": false, + "runtime_package_dependency_added": false, + "live_state_imported": false, + "reuse_mode": "exact_version_external_contract", + "public_ci_cross_repository_credential": false + }, + "items": [ + { + "path": "mapa/exchange.py", + "git_blob_sha1": "9baa26aca2caee9342e34c633d7888576c3b1e86", + "sha256": "78e2c619076d444b311588507b76eae3154e23d9f33f9d8112b6fe59c1397df4", + "size": 114201, + "use": "real isolated integration through injected source and publication transports; not imported" + }, + { + "path": "schemas/exchange/v1/contracts.schema.json", + "git_blob_sha1": "6c032b728d3bd458b133119f48f790eea4ef4583", + "sha256": "2aad43d1b309ee95108c855fc8dc682a854e5fdf3a1e799ecfca96d3ebf7c5d9", + "size": 17564, + "use": "exact upstream contract identity and comparison; Matrix schema is independently generated" + }, + { + "path": "vectors/exchange/v1/index.json", + "git_blob_sha1": "c9be99ed06e4f5ea56ab7b50c3dacd422707dad4", + "sha256": "2aa1eaee32d02ffdba053d72ef42e3ad031bea7c7079c77e31b3774e44b275cf", + "size": 1571, + "use": "interop comparison; Matrix vectors are independently generated" + }, + { + "path": "LICENSE", + "git_blob_sha1": "9cf106272ac3b56b0c4c80218e8fc10a664ca5f4", + "sha256": "508a77d2e7b51d98adeed32648ad124b7b30241a8e70b2e72c99f92d8e5874d1", + "size": 1036, + "use": "upstream license evidence" + } + ] +} diff --git a/provenance/hermes-agent-0.19.0.json b/provenance/hermes-agent-0.19.0.json index 8c8d24a..4ad130c 100644 --- a/provenance/hermes-agent-0.19.0.json +++ b/provenance/hermes-agent-0.19.0.json @@ -1 +1 @@ -{"audited_at":"2026-08-05","commit":"0db1912911fafa384aa5ee0145929658a9d1dd33","contract_digests":{"agent/agent_init.py":"6b4aa7a877d25e7065af35fd5a5e99dc0b85ef0ede349d95c5bd74818a70b89f","agent/memory_manager.py":"35e4e313f08e7529d7407f2d36b1639681acbc347eedfe9f2e38c2610973761c","agent/memory_provider.py":"7a86b453edbe3dae6ea02f3af406fac7e39fe7ee7b61bfc84b8c8e4b4a0ce8cd","agent/plugin_llm.py":"4158b0ed2be2140eb99f09e1a488daed04da496052fe415cb84e412e4b71fb30","agent/system_prompt.py":"261481c471ddee92ced3fe381d63acbbe9136bedb6420f613983225007e2bb9a","agent/turn_context.py":"726ebf615b90237cca98d8f5d2d4e04f4507690d66917eb88f5d478c0b1ecaa2","agent/turn_finalizer.py":"a6c91daefaa805ef71604ddf9e9e08399825c1448bff2982bf72099da35957ac","hermes_cli/_parser.py":"82db5bb23c4619bf11536f44b56ea72c2b87cafcd062660af3593d2a4e08db0b","hermes_cli/config.py":"038040b0c0bbdd5f740b39c64d2e9ea1d7c78ae3d582cb337dbe8c145b4c8b03","hermes_cli/main.py":"55333e3fb37bec97b12c404760968716a3e81f3ae373896fc628df8dee3fb416","hermes_cli/plugins.py":"0f6c28614bebb7444392625a63c2b3186039f04238fe6ca79ad62d4849b0551c","model_tools.py":"db74ee29c8d335d80f3c18cc31f8c441956af956b2ed08e5113ced249fad32b4","plugins/memory/__init__.py":"f6bc37128d23f931ea1db52fc60cf25cca448f41105ed37d5b46f3843ab71b3a","plugins/memory/config_schema.py":"b6f58adffb2fd01a9605f10b891d905b8dfa72a509d1577499b82ad66ff7a937","pyproject.toml":"35462080afc8177258babd430dcdc2ac654fdf69332cc4370fec555295f7eaba"},"copy_policy":"No Hermes source or private profile bytes are distributed.","git_archive_sha256":"860a664f622e1099a095cb6cf06b04bcfe78b2fd3affc7192da9cf7ccefcdd63","license":"MIT","matrix_payload":{"contract_schema_sha256":"e23b47040d45c64676b2fc793e375f9be9d7d95078f0b5c384bb5a33383f499f","current_memory_schema_sha256":"3422f822f7b3e05c4b2f422a19a96283993938f11c798cb1982cb5d3cdc38169","package_tree_sha256":"9b76294d4eed59111999adc9b145f0ce57be5c2f112313eb5ae5193295cc6467","plugin_manifest_sha256":"6e2bcb16b394f4653727334653e5a4007149e5a3bc72c9958f9dc7e814dd8b92","plugin_sha256":"c6ef73ae9adedf45a5bd628ca745c1ff958a961f4b8ae0125b134ecddf6d88f5","provider_name":"daimon-matrix","skill_sha256":"6e0e021c6b0a2423dc95930c6901387738c15600deee2bb8f90a20110c66a275","soul_sha256":"0fc541f40cedc23056c07011a9eb99138028968930074c0a0a88db37eb81f25a","tools":["matrix_scope","matrix_propose_observation"]},"python_interval":">=3.11,<3.14","repository":"https://github.com/nicoechaniz/hermes-agent","schema":"dm.hermes-body.provenance/v1","supported_surfaces":["external-memory-provider","profile-plugin-discovery","memory-manager","current-user-api-content-sidecar","session-lifecycle-hooks","static-system-prompt-block"],"tree":"ac7dec02ca029e895963402788bd1cdc3afb36f8","version":"0.19.0"} +{"audited_at":"2026-08-05","commit":"0db1912911fafa384aa5ee0145929658a9d1dd33","contract_digests":{"agent/agent_init.py":"6b4aa7a877d25e7065af35fd5a5e99dc0b85ef0ede349d95c5bd74818a70b89f","agent/memory_manager.py":"35e4e313f08e7529d7407f2d36b1639681acbc347eedfe9f2e38c2610973761c","agent/memory_provider.py":"7a86b453edbe3dae6ea02f3af406fac7e39fe7ee7b61bfc84b8c8e4b4a0ce8cd","agent/plugin_llm.py":"4158b0ed2be2140eb99f09e1a488daed04da496052fe415cb84e412e4b71fb30","agent/system_prompt.py":"261481c471ddee92ced3fe381d63acbbe9136bedb6420f613983225007e2bb9a","agent/turn_context.py":"726ebf615b90237cca98d8f5d2d4e04f4507690d66917eb88f5d478c0b1ecaa2","agent/turn_finalizer.py":"a6c91daefaa805ef71604ddf9e9e08399825c1448bff2982bf72099da35957ac","hermes_cli/_parser.py":"82db5bb23c4619bf11536f44b56ea72c2b87cafcd062660af3593d2a4e08db0b","hermes_cli/config.py":"038040b0c0bbdd5f740b39c64d2e9ea1d7c78ae3d582cb337dbe8c145b4c8b03","hermes_cli/main.py":"55333e3fb37bec97b12c404760968716a3e81f3ae373896fc628df8dee3fb416","hermes_cli/plugins.py":"0f6c28614bebb7444392625a63c2b3186039f04238fe6ca79ad62d4849b0551c","model_tools.py":"db74ee29c8d335d80f3c18cc31f8c441956af956b2ed08e5113ced249fad32b4","plugins/memory/__init__.py":"f6bc37128d23f931ea1db52fc60cf25cca448f41105ed37d5b46f3843ab71b3a","plugins/memory/config_schema.py":"b6f58adffb2fd01a9605f10b891d905b8dfa72a509d1577499b82ad66ff7a937","pyproject.toml":"35462080afc8177258babd430dcdc2ac654fdf69332cc4370fec555295f7eaba"},"copy_policy":"No Hermes source or private profile bytes are distributed.","git_archive_sha256":"860a664f622e1099a095cb6cf06b04bcfe78b2fd3affc7192da9cf7ccefcdd63","license":"MIT","matrix_payload":{"contract_schema_sha256":"e23b47040d45c64676b2fc793e375f9be9d7d95078f0b5c384bb5a33383f499f","current_memory_schema_sha256":"3422f822f7b3e05c4b2f422a19a96283993938f11c798cb1982cb5d3cdc38169","package_tree_sha256":"fef0db12104acd704ff53ada40be70c98fc982194fb5e0c1f9892086931fec60","plugin_manifest_sha256":"6e2bcb16b394f4653727334653e5a4007149e5a3bc72c9958f9dc7e814dd8b92","plugin_sha256":"c6ef73ae9adedf45a5bd628ca745c1ff958a961f4b8ae0125b134ecddf6d88f5","provider_name":"daimon-matrix","skill_sha256":"6e0e021c6b0a2423dc95930c6901387738c15600deee2bb8f90a20110c66a275","soul_sha256":"0fc541f40cedc23056c07011a9eb99138028968930074c0a0a88db37eb81f25a","tools":["matrix_scope","matrix_propose_observation"]},"python_interval":">=3.11,<3.14","repository":"https://github.com/nicoechaniz/hermes-agent","schema":"dm.hermes-body.provenance/v1","supported_surfaces":["external-memory-provider","profile-plugin-discovery","memory-manager","current-user-api-content-sidecar","session-lifecycle-hooks","static-system-prompt-block"],"tree":"ac7dec02ca029e895963402788bd1cdc3afb36f8","version":"0.19.0"} diff --git a/schemas/collective-memory/v1/contracts.schema.json b/schemas/collective-memory/v1/contracts.schema.json new file mode 100644 index 0000000..7270599 --- /dev/null +++ b/schemas/collective-memory/v1/contracts.schema.json @@ -0,0 +1,385 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://daimon.network/schemas/collective-memory/v1/contracts.schema.json", + "title": "Daimon Matrix collective-memory adapter contracts", + "oneOf": [ + {"$ref": "#/$defs/sourceProfile"}, + {"$ref": "#/$defs/sourcePreview"}, + {"$ref": "#/$defs/sourceReceipt"}, + {"$ref": "#/$defs/publisherProfile"}, + {"$ref": "#/$defs/publisherRequest"}, + {"$ref": "#/$defs/publisherAcceptance"} + ], + "$defs": { + "hash": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, + "identifier": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]{0,255}$" + }, + "sourceRef": { + "type": "object", + "additionalProperties": false, + "required": ["id", "hash"], + "properties": { + "id": {"$ref": "#/$defs/identifier"}, + "hash": {"$ref": "#/$defs/hash"} + } + }, + "projection": { + "type": "object", + "additionalProperties": false, + "required": ["index_generation", "ui_generation"], + "properties": { + "index_generation": {"type": ["string", "null"], "maxLength": 128}, + "ui_generation": {"type": ["string", "null"], "maxLength": 256} + } + }, + "exportArtifact": { + "type": "object", + "additionalProperties": false, + "required": [ + "artifact_id", "logical_id", "media_type", "authors", "source_refs", + "license", "consent_scope", "classification", "predecessor_artifact_id", + "state", "content_hash", "content_length", "content_ref" + ], + "properties": { + "artifact_id": {"$ref": "#/$defs/identifier"}, + "logical_id": {"$ref": "#/$defs/identifier"}, + "media_type": {"enum": ["text/markdown; charset=utf-8", "text/plain; charset=utf-8"]}, + "authors": {"type": "array", "minItems": 1, "maxItems": 128, "uniqueItems": true, "items": {"$ref": "#/$defs/identifier"}}, + "source_refs": {"type": "array", "minItems": 1, "maxItems": 128, "uniqueItems": true, "items": {"$ref": "#/$defs/sourceRef"}}, + "license": {"$ref": "#/$defs/identifier"}, + "consent_scope": {"$ref": "#/$defs/identifier"}, + "classification": {"$ref": "#/$defs/identifier"}, + "predecessor_artifact_id": {"oneOf": [{"type": "null"}, {"$ref": "#/$defs/identifier"}]}, + "state": {"enum": ["active", "tombstone"]}, + "content_hash": {"oneOf": [{"type": "null"}, {"$ref": "#/$defs/hash"}]}, + "content_length": {"type": "integer", "minimum": 0, "maximum": 2097152}, + "content_ref": {"type": ["string", "null"], "pattern": "^sha256:[0-9a-f]{64}$"} + } + }, + "exportManifest": { + "type": "object", + "additionalProperties": false, + "required": ["schema", "generation_id", "manifest_hash", "body"], + "properties": { + "schema": {"const": "collective-export-manifest/v1"}, + "generation_id": {"$ref": "#/$defs/identifier"}, + "manifest_hash": {"$ref": "#/$defs/hash"}, + "body": { + "type": "object", + "additionalProperties": false, + "required": [ + "producer_instance", "producer_release", "policy_version", "scope_id", + "projection", "created_at", "predecessor_generation", "state_digest", + "artifact_count", "total_content_bytes", "artifacts" + ], + "properties": { + "producer_instance": {"$ref": "#/$defs/identifier"}, + "producer_release": {"$ref": "#/$defs/identifier"}, + "policy_version": {"$ref": "#/$defs/identifier"}, + "scope_id": {"$ref": "#/$defs/identifier"}, + "projection": {"$ref": "#/$defs/projection"}, + "created_at": {"type": "string", "format": "date-time", "pattern": "Z$"}, + "predecessor_generation": {"oneOf": [{"type": "null"}, {"$ref": "#/$defs/identifier"}]}, + "state_digest": {"$ref": "#/$defs/hash"}, + "artifact_count": {"type": "integer", "minimum": 0, "maximum": 4096}, + "total_content_bytes": {"type": "integer", "minimum": 0, "maximum": 67108864}, + "artifacts": {"type": "array", "maxItems": 4096, "items": {"$ref": "#/$defs/exportArtifact"}} + } + } + } + }, + "sourceProfile": { + "type": "object", + "additionalProperties": false, + "required": [ + "schema", "adapter_id", "contract_version", "upstream_commit", + "upstream_schema_sha256", "producer_instance", "producer_release", + "policy_version", "scope_id" + ], + "properties": { + "schema": {"const": "dm.collective-source.profile/v1"}, + "adapter_id": {"const": "dm:adapter:v0:Sh-2fDC4rpFOZz_ddjWqLptoX2SgUUzJPKhe6XQOtj8"}, + "contract_version": {"const": "v1"}, + "upstream_commit": {"const": "3e3b39416917f8e3c2bc5ca69362b20296205938"}, + "upstream_schema_sha256": {"const": "2aad43d1b309ee95108c855fc8dc682a854e5fdf3a1e799ecfca96d3ebf7c5d9"}, + "producer_instance": {"$ref": "#/$defs/identifier"}, + "producer_release": {"$ref": "#/$defs/identifier"}, + "policy_version": {"$ref": "#/$defs/identifier"}, + "scope_id": {"$ref": "#/$defs/identifier"} + } + }, + "contentBinding": { + "type": "object", + "additionalProperties": false, + "required": ["artifact_id", "content_hash", "content_length", "state"], + "properties": { + "artifact_id": {"$ref": "#/$defs/identifier"}, + "content_hash": {"oneOf": [{"type": "null"}, {"$ref": "#/$defs/hash"}]}, + "content_length": {"type": "integer", "minimum": 0, "maximum": 2097152}, + "state": {"enum": ["active", "tombstone"]} + } + }, + "sourcePreview": { + "type": "object", + "additionalProperties": false, + "required": ["schema", "preview_id", "preview_hash", "manifest", "content_bindings"], + "properties": { + "schema": {"const": "dm.collective-source.preview/v1"}, + "preview_id": {"$ref": "#/$defs/identifier"}, + "preview_hash": {"$ref": "#/$defs/hash"}, + "manifest": {"$ref": "#/$defs/exportManifest"}, + "content_bindings": {"type": "array", "maxItems": 4096, "items": {"$ref": "#/$defs/contentBinding"}} + } + }, + "sourceOutcomes": { + "type": "object", + "additionalProperties": false, + "required": ["admitted_to_quarantine", "tombstoned", "personal_memory_assertions"], + "properties": { + "admitted_to_quarantine": {"type": "integer", "minimum": 0, "maximum": 4096}, + "tombstoned": {"type": "integer", "minimum": 0, "maximum": 4096}, + "personal_memory_assertions": {"const": 0} + } + }, + "sourceReceipt": { + "type": "object", + "additionalProperties": false, + "required": ["schema", "receipt_id", "body"], + "properties": { + "schema": {"const": "dm.collective-source.receipt/v1"}, + "receipt_id": {"$ref": "#/$defs/identifier"}, + "body": { + "type": "object", + "additionalProperties": false, + "required": [ + "adapter_id", "importer_version", "producer_instance", "producer_release", + "policy_version", "scope_id", "generation_id", "manifest_hash", + "predecessor_generation", "state_digest", "artifact_count", + "total_content_bytes", "outcomes", "preview_id", "preview_hash", + "source_log_hash", "import_event_id", "imported_at_ms", "decision", + "reason_codes" + ], + "properties": { + "adapter_id": {"const": "dm:adapter:v0:Sh-2fDC4rpFOZz_ddjWqLptoX2SgUUzJPKhe6XQOtj8"}, + "importer_version": {"const": "daimon-matrix-collective-source/1.0.0"}, + "producer_instance": {"$ref": "#/$defs/identifier"}, + "producer_release": {"$ref": "#/$defs/identifier"}, + "policy_version": {"$ref": "#/$defs/identifier"}, + "scope_id": {"$ref": "#/$defs/identifier"}, + "generation_id": {"$ref": "#/$defs/identifier"}, + "manifest_hash": {"$ref": "#/$defs/hash"}, + "predecessor_generation": {"oneOf": [{"type": "null"}, {"$ref": "#/$defs/identifier"}]}, + "state_digest": {"$ref": "#/$defs/hash"}, + "artifact_count": {"type": "integer", "minimum": 0, "maximum": 4096}, + "total_content_bytes": {"type": "integer", "minimum": 0, "maximum": 67108864}, + "outcomes": {"$ref": "#/$defs/sourceOutcomes"}, + "preview_id": {"$ref": "#/$defs/identifier"}, + "preview_hash": {"$ref": "#/$defs/hash"}, + "source_log_hash": {"$ref": "#/$defs/hash"}, + "import_event_id": {"type": "string", "format": "uuid"}, + "imported_at_ms": {"type": "integer", "minimum": 0, "maximum": 9007199254740991}, + "decision": {"const": "quarantined"}, + "reason_codes": {"const": ["quarantined:initial-pull"]} + } + } + } + }, + "publisherProfile": { + "type": "object", + "additionalProperties": false, + "required": [ + "schema", "adapter_id", "contract_version", "upstream_commit", + "upstream_schema_sha256", "requester_id", "policy_version", "target_ids" + ], + "properties": { + "schema": {"const": "dm.collective-publisher.profile/v1"}, + "adapter_id": {"const": "dm:adapter:v0:Jsug9D2N641xJwE5Q_oLaHDy0wxT5knRJfxzV2ZEOXc"}, + "contract_version": {"const": "v1"}, + "upstream_commit": {"const": "3e3b39416917f8e3c2bc5ca69362b20296205938"}, + "upstream_schema_sha256": {"const": "2aad43d1b309ee95108c855fc8dc682a854e5fdf3a1e799ecfca96d3ebf7c5d9"}, + "requester_id": {"$ref": "#/$defs/identifier"}, + "policy_version": {"$ref": "#/$defs/identifier"}, + "target_ids": {"type": "array", "minItems": 1, "maxItems": 128, "uniqueItems": true, "items": {"$ref": "#/$defs/identifier"}} + } + }, + "evidenceRef": { + "type": "object", + "additionalProperties": false, + "required": ["evidence_id", "evidence_hash"], + "properties": { + "evidence_id": {"$ref": "#/$defs/identifier"}, + "evidence_hash": {"$ref": "#/$defs/hash"} + } + }, + "reviewRef": { + "type": "object", + "additionalProperties": false, + "required": ["evidence_id", "evidence_hash", "reviewer"], + "properties": { + "evidence_id": {"$ref": "#/$defs/identifier"}, + "evidence_hash": {"$ref": "#/$defs/hash"}, + "reviewer": {"$ref": "#/$defs/identifier"} + } + }, + "predecessor": { + "type": "object", + "additionalProperties": false, + "required": ["receipt_id", "receipt_hash"], + "properties": { + "receipt_id": {"$ref": "#/$defs/identifier"}, + "receipt_hash": {"$ref": "#/$defs/hash"} + } + }, + "publisherRequest": { + "type": "object", + "additionalProperties": false, + "required": ["schema", "summary_id", "body"], + "properties": { + "schema": {"const": "dm.collective-publisher.request/v1"}, + "summary_id": {"$ref": "#/$defs/identifier"}, + "body": { + "type": "object", + "additionalProperties": false, + "required": [ + "adapter_id", "publisher_version", "request_id", "request_hash", "plan_id", + "plan_hash", "idempotency_key", "requester_id", "subject_id", "target_id", + "action", "source_refs", "source_checkpoint", "classification", "policy_version", + "preview_id", "preview_hash", "content_hash", "content_length", "consent", + "review", "predecessor", "requested_at_ms" + ], + "properties": { + "adapter_id": {"const": "dm:adapter:v0:Jsug9D2N641xJwE5Q_oLaHDy0wxT5knRJfxzV2ZEOXc"}, + "publisher_version": {"const": "daimon-matrix-collective-publisher/1.0.0"}, + "request_id": {"$ref": "#/$defs/identifier"}, + "request_hash": {"$ref": "#/$defs/hash"}, + "plan_id": {"$ref": "#/$defs/identifier"}, + "plan_hash": {"$ref": "#/$defs/hash"}, + "idempotency_key": {"$ref": "#/$defs/identifier"}, + "requester_id": {"$ref": "#/$defs/identifier"}, + "subject_id": {"$ref": "#/$defs/identifier"}, + "target_id": {"$ref": "#/$defs/identifier"}, + "action": {"enum": ["publish", "successor", "tombstone"]}, + "source_refs": {"type": "array", "minItems": 1, "maxItems": 128, "uniqueItems": true, "items": {"$ref": "#/$defs/sourceRef"}}, + "source_checkpoint": {"$ref": "#/$defs/sourceRef"}, + "classification": {"$ref": "#/$defs/identifier"}, + "policy_version": {"$ref": "#/$defs/identifier"}, + "preview_id": {"$ref": "#/$defs/identifier"}, + "preview_hash": {"$ref": "#/$defs/hash"}, + "content_hash": {"$ref": "#/$defs/hash"}, + "content_length": {"type": "integer", "minimum": 1, "maximum": 1048576}, + "consent": {"$ref": "#/$defs/evidenceRef"}, + "review": {"$ref": "#/$defs/reviewRef"}, + "predecessor": {"oneOf": [{"type": "null"}, {"$ref": "#/$defs/predecessor"}]}, + "requested_at_ms": {"type": "integer", "minimum": 0, "maximum": 9007199254740991} + } + } + } + }, + "providerReceipt": { + "type": "object", + "additionalProperties": false, + "required": ["schema", "receipt_id", "receipt_hash", "body"], + "properties": { + "schema": {"const": "collective-publication-receipt/v1"}, + "receipt_id": {"$ref": "#/$defs/identifier"}, + "receipt_hash": {"$ref": "#/$defs/hash"}, + "body": { + "type": "object", + "additionalProperties": false, + "required": [ + "transaction_id", "request_hash", "plan_id", "idempotency_key", + "target_id", "action", "before", "after", "source_refs", + "source_checkpoint", "classification", "policy_version", "consent", + "review", "projection", "committed_at", "status" + ], + "properties": { + "transaction_id": {"$ref": "#/$defs/identifier"}, + "request_hash": {"$ref": "#/$defs/hash"}, + "plan_id": {"$ref": "#/$defs/identifier"}, + "idempotency_key": {"$ref": "#/$defs/identifier"}, + "target_id": {"$ref": "#/$defs/identifier"}, + "action": {"enum": ["publish", "successor", "tombstone"]}, + "before": {"oneOf": [{"type": "null"}, {"$ref": "#/$defs/targetRecord"}]}, + "after": {"$ref": "#/$defs/afterRecord"}, + "source_refs": {"type": "array", "minItems": 1, "maxItems": 128, "uniqueItems": true, "items": {"$ref": "#/$defs/sourceRef"}}, + "source_checkpoint": {"$ref": "#/$defs/sourceRef"}, + "classification": {"$ref": "#/$defs/identifier"}, + "policy_version": {"$ref": "#/$defs/identifier"}, + "consent": {"$ref": "#/$defs/evidenceRef"}, + "review": {"$ref": "#/$defs/evidenceRef"}, + "projection": {"$ref": "#/$defs/receiptProjection"}, + "committed_at": {"type": "string", "format": "date-time", "pattern": "Z$"}, + "status": {"const": "committed"} + } + } + } + }, + "targetRecord": { + "type": "object", + "additionalProperties": false, + "required": ["receipt_id", "receipt_hash", "content_hash", "content_length", "state"], + "properties": { + "receipt_id": {"$ref": "#/$defs/identifier"}, + "receipt_hash": {"$ref": "#/$defs/hash"}, + "content_hash": {"$ref": "#/$defs/hash"}, + "content_length": {"type": "integer", "minimum": 1, "maximum": 1048576}, + "state": {"enum": ["active", "tombstone"]} + } + }, + "afterRecord": { + "type": "object", + "additionalProperties": false, + "required": ["content_hash", "content_length", "media_type", "state"], + "properties": { + "content_hash": {"$ref": "#/$defs/hash"}, + "content_length": {"type": "integer", "minimum": 1, "maximum": 1048576}, + "media_type": {"const": "text/markdown; charset=utf-8"}, + "state": {"enum": ["active", "tombstone"]} + } + }, + "receiptProjection": { + "type": "object", + "additionalProperties": false, + "required": ["index_generation", "ui_generation", "index_content_hash"], + "properties": { + "index_generation": {"type": "string", "minLength": 1, "maxLength": 128}, + "ui_generation": {"type": "string", "minLength": 1, "maxLength": 256}, + "index_content_hash": {"$ref": "#/$defs/hash"} + } + }, + "publisherAcceptance": { + "type": "object", + "additionalProperties": false, + "required": ["schema", "acceptance_id", "body"], + "properties": { + "schema": {"const": "dm.collective-publisher.acceptance/v1"}, + "acceptance_id": {"$ref": "#/$defs/identifier"}, + "body": { + "type": "object", + "additionalProperties": false, + "required": [ + "adapter_id", "publisher_version", "request_event_id", "request_event_hash", + "request_id", "request_hash", "summary_id", "provider_receipt", + "reconciliation_state_hash", "accepted_at_ms" + ], + "properties": { + "adapter_id": {"const": "dm:adapter:v0:Jsug9D2N641xJwE5Q_oLaHDy0wxT5knRJfxzV2ZEOXc"}, + "publisher_version": {"const": "daimon-matrix-collective-publisher/1.0.0"}, + "request_event_id": {"type": "string", "format": "uuid"}, + "request_event_hash": {"$ref": "#/$defs/hash"}, + "request_id": {"$ref": "#/$defs/identifier"}, + "request_hash": {"$ref": "#/$defs/hash"}, + "summary_id": {"$ref": "#/$defs/identifier"}, + "provider_receipt": {"$ref": "#/$defs/providerReceipt"}, + "reconciliation_state_hash": {"$ref": "#/$defs/hash"}, + "accepted_at_ms": {"type": "integer", "minimum": 0, "maximum": 9007199254740991} + } + } + } + } + } +} diff --git a/schemas/weave/v1/event.schema.json b/schemas/weave/v1/event.schema.json index c11c5c0..97b315a 100644 --- a/schemas/weave/v1/event.schema.json +++ b/schemas/weave/v1/event.schema.json @@ -35,7 +35,7 @@ "previous_event_id": {"type": ["string", "null"], "format": "uuid"}, "occurred_at_ms": {"type": "integer", "minimum": 0, "maximum": 9007199254740991}, "causal_parents": {"type": "array", "maxItems": 64, "uniqueItems": true, "items": {"type": "string", "format": "uuid"}}, - "kind": {"enum": ["experience.observed", "skill.proposed", "preference.proposed", "configuration.proposed", "adoption.decided", "projection.receipted", "lifecycle.announced", "memory.recorded", "review.authorization.issued", "review.authorization.revoked", "review.requested", "review.decided", "review.executed", "publication.requested", "publication.receipted", "matrix/species-release-application", "matrix/source-claim", "matrix/source-assessment", "matrix/source-publication", "matrix/source-cursor", "matrix/source-import-decision", "matrix/relationship-card", "matrix/relationship-offer", "matrix/relationship-acceptance", "matrix/relationship-close", "matrix/tribe-declaration", "matrix/tribe-invitation", "matrix/tribe-membership-acceptance", "matrix/tribe-membership-leave", "matrix/tribe-membership-expulsion", "matrix/tribe-founder-transfer", "matrix/tribe-founder-acceptance", "matrix/relationship-grant", "matrix/relationship-grant-acceptance", "matrix/relationship-grant-revocation"]}, + "kind": {"enum": ["experience.observed", "skill.proposed", "preference.proposed", "configuration.proposed", "adoption.decided", "projection.receipted", "lifecycle.announced", "memory.recorded", "review.authorization.issued", "review.authorization.revoked", "review.requested", "review.decided", "review.executed", "publication.requested", "publication.receipted", "matrix/species-release-application", "matrix/source-claim", "matrix/source-assessment", "matrix/source-publication", "matrix/source-cursor", "matrix/source-import-decision", "matrix/relationship-card", "matrix/relationship-offer", "matrix/relationship-acceptance", "matrix/relationship-close", "matrix/tribe-declaration", "matrix/tribe-invitation", "matrix/tribe-membership-acceptance", "matrix/tribe-membership-leave", "matrix/tribe-membership-expulsion", "matrix/tribe-founder-transfer", "matrix/tribe-founder-acceptance", "matrix/relationship-grant", "matrix/relationship-grant-acceptance", "matrix/relationship-grant-revocation", "source.imported", "collective.publication.requested", "collective.publication.receipted"]}, "subject": {"type": "string", "minLength": 1, "maxLength": 256}, "payload": { "type": "object", @@ -165,6 +165,30 @@ { "if": {"properties": {"kind": {"const": "matrix/source-import-decision"}}, "required": ["kind"]}, "then": {"properties": {"payload": {"$ref": "../../source/v0/contracts.schema.json#/$defs/importDecision"}}} + }, + { + "if": {"properties": {"kind": {"const": "source.imported"}}, "required": ["kind"]}, + "then": { + "properties": { + "payload": {"$ref": "../../collective-memory/v1/contracts.schema.json#/$defs/sourceReceipt"} + } + } + }, + { + "if": {"properties": {"kind": {"const": "collective.publication.requested"}}, "required": ["kind"]}, + "then": { + "properties": { + "payload": {"$ref": "../../collective-memory/v1/contracts.schema.json#/$defs/publisherRequest"} + } + } + }, + { + "if": {"properties": {"kind": {"const": "collective.publication.receipted"}}, "required": ["kind"]}, + "then": { + "properties": { + "payload": {"$ref": "../../collective-memory/v1/contracts.schema.json#/$defs/publisherAcceptance"} + } + } } ], "$defs": { diff --git a/specs/adapter-contracts.md b/specs/adapter-contracts.md index dd8ee36..ae6e3be 100644 --- a/specs/adapter-contracts.md +++ b/specs/adapter-contracts.md @@ -167,3 +167,27 @@ one writer. Successor, withdrawal and rollback are new reviewed monotonic transactions; they never erase audit history or lower a high-water. The exact contracts, crash matrix, private-provider CI split and rollback procedure are normative in `docs/dm035-publication.md`. + +### collective-memory exchange + +DM-036 defines two deliberately separate adapters over the +`collective-memory` exchange boundary pinned at +`3e3b39416917f8e3c2bc5ca69362b20296205938`. The inbound source adapter reads +and verifies immutable export generations into an append-only source log, then +authors only `source.imported` quarantine receipts. It may walk an immutable +predecessor chain oldest-first for offline catch-up, but it never promotes an +artifact or asserts personal memory. + +The outbound publisher accepts only deterministic derived bytes bound to exact +current Matrix events, current subject consent and a distinct current human +review. It authors `collective.publication.requested`, invokes the supported +upstream transaction and authors `collective.publication.receipted` only after +validating the complete provider receipt and freshly observing +`reconcile=verified`. + +The directions have different adapter IDs, DM-018 manifests, transport +identities, credentials, capabilities, stores, locks, queues, idempotency +spaces, events and receipts. No outbound receipt admits an inbound source; no +inbound trust grants publication, review or consent authority. Their closed +contracts, vectors, recovery state machines and isolated real-I/O gate are +normative in `docs/dm036-collective-memory.md`. diff --git a/specs/source-ancestry.md b/specs/source-ancestry.md index fe180bf..eadb3bb 100644 --- a/specs/source-ancestry.md +++ b/specs/source-ancestry.md @@ -801,8 +801,14 @@ Conformance vectors and implementation tests MUST cover at least: - DM-018 freezes adapters without granting them source or policy authority. - DM-023 persists cursors, forks, blobs, transaction journals, and receipts without changing event IDs or intrinsic/local-state separation. -- DM-036 implements separate inbound collective-memory and reviewed outbound - HMK publication adapters from Section 10. +- DM-036 implements separate inbound collective-memory source and reviewed + outbound collective-memory publication adapters. Inbound generations author + only `source.imported` quarantine receipts and may catch up through an exact + immutable predecessor chain; outbound requests and accepted effects use + `collective.publication.requested` and + `collective.publication.receipted`. The two directions never share authority, + stores or receipts. See + [`dm036-collective-memory.md`](../docs/dm036-collective-memory.md). - DM-054 routes the already-resolved recipients; routes never create source membership. - DM-071 validates consented cross-daimon source exchange, pagination, diff --git a/src/daimon_matrix/collective_memory.py b/src/daimon_matrix/collective_memory.py new file mode 100644 index 0000000..71b3224 --- /dev/null +++ b/src/daimon_matrix/collective_memory.py @@ -0,0 +1,3555 @@ +"""Separated collective-memory source and reviewed-publication adapters. + +The inbound adapter imports immutable exchange generations into an owner-local +source log and authors one Matrix quarantine receipt. The outbound adapter +submits only exact reviewed derived bytes to the pinned collective-memory +publication transaction. The two adapters deliberately have different +identities, transports, stores and idempotency domains. +""" + +from __future__ import annotations + +import copy +import datetime as dt +import fcntl +import hashlib +import json +import os +import re +import sqlite3 +import stat +import unicodedata +import uuid +from collections.abc import Callable, Iterator, Mapping, Sequence +from contextlib import closing, contextmanager +from dataclasses import dataclass +from pathlib import Path +from typing import Any, Final, Protocol, cast + +from cryptography.exceptions import InvalidSignature +from cryptography.hazmat.primitives.asymmetric.ed25519 import ( + Ed25519PrivateKey, + Ed25519PublicKey, +) + +from .canonical import CanonicalError, b64url, canonical_bytes, unb64url +from .ledger import Ledger +from .weave import Event, EventSigner + +COLLECTIVE_MEMORY_COMMIT: Final = "3e3b39416917f8e3c2bc5ca69362b20296205938" +COLLECTIVE_SCHEMA_SHA256: Final = ( + "2aad43d1b309ee95108c855fc8dc682a854e5fdf3a1e799ecfca96d3ebf7c5d9" +) +COLLECTIVE_CONTRACT_VERSION: Final = "v1" + +SOURCE_ADAPTER_ID: Final = "dm:adapter:v0:Sh-2fDC4rpFOZz_ddjWqLptoX2SgUUzJPKhe6XQOtj8" +PUBLISHER_ADAPTER_ID: Final = ( + "dm:adapter:v0:Jsug9D2N641xJwE5Q_oLaHDy0wxT5knRJfxzV2ZEOXc" +) +SOURCE_IMPORTER_VERSION: Final = "daimon-matrix-collective-source/1.0.0" +PUBLISHER_VERSION: Final = "daimon-matrix-collective-publisher/1.0.0" + +EXPORT_MANIFEST_SCHEMA: Final = "collective-export-manifest/v1" +EXPORT_PAGE_SCHEMA: Final = "collective-export-page/v1" +PUBLICATION_DRAFT_SCHEMA: Final = "collective-publication-draft/v1" +PUBLICATION_PREVIEW_SCHEMA: Final = "collective-publication-preview/v1" +PUBLICATION_EVIDENCE_SCHEMA: Final = "collective-publication-evidence/v1" +PUBLICATION_REQUEST_SCHEMA: Final = "collective-publication-request/v1" +PUBLICATION_PLAN_SCHEMA: Final = "collective-publication-plan/v1" +PUBLICATION_RECEIPT_SCHEMA: Final = "collective-publication-receipt/v1" +PUBLICATION_RECONCILIATION_SCHEMA: Final = "collective-publication-reconciliation/v1" + +SOURCE_PROFILE_SCHEMA: Final = "dm.collective-source.profile/v1" +SOURCE_PREVIEW_SCHEMA: Final = "dm.collective-source.preview/v1" +SOURCE_RECEIPT_SCHEMA: Final = "dm.collective-source.receipt/v1" +PUBLISHER_PROFILE_SCHEMA: Final = "dm.collective-publisher.profile/v1" +PUBLISHER_REQUEST_SCHEMA: Final = "dm.collective-publisher.request/v1" +PUBLISHER_ACCEPTANCE_SCHEMA: Final = "dm.collective-publisher.acceptance/v1" + +MAX_ARTIFACTS: Final = 4096 +MAX_ARTIFACT_BYTES: Final = 2 * 1024 * 1024 +MAX_EXPORT_BYTES: Final = 64 * 1024 * 1024 +MAX_PUBLICATION_BYTES: Final = 1024 * 1024 +MAX_PAGE: Final = 256 +MAX_SOURCE_REFS: Final = 128 +MAX_DOCUMENT_BYTES: Final = 70 * 1024 * 1024 +MAX_SAFE_INTEGER: Final = 2**53 - 1 + +_HASH = re.compile(r"^[0-9a-f]{64}$") +_IDENTIFIER = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:/@+\-]{0,255}$") +_B64_HASH = re.compile(r"^[A-Za-z0-9_-]{43}$") +_UTC = re.compile(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,6})?Z$") +_SECRET_PATTERNS: Final = ( + re.compile(rb"-----BEGIN (?:RSA |DSA |EC |OPENSSH |PGP )?PRIVATE KEY-----"), + re.compile(rb"\bBearer\s+[A-Za-z0-9._~+/=-]{24,}\b", re.IGNORECASE), + re.compile(rb"\bsk-[A-Za-z0-9_-]{20,}\b"), + re.compile(rb"\bgh[pousr]_[A-Za-z0-9_]{20,}\b"), + re.compile(rb"\bAKIA[0-9A-Z]{16}\b"), + re.compile( + rb"\b(password|passwd|api_key|apikey|token)\b\s*[:=]\s*['\"]?[^'\"\s]{8,}", + re.IGNORECASE, + ), + re.compile(rb"[a-z][a-z0-9+.-]*://[^/\s:@]+:[^/\s@]+@[^/\s]+", re.IGNORECASE), +) + +SOURCE_PREVIEW_DOMAIN: Final = b"daimon/collective-source-preview/v1\x00" +SOURCE_RECEIPT_DOMAIN: Final = b"daimon/collective-source-receipt/v1\x00" +PUBLISHER_REQUEST_DOMAIN: Final = b"daimon/collective-publisher-request/v1\x00" +PUBLISHER_ACCEPTANCE_DOMAIN: Final = b"daimon/collective-publisher-acceptance/v1\x00" +SOURCE_ADAPTER_DOMAIN: Final = b"daimon/collective-source/adapter/v1\x00" +PUBLISHER_ADAPTER_DOMAIN: Final = b"daimon/collective-publisher/adapter/v1\x00" + + +class CollectiveMemoryError(RuntimeError): + """Stable fail-closed collective-memory adapter error.""" + + def __init__(self, code: str, *, retryable: bool = False) -> None: + super().__init__(code) + self.code = code + self.retryable = retryable + + +class SourceTransport(Protocol): + """Injected read-only transport; credentials remain implementation-private.""" + + def __call__( + self, operation: str, document: Mapping[str, Any] + ) -> Mapping[str, Any] | bytes: ... + + +class PublisherTransport(Protocol): + """Injected write-only reviewed publisher transport.""" + + def __call__( + self, operation: str, document: Mapping[str, Any] + ) -> Mapping[str, Any]: ... + + +Clock = Callable[[], int] +Fault = Callable[[str], None] + + +def _no_fault(_stage: str) -> None: + return None + + +def _canonical(value: Any, code: str) -> bytes: + try: + raw = canonical_bytes(value) + except CanonicalError as exception: + raise CollectiveMemoryError(code) from exception + if len(raw) > MAX_DOCUMENT_BYTES: + raise CollectiveMemoryError("collective_document_too_large") + return raw + + +def _closed(value: Any, fields: set[str], code: str) -> Mapping[str, Any]: + if not isinstance(value, Mapping) or set(value) != fields: + raise CollectiveMemoryError(code) + return value + + +def _text(value: Any, code: str, *, maximum: int = 256, empty: bool = False) -> str: + if not isinstance(value, str): + raise CollectiveMemoryError(code) + raw = value.encode("utf-8") + if (not empty and not raw) or len(raw) > maximum: + raise CollectiveMemoryError(code) + if unicodedata.normalize("NFC", value) != value or any( + 0xD800 <= ord(character) <= 0xDFFF for character in value + ): + raise CollectiveMemoryError(code) + return value + + +def _identifier(value: Any, code: str) -> str: + result = _text(value, code) + if _IDENTIFIER.fullmatch(result) is None: + raise CollectiveMemoryError(code) + return result + + +def _hash(value: Any, code: str) -> str: + if not isinstance(value, str) or _HASH.fullmatch(value) is None: + raise CollectiveMemoryError(code) + return value + + +def _uint( + value: Any, code: str, *, minimum: int = 0, maximum: int = MAX_SAFE_INTEGER +) -> int: + if ( + isinstance(value, bool) + or not isinstance(value, int) + or not minimum <= value <= maximum + ): + raise CollectiveMemoryError(code) + return value + + +def _nullable_identifier(value: Any, code: str) -> str | None: + return None if value is None else _identifier(value, code) + + +def _nullable_hash(value: Any, code: str) -> str | None: + return None if value is None else _hash(value, code) + + +def _timestamp(value: Any, code: str) -> str: + result = _text(value, code, maximum=40) + if _UTC.fullmatch(result) is None: + raise CollectiveMemoryError(code) + try: + parsed = dt.datetime.fromisoformat(result.removesuffix("Z") + "+00:00") + except ValueError as exception: + raise CollectiveMemoryError(code) from exception + if parsed.tzinfo != dt.UTC: + raise CollectiveMemoryError(code) + return result + + +def _uuid(value: Any, code: str) -> str: + if not isinstance(value, str): + raise CollectiveMemoryError(code) + try: + parsed = uuid.UUID(value) + except ValueError as exception: + raise CollectiveMemoryError(code) from exception + if str(parsed) != value: + raise CollectiveMemoryError(code) + return value + + +def _content_id(prefix: str, body: Mapping[str, Any]) -> tuple[str, str]: + digest = hashlib.sha256(_canonical(body, "invalid_collective_artifact")).digest() + return f"{prefix}:{b64url(digest)}", digest.hex() + + +def _derived(prefix: str, domain: bytes, body: Mapping[str, Any]) -> str: + return prefix + b64url( + hashlib.sha256( + domain + _canonical(body, "invalid_collective_artifact") + ).digest() + ) + + +def _source_ref(value: Any, code: str) -> dict[str, str]: + row = _closed(value, {"id", "hash"}, code) + return {"id": _identifier(row["id"], code), "hash": _hash(row["hash"], code)} + + +def _checkpoint(value: Any, code: str) -> dict[str, str]: + return _source_ref(value, code) + + +def _authority_denial() -> dict[str, bool]: + return { + "matrix_authority": False, + "may_append_ledger": False, + "may_issue_presence": False, + "may_mint_membership": False, + "may_sign_as_me": False, + } + + +def create_source_manifest() -> dict[str, Any]: + core = { + "authority": _authority_denial(), + "capabilities": ["inspect", "page", "read", "recover", "reconcile"], + "contracts": [{"contract": "source", "versions": ["v1"]}], + "limits": { + "max_input_bytes": 1024 * 1024, + "max_output_bytes": 2 * 1024 * 1024, + "max_runtime_ms": 86_400_000, + }, + "provider_kind": "source", + } + if _derived("dm:adapter:v0:", SOURCE_ADAPTER_DOMAIN, core) != SOURCE_ADAPTER_ID: + raise CollectiveMemoryError("collective_source_manifest_identity_mismatch") + return { + "schema": "daimon-adapter-manifest/v0", + "adapter_id": SOURCE_ADAPTER_ID, + **core, + } + + +def create_publisher_manifest() -> dict[str, Any]: + core = { + "authority": _authority_denial(), + "capabilities": ["apply", "plan", "preview", "recover", "reconcile"], + "contracts": [{"contract": "artifact-store", "versions": ["v1"]}], + "limits": { + "max_input_bytes": 2 * 1024 * 1024, + "max_output_bytes": 2 * 1024 * 1024, + "max_runtime_ms": 86_400_000, + }, + "provider_kind": "artifact-store", + } + if ( + _derived("dm:adapter:v0:", PUBLISHER_ADAPTER_DOMAIN, core) + != PUBLISHER_ADAPTER_ID + ): + raise CollectiveMemoryError("collective_publisher_manifest_identity_mismatch") + return { + "schema": "daimon-adapter-manifest/v0", + "adapter_id": PUBLISHER_ADAPTER_ID, + **core, + } + + +def create_source_profile( + *, producer_instance: str, producer_release: str, policy_version: str, scope_id: str +) -> dict[str, Any]: + return validate_source_profile( + { + "schema": SOURCE_PROFILE_SCHEMA, + "adapter_id": SOURCE_ADAPTER_ID, + "contract_version": COLLECTIVE_CONTRACT_VERSION, + "upstream_commit": COLLECTIVE_MEMORY_COMMIT, + "upstream_schema_sha256": COLLECTIVE_SCHEMA_SHA256, + "producer_instance": producer_instance, + "producer_release": producer_release, + "policy_version": policy_version, + "scope_id": scope_id, + } + ) + + +def validate_source_profile(value: Any) -> dict[str, Any]: + row = _closed( + value, + { + "schema", + "adapter_id", + "contract_version", + "upstream_commit", + "upstream_schema_sha256", + "producer_instance", + "producer_release", + "policy_version", + "scope_id", + }, + "invalid_collective_source_profile", + ) + if ( + row["schema"] != SOURCE_PROFILE_SCHEMA + or row["adapter_id"] != SOURCE_ADAPTER_ID + or row["contract_version"] != COLLECTIVE_CONTRACT_VERSION + or row["upstream_commit"] != COLLECTIVE_MEMORY_COMMIT + or row["upstream_schema_sha256"] != COLLECTIVE_SCHEMA_SHA256 + ): + raise CollectiveMemoryError("collective_source_profile_pin_mismatch") + return { + **dict(row), + "producer_instance": _identifier( + row["producer_instance"], "invalid_collective_source_profile" + ), + "producer_release": _identifier( + row["producer_release"], "invalid_collective_source_profile" + ), + "policy_version": _identifier( + row["policy_version"], "invalid_collective_source_profile" + ), + "scope_id": _identifier(row["scope_id"], "invalid_collective_source_profile"), + } + + +def create_publisher_profile( + *, requester_id: str, policy_version: str, target_ids: Sequence[str] +) -> dict[str, Any]: + return validate_publisher_profile( + { + "schema": PUBLISHER_PROFILE_SCHEMA, + "adapter_id": PUBLISHER_ADAPTER_ID, + "contract_version": COLLECTIVE_CONTRACT_VERSION, + "upstream_commit": COLLECTIVE_MEMORY_COMMIT, + "upstream_schema_sha256": COLLECTIVE_SCHEMA_SHA256, + "requester_id": requester_id, + "policy_version": policy_version, + "target_ids": sorted(target_ids), + } + ) + + +def validate_publisher_profile(value: Any) -> dict[str, Any]: + row = _closed( + value, + { + "schema", + "adapter_id", + "contract_version", + "upstream_commit", + "upstream_schema_sha256", + "requester_id", + "policy_version", + "target_ids", + }, + "invalid_collective_publisher_profile", + ) + if ( + row["schema"] != PUBLISHER_PROFILE_SCHEMA + or row["adapter_id"] != PUBLISHER_ADAPTER_ID + or row["contract_version"] != COLLECTIVE_CONTRACT_VERSION + or row["upstream_commit"] != COLLECTIVE_MEMORY_COMMIT + or row["upstream_schema_sha256"] != COLLECTIVE_SCHEMA_SHA256 + ): + raise CollectiveMemoryError("collective_publisher_profile_pin_mismatch") + raw_targets = row["target_ids"] + if not isinstance(raw_targets, list) or not raw_targets: + raise CollectiveMemoryError("invalid_collective_publisher_profile") + targets = [ + _identifier(item, "invalid_collective_publisher_profile") + for item in raw_targets + ] + if targets != sorted(set(targets)): + raise CollectiveMemoryError("invalid_collective_publisher_profile") + return { + **dict(row), + "requester_id": _identifier( + row["requester_id"], "invalid_collective_publisher_profile" + ), + "policy_version": _identifier( + row["policy_version"], "invalid_collective_publisher_profile" + ), + "target_ids": targets, + } + + +def _validate_export_artifact(value: Any) -> dict[str, Any]: + code = "invalid_collective_export_artifact" + row = _closed( + value, + { + "artifact_id", + "logical_id", + "media_type", + "authors", + "source_refs", + "license", + "consent_scope", + "classification", + "predecessor_artifact_id", + "state", + "content_hash", + "content_length", + "content_ref", + }, + code, + ) + artifact_id = _identifier(row["artifact_id"], code) + logical_id = _identifier(row["logical_id"], code) + if row["media_type"] not in { + "text/markdown; charset=utf-8", + "text/plain; charset=utf-8", + }: + raise CollectiveMemoryError("collective_unsupported_media_type") + if not isinstance(row["authors"], list) or not row["authors"]: + raise CollectiveMemoryError("collective_missing_provenance") + authors = [_identifier(item, code) for item in row["authors"]] + if authors != sorted(set(authors)): + raise CollectiveMemoryError("collective_noncanonical_provenance") + if not isinstance(row["source_refs"], list) or not row["source_refs"]: + raise CollectiveMemoryError("collective_missing_provenance") + source_refs = [_source_ref(item, code) for item in row["source_refs"]] + if source_refs != sorted(source_refs, key=lambda item: (item["id"], item["hash"])): + raise CollectiveMemoryError("collective_noncanonical_sources") + if len({(item["id"], item["hash"]) for item in source_refs}) != len(source_refs): + raise CollectiveMemoryError("collective_duplicate_source") + state = row["state"] + if state not in {"active", "tombstone"}: + raise CollectiveMemoryError(code) + content_hash = _nullable_hash(row["content_hash"], code) + length = _uint(row["content_length"], code, maximum=MAX_ARTIFACT_BYTES) + content_ref = row["content_ref"] + if state == "active": + if ( + content_hash is None + or length < 1 + or content_ref != f"sha256:{content_hash}" + ): + raise CollectiveMemoryError("collective_invalid_active_artifact") + elif content_hash is not None or length != 0 or content_ref is not None: + raise CollectiveMemoryError("collective_invalid_tombstone") + return { + "artifact_id": artifact_id, + "logical_id": logical_id, + "media_type": row["media_type"], + "authors": authors, + "source_refs": source_refs, + "license": _identifier(row["license"], code), + "consent_scope": _identifier(row["consent_scope"], code), + "classification": _identifier(row["classification"], code), + "predecessor_artifact_id": _nullable_identifier( + row["predecessor_artifact_id"], code + ), + "state": state, + "content_hash": content_hash, + "content_length": length, + "content_ref": content_ref, + } + + +def validate_export_manifest(value: Any) -> dict[str, Any]: + code = "invalid_collective_export_manifest" + row = _closed(value, {"schema", "generation_id", "manifest_hash", "body"}, code) + if row["schema"] != EXPORT_MANIFEST_SCHEMA: + raise CollectiveMemoryError("collective_unsupported_schema") + body = _closed( + row["body"], + { + "producer_instance", + "producer_release", + "policy_version", + "scope_id", + "projection", + "created_at", + "predecessor_generation", + "state_digest", + "artifact_count", + "total_content_bytes", + "artifacts", + }, + code, + ) + projection = _closed( + body["projection"], {"index_generation", "ui_generation"}, code + ) + for field, maximum in (("index_generation", 128), ("ui_generation", 256)): + if projection[field] is not None: + _text(projection[field], code, maximum=maximum) + raw_artifacts = body["artifacts"] + if not isinstance(raw_artifacts, list) or len(raw_artifacts) > MAX_ARTIFACTS: + raise CollectiveMemoryError("collective_export_too_large") + artifacts = [_validate_export_artifact(item) for item in raw_artifacts] + if artifacts != sorted(artifacts, key=lambda item: item["artifact_id"]): + raise CollectiveMemoryError("collective_noncanonical_artifacts") + if len({item["artifact_id"] for item in artifacts}) != len(artifacts) or len( + {item["logical_id"] for item in artifacts} + ) != len(artifacts): + raise CollectiveMemoryError("collective_duplicate_artifact") + artifact_count = _uint(body["artifact_count"], code, maximum=MAX_ARTIFACTS) + total = _uint(body["total_content_bytes"], code, maximum=MAX_EXPORT_BYTES) + if artifact_count != len(artifacts) or total != sum( + cast(int, item["content_length"]) for item in artifacts + ): + raise CollectiveMemoryError("collective_manifest_count_mismatch") + scope_id = _identifier(body["scope_id"], code) + if any( + item["consent_scope"] != scope_id or item["classification"] != scope_id + for item in artifacts + ): + raise CollectiveMemoryError("collective_scope_violation") + normalized_body = { + "producer_instance": _identifier(body["producer_instance"], code), + "producer_release": _identifier(body["producer_release"], code), + "policy_version": _identifier(body["policy_version"], code), + "scope_id": scope_id, + "projection": dict(projection), + "created_at": _timestamp(body["created_at"], code), + "predecessor_generation": _nullable_identifier( + body["predecessor_generation"], code + ), + "state_digest": _hash(body["state_digest"], code), + "artifact_count": artifact_count, + "total_content_bytes": total, + "artifacts": artifacts, + } + generation_id, manifest_hash = _content_id("cm:export:v1", normalized_body) + if row["generation_id"] != generation_id or row["manifest_hash"] != manifest_hash: + raise CollectiveMemoryError("collective_manifest_identity_mismatch") + return { + "schema": EXPORT_MANIFEST_SCHEMA, + "generation_id": generation_id, + "manifest_hash": manifest_hash, + "body": normalized_body, + } + + +def validate_export_page( + value: Any, + manifest: Mapping[str, Any], + *, + expected_offset: int, + expected_limit: int, +) -> dict[str, Any]: + code = "invalid_collective_export_page" + row = _closed( + value, + { + "schema", + "generation_id", + "manifest_hash", + "offset", + "limit", + "artifacts", + "next_cursor", + }, + code, + ) + if ( + row["schema"] != EXPORT_PAGE_SCHEMA + or row["generation_id"] != manifest["generation_id"] + or row["manifest_hash"] != manifest["manifest_hash"] + ): + raise CollectiveMemoryError("collective_mixed_generation") + if ( + _uint(row["offset"], code) != expected_offset + or _uint(row["limit"], code, minimum=1, maximum=MAX_PAGE) != expected_limit + ): + raise CollectiveMemoryError("collective_page_position_mismatch") + raw = row["artifacts"] + if not isinstance(raw, list) or len(raw) > expected_limit: + raise CollectiveMemoryError(code) + artifacts = [_validate_export_artifact(item) for item in raw] + cursor = row["next_cursor"] + if cursor is not None: + _text(cursor, code, maximum=4096) + try: + unb64url(cursor) + except CanonicalError as exception: + raise CollectiveMemoryError(code) from exception + return {**dict(row), "artifacts": artifacts} + + +def _validate_draft(value: Any, profile: Mapping[str, Any]) -> dict[str, Any]: + code = "invalid_collective_publication_draft" + row = _closed( + value, + { + "schema", + "action", + "requester_id", + "subject_id", + "target_id", + "source_refs", + "source_checkpoint", + "classification", + "policy_version", + "media_type", + "title", + "body", + "predecessor_receipt_id", + "predecessor_receipt_hash", + }, + code, + ) + if row["schema"] != PUBLICATION_DRAFT_SCHEMA: + raise CollectiveMemoryError("collective_unsupported_schema") + if row["action"] not in {"publish", "successor", "tombstone"}: + raise CollectiveMemoryError(code) + requester = _identifier(row["requester_id"], code) + target = _identifier(row["target_id"], code) + if requester != profile["requester_id"] or target not in profile["target_ids"]: + raise CollectiveMemoryError("collective_publication_authority_mismatch") + raw_refs = row["source_refs"] + if not isinstance(raw_refs, list) or not 1 <= len(raw_refs) <= MAX_SOURCE_REFS: + raise CollectiveMemoryError(code) + refs = [_source_ref(item, code) for item in raw_refs] + if refs != sorted(refs, key=lambda item: (item["id"], item["hash"])) or len( + {(item["id"], item["hash"]) for item in refs} + ) != len(refs): + raise CollectiveMemoryError("collective_noncanonical_sources") + if row["policy_version"] != profile["policy_version"]: + raise CollectiveMemoryError("collective_policy_mismatch") + if row["media_type"] != "text/markdown; charset=utf-8": + raise CollectiveMemoryError("collective_unsupported_media_type") + title = _text(row["title"], code, maximum=1024, empty=row["action"] == "tombstone") + body = _text( + row["body"], + code, + maximum=MAX_PUBLICATION_BYTES, + empty=row["action"] == "tombstone", + ) + predecessor_id = _nullable_identifier(row["predecessor_receipt_id"], code) + predecessor_hash = _nullable_hash(row["predecessor_receipt_hash"], code) + if (predecessor_id is None) != (predecessor_hash is None): + raise CollectiveMemoryError("collective_invalid_predecessor") + if row["action"] == "publish" and predecessor_id is not None: + raise CollectiveMemoryError("collective_invalid_predecessor") + if row["action"] != "publish" and predecessor_id is None: + raise CollectiveMemoryError("collective_missing_predecessor") + if row["action"] == "tombstone" and (title or body): + raise CollectiveMemoryError("collective_invalid_tombstone") + return { + "schema": PUBLICATION_DRAFT_SCHEMA, + "action": row["action"], + "requester_id": requester, + "subject_id": _identifier(row["subject_id"], code), + "target_id": target, + "source_refs": refs, + "source_checkpoint": _checkpoint(row["source_checkpoint"], code), + "classification": _identifier(row["classification"], code), + "policy_version": row["policy_version"], + "media_type": row["media_type"], + "title": title, + "body": body, + "predecessor_receipt_id": predecessor_id, + "predecessor_receipt_hash": predecessor_hash, + } + + +def _render_collective(draft: Mapping[str, Any]) -> bytes: + metadata = { + "action": draft["action"], + "classification": draft["classification"], + "policy_version": draft["policy_version"], + "schema": "collective-publication-artifact/v1", + "source_checkpoint": draft["source_checkpoint"], + "source_refs": draft["source_refs"], + "subject_id": draft["subject_id"], + "target_id": draft["target_id"], + } + if draft["action"] == "tombstone": + title = "Publication withdrawn" + body = "This logical artifact was withdrawn by an explicit reviewed successor." + else: + title = ( + cast(str, draft["title"]).replace("\r\n", "\n").replace("\r", "\n").strip() + ) + body = ( + cast(str, draft["body"]).replace("\r\n", "\n").replace("\r", "\n").rstrip() + ) + rendered = ( + b"---\n" + + _canonical(metadata, "invalid_collective_render") + + b"\n---\n# " + + title.encode() + + b"\n\n" + + body.encode() + + b"\n" + ) + if len(rendered) > MAX_PUBLICATION_BYTES: + raise CollectiveMemoryError("collective_publication_too_large") + if any(pattern.search(rendered) for pattern in _SECRET_PATTERNS): + raise CollectiveMemoryError("collective_secret_detected") + return rendered + + +def validate_publication_preview( + value: Any, + draft: Mapping[str, Any], + *, + expected_before: Mapping[str, Any] | None = None, +) -> dict[str, Any]: + code = "invalid_collective_publication_preview" + row = _closed(value, {"schema", "preview_id", "preview_hash", "body"}, code) + if row["schema"] != PUBLICATION_PREVIEW_SCHEMA: + raise CollectiveMemoryError("collective_unsupported_schema") + body = _closed( + row["body"], {"draft_hash", "state_hash", "before", "rendered"}, code + ) + if body["draft_hash"] != hashlib.sha256(_canonical(draft, code)).hexdigest(): + raise CollectiveMemoryError("collective_preview_draft_mismatch") + state_hash = _hash(body["state_hash"], code) + before = body["before"] + if before is not None: + before_row = _closed( + before, + {"receipt_id", "receipt_hash", "content_hash", "content_length", "state"}, + code, + ) + before = { + "receipt_id": _identifier(before_row["receipt_id"], code), + "receipt_hash": _hash(before_row["receipt_hash"], code), + "content_hash": _hash(before_row["content_hash"], code), + "content_length": _uint( + before_row["content_length"], + code, + minimum=1, + maximum=MAX_PUBLICATION_BYTES, + ), + "state": before_row["state"], + } + if before["state"] not in {"active", "tombstone"}: + raise CollectiveMemoryError(code) + if expected_before is not None and before != expected_before: + raise CollectiveMemoryError("collective_preview_predecessor_mismatch") + rendered_row = _closed( + body["rendered"], + {"content_hash", "content_length", "media_type", "bytes_b64"}, + code, + ) + if rendered_row["media_type"] != "text/markdown; charset=utf-8": + raise CollectiveMemoryError("collective_unsupported_media_type") + encoded = _text(rendered_row["bytes_b64"], code, maximum=1_398_104) + try: + rendered = unb64url(encoded) + except CanonicalError as exception: + raise CollectiveMemoryError(code) from exception + expected_rendered = _render_collective(draft) + if ( + rendered != expected_rendered + or rendered_row["content_hash"] != hashlib.sha256(rendered).hexdigest() + or rendered_row["content_length"] != len(rendered) + ): + raise CollectiveMemoryError("collective_preview_render_mismatch") + normalized_body = { + "draft_hash": body["draft_hash"], + "state_hash": state_hash, + "before": before, + "rendered": { + "content_hash": _hash(rendered_row["content_hash"], code), + "content_length": _uint( + rendered_row["content_length"], + code, + minimum=1, + maximum=MAX_PUBLICATION_BYTES, + ), + "media_type": rendered_row["media_type"], + "bytes_b64": encoded, + }, + } + preview_id, preview_hash = _content_id("cm:publication-preview:v1", normalized_body) + if row["preview_id"] != preview_id or row["preview_hash"] != preview_hash: + raise CollectiveMemoryError("collective_preview_identity_mismatch") + return { + "schema": PUBLICATION_PREVIEW_SCHEMA, + "preview_id": preview_id, + "preview_hash": preview_hash, + "body": normalized_body, + } + + +def evidence_issuer( + principal: str, + key: Ed25519PublicKey, + *, + valid_from_ms: int = 0, + valid_until_ms: int = MAX_SAFE_INTEGER, + revoked_at_ms: int | None = None, +) -> dict[str, Any]: + valid_from_ms = _uint(valid_from_ms, "invalid_collective_evidence_issuer") + valid_until_ms = _uint(valid_until_ms, "invalid_collective_evidence_issuer") + if valid_until_ms <= valid_from_ms: + raise CollectiveMemoryError("invalid_collective_evidence_issuer") + if revoked_at_ms is not None: + revoked_at_ms = _uint(revoked_at_ms, "invalid_collective_evidence_issuer") + return { + "principal": _identifier(principal, "invalid_collective_evidence_issuer"), + "public_key": b64url(key.public_bytes_raw()), + "kid": "ed25519:" + b64url(hashlib.sha256(key.public_bytes_raw()).digest()), + "valid_from_ms": valid_from_ms, + "valid_until_ms": valid_until_ms, + "revoked_at_ms": revoked_at_ms, + } + + +def _evidence_core( + draft: Mapping[str, Any], + preview: Mapping[str, Any], + *, + kind: str, + evidence_id: str, + issuer: str, + issued_at: str, + not_before: str, + not_after: str, +) -> dict[str, Any]: + if kind not in {"consent", "review"}: + raise CollectiveMemoryError("invalid_collective_evidence") + return { + "schema": PUBLICATION_EVIDENCE_SCHEMA, + "kind": kind, + "evidence_id": _identifier(evidence_id, "invalid_collective_evidence"), + "issuer": _identifier(issuer, "invalid_collective_evidence"), + "subject_id": draft["subject_id"], + "requester_id": draft["requester_id"], + "action": draft["action"], + "target_id": draft["target_id"], + "source_checkpoint": copy.deepcopy(draft["source_checkpoint"]), + "classification": draft["classification"], + "policy_version": draft["policy_version"], + "preview_hash": preview["preview_hash"], + "content_hash": preview["body"]["rendered"]["content_hash"], + "issued_at": _timestamp(issued_at, "invalid_collective_evidence"), + "not_before": _timestamp(not_before, "invalid_collective_evidence"), + "not_after": _timestamp(not_after, "invalid_collective_evidence"), + } + + +def sign_publication_evidence( + draft: Mapping[str, Any], + preview: Mapping[str, Any], + *, + kind: str, + evidence_id: str, + issuer: Mapping[str, Any], + private_key: Ed25519PrivateKey, + issued_at: str, + not_before: str, + not_after: str, +) -> dict[str, Any]: + if set(issuer) != { + "principal", + "public_key", + "kid", + "valid_from_ms", + "valid_until_ms", + "revoked_at_ms", + }: + raise CollectiveMemoryError("invalid_collective_evidence_issuer") + if b64url(private_key.public_key().public_bytes_raw()) != issuer["public_key"]: + raise CollectiveMemoryError("collective_evidence_key_mismatch") + body = _evidence_core( + draft, + preview, + kind=kind, + evidence_id=evidence_id, + issuer=issuer["principal"], + issued_at=issued_at, + not_before=not_before, + not_after=not_after, + ) + signature = private_key.sign(_canonical(body, "invalid_collective_evidence")) + return { + "schema": PUBLICATION_EVIDENCE_SCHEMA, + "body": body, + "signature": { + "alg": "Ed25519", + "kid": issuer["kid"], + "value": b64url(signature), + }, + } + + +def validate_publication_evidence( + value: Any, + draft: Mapping[str, Any], + preview: Mapping[str, Any], + *, + kind: str, + issuer: Mapping[str, Any], + at: str, +) -> tuple[dict[str, Any], str]: + code = "invalid_collective_evidence" + row = _closed(value, {"schema", "body", "signature"}, code) + if row["schema"] != PUBLICATION_EVIDENCE_SCHEMA: + raise CollectiveMemoryError("collective_unsupported_schema") + body = _closed( + row["body"], + { + "schema", + "kind", + "evidence_id", + "issuer", + "subject_id", + "requester_id", + "action", + "target_id", + "source_checkpoint", + "classification", + "policy_version", + "preview_hash", + "content_hash", + "issued_at", + "not_before", + "not_after", + }, + code, + ) + expected = _evidence_core( + draft, + preview, + kind=kind, + evidence_id=cast(str, body["evidence_id"]), + issuer=issuer["principal"], + issued_at=cast(str, body["issued_at"]), + not_before=cast(str, body["not_before"]), + not_after=cast(str, body["not_after"]), + ) + if dict(body) != expected: + raise CollectiveMemoryError("collective_evidence_binding_mismatch") + signature = _closed(row["signature"], {"alg", "kid", "value"}, code) + if signature["alg"] != "Ed25519" or signature["kid"] != issuer["kid"]: + raise CollectiveMemoryError(code) + try: + raw_signature = unb64url(cast(str, signature["value"]), length=64) + public = Ed25519PublicKey.from_public_bytes( + unb64url(issuer["public_key"], length=32) + ) + public.verify(raw_signature, _canonical(expected, code)) + except (CanonicalError, InvalidSignature, ValueError) as exception: + raise CollectiveMemoryError( + "collective_evidence_signature_invalid" + ) from exception + instant = dt.datetime.fromisoformat( + _timestamp(at, code).removesuffix("Z") + "+00:00" + ) + before = dt.datetime.fromisoformat( + expected["not_before"].removesuffix("Z") + "+00:00" + ) + after = dt.datetime.fromisoformat( + expected["not_after"].removesuffix("Z") + "+00:00" + ) + issued = dt.datetime.fromisoformat( + expected["issued_at"].removesuffix("Z") + "+00:00" + ) + if not before <= issued <= instant <= after: + raise CollectiveMemoryError("collective_evidence_expired") + issued_ms = int(issued.timestamp() * 1000) + instant_ms = int(instant.timestamp() * 1000) + if not ( + issuer["valid_from_ms"] <= issued_ms <= issuer["valid_until_ms"] + and issuer["valid_from_ms"] <= instant_ms <= issuer["valid_until_ms"] + ): + raise CollectiveMemoryError("collective_evidence_key_expired") + revoked_at_ms = issuer["revoked_at_ms"] + if revoked_at_ms is not None and ( + issued_ms >= revoked_at_ms or instant_ms >= revoked_at_ms + ): + raise CollectiveMemoryError("collective_evidence_key_revoked") + normalized = { + "schema": PUBLICATION_EVIDENCE_SCHEMA, + "body": expected, + "signature": dict(signature), + } + return normalized, hashlib.sha256(_canonical(normalized, code)).hexdigest() + + +def create_publication_request( + draft: Mapping[str, Any], + preview: Mapping[str, Any], + *, + idempotency_key: str, + consent: Mapping[str, Any], + review: Mapping[str, Any], +) -> dict[str, Any]: + return { + "schema": PUBLICATION_REQUEST_SCHEMA, + "draft": copy.deepcopy(dict(draft)), + "preview_hash": preview["preview_hash"], + "idempotency_key": _identifier(idempotency_key, "invalid_collective_request"), + "consent": copy.deepcopy(dict(consent)), + "review": copy.deepcopy(dict(review)), + } + + +def validate_publication_plan( + value: Any, + request: Mapping[str, Any], + preview: Mapping[str, Any], + *, + consent_hash: str, + review_hash: str, +) -> dict[str, Any]: + code = "invalid_collective_publication_plan" + row = _closed(value, {"schema", "plan_id", "plan_hash", "body"}, code) + if row["schema"] != PUBLICATION_PLAN_SCHEMA: + raise CollectiveMemoryError("collective_unsupported_schema") + body = _closed( + row["body"], + { + "request_hash", + "preview_hash", + "target_id", + "action", + "before", + "after", + "consent_hash", + "review_hash", + }, + code, + ) + draft = cast(Mapping[str, Any], request["draft"]) + after = _closed( + body["after"], {"content_hash", "content_length", "media_type", "state"}, code + ) + expected_body = { + "request_hash": hashlib.sha256(_canonical(request, code)).hexdigest(), + "preview_hash": preview["preview_hash"], + "target_id": draft["target_id"], + "action": draft["action"], + "before": preview["body"]["before"], + "after": { + "content_hash": preview["body"]["rendered"]["content_hash"], + "content_length": preview["body"]["rendered"]["content_length"], + "media_type": draft["media_type"], + "state": "tombstone" if draft["action"] == "tombstone" else "active", + }, + "consent_hash": consent_hash, + "review_hash": review_hash, + } + if dict(body) != expected_body or dict(after) != expected_body["after"]: + raise CollectiveMemoryError("collective_publication_plan_mismatch") + plan_id, plan_hash = _content_id("cm:publication-plan:v1", expected_body) + if row["plan_id"] != plan_id or row["plan_hash"] != plan_hash: + raise CollectiveMemoryError("collective_publication_plan_identity_mismatch") + return { + "schema": PUBLICATION_PLAN_SCHEMA, + "plan_id": plan_id, + "plan_hash": plan_hash, + "body": expected_body, + } + + +def _validate_target_record(value: Any, code: str) -> dict[str, Any] | None: + if value is None: + return None + row = _closed( + value, + {"receipt_id", "receipt_hash", "content_hash", "content_length", "state"}, + code, + ) + if row["state"] not in {"active", "tombstone"}: + raise CollectiveMemoryError(code) + return { + "receipt_id": _identifier(row["receipt_id"], code), + "receipt_hash": _hash(row["receipt_hash"], code), + "content_hash": _hash(row["content_hash"], code), + "content_length": _uint( + row["content_length"], code, minimum=1, maximum=MAX_PUBLICATION_BYTES + ), + "state": row["state"], + } + + +def _validate_publication_receipt_shape(value: Any) -> dict[str, Any]: + code = "invalid_collective_publication_receipt" + row = _closed(value, {"schema", "receipt_id", "receipt_hash", "body"}, code) + if row["schema"] != PUBLICATION_RECEIPT_SCHEMA: + raise CollectiveMemoryError("collective_unsupported_schema") + body = _closed( + row["body"], + { + "transaction_id", + "request_hash", + "plan_id", + "idempotency_key", + "target_id", + "action", + "before", + "after", + "source_refs", + "source_checkpoint", + "classification", + "policy_version", + "consent", + "review", + "projection", + "committed_at", + "status", + }, + code, + ) + if body["action"] not in {"publish", "successor", "tombstone"}: + raise CollectiveMemoryError(code) + before = _validate_target_record(body["before"], code) + if (body["action"] == "publish") != (before is None): + raise CollectiveMemoryError( + "collective_publication_receipt_predecessor_mismatch" + ) + after = _closed( + body["after"], {"content_hash", "content_length", "media_type", "state"}, code + ) + expected_state = "tombstone" if body["action"] == "tombstone" else "active" + if ( + after["media_type"] != "text/markdown; charset=utf-8" + or after["state"] != expected_state + ): + raise CollectiveMemoryError(code) + raw_refs = body["source_refs"] + if not isinstance(raw_refs, list) or not 1 <= len(raw_refs) <= MAX_SOURCE_REFS: + raise CollectiveMemoryError(code) + source_refs = [_source_ref(item, code) for item in raw_refs] + if source_refs != sorted(source_refs, key=lambda item: (item["id"], item["hash"])): + raise CollectiveMemoryError("collective_noncanonical_sources") + consent = _closed(body["consent"], {"evidence_id", "evidence_hash"}, code) + review = _closed(body["review"], {"evidence_id", "evidence_hash"}, code) + projection = _closed( + body["projection"], + {"index_generation", "ui_generation", "index_content_hash"}, + code, + ) + if body["status"] != "committed": + raise CollectiveMemoryError(code) + normalized_body = { + "transaction_id": _identifier(body["transaction_id"], code), + "request_hash": _hash(body["request_hash"], code), + "plan_id": _identifier(body["plan_id"], code), + "idempotency_key": _identifier(body["idempotency_key"], code), + "target_id": _identifier(body["target_id"], code), + "action": body["action"], + "before": before, + "after": { + "content_hash": _hash(after["content_hash"], code), + "content_length": _uint( + after["content_length"], + code, + minimum=1, + maximum=MAX_PUBLICATION_BYTES, + ), + "media_type": after["media_type"], + "state": after["state"], + }, + "source_refs": source_refs, + "source_checkpoint": _checkpoint(body["source_checkpoint"], code), + "classification": _identifier(body["classification"], code), + "policy_version": _identifier(body["policy_version"], code), + "consent": { + "evidence_id": _identifier(consent["evidence_id"], code), + "evidence_hash": _hash(consent["evidence_hash"], code), + }, + "review": { + "evidence_id": _identifier(review["evidence_id"], code), + "evidence_hash": _hash(review["evidence_hash"], code), + }, + "projection": { + "index_generation": _text( + projection["index_generation"], code, maximum=128 + ), + "ui_generation": _text(projection["ui_generation"], code, maximum=256), + "index_content_hash": _hash(projection["index_content_hash"], code), + }, + "committed_at": _timestamp(body["committed_at"], code), + "status": "committed", + } + receipt_id, receipt_hash = _content_id("cm:publication-receipt:v1", normalized_body) + if row["receipt_id"] != receipt_id or row["receipt_hash"] != receipt_hash: + raise CollectiveMemoryError("collective_publication_receipt_identity_mismatch") + return { + "schema": PUBLICATION_RECEIPT_SCHEMA, + "receipt_id": receipt_id, + "receipt_hash": receipt_hash, + "body": normalized_body, + } + + +def validate_publication_receipt( + value: Any, + request: Mapping[str, Any], + plan: Mapping[str, Any], + *, + consent_hash: str, + review_hash: str, +) -> dict[str, Any]: + code = "invalid_collective_publication_receipt" + row = _validate_publication_receipt_shape(value) + body = _closed( + row["body"], + { + "transaction_id", + "request_hash", + "plan_id", + "idempotency_key", + "target_id", + "action", + "before", + "after", + "source_refs", + "source_checkpoint", + "classification", + "policy_version", + "consent", + "review", + "projection", + "committed_at", + "status", + }, + code, + ) + draft = cast(Mapping[str, Any], request["draft"]) + before = _validate_target_record(body["before"], code) + after = _closed( + body["after"], {"content_hash", "content_length", "media_type", "state"}, code + ) + if after["state"] not in {"active", "tombstone"}: + raise CollectiveMemoryError(code) + normalized_after = { + "content_hash": _hash(after["content_hash"], code), + "content_length": _uint( + after["content_length"], code, minimum=1, maximum=MAX_PUBLICATION_BYTES + ), + "media_type": after["media_type"], + "state": after["state"], + } + consent = _closed(body["consent"], {"evidence_id", "evidence_hash"}, code) + review = _closed(body["review"], {"evidence_id", "evidence_hash"}, code) + projection = _closed( + body["projection"], + {"index_generation", "ui_generation", "index_content_hash"}, + code, + ) + expected_bindings = { + "request_hash": plan["body"]["request_hash"], + "plan_id": plan["plan_id"], + "idempotency_key": request["idempotency_key"], + "target_id": draft["target_id"], + "action": draft["action"], + "before": plan["body"]["before"], + "after": plan["body"]["after"], + "source_refs": draft["source_refs"], + "source_checkpoint": draft["source_checkpoint"], + "classification": draft["classification"], + "policy_version": draft["policy_version"], + "consent_hash": consent_hash, + "review_hash": review_hash, + } + observed = { + "request_hash": body["request_hash"], + "plan_id": body["plan_id"], + "idempotency_key": body["idempotency_key"], + "target_id": body["target_id"], + "action": body["action"], + "before": before, + "after": normalized_after, + "source_refs": body["source_refs"], + "source_checkpoint": body["source_checkpoint"], + "classification": body["classification"], + "policy_version": body["policy_version"], + "consent_hash": consent["evidence_hash"], + "review_hash": review["evidence_hash"], + } + if observed != expected_bindings: + raise CollectiveMemoryError("collective_publication_receipt_mismatch") + if ( + consent["evidence_id"] != request["consent"]["body"]["evidence_id"] + or review["evidence_id"] != request["review"]["body"]["evidence_id"] + or body["status"] != "committed" + ): + raise CollectiveMemoryError("collective_publication_receipt_mismatch") + normalized_body = { + "transaction_id": _identifier(body["transaction_id"], code), + "request_hash": _hash(body["request_hash"], code), + "plan_id": _identifier(body["plan_id"], code), + "idempotency_key": _identifier(body["idempotency_key"], code), + "target_id": _identifier(body["target_id"], code), + "action": body["action"], + "before": before, + "after": normalized_after, + "source_refs": [_source_ref(item, code) for item in body["source_refs"]], + "source_checkpoint": _checkpoint(body["source_checkpoint"], code), + "classification": _identifier(body["classification"], code), + "policy_version": _identifier(body["policy_version"], code), + "consent": { + "evidence_id": _identifier(consent["evidence_id"], code), + "evidence_hash": _hash(consent["evidence_hash"], code), + }, + "review": { + "evidence_id": _identifier(review["evidence_id"], code), + "evidence_hash": _hash(review["evidence_hash"], code), + }, + "projection": { + "index_generation": _text( + projection["index_generation"], code, maximum=128 + ), + "ui_generation": _text(projection["ui_generation"], code, maximum=256), + "index_content_hash": _hash(projection["index_content_hash"], code), + }, + "committed_at": _timestamp(body["committed_at"], code), + "status": "committed", + } + receipt_id, receipt_hash = _content_id("cm:publication-receipt:v1", normalized_body) + if row["receipt_id"] != receipt_id or row["receipt_hash"] != receipt_hash: + raise CollectiveMemoryError("collective_publication_receipt_identity_mismatch") + return { + "schema": PUBLICATION_RECEIPT_SCHEMA, + "receipt_id": receipt_id, + "receipt_hash": receipt_hash, + "body": normalized_body, + } + + +def validate_publication_reconciliation( + value: Any, receipt: Mapping[str, Any] +) -> dict[str, Any]: + code = "invalid_collective_publication_reconciliation" + row = _closed( + value, + {"schema", "receipt_id", "receipt_hash", "state_hash", "effect", "projection"}, + code, + ) + if ( + row["schema"] != PUBLICATION_RECONCILIATION_SCHEMA + or row["receipt_id"] != receipt["receipt_id"] + or row["receipt_hash"] != receipt["receipt_hash"] + or row["effect"] != "verified" + or row["projection"] != receipt["body"]["projection"] + ): + raise CollectiveMemoryError("collective_effect_truth_discrepancy") + _hash(row["state_hash"], code) + return dict(row) + + +def _assert_private_parent(path: Path) -> None: + parent = path.parent + parent.mkdir(mode=0o700, parents=True, exist_ok=True) + current = parent + while True: + try: + information = current.lstat() + except OSError as exception: + raise CollectiveMemoryError("collective_store_unavailable") from exception + if stat.S_ISLNK(information.st_mode) or not stat.S_ISDIR(information.st_mode): + raise CollectiveMemoryError("collective_store_unsafe") + if information.st_uid != os.getuid() or information.st_mode & 0o077: + raise CollectiveMemoryError("collective_store_permissions") + if current.parent == current: + break + if current == Path(parent.anchor): + break + # Only the adapter-created leaf hierarchy is required to be private; + # system ancestors such as /tmp and /home may be shared/traversable. + if current.parent == parent.parent: + break + current = current.parent + + +class _Store: + def __init__(self, path: str | Path) -> None: + self.path = Path(os.path.abspath(path)) + self.lock_path = self.path.with_suffix(self.path.suffix + ".lock") + + def _prepare(self) -> None: + _assert_private_parent(self.path) + for candidate in (self.path, self.lock_path): + if candidate.exists() or candidate.is_symlink(): + info = candidate.lstat() + if stat.S_ISLNK(info.st_mode) or not stat.S_ISREG(info.st_mode): + raise CollectiveMemoryError("collective_store_unsafe") + if info.st_uid != os.getuid() or info.st_mode & 0o077: + raise CollectiveMemoryError("collective_store_permissions") + + def connect(self) -> sqlite3.Connection: + self._prepare() + connection = sqlite3.connect(self.path, timeout=30, isolation_level=None) + connection.row_factory = sqlite3.Row + connection.execute("PRAGMA busy_timeout=30000") + connection.execute("PRAGMA foreign_keys=ON") + if ( + str(connection.execute("PRAGMA journal_mode=DELETE").fetchone()[0]).lower() + != "delete" + ): + connection.close() + raise CollectiveMemoryError("collective_store_journal_mode") + connection.execute("PRAGMA synchronous=FULL") + os.chmod(self.path, 0o600) + return connection + + @contextmanager + def exclusive(self) -> Iterator[None]: + self._prepare() + descriptor = os.open( + self.lock_path, + os.O_RDWR | os.O_CREAT | getattr(os, "O_NOFOLLOW", 0), + 0o600, + ) + try: + os.fchmod(descriptor, 0o600) + fcntl.flock(descriptor, fcntl.LOCK_EX) + yield + finally: + try: + fcntl.flock(descriptor, fcntl.LOCK_UN) + finally: + os.close(descriptor) + + +class CollectiveSourceStore(_Store): + """Append-only inbound source log; never reused by the publisher.""" + + def initialize(self) -> None: + with closing(self.connect()) as database: + database.executescript( + """ + CREATE TABLE IF NOT EXISTS metadata ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL + ) WITHOUT ROWID; + CREATE TABLE IF NOT EXISTS generations ( + generation_id TEXT PRIMARY KEY, + manifest_hash TEXT NOT NULL, + predecessor_generation TEXT, + state_digest TEXT NOT NULL, + source_log_hash TEXT NOT NULL, + preview_id TEXT NOT NULL UNIQUE, + preview_hash TEXT NOT NULL, + manifest_json BLOB NOT NULL, + state TEXT NOT NULL + CHECK(state IN ('prepared','active','superseded')), + import_event_id TEXT NOT NULL UNIQUE, + prepared_at_ms INTEGER NOT NULL, + import_event_hash TEXT, + receipt_json BLOB + ); + CREATE TABLE IF NOT EXISTS artifacts ( + generation_id TEXT NOT NULL REFERENCES generations(generation_id), + artifact_id TEXT NOT NULL, + logical_id TEXT NOT NULL, + descriptor_json BLOB NOT NULL, + content BLOB, + PRIMARY KEY(generation_id, artifact_id), + UNIQUE(generation_id, logical_id) + ); + CREATE TABLE IF NOT EXISTS errors ( + error_id TEXT PRIMARY KEY, + code TEXT NOT NULL, + retryable INTEGER NOT NULL, + occurred_at_ms INTEGER NOT NULL, + context_hash TEXT NOT NULL + ) WITHOUT ROWID; + """ + ) + expected = { + "adapter_id": SOURCE_ADAPTER_ID, + "schema_version": "1", + "upstream_commit": COLLECTIVE_MEMORY_COMMIT, + } + observed = { + str(row["key"]): str(row["value"]) + for row in database.execute("SELECT key,value FROM metadata") + } + if not observed: + database.executemany( + "INSERT INTO metadata(key,value) VALUES (?,?)", + sorted(expected.items()), + ) + elif observed != expected: + raise CollectiveMemoryError("collective_source_store_mismatch") + + def current(self) -> dict[str, Any] | None: + self.initialize() + with closing(self.connect()) as database: + row = database.execute( + "SELECT manifest_json FROM generations WHERE state='active'" + ).fetchone() + return None if row is None else json.loads(bytes(row["manifest_json"])) + + def artifact_heads(self) -> dict[str, dict[str, Any]]: + current = self.current() + if current is None: + return {} + return { + cast(str, item["logical_id"]): copy.deepcopy(item) + for item in current["body"]["artifacts"] + } + + def pending(self) -> list[dict[str, Any]]: + self.initialize() + with closing(self.connect()) as database: + rows = database.execute( + "SELECT manifest_json,preview_id,preview_hash,source_log_hash," + "import_event_id,prepared_at_ms FROM generations " + "WHERE state='prepared' " + "ORDER BY generation_id" + ).fetchall() + return [ + { + "manifest": json.loads(bytes(row["manifest_json"])), + "preview_id": row["preview_id"], + "preview_hash": row["preview_hash"], + "source_log_hash": row["source_log_hash"], + "import_event_id": row["import_event_id"], + "prepared_at_ms": int(row["prepared_at_ms"]), + } + for row in rows + ] + + def recorded_receipt(self, generation_id: str) -> dict[str, Any] | None: + self.initialize() + with closing(self.connect()) as database: + row = database.execute( + "SELECT receipt_json FROM generations WHERE generation_id=?", + (generation_id,), + ).fetchone() + if row is None or row["receipt_json"] is None: + return None + return cast(dict[str, Any], json.loads(bytes(row["receipt_json"]))) + + def prepare( + self, + preview: Mapping[str, Any], + contents: Mapping[str, bytes], + *, + prepared_at_ms: int, + ) -> dict[str, Any]: + self.initialize() + manifest = cast(Mapping[str, Any], preview["manifest"]) + generation_id = cast(str, manifest["generation_id"]) + with self.exclusive(), closing(self.connect()) as database: + database.execute("BEGIN IMMEDIATE") + try: + replay = database.execute( + "SELECT preview_hash,state,receipt_json FROM generations " + "WHERE generation_id=?", + (generation_id,), + ).fetchone() + if replay is not None: + if replay["preview_hash"] != preview["preview_hash"]: + raise CollectiveMemoryError("collective_generation_collision") + database.commit() + return { + "state": replay["state"], + "receipt": None + if replay["receipt_json"] is None + else json.loads(bytes(replay["receipt_json"])), + } + active = database.execute( + "SELECT generation_id,source_log_hash,manifest_json " + "FROM generations " + "WHERE state='active'" + ).fetchone() + predecessor = manifest["body"]["predecessor_generation"] + if (active is None and predecessor is not None) or ( + active is not None and predecessor != active["generation_id"] + ): + raise CollectiveMemoryError("collective_generation_fork") + previous_heads: dict[str, Mapping[str, Any]] = {} + previous_log_hash: str | None = None + if active is not None: + previous = json.loads(bytes(active["manifest_json"])) + previous_heads = { + item["logical_id"]: item + for item in previous["body"]["artifacts"] + } + previous_log_hash = cast(str, active["source_log_hash"]) + for item in manifest["body"]["artifacts"]: + prior = previous_heads.get(item["logical_id"]) + if prior is None and item["predecessor_artifact_id"] is not None: + raise CollectiveMemoryError("collective_dangling_predecessor") + if prior is not None: + if item["artifact_id"] == prior["artifact_id"]: + if item != prior: + raise CollectiveMemoryError( + "collective_artifact_collision" + ) + elif item["predecessor_artifact_id"] != prior["artifact_id"]: + raise CollectiveMemoryError("collective_artifact_fork") + if set(previous_heads) - { + item["logical_id"] for item in manifest["body"]["artifacts"] + }: + raise CollectiveMemoryError("collective_implicit_removal") + source_log_core = { + "previous_source_log_hash": previous_log_hash, + "generation_id": generation_id, + "manifest_hash": manifest["manifest_hash"], + "state_digest": manifest["body"]["state_digest"], + "artifacts": [ + { + "artifact_id": item["artifact_id"], + "logical_id": item["logical_id"], + "state": item["state"], + "content_hash": item["content_hash"], + } + for item in manifest["body"]["artifacts"] + ], + } + source_log_hash = hashlib.sha256( + _canonical(source_log_core, "invalid_collective_source_log") + ).hexdigest() + event_id = str( + uuid.uuid5(uuid.NAMESPACE_URL, cast(str, preview["preview_id"])) + ) + database.execute( + "INSERT INTO generations VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)", + ( + generation_id, + manifest["manifest_hash"], + predecessor, + manifest["body"]["state_digest"], + source_log_hash, + preview["preview_id"], + preview["preview_hash"], + _canonical(manifest, "invalid_collective_export_manifest"), + "prepared", + event_id, + _uint(prepared_at_ms, "invalid_collective_clock"), + None, + None, + ), + ) + for item in manifest["body"]["artifacts"]: + content = None + if item["state"] == "active": + content = contents.get(item["artifact_id"]) + if content is None: + raise CollectiveMemoryError("collective_content_missing") + database.execute( + "INSERT INTO artifacts VALUES (?,?,?,?,?)", + ( + generation_id, + item["artifact_id"], + item["logical_id"], + _canonical(item, "invalid_collective_export_artifact"), + content, + ), + ) + database.commit() + return { + "state": "prepared", + "receipt": None, + "source_log_hash": source_log_hash, + "import_event_id": event_id, + } + except BaseException: + database.rollback() + raise + + def finalize( + self, generation_id: str, receipt: Mapping[str, Any], event: Mapping[str, Any] + ) -> None: + self.initialize() + with self.exclusive(), closing(self.connect()) as database: + database.execute("BEGIN IMMEDIATE") + try: + row = database.execute( + "SELECT state,import_event_id,preview_hash FROM generations " + "WHERE generation_id=?", + (generation_id,), + ).fetchone() + if row is None: + raise CollectiveMemoryError("collective_generation_unknown") + if row["import_event_id"] != event["event_id"]: + raise CollectiveMemoryError("collective_import_event_mismatch") + if row["state"] == "active": + existing = database.execute( + "SELECT receipt_json,import_event_hash FROM generations " + "WHERE generation_id=?", + (generation_id,), + ).fetchone() + if ( + json.loads(bytes(existing["receipt_json"])) != receipt + or existing["import_event_hash"] != event["content_hash"] + ): + raise CollectiveMemoryError("collective_source_replay_mismatch") + database.commit() + return + database.execute( + "UPDATE generations SET state='superseded' WHERE state='active'" + ) + database.execute( + "UPDATE generations SET state='active',import_event_hash=?," + "receipt_json=? " + "WHERE generation_id=? AND state='prepared'", + ( + event["content_hash"], + _canonical(receipt, "invalid_collective_source_receipt"), + generation_id, + ), + ) + if database.total_changes < 1: + raise CollectiveMemoryError("collective_source_state_conflict") + database.commit() + except BaseException: + database.rollback() + raise + + def rebuild_projection( + self, ledger: Ledger, *, repair: bool = True + ) -> dict[str, Any]: + """Rebuild the active pointer from the immutable source log and ledger. + + Generation manifests, descriptors, content bytes and source-log hashes are + authoritative local source evidence. Matrix ``source.imported`` events + decide which durable prefix was accepted. This operation repairs only + derived state/receipt columns and refuses contradictory source evidence. + """ + + self.initialize() + with self.exclusive(), closing(self.connect()) as database: + database.execute("BEGIN IMMEDIATE") + try: + rows = database.execute( + "SELECT * FROM generations ORDER BY generation_id" + ).fetchall() + if not rows: + database.commit() + empty = { + "accepted_generation_count": 0, + "generation_count": 0, + "head_generation_id": None, + "source_log_head": None, + } + return { + "schema": "dm.collective-source.rebuild/v1", + **empty, + "state_hash": hashlib.sha256( + _canonical(empty, "invalid_collective_source_rebuild") + ).hexdigest(), + } + + decoded: dict[str, tuple[sqlite3.Row, dict[str, Any]]] = {} + children: dict[str | None, list[str]] = {} + for row in rows: + manifest = validate_export_manifest( + json.loads(bytes(row["manifest_json"])) + ) + generation_id = cast(str, manifest["generation_id"]) + body = cast(Mapping[str, Any], manifest["body"]) + predecessor = cast(str | None, body["predecessor_generation"]) + if generation_id in decoded or any( + ( + row["generation_id"] != generation_id, + row["manifest_hash"] != manifest["manifest_hash"], + row["predecessor_generation"] != predecessor, + row["state_digest"] != body["state_digest"], + ) + ): + raise CollectiveMemoryError("collective_source_log_drift") + bindings = [ + { + "artifact_id": item["artifact_id"], + "content_hash": item["content_hash"], + "content_length": item["content_length"], + "state": item["state"], + } + for item in body["artifacts"] + ] + preview_core = { + "manifest": manifest, + "content_bindings": bindings, + } + if ( + row["preview_id"] + != _derived( + "dm:collective-source-preview:v1:", + SOURCE_PREVIEW_DOMAIN, + preview_core, + ) + or row["preview_hash"] + != hashlib.sha256( + _canonical( + preview_core, "invalid_collective_source_preview" + ) + ).hexdigest() + ): + raise CollectiveMemoryError("collective_source_log_drift") + decoded[generation_id] = (row, manifest) + children.setdefault(predecessor, []).append(generation_id) + + roots = children.get(None, []) + if len(roots) != 1: + raise CollectiveMemoryError("collective_source_log_fork") + ordered: list[str] = [] + candidate = roots[0] + while True: + if candidate in ordered: + raise CollectiveMemoryError("collective_source_log_cycle") + ordered.append(candidate) + successors = children.get(candidate, []) + if len(successors) > 1: + raise CollectiveMemoryError("collective_source_log_fork") + if not successors: + break + candidate = successors[0] + if len(ordered) != len(decoded): + raise CollectiveMemoryError("collective_source_log_disconnected") + + previous_source_log_hash: str | None = None + accepted: list[tuple[str, dict[str, Any], Event]] = [] + prepared: list[str] = [] + missing_event_seen = False + for generation_id in ordered: + row, manifest = decoded[generation_id] + body = cast(Mapping[str, Any], manifest["body"]) + artifact_rows = database.execute( + "SELECT artifact_id,logical_id,descriptor_json,content " + "FROM artifacts WHERE generation_id=? ORDER BY artifact_id", + (generation_id,), + ).fetchall() + artifacts = cast(list[Mapping[str, Any]], body["artifacts"]) + if len(artifact_rows) != len(artifacts): + raise CollectiveMemoryError("collective_source_log_drift") + stored_artifacts = { + cast(str, item["artifact_id"]): item for item in artifact_rows + } + for descriptor in artifacts: + stored = stored_artifacts.get( + cast(str, descriptor["artifact_id"]) + ) + if ( + stored is None + or stored["logical_id"] != descriptor["logical_id"] + or json.loads(bytes(stored["descriptor_json"])) + != descriptor + ): + raise CollectiveMemoryError("collective_source_log_drift") + content = stored["content"] + if descriptor["state"] == "tombstone": + if content is not None: + raise CollectiveMemoryError( + "collective_source_log_drift" + ) + elif ( + content is None + or len(bytes(content)) != descriptor["content_length"] + or hashlib.sha256(bytes(content)).hexdigest() + != descriptor["content_hash"] + ): + raise CollectiveMemoryError("collective_source_log_drift") + + source_log_core = { + "previous_source_log_hash": previous_source_log_hash, + "generation_id": generation_id, + "manifest_hash": manifest["manifest_hash"], + "state_digest": body["state_digest"], + "artifacts": [ + { + "artifact_id": item["artifact_id"], + "logical_id": item["logical_id"], + "state": item["state"], + "content_hash": item["content_hash"], + } + for item in artifacts + ], + } + observed_source_log_hash = hashlib.sha256( + _canonical(source_log_core, "invalid_collective_source_log") + ).hexdigest() + if row["source_log_hash"] != observed_source_log_hash: + raise CollectiveMemoryError("collective_source_log_drift") + previous_source_log_hash = observed_source_log_hash + + event = ledger.event(cast(str, row["import_event_id"])) + if event is None: + missing_event_seen = True + if ( + row["receipt_json"] is not None + or row["import_event_hash"] is not None + ): + raise CollectiveMemoryError("collective_source_log_drift") + prepared.append(generation_id) + continue + if missing_event_seen or event["kind"] != "source.imported": + raise CollectiveMemoryError("collective_source_log_event_drift") + receipt = validate_source_receipt( + _source_receipt( + manifest, + preview_id=cast(str, row["preview_id"]), + preview_hash=cast(str, row["preview_hash"]), + source_log_hash=observed_source_log_hash, + import_event_id=cast(str, row["import_event_id"]), + imported_at_ms=int(row["prepared_at_ms"]), + ) + ) + if ( + event["payload"] != receipt + or event["subject"] != ledger.authority.manifest.being_ref + or ( + row["receipt_json"] is not None + and json.loads(bytes(row["receipt_json"])) != receipt + ) + or ( + row["import_event_hash"] is not None + and row["import_event_hash"] != event["content_hash"] + ) + ): + raise CollectiveMemoryError("collective_source_log_event_drift") + accepted.append((generation_id, receipt, event)) + + if len(prepared) > 1: + raise CollectiveMemoryError( + "collective_source_log_uncommitted_tail" + ) + head = None if not accepted else accepted[-1][0] + if repair: + database.execute("UPDATE generations SET state='prepared'") + for generation_id, receipt, event in accepted: + database.execute( + "UPDATE generations SET state='superseded'," + "receipt_json=?,import_event_hash=? WHERE generation_id=?", + ( + _canonical( + receipt, "invalid_collective_source_receipt" + ), + event["content_hash"], + generation_id, + ), + ) + if head is not None: + database.execute( + "UPDATE generations SET state='active' " + "WHERE generation_id=?", + (head,), + ) + else: + accepted_by_id = { + generation_id: (receipt, event) + for generation_id, receipt, event in accepted + } + for generation_id in ordered: + row, _manifest = decoded[generation_id] + expected_state = ( + "prepared" + if generation_id not in accepted_by_id + else "active" + if generation_id == head + else "superseded" + ) + if row["state"] != expected_state: + raise CollectiveMemoryError( + "collective_source_effect_truth_discrepancy" + ) + accepted_row = accepted_by_id.get(generation_id) + if accepted_row is not None: + receipt, event = accepted_row + if ( + row["receipt_json"] is None + or row["import_event_hash"] is None + or json.loads(bytes(row["receipt_json"])) != receipt + or row["import_event_hash"] != event["content_hash"] + ): + raise CollectiveMemoryError( + "collective_source_effect_truth_discrepancy" + ) + state = { + "accepted_generation_count": len(accepted), + "generation_count": len(ordered), + "head_generation_id": head, + "source_log_head": previous_source_log_hash, + } + database.commit() + return { + "schema": "dm.collective-source.rebuild/v1", + **state, + "state_hash": hashlib.sha256( + _canonical(state, "invalid_collective_source_rebuild") + ).hexdigest(), + } + except BaseException: + database.rollback() + raise + + def record_error( + self, + code: str, + *, + retryable: bool, + occurred_at_ms: int, + context: Mapping[str, Any], + ) -> None: + self.initialize() + context_hash = hashlib.sha256( + _canonical(context, "invalid_collective_error") + ).hexdigest() + error_id = "dm:collective-error:v1:" + b64url( + hashlib.sha256(f"{code}:{occurred_at_ms}:{context_hash}".encode()).digest() + ) + with closing(self.connect()) as database: + database.execute( + "INSERT OR IGNORE INTO errors VALUES (?,?,?,?,?)", + (error_id, code, int(retryable), occurred_at_ms, context_hash), + ) + + +def validate_source_preview(value: Any) -> dict[str, Any]: + code = "invalid_collective_source_preview" + row = _closed( + value, + {"schema", "preview_id", "preview_hash", "manifest", "content_bindings"}, + code, + ) + if row["schema"] != SOURCE_PREVIEW_SCHEMA: + raise CollectiveMemoryError(code) + manifest = validate_export_manifest(row["manifest"]) + raw = row["content_bindings"] + if not isinstance(raw, list): + raise CollectiveMemoryError(code) + bindings: list[dict[str, Any]] = [] + for item in raw: + binding = _closed( + item, {"artifact_id", "content_hash", "content_length", "state"}, code + ) + if binding["state"] not in {"active", "tombstone"}: + raise CollectiveMemoryError(code) + bindings.append( + { + "artifact_id": _identifier(binding["artifact_id"], code), + "content_hash": _nullable_hash(binding["content_hash"], code), + "content_length": _uint( + binding["content_length"], code, maximum=MAX_ARTIFACT_BYTES + ), + "state": binding["state"], + } + ) + expected = [ + { + "artifact_id": item["artifact_id"], + "content_hash": item["content_hash"], + "content_length": item["content_length"], + "state": item["state"], + } + for item in manifest["body"]["artifacts"] + ] + if bindings != expected: + raise CollectiveMemoryError("collective_source_preview_manifest_mismatch") + core = {"manifest": manifest, "content_bindings": bindings} + preview_id = _derived( + "dm:collective-source-preview:v1:", SOURCE_PREVIEW_DOMAIN, core + ) + preview_hash = hashlib.sha256(_canonical(core, code)).hexdigest() + if row["preview_id"] != preview_id or row["preview_hash"] != preview_hash: + raise CollectiveMemoryError("collective_source_preview_identity_mismatch") + return { + "schema": SOURCE_PREVIEW_SCHEMA, + "preview_id": preview_id, + "preview_hash": preview_hash, + **core, + } + + +def _source_receipt( + manifest: Mapping[str, Any], + *, + preview_id: str, + preview_hash: str, + source_log_hash: str, + import_event_id: str, + imported_at_ms: int, +) -> dict[str, Any]: + active = sum(item["state"] == "active" for item in manifest["body"]["artifacts"]) + tombstones = manifest["body"]["artifact_count"] - active + body = { + "adapter_id": SOURCE_ADAPTER_ID, + "importer_version": SOURCE_IMPORTER_VERSION, + "producer_instance": manifest["body"]["producer_instance"], + "producer_release": manifest["body"]["producer_release"], + "policy_version": manifest["body"]["policy_version"], + "scope_id": manifest["body"]["scope_id"], + "generation_id": manifest["generation_id"], + "manifest_hash": manifest["manifest_hash"], + "predecessor_generation": manifest["body"]["predecessor_generation"], + "state_digest": manifest["body"]["state_digest"], + "artifact_count": manifest["body"]["artifact_count"], + "total_content_bytes": manifest["body"]["total_content_bytes"], + "outcomes": { + "admitted_to_quarantine": active, + "tombstoned": tombstones, + "personal_memory_assertions": 0, + }, + "preview_id": preview_id, + "preview_hash": preview_hash, + "source_log_hash": source_log_hash, + "import_event_id": import_event_id, + "imported_at_ms": _uint(imported_at_ms, "invalid_collective_clock"), + "decision": "quarantined", + "reason_codes": ["quarantined:initial-pull"], + } + receipt_id = _derived( + "dm:collective-source-receipt:v1:", SOURCE_RECEIPT_DOMAIN, body + ) + return {"schema": SOURCE_RECEIPT_SCHEMA, "receipt_id": receipt_id, "body": body} + + +def validate_source_receipt(value: Any) -> dict[str, Any]: + code = "invalid_collective_source_receipt" + row = _closed(value, {"schema", "receipt_id", "body"}, code) + if row["schema"] != SOURCE_RECEIPT_SCHEMA: + raise CollectiveMemoryError(code) + body = _closed( + row["body"], + { + "adapter_id", + "importer_version", + "producer_instance", + "producer_release", + "policy_version", + "scope_id", + "generation_id", + "manifest_hash", + "predecessor_generation", + "state_digest", + "artifact_count", + "total_content_bytes", + "outcomes", + "preview_id", + "preview_hash", + "source_log_hash", + "import_event_id", + "imported_at_ms", + "decision", + "reason_codes", + }, + code, + ) + if ( + body["adapter_id"] != SOURCE_ADAPTER_ID + or body["importer_version"] != SOURCE_IMPORTER_VERSION + or body["decision"] != "quarantined" + or body["reason_codes"] != ["quarantined:initial-pull"] + ): + raise CollectiveMemoryError(code) + outcomes = _closed( + body["outcomes"], + {"admitted_to_quarantine", "tombstoned", "personal_memory_assertions"}, + code, + ) + artifact_count = _uint(body["artifact_count"], code, maximum=MAX_ARTIFACTS) + active = _uint(outcomes["admitted_to_quarantine"], code, maximum=MAX_ARTIFACTS) + tombstones = _uint(outcomes["tombstoned"], code, maximum=MAX_ARTIFACTS) + if ( + active + tombstones != artifact_count + or outcomes["personal_memory_assertions"] != 0 + ): + raise CollectiveMemoryError(code) + normalized_body = { + **dict(body), + "producer_instance": _identifier(body["producer_instance"], code), + "producer_release": _identifier(body["producer_release"], code), + "policy_version": _identifier(body["policy_version"], code), + "scope_id": _identifier(body["scope_id"], code), + "generation_id": _identifier(body["generation_id"], code), + "manifest_hash": _hash(body["manifest_hash"], code), + "predecessor_generation": _nullable_identifier( + body["predecessor_generation"], code + ), + "state_digest": _hash(body["state_digest"], code), + "artifact_count": artifact_count, + "total_content_bytes": _uint( + body["total_content_bytes"], code, maximum=MAX_EXPORT_BYTES + ), + "outcomes": { + "admitted_to_quarantine": active, + "tombstoned": tombstones, + "personal_memory_assertions": 0, + }, + "preview_id": _identifier(body["preview_id"], code), + "preview_hash": _hash(body["preview_hash"], code), + "source_log_hash": _hash(body["source_log_hash"], code), + "import_event_id": _uuid(body["import_event_id"], code), + "imported_at_ms": _uint(body["imported_at_ms"], code), + } + expected_id = _derived( + "dm:collective-source-receipt:v1:", SOURCE_RECEIPT_DOMAIN, normalized_body + ) + if row["receipt_id"] != expected_id: + raise CollectiveMemoryError("collective_source_receipt_identity_mismatch") + return { + "schema": SOURCE_RECEIPT_SCHEMA, + "receipt_id": expected_id, + "body": normalized_body, + } + + +@dataclass(frozen=True) +class CollectiveSourceAdapter: + ledger: Ledger + profile: Mapping[str, Any] + transport: SourceTransport + store: CollectiveSourceStore + signer: EventSigner + clock: Clock + fault: Fault = _no_fault + + def __post_init__(self) -> None: + profile = validate_source_profile(self.profile) + object.__setattr__(self, "profile", profile) + self.store.initialize() + + def _fetch( + self, generation_id: str | None = None + ) -> tuple[dict[str, Any], dict[str, bytes]]: + try: + manifest_raw = self.transport( + "manifest", + { + "scope_id": self.profile["scope_id"], + "generation_id": generation_id, + }, + ) + manifest = validate_export_manifest(manifest_raw) + if generation_id is not None and manifest["generation_id"] != generation_id: + raise CollectiveMemoryError("collective_mixed_generation") + body = manifest["body"] + for field in ( + "producer_instance", + "producer_release", + "policy_version", + "scope_id", + ): + if body[field] != self.profile[field]: + raise CollectiveMemoryError("collective_source_profile_mismatch") + artifacts: list[dict[str, Any]] = [] + cursor: str | None = None + offset = 0 + seen: set[str] = set() + while offset < body["artifact_count"] or (offset == 0 and not artifacts): + request = { + "generation_id": manifest["generation_id"], + "cursor": cursor, + "limit": MAX_PAGE, + } + page_raw = self.transport("page", request) + page = validate_export_page( + page_raw, + manifest, + expected_offset=offset, + expected_limit=MAX_PAGE, + ) + page_artifacts = cast(list[dict[str, Any]], page["artifacts"]) + artifacts.extend(page_artifacts) + offset += len(page_artifacts) + next_cursor = cast(str | None, page["next_cursor"]) + if next_cursor is None: + break + if next_cursor in seen or not page_artifacts: + raise CollectiveMemoryError("collective_cursor_cycle") + seen.add(next_cursor) + cursor = next_cursor + if artifacts != body["artifacts"]: + raise CollectiveMemoryError("collective_partial_or_mixed_generation") + contents: dict[str, bytes] = {} + for item in artifacts: + if item["state"] == "tombstone": + continue + raw = self.transport( + "object", + { + "generation_id": manifest["generation_id"], + "content_ref": item["content_ref"], + }, + ) + if not isinstance(raw, bytes): + raise CollectiveMemoryError("collective_invalid_content_response") + if ( + len(raw) != item["content_length"] + or hashlib.sha256(raw).hexdigest() != item["content_hash"] + ): + raise CollectiveMemoryError("collective_content_mismatch") + try: + raw.decode("utf-8") + except UnicodeDecodeError as exception: + raise CollectiveMemoryError( + "collective_content_not_utf8" + ) from exception + existing = contents.get(item["artifact_id"]) + if existing is not None and existing != raw: + raise CollectiveMemoryError("collective_artifact_collision") + contents[item["artifact_id"]] = raw + return manifest, contents + except CollectiveMemoryError: + raise + except (ConnectionError, TimeoutError, OSError) as exception: + raise CollectiveMemoryError( + "collective_source_unavailable", retryable=True + ) from exception + except Exception as exception: + raise CollectiveMemoryError( + "collective_source_invalid_response" + ) from exception + + @staticmethod + def _preview_value(manifest: Mapping[str, Any]) -> dict[str, Any]: + bindings = [ + { + "artifact_id": item["artifact_id"], + "content_hash": item["content_hash"], + "content_length": item["content_length"], + "state": item["state"], + } + for item in manifest["body"]["artifacts"] + ] + core = {"manifest": manifest, "content_bindings": bindings} + preview = { + "schema": SOURCE_PREVIEW_SCHEMA, + "preview_id": _derived( + "dm:collective-source-preview:v1:", SOURCE_PREVIEW_DOMAIN, core + ), + "preview_hash": hashlib.sha256( + _canonical(core, "invalid_collective_source_preview") + ).hexdigest(), + **core, + } + return validate_source_preview(preview) + + def preview(self, generation_id: str | None = None) -> dict[str, Any]: + manifest, _contents = self._fetch(generation_id) + current = self.store.current() + if current is None: + if manifest["body"]["predecessor_generation"] is not None: + raise CollectiveMemoryError("collective_generation_gap") + elif ( + manifest["generation_id"] != current["generation_id"] + and manifest["body"]["predecessor_generation"] != current["generation_id"] + ): + raise CollectiveMemoryError("collective_generation_gap") + return self._preview_value(manifest) + + def preview_catch_up(self) -> list[dict[str, Any]]: + latest, _contents = self._fetch() + current = self.store.current() + stop = None if current is None else current["generation_id"] + if latest["generation_id"] == stop: + return [] + reverse: list[dict[str, Any]] = [] + seen: set[str] = set() + candidate = latest + while candidate["generation_id"] != stop: + generation_id = cast(str, candidate["generation_id"]) + if generation_id in seen or len(reverse) >= MAX_ARTIFACTS: + raise CollectiveMemoryError("collective_generation_cycle") + seen.add(generation_id) + reverse.append(candidate) + predecessor = candidate["body"]["predecessor_generation"] + if predecessor is None: + if stop is not None: + raise CollectiveMemoryError("collective_generation_gap") + break + candidate, _contents = self._fetch(predecessor) + if ( + reverse[-1]["body"]["predecessor_generation"] + != candidate["generation_id"] + ): + raise CollectiveMemoryError("collective_generation_fork") + chain = list(reversed(reverse)) + predecessor = stop + for manifest in chain: + if manifest["body"]["predecessor_generation"] != predecessor: + raise CollectiveMemoryError("collective_generation_fork") + predecessor = manifest["generation_id"] + return [self._preview_value(manifest) for manifest in chain] + + def catch_up(self) -> list[dict[str, Any]]: + return [self.apply(preview) for preview in self.preview_catch_up()] + + def apply(self, preview: Mapping[str, Any]) -> dict[str, Any]: + try: + return self._apply(preview) + except CollectiveMemoryError as exception: + self.store.record_error( + exception.code, + retryable=exception.retryable, + occurred_at_ms=self.clock(), + context={"operation": "apply", "scope_id": self.profile["scope_id"]}, + ) + raise + + def _apply(self, preview: Mapping[str, Any]) -> dict[str, Any]: + normalized = validate_source_preview(preview) + recorded = self.store.recorded_receipt(normalized["manifest"]["generation_id"]) + if recorded is not None: + recorded = validate_source_receipt(recorded) + self.reconcile(recorded) + return recorded + fresh_manifest, contents = self._fetch(normalized["manifest"]["generation_id"]) + if fresh_manifest != normalized["manifest"]: + raise CollectiveMemoryError("collective_source_preview_stale") + fresh = self._preview_value(fresh_manifest) + if fresh != normalized: + raise CollectiveMemoryError("collective_source_preview_stale") + prepared_at = self.clock() + prepared = self.store.prepare(normalized, contents, prepared_at_ms=prepared_at) + if prepared.get("receipt") is not None: + return validate_source_receipt(prepared["receipt"]) + self.fault("source-prepared") + return self._commit_pending( + { + "manifest": normalized["manifest"], + "preview_id": normalized["preview_id"], + "preview_hash": normalized["preview_hash"], + "source_log_hash": prepared["source_log_hash"], + "import_event_id": prepared["import_event_id"], + "prepared_at_ms": prepared_at, + } + ) + + def _commit_pending(self, pending: Mapping[str, Any]) -> dict[str, Any]: + manifest = pending["manifest"] + receipt = _source_receipt( + manifest, + preview_id=pending["preview_id"], + preview_hash=pending["preview_hash"], + source_log_hash=pending["source_log_hash"], + import_event_id=pending["import_event_id"], + imported_at_ms=pending["prepared_at_ms"], + ) + receipt = validate_source_receipt(receipt) + event = self.ledger.append_local_idempotent( + client_id="collective-source-v1", + request_id=pending["import_event_id"], + request_hash=pending["preview_hash"], + kind="source.imported", + subject=self.ledger.authority.manifest.being_ref, + payload=receipt, + signer=self.signer, + sensitivity="shareable" + if manifest["body"]["scope_id"] == "public" + else "private", + occurred_at_ms=pending["prepared_at_ms"], + event_id=pending["import_event_id"], + ) + self.fault("source-ledger-appended") + self.store.finalize(manifest["generation_id"], receipt, event) + self.fault("source-activated") + return receipt + + def recover(self) -> list[dict[str, Any]]: + results: list[dict[str, Any]] = [] + for pending in self.store.pending(): + receipt = self._commit_pending(pending) + results.append( + { + "generation_id": pending["manifest"]["generation_id"], + "receipt_id": receipt["receipt_id"], + "outcome": "activated", + } + ) + return results + + def rebuild(self) -> dict[str, Any]: + """Rebuild the derived active generation without remote input.""" + + return self.store.rebuild_projection(self.ledger) + + def reconcile(self, receipt: Mapping[str, Any]) -> dict[str, Any]: + normalized = validate_source_receipt(receipt) + self.store.rebuild_projection(self.ledger, repair=False) + current = self.store.current() + if ( + current is None + or current["generation_id"] != normalized["body"]["generation_id"] + ): + raise CollectiveMemoryError("collective_source_effect_truth_discrepancy") + stored = self.store.recorded_receipt(current["generation_id"]) + if stored != normalized: + raise CollectiveMemoryError("collective_source_effect_truth_discrepancy") + event = self.ledger.event(normalized["body"]["import_event_id"]) + if ( + event is None + or event["kind"] != "source.imported" + or event["payload"] != normalized + ): + raise CollectiveMemoryError("collective_source_effect_truth_discrepancy") + return { + "schema": "dm.collective-source.reconciliation/v1", + "receipt_id": normalized["receipt_id"], + "generation_id": current["generation_id"], + "effect": "verified", + } + + +class CollectivePublisherJournal(_Store): + """Outbound-only queue and recovery journal.""" + + def initialize(self) -> None: + with closing(self.connect()) as database: + database.executescript( + """ + CREATE TABLE IF NOT EXISTS metadata ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL + ) WITHOUT ROWID; + CREATE TABLE IF NOT EXISTS requests ( + request_id TEXT PRIMARY KEY, + request_hash TEXT NOT NULL, + idempotency_key TEXT NOT NULL UNIQUE, + target_id TEXT NOT NULL, + request_json BLOB NOT NULL, + preview_json BLOB NOT NULL, + plan_json BLOB NOT NULL, + consent_hash TEXT NOT NULL, + review_hash TEXT NOT NULL, + request_event_id TEXT NOT NULL UNIQUE, + request_event_hash TEXT, + provider_receipt_json BLOB, + reconciliation_json BLOB, + acceptance_json BLOB, + acceptance_event_id TEXT UNIQUE, + acceptance_event_hash TEXT, + state TEXT NOT NULL + CHECK(state IN ('prepared','queued','effected','accepted')), + created_at_ms INTEGER NOT NULL + ); + CREATE UNIQUE INDEX IF NOT EXISTS one_pending_target + ON requests(target_id) + WHERE state IN ('prepared','queued','effected'); + """ + ) + expected = { + "adapter_id": PUBLISHER_ADAPTER_ID, + "schema_version": "1", + "upstream_commit": COLLECTIVE_MEMORY_COMMIT, + } + observed = { + str(row["key"]): str(row["value"]) + for row in database.execute("SELECT key,value FROM metadata") + } + if not observed: + database.executemany( + "INSERT INTO metadata(key,value) VALUES (?,?)", + sorted(expected.items()), + ) + elif observed != expected: + raise CollectiveMemoryError("collective_publisher_store_mismatch") + + @staticmethod + def _decode(row: sqlite3.Row) -> dict[str, Any]: + result: dict[str, Any] = dict(row) + for field in ( + "request_json", + "preview_json", + "plan_json", + "provider_receipt_json", + "reconciliation_json", + "acceptance_json", + ): + if result[field] is not None: + result[field.removesuffix("_json")] = json.loads(bytes(result[field])) + result.pop(field) + return result + + def prepare( + self, + *, + request_id: str, + request: Mapping[str, Any], + preview: Mapping[str, Any], + plan: Mapping[str, Any], + consent_hash: str, + review_hash: str, + request_event_id: str, + created_at_ms: int, + ) -> dict[str, Any]: + self.initialize() + request_hash = hashlib.sha256( + _canonical(request, "invalid_collective_publication_request") + ).hexdigest() + with self.exclusive(), closing(self.connect()) as database: + database.execute("BEGIN IMMEDIATE") + try: + replay = database.execute( + "SELECT * FROM requests WHERE idempotency_key=?", + (request["idempotency_key"],), + ).fetchone() + if replay is not None: + decoded = self._decode(replay) + if ( + decoded["request_hash"] != request_hash + or decoded["request_id"] != request_id + ): + raise CollectiveMemoryError("collective_idempotency_conflict") + database.commit() + return decoded + database.execute( + "INSERT INTO requests VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", + ( + request_id, + request_hash, + request["idempotency_key"], + request["draft"]["target_id"], + _canonical(request, "invalid_collective_publication_request"), + _canonical(preview, "invalid_collective_publication_preview"), + _canonical(plan, "invalid_collective_publication_plan"), + consent_hash, + review_hash, + request_event_id, + None, + None, + None, + None, + None, + None, + "prepared", + created_at_ms, + ), + ) + row = database.execute( + "SELECT * FROM requests WHERE request_id=?", (request_id,) + ).fetchone() + database.commit() + return self._decode(row) + except sqlite3.IntegrityError as exception: + database.rollback() + raise CollectiveMemoryError( + "collective_publication_target_pending", retryable=True + ) from exception + except BaseException: + database.rollback() + raise + + def rows(self, *, states: Sequence[str] | None = None) -> list[dict[str, Any]]: + self.initialize() + query = "SELECT * FROM requests" + parameters: tuple[Any, ...] = () + if states: + query += " WHERE state IN (" + ",".join("?" for _ in states) + ")" + parameters = tuple(states) + query += " ORDER BY request_id" + with closing(self.connect()) as database: + rows = database.execute(query, parameters).fetchall() + return [self._decode(row) for row in rows] + + def by_event(self, event_id: str) -> dict[str, Any]: + self.initialize() + with closing(self.connect()) as database: + row = database.execute( + "SELECT * FROM requests WHERE request_event_id=?", (event_id,) + ).fetchone() + if row is None: + raise CollectiveMemoryError("collective_publication_request_unknown") + return self._decode(row) + + def mark_queued(self, request_id: str, event: Mapping[str, Any]) -> None: + self.initialize() + with self.exclusive(), closing(self.connect()) as database: + database.execute("BEGIN IMMEDIATE") + row = database.execute( + "SELECT state,request_event_id,request_event_hash FROM requests " + "WHERE request_id=?", + (request_id,), + ).fetchone() + if row is None or row["request_event_id"] != event["event_id"]: + database.rollback() + raise CollectiveMemoryError("collective_publication_request_mismatch") + if ( + row["state"] != "prepared" + and row["request_event_hash"] != event["content_hash"] + ): + database.rollback() + raise CollectiveMemoryError("collective_publication_request_mismatch") + database.execute( + "UPDATE requests SET state='queued',request_event_hash=? " + "WHERE request_id=? AND state='prepared'", + (event["content_hash"], request_id), + ) + database.commit() + + def mark_effected( + self, + request_id: str, + receipt: Mapping[str, Any], + reconciliation: Mapping[str, Any], + ) -> None: + self.initialize() + with self.exclusive(), closing(self.connect()) as database: + database.execute("BEGIN IMMEDIATE") + row = database.execute( + "SELECT state,provider_receipt_json,reconciliation_json FROM requests " + "WHERE request_id=?", + (request_id,), + ).fetchone() + if row is None or row["state"] not in {"queued", "effected"}: + database.rollback() + raise CollectiveMemoryError("collective_publication_state_conflict") + if row["state"] == "effected" and ( + json.loads(bytes(row["provider_receipt_json"])) != receipt + or json.loads(bytes(row["reconciliation_json"])) != reconciliation + ): + database.rollback() + raise CollectiveMemoryError("collective_publication_effect_collision") + database.execute( + "UPDATE requests SET state='effected',provider_receipt_json=?," + "reconciliation_json=? WHERE request_id=?", + ( + _canonical(receipt, "invalid_collective_publication_receipt"), + _canonical( + reconciliation, "invalid_collective_publication_reconciliation" + ), + request_id, + ), + ) + database.commit() + + def mark_accepted( + self, + request_id: str, + acceptance: Mapping[str, Any], + event: Mapping[str, Any], + ) -> None: + self.initialize() + with self.exclusive(), closing(self.connect()) as database: + database.execute("BEGIN IMMEDIATE") + row = database.execute( + "SELECT state,acceptance_json,acceptance_event_id," + "acceptance_event_hash " + "FROM requests WHERE request_id=?", + (request_id,), + ).fetchone() + if row is None: + database.rollback() + raise CollectiveMemoryError("collective_publication_request_unknown") + if row["state"] == "accepted": + if ( + json.loads(bytes(row["acceptance_json"])) != acceptance + or row["acceptance_event_id"] != event["event_id"] + or row["acceptance_event_hash"] != event["content_hash"] + ): + database.rollback() + raise CollectiveMemoryError( + "collective_publication_acceptance_collision" + ) + database.commit() + return + if row["state"] != "effected": + database.rollback() + raise CollectiveMemoryError("collective_publication_state_conflict") + database.execute( + "UPDATE requests SET state='accepted',acceptance_json=?," + "acceptance_event_id=?,acceptance_event_hash=? WHERE request_id=?", + ( + _canonical(acceptance, "invalid_collective_publication_acceptance"), + event["event_id"], + event["content_hash"], + request_id, + ), + ) + database.commit() + + +def assert_separate_collective_stores( + source: CollectiveSourceStore, publisher: CollectivePublisherJournal +) -> None: + if source.path == publisher.path: + raise CollectiveMemoryError("collective_direction_store_shared") + for left, right in ( + (source.path, publisher.path), + (source.lock_path, publisher.lock_path), + ): + if left.exists() and right.exists() and os.path.samefile(left, right): + raise CollectiveMemoryError("collective_direction_store_shared") + + +def _utc_from_ms(value: int) -> str: + instant = dt.datetime.fromtimestamp( + _uint(value, "invalid_collective_clock") / 1000, tz=dt.UTC + ) + return instant.isoformat(timespec="microseconds").replace("+00:00", "Z") + + +def _known_events(ledger: Ledger) -> list[Event]: + return ledger.events(include_incomplete=False) + + +def collective_checkpoint( + ledger: Ledger, source_event_ids: Sequence[str] +) -> dict[str, Any]: + if not source_event_ids or len(source_event_ids) > MAX_SOURCE_REFS: + raise CollectiveMemoryError("invalid_collective_source_events") + by_id = {event["event_id"]: event for event in _known_events(ledger)} + superseded = { + event["supersedes"] + for event in by_id.values() + if event["supersedes"] is not None + } + refs: list[dict[str, str]] = [] + for event_id in source_event_ids: + event = by_id.get(_uuid(event_id, "invalid_collective_source_events")) + if event is None or event_id in superseded: + raise CollectiveMemoryError("collective_source_event_unavailable") + refs.append({"id": event_id, "hash": event["content_hash"]}) + refs.sort(key=lambda item: (item["id"], item["hash"])) + if len({item["id"] for item in refs}) != len(refs): + raise CollectiveMemoryError("invalid_collective_source_events") + core = { + "being_ref": ledger.authority.manifest.being_ref, + "manifest_hash": ledger.authority.manifest.digest, + "source_refs": refs, + } + return { + "id": _derived("dm:collective-checkpoint:v1:", PUBLISHER_REQUEST_DOMAIN, core), + "hash": hashlib.sha256( + _canonical(core, "invalid_collective_checkpoint") + ).hexdigest(), + "core": core, + } + + +def _request_summary( + request: Mapping[str, Any], + preview: Mapping[str, Any], + plan: Mapping[str, Any], + *, + request_id: str, + consent_hash: str, + review_hash: str, + requested_at_ms: int, +) -> dict[str, Any]: + draft = request["draft"] + body = { + "adapter_id": PUBLISHER_ADAPTER_ID, + "publisher_version": PUBLISHER_VERSION, + "request_id": request_id, + "request_hash": hashlib.sha256( + _canonical(request, "invalid_collective_publication_request") + ).hexdigest(), + "plan_id": plan["plan_id"], + "plan_hash": plan["plan_hash"], + "idempotency_key": request["idempotency_key"], + "requester_id": draft["requester_id"], + "subject_id": draft["subject_id"], + "target_id": draft["target_id"], + "action": draft["action"], + "source_refs": copy.deepcopy(draft["source_refs"]), + "source_checkpoint": copy.deepcopy(draft["source_checkpoint"]), + "classification": draft["classification"], + "policy_version": draft["policy_version"], + "preview_id": preview["preview_id"], + "preview_hash": preview["preview_hash"], + "content_hash": preview["body"]["rendered"]["content_hash"], + "content_length": preview["body"]["rendered"]["content_length"], + "consent": { + "evidence_id": request["consent"]["body"]["evidence_id"], + "evidence_hash": consent_hash, + }, + "review": { + "evidence_id": request["review"]["body"]["evidence_id"], + "evidence_hash": review_hash, + "reviewer": request["review"]["body"]["issuer"], + }, + "predecessor": { + "receipt_id": draft["predecessor_receipt_id"], + "receipt_hash": draft["predecessor_receipt_hash"], + } + if draft["predecessor_receipt_id"] is not None + else None, + "requested_at_ms": requested_at_ms, + } + summary_id = _derived( + "dm:collective-publisher-request:v1:", PUBLISHER_REQUEST_DOMAIN, body + ) + return {"schema": PUBLISHER_REQUEST_SCHEMA, "summary_id": summary_id, "body": body} + + +def validate_publisher_request_payload(value: Any) -> dict[str, Any]: + code = "invalid_collective_publisher_request" + row = _closed(value, {"schema", "summary_id", "body"}, code) + if row["schema"] != PUBLISHER_REQUEST_SCHEMA: + raise CollectiveMemoryError(code) + body = _closed( + row["body"], + { + "adapter_id", + "publisher_version", + "request_id", + "request_hash", + "plan_id", + "plan_hash", + "idempotency_key", + "requester_id", + "subject_id", + "target_id", + "action", + "source_refs", + "source_checkpoint", + "classification", + "policy_version", + "preview_id", + "preview_hash", + "content_hash", + "content_length", + "consent", + "review", + "predecessor", + "requested_at_ms", + }, + code, + ) + if ( + body["adapter_id"] != PUBLISHER_ADAPTER_ID + or body["publisher_version"] != PUBLISHER_VERSION + ): + raise CollectiveMemoryError(code) + if body["action"] not in {"publish", "successor", "tombstone"}: + raise CollectiveMemoryError(code) + refs = body["source_refs"] + if not isinstance(refs, list) or not refs: + raise CollectiveMemoryError(code) + normalized_refs = [_source_ref(item, code) for item in refs] + if normalized_refs != sorted( + normalized_refs, key=lambda item: (item["id"], item["hash"]) + ): + raise CollectiveMemoryError(code) + consent = _closed(body["consent"], {"evidence_id", "evidence_hash"}, code) + review = _closed(body["review"], {"evidence_id", "evidence_hash", "reviewer"}, code) + predecessor = body["predecessor"] + if predecessor is not None: + predecessor = _closed(predecessor, {"receipt_id", "receipt_hash"}, code) + predecessor = { + "receipt_id": _identifier(predecessor["receipt_id"], code), + "receipt_hash": _hash(predecessor["receipt_hash"], code), + } + normalized_body = { + **dict(body), + "request_id": _identifier(body["request_id"], code), + "request_hash": _hash(body["request_hash"], code), + "plan_id": _identifier(body["plan_id"], code), + "plan_hash": _hash(body["plan_hash"], code), + "idempotency_key": _identifier(body["idempotency_key"], code), + "requester_id": _identifier(body["requester_id"], code), + "subject_id": _identifier(body["subject_id"], code), + "target_id": _identifier(body["target_id"], code), + "source_refs": normalized_refs, + "source_checkpoint": _checkpoint(body["source_checkpoint"], code), + "classification": _identifier(body["classification"], code), + "policy_version": _identifier(body["policy_version"], code), + "preview_id": _identifier(body["preview_id"], code), + "preview_hash": _hash(body["preview_hash"], code), + "content_hash": _hash(body["content_hash"], code), + "content_length": _uint( + body["content_length"], code, minimum=1, maximum=MAX_PUBLICATION_BYTES + ), + "consent": { + "evidence_id": _identifier(consent["evidence_id"], code), + "evidence_hash": _hash(consent["evidence_hash"], code), + }, + "review": { + "evidence_id": _identifier(review["evidence_id"], code), + "evidence_hash": _hash(review["evidence_hash"], code), + "reviewer": _identifier(review["reviewer"], code), + }, + "predecessor": predecessor, + "requested_at_ms": _uint(body["requested_at_ms"], code), + } + expected_id = _derived( + "dm:collective-publisher-request:v1:", PUBLISHER_REQUEST_DOMAIN, normalized_body + ) + if row["summary_id"] != expected_id: + raise CollectiveMemoryError("collective_publisher_request_identity_mismatch") + return { + "schema": PUBLISHER_REQUEST_SCHEMA, + "summary_id": expected_id, + "body": normalized_body, + } + + +def _publisher_acceptance( + summary: Mapping[str, Any], + receipt: Mapping[str, Any], + reconciliation: Mapping[str, Any], + *, + request_event_id: str, + request_event_hash: str, + accepted_at_ms: int, +) -> dict[str, Any]: + body = { + "adapter_id": PUBLISHER_ADAPTER_ID, + "publisher_version": PUBLISHER_VERSION, + "request_event_id": request_event_id, + "request_event_hash": request_event_hash, + "request_id": summary["body"]["request_id"], + "request_hash": summary["body"]["request_hash"], + "summary_id": summary["summary_id"], + "provider_receipt": copy.deepcopy(dict(receipt)), + "reconciliation_state_hash": reconciliation["state_hash"], + "accepted_at_ms": _uint(accepted_at_ms, "invalid_collective_clock"), + } + acceptance_id = _derived( + "dm:collective-publisher-acceptance:v1:", + PUBLISHER_ACCEPTANCE_DOMAIN, + body, + ) + return { + "schema": PUBLISHER_ACCEPTANCE_SCHEMA, + "acceptance_id": acceptance_id, + "body": body, + } + + +def validate_publisher_acceptance_payload(value: Any) -> dict[str, Any]: + code = "invalid_collective_publisher_acceptance" + row = _closed(value, {"schema", "acceptance_id", "body"}, code) + if row["schema"] != PUBLISHER_ACCEPTANCE_SCHEMA: + raise CollectiveMemoryError(code) + body = _closed( + row["body"], + { + "adapter_id", + "publisher_version", + "request_event_id", + "request_event_hash", + "request_id", + "request_hash", + "summary_id", + "provider_receipt", + "reconciliation_state_hash", + "accepted_at_ms", + }, + code, + ) + if ( + body["adapter_id"] != PUBLISHER_ADAPTER_ID + or body["publisher_version"] != PUBLISHER_VERSION + ): + raise CollectiveMemoryError(code) + provider = body["provider_receipt"] + provider_row = _validate_publication_receipt_shape(provider) + provider_body = provider_row["body"] + if not isinstance(provider_body, Mapping): + raise CollectiveMemoryError(code) + normalized_body = { + **dict(body), + "request_event_id": _uuid(body["request_event_id"], code), + "request_event_hash": _hash(body["request_event_hash"], code), + "request_id": _identifier(body["request_id"], code), + "request_hash": _hash(body["request_hash"], code), + "summary_id": _identifier(body["summary_id"], code), + "provider_receipt": provider_row, + "reconciliation_state_hash": _hash(body["reconciliation_state_hash"], code), + "accepted_at_ms": _uint(body["accepted_at_ms"], code), + } + if ( + provider_row["receipt_hash"] != _hash(provider_row["receipt_hash"], code) + or provider_body.get("request_hash") != normalized_body["request_hash"] + ): + raise CollectiveMemoryError("collective_publisher_acceptance_mismatch") + expected_id = _derived( + "dm:collective-publisher-acceptance:v1:", + PUBLISHER_ACCEPTANCE_DOMAIN, + normalized_body, + ) + if row["acceptance_id"] != expected_id: + raise CollectiveMemoryError("collective_publisher_acceptance_identity_mismatch") + return { + "schema": PUBLISHER_ACCEPTANCE_SCHEMA, + "acceptance_id": expected_id, + "body": normalized_body, + } + + +@dataclass(frozen=True) +class CollectivePublisherAdapter: + ledger: Ledger + profile: Mapping[str, Any] + transport: PublisherTransport + journal: CollectivePublisherJournal + signer: EventSigner + consent_issuers: Mapping[str, Mapping[str, Any]] + review_issuers: Mapping[str, Mapping[str, Any]] + clock: Clock + fault: Fault = _no_fault + + def __post_init__(self) -> None: + profile = validate_publisher_profile(self.profile) + object.__setattr__(self, "profile", profile) + if not self.consent_issuers or not self.review_issuers: + raise CollectiveMemoryError("collective_publication_trust_empty") + consent = { + key: self._issuer(value) for key, value in self.consent_issuers.items() + } + reviewers = { + key: self._issuer(value) for key, value in self.review_issuers.items() + } + if set(consent) & set(reviewers): + raise CollectiveMemoryError("collective_publication_roles_overlap") + object.__setattr__(self, "consent_issuers", consent) + object.__setattr__(self, "review_issuers", reviewers) + self.journal.initialize() + + @staticmethod + def _issuer(value: Mapping[str, Any]) -> dict[str, Any]: + if set(value) != { + "principal", + "public_key", + "kid", + "valid_from_ms", + "valid_until_ms", + "revoked_at_ms", + }: + raise CollectiveMemoryError("invalid_collective_evidence_issuer") + principal = _identifier( + value["principal"], "invalid_collective_evidence_issuer" + ) + try: + public = unb64url(value["public_key"], length=32) + kid_hash = unb64url(value["kid"].removeprefix("ed25519:"), length=32) + except CanonicalError as exception: + raise CollectiveMemoryError( + "invalid_collective_evidence_issuer" + ) from exception + if ( + not value["kid"].startswith("ed25519:") + or kid_hash != hashlib.sha256(public).digest() + ): + raise CollectiveMemoryError("invalid_collective_evidence_issuer") + valid_from_ms = _uint( + value["valid_from_ms"], "invalid_collective_evidence_issuer" + ) + valid_until_ms = _uint( + value["valid_until_ms"], "invalid_collective_evidence_issuer" + ) + revoked_at_ms = value["revoked_at_ms"] + if revoked_at_ms is not None: + revoked_at_ms = _uint(revoked_at_ms, "invalid_collective_evidence_issuer") + if valid_until_ms <= valid_from_ms: + raise CollectiveMemoryError("invalid_collective_evidence_issuer") + return { + "principal": principal, + "public_key": value["public_key"], + "kid": value["kid"], + "valid_from_ms": valid_from_ms, + "valid_until_ms": valid_until_ms, + "revoked_at_ms": revoked_at_ms, + } + + def _verify_sources(self, draft: Mapping[str, Any]) -> None: + by_id = {event["event_id"]: event for event in _known_events(self.ledger)} + superseded = { + event["supersedes"] + for event in by_id.values() + if event["supersedes"] is not None + } + for ref in draft["source_refs"]: + event = by_id.get(ref["id"]) + if ( + event is None + or event["content_hash"] != ref["hash"] + or ref["id"] in superseded + ): + raise CollectiveMemoryError("collective_publication_source_drift") + checkpoint = collective_checkpoint( + self.ledger, [cast(str, ref["id"]) for ref in draft["source_refs"]] + ) + if draft["source_checkpoint"] != { + "id": checkpoint["id"], + "hash": checkpoint["hash"], + }: + raise CollectiveMemoryError("collective_publication_checkpoint_mismatch") + + def _call(self, operation: str, document: Mapping[str, Any]) -> Mapping[str, Any]: + try: + return self.transport(operation, document) + except CollectiveMemoryError: + raise + except (ConnectionError, TimeoutError, OSError) as exception: + raise CollectiveMemoryError( + "collective_publisher_unavailable", retryable=True + ) from exception + except Exception as exception: + raise CollectiveMemoryError("collective_publisher_rejected") from exception + + def draft( + self, + *, + source_event_ids: Sequence[str], + subject_id: str, + target_id: str, + action: str, + classification: str, + title: str, + body: str, + predecessor_receipt_id: str | None, + predecessor_receipt_hash: str | None, + ) -> dict[str, Any]: + checkpoint = collective_checkpoint(self.ledger, source_event_ids) + value = { + "schema": PUBLICATION_DRAFT_SCHEMA, + "action": action, + "requester_id": self.profile["requester_id"], + "subject_id": subject_id, + "target_id": target_id, + "source_refs": checkpoint["core"]["source_refs"], + "source_checkpoint": {"id": checkpoint["id"], "hash": checkpoint["hash"]}, + "classification": classification, + "policy_version": self.profile["policy_version"], + "media_type": "text/markdown; charset=utf-8", + "title": title, + "body": body, + "predecessor_receipt_id": predecessor_receipt_id, + "predecessor_receipt_hash": predecessor_receipt_hash, + } + return _validate_draft(value, self.profile) + + def preview(self, draft: Mapping[str, Any]) -> dict[str, Any]: + normalized = _validate_draft(draft, self.profile) + self._verify_sources(normalized) + _render_collective(normalized) + response = self._call("preview", {"draft": normalized}) + return validate_publication_preview(response, normalized) + + def submit( + self, + draft: Mapping[str, Any], + preview: Mapping[str, Any], + *, + idempotency_key: str, + consent: Mapping[str, Any], + review: Mapping[str, Any], + ) -> Event: + normalized_draft = _validate_draft(draft, self.profile) + normalized_preview = validate_publication_preview(preview, normalized_draft) + self._verify_sources(normalized_draft) + consent_body = consent.get("body") if isinstance(consent, Mapping) else None + review_body = review.get("body") if isinstance(review, Mapping) else None + if not isinstance(consent_body, Mapping) or not isinstance( + review_body, Mapping + ): + raise CollectiveMemoryError("invalid_collective_evidence") + consent_issuer = self.consent_issuers.get(cast(str, consent_body.get("issuer"))) + review_issuer = self.review_issuers.get(cast(str, review_body.get("issuer"))) + if consent_issuer is None or review_issuer is None: + raise CollectiveMemoryError("collective_evidence_issuer_untrusted") + at = _utc_from_ms(self.clock()) + normalized_consent, consent_hash = validate_publication_evidence( + consent, + normalized_draft, + normalized_preview, + kind="consent", + issuer=consent_issuer, + at=at, + ) + normalized_review, review_hash = validate_publication_evidence( + review, + normalized_draft, + normalized_preview, + kind="review", + issuer=review_issuer, + at=at, + ) + if ( + consent_issuer["principal"] != normalized_draft["subject_id"] + or review_issuer["principal"] + in {normalized_draft["subject_id"], normalized_draft["requester_id"]} + or not cast(str, review_issuer["principal"]).startswith("human:") + ): + raise CollectiveMemoryError("collective_self_review") + request = create_publication_request( + normalized_draft, + normalized_preview, + idempotency_key=idempotency_key, + consent=normalized_consent, + review=normalized_review, + ) + raw_plan = self._call("plan", {"request": request}) + plan = validate_publication_plan( + raw_plan, + request, + normalized_preview, + consent_hash=consent_hash, + review_hash=review_hash, + ) + request_hash = hashlib.sha256( + _canonical(request, "invalid_collective_publication_request") + ).hexdigest() + request_id = _derived( + "dm:collective-publisher-operation:v1:", + PUBLISHER_REQUEST_DOMAIN, + { + "idempotency_key": request["idempotency_key"], + "request_hash": request_hash, + }, + ) + request_event_id = str(uuid.uuid5(uuid.NAMESPACE_URL, request_id)) + created_at = self.clock() + row = self.journal.prepare( + request_id=request_id, + request=request, + preview=normalized_preview, + plan=plan, + consent_hash=consent_hash, + review_hash=review_hash, + request_event_id=request_event_id, + created_at_ms=created_at, + ) + self.fault("publisher-prepared") + if row["state"] != "prepared": + event = self.ledger.event(row["request_event_id"]) + if event is None: + raise CollectiveMemoryError("collective_publication_journal_drift") + return event + return self._queue(row) + + def _queue(self, row: Mapping[str, Any]) -> Event: + summary = _request_summary( + row["request"], + row["preview"], + row["plan"], + request_id=row["request_id"], + consent_hash=row["consent_hash"], + review_hash=row["review_hash"], + requested_at_ms=row["created_at_ms"], + ) + summary = validate_publisher_request_payload(summary) + causal = [item["id"] for item in summary["body"]["source_refs"]] + predecessor_event = self._predecessor_acceptance(summary["body"]["predecessor"]) + if predecessor_event is not None: + causal.append(predecessor_event["event_id"]) + event = self.ledger.append_local_idempotent( + client_id="collective-publisher-v1", + request_id=row["request_event_id"], + request_hash=row["request_hash"], + kind="collective.publication.requested", + subject=self.ledger.authority.manifest.being_ref, + payload=summary, + signer=self.signer, + sensitivity="shareable" + if summary["body"]["classification"] == "public" + else "private", + causal_parents=causal, + occurred_at_ms=row["created_at_ms"], + event_id=row["request_event_id"], + ) + self.journal.mark_queued(row["request_id"], event) + self.fault("publisher-queued") + return event + + def _predecessor_acceptance( + self, predecessor: Mapping[str, Any] | None + ) -> Event | None: + if predecessor is None: + return None + matches = [] + for event in _known_events(self.ledger): + if event["kind"] != "collective.publication.receipted": + continue + acceptance = validate_publisher_acceptance_payload(event["payload"]) + receipt = acceptance["body"]["provider_receipt"] + if ( + receipt["receipt_id"] == predecessor["receipt_id"] + and receipt["receipt_hash"] == predecessor["receipt_hash"] + ): + matches.append(event) + if len(matches) != 1: + raise CollectiveMemoryError("collective_publication_predecessor_unknown") + return matches[0] + + def execute(self, request_event_id: str) -> dict[str, Any]: + row = self.journal.by_event( + _uuid(request_event_id, "invalid_collective_request_event") + ) + if row["state"] == "prepared": + self._queue(row) + row = self.journal.by_event(request_event_id) + request_event = self.ledger.event(request_event_id) + if ( + request_event is None + or request_event["kind"] != "collective.publication.requested" + ): + raise CollectiveMemoryError("collective_publication_request_event_missing") + summary = validate_publisher_request_payload(request_event["payload"]) + if summary["body"]["request_hash"] != row["request_hash"]: + raise CollectiveMemoryError("collective_publication_request_mismatch") + self._verify_sources(row["request"]["draft"]) + if row["state"] == "accepted": + acceptance = validate_publisher_acceptance_payload(row["acceptance"]) + self._fresh_reconcile(row["provider_receipt"]) + event = self.ledger.event(row["acceptance_event_id"]) + if event is None or event["payload"] != acceptance: + raise CollectiveMemoryError("collective_publication_acceptance_missing") + return {"event": event, "acceptance": acceptance} + if row["state"] == "queued": + raw_receipt = self._call( + "apply", {"request": row["request"], "plan": row["plan"]} + ) + receipt = validate_publication_receipt( + raw_receipt, + row["request"], + row["plan"], + consent_hash=row["consent_hash"], + review_hash=row["review_hash"], + ) + reconciliation = self._fresh_reconcile(receipt) + self.journal.mark_effected(row["request_id"], receipt, reconciliation) + self.fault("publisher-effected") + row = self.journal.by_event(request_event_id) + if row["state"] != "effected": + raise CollectiveMemoryError("collective_publication_state_conflict") + receipt = validate_publication_receipt( + row["provider_receipt"], + row["request"], + row["plan"], + consent_hash=row["consent_hash"], + review_hash=row["review_hash"], + ) + reconciliation = self._fresh_reconcile(receipt) + acceptance = _publisher_acceptance( + summary, + receipt, + reconciliation, + request_event_id=request_event_id, + request_event_hash=request_event["content_hash"], + accepted_at_ms=row["created_at_ms"], + ) + acceptance = validate_publisher_acceptance_payload(acceptance) + acceptance_event_id = str( + uuid.uuid5(uuid.NAMESPACE_URL, acceptance["acceptance_id"]) + ) + predecessor_event = self._predecessor_acceptance(summary["body"]["predecessor"]) + event = self.ledger.append_local_idempotent( + client_id="collective-publisher-acceptance-v1", + request_id=acceptance_event_id, + request_hash=hashlib.sha256( + _canonical(acceptance, "invalid_collective_publication_acceptance") + ).hexdigest(), + kind="collective.publication.receipted", + subject=self.ledger.authority.manifest.being_ref, + payload=acceptance, + signer=self.signer, + sensitivity=request_event["sensitivity"], + causal_parents=[request_event_id], + supersedes=None + if predecessor_event is None + else predecessor_event["event_id"], + occurred_at_ms=row["created_at_ms"], + event_id=acceptance_event_id, + ) + self.fault("publisher-ledger-appended") + self.journal.mark_accepted(row["request_id"], acceptance, event) + self.fault("publisher-accepted") + return {"event": event, "acceptance": acceptance} + + def _fresh_reconcile(self, receipt: Mapping[str, Any]) -> dict[str, Any]: + try: + raw = self._call("reconcile", {"receipt_id": receipt["receipt_id"]}) + except CollectiveMemoryError as exception: + if exception.retryable: + raise CollectiveMemoryError( + "collective_effect_unverifiable", retryable=True + ) from exception + raise + return validate_publication_reconciliation(raw, receipt) + + def recover(self) -> list[dict[str, Any]]: + results: list[dict[str, Any]] = [] + self._call("recover", {}) + for row in self.journal.rows(states=["prepared", "queued", "effected"]): + if row["state"] == "prepared": + self._queue(row) + result = self.execute(row["request_event_id"]) + results.append( + { + "request_id": row["request_id"], + "acceptance_id": result["acceptance"]["acceptance_id"], + "outcome": "accepted", + } + ) + return results + + def reconcile(self, acceptance_event_id: str) -> dict[str, Any]: + event = self.ledger.event( + _uuid(acceptance_event_id, "invalid_collective_acceptance_event") + ) + if event is None or event["kind"] != "collective.publication.receipted": + raise CollectiveMemoryError("collective_publication_acceptance_missing") + acceptance = validate_publisher_acceptance_payload(event["payload"]) + reconciliation = self._fresh_reconcile(acceptance["body"]["provider_receipt"]) + return { + "schema": "dm.collective-publisher.reconciliation/v1", + "acceptance_event_id": acceptance_event_id, + "acceptance_id": acceptance["acceptance_id"], + "provider_receipt_id": acceptance["body"]["provider_receipt"]["receipt_id"], + "state_hash": reconciliation["state_hash"], + "effect": "verified", + } + + +__all__ = [ + "COLLECTIVE_CONTRACT_VERSION", + "COLLECTIVE_MEMORY_COMMIT", + "COLLECTIVE_SCHEMA_SHA256", + "PUBLISHER_ADAPTER_ID", + "SOURCE_ADAPTER_ID", + "CollectiveMemoryError", + "CollectivePublisherAdapter", + "CollectivePublisherJournal", + "CollectiveSourceAdapter", + "CollectiveSourceStore", + "assert_separate_collective_stores", + "collective_checkpoint", + "create_publication_request", + "create_publisher_manifest", + "create_publisher_profile", + "create_source_manifest", + "create_source_profile", + "evidence_issuer", + "sign_publication_evidence", + "validate_export_manifest", + "validate_publication_evidence", + "validate_publication_plan", + "validate_publication_preview", + "validate_publication_receipt", + "validate_publisher_acceptance_payload", + "validate_publisher_profile", + "validate_publisher_request_payload", + "validate_source_preview", + "validate_source_profile", + "validate_source_receipt", +] diff --git a/src/daimon_matrix/conformance.py b/src/daimon_matrix/conformance.py index 4a878f2..c37fa53 100644 --- a/src/daimon_matrix/conformance.py +++ b/src/daimon_matrix/conformance.py @@ -29,7 +29,7 @@ MAX_SCENARIOS: Final = 128 MAX_EVIDENCE_TESTS: Final = 4096 REQUIRED_REGISTRY_SHA256: Final = ( - "99887a23c1e1b2dbfa7ed050102162d6fd32a8942b71810b1218e04ef6897ffc" + "12f01cb1704e9943b7f2069ba41fa3f7f9b153c8cf5a22f506e404ac918b963f" ) REQUIRED_SCENARIO_IDS: Final = frozenset( { @@ -42,6 +42,10 @@ "causal_quarantine_promotion", "cli_closed_retry_surface", "cluster_effect_truth", + "collective_exchange_recovery", + "collective_publication_review", + "collective_real_io", + "collective_source_quarantine", "communication_cursor_contiguous", "communication_fanout_receipts", "communication_route_replay", diff --git a/src/daimon_matrix/ledger.py b/src/daimon_matrix/ledger.py index 3f6dc15..d4ccc61 100644 --- a/src/daimon_matrix/ledger.py +++ b/src/daimon_matrix/ledger.py @@ -519,6 +519,12 @@ def _dependencies(event: Mapping[str, Any]) -> set[str]: predecessor = payload["previous_decision_event_id"] if predecessor is not None: dependencies.add(predecessor) + if event["kind"] == "collective.publication.requested": + dependencies.update( + item["id"] for item in event["payload"]["body"]["source_refs"] + ) + if event["kind"] == "collective.publication.receipted": + dependencies.add(event["payload"]["body"]["request_event_id"]) if event["supersedes"] is not None: dependencies.add(event["supersedes"]) return dependencies diff --git a/src/daimon_matrix/weave.py b/src/daimon_matrix/weave.py index c18a9f1..e8e7f24 100644 --- a/src/daimon_matrix/weave.py +++ b/src/daimon_matrix/weave.py @@ -71,6 +71,9 @@ "matrix/species-release-application", *RELATIONSHIP_EVENT_KINDS, *SOURCE_EVENT_KINDS, + "source.imported", + "collective.publication.requested", + "collective.publication.receipted", } ) DECISIONS: Final = frozenset({"adopt", "reject", "defer", "revert"}) @@ -1030,6 +1033,50 @@ def _validate_core(core: Any, manifest: BeingManifest) -> Mapping[str, Any]: raise WeaveProtocolError( f"invalid_relationship_event:{exception.code}" ) from exception + if value["kind"] == "source.imported": + from .collective_memory import CollectiveMemoryError, validate_source_receipt + + try: + receipt = validate_source_receipt(payload) + if ( + value["subject"] != value["being_ref"] + or receipt["body"]["import_event_id"] != value["event_id"] + or value["supersedes"] is not None + ): + raise CollectiveMemoryError("collective_source_event_mismatch") + except CollectiveMemoryError as exception: + raise WeaveProtocolError("invalid_collective_source_event") from exception + if value["kind"].startswith("collective.publication."): + from .collective_memory import ( + CollectiveMemoryError, + validate_publisher_acceptance_payload, + validate_publisher_request_payload, + ) + + try: + if value["kind"] == "collective.publication.requested": + request = validate_publisher_request_payload(payload) + if ( + value["subject"] != value["being_ref"] + or request["body"]["subject_id"] != value["being_ref"] + or value["supersedes"] is not None + ): + raise CollectiveMemoryError( + "collective_publication_request_event_mismatch" + ) + else: + acceptance = validate_publisher_acceptance_payload(payload) + action = acceptance["body"]["provider_receipt"]["body"]["action"] + if value["subject"] != value["being_ref"] or (action == "publish") != ( + value["supersedes"] is None + ): + raise CollectiveMemoryError( + "collective_publication_acceptance_event_mismatch" + ) + except CollectiveMemoryError as exception: + raise WeaveProtocolError( + "invalid_collective_publication_event" + ) from exception return value diff --git a/tests/test_dm036_collective_memory.py b/tests/test_dm036_collective_memory.py new file mode 100644 index 0000000..f7593d6 --- /dev/null +++ b/tests/test_dm036_collective_memory.py @@ -0,0 +1,1496 @@ +from __future__ import annotations + +import copy +import datetime as dt +import hashlib +import importlib.util +import json +import os +import sqlite3 +import subprocess +import sys +import unittest +from collections.abc import Callable, Mapping +from concurrent.futures import ThreadPoolExecutor +from contextlib import closing +from pathlib import Path +from types import ModuleType +from typing import Any, cast + +from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey +from jsonschema import ( # type: ignore[import-untyped] + Draft202012Validator, + FormatChecker, +) +from referencing import Registry, Resource + +from daimon_matrix.canonical import b64url, canonical_bytes +from daimon_matrix.collective_memory import ( + COLLECTIVE_MEMORY_COMMIT, + COLLECTIVE_SCHEMA_SHA256, + CollectiveMemoryError, + CollectivePublisherAdapter, + CollectivePublisherJournal, + CollectiveSourceAdapter, + CollectiveSourceStore, + assert_separate_collective_stores, + create_publisher_manifest, + create_publisher_profile, + create_source_manifest, + create_source_profile, + evidence_issuer, + sign_publication_evidence, + validate_export_manifest, + validate_publisher_acceptance_payload, + validate_publisher_profile, + validate_publisher_request_payload, + validate_source_preview, + validate_source_profile, + validate_source_receipt, +) +from daimon_matrix.weave import EVENT_KINDS, verify_event +from tests.test_dm022_ledger import NOW, RootLedgerFixture, seed + +ROOT = Path(__file__).resolve().parents[1] +COLLECTIVE_ROOT = Path( + os.environ.get( + "COLLECTIVE_MEMORY_CONTRACT_ROOT", str(ROOT.parent / "collective-memory") + ) +).resolve() +SCHEMA_PATH = ROOT / "schemas" / "collective-memory" / "v1" / "contracts.schema.json" +VECTOR_ROOT = ROOT / "vectors" / "collective-memory" / "v1" +PROVENANCE_PATH = ROOT / "provenance" / "collective-memory-exchange-v1.json" +UPSTREAM_SCHEMA = ( + COLLECTIVE_ROOT / "schemas" / "exchange" / "v1" / "contracts.schema.json" +) + + +class Crash(BaseException): + pass + + +class MutableClock: + def __init__(self, value: int = NOW) -> None: + self.value = value + + def __call__(self) -> int: + return self.value + + def utc(self) -> dt.datetime: + return dt.datetime.fromtimestamp(self.value / 1000, tz=dt.UTC) + + +def utc(value: int) -> str: + return ( + dt.datetime.fromtimestamp(value / 1000, tz=dt.UTC) + .isoformat(timespec="microseconds") + .replace("+00:00", "Z") + ) + + +def load_upstream() -> ModuleType: + name = "dm036_collective_exchange" + existing = sys.modules.get(name) + if existing is not None: + return existing + spec = importlib.util.spec_from_file_location( + name, COLLECTIVE_ROOT / "mapa" / "exchange.py" + ) + if spec is None or spec.loader is None: + raise RuntimeError("collective-memory exchange module unavailable") + module = importlib.util.module_from_spec(spec) + sys.modules[name] = module + spec.loader.exec_module(module) + return module + + +class TestProjectionRunner: + def __init__(self, root: Path) -> None: + self.root = root + self.generation = 0 + + def snapshot(self, tx: Path) -> None: + (tx / "projection.before").write_text(str(self.generation), encoding="ascii") + + def restore(self, tx: Path) -> None: + self.generation = int((tx / "projection.before").read_text(encoding="ascii")) + + def build(self) -> None: + self.generation += 1 + + def verify(self, relative: str, content_hash: str) -> dict[str, str]: + content = (self.root / relative).read_bytes() + if hashlib.sha256(content).hexdigest() != content_hash: + raise RuntimeError("projection content mismatch") + return { + "index_generation": str(self.generation), + "ui_generation": f"ui:{self.generation}", + "index_content_hash": content_hash, + } + + +class UpstreamFixture: + def __init__( + self, base: Path, clock: MutableClock, subject_id: str, *, real: bool = False + ) -> None: + self.module = load_upstream() + self.root = base / "collective-corpus" + self.data = base / "collective-data" + base.mkdir(mode=0o700) + self.root.mkdir(mode=0o700) + self.data.mkdir(mode=0o700) + (self.root / "mapa").mkdir() + (self.root / "mapa" / "public.md").write_text( + "# Public collective source\n", encoding="utf-8" + ) + self.config = self.module.ExchangeConfig.from_object( + { + "schema": "collective-exchange-config/v1", + "producer_instance": "collective:test", + "producer_release": "collective:release:test", + "policy_version": "policy:v1", + "targets": [ + { + "target_id": "collective:article:alpha", + "relative_path": "published/alpha.md", + } + ], + "index_scope": "total", + } + ) + self.reader_capability = self.module.ExchangeCapability( + "cap:test:reader", "export-reader", ("public",), b"r" * 32 + ) + self.publisher_capability = self.module.ExchangeCapability( + "cap:test:publisher", + "reviewed-publisher", + ("collective:article:alpha",), + b"p" * 32, + ) + self.subject_key = Ed25519PrivateKey.from_private_bytes(seed("dm036-subject")) + self.reviewer_key = Ed25519PrivateKey.from_private_bytes(seed("dm036-reviewer")) + self.subject = evidence_issuer(subject_id, self.subject_key.public_key()) + self.reviewer = evidence_issuer( + "human:collective-reviewer", self.reviewer_key.public_key() + ) + valid_from = utc(clock.value - 86_400_000) + valid_to = utc(clock.value + 86_400_000) + self.trust = self.module.TrustStore.from_object( + { + "schema": "collective-exchange-trust/v1", + "keys": [ + { + "kid": self.subject["kid"], + "principal": self.subject["principal"], + "roles": ["subject-consent"], + "public_key": self.subject["public_key"], + "not_before": valid_from, + "not_after": valid_to, + "revoked_at": None, + }, + { + "kid": self.reviewer["kid"], + "principal": self.reviewer["principal"], + "roles": ["independent-review"], + "public_key": self.reviewer["public_key"], + "not_before": valid_from, + "not_after": valid_to, + "revoked_at": None, + }, + ], + } + ) + self.catalog: dict[str, Any] = { + "schema": "collective-export-catalog/v1", + "policy_version": "policy:v1", + "scope_id": "public", + "entries": [ + { + "artifact_id": "artifact:test:v1", + "logical_id": "logical:test", + "relative_path": "mapa/public.md", + "media_type": "text/markdown; charset=utf-8", + "authors": ["author:test"], + "source_refs": [{"id": "source:test", "hash": "1" * 64}], + "license": "MIT", + "consent_scope": "public", + "classification": "public", + "predecessor_artifact_id": None, + "state": "active", + } + ], + } + self.export = self.module.ExportBoundary( + self.root, + self.data, + self.config, + self.reader_capability, + lambda _scope: copy.deepcopy(self.catalog), + clock=clock.utc, + ) + self.projections = None if real else TestProjectionRunner(self.root) + self.publication = self.module.PublicationBoundary( + self.root, + self.data, + self.config, + self.publisher_capability, + self.trust, + projection_runner=self.projections, + clock=clock.utc, + ) + + def successor(self, *, tombstone: bool = False) -> None: + if not tombstone: + (self.root / "mapa" / "public.md").write_text( + "# Public collective successor\n", encoding="utf-8" + ) + self.catalog["entries"] = [ + { + **self.catalog["entries"][0], + "artifact_id": "artifact:test:tombstone" + if tombstone + else "artifact:test:v2", + "relative_path": None if tombstone else "mapa/public.md", + "predecessor_artifact_id": self.catalog["entries"][0]["artifact_id"], + "state": "tombstone" if tombstone else "active", + } + ] + + +class SourceTransport: + def __init__(self, upstream: UpstreamFixture) -> None: + self.upstream = upstream + self.corrupt_object = False + self.mix_page = False + self.fail = False + self.page_calls = 0 + self.manifest_mutator: Callable[[dict[str, Any]], None] | None = None + self.page_mutator: Callable[[dict[str, Any]], None] | None = None + + def __call__( + self, operation: str, document: Mapping[str, Any] + ) -> Mapping[str, Any] | bytes: + if self.fail: + raise ConnectionError("synthetic source outage") + if operation == "manifest": + if document["generation_id"] is not None: + manifest = self.upstream.export.manifest(document["generation_id"]) + else: + manifest = self.upstream.export.create(document["scope_id"]) + manifest = copy.deepcopy(manifest) + if self.manifest_mutator is not None: + self.manifest_mutator(manifest) + return cast(Mapping[str, Any], manifest) + if operation == "page": + self.page_calls += 1 + page = self.upstream.export.page( + document["generation_id"], + cursor=document["cursor"], + limit=document["limit"], + ) + if self.mix_page: + page = copy.deepcopy(page) + page["manifest_hash"] = "0" * 64 + if self.page_mutator is not None: + page = copy.deepcopy(page) + self.page_mutator(page) + return cast(Mapping[str, Any], page) + if operation == "object": + result = cast( + bytes, + self.upstream.export.object_bytes( + document["generation_id"], document["content_ref"] + ), + ) + return result + b"tamper" if self.corrupt_object else result + raise RuntimeError("source direction cannot publish") + + +class PublisherTransport: + def __init__(self, upstream: UpstreamFixture) -> None: + self.upstream = upstream + self.lose_after_apply = False + self.corrupt_receipt = False + self.preview_mutator: Callable[[dict[str, Any]], None] | None = None + + def __call__( + self, operation: str, document: Mapping[str, Any] + ) -> Mapping[str, Any]: + if operation == "preview": + preview = copy.deepcopy( + self.upstream.publication.preview(document["draft"]) + ) + if self.preview_mutator is not None: + self.preview_mutator(preview) + return cast(Mapping[str, Any], preview) + if operation == "plan": + return cast( + Mapping[str, Any], self.upstream.publication.plan(document["request"]) + ) + if operation == "apply": + receipt = self.upstream.publication.apply( + document["request"], document["plan"] + ) + if self.lose_after_apply: + self.lose_after_apply = False + raise ConnectionError("synthetic response loss") + if self.corrupt_receipt: + receipt = copy.deepcopy(receipt) + receipt["body"]["target_id"] = "collective:article:other" + return cast(Mapping[str, Any], receipt) + if operation == "reconcile": + try: + return cast( + Mapping[str, Any], + self.upstream.publication.reconcile(document["receipt_id"]), + ) + except self.upstream.module.ExchangeError as exception: + if exception.code == "effect_truth_discrepancy": + raise CollectiveMemoryError( + "collective_effect_truth_discrepancy" + ) from exception + raise + if operation == "recover": + return {"recovered": self.upstream.publication.recover()} + raise RuntimeError("publisher direction cannot read exports") + + +class DM036ContractTests(unittest.TestCase): + def test_pinned_upstream_and_closed_contracts(self) -> None: + head = subprocess.run( + ["git", "rev-parse", "HEAD"], + cwd=COLLECTIVE_ROOT, + check=True, + capture_output=True, + text=True, + ).stdout.strip() + self.assertEqual(head, COLLECTIVE_MEMORY_COMMIT) + self.assertEqual( + hashlib.sha256(UPSTREAM_SCHEMA.read_bytes()).hexdigest(), + COLLECTIVE_SCHEMA_SHA256, + ) + schema = json.loads(SCHEMA_PATH.read_bytes()) + Draft202012Validator.check_schema(schema) + event_schema = json.loads( + (ROOT / "schemas" / "weave" / "v1" / "event.schema.json").read_bytes() + ) + self.assertEqual(set(event_schema["properties"]["kind"]["enum"]), EVENT_KINDS) + upstream_index = json.loads( + ( + COLLECTIVE_ROOT / "vectors" / "exchange" / "v1" / "index.json" + ).read_bytes() + ) + for entry in upstream_index["files"]: + raw = ( + COLLECTIVE_ROOT / "vectors" / "exchange" / "v1" / entry["name"] + ).read_bytes() + self.assertEqual(len(raw), entry["size"]) + self.assertEqual(hashlib.sha256(raw).hexdigest(), entry["sha256"]) + + def test_upstream_provenance_inventory_matches_exact_tree(self) -> None: + provenance = json.loads(PROVENANCE_PATH.read_bytes()) + self.assertEqual(provenance["upstream"]["commit"], COLLECTIVE_MEMORY_COMMIT) + tree = subprocess.run( + ["git", "rev-parse", f"{COLLECTIVE_MEMORY_COMMIT}^{{tree}}"], + cwd=COLLECTIVE_ROOT, + check=True, + capture_output=True, + text=True, + ).stdout.strip() + self.assertEqual(tree, provenance["upstream"]["tree"]) + for item in provenance["items"]: + line = subprocess.run( + ["git", "ls-tree", COLLECTIVE_MEMORY_COMMIT, "--", item["path"]], + cwd=COLLECTIVE_ROOT, + check=True, + capture_output=True, + text=True, + ).stdout.strip() + self.assertTrue(line, item["path"]) + self.assertEqual(line.split()[2], item["git_blob_sha1"]) + raw = (COLLECTIVE_ROOT / item["path"]).read_bytes() + self.assertEqual(len(raw), item["size"]) + self.assertEqual(hashlib.sha256(raw).hexdigest(), item["sha256"]) + + def test_direction_manifests_are_distinct_closed_dm018_denials(self) -> None: + adapter_schema = json.loads( + ( + ROOT / "schemas" / "adapters" / "v0" / "contracts.schema.json" + ).read_bytes() + ) + validator = Draft202012Validator(adapter_schema, format_checker=FormatChecker()) + source = create_source_manifest() + publisher = create_publisher_manifest() + validator.validate(source) + validator.validate(publisher) + self.assertNotEqual(source["adapter_id"], publisher["adapter_id"]) + self.assertNotEqual(source["contracts"], publisher["contracts"]) + self.assertTrue(all(value is False for value in source["authority"].values())) + self.assertTrue( + all(value is False for value in publisher["authority"].values()) + ) + self.assertNotIn("apply", source["capabilities"]) + self.assertNotIn("read", publisher["capabilities"]) + + def test_matrix_vectors_are_canonical_closed_and_self_verifying(self) -> None: + schema = json.loads(SCHEMA_PATH.read_bytes()) + validator = Draft202012Validator(schema, format_checker=FormatChecker()) + index = json.loads((VECTOR_ROOT / "index.json").read_bytes()) + self.assertEqual(index["upstream_commit"], COLLECTIVE_MEMORY_COMMIT) + self.assertEqual(index["upstream_schema_sha256"], COLLECTIVE_SCHEMA_SHA256) + validators = { + "dm.collective-source.profile/v1": validate_source_profile, + "dm.collective-source.preview/v1": validate_source_preview, + "dm.collective-source.receipt/v1": validate_source_receipt, + "dm.collective-publisher.profile/v1": validate_publisher_profile, + "dm.collective-publisher.request/v1": validate_publisher_request_payload, + "dm.collective-publisher.acceptance/v1": ( + validate_publisher_acceptance_payload + ), + } + for entry in index["files"]: + raw = (VECTOR_ROOT / entry["name"]).read_bytes() + self.assertEqual(len(raw), entry["size"]) + self.assertEqual(hashlib.sha256(raw).hexdigest(), entry["sha256"]) + value = json.loads(raw) + accepted = entry["expect"] == "accept" + self.assertEqual(validator.is_valid(value), accepted, entry["name"]) + if accepted: + self.assertEqual(validators[value["schema"]](value), value) + + +class DM036IntegrationTests(RootLedgerFixture): + clock: MutableClock + upstream: UpstreamFixture + source_transport: SourceTransport + publisher_transport: PublisherTransport + source_store: CollectiveSourceStore + publisher_journal: CollectivePublisherJournal + source_adapter: CollectiveSourceAdapter + publisher_adapter: CollectivePublisherAdapter + source_event: dict[str, Any] + + def setUp(self) -> None: + super().setUp() + self.clock = MutableClock() + self.upstream = UpstreamFixture( + self.root_path / "upstream", self.clock, self.state.being_ref + ) + self.source_transport = SourceTransport(self.upstream) + self.publisher_transport = PublisherTransport(self.upstream) + self.source_store = CollectiveSourceStore( + self.root_path / "inbound" / "source.sqlite" + ) + self.publisher_journal = CollectivePublisherJournal( + self.root_path / "outbound" / "publisher.sqlite" + ) + assert_separate_collective_stores(self.source_store, self.publisher_journal) + self.source_adapter = self.make_source() + self.publisher_adapter = self.make_publisher() + self.source_event = self.append( + self.ledger_a, + "legion", + self.state.being_ref, + payload={"summary": "reviewed derived public source"}, + ) + + def make_source(self, fault: Any = lambda _stage: None) -> CollectiveSourceAdapter: + return CollectiveSourceAdapter( + ledger=self.ledger_a, + profile=create_source_profile( + producer_instance="collective:test", + producer_release="collective:release:test", + policy_version="policy:v1", + scope_id="public", + ), + transport=self.source_transport, + store=self.source_store, + signer=self.signers["legion"], + clock=self.clock, + fault=fault, + ) + + def assert_matrix_contract(self, value: Mapping[str, Any]) -> None: + schema = json.loads(SCHEMA_PATH.read_bytes()) + Draft202012Validator(schema, format_checker=FormatChecker()).validate(value) + + def assert_event_contract(self, value: Mapping[str, Any]) -> None: + schema = json.loads(SCHEMA_PATH.read_bytes()) + event_schema = json.loads( + (ROOT / "schemas" / "weave" / "v1" / "event.schema.json").read_bytes() + ) + registry = Registry().with_resource( + schema["$id"], Resource.from_contents(schema) + ) + Draft202012Validator( + event_schema, + registry=registry, + format_checker=FormatChecker(), + ).validate(value) + + def current_source(self) -> dict[str, Any]: + current = self.source_store.current() + self.assertIsNotNone(current) + return cast(dict[str, Any], current) + + def make_publisher( + self, fault: Any = lambda _stage: None + ) -> CollectivePublisherAdapter: + return CollectivePublisherAdapter( + ledger=self.ledger_a, + profile=create_publisher_profile( + requester_id="operator:matrix", + policy_version="policy:v1", + target_ids=["collective:article:alpha"], + ), + transport=self.publisher_transport, + journal=self.publisher_journal, + signer=self.signers["legion"], + consent_issuers={self.upstream.subject["principal"]: self.upstream.subject}, + review_issuers={ + self.upstream.reviewer["principal"]: self.upstream.reviewer + }, + clock=self.clock, + fault=fault, + ) + + def evidence( + self, draft: Mapping[str, Any], preview: Mapping[str, Any] + ) -> tuple[dict[str, Any], dict[str, Any]]: + common = { + "issued_at": utc(self.clock.value - 1), + "not_before": utc(self.clock.value - 60_000), + "not_after": utc(self.clock.value + 600_000), + } + consent = sign_publication_evidence( + draft, + preview, + kind="consent", + evidence_id="consent:test:v1", + issuer=self.upstream.subject, + private_key=self.upstream.subject_key, + **common, + ) + review = sign_publication_evidence( + draft, + preview, + kind="review", + evidence_id="review:test:v1", + issuer=self.upstream.reviewer, + private_key=self.upstream.reviewer_key, + **common, + ) + return consent, review + + def publication_draft( + self, + *, + action: str = "publish", + predecessor: Mapping[str, Any] | None = None, + title: str = "Reviewed collective artifact", + body: str = "Derived public bytes with exact Matrix provenance.", + ) -> dict[str, Any]: + return self.publisher_adapter.draft( + source_event_ids=[self.source_event["event_id"]], + subject_id=self.state.being_ref, + target_id="collective:article:alpha", + action=action, + classification="public", + title="" if action == "tombstone" else title, + body="" if action == "tombstone" else body, + predecessor_receipt_id=None + if predecessor is None + else predecessor["receipt_id"], + predecessor_receipt_hash=None + if predecessor is None + else predecessor["receipt_hash"], + ) + + def publish( + self, + *, + action: str = "publish", + predecessor: Mapping[str, Any] | None = None, + key: str = "idem:test:v1", + ) -> dict[str, Any]: + draft = self.publication_draft(action=action, predecessor=predecessor) + preview = self.publisher_adapter.preview(draft) + consent, review = self.evidence(draft, preview) + event = self.publisher_adapter.submit( + draft, + preview, + idempotency_key=key, + consent=consent, + review=review, + ) + return self.publisher_adapter.execute(event["event_id"]) + + def test_inbound_initial_retry_successor_and_tombstone_remain_quarantined( + self, + ) -> None: + preview = self.source_adapter.preview() + self.assertIsNone(self.source_store.current()) + receipt = self.source_adapter.apply(preview) + self.assert_matrix_contract(receipt) + self.assertEqual(receipt, self.source_adapter.apply(preview)) + self.assertEqual(receipt["body"]["outcomes"]["personal_memory_assertions"], 0) + self.assertEqual(receipt["body"]["decision"], "quarantined") + self.assertEqual(self.source_adapter.reconcile(receipt)["effect"], "verified") + event = self.ledger_a.event(receipt["body"]["import_event_id"]) + self.assertIsNotNone(event) + self.assert_event_contract(cast(Mapping[str, Any], event)) + verify_event(event, self.authority) + self.assertFalse( + any(item["kind"] == "memory.recorded" for item in self.ledger_a.events()) + ) + + self.upstream.successor() + second = self.source_adapter.apply(self.source_adapter.preview()) + self.assertEqual( + second["body"]["predecessor_generation"], receipt["body"]["generation_id"] + ) + with self.assertRaisesRegex( + CollectiveMemoryError, "collective_source_effect_truth_discrepancy" + ): + self.source_adapter.apply(preview) + self.upstream.successor(tombstone=True) + third = self.source_adapter.apply(self.source_adapter.preview()) + self.assertEqual(third["body"]["outcomes"]["tombstoned"], 1) + self.assertEqual( + self.current_source()["body"]["artifacts"][0]["state"], "tombstone" + ) + + def test_inbound_invalid_refresh_and_outage_preserve_prior_generation(self) -> None: + first = self.source_adapter.apply(self.source_adapter.preview()) + active = self.source_store.current() + self.upstream.successor() + self.source_transport.corrupt_object = True + with self.assertRaisesRegex( + CollectiveMemoryError, "collective_content_mismatch" + ): + self.source_adapter.preview() + self.assertEqual(self.source_store.current(), active) + self.source_transport.corrupt_object = False + self.source_transport.mix_page = True + with self.assertRaisesRegex( + CollectiveMemoryError, "collective_mixed_generation" + ): + self.source_adapter.preview() + self.assertEqual( + self.current_source()["generation_id"], + first["body"]["generation_id"], + ) + self.source_transport.mix_page = False + self.source_transport.fail = True + with self.assertRaisesRegex( + CollectiveMemoryError, "collective_source_unavailable" + ): + self.source_adapter.preview() + self.assertEqual(self.source_store.current(), active) + + def test_inbound_contract_rejects_adversarial_manifests_and_partial_page( + self, + ) -> None: + manifest = self.upstream.export.create("public") + + def mutate_unknown(value: dict[str, Any]) -> None: + value["body"]["host_path"] = "/private/corpus" + + def mutate_version(value: dict[str, Any]) -> None: + value["schema"] = "collective-export-manifest/v2" + + def mutate_duplicate(value: dict[str, Any]) -> None: + value["body"]["artifacts"].append( + copy.deepcopy(value["body"]["artifacts"][0]) + ) + value["body"]["artifact_count"] += 1 + value["body"]["total_content_bytes"] *= 2 + + def mutate_missing_author(value: dict[str, Any]) -> None: + value["body"]["artifacts"][0]["authors"] = [] + + def mutate_missing_license(value: dict[str, Any]) -> None: + del value["body"]["artifacts"][0]["license"] + + def mutate_missing_consent(value: dict[str, Any]) -> None: + del value["body"]["artifacts"][0]["consent_scope"] + + def mutate_missing_source(value: dict[str, Any]) -> None: + value["body"]["artifacts"][0]["source_refs"] = [] + + def mutate_media_type(value: dict[str, Any]) -> None: + value["body"]["artifacts"][0]["media_type"] = "application/x-executable" + + def mutate_traversal(value: dict[str, Any]) -> None: + value["body"]["artifacts"][0]["relative_path"] = "../../private" + + def mutate_oversized(value: dict[str, Any]) -> None: + value["body"]["artifacts"][0]["content_length"] = 2 * 1024 * 1024 + 1 + value["body"]["total_content_bytes"] = 2 * 1024 * 1024 + 1 + + mutations = { + "unknown-field": mutate_unknown, + "unknown-version": mutate_version, + "duplicate-id": mutate_duplicate, + "missing-author": mutate_missing_author, + "missing-license": mutate_missing_license, + "missing-consent": mutate_missing_consent, + "missing-source": mutate_missing_source, + "unknown-media-type": mutate_media_type, + "traversal-field": mutate_traversal, + "oversized-artifact": mutate_oversized, + } + for name, mutation in mutations.items(): + with self.subTest(name=name): + candidate = copy.deepcopy(manifest) + mutation(candidate) + with self.assertRaises(CollectiveMemoryError): + validate_export_manifest(candidate) + + self.source_transport.page_mutator = lambda page: page.update( + {"artifacts": [], "next_cursor": None} + ) + with self.assertRaisesRegex( + CollectiveMemoryError, "collective_partial_or_mixed_generation" + ): + self.source_adapter.preview() + self.assertIsNone(self.source_store.current()) + + def test_inbound_rejects_artifact_fork_and_symlink_source(self) -> None: + first = self.source_adapter.apply(self.source_adapter.preview()) + self.upstream.successor() + self.upstream.catalog["entries"][0]["predecessor_artifact_id"] = ( + "artifact:test:alien" + ) + with self.assertRaisesRegex( + CollectiveMemoryError, "collective_source_invalid_response" + ): + self.source_adapter.apply(self.source_adapter.preview()) + self.assertEqual( + self.current_source()["generation_id"], + first["body"]["generation_id"], + ) + + symlink_upstream = UpstreamFixture( + self.root_path / "symlink-upstream", self.clock, self.state.being_ref + ) + outside = self.root_path / "outside.md" + outside.write_text("must not cross symlink\n", encoding="utf-8") + symlink = symlink_upstream.root / "mapa" / "linked.md" + symlink.symlink_to(outside) + symlink_upstream.catalog["entries"] = [ + { + **symlink_upstream.catalog["entries"][0], + "artifact_id": "artifact:test:symlink", + "logical_id": "logical:test:symlink", + "relative_path": "mapa/linked.md", + "predecessor_artifact_id": None, + } + ] + with self.assertRaisesRegex( + symlink_upstream.module.ExchangeError, "unsafe_path" + ): + symlink_upstream.export.create("public") + + def test_inbound_paginates_bounded_generation_without_large_ledger_event( + self, + ) -> None: + entries = [] + for number in range(257): + name = f"item-{number:03d}" + relative = f"mapa/{name}.md" + (self.upstream.root / relative).write_text( + f"# Collective item {number}\n", encoding="utf-8" + ) + entries.append( + { + "artifact_id": f"artifact:test:{name}", + "logical_id": f"logical:test:{name}", + "relative_path": relative, + "media_type": "text/markdown; charset=utf-8", + "authors": ["author:test"], + "source_refs": [ + {"id": f"source:test:{name}", "hash": f"{number:064x}"} + ], + "license": "MIT", + "consent_scope": "public", + "classification": "public", + "predecessor_artifact_id": None, + "state": "active", + } + ) + self.upstream.catalog["entries"] = entries + preview = self.source_adapter.preview() + self.assertEqual(self.source_transport.page_calls, 2) + receipt = self.source_adapter.apply(preview) + self.assertEqual(receipt["body"]["artifact_count"], 257) + event = self.ledger_a.event(receipt["body"]["import_event_id"]) + self.assertLess(len(canonical_bytes(event)), 16 * 1024) + + def test_inbound_recovers_crash_after_ledger_without_mixed_head(self) -> None: + def fail(stage: str) -> None: + if stage == "source-ledger-appended": + raise Crash(stage) + + broken = self.make_source(fail) + with self.assertRaises(Crash): + broken.apply(broken.preview()) + self.assertIsNone(self.source_store.current()) + recovered = self.make_source() + result = recovered.recover() + self.assertEqual(result[0]["outcome"], "activated") + receipt = self.source_store.recorded_receipt(result[0]["generation_id"]) + self.assertIsNotNone(receipt) + self.assertEqual( + recovered.reconcile(cast(Mapping[str, Any], receipt))["effect"], + "verified", + ) + + def test_inbound_recovers_crash_after_prepare_before_ledger(self) -> None: + def fail(stage: str) -> None: + if stage == "source-prepared": + raise Crash(stage) + + broken = self.make_source(fail) + with self.assertRaises(Crash): + broken.apply(broken.preview()) + self.assertIsNone(self.source_store.current()) + self.assertFalse( + any(item["kind"] == "source.imported" for item in self.ledger_a.events()) + ) + recovered = self.make_source() + result = recovered.recover() + self.assertEqual(result[0]["outcome"], "activated") + receipt = self.source_store.recorded_receipt(result[0]["generation_id"]) + self.assertIsNotNone(receipt) + self.assertEqual( + recovered.reconcile(cast(Mapping[str, Any], receipt))["effect"], + "verified", + ) + + def test_inbound_rebuilds_active_projection_from_ledger_and_source_log( + self, + ) -> None: + first = self.source_adapter.apply(self.source_adapter.preview()) + self.upstream.successor() + second = self.source_adapter.apply(self.source_adapter.preview()) + with closing(self.source_store.connect()) as database: + database.execute("UPDATE generations SET state='superseded'") + self.assertIsNone(self.source_store.current()) + + rebuilt = self.source_adapter.rebuild() + self.assertEqual(rebuilt["accepted_generation_count"], 2) + self.assertEqual(rebuilt["head_generation_id"], second["body"]["generation_id"]) + self.assertEqual( + self.current_source()["generation_id"], + second["body"]["generation_id"], + ) + self.assertEqual(self.source_adapter.reconcile(second)["effect"], "verified") + self.assertEqual( + self.source_store.recorded_receipt(first["body"]["generation_id"]), + first, + ) + with closing(self.source_store.connect()) as database: + database.execute( + "UPDATE artifacts SET content=? WHERE generation_id=? " + "AND content IS NOT NULL", + (b"tampered source-log content", second["body"]["generation_id"]), + ) + with self.assertRaisesRegex( + CollectiveMemoryError, "collective_source_log_drift" + ): + self.source_adapter.reconcile(second) + + def test_inbound_offline_catch_up_walks_historical_manifests(self) -> None: + first_manifest = self.upstream.export.create("public") + self.upstream.successor() + second_manifest = self.upstream.export.create("public") + self.upstream.successor(tombstone=True) + third_manifest = self.upstream.export.create("public") + with self.assertRaisesRegex(CollectiveMemoryError, "collective_generation_gap"): + self.source_adapter.preview() + chain = self.source_adapter.preview_catch_up() + self.assertEqual( + [item["manifest"]["generation_id"] for item in chain], + [ + first_manifest["generation_id"], + second_manifest["generation_id"], + third_manifest["generation_id"], + ], + ) + receipts = self.source_adapter.catch_up() + self.assertEqual(len(receipts), 3) + self.assertEqual( + self.current_source()["generation_id"], + third_manifest["generation_id"], + ) + self.assertEqual(receipts[-1]["body"]["outcomes"]["tombstoned"], 1) + + def test_outbound_real_boundary_publish_response_loss_and_replay(self) -> None: + draft = self.publication_draft() + preview = self.publisher_adapter.preview(draft) + consent, review = self.evidence(draft, preview) + request_event = self.publisher_adapter.submit( + draft, + preview, + idempotency_key="idem:response-loss", + consent=consent, + review=review, + ) + self.publisher_transport.lose_after_apply = True + with self.assertRaisesRegex( + CollectiveMemoryError, "collective_publisher_unavailable" + ): + self.publisher_adapter.execute(request_event["event_id"]) + result = self.publisher_adapter.execute(request_event["event_id"]) + replay = self.publisher_adapter.execute(request_event["event_id"]) + self.assertEqual(result, replay) + self.assert_event_contract(request_event) + self.assert_event_contract(result["event"]) + acceptance = validate_publisher_acceptance_payload(result["acceptance"]) + self.assert_matrix_contract(acceptance) + self.assertEqual( + self.publisher_adapter.reconcile(result["event"]["event_id"])["effect"], + "verified", + ) + self.assertEqual( + acceptance["body"]["provider_receipt"]["body"]["source_refs"], + [ + { + "id": self.source_event["event_id"], + "hash": self.source_event["content_hash"], + } + ], + ) + verify_event(result["event"], self.authority) + + def test_outbound_concurrent_writers_accept_exactly_one_target_request( + self, + ) -> None: + candidates = [] + for number in (1, 2): + draft = self.publication_draft( + title=f"Concurrent {number}", body=f"Concurrent reviewed body {number}." + ) + preview = self.publisher_adapter.preview(draft) + common = { + "issued_at": utc(self.clock.value - 1), + "not_before": utc(self.clock.value - 60_000), + "not_after": utc(self.clock.value + 600_000), + } + consent = sign_publication_evidence( + draft, + preview, + kind="consent", + evidence_id=f"consent:concurrent:{number}", + issuer=self.upstream.subject, + private_key=self.upstream.subject_key, + **common, + ) + review = sign_publication_evidence( + draft, + preview, + kind="review", + evidence_id=f"review:concurrent:{number}", + issuer=self.upstream.reviewer, + private_key=self.upstream.reviewer_key, + **common, + ) + candidates.append((number, draft, preview, consent, review)) + + def submit(candidate: tuple[Any, ...]) -> Any: + number, draft, preview, consent, review = candidate + try: + return self.publisher_adapter.submit( + draft, + preview, + idempotency_key=f"idem:concurrent:{number}", + consent=consent, + review=review, + ) + except CollectiveMemoryError as exception: + return exception + + with ThreadPoolExecutor(max_workers=2) as pool: + outcomes = list(pool.map(submit, candidates)) + events = [item for item in outcomes if isinstance(item, dict)] + errors = [item for item in outcomes if isinstance(item, CollectiveMemoryError)] + self.assertEqual(len(events), 1) + self.assertEqual(len(errors), 1) + self.assertEqual(errors[0].code, "collective_publication_target_pending") + accepted = self.publisher_adapter.execute(events[0]["event_id"]) + self.assertEqual( + accepted["acceptance"]["body"]["provider_receipt"]["body"]["status"], + "committed", + ) + + def test_outbound_successor_and_reviewed_tombstone_are_monotonic(self) -> None: + first = self.publish() + first_receipt = first["acceptance"]["body"]["provider_receipt"] + second = self.publish( + action="successor", predecessor=first_receipt, key="idem:test:v2" + ) + second_receipt = second["acceptance"]["body"]["provider_receipt"] + self.assertEqual(second["event"]["supersedes"], first["event"]["event_id"]) + third = self.publish( + action="tombstone", predecessor=second_receipt, key="idem:test:tombstone" + ) + self.assertEqual(third["event"]["supersedes"], second["event"]["event_id"]) + self.assertEqual( + third["acceptance"]["body"]["provider_receipt"]["body"]["after"]["state"], + "tombstone", + ) + + def test_real_export_matrix_quarantine_publish_search_atlas_and_tombstone( + self, + ) -> None: + real = UpstreamFixture( + self.root_path / "real-upstream", + self.clock, + self.state.being_ref, + real=True, + ) + (real.root / "mapa" / "unrelated.md").write_text( + "# Unrelated\n\nMust survive every transaction.\n", encoding="utf-8" + ) + source_store = CollectiveSourceStore( + self.root_path / "real-inbound" / "source.sqlite" + ) + source_adapter = CollectiveSourceAdapter( + ledger=self.ledger_a, + profile=create_source_profile( + producer_instance="collective:test", + producer_release="collective:release:test", + policy_version="policy:v1", + scope_id="public", + ), + transport=SourceTransport(real), + store=source_store, + signer=self.signers["legion"], + clock=self.clock, + ) + inbound = source_adapter.apply(source_adapter.preview()) + self.assertEqual(inbound["body"]["decision"], "quarantined") + + journal = CollectivePublisherJournal( + self.root_path / "real-outbound" / "publisher.sqlite" + ) + publisher = CollectivePublisherAdapter( + ledger=self.ledger_a, + profile=create_publisher_profile( + requester_id="operator:matrix", + policy_version="policy:v1", + target_ids=["collective:article:alpha"], + ), + transport=PublisherTransport(real), + journal=journal, + signer=self.signers["legion"], + consent_issuers={real.subject["principal"]: real.subject}, + review_issuers={real.reviewer["principal"]: real.reviewer}, + clock=self.clock, + ) + + def reviewed( + action: str, + predecessor: Mapping[str, Any] | None, + suffix: str, + ) -> dict[str, Any]: + draft = publisher.draft( + source_event_ids=[self.source_event["event_id"]], + subject_id=self.state.being_ref, + target_id="collective:article:alpha", + action=action, + classification="public", + title="" if action == "tombstone" else "Real Matrix publication", + body="" + if action == "tombstone" + else "Visible through real FTS and Atlas projections.", + predecessor_receipt_id=None + if predecessor is None + else predecessor["receipt_id"], + predecessor_receipt_hash=None + if predecessor is None + else predecessor["receipt_hash"], + ) + preview = publisher.preview(draft) + common = { + "issued_at": utc(self.clock.value - 1), + "not_before": utc(self.clock.value - 60_000), + "not_after": utc(self.clock.value + 600_000), + } + consent = sign_publication_evidence( + draft, + preview, + kind="consent", + evidence_id=f"consent:real:{suffix}", + issuer=real.subject, + private_key=real.subject_key, + **common, + ) + review = sign_publication_evidence( + draft, + preview, + kind="review", + evidence_id=f"review:real:{suffix}", + issuer=real.reviewer, + private_key=real.reviewer_key, + **common, + ) + request = publisher.submit( + draft, + preview, + idempotency_key=f"idem:real:{suffix}", + consent=consent, + review=review, + ) + return publisher.execute(request["event_id"]) + + published = reviewed("publish", None, "publish") + receipt = published["acceptance"]["body"]["provider_receipt"] + with closing( + sqlite3.connect(f"file:{real.data / 'index.db'}?mode=ro", uri=True) + ) as db: + self.assertEqual(db.execute("PRAGMA integrity_check").fetchone()[0], "ok") + self.assertEqual( + db.execute( + "SELECT count(*) FROM docs WHERE doc_id='published/alpha.md'" + ).fetchone()[0], + 1, + ) + self.assertEqual( + db.execute( + "SELECT count(*) FROM docs WHERE doc_id='mapa/unrelated.md'" + ).fetchone()[0], + 1, + ) + ui_db = (real.data / "ui" / "ui_v2.db").resolve() + with closing(sqlite3.connect(f"file:{ui_db}?mode=ro", uri=True)) as db: + self.assertEqual(db.execute("PRAGMA integrity_check").fetchone()[0], "ok") + self.assertEqual( + db.execute( + "SELECT count(*) FROM docs WHERE doc_id='published/alpha.md'" + ).fetchone()[0], + 1, + ) + tombstone = reviewed("tombstone", receipt, "tombstone") + self.assertEqual( + tombstone["acceptance"]["body"]["provider_receipt"]["body"]["after"][ + "state" + ], + "tombstone", + ) + self.assertTrue((real.root / "mapa" / "unrelated.md").is_file()) + self.assertNotEqual(source_store.path, real.data / "index.db") + for database in (source_store.path, journal.path, real.data / "index.db"): + self.assertFalse(Path(str(database) + "-wal").exists()) + self.assertFalse(Path(str(database) + "-shm").exists()) + + def test_direction_separation_and_exact_review_fail_closed(self) -> None: + with self.assertRaisesRegex(CollectiveMemoryError, "direction_store_shared"): + assert_separate_collective_stores( + self.source_store, + CollectivePublisherJournal(self.source_store.path), + ) + with self.assertRaises(RuntimeError): + self.source_transport("apply", {}) + with self.assertRaises(RuntimeError): + self.publisher_transport("manifest", {}) + draft = self.publication_draft() + preview = self.publisher_adapter.preview(draft) + consent, review = self.evidence(draft, preview) + changed = copy.deepcopy(draft) + changed["body"] += " changed" + with self.assertRaises(CollectiveMemoryError): + self.publisher_adapter.submit( + changed, + preview, + idempotency_key="idem:changed", + consent=consent, + review=review, + ) + changed_checkpoint = copy.deepcopy(draft) + changed_checkpoint["source_checkpoint"]["hash"] = "0" * 64 + with self.assertRaisesRegex( + CollectiveMemoryError, "collective_publication_checkpoint_mismatch" + ): + self.publisher_adapter.preview(changed_checkpoint) + wrong_target_consent = copy.deepcopy(consent) + wrong_target_consent["body"]["target_id"] = "collective:article:other" + wrong_target_consent["signature"]["value"] = b64url( + self.upstream.subject_key.sign( + canonical_bytes(wrong_target_consent["body"]) + ) + ) + with self.assertRaisesRegex( + CollectiveMemoryError, "collective_evidence_binding_mismatch" + ): + self.publisher_adapter.submit( + draft, + preview, + idempotency_key="idem:wrong-target-approval", + consent=wrong_target_consent, + review=review, + ) + self_review = copy.deepcopy(review) + self_review["body"]["issuer"] = self.state.being_ref + with self.assertRaises(CollectiveMemoryError): + self.publisher_adapter.submit( + draft, + preview, + idempotency_key="idem:self-review", + consent=consent, + review=self_review, + ) + for secret_draft in ( + self.publication_draft(title="api_key=supersecretvalue"), + self.publication_draft(body="Bearer " + "a" * 32), + self.publication_draft( + body="[private](https://operator:supersecret@internal.invalid/path)" + ), + ): + with self.assertRaisesRegex( + CollectiveMemoryError, "collective_secret_detected" + ): + self.publisher_adapter.preview(secret_draft) + revoked_reviewer = evidence_issuer( + self.upstream.reviewer["principal"], + self.upstream.reviewer_key.public_key(), + revoked_at_ms=self.clock.value - 1, + ) + revoked_adapter = CollectivePublisherAdapter( + ledger=self.ledger_a, + profile=self.publisher_adapter.profile, + transport=self.publisher_transport, + journal=CollectivePublisherJournal( + self.root_path / "outbound-revoked" / "publisher.sqlite" + ), + signer=self.signers["legion"], + consent_issuers={self.upstream.subject["principal"]: self.upstream.subject}, + review_issuers={revoked_reviewer["principal"]: revoked_reviewer}, + clock=self.clock, + ) + with self.assertRaisesRegex( + CollectiveMemoryError, "collective_evidence_key_revoked" + ): + revoked_adapter.submit( + draft, + preview, + idempotency_key="idem:revoked-review", + consent=consent, + review=review, + ) + + def test_outbound_rejects_expired_or_revoked_consent_and_source_drift( + self, + ) -> None: + draft = self.publication_draft() + preview = self.publisher_adapter.preview(draft) + _consent, review = self.evidence(draft, preview) + expired = sign_publication_evidence( + draft, + preview, + kind="consent", + evidence_id="consent:expired", + issuer=self.upstream.subject, + private_key=self.upstream.subject_key, + issued_at=utc(self.clock.value - 120_000), + not_before=utc(self.clock.value - 120_000), + not_after=utc(self.clock.value - 1), + ) + with self.assertRaisesRegex( + CollectiveMemoryError, "collective_evidence_expired" + ): + self.publisher_adapter.submit( + draft, + preview, + idempotency_key="idem:expired-consent", + consent=expired, + review=review, + ) + + revoked_subject = evidence_issuer( + self.upstream.subject["principal"], + self.upstream.subject_key.public_key(), + revoked_at_ms=self.clock.value - 1, + ) + revoked_adapter = CollectivePublisherAdapter( + ledger=self.ledger_a, + profile=self.publisher_adapter.profile, + transport=self.publisher_transport, + journal=CollectivePublisherJournal( + self.root_path / "outbound-revoked-consent" / "publisher.sqlite" + ), + signer=self.signers["legion"], + consent_issuers={revoked_subject["principal"]: revoked_subject}, + review_issuers={ + self.upstream.reviewer["principal"]: self.upstream.reviewer + }, + clock=self.clock, + ) + consent, _review = self.evidence(draft, preview) + with self.assertRaisesRegex( + CollectiveMemoryError, "collective_evidence_key_revoked" + ): + revoked_adapter.submit( + draft, + preview, + idempotency_key="idem:revoked-consent", + consent=consent, + review=review, + ) + + self.ledger_a.append_local( + kind="experience.observed", + subject=self.state.being_ref, + payload={"summary": "source correction"}, + signer=self.signers["legion"], + supersedes=self.source_event["event_id"], + occurred_at_ms=NOW, + ) + with self.assertRaisesRegex( + CollectiveMemoryError, "collective_publication_source_drift" + ): + self.publisher_adapter.submit( + draft, + preview, + idempotency_key="idem:source-drift", + consent=consent, + review=review, + ) + + def test_outbound_rejects_provider_preview_injection_untracked_tombstone_and_drift( + self, + ) -> None: + draft = self.publication_draft() + self.publisher_transport.preview_mutator = lambda value: value.update( + {"host_path": "/private/corpus"} + ) + with self.assertRaises(CollectiveMemoryError): + self.publisher_adapter.preview(draft) + self.publisher_transport.preview_mutator = None + + with self.assertRaises(CollectiveMemoryError): + self.publisher_adapter.draft( + source_event_ids=[self.source_event["event_id"]], + subject_id=self.state.being_ref, + target_id="collective:article:alpha", + action="tombstone", + classification="public", + title="", + body="", + predecessor_receipt_id=None, + predecessor_receipt_hash=None, + ) + + published = self.publish(key="idem:target-drift") + target = self.upstream.root / "published" / "alpha.md" + target.write_text("drifted outside the transaction\n", encoding="utf-8") + with self.assertRaisesRegex( + CollectiveMemoryError, "collective_effect_truth_discrepancy" + ): + self.publisher_adapter.reconcile(published["event"]["event_id"]) + + def test_outbound_recovers_after_prepare_before_request_event(self) -> None: + def fail(stage: str) -> None: + if stage == "publisher-prepared": + raise Crash(stage) + + broken = self.make_publisher(fail) + draft = self.publication_draft() + preview = broken.preview(draft) + consent, review = self.evidence(draft, preview) + with self.assertRaises(Crash): + broken.submit( + draft, + preview, + idempotency_key="idem:crash-prepared", + consent=consent, + review=review, + ) + self.assertEqual(len(self.publisher_journal.rows(states=["prepared"])), 1) + self.assertFalse( + any( + event["kind"] == "collective.publication.requested" + for event in self.ledger_a.events() + ) + ) + recovered = self.make_publisher() + outcomes = recovered.recover() + self.assertEqual(outcomes[0]["outcome"], "accepted") + self.assertEqual(len(self.publisher_journal.rows(states=["accepted"])), 1) + + def test_outbound_recovers_after_effect_before_matrix_acceptance(self) -> None: + def fail(stage: str) -> None: + if stage == "publisher-effected": + raise Crash(stage) + + broken = self.make_publisher(fail) + draft = broken.draft( + source_event_ids=[self.source_event["event_id"]], + subject_id=self.state.being_ref, + target_id="collective:article:alpha", + action="publish", + classification="public", + title="Crash recovery", + body="Exact reviewed recovery bytes.", + predecessor_receipt_id=None, + predecessor_receipt_hash=None, + ) + preview = broken.preview(draft) + consent, review = self.evidence(draft, preview) + request_event = broken.submit( + draft, + preview, + idempotency_key="idem:crash-effected", + consent=consent, + review=review, + ) + with self.assertRaises(Crash): + broken.execute(request_event["event_id"]) + recovered = self.make_publisher() + outcomes = recovered.recover() + self.assertEqual(outcomes[0]["outcome"], "accepted") + rows = recovered.journal.rows(states=["accepted"]) + self.assertEqual(len(rows), 1) + self.assertEqual( + recovered.reconcile(rows[0]["acceptance_event_id"])["effect"], "verified" + ) + + def test_outbound_recovers_after_acceptance_event_before_journal_commit( + self, + ) -> None: + def fail(stage: str) -> None: + if stage == "publisher-ledger-appended": + raise Crash(stage) + + broken = self.make_publisher(fail) + draft = self.publication_draft() + preview = broken.preview(draft) + consent, review = self.evidence(draft, preview) + request = broken.submit( + draft, + preview, + idempotency_key="idem:crash-acceptance-ledger", + consent=consent, + review=review, + ) + with self.assertRaises(Crash): + broken.execute(request["event_id"]) + self.assertEqual(len(self.publisher_journal.rows(states=["effected"])), 1) + self.assertEqual( + sum( + event["kind"] == "collective.publication.receipted" + for event in self.ledger_a.events() + ), + 1, + ) + recovered = self.make_publisher() + outcomes = recovered.recover() + self.assertEqual(outcomes[0]["outcome"], "accepted") + self.assertEqual( + sum( + event["kind"] == "collective.publication.receipted" + for event in self.ledger_a.events() + ), + 1, + ) diff --git a/tests/test_dm041_hermes_body.py b/tests/test_dm041_hermes_body.py index 1930ab1..e130af8 100644 --- a/tests/test_dm041_hermes_body.py +++ b/tests/test_dm041_hermes_body.py @@ -502,7 +502,11 @@ def test_python_venv_launcher_is_preserved_and_target_is_bound(self) -> None: def test_profile_is_deterministic_exclusive_and_native_memory_free(self) -> None: manifest = self.create() self.assertEqual(manifest, verify_profile(self.plan)) - self.assertEqual(len(manifest["matrix_package"]["modules"]), 47) + self.assertEqual(len(manifest["matrix_package"]["modules"]), 48) + self.assertIn( + "collective_memory.py", + {item["name"] for item in manifest["matrix_package"]["modules"]}, + ) self.assertEqual( len(manifest["matrix_package"]["tree_sha256"]), 64, diff --git a/tests/test_package_scaffold.py b/tests/test_package_scaffold.py index 088eb03..70afe91 100644 --- a/tests/test_package_scaffold.py +++ b/tests/test_package_scaffold.py @@ -162,6 +162,7 @@ def test_allowlists_are_frozen(self) -> None: "src/daimon_matrix/client.py", "src/daimon_matrix/cluster.py", "src/daimon_matrix/codex_body.py", + "src/daimon_matrix/collective_memory.py", "src/daimon_matrix/communication.py", "src/daimon_matrix/conformance.py", "src/daimon_matrix/curator.py", @@ -215,6 +216,7 @@ def test_allowlists_are_frozen(self) -> None: "daimon_matrix/client.py", "daimon_matrix/cluster.py", "daimon_matrix/codex_body.py", + "daimon_matrix/collective_memory.py", "daimon_matrix/communication.py", "daimon_matrix/conformance.py", "daimon_matrix/curator.py", @@ -299,7 +301,16 @@ def test_zip_symlink_is_rejected_without_extraction(self) -> None: scan_archive(archive_path) def test_checkout_secret_scan_is_clean(self) -> None: - self.assertGreater(scan_path(ROOT), 0) + excluded_roots: tuple[Path, ...] = () + configured = os.environ.get("COLLECTIVE_MEMORY_CONTRACT_ROOT") + if configured is not None: + collective_root = Path(configured).resolve() + if ROOT in collective_root.parents: + self.assertEqual( + collective_root, (ROOT / ".collective-memory-contract").resolve() + ) + excluded_roots = (collective_root,) + self.assertGreater(scan_path(ROOT, excluded_roots=excluded_roots), 0) if __name__ == "__main__": diff --git a/tools/check_distribution.py b/tools/check_distribution.py index 258cb23..c7900a0 100644 --- a/tools/check_distribution.py +++ b/tools/check_distribution.py @@ -43,6 +43,7 @@ "src/daimon_matrix/client.py", "src/daimon_matrix/cluster.py", "src/daimon_matrix/codex_body.py", + "src/daimon_matrix/collective_memory.py", "src/daimon_matrix/communication.py", "src/daimon_matrix/conformance.py", "src/daimon_matrix/curator.py", @@ -95,6 +96,7 @@ "daimon_matrix/client.py", "daimon_matrix/cluster.py", "daimon_matrix/codex_body.py", + "daimon_matrix/collective_memory.py", "daimon_matrix/communication.py", "daimon_matrix/conformance.py", "daimon_matrix/curator.py", @@ -303,6 +305,7 @@ def inspect_sdist(path: Path, source_root: Path) -> dict[str, object]: "src/daimon_matrix/cli.py", "src/daimon_matrix/client.py", "src/daimon_matrix/cluster.py", + "src/daimon_matrix/collective_memory.py", "src/daimon_matrix/communication.py", "src/daimon_matrix/conformance.py", "src/daimon_matrix/curator.py", @@ -439,6 +442,9 @@ def inspect_wheel(path: Path, source_root: Path) -> dict[str, object]: "daimon_matrix/cli.py": "src/daimon_matrix/cli.py", "daimon_matrix/client.py": "src/daimon_matrix/client.py", "daimon_matrix/cluster.py": "src/daimon_matrix/cluster.py", + "daimon_matrix/collective_memory.py": ( + "src/daimon_matrix/collective_memory.py" + ), "daimon_matrix/communication.py": "src/daimon_matrix/communication.py", "daimon_matrix/conformance.py": "src/daimon_matrix/conformance.py", "daimon_matrix/curator.py": "src/daimon_matrix/curator.py", diff --git a/tools/generate_dm036_vectors.py b/tools/generate_dm036_vectors.py new file mode 100755 index 0000000..c9fef7e --- /dev/null +++ b/tools/generate_dm036_vectors.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python3 +"""Generate deterministic DM-036 collective-memory interop vectors.""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import os +import sys +import tempfile +from pathlib import Path +from typing import Any, cast + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / "src")) + +import daimon_matrix.collective_memory as collective # noqa: E402 +from daimon_matrix.canonical import canonical_bytes # noqa: E402 + +DEFAULT_OUTPUT = ROOT / "vectors" / "collective-memory" / "v1" +UPSTREAM_ROOT = Path( + os.environ.get( + "COLLECTIVE_MEMORY_CONTRACT_ROOT", str(ROOT.parent / "collective-memory") + ) +).resolve() +UPSTREAM = UPSTREAM_ROOT / "vectors" / "exchange" / "v1" +NOW = 1_800_000_000_000 +IMPORT_EVENT = "36000000-0000-4000-8000-000000000001" +REQUEST_EVENT = "36000000-0000-4000-8000-000000000002" + + +def read(name: str) -> dict[str, Any]: + return cast(dict[str, Any], json.loads((UPSTREAM / name).read_bytes())) + + +def objects() -> dict[str, dict[str, Any]]: + manifest = collective.validate_export_manifest(read("export-manifest.json")) + source_core = { + "manifest": manifest, + "content_bindings": [ + { + "artifact_id": item["artifact_id"], + "content_hash": item["content_hash"], + "content_length": item["content_length"], + "state": item["state"], + } + for item in manifest["body"]["artifacts"] + ], + } + source_preview: dict[str, Any] = { + "schema": collective.SOURCE_PREVIEW_SCHEMA, + "preview_id": collective._derived( + "dm:collective-source-preview:v1:", + collective.SOURCE_PREVIEW_DOMAIN, + source_core, + ), + "preview_hash": hashlib.sha256(canonical_bytes(source_core)).hexdigest(), + **source_core, + } + source_preview = collective.validate_source_preview(source_preview) + source_receipt = collective._source_receipt( + manifest, + preview_id=source_preview["preview_id"], + preview_hash=source_preview["preview_hash"], + source_log_hash="4" * 64, + import_event_id=IMPORT_EVENT, + imported_at_ms=NOW, + ) + source_receipt = collective.validate_source_receipt(source_receipt) + + upstream_request = read("publication-request.json") + upstream_preview = read("publication-preview.json") + upstream_plan = read("publication-plan.json") + upstream_receipt = read("publication-receipt.json") + upstream_reconciliation = read("publication-reconciliation.json") + consent_hash = hashlib.sha256( + canonical_bytes(upstream_request["consent"]) + ).hexdigest() + review_hash = hashlib.sha256( + canonical_bytes(upstream_request["review"]) + ).hexdigest() + request_id = "dm:collective-publisher-operation:v1:" + "A" * 43 + publisher_request = collective._request_summary( + upstream_request, + upstream_preview, + upstream_plan, + request_id=request_id, + consent_hash=consent_hash, + review_hash=review_hash, + requested_at_ms=NOW, + ) + publisher_request = collective.validate_publisher_request_payload(publisher_request) + publisher_acceptance = collective._publisher_acceptance( + publisher_request, + upstream_receipt, + upstream_reconciliation, + request_event_id=REQUEST_EVENT, + request_event_hash="5" * 64, + accepted_at_ms=NOW, + ) + publisher_acceptance = collective.validate_publisher_acceptance_payload( + publisher_acceptance + ) + negative = dict(source_receipt) + negative["host_path"] = "/tmp/forbidden" + return { + "source-profile.json": collective.create_source_profile( + producer_instance="collective:vector", + producer_release="collective:release:vector", + policy_version="policy:v1", + scope_id="public", + ), + "source-preview.json": source_preview, + "source-receipt.json": source_receipt, + "publisher-profile.json": collective.create_publisher_profile( + requester_id="operator:matrix-vector", + policy_version="policy:v1", + target_ids=["collective:article:vector"], + ), + "publisher-request.json": publisher_request, + "publisher-acceptance.json": publisher_acceptance, + "negative-host-path.json": negative, + } + + +def write(output: Path) -> None: + output.mkdir(parents=True, exist_ok=True) + entries = [] + for name, value in sorted(objects().items()): + raw = canonical_bytes(value) + b"\n" + (output / name).write_bytes(raw) + entries.append( + { + "name": name, + "sha256": hashlib.sha256(raw).hexdigest(), + "size": len(raw), + "expect": "reject" if name.startswith("negative-") else "accept", + } + ) + index = { + "schema": "dm.collective-memory.vector-index/v1", + "upstream_commit": collective.COLLECTIVE_MEMORY_COMMIT, + "upstream_schema_sha256": collective.COLLECTIVE_SCHEMA_SHA256, + "files": entries, + } + (output / "index.json").write_bytes(canonical_bytes(index) + b"\n") + + +def check(output: Path) -> bool: + with tempfile.TemporaryDirectory(prefix="dm036-vectors-") as directory: + expected = Path(directory) + write(expected) + current = sorted(path.name for path in output.glob("*.json")) + generated = sorted(path.name for path in expected.glob("*.json")) + return current == generated and all( + (output / name).read_bytes() == (expected / name).read_bytes() + for name in generated + ) + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--output", type=Path, default=DEFAULT_OUTPUT) + parser.add_argument("--check", action="store_true") + arguments = parser.parse_args() + if arguments.check: + return 0 if check(arguments.output) else 1 + write(arguments.output) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/reproducible_build.py b/tools/reproducible_build.py index 8bafaa8..fc896b1 100644 --- a/tools/reproducible_build.py +++ b/tools/reproducible_build.py @@ -37,6 +37,7 @@ Path("src/daimon_matrix/client.py"), Path("src/daimon_matrix/cluster.py"), Path("src/daimon_matrix/codex_body.py"), + Path("src/daimon_matrix/collective_memory.py"), Path("src/daimon_matrix/communication.py"), Path("src/daimon_matrix/conformance.py"), Path("src/daimon_matrix/curator.py"), diff --git a/tools/scan_secrets.py b/tools/scan_secrets.py index 073ade3..06dd9e9 100644 --- a/tools/scan_secrets.py +++ b/tools/scan_secrets.py @@ -86,8 +86,14 @@ def scan_archive(path: Path) -> int: return count -def _source_files(root: Path) -> Iterable[Path]: +def _source_files( + root: Path, *, excluded_roots: frozenset[Path] = frozenset() +) -> Iterable[Path]: for path in sorted(root.rglob("*")): + if any( + path == excluded or excluded in path.parents for excluded in excluded_roots + ): + continue parts = path.relative_to(root).parts if any(part in SKIP_DIRECTORIES for part in parts) or ( parts and parts[0] in SKIP_ROOT_DIRECTORIES @@ -99,13 +105,21 @@ def _source_files(root: Path) -> Iterable[Path]: yield path -def scan_path(path: Path) -> int: +def scan_path(path: Path, *, excluded_roots: Iterable[Path] = ()) -> int: """Scan one source tree, regular file, or package archive.""" path = path.resolve() if path.is_dir(): + resolved_exclusions = frozenset(root.resolve() for root in excluded_roots) + for excluded in resolved_exclusions: + if excluded == path or path not in excluded.parents: + raise SecretScanError( + f"excluded scan root must be a strict descendant: {excluded}" + ) + if excluded.is_symlink(): + raise SecretScanError(f"excluded scan root is a symlink: {excluded}") count = 0 - for member in _source_files(path): + for member in _source_files(path, excluded_roots=resolved_exclusions): relative = member.relative_to(path).as_posix() if member.stat().st_size > MAX_FILE_SIZE: raise SecretScanError(f"oversized source file: {relative}") diff --git a/vectors/collective-memory/v1/index.json b/vectors/collective-memory/v1/index.json new file mode 100644 index 0000000..08548ad --- /dev/null +++ b/vectors/collective-memory/v1/index.json @@ -0,0 +1 @@ +{"files":[{"expect":"reject","name":"negative-host-path.json","sha256":"d63776133ac10586c9340b990fd78a7ad895390db0b67683cae431d8d0bf4b4f","size":1257},{"expect":"accept","name":"publisher-acceptance.json","sha256":"a97750bc549a1c6fa7e751aa93d5e2fb76b3af2af0d1ec2c591f3fbffa59f62e","size":2419},{"expect":"accept","name":"publisher-profile.json","sha256":"a082bfc4c80acfa2303038368662990796f7e49c148bcad688df285d273ccce4","size":410},{"expect":"accept","name":"publisher-request.json","sha256":"d5be6de1a97fbc7231dc0ee4052398bac6fca7fa546d25b65c8d8f999feccaa9","size":1707},{"expect":"accept","name":"source-preview.json","sha256":"ae72c0d202b60efe8b395b850d5610f04cc40d01c6b2dbbbe1e2075310b1fc98","size":1582},{"expect":"accept","name":"source-profile.json","sha256":"99bac5b827750eb09c61a6cf7154a974fa06547c8ec26805e8658dc33fab47a7","size":431},{"expect":"accept","name":"source-receipt.json","sha256":"d3b60412230632fec5b2f754e40573b327907ef50a1e0ea31641a4ecfdc0f3ed","size":1228}],"schema":"dm.collective-memory.vector-index/v1","upstream_commit":"3e3b39416917f8e3c2bc5ca69362b20296205938","upstream_schema_sha256":"2aad43d1b309ee95108c855fc8dc682a854e5fdf3a1e799ecfca96d3ebf7c5d9"} diff --git a/vectors/collective-memory/v1/negative-host-path.json b/vectors/collective-memory/v1/negative-host-path.json new file mode 100644 index 0000000..af9b8e3 --- /dev/null +++ b/vectors/collective-memory/v1/negative-host-path.json @@ -0,0 +1 @@ +{"body":{"adapter_id":"dm:adapter:v0:Sh-2fDC4rpFOZz_ddjWqLptoX2SgUUzJPKhe6XQOtj8","artifact_count":1,"decision":"quarantined","generation_id":"cm:export:v1:ljMgj7Pr1Bu9kxB6AKExOMANasirsRzbjl0LDc6fBF8","import_event_id":"36000000-0000-4000-8000-000000000001","imported_at_ms":1800000000000,"importer_version":"daimon-matrix-collective-source/1.0.0","manifest_hash":"9633208fb3ebd41bbd93107a00a13138c00d6ac8abb11cdb8e5d0b0dce9f045f","outcomes":{"admitted_to_quarantine":1,"personal_memory_assertions":0,"tombstoned":0},"policy_version":"policy:v1","predecessor_generation":null,"preview_hash":"2ad099ee0d2eecd513a6708a3672479f7776bee0e0068024daa93112af40d7f7","preview_id":"dm:collective-source-preview:v1:wYA9OFPALKQbwy67imb4Y6uG5dqNbBajFP9jxq06rBI","producer_instance":"collective:vector","producer_release":"collective:release:vector","reason_codes":["quarantined:initial-pull"],"scope_id":"public","source_log_hash":"4444444444444444444444444444444444444444444444444444444444444444","state_digest":"d48329f7fe82b25ed06900e9549b32e591470e9683b111f6d53ed03c8e37f54b","total_content_bytes":27},"host_path":"/tmp/forbidden","receipt_id":"dm:collective-source-receipt:v1:Q2Uop32x8d4562E0aKTF3Fnjg3kT2toNNfTwTHQR5xI","schema":"dm.collective-source.receipt/v1"} diff --git a/vectors/collective-memory/v1/publisher-acceptance.json b/vectors/collective-memory/v1/publisher-acceptance.json new file mode 100644 index 0000000..f7108c0 --- /dev/null +++ b/vectors/collective-memory/v1/publisher-acceptance.json @@ -0,0 +1 @@ +{"acceptance_id":"dm:collective-publisher-acceptance:v1:0WIhUHu78rtTx0rJdmCHEt1dBP63Td95SRGNcSJKP70","body":{"accepted_at_ms":1800000000000,"adapter_id":"dm:adapter:v0:Jsug9D2N641xJwE5Q_oLaHDy0wxT5knRJfxzV2ZEOXc","provider_receipt":{"body":{"action":"publish","after":{"content_hash":"152dbecb54ece6eb7d1d3b217ce982e5d20b1aa5e8f7b77c72a06e315701e352","content_length":495,"media_type":"text/markdown; charset=utf-8","state":"active"},"before":null,"classification":"public","committed_at":"2026-08-05T06:00:00.000000Z","consent":{"evidence_hash":"8fceb143dfd53fe71d872dbef0981305d29b8dde94ba8e509d04fe0e31cddba6","evidence_id":"evidence:consent:vector"},"idempotency_key":"idempotency:vector","plan_id":"cm:publication-plan:v1:WSRFFXPvbakGbNxaH5h0kaKoMKgXYzIjLQzPmzf9lkw","policy_version":"policy:v1","projection":{"index_content_hash":"152dbecb54ece6eb7d1d3b217ce982e5d20b1aa5e8f7b77c72a06e315701e352","index_generation":"vector-index-1","ui_generation":"vector-ui-1"},"request_hash":"f4cc06f68091abce8421c3cb7cc3e18310934dc2c7256e3cac1c484e561e54a3","review":{"evidence_hash":"a9851e062dcb7e6838507fe86ed1dae57791a841623ee34c20122feed99b3813","evidence_id":"evidence:review:vector"},"source_checkpoint":{"hash":"3333333333333333333333333333333333333333333333333333333333333333","id":"dm:checkpoint:vector"},"source_refs":[{"hash":"2222222222222222222222222222222222222222222222222222222222222222","id":"dm:event:vector"}],"status":"committed","target_id":"collective:article:vector","transaction_id":"cm:publication-transaction:v1:8VqhnhaABJl2mQegRzX-hZ6ocQeH_31gAwGdmx-ustA"},"receipt_hash":"e07b8a0af5555df0c9d946bd42ddd1d331a228ec302c7d19f0f4aedeeed52229","receipt_id":"cm:publication-receipt:v1:4HuKCvVVXfDJ2Ua9Qt3R0zGiKOwwLH0Z8PSu3u7VIik","schema":"collective-publication-receipt/v1"},"publisher_version":"daimon-matrix-collective-publisher/1.0.0","reconciliation_state_hash":"3f4c84f226d78de37710f7813b79b999eec4f88cf70d78e19de73aa8c6232722","request_event_hash":"5555555555555555555555555555555555555555555555555555555555555555","request_event_id":"36000000-0000-4000-8000-000000000002","request_hash":"f4cc06f68091abce8421c3cb7cc3e18310934dc2c7256e3cac1c484e561e54a3","request_id":"dm:collective-publisher-operation:v1:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","summary_id":"dm:collective-publisher-request:v1:rZF_MYFR8xhCNF_D8UsMOBt5a65KG4epza00rkstx20"},"schema":"dm.collective-publisher.acceptance/v1"} diff --git a/vectors/collective-memory/v1/publisher-profile.json b/vectors/collective-memory/v1/publisher-profile.json new file mode 100644 index 0000000..8aed5a5 --- /dev/null +++ b/vectors/collective-memory/v1/publisher-profile.json @@ -0,0 +1 @@ +{"adapter_id":"dm:adapter:v0:Jsug9D2N641xJwE5Q_oLaHDy0wxT5knRJfxzV2ZEOXc","contract_version":"v1","policy_version":"policy:v1","requester_id":"operator:matrix-vector","schema":"dm.collective-publisher.profile/v1","target_ids":["collective:article:vector"],"upstream_commit":"3e3b39416917f8e3c2bc5ca69362b20296205938","upstream_schema_sha256":"2aad43d1b309ee95108c855fc8dc682a854e5fdf3a1e799ecfca96d3ebf7c5d9"} diff --git a/vectors/collective-memory/v1/publisher-request.json b/vectors/collective-memory/v1/publisher-request.json new file mode 100644 index 0000000..f650867 --- /dev/null +++ b/vectors/collective-memory/v1/publisher-request.json @@ -0,0 +1 @@ +{"body":{"action":"publish","adapter_id":"dm:adapter:v0:Jsug9D2N641xJwE5Q_oLaHDy0wxT5knRJfxzV2ZEOXc","classification":"public","consent":{"evidence_hash":"8fceb143dfd53fe71d872dbef0981305d29b8dde94ba8e509d04fe0e31cddba6","evidence_id":"evidence:consent:vector"},"content_hash":"152dbecb54ece6eb7d1d3b217ce982e5d20b1aa5e8f7b77c72a06e315701e352","content_length":495,"idempotency_key":"idempotency:vector","plan_hash":"5924451573ef6da9066cdc5a1f987491a2a830a8176332232d0ccf9b37fd964c","plan_id":"cm:publication-plan:v1:WSRFFXPvbakGbNxaH5h0kaKoMKgXYzIjLQzPmzf9lkw","policy_version":"policy:v1","predecessor":null,"preview_hash":"63997796b2fd84f0e477a164dacad8a3c43f72c8435b6693d9944fa816b8675f","preview_id":"cm:publication-preview:v1:Y5l3lrL9hPDkd6Fk2srYo8Q_cshDW2aT2ZRPqBa4Z18","publisher_version":"daimon-matrix-collective-publisher/1.0.0","request_hash":"f4cc06f68091abce8421c3cb7cc3e18310934dc2c7256e3cac1c484e561e54a3","request_id":"dm:collective-publisher-operation:v1:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","requested_at_ms":1800000000000,"requester_id":"operator:matrix-vector","review":{"evidence_hash":"a9851e062dcb7e6838507fe86ed1dae57791a841623ee34c20122feed99b3813","evidence_id":"evidence:review:vector","reviewer":"human:vector-reviewer"},"source_checkpoint":{"hash":"3333333333333333333333333333333333333333333333333333333333333333","id":"dm:checkpoint:vector"},"source_refs":[{"hash":"2222222222222222222222222222222222222222222222222222222222222222","id":"dm:event:vector"}],"subject_id":"being:vector","target_id":"collective:article:vector"},"schema":"dm.collective-publisher.request/v1","summary_id":"dm:collective-publisher-request:v1:rZF_MYFR8xhCNF_D8UsMOBt5a65KG4epza00rkstx20"} diff --git a/vectors/collective-memory/v1/source-preview.json b/vectors/collective-memory/v1/source-preview.json new file mode 100644 index 0000000..21ab9bc --- /dev/null +++ b/vectors/collective-memory/v1/source-preview.json @@ -0,0 +1 @@ +{"content_bindings":[{"artifact_id":"artifact:vector:v1","content_hash":"7a18be5061b12c743c5512f02c9fc7d432fc466061b60a3101ece88b2cc1c70e","content_length":27,"state":"active"}],"manifest":{"body":{"artifact_count":1,"artifacts":[{"artifact_id":"artifact:vector:v1","authors":["author:vector"],"classification":"public","consent_scope":"public","content_hash":"7a18be5061b12c743c5512f02c9fc7d432fc466061b60a3101ece88b2cc1c70e","content_length":27,"content_ref":"sha256:7a18be5061b12c743c5512f02c9fc7d432fc466061b60a3101ece88b2cc1c70e","license":"MIT","logical_id":"logical:vector","media_type":"text/markdown; charset=utf-8","predecessor_artifact_id":null,"source_refs":[{"hash":"1111111111111111111111111111111111111111111111111111111111111111","id":"source:vector"}],"state":"active"}],"created_at":"2026-08-05T06:00:00.000000Z","policy_version":"policy:v1","predecessor_generation":null,"producer_instance":"collective:vector","producer_release":"collective:release:vector","projection":{"index_generation":null,"ui_generation":null},"scope_id":"public","state_digest":"d48329f7fe82b25ed06900e9549b32e591470e9683b111f6d53ed03c8e37f54b","total_content_bytes":27},"generation_id":"cm:export:v1:ljMgj7Pr1Bu9kxB6AKExOMANasirsRzbjl0LDc6fBF8","manifest_hash":"9633208fb3ebd41bbd93107a00a13138c00d6ac8abb11cdb8e5d0b0dce9f045f","schema":"collective-export-manifest/v1"},"preview_hash":"2ad099ee0d2eecd513a6708a3672479f7776bee0e0068024daa93112af40d7f7","preview_id":"dm:collective-source-preview:v1:wYA9OFPALKQbwy67imb4Y6uG5dqNbBajFP9jxq06rBI","schema":"dm.collective-source.preview/v1"} diff --git a/vectors/collective-memory/v1/source-profile.json b/vectors/collective-memory/v1/source-profile.json new file mode 100644 index 0000000..d0de896 --- /dev/null +++ b/vectors/collective-memory/v1/source-profile.json @@ -0,0 +1 @@ +{"adapter_id":"dm:adapter:v0:Sh-2fDC4rpFOZz_ddjWqLptoX2SgUUzJPKhe6XQOtj8","contract_version":"v1","policy_version":"policy:v1","producer_instance":"collective:vector","producer_release":"collective:release:vector","schema":"dm.collective-source.profile/v1","scope_id":"public","upstream_commit":"3e3b39416917f8e3c2bc5ca69362b20296205938","upstream_schema_sha256":"2aad43d1b309ee95108c855fc8dc682a854e5fdf3a1e799ecfca96d3ebf7c5d9"} diff --git a/vectors/collective-memory/v1/source-receipt.json b/vectors/collective-memory/v1/source-receipt.json new file mode 100644 index 0000000..30f806b --- /dev/null +++ b/vectors/collective-memory/v1/source-receipt.json @@ -0,0 +1 @@ +{"body":{"adapter_id":"dm:adapter:v0:Sh-2fDC4rpFOZz_ddjWqLptoX2SgUUzJPKhe6XQOtj8","artifact_count":1,"decision":"quarantined","generation_id":"cm:export:v1:ljMgj7Pr1Bu9kxB6AKExOMANasirsRzbjl0LDc6fBF8","import_event_id":"36000000-0000-4000-8000-000000000001","imported_at_ms":1800000000000,"importer_version":"daimon-matrix-collective-source/1.0.0","manifest_hash":"9633208fb3ebd41bbd93107a00a13138c00d6ac8abb11cdb8e5d0b0dce9f045f","outcomes":{"admitted_to_quarantine":1,"personal_memory_assertions":0,"tombstoned":0},"policy_version":"policy:v1","predecessor_generation":null,"preview_hash":"2ad099ee0d2eecd513a6708a3672479f7776bee0e0068024daa93112af40d7f7","preview_id":"dm:collective-source-preview:v1:wYA9OFPALKQbwy67imb4Y6uG5dqNbBajFP9jxq06rBI","producer_instance":"collective:vector","producer_release":"collective:release:vector","reason_codes":["quarantined:initial-pull"],"scope_id":"public","source_log_hash":"4444444444444444444444444444444444444444444444444444444444444444","state_digest":"d48329f7fe82b25ed06900e9549b32e591470e9683b111f6d53ed03c8e37f54b","total_content_bytes":27},"receipt_id":"dm:collective-source-receipt:v1:Q2Uop32x8d4562E0aKTF3Fnjg3kT2toNNfTwTHQR5xI","schema":"dm.collective-source.receipt/v1"} diff --git a/vectors/hermes/v1/index.json b/vectors/hermes/v1/index.json index 3863976..89015a2 100644 --- a/vectors/hermes/v1/index.json +++ b/vectors/hermes/v1/index.json @@ -1 +1 @@ -{"files":[{"name":"negative/context-id-tampered.json","sha256":"542687c5286ba74d4157bca175ac6c98f591a9c73ad3eec90ecb30861a3a7c6c","valid":false},{"name":"negative/park-presence-not-relinquished.json","sha256":"08da5c7d7e4bcfe080ef3bc5a1ee785dd26f277902e63021ef03b139b3a279b5","valid":false},{"name":"negative/plan-unknown-field.json","sha256":"7c8b6b6d196ba088fc3417d0191bd9e1200f207205874d5c5d2b85bb86eab541","valid":false},{"name":"valid/bootstrap.json","sha256":"288f985c7a31a1ac9dd54d16ed7f49f81752918d60caaf0f7a4dabfb868aef4b","valid":true},{"name":"valid/compatibility.json","sha256":"e4318d28ae7bf72d0685eb48acf3244ba09e9f20d2d4ac069e3ab0fff5fd725b","valid":true},{"name":"valid/context.json","sha256":"ab55dbcd81211b009fc797095368e389ad24639b19578017fa8e5c334f8fcf3a","valid":true},{"name":"valid/current-memory-projection.json","sha256":"ab9b4d897da7fd12c89ec782486378884867bd2afbb8327f67b61c0a10bb6c55","valid":true},{"name":"valid/effect-receipt.json","sha256":"f5aa1b7d7a97a2c818b66d7a9b672e91b224b52b3203de419042f0a0927c7b46","valid":true},{"name":"valid/launch-receipt.json","sha256":"fb37aada721fcf2d808ea0c704072b2d740f2a1e425b469cd05108019696bf15","valid":true},{"name":"valid/park-receipt.json","sha256":"764774f4b4993668ff4f8d8272b4dc1c0b468e5df38148c9d1e261fdbe03069c","valid":true},{"name":"valid/park-request.json","sha256":"1c1c1b06c162371edfaaae7bd5562f9e5acb2853efbc12ccdd523ac2431ea2cf","valid":true},{"name":"valid/plan.json","sha256":"d711657a4fa972cfbdf83ddcb78b30c0703dd0674697641d7dd3239efc549878","valid":true},{"name":"valid/profile-manifest.json","sha256":"85573ed084976009d682736c2241703aa740a05387a2487cb6c491fa10628baa","valid":true},{"name":"valid/provider-config.json","sha256":"3977a77b68b6d2d70f41540e00529cf88216da21654616e8bd8882bd49493dd5","valid":true},{"name":"valid/provider-ready.json","sha256":"d9f9684474222e2339e8da122570bd8fb4266f81ff5499952049b64d48f7cc5e","valid":true},{"name":"valid/runtime-active.json","sha256":"12c9f5e7516adf8aa58bb7ef90c3965db557612d50b1618e252fe13f629c4b8b","valid":true},{"name":"valid/runtime-parking.json","sha256":"bb2adcfef7a7b31ba3c2b79a62369c20ab6ab612e1d0b6bc33a88c59685ccba9","valid":true},{"name":"valid/runtime-starting.json","sha256":"52f448d212d230cda8dce0f88b946592c5ccbe5a462046f0a7f39a3dc4e8ffb6","valid":true},{"name":"valid/scope-result.json","sha256":"0dd3cc8b1c72062666e4d9af67b2eb2b149fe1a89da730113952fccb12b15db4","valid":true},{"name":"valid/tool-error.json","sha256":"01bd1caee46e7972e3e246094f77979231c8bd173515aa2fe12151dbe22edf75","valid":true}],"hermes_commit":"0db1912911fafa384aa5ee0145929658a9d1dd33","hermes_version":"0.19.0","schema":"dm.hermes-body.vector-index/v1"} +{"files":[{"name":"negative/context-id-tampered.json","sha256":"542687c5286ba74d4157bca175ac6c98f591a9c73ad3eec90ecb30861a3a7c6c","valid":false},{"name":"negative/park-presence-not-relinquished.json","sha256":"08da5c7d7e4bcfe080ef3bc5a1ee785dd26f277902e63021ef03b139b3a279b5","valid":false},{"name":"negative/plan-unknown-field.json","sha256":"7c8b6b6d196ba088fc3417d0191bd9e1200f207205874d5c5d2b85bb86eab541","valid":false},{"name":"valid/bootstrap.json","sha256":"288f985c7a31a1ac9dd54d16ed7f49f81752918d60caaf0f7a4dabfb868aef4b","valid":true},{"name":"valid/compatibility.json","sha256":"e4318d28ae7bf72d0685eb48acf3244ba09e9f20d2d4ac069e3ab0fff5fd725b","valid":true},{"name":"valid/context.json","sha256":"ab55dbcd81211b009fc797095368e389ad24639b19578017fa8e5c334f8fcf3a","valid":true},{"name":"valid/current-memory-projection.json","sha256":"ab9b4d897da7fd12c89ec782486378884867bd2afbb8327f67b61c0a10bb6c55","valid":true},{"name":"valid/effect-receipt.json","sha256":"f5aa1b7d7a97a2c818b66d7a9b672e91b224b52b3203de419042f0a0927c7b46","valid":true},{"name":"valid/launch-receipt.json","sha256":"e06da99ab5914e4ae0b99158d4b9534ac3658d84b927023b2dfdf83e5ff80ccd","valid":true},{"name":"valid/park-receipt.json","sha256":"764774f4b4993668ff4f8d8272b4dc1c0b468e5df38148c9d1e261fdbe03069c","valid":true},{"name":"valid/park-request.json","sha256":"1c1c1b06c162371edfaaae7bd5562f9e5acb2853efbc12ccdd523ac2431ea2cf","valid":true},{"name":"valid/plan.json","sha256":"d711657a4fa972cfbdf83ddcb78b30c0703dd0674697641d7dd3239efc549878","valid":true},{"name":"valid/profile-manifest.json","sha256":"8a7db8f14d3ccb7af92f04b10f41ecb1407b216e629a405bf653363ac43889b4","valid":true},{"name":"valid/provider-config.json","sha256":"3977a77b68b6d2d70f41540e00529cf88216da21654616e8bd8882bd49493dd5","valid":true},{"name":"valid/provider-ready.json","sha256":"d9f9684474222e2339e8da122570bd8fb4266f81ff5499952049b64d48f7cc5e","valid":true},{"name":"valid/runtime-active.json","sha256":"12c9f5e7516adf8aa58bb7ef90c3965db557612d50b1618e252fe13f629c4b8b","valid":true},{"name":"valid/runtime-parking.json","sha256":"bb2adcfef7a7b31ba3c2b79a62369c20ab6ab612e1d0b6bc33a88c59685ccba9","valid":true},{"name":"valid/runtime-starting.json","sha256":"52f448d212d230cda8dce0f88b946592c5ccbe5a462046f0a7f39a3dc4e8ffb6","valid":true},{"name":"valid/scope-result.json","sha256":"0dd3cc8b1c72062666e4d9af67b2eb2b149fe1a89da730113952fccb12b15db4","valid":true},{"name":"valid/tool-error.json","sha256":"01bd1caee46e7972e3e246094f77979231c8bd173515aa2fe12151dbe22edf75","valid":true}],"hermes_commit":"0db1912911fafa384aa5ee0145929658a9d1dd33","hermes_version":"0.19.0","schema":"dm.hermes-body.vector-index/v1"} diff --git a/vectors/hermes/v1/valid/launch-receipt.json b/vectors/hermes/v1/valid/launch-receipt.json index ea169a4..3b40006 100644 --- a/vectors/hermes/v1/valid/launch-receipt.json +++ b/vectors/hermes/v1/valid/launch-receipt.json @@ -1 +1 @@ -{"active_handle_id":"dm:hermes-handle:v1:7qD764Q4xNYnguH0C2VESNXWHnCaL-XjR1RM3gdZ_rg","deployment":"synthetic-isolated","hermes_commit":"0db1912911fafa384aa5ee0145929658a9d1dd33","hermes_python":{"executable_sha256":"a2fdb31eaf46a78ba3f0687f59b4c5a3bc318f7cd56dbbe6f68272c4af34cc71","implementation":"cpython","supported_interval":">=3.11,<3.14","version":"3.13.5"},"hermes_session_id":"hermes-session-vector","hermes_version":"0.19.0","launch_receipt_id":"dm:hermes-launch:v1:6VOt4fOl_N1B76807_1r7B_1G8R-BlHuetm_yG-8H7Y","matrix_high_water":"654a43432c2bc0c249a5be9c749c50257b047ee6b9fc7c8a5c92dac0f145df82","matrix_package":{"contract_schema_sha256":"e23b47040d45c64676b2fc793e375f9be9d7d95078f0b5c384bb5a33383f499f","current_memory_schema_sha256":"3422f822f7b3e05c4b2f422a19a96283993938f11c798cb1982cb5d3cdc38169","modules":[{"name":"__init__.py","sha256":"975cc4fc1bc2628e42c1e24c972fbb458f7522378ba8c3cc720b248bfdf52de5"},{"name":"authority_epochs.py","sha256":"febc83190dbc0b5b3b028ef3ad69431bfe4e6edba9100e0b41eae800145bcd0f"},{"name":"birth.py","sha256":"8407f496592b0b84e5caf78b7384c379d41d40ce78ed61351be4e0dc02741749"},{"name":"canonical.py","sha256":"1abee4501a0621cf981cc247ac21ab825e278dc0a71ed98c2e57cfdc91efbd47"},{"name":"cli.py","sha256":"c2f0d634c2faa1619f280a76e714dbe963300705dd48630c2fa14dfd090bb603"},{"name":"client.py","sha256":"1fd3a27158035280a1ef37ed094305392a580896f21ce665a9d8c38f91148821"},{"name":"cluster.py","sha256":"f183c54a2876b3531a1bbb3fe6d45d9c9c60069608b5c3ec3d2f07dbb50cae91"},{"name":"codex_body.py","sha256":"76eb0b27cecd06d89a220f172042bd94250e3c79dfee3cdd25737e9801565fd4"},{"name":"communication.py","sha256":"ea9342f8326074a0c69ea8534f21310e784b0bdc93c62e1221142078ae22aa3b"},{"name":"conformance.py","sha256":"85493244a77587db27ae2582df1291774aba22401a31cdf0e4cb6a25c8e90a33"},{"name":"curator.py","sha256":"ff9718bdc1577657f3029b71fd35bad701063a185acddb797f12b196a04739ce"},{"name":"curator_worker.py","sha256":"3f717165540cb209c3545cab95bf846803a454c8bef73b3067904b65a4959edb"},{"name":"curator_worker_process.py","sha256":"bdbd6296072b27d1b42222924e5e0af95c5c1ac8d01cfa9c59fdad57fa2d5a2d"},{"name":"daemon.py","sha256":"b1938dfb5a44f3555607a36eb933c1f4f2dab0c8130e063de605630a852b4988"},{"name":"hermes_body.py","sha256":"9b32654d3093d3c1b7a33d3fd932a61fd0d21b32fa28a7c843c4e3f41605a8c9"},{"name":"human_review.py","sha256":"40a1ff0a6da8278c60383ceda0b2e1dfaf90d60929f08da77f3926d8ecd9169f"},{"name":"identity.py","sha256":"6c7909d6e453d7d955c88c552c0a30fe8ce2c479f93008fd866caad1fb8b9412"},{"name":"keystore.py","sha256":"d9f9bebc247f4d613d04bece26f20ef877c375454d5aaabafd038ecfd89698a4"},{"name":"ledger.py","sha256":"1e2d3d3bb8ecbea4ee44fc77b6d31c16948bc9189847026c7c10b01bfb4b4dc7"},{"name":"local_api.py","sha256":"3f4a179815ecf62b6d60e5825eea836d511daa2371ebe25bfb3abdd40787d55b"},{"name":"local_we.py","sha256":"2d9fddcf6c4c1a93a704d99c65d7fe839377d6801e66fea8f552b474083ee224"},{"name":"mcp_server.py","sha256":"1ba204a3b1c865b579f761e5e6792e5d80529983ad55106cb1c1a0b3b6bc48b4"},{"name":"memory_policy.py","sha256":"59390545f633ba8d71b40b51ef5b6a897f994e1adb4bcfa246ab4301fb3f6310"},{"name":"memory_projection.py","sha256":"f260473f31731d8d63f69f1cb3568d10c3b65f2b5948801deeab2d75243a0652"},{"name":"multihost.py","sha256":"37c824e30469aeebd8dcd0269c4249715a16ee5afa05c2b9d4a74b8e26e68664"},{"name":"operator_bootstrap.py","sha256":"23d0b19cb566df0e9a337c791fbde1fc74c75c6a21e3124c1c12ed0014532e9f"},{"name":"peer_transport.py","sha256":"3c86895783e908165508158c17bc5d474b2effcc98c08c8d629aaeaafb2c11a4"},{"name":"projections.py","sha256":"dd31294f4d6dc2ff350ca05f57ed79d7f24f7699d903a9fc8d00cf9932f254b1"},{"name":"publication.py","sha256":"31d0623f4637d280f3f8b6f65fae6ffa961e592004b418d3cae7f4ca5eba0459"},{"name":"relationship_store.py","sha256":"08afda06469d7c89710de115095b6b06e094baa79db6e7f818626c9c76e4abd4"},{"name":"relationships.py","sha256":"d572abd90da60e0beb301fb34af11bdbfac52ecc13605aeb00bfe4ac368092e0"},{"name":"reviewer_cli.py","sha256":"abf92a9f43a8811a36affad7182de085736cb9bde4f55da78fbc064236ff1bc3"},{"name":"routes.py","sha256":"04d5b6b8ba44499d57689f9e97f520a2e309d86532cb4b658a8e168099fc7799"},{"name":"runtime.py","sha256":"f9811a380f14799bde730a7daa9ea66d91da02553bf1a60936d0533fafe1e6d0"},{"name":"scopes.py","sha256":"7a7e07f38ddf3a7866dbba7a66cc4e7ecbcad563c6d8041c2f0ecd859d0978c4"},{"name":"sealed.py","sha256":"60d4f0d85ed89afeeb56ddc2bde9beb331dfe7abf56f82a04d41a4c2f03d6482"},{"name":"service.py","sha256":"be549bce2055a86a8ef06f32c71e54c0dd6fb820a932284a6b5d6a064748401e"},{"name":"sources.py","sha256":"803aaba82c14f6d5d39e10a0e2e583da0641295ab8e910bc29a39a717aa92c38"},{"name":"species.py","sha256":"824978695997e1384614e0e77239eaae63baed1a64a502b29a414492551a8d40"},{"name":"species_runner.py","sha256":"3312dda2c13548503863698482bdd7c6d585415f445cbad765cce41a38ebdfc1"},{"name":"sync.py","sha256":"5836e80d6ea68cde799eb3bd66b4b3b3437db90ce55f8c7d77d7944b728585b2"},{"name":"synthetic_birth.py","sha256":"5035bc0d57d8c927b774b2792575477ae9ecc426a2518e69def3ab2d0eda9ac4"},{"name":"synthetic_multihost.py","sha256":"c41ea0920de7e03d82e510045ec52be8b2a3df493087fe55ef5eaceafb7e91e1"},{"name":"synthetic_relationships.py","sha256":"393fd27a49a7a996ef9e433bf1472cfd9746a08336dc1e22882ce675e878d8ee"},{"name":"synthetic_sources.py","sha256":"d9090c4adf0189b023226357b5c544e461d674e86db7050057cfbeec37ec0c1d"},{"name":"synthetic_species.py","sha256":"4a839e42820426ea3af33ef9f96551d51159e1b48dbb692259b6d521227bf256"},{"name":"weave.py","sha256":"deebdf47950ec1c99ee3a4fee49022c5098f5e98bda5624c114d48f1f6d065e0"}],"tree_sha256":"9b76294d4eed59111999adc9b145f0ce57be5c2f112313eb5ae5193295cc6467"},"plan_id":"dm:hermes-plan:v1:O3YR1x4fwBrTHF-QO-fbGii0f_D0O-kSzsZbMcuL0vM","profile_id":"dm:hermes-profile:v1:-_Nflr6K0kKZE-trs6NGUeRn3l9mZTdOgx6oZ0D3nPY","provider_ready_id":"dm:hermes-ready:v1:UHiM5_UrowyOAwjwZxkOXdsvbLGRjHVSRZN69JTU0O4","schema":"dm.hermes-body.launch-receipt/v1","starting_handle_id":"dm:hermes-handle:v1:L9ZIK64wzJ-zg-l9QVw_b1btW7o6SOxOhQL_6TET0rM"} +{"active_handle_id":"dm:hermes-handle:v1:7qD764Q4xNYnguH0C2VESNXWHnCaL-XjR1RM3gdZ_rg","deployment":"synthetic-isolated","hermes_commit":"0db1912911fafa384aa5ee0145929658a9d1dd33","hermes_python":{"executable_sha256":"a2fdb31eaf46a78ba3f0687f59b4c5a3bc318f7cd56dbbe6f68272c4af34cc71","implementation":"cpython","supported_interval":">=3.11,<3.14","version":"3.13.5"},"hermes_session_id":"hermes-session-vector","hermes_version":"0.19.0","launch_receipt_id":"dm:hermes-launch:v1:GNz9YfAxVYSmLhhA5wDUJuiG0hagOIPB8E4NTQ4rcY0","matrix_high_water":"654a43432c2bc0c249a5be9c749c50257b047ee6b9fc7c8a5c92dac0f145df82","matrix_package":{"contract_schema_sha256":"e23b47040d45c64676b2fc793e375f9be9d7d95078f0b5c384bb5a33383f499f","current_memory_schema_sha256":"3422f822f7b3e05c4b2f422a19a96283993938f11c798cb1982cb5d3cdc38169","modules":[{"name":"__init__.py","sha256":"975cc4fc1bc2628e42c1e24c972fbb458f7522378ba8c3cc720b248bfdf52de5"},{"name":"authority_epochs.py","sha256":"febc83190dbc0b5b3b028ef3ad69431bfe4e6edba9100e0b41eae800145bcd0f"},{"name":"birth.py","sha256":"8407f496592b0b84e5caf78b7384c379d41d40ce78ed61351be4e0dc02741749"},{"name":"canonical.py","sha256":"1abee4501a0621cf981cc247ac21ab825e278dc0a71ed98c2e57cfdc91efbd47"},{"name":"cli.py","sha256":"c2f0d634c2faa1619f280a76e714dbe963300705dd48630c2fa14dfd090bb603"},{"name":"client.py","sha256":"1fd3a27158035280a1ef37ed094305392a580896f21ce665a9d8c38f91148821"},{"name":"cluster.py","sha256":"f183c54a2876b3531a1bbb3fe6d45d9c9c60069608b5c3ec3d2f07dbb50cae91"},{"name":"codex_body.py","sha256":"76eb0b27cecd06d89a220f172042bd94250e3c79dfee3cdd25737e9801565fd4"},{"name":"collective_memory.py","sha256":"90dbc64cc0a9e8c9d37e7ec509d963958eb90915d88d563b6a4d6e6ed4f4bb5f"},{"name":"communication.py","sha256":"ea9342f8326074a0c69ea8534f21310e784b0bdc93c62e1221142078ae22aa3b"},{"name":"conformance.py","sha256":"215da0d092fe16267a0b0910d826545f8a7fb617b5cc25a19f22353a798641c2"},{"name":"curator.py","sha256":"ff9718bdc1577657f3029b71fd35bad701063a185acddb797f12b196a04739ce"},{"name":"curator_worker.py","sha256":"3f717165540cb209c3545cab95bf846803a454c8bef73b3067904b65a4959edb"},{"name":"curator_worker_process.py","sha256":"bdbd6296072b27d1b42222924e5e0af95c5c1ac8d01cfa9c59fdad57fa2d5a2d"},{"name":"daemon.py","sha256":"b1938dfb5a44f3555607a36eb933c1f4f2dab0c8130e063de605630a852b4988"},{"name":"hermes_body.py","sha256":"9b32654d3093d3c1b7a33d3fd932a61fd0d21b32fa28a7c843c4e3f41605a8c9"},{"name":"human_review.py","sha256":"40a1ff0a6da8278c60383ceda0b2e1dfaf90d60929f08da77f3926d8ecd9169f"},{"name":"identity.py","sha256":"6c7909d6e453d7d955c88c552c0a30fe8ce2c479f93008fd866caad1fb8b9412"},{"name":"keystore.py","sha256":"d9f9bebc247f4d613d04bece26f20ef877c375454d5aaabafd038ecfd89698a4"},{"name":"ledger.py","sha256":"9673f7e12d3d35ed6ec8043ddf9f729c6f20153f795e748e5e3f3670d68b1e96"},{"name":"local_api.py","sha256":"3f4a179815ecf62b6d60e5825eea836d511daa2371ebe25bfb3abdd40787d55b"},{"name":"local_we.py","sha256":"2d9fddcf6c4c1a93a704d99c65d7fe839377d6801e66fea8f552b474083ee224"},{"name":"mcp_server.py","sha256":"1ba204a3b1c865b579f761e5e6792e5d80529983ad55106cb1c1a0b3b6bc48b4"},{"name":"memory_policy.py","sha256":"59390545f633ba8d71b40b51ef5b6a897f994e1adb4bcfa246ab4301fb3f6310"},{"name":"memory_projection.py","sha256":"f260473f31731d8d63f69f1cb3568d10c3b65f2b5948801deeab2d75243a0652"},{"name":"multihost.py","sha256":"37c824e30469aeebd8dcd0269c4249715a16ee5afa05c2b9d4a74b8e26e68664"},{"name":"operator_bootstrap.py","sha256":"23d0b19cb566df0e9a337c791fbde1fc74c75c6a21e3124c1c12ed0014532e9f"},{"name":"peer_transport.py","sha256":"3c86895783e908165508158c17bc5d474b2effcc98c08c8d629aaeaafb2c11a4"},{"name":"projections.py","sha256":"dd31294f4d6dc2ff350ca05f57ed79d7f24f7699d903a9fc8d00cf9932f254b1"},{"name":"publication.py","sha256":"31d0623f4637d280f3f8b6f65fae6ffa961e592004b418d3cae7f4ca5eba0459"},{"name":"relationship_store.py","sha256":"08afda06469d7c89710de115095b6b06e094baa79db6e7f818626c9c76e4abd4"},{"name":"relationships.py","sha256":"d572abd90da60e0beb301fb34af11bdbfac52ecc13605aeb00bfe4ac368092e0"},{"name":"reviewer_cli.py","sha256":"abf92a9f43a8811a36affad7182de085736cb9bde4f55da78fbc064236ff1bc3"},{"name":"routes.py","sha256":"04d5b6b8ba44499d57689f9e97f520a2e309d86532cb4b658a8e168099fc7799"},{"name":"runtime.py","sha256":"f9811a380f14799bde730a7daa9ea66d91da02553bf1a60936d0533fafe1e6d0"},{"name":"scopes.py","sha256":"7a7e07f38ddf3a7866dbba7a66cc4e7ecbcad563c6d8041c2f0ecd859d0978c4"},{"name":"sealed.py","sha256":"60d4f0d85ed89afeeb56ddc2bde9beb331dfe7abf56f82a04d41a4c2f03d6482"},{"name":"service.py","sha256":"be549bce2055a86a8ef06f32c71e54c0dd6fb820a932284a6b5d6a064748401e"},{"name":"sources.py","sha256":"803aaba82c14f6d5d39e10a0e2e583da0641295ab8e910bc29a39a717aa92c38"},{"name":"species.py","sha256":"824978695997e1384614e0e77239eaae63baed1a64a502b29a414492551a8d40"},{"name":"species_runner.py","sha256":"3312dda2c13548503863698482bdd7c6d585415f445cbad765cce41a38ebdfc1"},{"name":"sync.py","sha256":"5836e80d6ea68cde799eb3bd66b4b3b3437db90ce55f8c7d77d7944b728585b2"},{"name":"synthetic_birth.py","sha256":"5035bc0d57d8c927b774b2792575477ae9ecc426a2518e69def3ab2d0eda9ac4"},{"name":"synthetic_multihost.py","sha256":"c41ea0920de7e03d82e510045ec52be8b2a3df493087fe55ef5eaceafb7e91e1"},{"name":"synthetic_relationships.py","sha256":"393fd27a49a7a996ef9e433bf1472cfd9746a08336dc1e22882ce675e878d8ee"},{"name":"synthetic_sources.py","sha256":"d9090c4adf0189b023226357b5c544e461d674e86db7050057cfbeec37ec0c1d"},{"name":"synthetic_species.py","sha256":"4a839e42820426ea3af33ef9f96551d51159e1b48dbb692259b6d521227bf256"},{"name":"weave.py","sha256":"68980eff472720dd3b3e2cdd01ea2381ded7b0b142f443fdcf85e24e81da7325"}],"tree_sha256":"fef0db12104acd704ff53ada40be70c98fc982194fb5e0c1f9892086931fec60"},"plan_id":"dm:hermes-plan:v1:O3YR1x4fwBrTHF-QO-fbGii0f_D0O-kSzsZbMcuL0vM","profile_id":"dm:hermes-profile:v1:-_Nflr6K0kKZE-trs6NGUeRn3l9mZTdOgx6oZ0D3nPY","provider_ready_id":"dm:hermes-ready:v1:UHiM5_UrowyOAwjwZxkOXdsvbLGRjHVSRZN69JTU0O4","schema":"dm.hermes-body.launch-receipt/v1","starting_handle_id":"dm:hermes-handle:v1:L9ZIK64wzJ-zg-l9QVw_b1btW7o6SOxOhQL_6TET0rM"} diff --git a/vectors/hermes/v1/valid/profile-manifest.json b/vectors/hermes/v1/valid/profile-manifest.json index f31d1ee..b82db44 100644 --- a/vectors/hermes/v1/valid/profile-manifest.json +++ b/vectors/hermes/v1/valid/profile-manifest.json @@ -1 +1 @@ -{"adapter_version":"1.0.0","being_ref":"dm:being:v1:c020x2AEpKTIXF0hnAal0Q64Ia--MtxTfGGk_e4itVQ","body_ref":"dm:body:v1:B0iU3se3iyz0g6tPShEM8_Z-oX7BkspNY4KGQzFQEn0","embodiment_id":"dm:embodiment:v1:c6daXNxOWxfE9qM-j85N8YbFN35Q6Kl7g6AYGf7bbZE","files":[{"name":"SOUL.md","sha256":"0fc541f40cedc23056c07011a9eb99138028968930074c0a0a88db37eb81f25a"},{"name":"config.yaml","sha256":"47a67b881a4530c67c8d609d7bf3f758717acb9a266a5a55fc65049ae4ee38ab"},{"name":"plugins/daimon-matrix/__init__.py","sha256":"c6ef73ae9adedf45a5bd628ca745c1ff958a961f4b8ae0125b134ecddf6d88f5"},{"name":"plugins/daimon-matrix/matrix.json","sha256":"3977a77b68b6d2d70f41540e00529cf88216da21654616e8bd8882bd49493dd5"},{"name":"plugins/daimon-matrix/plugin.yaml","sha256":"6e2bcb16b394f4653727334653e5a4007149e5a3bc72c9958f9dc7e814dd8b92"},{"name":"skills/daimon-matrix/SKILL.md","sha256":"6e0e021c6b0a2423dc95930c6901387738c15600deee2bb8f90a20110c66a275"}],"hermes_commit":"0db1912911fafa384aa5ee0145929658a9d1dd33","hermes_python":{"executable_sha256":"a2fdb31eaf46a78ba3f0687f59b4c5a3bc318f7cd56dbbe6f68272c4af34cc71","implementation":"cpython","supported_interval":">=3.11,<3.14","version":"3.13.5"},"hermes_version":"0.19.0","incarnation_id":"dm:incarnation:v1:VMS4o9jcMXzf6bmAuMHSsZUMK_g_W9N46SK-LLD_7G8","matrix_package":{"contract_schema_sha256":"e23b47040d45c64676b2fc793e375f9be9d7d95078f0b5c384bb5a33383f499f","current_memory_schema_sha256":"3422f822f7b3e05c4b2f422a19a96283993938f11c798cb1982cb5d3cdc38169","modules":[{"name":"__init__.py","sha256":"975cc4fc1bc2628e42c1e24c972fbb458f7522378ba8c3cc720b248bfdf52de5"},{"name":"authority_epochs.py","sha256":"febc83190dbc0b5b3b028ef3ad69431bfe4e6edba9100e0b41eae800145bcd0f"},{"name":"birth.py","sha256":"8407f496592b0b84e5caf78b7384c379d41d40ce78ed61351be4e0dc02741749"},{"name":"canonical.py","sha256":"1abee4501a0621cf981cc247ac21ab825e278dc0a71ed98c2e57cfdc91efbd47"},{"name":"cli.py","sha256":"c2f0d634c2faa1619f280a76e714dbe963300705dd48630c2fa14dfd090bb603"},{"name":"client.py","sha256":"1fd3a27158035280a1ef37ed094305392a580896f21ce665a9d8c38f91148821"},{"name":"cluster.py","sha256":"f183c54a2876b3531a1bbb3fe6d45d9c9c60069608b5c3ec3d2f07dbb50cae91"},{"name":"codex_body.py","sha256":"76eb0b27cecd06d89a220f172042bd94250e3c79dfee3cdd25737e9801565fd4"},{"name":"communication.py","sha256":"ea9342f8326074a0c69ea8534f21310e784b0bdc93c62e1221142078ae22aa3b"},{"name":"conformance.py","sha256":"85493244a77587db27ae2582df1291774aba22401a31cdf0e4cb6a25c8e90a33"},{"name":"curator.py","sha256":"ff9718bdc1577657f3029b71fd35bad701063a185acddb797f12b196a04739ce"},{"name":"curator_worker.py","sha256":"3f717165540cb209c3545cab95bf846803a454c8bef73b3067904b65a4959edb"},{"name":"curator_worker_process.py","sha256":"bdbd6296072b27d1b42222924e5e0af95c5c1ac8d01cfa9c59fdad57fa2d5a2d"},{"name":"daemon.py","sha256":"b1938dfb5a44f3555607a36eb933c1f4f2dab0c8130e063de605630a852b4988"},{"name":"hermes_body.py","sha256":"9b32654d3093d3c1b7a33d3fd932a61fd0d21b32fa28a7c843c4e3f41605a8c9"},{"name":"human_review.py","sha256":"40a1ff0a6da8278c60383ceda0b2e1dfaf90d60929f08da77f3926d8ecd9169f"},{"name":"identity.py","sha256":"6c7909d6e453d7d955c88c552c0a30fe8ce2c479f93008fd866caad1fb8b9412"},{"name":"keystore.py","sha256":"d9f9bebc247f4d613d04bece26f20ef877c375454d5aaabafd038ecfd89698a4"},{"name":"ledger.py","sha256":"1e2d3d3bb8ecbea4ee44fc77b6d31c16948bc9189847026c7c10b01bfb4b4dc7"},{"name":"local_api.py","sha256":"3f4a179815ecf62b6d60e5825eea836d511daa2371ebe25bfb3abdd40787d55b"},{"name":"local_we.py","sha256":"2d9fddcf6c4c1a93a704d99c65d7fe839377d6801e66fea8f552b474083ee224"},{"name":"mcp_server.py","sha256":"1ba204a3b1c865b579f761e5e6792e5d80529983ad55106cb1c1a0b3b6bc48b4"},{"name":"memory_policy.py","sha256":"59390545f633ba8d71b40b51ef5b6a897f994e1adb4bcfa246ab4301fb3f6310"},{"name":"memory_projection.py","sha256":"f260473f31731d8d63f69f1cb3568d10c3b65f2b5948801deeab2d75243a0652"},{"name":"multihost.py","sha256":"37c824e30469aeebd8dcd0269c4249715a16ee5afa05c2b9d4a74b8e26e68664"},{"name":"operator_bootstrap.py","sha256":"23d0b19cb566df0e9a337c791fbde1fc74c75c6a21e3124c1c12ed0014532e9f"},{"name":"peer_transport.py","sha256":"3c86895783e908165508158c17bc5d474b2effcc98c08c8d629aaeaafb2c11a4"},{"name":"projections.py","sha256":"dd31294f4d6dc2ff350ca05f57ed79d7f24f7699d903a9fc8d00cf9932f254b1"},{"name":"publication.py","sha256":"31d0623f4637d280f3f8b6f65fae6ffa961e592004b418d3cae7f4ca5eba0459"},{"name":"relationship_store.py","sha256":"08afda06469d7c89710de115095b6b06e094baa79db6e7f818626c9c76e4abd4"},{"name":"relationships.py","sha256":"d572abd90da60e0beb301fb34af11bdbfac52ecc13605aeb00bfe4ac368092e0"},{"name":"reviewer_cli.py","sha256":"abf92a9f43a8811a36affad7182de085736cb9bde4f55da78fbc064236ff1bc3"},{"name":"routes.py","sha256":"04d5b6b8ba44499d57689f9e97f520a2e309d86532cb4b658a8e168099fc7799"},{"name":"runtime.py","sha256":"f9811a380f14799bde730a7daa9ea66d91da02553bf1a60936d0533fafe1e6d0"},{"name":"scopes.py","sha256":"7a7e07f38ddf3a7866dbba7a66cc4e7ecbcad563c6d8041c2f0ecd859d0978c4"},{"name":"sealed.py","sha256":"60d4f0d85ed89afeeb56ddc2bde9beb331dfe7abf56f82a04d41a4c2f03d6482"},{"name":"service.py","sha256":"be549bce2055a86a8ef06f32c71e54c0dd6fb820a932284a6b5d6a064748401e"},{"name":"sources.py","sha256":"803aaba82c14f6d5d39e10a0e2e583da0641295ab8e910bc29a39a717aa92c38"},{"name":"species.py","sha256":"824978695997e1384614e0e77239eaae63baed1a64a502b29a414492551a8d40"},{"name":"species_runner.py","sha256":"3312dda2c13548503863698482bdd7c6d585415f445cbad765cce41a38ebdfc1"},{"name":"sync.py","sha256":"5836e80d6ea68cde799eb3bd66b4b3b3437db90ce55f8c7d77d7944b728585b2"},{"name":"synthetic_birth.py","sha256":"5035bc0d57d8c927b774b2792575477ae9ecc426a2518e69def3ab2d0eda9ac4"},{"name":"synthetic_multihost.py","sha256":"c41ea0920de7e03d82e510045ec52be8b2a3df493087fe55ef5eaceafb7e91e1"},{"name":"synthetic_relationships.py","sha256":"393fd27a49a7a996ef9e433bf1472cfd9746a08336dc1e22882ce675e878d8ee"},{"name":"synthetic_sources.py","sha256":"d9090c4adf0189b023226357b5c544e461d674e86db7050057cfbeec37ec0c1d"},{"name":"synthetic_species.py","sha256":"4a839e42820426ea3af33ef9f96551d51159e1b48dbb692259b6d521227bf256"},{"name":"weave.py","sha256":"deebdf47950ec1c99ee3a4fee49022c5098f5e98bda5624c114d48f1f6d065e0"}],"tree_sha256":"9b76294d4eed59111999adc9b145f0ce57be5c2f112313eb5ae5193295cc6467"},"matrix_session_id":"dm:session:v1:RlH_IRj2gD0v96VNOccuSaxzQix7p-HZcXTvPhOSu3w","plan_hash":"3b7611d71e1fc01ad31c5f903be7db1a28b47ff0f43be912cec65b31cb8bd2f3","profile_id":"dm:hermes-profile:v1:1WTPEnY6XIj9l-jKUPpGOALDeuS3PMBy2omuXQW-aSg","schema":"dm.hermes-body.profile-manifest/v1","workspace_ref":"dm:workspace:v1:1g9p07RgFRlBvtWJcvb92rOP7KNe9mB8v3TeueVtERM"} +{"adapter_version":"1.0.0","being_ref":"dm:being:v1:c020x2AEpKTIXF0hnAal0Q64Ia--MtxTfGGk_e4itVQ","body_ref":"dm:body:v1:B0iU3se3iyz0g6tPShEM8_Z-oX7BkspNY4KGQzFQEn0","embodiment_id":"dm:embodiment:v1:c6daXNxOWxfE9qM-j85N8YbFN35Q6Kl7g6AYGf7bbZE","files":[{"name":"SOUL.md","sha256":"0fc541f40cedc23056c07011a9eb99138028968930074c0a0a88db37eb81f25a"},{"name":"config.yaml","sha256":"47a67b881a4530c67c8d609d7bf3f758717acb9a266a5a55fc65049ae4ee38ab"},{"name":"plugins/daimon-matrix/__init__.py","sha256":"c6ef73ae9adedf45a5bd628ca745c1ff958a961f4b8ae0125b134ecddf6d88f5"},{"name":"plugins/daimon-matrix/matrix.json","sha256":"3977a77b68b6d2d70f41540e00529cf88216da21654616e8bd8882bd49493dd5"},{"name":"plugins/daimon-matrix/plugin.yaml","sha256":"6e2bcb16b394f4653727334653e5a4007149e5a3bc72c9958f9dc7e814dd8b92"},{"name":"skills/daimon-matrix/SKILL.md","sha256":"6e0e021c6b0a2423dc95930c6901387738c15600deee2bb8f90a20110c66a275"}],"hermes_commit":"0db1912911fafa384aa5ee0145929658a9d1dd33","hermes_python":{"executable_sha256":"a2fdb31eaf46a78ba3f0687f59b4c5a3bc318f7cd56dbbe6f68272c4af34cc71","implementation":"cpython","supported_interval":">=3.11,<3.14","version":"3.13.5"},"hermes_version":"0.19.0","incarnation_id":"dm:incarnation:v1:VMS4o9jcMXzf6bmAuMHSsZUMK_g_W9N46SK-LLD_7G8","matrix_package":{"contract_schema_sha256":"e23b47040d45c64676b2fc793e375f9be9d7d95078f0b5c384bb5a33383f499f","current_memory_schema_sha256":"3422f822f7b3e05c4b2f422a19a96283993938f11c798cb1982cb5d3cdc38169","modules":[{"name":"__init__.py","sha256":"975cc4fc1bc2628e42c1e24c972fbb458f7522378ba8c3cc720b248bfdf52de5"},{"name":"authority_epochs.py","sha256":"febc83190dbc0b5b3b028ef3ad69431bfe4e6edba9100e0b41eae800145bcd0f"},{"name":"birth.py","sha256":"8407f496592b0b84e5caf78b7384c379d41d40ce78ed61351be4e0dc02741749"},{"name":"canonical.py","sha256":"1abee4501a0621cf981cc247ac21ab825e278dc0a71ed98c2e57cfdc91efbd47"},{"name":"cli.py","sha256":"c2f0d634c2faa1619f280a76e714dbe963300705dd48630c2fa14dfd090bb603"},{"name":"client.py","sha256":"1fd3a27158035280a1ef37ed094305392a580896f21ce665a9d8c38f91148821"},{"name":"cluster.py","sha256":"f183c54a2876b3531a1bbb3fe6d45d9c9c60069608b5c3ec3d2f07dbb50cae91"},{"name":"codex_body.py","sha256":"76eb0b27cecd06d89a220f172042bd94250e3c79dfee3cdd25737e9801565fd4"},{"name":"collective_memory.py","sha256":"90dbc64cc0a9e8c9d37e7ec509d963958eb90915d88d563b6a4d6e6ed4f4bb5f"},{"name":"communication.py","sha256":"ea9342f8326074a0c69ea8534f21310e784b0bdc93c62e1221142078ae22aa3b"},{"name":"conformance.py","sha256":"215da0d092fe16267a0b0910d826545f8a7fb617b5cc25a19f22353a798641c2"},{"name":"curator.py","sha256":"ff9718bdc1577657f3029b71fd35bad701063a185acddb797f12b196a04739ce"},{"name":"curator_worker.py","sha256":"3f717165540cb209c3545cab95bf846803a454c8bef73b3067904b65a4959edb"},{"name":"curator_worker_process.py","sha256":"bdbd6296072b27d1b42222924e5e0af95c5c1ac8d01cfa9c59fdad57fa2d5a2d"},{"name":"daemon.py","sha256":"b1938dfb5a44f3555607a36eb933c1f4f2dab0c8130e063de605630a852b4988"},{"name":"hermes_body.py","sha256":"9b32654d3093d3c1b7a33d3fd932a61fd0d21b32fa28a7c843c4e3f41605a8c9"},{"name":"human_review.py","sha256":"40a1ff0a6da8278c60383ceda0b2e1dfaf90d60929f08da77f3926d8ecd9169f"},{"name":"identity.py","sha256":"6c7909d6e453d7d955c88c552c0a30fe8ce2c479f93008fd866caad1fb8b9412"},{"name":"keystore.py","sha256":"d9f9bebc247f4d613d04bece26f20ef877c375454d5aaabafd038ecfd89698a4"},{"name":"ledger.py","sha256":"9673f7e12d3d35ed6ec8043ddf9f729c6f20153f795e748e5e3f3670d68b1e96"},{"name":"local_api.py","sha256":"3f4a179815ecf62b6d60e5825eea836d511daa2371ebe25bfb3abdd40787d55b"},{"name":"local_we.py","sha256":"2d9fddcf6c4c1a93a704d99c65d7fe839377d6801e66fea8f552b474083ee224"},{"name":"mcp_server.py","sha256":"1ba204a3b1c865b579f761e5e6792e5d80529983ad55106cb1c1a0b3b6bc48b4"},{"name":"memory_policy.py","sha256":"59390545f633ba8d71b40b51ef5b6a897f994e1adb4bcfa246ab4301fb3f6310"},{"name":"memory_projection.py","sha256":"f260473f31731d8d63f69f1cb3568d10c3b65f2b5948801deeab2d75243a0652"},{"name":"multihost.py","sha256":"37c824e30469aeebd8dcd0269c4249715a16ee5afa05c2b9d4a74b8e26e68664"},{"name":"operator_bootstrap.py","sha256":"23d0b19cb566df0e9a337c791fbde1fc74c75c6a21e3124c1c12ed0014532e9f"},{"name":"peer_transport.py","sha256":"3c86895783e908165508158c17bc5d474b2effcc98c08c8d629aaeaafb2c11a4"},{"name":"projections.py","sha256":"dd31294f4d6dc2ff350ca05f57ed79d7f24f7699d903a9fc8d00cf9932f254b1"},{"name":"publication.py","sha256":"31d0623f4637d280f3f8b6f65fae6ffa961e592004b418d3cae7f4ca5eba0459"},{"name":"relationship_store.py","sha256":"08afda06469d7c89710de115095b6b06e094baa79db6e7f818626c9c76e4abd4"},{"name":"relationships.py","sha256":"d572abd90da60e0beb301fb34af11bdbfac52ecc13605aeb00bfe4ac368092e0"},{"name":"reviewer_cli.py","sha256":"abf92a9f43a8811a36affad7182de085736cb9bde4f55da78fbc064236ff1bc3"},{"name":"routes.py","sha256":"04d5b6b8ba44499d57689f9e97f520a2e309d86532cb4b658a8e168099fc7799"},{"name":"runtime.py","sha256":"f9811a380f14799bde730a7daa9ea66d91da02553bf1a60936d0533fafe1e6d0"},{"name":"scopes.py","sha256":"7a7e07f38ddf3a7866dbba7a66cc4e7ecbcad563c6d8041c2f0ecd859d0978c4"},{"name":"sealed.py","sha256":"60d4f0d85ed89afeeb56ddc2bde9beb331dfe7abf56f82a04d41a4c2f03d6482"},{"name":"service.py","sha256":"be549bce2055a86a8ef06f32c71e54c0dd6fb820a932284a6b5d6a064748401e"},{"name":"sources.py","sha256":"803aaba82c14f6d5d39e10a0e2e583da0641295ab8e910bc29a39a717aa92c38"},{"name":"species.py","sha256":"824978695997e1384614e0e77239eaae63baed1a64a502b29a414492551a8d40"},{"name":"species_runner.py","sha256":"3312dda2c13548503863698482bdd7c6d585415f445cbad765cce41a38ebdfc1"},{"name":"sync.py","sha256":"5836e80d6ea68cde799eb3bd66b4b3b3437db90ce55f8c7d77d7944b728585b2"},{"name":"synthetic_birth.py","sha256":"5035bc0d57d8c927b774b2792575477ae9ecc426a2518e69def3ab2d0eda9ac4"},{"name":"synthetic_multihost.py","sha256":"c41ea0920de7e03d82e510045ec52be8b2a3df493087fe55ef5eaceafb7e91e1"},{"name":"synthetic_relationships.py","sha256":"393fd27a49a7a996ef9e433bf1472cfd9746a08336dc1e22882ce675e878d8ee"},{"name":"synthetic_sources.py","sha256":"d9090c4adf0189b023226357b5c544e461d674e86db7050057cfbeec37ec0c1d"},{"name":"synthetic_species.py","sha256":"4a839e42820426ea3af33ef9f96551d51159e1b48dbb692259b6d521227bf256"},{"name":"weave.py","sha256":"68980eff472720dd3b3e2cdd01ea2381ded7b0b142f443fdcf85e24e81da7325"}],"tree_sha256":"fef0db12104acd704ff53ada40be70c98fc982194fb5e0c1f9892086931fec60"},"matrix_session_id":"dm:session:v1:RlH_IRj2gD0v96VNOccuSaxzQix7p-HZcXTvPhOSu3w","plan_hash":"3b7611d71e1fc01ad31c5f903be7db1a28b47ff0f43be912cec65b31cb8bd2f3","profile_id":"dm:hermes-profile:v1:ONrj7wpkjhJU77faXS7Ds3Utdjq5iH5JBTIznF8DOLk","schema":"dm.hermes-body.profile-manifest/v1","workspace_ref":"dm:workspace:v1:1g9p07RgFRlBvtWJcvb92rOP7KNe9mB8v3TeueVtERM"}