From 931e0b32490f47bd4eb577c5c9dbd90b7e689fc7 Mon Sep 17 00:00:00 2001 From: tegwick Date: Sat, 3 Oct 2026 22:59:46 +0200 Subject: [PATCH] Migrate owned cadence declaration to published contract Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a1029d-2c93-7500-a0e0-e3e089668b29 --- cadence.yaml | 234 +++++++++++++++--- docs/emission-cadence.md | 58 ++++- tests/test_cadence.py | 35 ++- ...duction-readiness-and-consumer-adoption.md | 14 +- 4 files changed, 289 insertions(+), 52 deletions(-) diff --git a/cadence.yaml b/cadence.yaml index d353cd6..80d6669 100644 --- a/cadence.yaml +++ b/cadence.yaml @@ -1,38 +1,196 @@ -# Source-side emission cadence for load-bearing approval evidence. -# Statute §9.6; detection surface GH-WP-0002-T04. -schema_version: "0.1" -source: approval-engine -kind: load-bearing -form: heartbeat-or-reconciliation -rate_monitoring: forbidden - -heartbeat: - class: heartbeat - interval: 24h - assertion: nothing-to-report - missing: finding - -reconciliation: - compare: - local: committed outbox counts per class - remote: "audit-core event counts where source=approval-engine" - divergence: finding - undrained_local: lag-not-divergence - -lag_bound: - outbox_depth: 100 - outbox_age: 1h - exceed: finding - -classes: - issuance: - action: approval.issuance - use: - action: approval.use - note: "Public CAS consumption under GH-DEC-2026-003." - supersession: - action: approval.supersession - revocation: - action: approval.revocation - heartbeat: - action: approval.heartbeat +{ + "schema_version": "0.1", + "declaration_id": "approval-engine-emission-cadence-v0.1", + "source": "approval-engine", + "sources": [ + { + "source_id": "approval-engine:issuance", + "source_system": "approval-engine", + "event_class": "issuance", + "form": "heartbeat-or-reconciliation", + "reconciliation": { + "compare_local": "committed outbox issuance counts for the same tenant and bounded window; drained counts exclude pending lag", + "compare_observed": "audit-core accepted action=approval.issuance counts for source=approval-engine, tenant=tenant:platform and identical since/until bounds", + "divergence": "finding", + "undrained_local": "lag-not-divergence" + }, + "extensions": { + "netkingdom": { + "evidence_class": "load-bearing", + "rate_monitoring": "forbidden" + }, + "approval-engine": { + "action": "approval.issuance" + } + }, + "heartbeat": { + "event_class": "audit-core.heartbeat", + "interval_seconds": 86400, + "assertion": "nothing-to-report", + "missing": "finding", + "extensions": { + "approval-engine": { + "asserted_class": "issuance", + "requested_observer_max_gap_seconds": 172800, + "observer_registration_status": "pending" + } + } + } + }, + { + "source_id": "approval-engine:use", + "source_system": "approval-engine", + "event_class": "use", + "form": "heartbeat-or-reconciliation", + "reconciliation": { + "compare_local": "committed outbox use counts for the same tenant and bounded window; drained counts exclude pending lag", + "compare_observed": "audit-core accepted action=approval.use counts for source=approval-engine, tenant=tenant:platform and identical since/until bounds", + "divergence": "finding", + "undrained_local": "lag-not-divergence" + }, + "extensions": { + "netkingdom": { + "evidence_class": "load-bearing", + "rate_monitoring": "forbidden" + }, + "approval-engine": { + "action": "approval.use" + } + }, + "heartbeat": { + "event_class": "audit-core.heartbeat", + "interval_seconds": 86400, + "assertion": "nothing-to-report", + "missing": "finding", + "extensions": { + "approval-engine": { + "asserted_class": "use", + "requested_observer_max_gap_seconds": 172800, + "observer_registration_status": "pending" + } + } + } + }, + { + "source_id": "approval-engine:supersession", + "source_system": "approval-engine", + "event_class": "supersession", + "form": "heartbeat-or-reconciliation", + "reconciliation": { + "compare_local": "committed outbox supersession counts for the same tenant and bounded window; drained counts exclude pending lag", + "compare_observed": "audit-core accepted action=approval.supersession counts for source=approval-engine, tenant=tenant:platform and identical since/until bounds", + "divergence": "finding", + "undrained_local": "lag-not-divergence" + }, + "extensions": { + "netkingdom": { + "evidence_class": "load-bearing", + "rate_monitoring": "forbidden" + }, + "approval-engine": { + "action": "approval.supersession" + } + }, + "heartbeat": { + "event_class": "audit-core.heartbeat", + "interval_seconds": 86400, + "assertion": "nothing-to-report", + "missing": "finding", + "extensions": { + "approval-engine": { + "asserted_class": "supersession", + "requested_observer_max_gap_seconds": 172800, + "observer_registration_status": "pending" + } + } + } + }, + { + "source_id": "approval-engine:revocation", + "source_system": "approval-engine", + "event_class": "revocation", + "form": "heartbeat-or-reconciliation", + "reconciliation": { + "compare_local": "committed outbox revocation counts for the same tenant and bounded window; drained counts exclude pending lag", + "compare_observed": "audit-core accepted action=approval.revocation counts for source=approval-engine, tenant=tenant:platform and identical since/until bounds", + "divergence": "finding", + "undrained_local": "lag-not-divergence" + }, + "extensions": { + "netkingdom": { + "evidence_class": "load-bearing", + "rate_monitoring": "forbidden" + }, + "approval-engine": { + "action": "approval.revocation" + } + }, + "heartbeat": { + "event_class": "audit-core.heartbeat", + "interval_seconds": 86400, + "assertion": "nothing-to-report", + "missing": "finding", + "extensions": { + "approval-engine": { + "asserted_class": "revocation", + "requested_observer_max_gap_seconds": 172800, + "observer_registration_status": "pending" + } + } + } + }, + { + "source_id": "approval-engine:heartbeat", + "source_system": "approval-engine", + "event_class": "heartbeat", + "form": "heartbeat-or-reconciliation", + "reconciliation": { + "compare_local": "committed outbox heartbeat counts for the same tenant and bounded window; drained counts exclude pending lag", + "compare_observed": "audit-core accepted action=audit-core.heartbeat counts for source=approval-engine, tenant=tenant:platform and identical since/until bounds", + "divergence": "finding", + "undrained_local": "lag-not-divergence" + }, + "extensions": { + "netkingdom": { + "evidence_class": "load-bearing", + "rate_monitoring": "forbidden" + }, + "approval-engine": { + "action": "audit-core.heartbeat" + } + } + } + ], + "extensions": { + "approval-engine": { + "lag_bound": { + "outbox_depth": 100, + "outbox_age_seconds": 3600, + "exceed": "finding", + "enforcement_status": "observer-delivery-pending" + }, + "inventory": { + "load_bearing": [ + "issuance", + "use", + "supersession", + "revocation", + "heartbeat" + ], + "rare_load_bearing": [ + "issuance", + "use", + "supersession", + "revocation" + ], + "attributive": [] + }, + "contract": { + "owner": "info-tech-canon", + "document_version": "0.2.0", + "status": "candidate", + "schema_sha256": "6d52692eb1e4d1325e0d6062d95acded98beddaae5bcfe24c6f0e91f1b6be6ae" + } + } + } +} diff --git a/docs/emission-cadence.md b/docs/emission-cadence.md index 50482eb..49bb901 100644 --- a/docs/emission-cadence.md +++ b/docs/emission-cadence.md @@ -24,7 +24,7 @@ becomes a missing positive rather than a quiet month. | Interval | 24 hours (SHOULD also be emittable on demand) | | Assertion | `nothing-to-report` | | Counts | `{issuance, use, supersession, revocation}` of committed outbox rows | -| Missing heartbeat | **finding** | +| Missing heartbeat | **finding** after the requested 172800s receiver bound; registration pending | ### Reconciliation @@ -38,7 +38,7 @@ accepted event counts for `source=approval-engine` and the corresponding | `use` | `approval.use` | | `supersession` | `approval.supersession` | | `revocation` | `approval.revocation` | -| `heartbeat` | `approval.heartbeat` | +| `heartbeat` | `audit-core.heartbeat` | Undrained local rows are this engine's lag, not yet a divergence. A row with `drained_at` set that `audit-core` does not hold is the omission @@ -46,7 +46,7 @@ case §9.6 names. ## What is a finding -- No heartbeat arrives for more than one interval. +- No per-class heartbeat arrives beyond the requested 48h receiver bound (24h emission interval). - `audit-core` count for a class is less than this engine's drained count for that class. - Outbox depth or age exceeds the lag bound in `cadence.yaml` (drain stuck). @@ -59,3 +59,55 @@ Atomicity prevents accidental omission (crash between mutation and emit). Cadence and reconciliation **detect** adversarial omission after the fact. Nothing in the model prevents a compromised source from suppressing. That residual is stated, not closed. + +## Imported declaration and source inventory — 2026-10-03 + +`cadence.yaml` now uses InfoTechCanon's published candidate 0.2.0 contract, +schema 0.1, rather than the historical flattened draft. The source-owned +inventory is `extensions.approval-engine.inventory`: issuance, use, +supersession and revocation are rare load-bearing transitions; heartbeat is +the load-bearing delivery control, reconciled without a recursive heartbeat. +No evidence classification or emission interval has been relaxed. + +The implementation emits four atomic `audit-core.heartbeat` events, asserting +the four transition classes through `data.class`; it does not emit +`approval.heartbeat`. Production CLI defaults to 86400s. The receiver request +in `audit-source-registration.md` remains 172800s; it is not a live admission +receipt. Lag depth 100 and age 3600s remain declared bounds, with observer +enforcement pending. Repository conformance does not prove stream completeness. + +Live closure stays in APPROVAL-WP-0002-T04: audit-core/platform must bind exact +sender `approval-engine`, tenant `tenant:platform`, explicit redact policy, +the already delivered protected credential/native custody (2026-09-11), +authenticated ingest and all four heartbeat class registrations. +Synthetic native accepted/duplicate, read-refusal and independent archive +readback already passed in the platform receipts +`docs/evidence/2026-09-11-factory-native-producers.json` and +`docs/evidence/2026-09-11-factory-native-readback.json`; these do not prove +deployed service or lifecycle transaction emission. Remaining acceptance +requires service admission, actual lifecycle drain and bearer revocation, +same-window drained-versus-accepted reconciliation, startup heartbeat and +missing-class/lag findings. Existing AUDIT-WP-0009 detection code is reused; +neither a second observer nor new receiver APIs are required. + +Orientation: Emergent Knowledge 0.2.5, digest +`9e186dbd6f2d5eb455bca24db4b03cd866c63c4e94660430dd15f2ca9856215f`, +start/evidence cards verified-files, orientation-only. + +Validation: full owner suite 169 passed. Imported owner schema SHA-256 +`6d52692eb1e4d1325e0d6062d95acded98beddaae5bcfe24c6f0e91f1b6be6ae` +and NetKingdom profile returned contract_valid/profile_assessed/conformant true +with zero findings for the authoritative inventory above. Reproduce from +net-kingdom with: + +```bash +uv run tools/emission-cadence-profile/emission_cadence_profile.py \ + --contract-schema ../info-tech-canon/infospace/schemas/emission-cadence.schema.yaml \ + --load-bearing heartbeat \ + --rare-load-bearing issuance --rare-load-bearing use \ + --rare-load-bearing supersession --rare-load-bearing revocation \ + ../approval-engine/cadence.yaml +``` + +This supplies an owner-authored instance to NK-WP-0035; it does not accept the +proposed NetKingdom profile or claim receiver admission/observed cadence. diff --git a/tests/test_cadence.py b/tests/test_cadence.py index 8ac9b49..6e40cf0 100644 --- a/tests/test_cadence.py +++ b/tests/test_cadence.py @@ -1,5 +1,9 @@ +import json from pathlib import Path +from approval_engine.audit import audit_envelope +from approval_engine.store import EVENT_CLASSES, HEARTBEAT_CLASSES + ROOT = Path(__file__).resolve().parents[1] @@ -8,15 +12,26 @@ def test_layer_yaml_cadence_declared(): assert "cadence_status: declared" in text assert "cadence_form: heartbeat-or-reconciliation" in text assert "cadence: cadence.yaml" in text - assert "cadence_status: undeclared" not in text -def test_cadence_yaml_forbids_rate_monitoring(): - text = (ROOT / "cadence.yaml").read_text() - assert "kind: load-bearing" in text - assert "form: heartbeat-or-reconciliation" in text - assert "rate_monitoring: forbidden" in text - assert "missing: finding" in text - assert "divergence: finding" in text - for cls in ("issuance", "use", "supersession", "revocation", "heartbeat"): - assert f"{cls}:" in text +def test_cadence_inventory_matches_durable_emitter(engine): + declaration = json.loads((ROOT / "cadence.yaml").read_text()) + inventory = declaration["extensions"]["approval-engine"]["inventory"] + assert set(inventory["load_bearing"]) == set(EVENT_CLASSES) + assert set(inventory["rare_load_bearing"]) == set(HEARTBEAT_CLASSES) + entries = {entry["event_class"]: entry for entry in declaration["sources"]} + assert set(entries) == set(EVENT_CLASSES) + engine.emit_heartbeat() + observed = {audit_envelope(row["payload"])["data"]["class"]: + audit_envelope(row["payload"]) for row in engine.undrained()} + assert set(observed) == set(HEARTBEAT_CLASSES) + for cls in HEARTBEAT_CLASSES: + beat = entries[cls]["heartbeat"] + assert beat["event_class"] == observed[cls]["type"] == "audit-core.heartbeat" + assert beat["assertion"] == observed[cls]["data"]["assertion"] + assert beat["extensions"]["approval-engine"]["asserted_class"] == cls + assert beat["interval_seconds"] == 86400 + assert beat["extensions"]["approval-engine"]["requested_observer_max_gap_seconds"] == 172800 + assert entries[cls]["extensions"]["netkingdom"]["rate_monitoring"] == "forbidden" + assert entries[cls]["reconciliation"]["undrained_local"] == "lag-not-divergence" + assert "heartbeat" not in entries["heartbeat"] # transport control has no recursive heartbeat diff --git a/workplans/APPROVAL-WP-0002-production-readiness-and-consumer-adoption.md b/workplans/APPROVAL-WP-0002-production-readiness-and-consumer-adoption.md index af48eb1..c43fa78 100644 --- a/workplans/APPROVAL-WP-0002-production-readiness-and-consumer-adoption.md +++ b/workplans/APPROVAL-WP-0002-production-readiness-and-consumer-adoption.md @@ -9,7 +9,7 @@ flavor: residual owner: codex topic_slug: netkingdom created: "2026-09-01" -updated: "2026-09-27" +updated: "2026-10-03" reviewed_at: "2026-09-01" reviewed_against_commit: "ebce5abb276c01ab29ce2526f3b8abb332dc9e90" reviewed_note: >- @@ -546,6 +546,7 @@ id: APPROVAL-WP-0002-T04 status: wait flavor: residual priority: high +blocking_reason: "Source declaration migrated and emitter mapping verified; sender/native custody delivered 2026-09-11; await canonical receiver declaration and heartbeat class admission, service admission and actual lifecycle outbox/cadence proof plus bearer revocation (synthetic accepted/duplicate/read-refusal/readback already verified 2026-09-11), exact-window reconciliation and observed per-class heartbeat/lag findings. Existing AUDIT-WP-0009 observer implementation is reused." state_hub_task_id: "777a5cf5-c1db-5e07-8de6-ab109db5ccdc" ``` @@ -793,3 +794,14 @@ has exhausted its own reachable surface until an external owner responds; inbox items received since the 2026-09-10 update (KeyCape/glas-harness/ flex-auth/gate-house/grok) are status requests or informational and do not supply the missing owner answers. No new task or workplan opened. + +## Cadence owner adoption — 2026-10-03 + +Migrated cadence.yaml to the owner-published InfoTechCanon candidate envelope +and published the source-owned class inventory. Tests compare the declaration +against actual atomic heartbeat outbox envelopes, fixing the historical +approval.heartbeat alias while preserving the 86400s emission and requested +172800s detection bound. Lag enforcement and receiver registration remain +explicitly pending. See docs/emission-cadence.md for the bounded owner acceptance +requirements; T04 stays wait, and this workplan stays blocked. Source validation +does not admit a workload or close its real delivery proof.