From 5eb7e86f7a5fccf8bb8c40bd991e32bb9ae440c9 Mon Sep 17 00:00:00 2001 From: tegwick Date: Thu, 15 Oct 2026 20:42:29 +0200 Subject: [PATCH] Use the shared coordination frame for audit heartbeat defaults Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a13eab-1e6a-7b33-9c37-372e90ada3a2 --- docs/operator-runbook.md | 16 ++++++++++++++++ specs/audit-emission-cadence.yaml | 20 +++++++++++++------- src/qonto_assistant/config.py | 4 +++- src/qonto_assistant/coordination.py | 6 ++++++ tests/test_cadence_declaration.py | 13 ++++++++++++- 5 files changed, 50 insertions(+), 9 deletions(-) create mode 100644 src/qonto_assistant/coordination.py diff --git a/docs/operator-runbook.md b/docs/operator-runbook.md index 195250c..7fb7b2a 100644 --- a/docs/operator-runbook.md +++ b/docs/operator-runbook.md @@ -268,3 +268,19 @@ next. Ordinary denies (`authz_denied`, `tenant_scope`, `unknown_capability`) never count toward this — only the two reason classes that indicate probing/exfiltration rather than a client mistake. See `docs/SecurityPractice.md` §9.3 and `src/qonto_assistant/security_watch.py`. + + +## Ecosystem coordination-frame adoption — 2026-10-15 + +The source heartbeat default now imports FRAME_SECONDS from the generated +qonto_assistant/coordination.py. Initial frame is f=3600s, configured once in +coordination-engine/config/coordination-frame.json. The source-owned cadence +and rapp-qonto manifest derive the same interval and preserve config SHA-256 +provenance. Regenerate them with net-kingdom/tools/coordination-frame/render.py; +--check detects drift. An explicitly configured instance interval still needs +matching declaration and receiver mapping. + +This source update does not deploy rapp-qonto or create its missing collector. +QONTO-WP-0006-T02/T03 and AUDIT-WP-0013-T05 still hold sender/delivery and +independent observation acceptance. Existing bank credential TTL, policy, +identity, rate limits and process-local counter limitations are unchanged. diff --git a/specs/audit-emission-cadence.yaml b/specs/audit-emission-cadence.yaml index 095b133..ac3946b 100644 --- a/specs/audit-emission-cadence.yaml +++ b/specs/audit-emission-cadence.yaml @@ -1,5 +1,4 @@ -# Source-owned InfoTechCanon declaration; intended cadence, not delivery proof. -# Generic document 0.2.0 / schema 0.1; owner bundle b08b4d95fc4b0bd3. +# Source-owned cadence; coordination timings generated from coordination-engine/config/coordination-frame.json schema_version: '0.1' declaration_id: qonto-assistant.audit.v1 source: qonto-assistant @@ -11,7 +10,7 @@ sources: form: heartbeat-or-reconciliation heartbeat: event_class: audit.heartbeat - interval: PT24H + interval: PT3600S assertion: nothing-to-report missing: finding extensions: &id001 @@ -33,7 +32,7 @@ sources: form: heartbeat-or-reconciliation heartbeat: event_class: audit.heartbeat - interval: PT24H + interval: PT3600S assertion: nothing-to-report missing: finding extensions: *id001 @@ -53,7 +52,7 @@ sources: form: heartbeat-or-reconciliation heartbeat: event_class: audit.heartbeat - interval: PT24H + interval: PT3600S assertion: nothing-to-report missing: finding extensions: *id001 @@ -65,6 +64,13 @@ extensions: netkingdom: contract_digest: b08b4d95fc4b0bd3 contract_document_version: 0.2.0 + coordination_frame: + ref: coordination-engine/config/coordination-frame.json + sha256: d15e092387f128ffdfd00b9815707a05ff77c5bb0ba4d9a428fbc37b8c440b67 + frame_seconds: 3600 + heartbeat_frames: 1 + registered_wait_frames: 2 + effective_deadline_frames: 3 qonto-assistant: transport: structured-stdout instance_boundary: process @@ -76,9 +82,9 @@ extensions: reset: permitted-only-when-stream_instance_id-changes heartbeat: event_class: audit.heartbeat - interval: PT24H + interval: PT3600S interval_config: QONTO_AUDIT_HEARTBEAT_INTERVAL_SECONDS - default_interval_seconds: 86400 + default_interval_seconds: 3600 lifecycle: startup: required periodic_while_process_active: required diff --git a/src/qonto_assistant/config.py b/src/qonto_assistant/config.py index 3e70b8f..1ffcda7 100644 --- a/src/qonto_assistant/config.py +++ b/src/qonto_assistant/config.py @@ -4,6 +4,8 @@ import os from dataclasses import dataclass from pathlib import Path +from .coordination import FRAME_SECONDS + def _repo_root() -> Path: return Path(__file__).resolve().parents[2] @@ -92,7 +94,7 @@ class Settings: os.getenv("QONTO_DENY_ESCALATION_LOCKOUT_SECONDS", "300") ), audit_heartbeat_interval_seconds=max( - 1, int(os.getenv("QONTO_AUDIT_HEARTBEAT_INTERVAL_SECONDS", "86400")) + 1, int(os.getenv("QONTO_AUDIT_HEARTBEAT_INTERVAL_SECONDS", str(FRAME_SECONDS))) ), key_cape_jwks_url=os.getenv("QONTO_KEY_CAPE_JWKS_URL") or None, key_cape_issuer=os.getenv("QONTO_KEY_CAPE_ISSUER", "https://key-cape.netkingdom"), diff --git a/src/qonto_assistant/coordination.py b/src/qonto_assistant/coordination.py new file mode 100644 index 0000000..875e896 --- /dev/null +++ b/src/qonto_assistant/coordination.py @@ -0,0 +1,6 @@ +"""Generated cadence timings; regenerate with NetKingdom coordination-frame tool.""" +# Source: coordination-engine/config/coordination-frame.json +FRAME_CONFIG_SHA256 = "d15e092387f128ffdfd00b9815707a05ff77c5bb0ba4d9a428fbc37b8c440b67" +FRAME_SECONDS = 3600 +RECEIVER_WAIT_SECONDS = 7200 +EFFECTIVE_DEADLINE_SECONDS = 10800 diff --git a/tests/test_cadence_declaration.py b/tests/test_cadence_declaration.py index 4d33e12..dc79a43 100644 --- a/tests/test_cadence_declaration.py +++ b/tests/test_cadence_declaration.py @@ -8,6 +8,7 @@ import pytest import yaml from qonto_assistant.audit import AUDIT_STREAM_ID, AuditLogger +from qonto_assistant.coordination import FRAME_SECONDS ROOT = Path(__file__).resolve().parents[1] @@ -17,7 +18,7 @@ def test_declaration_preserves_emitter_boundary_and_counters(): source = declaration["extensions"]["qonto-assistant"] assert declaration["stream_id"] == AUDIT_STREAM_ID assert source["instance_boundary"] == "process" - assert source["heartbeat"]["default_interval_seconds"] == 86400 + assert source["heartbeat"]["default_interval_seconds"] == FRAME_SECONDS assert source["reconciliation"]["persistence"] == "process-local" events = [] audit = AuditLogger(sink=events.append) @@ -58,3 +59,13 @@ def test_declaration_passes_supplied_owner_inventory_against_canonical_schema(): assert report["profile_assessed"] is True assert report["conformant"] is True assert report["assessment_scope"] == "supplied-inventory" + + + +def test_default_interval_uses_generated_frame_and_explicit_override_survives(monkeypatch): + from qonto_assistant.config import Settings + + monkeypatch.delenv("QONTO_AUDIT_HEARTBEAT_INTERVAL_SECONDS", raising=False) + assert Settings.from_env().audit_heartbeat_interval_seconds == FRAME_SECONDS + monkeypatch.setenv("QONTO_AUDIT_HEARTBEAT_INTERVAL_SECONDS", "600") + assert Settings.from_env().audit_heartbeat_interval_seconds == 600