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:
parent
9383b94019
commit
36303d25a3
7 changed files with 281 additions and 20 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = [
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue