Evaluate declared emission cadence and close AUDIT-WP-0009 (T05)
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 4s

The contract T05 waited on is published: info-tech-canon
emission-cadence wire schema 0.1, contract digest b08b4d95fc4b0bd3.
A source-owned declaration now travels as emission_cadence on the
sender registration; expected-rate entries raise below_declared_cadence
on /v1/stream-findings from the same counts /v1/reconciliation returns.
heartbeat-or-reconciliation entries are validated and left to T04/T06.

Records the observer evaluation of net-kingdom's local-identity
declaration: structurally clean, not operationally evaluated, one
heartbeat event_class mapping incompatibility.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 151986@bnt-lap001
Assistant-Session: ccd02b6b-80ae-48e5-8cad-9c8f74d21a67
This commit is contained in:
tegwick 2026-09-22 22:44:45 +02:00
parent 3b7bedc8e0
commit 01468ffc41
8 changed files with 645 additions and 22 deletions

View file

@ -22,6 +22,7 @@ from pathlib import Path
from typing import Any, Iterable
from audit_core.redaction import POLICIES, POLICY_REDACT
from audit_core.emission_cadence import parse_declaration
WILDCARD = "*"
@ -71,9 +72,9 @@ class SenderIdentity:
# §9.6 heartbeat declaration (AUDIT-WP-0009-T04): event class -> the
# longest gap, in seconds, that is not yet a finding.
#
# This is deliberately NOT the §17 emission-cadence schema that T05 waits
# on. Cadence describes a stream's expected *rate* and belongs to Taxonomy;
# this is a registration property saying how often a source promises to say
# This is deliberately NOT the emission-cadence declaration (T05, below).
# Cadence describes a stream's expected *rate* and its shape is
# info-tech-canon's; this is a registration property saying how often a source promises to say
# "nothing to report" for a class that may legitimately be silent. The two
# are complementary and audit-core is not inventing a competing rate shape.
#
@ -82,6 +83,10 @@ class SenderIdentity:
# nothing about the quiet, security-relevant one — which is the only case
# heartbeats exist for.
heartbeat_classes: tuple[tuple[str, int], ...] = ()
# The source-owned emission cadence declaration (AUDIT-WP-0009-T05),
# parsed from info-tech-canon wire schema 0.1 — the §17 shape the heartbeat
# field above deliberately did not invent. ``None`` means undeclared.
emission_cadence: Any = None
def __post_init__(self) -> None:
if not self.name:
@ -144,6 +149,9 @@ class SenderIdentity:
"completeness_claimed": False,
"completeness_trade": self.completeness_trade,
"heartbeat_classes": dict(self.heartbeat_classes),
"emission_cadence": (
self.emission_cadence.summary() if self.emission_cadence else None
),
"detection_surface": None,
}
@ -315,6 +323,7 @@ def _apply_scope_overlay(
else identity.completeness_trade
),
heartbeat_classes=_overlay_heartbeats(identity, extra),
emission_cadence=_overlay_cadence(identity, extra),
)
)
return merged
@ -370,6 +379,22 @@ def _overlay_heartbeats(
return tuple(sorted(proposed.items()))
def _overlay_cadence(identity: SenderIdentity, extra: dict[str, Any]) -> Any:
"""The overlay may not touch an emission cadence declaration.
A declaration is the source's, pinned to a contract digest; a partial
overlay has no sound merge with it, and replacing it wholesale could widen
a window or drop a class without anyone deciding to. Changing it is a
change to the Secret-backed registration.
"""
if "emission_cadence" in extra:
raise ValueError(
f"sender {identity.name!r}: the scope overlay may not set or change "
"emission_cadence"
)
return identity.emission_cadence
def _parse_identities(raw: str) -> list[SenderIdentity]:
try:
entries = json.loads(raw)
@ -396,6 +421,11 @@ def _identity_from(entry: Any) -> SenderIdentity:
evidence_kind=str(entry.get("evidence_kind", EVIDENCE_ATTRIBUTIVE)),
completeness_trade=_clean_trade(entry.get("completeness_trade")),
heartbeat_classes=_heartbeat_classes(entry.get("heartbeat_classes")),
emission_cadence=(
parse_declaration(entry["emission_cadence"])
if entry.get("emission_cadence") is not None
else None
),
)