fix(cases): validate optional exact machine descriptor pins
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a140c4-354a-7823-8f0d-8f717397f786
This commit is contained in:
tegwick 2026-10-16 21:23:04 +02:00
parent 8ae5150d4d
commit 015694ee41
6 changed files with 187 additions and 2 deletions

View file

@ -132,6 +132,24 @@
"items": { "items": {
"type": "object" "type": "object"
} }
},
"execution_descriptors": {
"type": "object",
"maxProperties": 128,
"propertyNames": {
"type": "string",
"pattern": "^[a-z0-9][a-z0-9-]*$",
"not": {
"pattern": "[^a-z0-9-]"
}
},
"additionalProperties": {
"type": "string",
"pattern": "^sha256:[0-9a-f]{64}$",
"minLength": 71,
"maxLength": 71
},
"description": "Optional exact owner machine-descriptor byte digests keyed by declared follow-through step. A pin alone is not owner acknowledgment, execution authority or native evidence."
} }
} }
} }

View file

@ -95,3 +95,18 @@ Integration tests invoke actual sibling Mason `prepare` and Warden `Journal.save
with metadata fixtures only. They need those owner source checkouts, and existing with metadata fixtures only. They need those owner source checkouts, and existing
type suites need the public KeyCape request file and provider declarations beside type suites need the public KeyCape request file and provider declarations beside
the checkout. `NET_KINGDOM_ROOT` selects the source root for the 1.2 package tests. the checkout. `NET_KINGDOM_ROOT` selects the source root for the 1.2 package tests.
## Machine descriptor source pins
The optional `spec.execution_descriptors` map uses the existing Warden kernel's
step-to-exact-descriptor-byte SHA-256 contract. The JSON schema accepts at most
128 lowercase `sha256:<64 hex>` pins. The semantic validator additionally requires
every key to name a declared follow-through step. Malformed maps are refused by
the direct evidence API too. Absent and partial maps remain legal for source
preparation; missing steps remain explicit in the evidence report.
A valid pin is source metadata. It does not authenticate its owner, acknowledge
a machine seam, admit an adapter or change the implemented-type requirement.
The existing browser types contain no descriptors; their bytes and lifecycle
states are unchanged. Owner descriptor/provenance acceptance and actual native
receipts remain prerequisites to execution and acceptance.

View file

@ -135,6 +135,11 @@ def check_steps(rows, case_source, type_source, rendered):
"journal_schema", "evidence_digest", "session_revoked", "session_revocation_digest", "journal_schema", "evidence_digest", "session_revoked", "session_revocation_digest",
"grant_revocation_digest", "attempt_id", "operation"} "grant_revocation_digest", "attempt_id", "operation"}
type_spec = yaml.safe_load(type_source)["spec"] type_spec = yaml.safe_load(type_source)["spec"]
# The direct API must refuse malformed maps too, without AttributeError or
# treating an undeclared step as an admitted descriptor.
from security_case_validator import validate_execution_descriptors
if validate_execution_descriptors(type_spec):
raise EvidenceRefused("invalid type machine descriptor pins")
descriptors = type_spec.get("execution_descriptors", {}) descriptors = type_spec.get("execution_descriptors", {})
seen, result = set(), [] seen, result = set(), []
for row in rows: for row in rows:

View file

@ -151,6 +151,7 @@ def validate_type(data: dict[str, Any], type_dir: Path | None = None) -> list[Is
params = _validate_type_parameters(spec["parameters"], issues) params = _validate_type_parameters(spec["parameters"], issues)
step_ids = _validate_follow_through(spec["follow_through"], issues) step_ids = _validate_follow_through(spec["follow_through"], issues)
issues.extend(validate_execution_descriptors(spec))
_validate_composition(spec["composition"], params, issues) _validate_composition(spec["composition"], params, issues)
_validate_materialization(spec["materialization"], params, issues) _validate_materialization(spec["materialization"], params, issues)
_validate_verification(spec["verification"], params, issues) _validate_verification(spec["verification"], params, issues)
@ -190,6 +191,30 @@ def validate_type(data: dict[str, Any], type_dir: Path | None = None) -> list[Is
return issues return issues
def validate_execution_descriptors(spec: dict[str, Any]) -> list[Issue]:
"""Validate optional source pins; never infer acknowledgment or authority.
Incomplete/absent maps remain legal for plan-only types. Admission and
provenance remain the executing owner's responsibility.
"""
if "execution_descriptors" not in spec:
return []
path = "type.spec.execution_descriptors"
pins = spec["execution_descriptors"]
if type(pins) is not dict or len(pins) > 128:
return [err(path, "must be a mapping of at most 128 step descriptor pins")]
steps = spec.get("follow_through")
declared = {step.get("id") for step in steps
if type(step) is dict and type(step.get("id")) is str} if type(steps) is list else set()
issues = []
for step, pin in pins.items():
if type(step) is not str or not ID.fullmatch(step) or step not in declared:
issues.append(err(path, "descriptor key must identify a declared follow-through step"))
if type(pin) is not str or re.fullmatch(r"sha256:[0-9a-f]{64}", pin) is None:
issues.append(err(path, "descriptor pin must be a lowercase SHA-256 byte digest"))
return issues
def _validate_type_parameters(params: Any, issues: list[Issue]) -> dict[str, dict]: def _validate_type_parameters(params: Any, issues: list[Issue]) -> dict[str, dict]:
out: dict[str, dict] = {} out: dict[str, dict] = {}
if not isinstance(params, list) or not params: if not isinstance(params, list) or not params:

View file

@ -0,0 +1,64 @@
"""Owner pin contract parity; synthetic pins never establish native acceptance."""
import copy
import json
from pathlib import Path
import sys
import jsonschema
import pytest
import yaml
TOOL = Path(__file__).resolve().parents[1]
REPO = TOOL.parents[1]
sys.path.insert(0, str(TOOL))
import security_case_validator as v
import security_case_evidence as e
TYPE = yaml.safe_load((REPO / 'security-case-types/_template/type.yaml').read_bytes())
SCHEMA = json.loads((REPO / 'canon/schemas/security-case-type_v0.1.schema.json').read_bytes())
PIN = 'sha256:' + 'a' * 64
STEP = TYPE['spec']['follow_through'][0]['id']
def with_pins(pins):
doc = copy.deepcopy(TYPE)
doc['spec']['execution_descriptors'] = pins
return doc
@pytest.mark.parametrize('pins', [{}, {STEP: PIN}])
def test_optional_partial_pin_contract_has_schema_semantic_parity(pins):
doc = with_pins(pins)
jsonschema.validate(doc, SCHEMA)
assert not v.has_errors(v.validate_type(doc))
assert doc['spec']['status'] == TYPE['spec']['status']
@pytest.mark.parametrize('pins', [None, [], 'digest', {STEP: True}, {STEP: None},
{STEP: 17}, {STEP: 'a' * 64}, {STEP: PIN.upper()}, {STEP: PIN + '\n'},
{STEP + '\n': PIN}, {7: PIN}, {'../escape': PIN},
{f'step-{i}': PIN for i in range(129)}])
def test_malformed_descriptor_contract_refused_by_both_validators(pins):
doc = with_pins(pins)
assert v.has_errors(v.validate_type(doc))
with pytest.raises(jsonschema.ValidationError):
jsonschema.validate(doc, SCHEMA)
def test_well_shaped_unknown_step_is_semantic_refusal():
doc = with_pins({'not-a-declared-step': PIN})
jsonschema.validate(doc, SCHEMA)
assert v.has_errors(v.validate_type(doc))
@pytest.mark.parametrize('pins', [None, [], {STEP: 'bad'}, {'undeclared-step': PIN}])
def test_direct_evidence_api_refuses_bad_map_without_crashing_or_needing_rows(pins):
with pytest.raises(e.EvidenceRefused, match='machine descriptor pins'):
e.check_steps((), b'case', yaml.safe_dump(with_pins(pins)).encode(), {'follow_through': []})
def test_partial_pin_cannot_bypass_ordinary_case_admission():
doc = with_pins({STEP: PIN})
case = yaml.safe_load((REPO / 'security-case-types/_template/examples/example-operator.yaml').read_bytes())
issues = v.validate_case(case, doc)
assert any('only implemented types' in issue.message for issue in issues)

View file

@ -9,7 +9,7 @@ flavor: implementation
owner: claude-code owner: claude-code
topic_slug: netkingdom topic_slug: netkingdom
created: "2026-10-08" created: "2026-10-08"
updated: "2026-10-10" updated: "2026-10-16"
depends_on: [] depends_on: []
related_repos: related_repos:
- ops-mason - ops-mason
@ -323,7 +323,7 @@ the rationale. The type is `acknowledged`; it becomes `implemented` with T06.
```task ```task
id: NK-WP-0048-T06 id: NK-WP-0048-T06
status: wait status: wait
depends_on: [NK-WP-0048-T05, NK-WP-0048-T08, NK-WP-0048-T09, MASON-WP-0010-T03, WARDEN-WP-0044-T03] depends_on: [NK-WP-0048-T05, NK-WP-0048-T08, NK-WP-0048-T09, NK-WP-0048-T12, MASON-WP-0010-T03, WARDEN-WP-0044-T03]
blocking_reason: "Type1.2 architecture has a confirmed source-only founder acknowledgment; canonical1.1 remains unchanged/default and the1.2 reference case stays proposed. Needs explicit version selection/reference-source integration without normal admission bypass, closed owner adapters plus global/native receipt verifier (WARDEN-WP-0044-T03), independently verified six-object structural materialization and CCR42 claims/created-only provenance, exact broker/case/source reconciliation, then separately authorized native mint/delivery and attended registry/consumer verification. Source acknowledgment, a bundle or local tests do not prove implemented/materialized/live status." blocking_reason: "Type1.2 architecture has a confirmed source-only founder acknowledgment; canonical1.1 remains unchanged/default and the1.2 reference case stays proposed. Needs explicit version selection/reference-source integration without normal admission bypass, closed owner adapters plus global/native receipt verifier (WARDEN-WP-0044-T03), independently verified six-object structural materialization and CCR42 claims/created-only provenance, exact broker/case/source reconciliation, then separately authorized native mint/delivery and attended registry/consumer verification. Source acknowledgment, a bundle or local tests do not prove implemented/materialized/live status."
priority: high priority: high
state_hub_task_id: "d9b26bcf-a7c6-59a2-92a0-f913d76a203c" state_hub_task_id: "d9b26bcf-a7c6-59a2-92a0-f913d76a203c"
@ -584,3 +584,61 @@ validator, then project only descriptor-declared safe target metadata; do not
remove the credential/value refusal or copy complete parameters into journals. remove the credential/value refusal or copy complete parameters into journals.
Actual owner descriptor acknowledgment and independent evidence admission remain Actual owner descriptor acknowledgment and independent evidence admission remain
prerequisites, with normal admission still refusing unimplemented types. prerequisites, with normal admission still refusing unimplemented types.
## T10 — Source compatibility checker for Mason and Warden evidence
```task
id: NK-WP-0048-T10
status: done
priority: high
```
Completed 2026-10-16 in published source `c31d21ba`. The read-only
`check-evidence` command checks the actual Mason structural request and Warden
journal source protocols against an exact version/render. Independent source
review passed 103 tests at that checkpoint. It always reports native completion
false and execution prohibited. Fixtures and local journal fingerprints do not
provide authenticated owner/native evidence. This records completed source scope
previously described under T06; it does not duplicate native acceptance credit.
## T11 — Validate the optional machine-descriptor pin contract
```task
id: NK-WP-0048-T11
status: done
priority: high
```
Completed 2026-10-16. Reproduced the JSON schema rejecting a valid
`execution_descriptors` map while the Python validator accepted malformed maps.
The schema now supports the existing Warden optional exact-byte pin map; semantic
validation binds keys to declared steps, bounds the map and checks digest shape.
The direct evidence API refuses malformed maps before accessing them. Twenty-one
new checks and all 127 affected source checks passed. No current type/case bytes,
status, owner descriptors or native acceptance changed. Source evidence and
publication receipt: Custodian
`docs/changes/netkingdom-target-2026-10-16/browser-acceptance-next/`.
## T12 — Prepare NetKingdom-owned attended step adapters
```task
id: NK-WP-0048-T12
status: todo
priority: high
```
Owner: NetKingdom. Next action: wrap the existing contained `keycape-client.py`
registration/config/env and identity-provisioner ensure/revoke entry points in
bounded owner adapters or receipt-only attended steps for Warden's closed kernel.
Reuse the existing caller/session boundary, CAS/readback and status-only outputs;
never acquire user-engine's runtime identity or expose registry/client secrets.
Completion requires exact adapter/source descriptors, positive and refusal source
checks, uncertainty/created-only rollback behavior and a separate owner review of
the proposed machine seam. Source preparation can begin without credentials.
Warden independently owns global provenance/admission integration; Platform owns
mint/broker and native structural receipts; Apps owns consumer delivery/group
acceptance. Their assent is not supplied by this task. Pinning descriptors into
an acknowledged type remains a separately reviewed type-source change. Actual
attended execution still requires its existing owner authorization and native
receipts. T06 retains all native/reference lifecycle acceptance scope.