From 51321ad504550233854297a892e420d81e3635b8 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Tue, 6 Oct 2026 20:35:58 +0100 Subject: [PATCH] =?UTF-8?q?fix(k9):=20reopen=20=C2=A75.5=20tails=20on=20co?= =?UTF-8?q?mponent=20records=20(D310,=20#1165)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #1154 removed the `..` tails from Component, Pedigree, Security, Metadata and Signature, together with the comments explaining them. Nickel records are closed by default, so the contract again rejected extension fields that SPEC §5.5 permits ("Unknown fields anywhere | PERMITTED"). The positive control fixtures/valid/extension-fields.k9.ncl failed K9-N001 at L2, and K9-SVC was red on main. This restores the tails and their rationale. It keeps #1154's later `.k9.ncl.in` template wording. HuntEvidence stays closed: §9.2 host evidence, where a misspelt field must be an error. Owner ruling D310 (standards#787): K9.Component is an open record. Closes #1165 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01D6JVLvmCeSFGcY6jSNesbL --- 1-formats/k9/spec/contract/k9_contract.ncl | 62 +++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/1-formats/k9/spec/contract/k9_contract.ncl b/1-formats/k9/spec/contract/k9_contract.ncl index da8c3176f..bb38dad32 100644 --- a/1-formats/k9/spec/contract/k9_contract.ncl +++ b/1-formats/k9/spec/contract/k9_contract.ncl @@ -282,6 +282,11 @@ in # The host supplies one Bool of EVIDENCE per precondition. Evidence is # runtime fact, not a static component field — which is why `check_level` # in leash.ncl can never authorise Hunt on its own. + # Host-side, and deliberately CLOSED unlike the component-facing records + # above: §9.2 says the host supplies one Boolean of evidence per + # precondition, so a misspelt evidence field must be an error rather than a + # silently ignored precondition. §5.5's openness is about a *component's* + # fields. HuntEvidence = { signature | Bool, policy | Bool, @@ -362,6 +367,10 @@ in payload_hash | String | optional, signed_at | String | optional, key_id | String | default = "primary", + # §5.5 — unknown fields are PERMITTED; see the `..` block above `Metadata`. + # A host that reads `signature.public_key` MUST NOT be the reason a future + # `counter_signature` field is rejected. + .. }, # ── §6 The component pedigree, v1 ──────────────────────────────────── @@ -372,16 +381,50 @@ in # never loaded; MIGRATION-1058 M3), with two normative tightenings: # `component_type` is REQUIRED (§6.2), and the security flags must be paid # for in the capability grant (§8.4). + # + # ── THE `..` TAIL, AND WHY EVERY COMPONENT-FACING RECORD HERE HAS ONE ── + # + # §5.5's table ends with "Unknown fields anywhere | PERMITTED. A reader SHOULD + # report them and MUST NOT consult them to relax any requirement of §8 or §9." + # That is normative prose, and by this file's own first rule the prose wins: + # where the two disagree, this file is the bug. + # + # They disagreed until 2026-10-05. A Nickel record contract is CLOSED by + # default — the sharp edge is recorded in Nickel's own integration suite, + # core/tests/integration/inputs/contracts/record_contract_extra_field.ncl, + # which asserts `EvalError::BlameError` with the message "extra field" and + # advises appending `, ..`. So a component carrying a legitimate extension + # field was blamed by this file while §5.5 permitted the field. The six + # `.machine_readable/contractiles/*/*.k9.ncl` components are the live case + # (they keep `semantics`, `contractile_verb`, `tier`, `authority`, + # `variance_schema`, `execution`, `failure_mode_defenses` in the pedigree; + # `probe_scope`, `probe_kinds_*`, `allow_filesystem_write_conditional` in + # security; `paired_xfile`, `paired_runner` in metadata), and + # `2-protocols/axel/config/*.k9.ncl` keep their payload at top level. + # + # Opening these five records is a CONFORMANCE fix, not a contract revision: + # the specified contract (the prose) never changed, so `contract_version` + # stays 1.0.0. It does not weaken §8 or §9 either — §5.5's own last clause + # forbids that, and the validator enforces the capability arithmetic over the + # extracted facts whatever an extension field claims (K9-S007, K9-S008). + # + # The positive control `tools/fixtures/valid/extension-fields.k9.ncl` exists + # so this cannot silently regress: it fails at L2 the moment any of these + # records closes again. Metadata = { name | String, version | String | default = "0.0.0", description | String | optional, author | String | optional, + # §5.5 — see the block comment above. The estate's contractiles add + # `paired_xfile` / `paired_runner` here. + .. }, Security = { - # §7 — closed set. An unknown tag is a contract violation, not a warning. + # §7 — closed set. An unknown LEASH TAG is a contract violation, not a + # warning; an unknown FIELD in this block is §5.5's business. leash | SecurityLevel, trust_level | String | default = "unset", allow_network | Bool | default = false, @@ -392,6 +435,11 @@ in signature_required | Bool | default = false, # §8.3 — the explicit grant. Default-deny: absent means empty. capabilities | CapabilityGrant | default = [], + # §5.5 — unknown fields are PERMITTED, and the prose names this block's own + # examples: "the estate's contractiles use `probe_scope` and + # `authorised_probes_only`". No conforming reader may let one of them + # weaken §8 or §9 — the grant above is what a host enforces. + .. }, Pedigree = { @@ -411,6 +459,12 @@ in side_effects | Array String | default = [], # §10.1 — the signature claim. Presence is per-leash; see Signature above. signature | Signature | optional, + # §5.5 — unknown fields are PERMITTED. This is the line MIGRATION-1058 M1 + # turned on: the contractile family keeps its own vocabulary in the pedigree + # (`semantics`, `contractile_verb`, `tier`, `authority`, `variance_schema`, + # `execution`, `failure_mode_defenses`), and nothing in this file may force + # those names out of it. + .. }, # The whole component: the pedigree plus the optional Nickel payload. @@ -435,6 +489,12 @@ in config | { _ : Dyn } | optional, recipes | { _ : Dyn } | optional, validation | { _ : Dyn } | optional, + # §5.5 — unknown fields are PERMITTED at top level too, which is the case + # MIGRATION-1058 M4 recorded for whoever first ran the corpus at L2: + # `2-protocols/axel/config/*.k9.ncl` keep their payload (`ci`, `local_dev`, + # `quality`; `metadata.k9.ncl`: `project`, `tech_stack`, `deployment`, + # `features`, `status`) beside `pedigree`. + .. }, # ── §11 Library dialect ──────────────────────────────────────────────