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" version: "0.1"
owner: net-kingdom owner: net-kingdom
created: "2026-09-04" created: "2026-09-04"
updated: "2026-09-05" updated: "2026-09-28"
last_reviewed: "2026-09-05" last_reviewed: "2026-09-28"
review_interval: 3m review_interval: 3m
scope: evidence-completeness scope: evidence-completeness
validator: validator:
@ -34,14 +34,21 @@ This profile imports that contract and defines only NetKingdom security
obligations over conforming declarations. obligations over conforming declarations.
The import is InfoTechCanon `standard/emission-cadence`, document version 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` - Contract: `info-tech-canon/infospace/standards/emission-cadence/InfoTechCanonEmissionCadenceStandard.md`
- Schema: `info-tech-canon/infospace/schemas/emission-cadence.schema.yaml` - Schema: `info-tech-canon/infospace/schemas/emission-cadence.schema.yaml`
- Schema ID: `https://info-tech-canon.local/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` - 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 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 network download from that hostname. This profile remains proposed pending
owner-instance migration and validation. The King's Guard draft is provenance, 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 from the source's authoritative event-class inventory; they are not guesses by
the checker. Rare load-bearing assertions imply load-bearing. the checker. Rare load-bearing assertions imply load-bearing.
MUST failures or generic contract failures produce a non-zero exit. Missing The report separates `contract_valid` (JSON Schema validity) from
attributive declarations produce a SHOULD finding and succeed by default; `profile_assessed` and nullable `conformant`. A profile assessment is performed
`--fail-on-should` is available for a stricter caller policy. 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 ## 5. Adoption gate

View file

@ -1,7 +1,11 @@
# local-identity emission cadence — fit findings (INFO-WP-0029-T02) # local-identity emission cadence — fit findings (INFO-WP-0029-T02)
Declaration: `local-identity/emission-cadence.yaml`, pinned to InfoTechCanon Declaration: `local-identity/emission-cadence.yaml`, currently pinned to
emission contract digest `972c0b6701d1693f` (document 0.2.0, wire schema 0.1). 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`. Emitter: `local-identity/src/local_identity/audit.py`.
## Validation (2026-09-21) ## 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 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 a code change to a bootstrap-only tool and is not planned. This file records the
incompatibility as the adoption result. 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). # 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). # (document 0.2.0, wire schema 0.1).
# #
# This states intended cadence only. It is not evidence that the events are # 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 stream_id: net-kingdom.local-identity.audit
extensions: extensions:
netkingdom: netkingdom:
contract_digest: 972c0b6701d1693f contract_digest: b08b4d95fc4b0bd3
contract_document_version: 0.2.0 contract_document_version: 0.2.0
sources: sources:
- source_id: net-kingdom.local-identity.audit.token-issued - 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 `--attributive` come from the source's authoritative inventory; the checker
does not infer them from names, payloads, or observed traffic. 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: Run its tests with:
```bash ```bash

View file

@ -348,10 +348,17 @@ def build_report(
rare_load_bearing: set[str], rare_load_bearing: set[str],
attributive: set[str], attributive: set[str],
fail_on_should: bool = False, fail_on_should: bool = False,
schema_only: bool = False,
) -> dict[str, Any]: ) -> 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) findings = _schema_findings(contract_schema, declaration)
contract_valid = not findings contract_valid = not findings
if contract_valid: profile_assessed = contract_valid and inventory_supplied and not schema_only
if profile_assessed:
findings.extend( findings.extend(
evaluate_profile( evaluate_profile(
declaration, declaration,
@ -362,17 +369,37 @@ def build_report(
) )
must_count = sum(item.level == "MUST" for item in findings) must_count = sum(item.level == "MUST" for item in findings)
should_count = sum(item.level == "SHOULD" 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 { return {
"profile": PROFILE_ID, "profile": PROFILE_ID,
"contract_schema": contract_schema_path, "contract_schema": contract_schema_path,
"declaration": declaration_path, "declaration": declaration_path,
"contract_valid": contract_valid, "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}, "summary": {"must": must_count, "should": should_count},
"findings": [asdict(item) for item in findings], "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: def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="Validate an imported emission-cadence declaration against the NetKingdom profile." 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("declaration", type=Path)
parser.add_argument("--contract-schema", required=True, type=Path) parser.add_argument("--contract-schema", required=True, type=Path)
parser.add_argument( 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( 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( 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") 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: 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), rare_load_bearing=set(args.rare_load_bearing),
attributive=set(args.attributive), attributive=set(args.attributive),
fail_on_should=args.fail_on_should, fail_on_should=args.fail_on_should,
schema_only=args.schema_only,
) )
print(json.dumps(report, indent=2, sort_keys=True)) 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 return 0 if report["conformant"] else 1

View file

@ -2,6 +2,7 @@ from __future__ import annotations
import copy import copy
import importlib.util import importlib.util
import json
import pathlib import pathlib
import sys 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"}) result = report(declaration(rare_entry()), rare={"audit.deny"})
assert result["contract_valid"] is True 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["conformant"] is True
assert result["findings"] == [] 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: def test_contract_validation_runs_before_profile() -> None:
result = report({"source": "example"}, rare={"audit.deny"}) result = report({"source": "example"}, rare={"audit.deny"})
assert result["contract_valid"] is False assert result["contract_valid"] is False
assert result["profile_assessed"] is False
assert result["conformant"] is None
assert codes(result) == {"contract-validation-failed"} assert codes(result) == {"contract-validation-failed"}
assert "load-bearing-cadence-missing" not in codes(result) 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): def test_should_policy_and_cli(tmp_path, capsys):
schema = tmp_path / "schema.json" schema = tmp_path / "schema.json"
document = tmp_path / "declaration.yaml" document = tmp_path / "declaration.yaml"
import json
schema.write_text(json.dumps(CONTRACT_SCHEMA)) schema.write_text(json.dumps(CONTRACT_SCHEMA))
document.write_text(json.dumps(declaration())) document.write_text(json.dumps(declaration()))
args = [ args = [

View file

@ -10,7 +10,7 @@ owner: codex
topic_slug: netkingdom topic_slug: netkingdom
planning_priority: P1 planning_priority: P1
created: "2026-09-04" created: "2026-09-04"
updated: "2026-09-07" updated: "2026-09-28"
related: related:
- GH-DEC-2026-004 - GH-DEC-2026-004
- canon/standards/security-layer-model_v0.7.md - 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 out or returned connection-refused errors. Generated index/intake metadata was
reviewed; the source files remain authoritative. 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 ## Review the layer model's use of the profile
```task ```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. 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 Change log item 6 and §14 record the review; the standard remains `proposed` and
publication waits on the close of the circulation round. 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).