From 81422e3d668de0640199796676d7f9aa081ebef7 Mon Sep 17 00:00:00 2001 From: tegwick Date: Wed, 28 Oct 2026 09:50:58 +0100 Subject: [PATCH] NK-WP-0035-T04: readback records audit-core's independent_reader basis Co-Authored-By: Claude Opus 5.5 Assistant: claude-code Assistant-Model: opus Assistant-Process: 308701@bnt-lap001 Assistant-Session: 5548e447-444c-4dcc-bed1-05c16eebfe56 --- .../emission-cadence-profile/receiver_readback.py | 8 ++++++-- .../tests/test_receiver_readback.py | 14 ++++++++++++-- ...NK-WP-0035-emission-cadence-security-profile.md | 10 +++++++++- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/tools/emission-cadence-profile/receiver_readback.py b/tools/emission-cadence-profile/receiver_readback.py index 1afc756..2ca0775 100644 --- a/tools/emission-cadence-profile/receiver_readback.py +++ b/tools/emission-cadence-profile/receiver_readback.py @@ -58,9 +58,13 @@ def readback(request, captured, get): findings = [] if status != 200: findings.append(f'reconciliation returned {status}: {body.get("error", "")}') - receiver = {} + receiver, basis = {}, None else: receiver = {row['class']: row['count'] for row in body.get('counts', [])} + # audit-core marks cross-source answers so they cannot pass for the source's own check. + basis = body.get('basis') + if basis != 'independent_reader': + findings.append(f'reconciliation basis is {basis!r}, not independent_reader') for cls in sorted(set(receiver) | set(captured)): if receiver.get(cls, 0) != captured.get(cls, 0): findings.append(f'class {cls}: captured {captured.get(cls, 0)}, receiver {receiver.get(cls, 0)}') @@ -73,7 +77,7 @@ def readback(request, captured, get): elif (body.get('source'), body.get('tenant')) not in ((request['source'], request['tenant']), (None, None)): findings.append(f'event {event_id[:12]}… source/tenant differs') return {'schema': SCHEMA, 'request': request, 'captured_counts': dict(captured), - 'receiver_counts': receiver, 'event_presence': presence, + 'receiver_counts': receiver, 'receiver_basis': basis, 'event_presence': presence, 'verdict': 'agree' if not findings else 'diverge', 'findings': findings, 'payloads_retained': False, 'token_retained': False, 'means': 'agreement proves neither completeness nor King\'s Guard acceptance'} diff --git a/tools/emission-cadence-profile/tests/test_receiver_readback.py b/tools/emission-cadence-profile/tests/test_receiver_readback.py index 02c3af4..2347895 100644 --- a/tools/emission-cadence-profile/tests/test_receiver_readback.py +++ b/tools/emission-cadence-profile/tests/test_receiver_readback.py @@ -14,7 +14,8 @@ spec.loader.exec_module(rb) CAPTURE = Path(__file__).parents[3] / 'docs/evidence/2026-10-15-cadence-adoption/hourly-observation-capture' -def fake_receiver(counts, present, source='qonto-assistant', tenant='tenant:friendly:binky', recon_status=200): +def fake_receiver(counts, present, source='qonto-assistant', tenant='tenant:friendly:binky', recon_status=200, + basis='independent_reader'): calls = [] def get(path): @@ -22,7 +23,7 @@ def fake_receiver(counts, present, source='qonto-assistant', tenant='tenant:frie if path.startswith('/v1/reconciliation?'): if recon_status != 200: return recon_status, {'error': 'source_not_allowed'} - return 200, {'counts': [{'class': k, 'count': v} for k, v in counts.items()]} + return 200, {'counts': [{'class': k, 'count': v} for k, v in counts.items()], 'basis': basis} event_id = path.rsplit('/', 1)[1] if event_id in present: return 200, {'source': source, 'tenant': tenant, 'id': event_id} @@ -90,3 +91,12 @@ def test_receipt_never_contains_token(tmp_path, monkeypatch): assert rb.main(['--capture-dir', str(CAPTURE), '--token-file', str(token), '--output', str(out)]) == 0 assert 'SENTINEL' not in out.read_text() assert json.loads(out.read_text())['verdict'] == 'agree' + + +def test_missing_independent_basis_diverges(): + request, captured = rb.load_capture(CAPTURE) + get, _ = fake_receiver(captured, set(request['event_ids']), basis=None) + receipt = rb.readback(request, captured, get) + assert receipt['verdict'] == 'diverge' + assert receipt['receiver_basis'] is None + assert any('basis' in f for f in receipt['findings']) diff --git a/workplans/NK-WP-0035-emission-cadence-security-profile.md b/workplans/NK-WP-0035-emission-cadence-security-profile.md index 7795d71..c174406 100644 --- a/workplans/NK-WP-0035-emission-cadence-security-profile.md +++ b/workplans/NK-WP-0035-emission-cadence-security-profile.md @@ -90,7 +90,7 @@ Python compilation and `git diff --check` pass. id: NK-WP-0035-T04 status: wait priority: high -blocking_reason: "Elapsed-hour capture done 2026-10-15 (3 events, receiver readback captured). Readback run 2026-10-23: presence 3/3, counts refused 403 source_not_allowed (operator reader scoped to user-engine); waiting on audit-core's cross-source count path and a rerun, then King's Guard acceptance (KG-WP-0005-T03, wait) and QONTO-WP-0006-T03." +blocking_reason: "Elapsed-hour capture done 2026-10-15 (3 events, receiver readback captured). Readback run 2026-10-23: presence 3/3, counts refused 403 source_not_allowed (operator reader scoped to user-engine); audit-core cross-source count path implemented (d46e89e); waiting on the founder's audit-core release (AUDIT-WP-0013-T07) and the rerun, then King's Guard acceptance (KG-WP-0005-T03, wait) and QONTO-WP-0006-T03." state_hub_task_id: "e3fbc8b8-00d5-57ce-ba0c-19b58044c950" ``` @@ -455,6 +455,14 @@ counts for a full-scope may_read reader, or name the sanctioned independent coun Rerun the same helper once that is answered. The receipt still goes to King's Guard as presence-only evidence. T04 stays wait. +2026-10-28: audit-core answered (audit-core `d46e89e`, AUDIT-WP-0013-T06 done): a full-scope +`may_read` reader (`tenants: ["*"]`) may now count another source's window on +`/v1/reconciliation`; cross-source answers carry `basis: "independent_reader"`. The helper +records `receiver_basis` and treats a 200 without that basis as `diverge`. Next: the founder +releases the audit-core image (AUDIT-WP-0013-T07: `make image-publish`, pin digest, dry run, +apply, rollout), checks `GET /v1/stats` returns 200 under the operator reader token, then reruns +the same readback. An `agree` receipt goes to King's Guard (KG-WP-0005-T03) and qonto-assistant. + ## Automate staged receiver derivation without sibling mutations ```task