Require explicit cadence profile assessment and correct contract pins

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a0e77d-47a4-7771-8e34-7339c7fac0e4
This commit is contained in:
tegwick 2026-09-28 12:40:07 +02:00
parent 9383b94019
commit 36303d25a3
7 changed files with 281 additions and 20 deletions

View file

@ -7,8 +7,8 @@ status: proposed
version: "0.1"
owner: net-kingdom
created: "2026-09-04"
updated: "2026-09-05"
last_reviewed: "2026-09-05"
updated: "2026-09-28"
last_reviewed: "2026-09-28"
review_interval: 3m
scope: evidence-completeness
validator:
@ -34,14 +34,21 @@ This profile imports that contract and defines only NetKingdom security
obligations over conforming declarations.
The import is InfoTechCanon `standard/emission-cadence`, document version
`0.1.0`, schema version `0.1` (published in canon 0.7.0; upstream status: draft).
`0.2.0`, schema version `0.1` (canon 0.7.0; upstream status: candidate).
- Contract: `info-tech-canon/infospace/standards/emission-cadence/InfoTechCanonEmissionCadenceStandard.md`
- Schema: `info-tech-canon/infospace/schemas/emission-cadence.schema.yaml`
- Schema ID: `https://info-tech-canon.local/schemas/emission-cadence.schema.yaml`
- Reviewed schema revision: `b081d39da1353201f879ee6832d4e3e52b791c73`
- Reviewed contract/schema revision: `4d0851c3fca306538b53838421f4499baf352778`
- Owner-published candidate bundle digest: `b08b4d95fc4b0bd3`
- Schema SHA-256: `6d52692eb1e4d1325e0d6062d95acded98beddaae5bcfe24c6f0e91f1b6be6ae`
The candidate bundle digest identifies the exported contract, including its
standard text and example; it is not the schema SHA-256. The September 28
review confirmed that the wire schema is byte-identical to the earlier import.
InfoTechCanon corrected its adoption brief: `972c0b6701d1693f` identified the
0.1.0 draft bundle, not candidate 0.2.0.
The schema ID is an identifier; supply the file from the owner checkout, not a
network download from that hostname. This profile remains proposed pending
owner-instance migration and validation. The King's Guard draft is provenance,
@ -118,9 +125,26 @@ contract and refuses to profile a document that fails the imported schema.
from the source's authoritative event-class inventory; they are not guesses by
the checker. Rare load-bearing assertions imply load-bearing.
MUST failures or generic contract failures produce a non-zero exit. Missing
attributive declarations produce a SHOULD finding and succeed by default;
`--fail-on-should` is available for a stricter caller policy.
The report separates `contract_valid` (JSON Schema validity) from
`profile_assessed` and nullable `conformant`. A profile assessment is performed
only after schema validation and with a nonempty supplied inventory. The report
records those assertions under `inventory` and marks `assessment_scope` as
`supplied-inventory`; its result covers only that inventory, not independently
verified completeness or operational emission.
Without inventory, default mode returns `profile_assessed: false`,
`conformant: null`, `assessment_scope: inventory-missing` and exit 2. Explicit
`--schema-only` returns `assessment_scope: schema-only` and exit 0 for a valid
schema instance, while leaving profile conformance unassessed. It cannot be
combined with inventory flags or `--fail-on-should`. Invalid schema/declaration
results return exit 1 with profile conformance unassessed. CLI/input errors
return exit 2.
MUST failures from an assessed profile produce exit 1. Missing attributive
declarations produce a SHOULD finding and succeed by default;
`--fail-on-should` is available for a stricter caller policy. Callers must check
`profile_assessed == true` and `conformant == true` before claiming profile
success; an empty findings list or schema-only success is insufficient.
## 5. Adoption gate

View file

@ -1,7 +1,11 @@
# local-identity emission cadence — fit findings (INFO-WP-0029-T02)
Declaration: `local-identity/emission-cadence.yaml`, pinned to InfoTechCanon
emission contract digest `972c0b6701d1693f` (document 0.2.0, wire schema 0.1).
Declaration: `local-identity/emission-cadence.yaml`, currently pinned to
InfoTechCanon candidate bundle `b08b4d95fc4b0bd3` (document 0.2.0, wire schema
0.1). The September 21 evaluation used `972c0b6701d1693f`, then incorrectly
labelled 0.2.0 by the owner brief; that digest actually identifies the 0.1.0
draft. The owner corrected the brief on September 22. The schema bytes are
unchanged, and the historical findings below remain valid.
Emitter: `local-identity/src/local_identity/audit.py`.
## Validation (2026-09-21)
@ -39,3 +43,13 @@ To close the gap, `serve` would have to emit a periodic `nothing-to-report`
heartbeat and a start/stop pair so that observers can bound the silence. That is
a code change to a bootstrap-only tool and is not planned. This file records the
incompatibility as the adoption result.
## Pin correction and revalidation — 2026-09-28
The current declaration now uses the owner-published candidate bundle digest
`b08b4d95fc4b0bd3`. Schema SHA-256 remains
`6d52692eb1e4d1325e0d6062d95acded98beddaae5bcfe24c6f0e91f1b6be6ae`.
Revalidated against the owner schema with both documented classes explicitly
supplied as rare load-bearing: generic-valid, with exactly two
`rare-heartbeat-missing` findings. All 15 focused checker tests pass. No
heartbeat, observer feed or runtime emission change is implied by this pin fix.

View file

@ -1,5 +1,5 @@
# Source-owned Emission Cadence declaration for local-identity (INFO-WP-0029-T02).
# Pinned to InfoTechCanon emission contract digest 972c0b6701d1693f
# Pinned to InfoTechCanon emission contract digest b08b4d95fc4b0bd3
# (document 0.2.0, wire schema 0.1).
#
# This states intended cadence only. It is not evidence that the events are
@ -12,7 +12,7 @@ source: net-kingdom
stream_id: net-kingdom.local-identity.audit
extensions:
netkingdom:
contract_digest: 972c0b6701d1693f
contract_digest: b08b4d95fc4b0bd3
contract_document_version: 0.2.0
sources:
- source_id: net-kingdom.local-identity.audit.token-issued

View file

@ -9,6 +9,56 @@ classes passed with `--load-bearing`, `--rare-load-bearing`, and
`--attributive` come from the source's authoritative inventory; the checker
does not infer them from names, payloads, or observed traffic.
## Assessment modes and results
A security-profile assessment requires at least one source-owned class
assertion. For example, to check local-identity's documented rare classes:
```bash
python3 tools/emission-cadence-profile/emission_cadence_profile.py \
--contract-schema ../info-tech-canon/infospace/schemas/emission-cadence.schema.yaml \
--rare-load-bearing serve/token.token_issued \
--rare-load-bearing revoke-token \
local-identity/emission-cadence.yaml
```
This declaration currently fails both heartbeat obligations. To intentionally
check only its structure against the imported JSON Schema:
```bash
python3 tools/emission-cadence-profile/emission_cadence_profile.py \
--contract-schema ../info-tech-canon/infospace/schemas/emission-cadence.schema.yaml \
--schema-only local-identity/emission-cadence.yaml
```
| Invocation/result | `contract_valid` | `profile_assessed` | `conformant` | Exit |
| --- | --- | --- | --- | --- |
| Valid schema, no inventory, default mode | true | false | null | 2 |
| Valid schema, explicit `--schema-only` | true | false | null | 0 |
| Invalid schema/declaration, either mode | false | false | null | 1 |
| Supplied inventory, profile passes | true | true | true | 0 |
| Supplied inventory, profile fails | true | true | false | 1 |
`assessment_scope` is `schema-only`, `inventory-missing`, or
`supplied-inventory`. The report includes the exact sorted `inventory`
assertions. Profile results cover only those assertions: the checker cannot
prove that the caller supplied a complete inventory or that events are emitted
and observed. `contract_valid` means JSON Schema validity, not every semantic
rule in the generic standard; duplicate source IDs are checked during profile
assessment.
`--schema-only` cannot be combined with class assertions or `--fail-on-should`.
Blank class arguments are rejected. Invalid CLI options and unreadable input
also exit 2. SHOULD findings remain advisory unless `--fail-on-should` is used.
**Compatibility:** callers that previously omitted class arguments must now
choose schema-only validation or supply an inventory. `conformant` can be null;
automation claiming profile success must require `profile_assessed == true`
and `conformant == true`, rather than merely checking for no findings or an
exit code of zero. No evidence classification is inferred from the document.
## Verification
Run its tests with:
```bash

View file

@ -348,10 +348,17 @@ def build_report(
rare_load_bearing: set[str],
attributive: set[str],
fail_on_should: bool = False,
schema_only: bool = False,
) -> dict[str, Any]:
inventory_supplied = bool(load_bearing or rare_load_bearing or attributive)
if schema_only and (inventory_supplied or fail_on_should):
raise ValueError("schema-only validation cannot include profile options")
if any(not name.strip() for name in load_bearing | rare_load_bearing | attributive):
raise ValueError("inventory event classes must not be blank")
findings = _schema_findings(contract_schema, declaration)
contract_valid = not findings
if contract_valid:
profile_assessed = contract_valid and inventory_supplied and not schema_only
if profile_assessed:
findings.extend(
evaluate_profile(
declaration,
@ -362,17 +369,37 @@ def build_report(
)
must_count = sum(item.level == "MUST" for item in findings)
should_count = sum(item.level == "SHOULD" for item in findings)
assessment_scope = "supplied-inventory" if inventory_supplied else "inventory-missing"
if schema_only:
assessment_scope = "schema-only"
return {
"profile": PROFILE_ID,
"contract_schema": contract_schema_path,
"declaration": declaration_path,
"contract_valid": contract_valid,
"conformant": must_count == 0 and (not fail_on_should or should_count == 0),
"profile_assessed": profile_assessed,
"assessment_scope": assessment_scope,
"inventory": {
"load_bearing": sorted(load_bearing),
"rare_load_bearing": sorted(rare_load_bearing),
"attributive": sorted(attributive),
},
"conformant": (
must_count == 0 and (not fail_on_should or should_count == 0)
if profile_assessed
else None
),
"summary": {"must": must_count, "should": should_count},
"findings": [asdict(item) for item in findings],
}
def _event_class(value: str) -> str:
if not value.strip():
raise argparse.ArgumentTypeError("event class must not be blank")
return value
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Validate an imported emission-cadence declaration against the NetKingdom profile."
@ -380,16 +407,40 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
parser.add_argument("declaration", type=Path)
parser.add_argument("--contract-schema", required=True, type=Path)
parser.add_argument(
"--load-bearing", action="append", default=[], metavar="EVENT_CLASS"
"--schema-only",
action="store_true",
help="Validate only the supplied JSON Schema; do not assess security-profile conformance.",
)
parser.add_argument(
"--rare-load-bearing", action="append", default=[], metavar="EVENT_CLASS"
"--load-bearing",
action="append",
default=[],
type=_event_class,
metavar="EVENT_CLASS",
)
parser.add_argument(
"--attributive", action="append", default=[], metavar="EVENT_CLASS"
"--rare-load-bearing",
action="append",
default=[],
type=_event_class,
metavar="EVENT_CLASS",
)
parser.add_argument(
"--attributive",
action="append",
default=[],
type=_event_class,
metavar="EVENT_CLASS",
)
parser.add_argument("--fail-on-should", action="store_true")
return parser.parse_args(argv)
args = parser.parse_args(argv)
if args.schema_only and (
args.load_bearing or args.rare_load_bearing or args.attributive or args.fail_on_should
):
parser.error(
"--schema-only cannot be combined with profile inventory or --fail-on-should"
)
return args
def main(argv: list[str] | None = None) -> int:
@ -409,8 +460,15 @@ def main(argv: list[str] | None = None) -> int:
rare_load_bearing=set(args.rare_load_bearing),
attributive=set(args.attributive),
fail_on_should=args.fail_on_should,
schema_only=args.schema_only,
)
print(json.dumps(report, indent=2, sort_keys=True))
if not report["contract_valid"]:
return 1
if args.schema_only:
return 0
if not report["profile_assessed"]:
return 2
return 0 if report["conformant"] else 1

View file

@ -2,6 +2,7 @@ from __future__ import annotations
import copy
import importlib.util
import json
import pathlib
import sys
@ -92,14 +93,86 @@ def test_valid_rare_load_bearing_requires_both_positive_controls() -> None:
result = report(declaration(rare_entry()), rare={"audit.deny"})
assert result["contract_valid"] is True
assert result["profile_assessed"] is True
assert result["inventory"]["rare_load_bearing"] == ["audit.deny"]
assert result["conformant"] is True
assert result["findings"] == []
def test_omitted_inventory_does_not_claim_profile_conformance() -> None:
item = rare_entry()
del item["heartbeat"]
result = report(declaration(item))
assert result["contract_valid"] is True
assert result["profile_assessed"] is False
assert result["conformant"] is None
assert result["assessment_scope"] == "inventory-missing"
assert result["findings"] == [] # Rarity is never inferred from the entry.
@pytest.mark.parametrize(
"options,valid,exit_code,scope",
[
([], True, 2, "inventory-missing"),
(["--schema-only"], True, 0, "schema-only"),
(["--schema-only"], False, 1, "schema-only"),
([], False, 1, "inventory-missing"),
],
)
def test_cli_unassessed_results(tmp_path, capsys, options, valid, exit_code, scope):
schema = tmp_path / "schema.yaml"
document = tmp_path / "declaration.yaml"
schema.write_text(yaml.safe_dump(CONTRACT_SCHEMA))
item = rare_entry()
del item["heartbeat"]
document.write_text(yaml.safe_dump(declaration(item) if valid else {}))
assert (
profile.main([str(document), "--contract-schema", str(schema), *options])
== exit_code
)
result = json.loads(capsys.readouterr().out)
assert result["contract_valid"] is valid
assert result["profile_assessed"] is False
assert result["conformant"] is None
assert result["assessment_scope"] == scope
@pytest.mark.parametrize(
"options",
[
["--load-bearing", "audit.deny"],
["--rare-load-bearing", "audit.deny"],
["--attributive", "audit.allow"],
["--fail-on-should"],
],
)
def test_schema_only_refuses_profile_options(options):
with pytest.raises(SystemExit) as exc:
profile.parse_args(
["source.yaml", "--contract-schema", "schema.yaml", "--schema-only", *options]
)
assert exc.value.code == 2
@pytest.mark.parametrize(
"option", ["--load-bearing", "--rare-load-bearing", "--attributive"]
)
def test_blank_class_is_not_an_inventory(option):
with pytest.raises(SystemExit) as exc:
profile.parse_args(
["source.yaml", "--contract-schema", "schema.yaml", option, " "]
)
assert exc.value.code == 2
def test_contract_validation_runs_before_profile() -> None:
result = report({"source": "example"}, rare={"audit.deny"})
assert result["contract_valid"] is False
assert result["profile_assessed"] is False
assert result["conformant"] is None
assert codes(result) == {"contract-validation-failed"}
assert "load-bearing-cadence-missing" not in codes(result)
@ -254,8 +327,6 @@ def test_duplicate_source_ids_fail_even_with_distinct_event_classes(upstream_sch
def test_should_policy_and_cli(tmp_path, capsys):
schema = tmp_path / "schema.json"
document = tmp_path / "declaration.yaml"
import json
schema.write_text(json.dumps(CONTRACT_SCHEMA))
document.write_text(json.dumps(declaration()))
args = [

View file

@ -10,7 +10,7 @@ owner: codex
topic_slug: netkingdom
planning_priority: P1
created: "2026-09-04"
updated: "2026-09-07"
updated: "2026-09-28"
related:
- GH-DEC-2026-004
- canon/standards/security-layer-model_v0.7.md
@ -122,6 +122,22 @@ checkout. Full reconciliation remains pending because API queries/writes timed
out or returned connection-refused errors. Generated index/intake metadata was
reviewed; the source files remain authoritative.
### Import metadata reconciled — 2026-09-28
Updated the importing profile to candidate document 0.2.0 / wire schema 0.1,
reviewed contract revision `4d0851c3fca306538b53838421f4499baf352778`, and
owner-published candidate bundle digest `b08b4d95fc4b0bd3`. Verified the schema
SHA-256 remains `6d52692eb1e4d1325e0d6062d95acded98beddaae5bcfe24c6f0e91f1b6be6ae`.
Corrected local-identity's current declaration pin and preserved the old pin
with its correction in the historical findings. No emission behavior changed.
All 15 focused cadence tests pass. Revalidation with the two source-owned
rare-class assertions is generic-valid and still returns exactly two
`rare-heartbeat-missing` findings. T04 remains `wait` for external source
migration, observer integration and the explicit local-identity incompatibility
resolution; the profile remains proposed. Metadata repair is complete and does
not count as source/observer adoption.
## Review the layer model's use of the profile
```task
@ -153,3 +169,31 @@ profile, carries the volume/rare split explicitly, and states that
classification is the source's to publish and never the checker's to infer.
Change log item 6 and §14 record the review; the standard remains `proposed` and
publication waits on the close of the circulation round.
## Infrastructure review — 2026-09-28
T04 remains `wait`, but upstream publication is no longer the blocker.
The current generic document is candidate 0.2.0 with wire schema 0.1;
InfoTechCanon corrected the candidate bundle digest to `b08b4d95fc4b0bd3`
(the wire schema was unchanged). Review and update the profile's old
draft/document/revision description and local-identity's stale bundle pin
against the exact owner artifact before handoff; do not confuse a bundle digest
with the schema SHA-256.
Approval Engine and Qonto still carry draft-shaped owner envelopes. In addition,
NetKingdom now has its own source declaration at
`local-identity/emission-cadence.yaml`: generic validation passed, but both
rare load-bearing entries lack the required heartbeat. The source has no
audit-core sender/feed; its findings also record dropped audit I/O errors and
no completeness claim. Generic validity is not security-profile conformance
or an operating observer result.
Extend T04 acceptance to cover the local source explicitly: either implement
and evidence activity-scoped heartbeat/reconciliation with its owners, or
retain the documented incompatibility without claiming profile-wide adoption.
Resolve the declared heartbeat event class versus audit-core's registered
`heartbeat_classes` mapping, migrate the two external owner instances, and
obtain a real source/observer result before the King's Guard handoff. Keep
the profile proposed; no weakened rare-class conjunction is approved.
Evidence and cross-plan priorities: [estate review](../history/2026-09-28-open-workplan-infrastructure-review.md).