From 5f019b8fe83be9dcb8ff1b4af8f17c8993eb14ed Mon Sep 17 00:00:00 2001 From: hyperpolymath <6759885+hyperpolymath@users.noreply.github.com> Date: Mon, 5 Oct 2026 04:10:53 +0000 Subject: [PATCH 1/4] fix(k9): templates are not components; make setup-repo a real component MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MIGRATION-1058 M3 (standards#1058, issue #C). A template is never loaded, so it must not claim the reserved `.k9.ncl` component suffix. The three trust-tier templates keep their TODO placeholders — that is the point of a template — and become `template-*.k9.ncl.in`, outside every scope that reads `*.k9`/`*.k9.ncl` as a loadable component (the pre-commit hook, the corpus walk, CI's Nickel pathspec). `setup-repo.k9.ncl` keeps the suffix because it IS a component, and is fixed as one: its grant pays for all three security flags (`net.fetch`, `fs.write`, `process.spawn` — K9-S007/§8.4), `side_effects` names what the recipes actually do (K9-S010/§6.5), and a `signature` block is present (K9-S009/§10.1) whose header says, per §10.2, that presence is not verification. - ledger: 17 → 13 — the four M3 entries removed, shrink-only ratchet intact - docs repointed: svc/k9 README, contractiles README + INDEX.a2ml, canonical templates, CONTRACTILE-SPEC, K9-CONTRACT-SPEC, ADR-001 amendment note - REGISTRY.a2ml regenerated with `just registry` (also refreshes three hashes already stale on main: 1-formats/k9, 2-protocols/axel, form-fill-provenance) Verified: --self-test passes; --fixtures 5 positive / 21 negative / 0 failures; L1 clean on the four svc/k9 components; the hook is green for this change set (1 conforming, 0 grandfathered). The corpus hook still exits 1 on the pre-existing contractile (#A) and axel (#D) failures, untouched here. Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com> --- .machine_readable/REGISTRY.a2ml | 8 ++-- .machine_readable/contractiles/INDEX.a2ml | 10 ++--- .machine_readable/contractiles/README.adoc | 11 ++++-- .machine_readable/k9-contract-debt.txt | 9 ++--- .machine_readable/svc/k9/README.adoc | 28 ++++++++++---- .../svc/k9/examples/setup-repo.k9.ncl | 38 +++++++++++++++++++ ...te-hunt.k9.ncl => template-hunt.k9.ncl.in} | 12 ++++++ ...ennel.k9.ncl => template-kennel.k9.ncl.in} | 12 ++++++ ...te-yard.k9.ncl => template-yard.k9.ncl.in} | 12 ++++++ .../contractiles/CANONICAL-TEMPLATES.adoc | 17 +++++++-- 1-formats/k9/spec/K9-CONTRACT-SPEC.adoc | 4 +- 1-formats/k9/spec/MIGRATION-1058.adoc | 11 ++++++ 1-formats/k9/spec/contract/k9_contract.ncl | 8 ++-- .../L1-K9-S003-todo-component-type.k9.ncl | 5 ++- docs/ADR-001-k9-relocation-to-svc.adoc | 9 +++++ docs/CONTRACTILE-SPEC.adoc | 24 +++++++----- 16 files changed, 176 insertions(+), 42 deletions(-) rename .machine_readable/svc/k9/{template-hunt.k9.ncl => template-hunt.k9.ncl.in} (87%) rename .machine_readable/svc/k9/{template-kennel.k9.ncl => template-kennel.k9.ncl.in} (67%) rename .machine_readable/svc/k9/{template-yard.k9.ncl => template-yard.k9.ncl.in} (79%) diff --git a/.machine_readable/REGISTRY.a2ml b/.machine_readable/REGISTRY.a2ml index 0f4ca3c25..a11f5f23a 100644 --- a/.machine_readable/REGISTRY.a2ml +++ b/.machine_readable/REGISTRY.a2ml @@ -45,7 +45,7 @@ name = "K9 Self-Validating Components" stream = "foundation" home = "1-formats/k9/" canonical_doc = "1-formats/k9/README.adoc" -source_hash = "sha256:19b4ac92f44aa5e5c133755e8e2e4c2d18383cb64cccd4f31d576ba14e020be5" +source_hash = "sha256:e79eb5bb03a891e7aa272064b7fa6e6108c50bb67864e9e9302900a944ca0d27" route = "the K9 specification, security analysis and adoption guidance (implementations live in hyperpolymath/k9-ecosystem)" [[spec]] @@ -54,7 +54,7 @@ name = "Contractiles (Must/Trust/Dust/Intend)" stream = "foundation" home = "1-formats/contractiles/" canonical_doc = "1-formats/contractiles/README.adoc" -source_hash = "sha256:b3bedbed23c8c79a9a94b059e09ff5865f8bbf198a82d90384290e8f501504d5" +source_hash = "sha256:d82e0007277aeea794555bd2f0d2b83e8235def2771ff0d9c8dc23f9fdfc300e" route = "policy-enforcement primitives the K9 layer is built from" [[spec]] @@ -153,7 +153,7 @@ name = "AXEL Protocol" stream = "protocol" home = "2-protocols/axel/" canonical_doc = "2-protocols/axel/README.adoc" -source_hash = "sha256:84005883477e12b0c748bc9e7d68cbb309c199e4509fbfaed27b0454e773a88d" +source_hash = "sha256:c67b62c6dcbb730664318eb235b25c687493bf74cabbc20af5a71c0d7849acea" route = "age-gating + explicit-content enforcement" [[spec]] @@ -288,7 +288,7 @@ name = "FFP — Form-Fill Provenance" stream = "foundation" home = "1-formats/sub-specs/form-fill-provenance/" canonical_doc = "1-formats/sub-specs/form-fill-provenance/README.adoc" -source_hash = "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" +source_hash = "sha256:d6e979cdf67dc045e0cf7fefc77e72fa1f2bc4424dd142f68e2f772875449124" route = "whether a PDF form was machine-filled or printed blank for hand completion, and what a print path must record" [[spec]] diff --git a/.machine_readable/contractiles/INDEX.a2ml b/.machine_readable/contractiles/INDEX.a2ml index d44c3f2e4..634d72c39 100644 --- a/.machine_readable/contractiles/INDEX.a2ml +++ b/.machine_readable/contractiles/INDEX.a2ml @@ -84,15 +84,15 @@ notes = "First trident instance in the estate (2026-04-18). Reports progress tow [[verbs]] name = "k9" -semantics = "trust-tier templates (EXCEPTION to one-verbfile rule)" +semantics = "trust-tier templates (relocated by ADR-001; not a verb contractile)" file_pair = [ - "k9/template-hunt.k9.ncl", - "k9/template-kennel.k9.ncl", - "k9/template-yard.k9.ncl", + "../svc/k9/template-hunt.k9.ncl.in", + "../svc/k9/template-kennel.k9.ncl.in", + "../svc/k9/template-yard.k9.ncl.in", ] status = "exception" gating = "not applicable" -notes = "k9 is service-automation meta-infrastructure, not a verb contractile. Three trust-tier templates (Kennel/Yard/Hunt). Does not have a Verbfile.a2ml. See CONTRACTILE-SPEC §k9-exception." +notes = "k9 is service-automation meta-infrastructure, not a verb contractile. Relocated out of this directory to .machine_readable/svc/k9/ by ADR-001. The three trust-tier templates (Kennel/Yard/Hunt) carry the .in suffix because a template is not a component and is never loaded (MIGRATION-1058 M3); instantiate by copying to .k9.ncl and filling the TODOs. Does not have a Verbfile.a2ml." # [[verbs]] lust REMOVED 2026-04-18 — name had unwanted associations; # the horizon/aspiration semantics were always meant to live inside `intend` diff --git a/.machine_readable/contractiles/README.adoc b/.machine_readable/contractiles/README.adoc index 9dc27756e..2aaa269ec 100644 --- a/.machine_readable/contractiles/README.adoc +++ b/.machine_readable/contractiles/README.adoc @@ -77,21 +77,26 @@ enforces or validates concern declarations. k9 provides three trust-tier |=== | File | Trust tier | Description -| `k9/template-kennel.k9.ncl` +| `../svc/k9/template-kennel.k9.ncl.in` | Kennel | Pure data. No subprocess, no filesystem write, no network. Safe for metadata and declarative settings. -| `k9/template-yard.k9.ncl` +| `../svc/k9/template-yard.k9.ncl.in` | Yard | Nickel evaluation with contracts and validation. No side effects. -| `k9/template-hunt.k9.ncl` +| `../svc/k9/template-hunt.k9.ncl.in` | Hunt | Full execution surface. Must declare side effects, support dry-run, and be signed before the estate treats it as trustworthy automation. |=== +The templates moved out of this directory with ADR-001 (k9 is svc, not a +contractile). The `.in` suffix is MIGRATION-1058 M3: a template is not a +component, so it does not carry the reserved `.k9.ncl` suffix. Copy one to +`.k9.ncl` and fill in its `TODO`s to instantiate it. + === Why the naming rule does not apply The one-verb-one-Verbfile rule exists to enforce clean concern separation. diff --git a/.machine_readable/k9-contract-debt.txt b/.machine_readable/k9-contract-debt.txt index 73cf20eae..7d644738b 100644 --- a/.machine_readable/k9-contract-debt.txt +++ b/.machine_readable/k9-contract-debt.txt @@ -18,11 +18,10 @@ # One repo-relative path per line. '#' comments and blanks ignored. # Baseline 2026-10-03, produced by: # 1-formats/k9/tools/k9-validate.sh --layer L1 --json -# Count: 17 (8 removed: 6 contractiles + 2 axel config files with K9! sentinel added) -.machine_readable/svc/k9/examples/setup-repo.k9.ncl -.machine_readable/svc/k9/template-hunt.k9.ncl -.machine_readable/svc/k9/template-kennel.k9.ncl -.machine_readable/svc/k9/template-yard.k9.ncl +# Count: 13 (12 removed: 6 contractiles + 2 axel config files with K9! sentinel +# added; 3 templates renamed off the reserved suffix as *.k9.ncl.in — a template +# is not a component and is never loaded; setup-repo.k9.ncl granted, described +# and given a signature block. MIGRATION-1058 M3 / standards#1058 #C) 3-practice/session-management-standards/continuity/checkpoint-before-major-change/PROTOCOL.k9 3-practice/session-management-standards/continuity/emergency-termination/PROTOCOL.k9 3-practice/session-management-standards/continuity/planned-session-close/PROTOCOL.k9 diff --git a/.machine_readable/svc/k9/README.adoc b/.machine_readable/svc/k9/README.adoc index 19e891133..923e1d57c 100644 --- a/.machine_readable/svc/k9/README.adoc +++ b/.machine_readable/svc/k9/README.adoc @@ -71,13 +71,13 @@ Choose the appropriate security level for your use case: [source,bash] ---- # Kennel: Pure configuration -cp .machine_readable/contractiles/k9/examples/project-metadata.k9.ncl config/metadata.k9.ncl +cp .machine_readable/svc/k9/examples/project-metadata.k9.ncl config/metadata.k9.ncl # Yard: Validated configuration -cp .machine_readable/contractiles/k9/examples/ci-config.k9.ncl .github/ci.k9.ncl +cp .machine_readable/svc/k9/examples/ci-config.k9.ncl .github/ci.k9.ncl # Hunt: Full automation -cp .machine_readable/contractiles/k9/examples/setup-repo.k9.ncl scripts/setup.k9.ncl +cp .machine_readable/svc/k9/examples/setup-repo.k9.ncl scripts/setup.k9.ncl ---- === 2. Validate Components @@ -134,11 +134,25 @@ K9 contractiles integrate with other RSR standards: == Template Files -Use these as starting points for your own K9 components: +Use these as starting points for your own K9 components. They are +`.k9.ncl.in` files on purpose: a *template* is not a component. It is never +loaded, it carries unfilled `TODO` placeholders, and a placeholder that passes +a presence check is indistinguishable from a field that was never written +(K9-S003/K9-S005, MIGRATION-1058 M3). Withholding the reserved `.k9.ncl` +suffix means no host leashes a template and no gate counts its placeholders as +a declaration. Copy one to a real component name, fill in every `TODO`, and it +becomes an ordinary K9 component: -- `template-kennel.k9.ncl` - Pure data template -- `template-yard.k9.ncl` - Validated config template -- `template-hunt.k9.ncl` - Full execution template +- `template-kennel.k9.ncl.in` - Pure data template +- `template-yard.k9.ncl.in` - Validated config template +- `template-hunt.k9.ncl.in` - Full execution template + +[source,bash] +---- +cp .machine_readable/svc/k9/template-kennel.k9.ncl.in config/metadata.k9.ncl +# ... fill in every TODO, then validate: +1-formats/k9/tools/k9-validate.sh config/metadata.k9.ncl +---- == Dependencies diff --git a/.machine_readable/svc/k9/examples/setup-repo.k9.ncl b/.machine_readable/svc/k9/examples/setup-repo.k9.ncl index 523e81767..081c34dd7 100644 --- a/.machine_readable/svc/k9/examples/setup-repo.k9.ncl +++ b/.machine_readable/svc/k9/examples/setup-repo.k9.ncl @@ -15,6 +15,17 @@ K9! allow_filesystem_write = true, allow_subprocess = true, signature_required = true, + # §8.3/§8.4 — the grant. Each flag above is a REQUEST for a capability, + # and a request the grant does not cover is a contradiction inside the + # pedigree, not a preference (K9-S007). Without this list the component + # asked for the network, the filesystem and subprocesses while granting + # itself nothing, so "default-deny" denied nothing. Keep it in step with + # the recipes below. + capabilities = [ + "net.fetch", # add-license fetches the licence text over HTTPS + "fs.write", # create-structure and create-checkpoint-files write + "process.spawn", # git, just, nickel, curl and mkdir are child processes + ], }, metadata = { name = "setup-repo", @@ -28,6 +39,33 @@ K9! "Review Just recipes before execution", "Use dry-run mode first: ./must --dry-run run setup-repo.k9.ncl", ], + # §6.5/K9-S010 — mandatory at 'Hunt and may not be a placeholder: §9 + # requires a dry_run precondition, i.e. a plan that was produced AND + # reviewed, and a reviewer cannot review a plan for a component that + # requests network, filesystem and subprocess access while describing none + # of it. Each entry names the recipe that causes it. + side_effects = [ + "creates src/, docs/, tests/, scripts/, .github/workflows/ and .machine_readable/contractiles/k9/ under the current directory (create-structure)", + "writes STATE.a2ml, ECOSYSTEM.a2ml and META.a2ml in the repository root, overwriting any existing copies (create-checkpoint-files)", + "writes README.adoc in the repository root when add-readme is selected (add-readme)", + "downloads the licence text from https://raw.githubusercontent.com/hyperpolymath/pmpl/main/LICENSE to ./LICENSE when add-license is selected (add-license)", + "runs git init and sets repository-local user.name and user.email (init-git)", + "spawns git, just, nickel, curl, mkdir and the shell builtins as child processes (every recipe)", + "deletes STATE.a2ml, ECOSYSTEM.a2ml and META.a2ml from the current directory, after a 5-second pause, when clean is selected (clean)", + ], + # §10.1/K9-S009 — a 'Hunt component MUST carry a signature block. This one + # is what an example can honestly carry: a CLAIM that a signature exists, + # not a verified signature. With no external verifier the verdict is + # 'Present_Unverified (§10.2), and 'Present_Unverified is not the Hunt + # `signature` precondition (§10.4) — so this file is conforming, and it is + # still NOT authorised to run. Replace the block before use: + # ./must sign setup-repo.k9.ncl + signature = { + algorithm = "Ed25519", + key_id = "setup-repo-example", + payload_hash = "sha256:0000000000000000000000000000000000000000000000000000000000000000", + signature = "EXAMPLE-NOT-A-REAL-SIGNATURE", + }, }, # Configuration with contracts diff --git a/.machine_readable/svc/k9/template-hunt.k9.ncl b/.machine_readable/svc/k9/template-hunt.k9.ncl.in similarity index 87% rename from .machine_readable/svc/k9/template-hunt.k9.ncl rename to .machine_readable/svc/k9/template-hunt.k9.ncl.in index a9cc350e3..d36b97fc3 100644 --- a/.machine_readable/svc/k9/template-hunt.k9.ncl +++ b/.machine_readable/svc/k9/template-hunt.k9.ncl.in @@ -1,5 +1,17 @@ K9! # SPDX-License-Identifier: MPL-2.0 +# +# .k9.ncl.in — a TEMPLATE, not a component (standards#1058, MIGRATION-1058 M3). +# The `.in` suffix is deliberate: a template is never loaded, so it must not +# claim the reserved `.k9.ncl` component suffix. Nothing tries to leash it and +# no gate mistakes its TODOs for a component's declaration. The placeholders +# are the point. Instantiate them, and the copy becomes a real component: +# +# cp template-hunt.k9.ncl.in my-task.k9.ncl # then fill in every TODO +# +# The `K9!` line is kept so the INSTANTIATED file carries the envelope, which +# §11.2 makes the thing that makes a leash enforceable at all. +# # K9 Hunt-level template: Full execution with Just recipes # Security Level: Hunt (full system access) # ⚠️ SIGNATURE REQUIRED - Review carefully before use diff --git a/.machine_readable/svc/k9/template-kennel.k9.ncl b/.machine_readable/svc/k9/template-kennel.k9.ncl.in similarity index 67% rename from .machine_readable/svc/k9/template-kennel.k9.ncl rename to .machine_readable/svc/k9/template-kennel.k9.ncl.in index fa7e3f350..6881ea50d 100644 --- a/.machine_readable/svc/k9/template-kennel.k9.ncl +++ b/.machine_readable/svc/k9/template-kennel.k9.ncl.in @@ -1,5 +1,17 @@ K9! # SPDX-License-Identifier: MPL-2.0 +# +# .k9.ncl.in — a TEMPLATE, not a component (standards#1058, MIGRATION-1058 M3). +# The `.in` suffix is deliberate: a template is never loaded, so it must not +# claim the reserved `.k9.ncl` component suffix. Nothing tries to leash it and +# no gate mistakes its TODOs for a component's declaration. The placeholders +# are the point. Instantiate them, and the copy becomes a real component: +# +# cp template-kennel.k9.ncl.in my-task.k9.ncl # then fill in every TODO +# +# The `K9!` line is kept so the INSTANTIATED file carries the envelope, which +# §11.2 makes the thing that makes a leash enforceable at all. +# # K9 Kennel-level template: Pure data configuration # Security Level: Kennel (data-only, no execution) # No signature required - safe for any use diff --git a/.machine_readable/svc/k9/template-yard.k9.ncl b/.machine_readable/svc/k9/template-yard.k9.ncl.in similarity index 79% rename from .machine_readable/svc/k9/template-yard.k9.ncl rename to .machine_readable/svc/k9/template-yard.k9.ncl.in index 358671cf4..cf7324c9c 100644 --- a/.machine_readable/svc/k9/template-yard.k9.ncl +++ b/.machine_readable/svc/k9/template-yard.k9.ncl.in @@ -1,5 +1,17 @@ K9! # SPDX-License-Identifier: MPL-2.0 +# +# .k9.ncl.in — a TEMPLATE, not a component (standards#1058, MIGRATION-1058 M3). +# The `.in` suffix is deliberate: a template is never loaded, so it must not +# claim the reserved `.k9.ncl` component suffix. Nothing tries to leash it and +# no gate mistakes its TODOs for a component's declaration. The placeholders +# are the point. Instantiate them, and the copy becomes a real component: +# +# cp template-yard.k9.ncl.in my-task.k9.ncl # then fill in every TODO +# +# The `K9!` line is kept so the INSTANTIATED file carries the envelope, which +# §11.2 makes the thing that makes a leash enforceable at all. +# # K9 Yard-level template: Configuration with validation # Security Level: Yard (Nickel evaluation with contracts) # Signature recommended but not required diff --git a/1-formats/contractiles/CANONICAL-TEMPLATES.adoc b/1-formats/contractiles/CANONICAL-TEMPLATES.adoc index d2ce79d5e..26169132b 100644 --- a/1-formats/contractiles/CANONICAL-TEMPLATES.adoc +++ b/1-formats/contractiles/CANONICAL-TEMPLATES.adoc @@ -159,21 +159,32 @@ not be presented elsewhere as already-shipped work. | Tier | Canonical Template | Capability | Audit Expectation | Kennel -| `1-formats/contractiles/k9/template-kennel.k9.ncl` +| `.machine_readable/svc/k9/template-kennel.k9.ncl.in` | Pure data. No subprocesses, no filesystem writes, no network access. | Safe for metadata, declarative settings, and other read-only structured outputs. | Yard -| `1-formats/contractiles/k9/template-yard.k9.ncl` +| `.machine_readable/svc/k9/template-yard.k9.ncl.in` | Nickel evaluation with contracts and validation, but no side effects. | Use for validated configuration, schemas, and policies that need machine-checked structure. | Hunt -| `1-formats/contractiles/k9/template-hunt.k9.ncl` +| `.machine_readable/svc/k9/template-hunt.k9.ncl.in` | Full execution surface with recipes and side effects. | Must declare side effects clearly, support dry-run review, and be signed before the estate treats it as trustworthy automation. |=== +The `.in` suffix is the ruling of MIGRATION-1058 M3 (standards#1058): a +*template* is not a component. It is never loaded, its `TODO` fields are the +point, and a placeholder that satisfies a presence check is indistinguishable +from a field that was never written (K9-CONTRACT-SPEC §6.2). Keeping the +reserved `.k9.ncl` suffix on a file nothing loads is what made three templates +count as components with placeholder pedigrees. Instantiate a template by +copying it to `.k9.ncl` and filling every `TODO`; the copy is then a real +component and must satisfy the K9 contract in full — including the capability +grant that pays for each security flag (§8.4) and, at `'Hunt`, a non-empty +`side_effects` list and a `signature` block (§6.5, §10.1). + == 4. How Contractiles And K9 Fit Together The plain contractiles describe what must be true, what is trusted, how to diff --git a/1-formats/k9/spec/K9-CONTRACT-SPEC.adoc b/1-formats/k9/spec/K9-CONTRACT-SPEC.adoc index 7189ab6f8..ae5b3888b 100644 --- a/1-formats/k9/spec/K9-CONTRACT-SPEC.adoc +++ b/1-formats/k9/spec/K9-CONTRACT-SPEC.adoc @@ -361,7 +361,9 @@ it, `.machine_readable/svc/k9/template-hunt.k9.ncl` satisfied every field check in the estate while declaring its own type as `"TODO: describe component type"`. A field that exists and says nothing is indistinguishable from a field that was never written, except that it passes -the gate. +the gate. (That file was a *template* claiming a component's suffix; it is now +`.machine_readable/svc/k9/template-hunt.k9.ncl.in` — a template is not a +component and is never loaded. MIGRATION-1058 M3, standards#1058.) A contractile component MUST set `component_type` to `contractile:` (for example `contractile:must`). This is the disambiguation promised in the diff --git a/1-formats/k9/spec/MIGRATION-1058.adoc b/1-formats/k9/spec/MIGRATION-1058.adoc index 484f36245..932f7cc24 100644 --- a/1-formats/k9/spec/MIGRATION-1058.adoc +++ b/1-formats/k9/spec/MIGRATION-1058.adoc @@ -210,6 +210,17 @@ the clearest single illustration of why §8.4 exists. Fix it as a component: grant `net.fetch`, `fs.write`, `process.spawn`, add a `signature` block, and describe what it actually does. +*Resolution (standards#C, 2026-10-05):* done as ruled. The three templates are +now `template-{hunt,kennel,yard}.k9.ncl.in`: the `.in` suffix keeps them out of +every scope that reads `*.k9.ncl` as a loadable component, and the `TODO` +placeholders stay, because they are the point of a template. `setup-repo.k9.ncl` +kept the component suffix and was fixed as one: its grant pays for all three +flags (`net.fetch`, `fs.write`, `process.spawn`, §8.4), its `side_effects` name +what the recipes do (§6.5), and its `signature` block states in the file that +presence is not verification (§10.2). The four ledger entries are removed — +count 17 → 13. L1 is clean for the example; L2/L3 remain CI-side, as for the +rest of this plan. + === M4 — Two Axel config files declare a leash nothing reads *Files:* `2-protocols/axel/config/{ci,metadata}.k9.ncl` diff --git a/1-formats/k9/spec/contract/k9_contract.ncl b/1-formats/k9/spec/contract/k9_contract.ncl index be8736428..1cce4456a 100644 --- a/1-formats/k9/spec/contract/k9_contract.ncl +++ b/1-formats/k9/spec/contract/k9_contract.ncl @@ -370,9 +370,11 @@ let is_semver_of = fun major v => # ── §6 The component pedigree, v1 ──────────────────────────────────── # # Field-for-field this is the shape the estate's `.k9.ncl` components - # actually carry (`.machine_readable/svc/k9/template-*.k9.ncl`), with two - # normative tightenings: `component_type` is REQUIRED (§6.2), and the - # security flags must be paid for in the capability grant (§8.4). + # actually carry (`.machine_readable/svc/k9/template-*.k9.ncl.in`, the + # trust-tier templates — `.in` because a template is not a component and is + # 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). Metadata = { name | String, diff --git a/1-formats/k9/tools/fixtures/invalid/L1-K9-S003-todo-component-type.k9.ncl b/1-formats/k9/tools/fixtures/invalid/L1-K9-S003-todo-component-type.k9.ncl index bab378f93..5bb3bd00e 100644 --- a/1-formats/k9/tools/fixtures/invalid/L1-K9-S003-todo-component-type.k9.ncl +++ b/1-formats/k9/tools/fixtures/invalid/L1-K9-S003-todo-component-type.k9.ncl @@ -1,8 +1,9 @@ K9! # SPDX-License-Identifier: MPL-2.0 # Negative control §6.2: an unfilled component_type placeholder. This is the -# exact state of .machine_readable/svc/k9/template-hunt.k9.ncl today: the field -# exists, so a presence check passes, and the value says nothing. +# exact state of .machine_readable/svc/k9/template-hunt.k9.ncl.in (a template, +# which is why it no longer claims the .k9.ncl suffix): the field exists, so a +# presence check passes, and the value says nothing. { pedigree = { schema_version = "1.0.0", diff --git a/docs/ADR-001-k9-relocation-to-svc.adoc b/docs/ADR-001-k9-relocation-to-svc.adoc index 3ea8fc54d..4cf045954 100644 --- a/docs/ADR-001-k9-relocation-to-svc.adoc +++ b/docs/ADR-001-k9-relocation-to-svc.adoc @@ -76,6 +76,15 @@ k9 lives at `.machine_readable/svc/k9/` in every repo. The └── setup-repo.k9.ncl ---- +[NOTE] +==== +Amendment 2026-10-05 (standards#1058, MIGRATION-1058 M3): the three templates +now carry the `.in` suffix — `template-kennel.k9.ncl.in`, +`template-yard.k9.ncl.in`, `template-hunt.k9.ncl.in`. A template is not a +component and is never loaded, so it does not claim the reserved `.k9.ncl` +suffix. The tree above is otherwise unchanged. +==== + `svc/` is a **directory of named service-automation subsystems**. Today it holds only `k9/`. Future service-layer infrastructure (watchers, daemons, long-running automations) belongs here under its own name. diff --git a/docs/CONTRACTILE-SPEC.adoc b/docs/CONTRACTILE-SPEC.adoc index 46622bff9..944c7f1d7 100644 --- a/docs/CONTRACTILE-SPEC.adoc +++ b/docs/CONTRACTILE-SPEC.adoc @@ -173,13 +173,13 @@ seven rows: six verbs and one exception. │ ├── Trustfile.a2ml │ └── trust.ncl │ -└── k9/ ← EXCEPTION: trust-tier templates, not a verb - ├── README.adoc - ├── template-kennel.k9.ncl - ├── template-yard.k9.ncl - └── template-hunt.k9.ncl +└── k9 → .machine_readable/svc/k9/ ← relocated by ADR-001: k9 is svc, not a verb ---- +k9 is deliberately NOT in this tree any more. ADR-001 (accepted 2026-04-18) +moved it to `.machine_readable/svc/k9/` estate-wide: no verb directory, no +exception. The `k9/` entry is kept as a signpost only. + Each verb directory MUST contain exactly: * One `file.a2ml` (capitalised verb, e.g. `Mustfile.a2ml`). @@ -456,23 +456,29 @@ k9 provides three trust-tier templates that repos copy and instantiate: [cols="1,1,3", options="header"] |=== -| File | Trust tier | What it does +| File (in `.machine_readable/svc/k9/`, ADR-001) | Trust tier | What it does -| `template-kennel.k9.ncl` +| `template-kennel.k9.ncl.in` | Kennel | Pure data, no execution. No subprocess, no filesystem write, no network. Safe for metadata and declarative settings. -| `template-yard.k9.ncl` +| `template-yard.k9.ncl.in` | Yard | Nickel evaluation with contracts; no side effects. Used for validated config. -| `template-hunt.k9.ncl` +| `template-hunt.k9.ncl.in` | Hunt | Full execution surface. Declares side effects, supports dry-run, requires signature before estate-wide trust. |=== +The `.in` suffix is MIGRATION-1058 M3: a template is not a component and is +never loaded, so it does not carry the reserved `.k9.ncl` suffix. Instantiate +by copying to `.k9.ncl` and filling every `TODO`; the copy is then a +full K9 component, bound by the capability grant (§8.4), the `'Hunt` +`side_effects` obligation and the `signature` block (§10.1). + === Why the naming rule does not apply The one-verb-one-Verbfile rule exists to enforce a clean concern separation. From e3fda07242e516dd9f1a1e1dc17cfb9e7276578d Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 04:49:17 +0000 Subject: [PATCH 2/4] fix(lock-sync): Handle KYAML commas and action repository casing --- .machine_readable/REGISTRY.a2ml | 2 +- 1-formats/k9/spec/contract/k9_contract.ncl | 87 +++++----- scripts/check-lock-sync.sh | 8 +- scripts/tests/check-lock-sync-test.sh | 183 +++++++++------------ 4 files changed, 130 insertions(+), 150 deletions(-) diff --git a/.machine_readable/REGISTRY.a2ml b/.machine_readable/REGISTRY.a2ml index a11f5f23a..6943c2c47 100644 --- a/.machine_readable/REGISTRY.a2ml +++ b/.machine_readable/REGISTRY.a2ml @@ -45,7 +45,7 @@ name = "K9 Self-Validating Components" stream = "foundation" home = "1-formats/k9/" canonical_doc = "1-formats/k9/README.adoc" -source_hash = "sha256:e79eb5bb03a891e7aa272064b7fa6e6108c50bb67864e9e9302900a944ca0d27" +source_hash = "sha256:d10e71f64586a5c6faac5833d6a52225d22042170e63df0ad2076ee610be8831" route = "the K9 specification, security analysis and adoption guidance (implementations live in hyperpolymath/k9-ecosystem)" [[spec]] diff --git a/1-formats/k9/spec/contract/k9_contract.ncl b/1-formats/k9/spec/contract/k9_contract.ncl index 1cce4456a..da8c3176f 100644 --- a/1-formats/k9/spec/contract/k9_contract.ncl +++ b/1-formats/k9/spec/contract/k9_contract.ncl @@ -72,18 +72,18 @@ let ExtensionPrefix = "x-" in # ───────────────────────────────────────────────────────────────────────── # §5.2 / §8.1 — name shapes, using nothing beyond length and substring # ───────────────────────────────────────────────────────────────────────── - let is_digit = fun c => c == "0" - || c == "1" - || c == "2" - || c == "3" - || c == "4" - || c == "5" - || c == "6" - || c == "7" - || c == "8" - || c == "9" in + || c == "1" + || c == "2" + || c == "3" + || c == "4" + || c == "5" + || c == "6" + || c == "7" + || c == "8" + || c == "9" +in # A numeric dot-triple whose MAJOR segment equals `major`. Walks the string # once. Rejects: non-digits, a leading or trailing dot, an empty segment, a @@ -109,14 +109,15 @@ let is_semver_of = fun major v => seg_len > 0 && go (i + 1) (dots + 1) 0 false s else if is_digit c then (first_zero == false || seg_len == 0) - && go (i + 1) dots (seg_len + 1) (c == "0") s + && go (i + 1) dots (seg_len + 1) (c == "0") s else false in # A dot-triple is at least `want` plus "N.N": three more characters. std.string.length v >= (std.string.length want + 3) - && std.string.substring 0 (std.string.length want) v == want - && go 0 0 0 false v in + && std.string.substring 0 (std.string.length want) v == want + && go 0 0 0 false v +in # ───────────────────────────────────────────────────────────────────────── # The contract record. Nickel records are recursive, so fields below refer to @@ -177,8 +178,7 @@ let is_semver_of = fun major v => # pedigree cannot crash on it. leash_of = fun pedigree => if std.record.has_field "security" pedigree - && std.record.has_field "leash" pedigree.security - then + && std.record.has_field "leash" pedigree.security then pedigree.security.leash else 'Kennel, @@ -208,13 +208,13 @@ let is_semver_of = fun major v => # after the vendor segment, i.e. "x-vendor.something", never bare "x-vendor". is_extension = fun name => std.string.length name > std.string.length ExtensionPrefix - && std.string.substring 0 (std.string.length ExtensionPrefix) name - == ExtensionPrefix - && std.array.length (std.string.split "." name) >= 2, + && std.string.substring 0 (std.string.length ExtensionPrefix) name == ExtensionPrefix + && std.array.length (std.string.split "." name) >= 2, - Capability = std.contract.from_predicate (fun name => - is_core name || is_extension name - ), + Capability = + std.contract.from_predicate (fun name => + is_core name || is_extension name + ), # An explicit allow-list. DEFAULT-DENY is expressed by the empty grant: it # permits nothing, and "nothing" is the correct answer for an absent grant. @@ -241,28 +241,25 @@ let is_semver_of = fun major v => required_capabilities = fun security => ( if std.record.has_field "allow_network" security - && security.allow_network - then + && security.allow_network then ["net.fetch"] else [] ) - @ ( - if std.record.has_field "allow_filesystem_write" security - && security.allow_filesystem_write - then - ["fs.write"] - else - [] - ) - @ ( - if std.record.has_field "allow_subprocess" security - && security.allow_subprocess - then - ["process.spawn"] - else - [] - ), + @ ( + if std.record.has_field "allow_filesystem_write" security + && security.allow_filesystem_write then + ["fs.write"] + else + [] + ) + @ ( + if std.record.has_field "allow_subprocess" security + && security.allow_subprocess then + ["process.spawn"] + else + [] + ), # What the grant fails to cover. Empty iff the pedigree is self-consistent. grant_deficit = fun grant security => @@ -299,10 +296,10 @@ let is_semver_of = fun major v => authorize_hunt = fun evidence => let unmet = (if evidence.signature then [] else ["signature"]) - @ (if evidence.policy then [] else ["policy"]) - @ (if evidence.sandbox then [] else ["sandbox"]) - @ (if evidence.dry_run then [] else ["dry_run"]) - @ (if evidence.capability_grant then [] else ["capability_grant"]) + @ (if evidence.policy then [] else ["policy"]) + @ (if evidence.sandbox then [] else ["sandbox"]) + @ (if evidence.dry_run then [] else ["dry_run"]) + @ (if evidence.capability_grant then [] else ["capability_grant"]) in if std.array.length unmet == 0 then { @@ -316,7 +313,7 @@ let is_semver_of = fun major v => enforced_level = 'Yard, reason = "Hunt requires all five preconditions; unmet: " - ++ std.string.join ", " unmet, + ++ std.string.join ", " unmet, unmet_preconditions = unmet, }, @@ -447,5 +444,5 @@ let is_semver_of = fun major v => # no pedigree, no leash claim, no execution licence of any kind. is_library = fun lib => !(std.record.has_field "pedigree" lib) - && !(std.record.has_field "leash" lib), + && !(std.record.has_field "leash" lib), } diff --git a/scripts/check-lock-sync.sh b/scripts/check-lock-sync.sh index be7581233..d06470fef 100755 --- a/scripts/check-lock-sync.sh +++ b/scripts/check-lock-sync.sh @@ -51,7 +51,8 @@ function norm(r, at, path, ref, n, parts) { if (path == "" || ref == "") return "" if (substr(path, 1, 2) == "./" || substr(path, 1, 2) == "$/") return "" # local action if (split(path, parts, "/") < 2) return "" - return parts[1] "/" parts[2] "@" ref + # GitHub owner/repository names are case-insensitive; refs are not. + return tolower(parts[1] "/" parts[2]) "@" ref } # ---------- pass 1: the lockfile ---------- @@ -68,7 +69,7 @@ FILENAME == lockfile { next } if (match($0, /^ - '"'"'([^'"'"']+)'"'"'[[:space:]]*$/, m) && cur != "") { - lock[cur, m[1]] = 1 + lock[cur, norm(m[1])] = 1 lockcount[cur]++ next } @@ -82,6 +83,9 @@ FNR == 1 { wf = FILENAME } sub(/[[:space:]]+#.*$/, "", line) # strip trailing comment if (match(line, /^[[:space:]]*-?[[:space:]]*uses:[[:space:]]*(.+)$/, m)) { raw = m[1] + # KYAML puts a comma after a quoted scalar. Strip the YAML delimiter + # before unquoting, otherwise the closing quote becomes part of the ref. + sub(/["\x27],[[:space:]]*$/, "", raw) gsub(/^["'"'"']|["'"'"']$/, "", raw) gsub(/[[:space:]]+$/, "", raw) if (raw ~ /^\$\//) { dollar[wf] = dollar[wf] " " raw; next } # known corruption diff --git a/scripts/tests/check-lock-sync-test.sh b/scripts/tests/check-lock-sync-test.sh index a40857b01..c65e59179 100755 --- a/scripts/tests/check-lock-sync-test.sh +++ b/scripts/tests/check-lock-sync-test.sh @@ -1,109 +1,88 @@ #!/usr/bin/env bash # SPDX-License-Identifier: MPL-2.0 -# -# Test suite for scripts/check-lock-sync.sh -# Part of hyperpolymath/standards#968 campaign - +# Regression tests for issue #968: compare workflow refs in both directions. set -euo pipefail -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_ROOT="$(dirname "$SCRIPT_DIR")" -CHECK_SCRIPT="$REPO_ROOT/check-lock-sync.sh" - -# Load test helpers if available -if [ -f "$SCRIPT_DIR/test-helpers.sh" ]; then - # shellcheck source=scripts/tests/test-helpers.sh - source "$SCRIPT_DIR/test-helpers.sh" -fi - -PASS=0 -FAIL=0 -TOTAL=0 - -fail() { - echo "FAIL: $*" - FAIL=$((FAIL + 1)) - TOTAL=$((TOTAL + 1)) -} - -pass() { - echo "PASS: $*" - PASS=$((PASS + 1)) - TOTAL=$((TOTAL + 1)) -} - -echo "=== Test suite for check-lock-sync.sh ===" - -# Test 1: Script exists and is executable -if [ -x "$CHECK_SCRIPT" ]; then - pass "Script exists and is executable" -else - fail "Script missing or not executable" -fi - -# Test 2: Script has SPDX header -grep -q "SPDX-License-Identifier: MPL-2.0" "$CHECK_SCRIPT" && \ - pass "Script has SPDX license header" || \ - fail "Script missing SPDX license header" - -# Test 3: Script exits 0 when lockfile is in sync (test with current repo if it has a lockfile) -if [ -f "$REPO_ROOT/.github/workflows/actions.lock" ]; then - if "$CHECK_SCRIPT" "$REPO_ROOT/.github/workflows" >/dev/null 2>&1; then - pass "Script exits 0 when lockfile is in sync (self-test)" +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +CHECK_SCRIPT="$ROOT/scripts/check-lock-sync.sh" +TMP="$(mktemp -d)" +trap 'rm -rf "$TMP"' EXIT +WF="$TMP/workflows" +mkdir -p "$WF" +pass=0 +fail=0 + +expect() { + local want="$1" label="$2" out rc=0 + out="$(bash "$CHECK_SCRIPT" "$WF" 2>&1)" || rc=$? + if [ "$rc" -eq "$want" ]; then + echo "PASS: $label" + pass=$((pass + 1)) else - fail "Script failed on current repo (may be out of sync, or script error)" + echo "FAIL: $label (expected $want, got $rc)" + printf '%s\n' "$out" + fail=$((fail + 1)) fi -else - echo "SKIP: No actions.lock in current repo, cannot test sync case" -fi - -# Test 4: Script exits 1 when no lockfile exists -mkdir -p /tmp/test-lock-sync-empty -cd /tmp/test-lock-sync-empty -mkdir -p .github/workflows -touch .github/workflows/test.yml -if "$CHECK_SCRIPT" .github/workflows >/dev/null 2>&1; then - fail "Script should exit 1 when no lockfile exists" -else - pass "Script exits 1 when no lockfile exists" -fi -rm -rf /tmp/test-lock-sync-empty - -# Test 5: Script handles empty workflows directory gracefully -# Note: An empty workflows directory with just actions.lock is an edge case. -# The script exits 0 because there are no workflows to validate. -mkdir -p /tmp/test-lock-sync-no-wf -cd /tmp/test-lock-sync-no-wf -mkdir -p .github/workflows -touch .github/workflows/actions.lock -if "$CHECK_SCRIPT" .github/workflows >/dev/null 2>&1; then - pass "Script handles empty workflows directory (exits 0 - no workflows to check)" -else - fail "Script failed unexpectedly on empty workflows directory" -fi -rm -rf /tmp/test-lock-sync-no-wf - -# Test 6: Script has proper documentation -if grep -q "standards#968\|issue #968" "$CHECK_SCRIPT"; then - pass "Script references issue #968" -else - fail "Script missing reference to issue #968" -fi - -if grep -q "burble#224" "$CHECK_SCRIPT"; then - pass "Script references burble#224" -else - fail "Script missing reference to burble#224" -fi - -echo "" -echo "=== Results ===" -echo "PASS: $PASS" -echo "FAIL: $FAIL" -echo "TOTAL: $TOTAL" - -if [ "$FAIL" -gt 0 ]; then - exit 1 -fi +} -exit 0 +cat > "$WF/test.yml" <<'YAML' +jobs: + test: + steps: + - uses: Actions/Checkout@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +YAML +expect 1 'missing lockfile fails closed' +cat > "$WF/actions.lock" <<'YAML' +workflows: + '.github/workflows/test.yml': + - 'actions/checkout@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +YAML +expect 0 'repository names compare case-insensitively' +sed -i 's/Actions\/Checkout/actions\/checkout/' "$WF/test.yml" +expect 0 'matching block workflow is accepted' + +cat > "$WF/test.yml" <<'YAML' +{ + jobs: { + test: { + steps: [ + { + uses: "actions/checkout@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", # pinned + }, + { + uses: 'actions/checkout@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + }, + ], + }, + }, +} +YAML +expect 0 'KYAML quoted refs exclude the trailing comma' +sed -i 's/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/g' "$WF/test.yml" +expect 1 'KYAML changed SHA remains a failure' + +cat > "$WF/test.yml" <<'YAML' +jobs: + test: + uses: owner/repo/.github/workflows/test.yml@Release +YAML +cat > "$WF/actions.lock" <<'YAML' +workflows: + '.github/workflows/test.yml': + - 'Owner/Repo@Release' +YAML +expect 0 'job-level reusable workflow is checked and lock names normalised' +sed -i 's/@Release/@release/' "$WF/test.yml" +expect 1 'ref case remains significant' +sed -i 's/@release/@Release/' "$WF/test.yml" +printf " - 'actions/checkout@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'\n" >> "$WF/actions.lock" +expect 1 'stale lock entries remain failures' +sed -i '/actions\/checkout/d' "$WF/actions.lock" +cp "$WF/test.yml" "$WF/unlocked.yml" +expect 1 'workflow missing from lock remains a failure' +rm "$WF/unlocked.yml" +rm "$WF/test.yml" +expect 1 'deleted workflow lock entries remain failures' + +printf '\ncheck-lock-sync regression: %s passed, %s failed\n' "$pass" "$fail" +[ "$fail" -eq 0 ] From d556b3db6c85718b971969b3d40da1b071aa4b2d Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Mon, 5 Oct 2026 06:07:10 +0100 Subject: [PATCH 3/4] fix: remove k9 exception from contractile registry per ADR-001 Remove k9 from INDEX.a2ml verb registry (now only 6 verbs, no exceptions). Update README.adoc and CONTRACTILE-SPEC.adoc to reflect k9 relocation to .machine_readable/svc/k9/ estate-wide. Rename [[k9-exception]] anchor to [[k9-relocation]] for clarity. Addresses CodeRabbit review comment on PR #1148 lines +176-+181. Signed-off-by: Mistral Vibe --- .machine_readable/contractiles/INDEX.a2ml | 17 ++---- .machine_readable/contractiles/README.adoc | 60 ++++------------------ docs/CONTRACTILE-SPEC.adoc | 45 +++++++--------- 3 files changed, 33 insertions(+), 89 deletions(-) diff --git a/.machine_readable/contractiles/INDEX.a2ml b/.machine_readable/contractiles/INDEX.a2ml index d44c3f2e4..9cfeeb731 100644 --- a/.machine_readable/contractiles/INDEX.a2ml +++ b/.machine_readable/contractiles/INDEX.a2ml @@ -10,9 +10,9 @@ --- id = "contractiles-registry" -version = "2.0.0" # 2.0.0 (2026-04-18): all 6 verbs on trident shape; verb set complete. +version = "2.1.0" # 2.1.0 (2026-10-05): removed k9 exception per ADR-001; registry now contains only 6 verbs with no exceptions. spec = "docs/CONTRACTILE-SPEC.adoc" -last_updated = "2026-04-18" +last_updated = "2026-10-05" base_schema = ".machine_readable/contractiles/_base.ncl" meta_schema_status = "pending — see CONTRACTILE-SPEC §validator-meta-schema" @@ -82,18 +82,7 @@ gating = "non-gating (continue)" cardinality = "one per repo" notes = "First trident instance in the estate (2026-04-18). Reports progress toward committed next-actions AND lists horizon aspirations. Absorbed the deprecated `lust` verb 2026-04-18. Never blocks. Remaining 5 verbs still on file_pair shape until tridents are built." -[[verbs]] -name = "k9" -semantics = "trust-tier templates (EXCEPTION to one-verbfile rule)" -file_pair = [ - "k9/template-hunt.k9.ncl", - "k9/template-kennel.k9.ncl", - "k9/template-yard.k9.ncl", -] -status = "exception" -gating = "not applicable" -notes = "k9 is service-automation meta-infrastructure, not a verb contractile. Three trust-tier templates (Kennel/Yard/Hunt). Does not have a Verbfile.a2ml. See CONTRACTILE-SPEC §k9-exception." - +# [[verbs]] k9 REMOVED 2026-10-05 — relocated by ADR-001 to .machine_readable/svc/k9/; not a verb contractile # [[verbs]] lust REMOVED 2026-04-18 — name had unwanted associations; # the horizon/aspiration semantics were always meant to live inside `intend` # (the north-star verb). The [[wishes]] schema was absorbed into diff --git a/.machine_readable/contractiles/README.adoc b/.machine_readable/contractiles/README.adoc index 9dc27756e..4b6cf61ba 100644 --- a/.machine_readable/contractiles/README.adoc +++ b/.machine_readable/contractiles/README.adoc @@ -21,7 +21,7 @@ PascalCase in the A2ML (e.g. `intend.ncl` + `Intentfile.a2ml`, All verb runners import `_base.ncl` (shared pedigree + run-defaults + probe-schema). See `docs/CONTRACTILE-SPEC.adoc` for the normative specification. -== Verbs (6 + k9 exception) +== Verbs (6) [cols="1,2,3", options="header"] |=== @@ -60,55 +60,18 @@ associations). Its [[wishes]] semantics live inside `intend/Intentfile.a2ml` as a second section alongside [[intents]]. Any `lust/` dir encountered in an estate repo is drift and should be removed. -== k9 — Service-Automation Layer (EXCEPTION to the one-verbfile rule) +[[k9-relocation]] +== k9 Service-Automation Layer (Relocated) -IMPORTANT: `k9/` is **not a contractile verb** and does NOT follow the -`file.a2ml` + `.ncl` pattern. This is an intentional, documented -exception. Do not apply the naming rule to k9. +IMPORTANT: k9 is **not a contractile verb**. As of ADR-001 (2026-04-18), +k9 has been relocated from this directory to `.machine_readable/svc/k9/` +estate-wide. The `k9/` directory entry in this location is a **signpost only**. -=== Why k9 is different - -The seven verb contractiles each declare *one concern per repo* in a single -xfile. k9 is not a concern; it is the *graded automation surface* that -enforces or validates concern declarations. k9 provides three trust-tier -*templates* that repos copy and instantiate: - -[cols="1,1,3", options="header"] -|=== -| File | Trust tier | Description - -| `k9/template-kennel.k9.ncl` -| Kennel -| Pure data. No subprocess, no filesystem write, no network. Safe for - metadata and declarative settings. - -| `k9/template-yard.k9.ncl` -| Yard -| Nickel evaluation with contracts and validation. No side effects. - -| `k9/template-hunt.k9.ncl` -| Hunt -| Full execution surface. Must declare side effects, support dry-run, and - be signed before the estate treats it as trustworthy automation. -|=== - -=== Why the naming rule does not apply - -The one-verb-one-Verbfile rule exists to enforce clean concern separation. -k9 is meta-infrastructure: it does not have a `K9file.a2ml` because it is -not a declarative xfile — it is a template set that instantiates into -specific repos. Applying the rule would produce a meaningless `K9file.a2ml` -with nothing to declare. - -=== Audit rule - -If a repo claims `k9` enforcement, each k9 component in that repo MUST -declare a `paired_xfile` pointing to a specific contractile xfile (e.g. -`../must/Mustfile.a2ml`). Floating k9 components with no paired xfile are -non-conformant. - -See `k9/README.adoc` for the full k9 security model and usage instructions. -See `docs/CONTRACTILE-SPEC.adoc §k9-exception` for the normative statement. +k9 provides three trust-tier templates (Kennel, Yard, Hunt) that repos copy +and instantiate. It is service-automation meta-infrastructure, not a verb +contractile, and therefore does not appear in the contractile registry +(INDEX.a2ml). See ADR-001 for the relocation rationale and +`.machine_readable/svc/k9/README.adoc` for the full k9 security model. == Fill-In Instructions @@ -125,7 +88,6 @@ When copying this set into a new repo: 6. `Bustfile` — declare real breakage / expiry / hard-stop conditions. 7. `Intentfile` — list tracked next-actions with observable probes ([[intents]] section) AND horizon aspirations ([[wishes]] section). -8. Pair any `k9/*.k9.ncl` with a specific contractile via `paired_xfile`. == Intentfile: Commitments vs Aspirations — Two Sections, One File diff --git a/docs/CONTRACTILE-SPEC.adoc b/docs/CONTRACTILE-SPEC.adoc index 46622bff9..96bebae89 100644 --- a/docs/CONTRACTILE-SPEC.adoc +++ b/docs/CONTRACTILE-SPEC.adoc @@ -33,11 +33,10 @@ probes. This spec covers: -* The six contractile verbs, the k9 exception, and their semantics. +* The six contractile verbs and their semantics. * The canonical directory layout and naming rules. * The shared Nickel base (`_base.ncl`) and how verb runners inherit from it. * The `probe` contract (current legacy String form and target structured form). -* The k9 exception — trust-tier template infrastructure, not a verb contractile. * The registry (`INDEX.a2ml`) that lists all verbs. * Test fixture conventions. * CLI binding and invocation. @@ -82,14 +81,14 @@ run-behaviour:: k9:: Service-automation trust-tier infrastructure. Lives at - `.machine_readable/svc/k9/` — NOT inside `1-formats/contractiles/`. See <>. + `.machine_readable/svc/k9/` — NOT inside `1-formats/contractiles/`. See <>. Moved out of `1-formats/contractiles/` per `ADR-001-k9-relocation-to-svc.adoc` (2026-04-18); this spec's v1.1.0 language predates that ADR. == The Verb Set Six verbs are defined, plus `k9`, which is an exception documented in -<> and is not a verb contractile. The table below therefore has +<> and is not a verb contractile. The table below therefore has seven rows: six verbs and one exception. [cols="1,2,5", options="header"] @@ -135,7 +134,7 @@ seven rows: six verbs and one exception. | `k9` *(exception)* | Trust-tier automation templates -| NOT a verb contractile. See <>. +| NOT a verb contractile. See <>. |=== [[directory-layout]] @@ -146,8 +145,8 @@ seven rows: six verbs and one exception. ---- .machine_readable/contractiles/ ├── _base.ncl ← shared base (pedigree + status-core + probe-schema + run-defaults) -├── INDEX.a2ml ← registry of all active verbs (6 + k9 exception) -├── README.adoc ← human overview + k9 exception note +├── INDEX.a2ml ← registry of all active verbs (6) +├── README.adoc ← human overview │ ├── adjust/ │ ├── Adjustfile.a2ml ← declaration (data) @@ -173,11 +172,7 @@ seven rows: six verbs and one exception. │ ├── Trustfile.a2ml │ └── trust.ncl │ -└── k9/ ← EXCEPTION: trust-tier templates, not a verb - ├── README.adoc - ├── template-kennel.k9.ncl - ├── template-yard.k9.ncl - └── template-hunt.k9.ncl +└── k9/ ← relocated by ADR-001 to .machine_readable/svc/k9/ estate-wide; signpost only ---- Each verb directory MUST contain exactly: @@ -205,7 +200,7 @@ Anything else in a verb directory is human notes or archive; machines ignore it. (see <>). 4. **k9 follows none of the above.** k9 is the explicit exception - (see <>). + (see <>). == Shared Base (`_base.ncl`) @@ -436,8 +431,8 @@ The migration path is: Adopters writing new xfiles should prefer the structured form where possible. -[[k9-exception]] -== K9 Exception +[[k9-relocation]] +== K9 Service-Automation Layer `k9/` is **not a contractile verb**. It is trust-tier template infrastructure that lives at `.machine_readable/svc/k9/`, separate from the verb directories @@ -513,12 +508,11 @@ When implemented, place as `.machine_readable/contractiles/contractile-meta.ncl` == Registry (`INDEX.a2ml`) `.machine_readable/contractiles/INDEX.a2ml` is the machine-readable catalogue -of all six verbs plus the k9 exception. It lists: +of all six verbs. It lists: * Verb name and one-line semantics. * The file pair (declaration + runner). -* Active vs exception status. -* Notes for exceptions. +* Active status. * The base schema location. * Meta-schema status. @@ -670,10 +664,10 @@ four classifications were wrong. | *Rival taxonomy* — conflicts | Not adoption notes. v0.1.0 Draft, RFC 2119 language, its own conformance clause. It defines a *five-tier* system — Must, Trust, Dust, *Lust*, *K9* — - against this spec's six verbs plus the k9 exception. Three substantive - divergences: `lust` is live as a tier, though it was deprecated and - absorbed into `intend` on 2026-04-18; `k9` is a peer tier rather than a - documented exception; and S07-3 says declarations "MAY be expressed in any + against this spec's six verbs. Three substantive divergences: `lust` is + live as a tier, though it was deprecated and absorbed into `intend` on + 2026-04-18; `k9` is a peer tier rather than service-automation + infrastructure; and S07-3 says declarations "MAY be expressed in any structured format", against this spec's fixed two-file pattern. Its reference implementation section sites files at `1-formats/contractiles/must/` and `1-formats/contractiles/lust/` — the wrong root, and a plausible source of the 76 @@ -795,7 +789,7 @@ mechanical move rather than a content change. === Cardinality is violated at scale -This spec already states (<>) that each verb declares *one +This spec already states (<>) that each verb declares *one concern per repo*, and the owner's layout ruling states it more strongly: exactly ONE of each verb per repo, no second copies, no variants, no per-subdir duplicates, with `ANCHOR.a2ml` the sole exception. @@ -992,9 +986,8 @@ cat lust/Lustfile.a2ml # extract any real wishes rm -r lust/ ---- -The verb count drops 7 → 6 (plus the k9 exception, unchanged). All tooling -dispatch on `lust` must be removed; consumers should read wishes from -`Intentfile.a2ml` instead. +The verb count drops 7 → 6. All tooling dispatch on `lust` must be removed; +consumers should read wishes from `Intentfile.a2ml` instead. === Intentfile ← Intendfile revert (2026-04-18) From 1ad500722a839b2212913e7ba163f8cf7f9a26a7 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Mon, 5 Oct 2026 06:40:56 +0100 Subject: [PATCH 4/4] fix(k9): remove k9 exception from contractile registry on pr-1148 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes the merge conflict by accepting the main branch's removal of k9 exception from the contractile registry, per ADR-001. - INDEX.a2ml: k9 removed from [[verbs]] section - README.adoc: Updated to reflect k9 relocation to .machine_readable/svc/k9/ - CONTRACTILE-SPEC.adoc: Updated tree diagram and registry description - codeql-reusable.yml: Fixed subpath pin (init@ → @) --- .github/workflows/codeql-reusable.yml | 2 +- .machine_readable/contractiles/INDEX.a2ml | 17 +----- .machine_readable/contractiles/README.adoc | 65 ++++------------------ docs/CONTRACTILE-SPEC.adoc | 59 ++++++++------------ 4 files changed, 38 insertions(+), 105 deletions(-) diff --git a/.github/workflows/codeql-reusable.yml b/.github/workflows/codeql-reusable.yml index fd9032afb..4e13fa00d 100644 --- a/.github/workflows/codeql-reusable.yml +++ b/.github/workflows/codeql-reusable.yml @@ -94,7 +94,7 @@ jobs: persist-credentials: false - name: Initialize CodeQL - uses: github/codeql-action@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0 (4.38.1 blocked estate-wide; nexia-list#100) + uses: github/codeql-action/init@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0 (4.38.1 blocked estate-wide; nexia-list#100) with: languages: ${{ inputs.language }} build-mode: ${{ inputs.build-mode }} diff --git a/.machine_readable/contractiles/INDEX.a2ml b/.machine_readable/contractiles/INDEX.a2ml index 634d72c39..9cfeeb731 100644 --- a/.machine_readable/contractiles/INDEX.a2ml +++ b/.machine_readable/contractiles/INDEX.a2ml @@ -10,9 +10,9 @@ --- id = "contractiles-registry" -version = "2.0.0" # 2.0.0 (2026-04-18): all 6 verbs on trident shape; verb set complete. +version = "2.1.0" # 2.1.0 (2026-10-05): removed k9 exception per ADR-001; registry now contains only 6 verbs with no exceptions. spec = "docs/CONTRACTILE-SPEC.adoc" -last_updated = "2026-04-18" +last_updated = "2026-10-05" base_schema = ".machine_readable/contractiles/_base.ncl" meta_schema_status = "pending — see CONTRACTILE-SPEC §validator-meta-schema" @@ -82,18 +82,7 @@ gating = "non-gating (continue)" cardinality = "one per repo" notes = "First trident instance in the estate (2026-04-18). Reports progress toward committed next-actions AND lists horizon aspirations. Absorbed the deprecated `lust` verb 2026-04-18. Never blocks. Remaining 5 verbs still on file_pair shape until tridents are built." -[[verbs]] -name = "k9" -semantics = "trust-tier templates (relocated by ADR-001; not a verb contractile)" -file_pair = [ - "../svc/k9/template-hunt.k9.ncl.in", - "../svc/k9/template-kennel.k9.ncl.in", - "../svc/k9/template-yard.k9.ncl.in", -] -status = "exception" -gating = "not applicable" -notes = "k9 is service-automation meta-infrastructure, not a verb contractile. Relocated out of this directory to .machine_readable/svc/k9/ by ADR-001. The three trust-tier templates (Kennel/Yard/Hunt) carry the .in suffix because a template is not a component and is never loaded (MIGRATION-1058 M3); instantiate by copying to .k9.ncl and filling the TODOs. Does not have a Verbfile.a2ml." - +# [[verbs]] k9 REMOVED 2026-10-05 — relocated by ADR-001 to .machine_readable/svc/k9/; not a verb contractile # [[verbs]] lust REMOVED 2026-04-18 — name had unwanted associations; # the horizon/aspiration semantics were always meant to live inside `intend` # (the north-star verb). The [[wishes]] schema was absorbed into diff --git a/.machine_readable/contractiles/README.adoc b/.machine_readable/contractiles/README.adoc index 2aaa269ec..4b6cf61ba 100644 --- a/.machine_readable/contractiles/README.adoc +++ b/.machine_readable/contractiles/README.adoc @@ -21,7 +21,7 @@ PascalCase in the A2ML (e.g. `intend.ncl` + `Intentfile.a2ml`, All verb runners import `_base.ncl` (shared pedigree + run-defaults + probe-schema). See `docs/CONTRACTILE-SPEC.adoc` for the normative specification. -== Verbs (6 + k9 exception) +== Verbs (6) [cols="1,2,3", options="header"] |=== @@ -60,60 +60,18 @@ associations). Its [[wishes]] semantics live inside `intend/Intentfile.a2ml` as a second section alongside [[intents]]. Any `lust/` dir encountered in an estate repo is drift and should be removed. -== k9 — Service-Automation Layer (EXCEPTION to the one-verbfile rule) +[[k9-relocation]] +== k9 Service-Automation Layer (Relocated) -IMPORTANT: `k9/` is **not a contractile verb** and does NOT follow the -`file.a2ml` + `.ncl` pattern. This is an intentional, documented -exception. Do not apply the naming rule to k9. +IMPORTANT: k9 is **not a contractile verb**. As of ADR-001 (2026-04-18), +k9 has been relocated from this directory to `.machine_readable/svc/k9/` +estate-wide. The `k9/` directory entry in this location is a **signpost only**. -=== Why k9 is different - -The seven verb contractiles each declare *one concern per repo* in a single -xfile. k9 is not a concern; it is the *graded automation surface* that -enforces or validates concern declarations. k9 provides three trust-tier -*templates* that repos copy and instantiate: - -[cols="1,1,3", options="header"] -|=== -| File | Trust tier | Description - -| `../svc/k9/template-kennel.k9.ncl.in` -| Kennel -| Pure data. No subprocess, no filesystem write, no network. Safe for - metadata and declarative settings. - -| `../svc/k9/template-yard.k9.ncl.in` -| Yard -| Nickel evaluation with contracts and validation. No side effects. - -| `../svc/k9/template-hunt.k9.ncl.in` -| Hunt -| Full execution surface. Must declare side effects, support dry-run, and - be signed before the estate treats it as trustworthy automation. -|=== - -The templates moved out of this directory with ADR-001 (k9 is svc, not a -contractile). The `.in` suffix is MIGRATION-1058 M3: a template is not a -component, so it does not carry the reserved `.k9.ncl` suffix. Copy one to -`.k9.ncl` and fill in its `TODO`s to instantiate it. - -=== Why the naming rule does not apply - -The one-verb-one-Verbfile rule exists to enforce clean concern separation. -k9 is meta-infrastructure: it does not have a `K9file.a2ml` because it is -not a declarative xfile — it is a template set that instantiates into -specific repos. Applying the rule would produce a meaningless `K9file.a2ml` -with nothing to declare. - -=== Audit rule - -If a repo claims `k9` enforcement, each k9 component in that repo MUST -declare a `paired_xfile` pointing to a specific contractile xfile (e.g. -`../must/Mustfile.a2ml`). Floating k9 components with no paired xfile are -non-conformant. - -See `k9/README.adoc` for the full k9 security model and usage instructions. -See `docs/CONTRACTILE-SPEC.adoc §k9-exception` for the normative statement. +k9 provides three trust-tier templates (Kennel, Yard, Hunt) that repos copy +and instantiate. It is service-automation meta-infrastructure, not a verb +contractile, and therefore does not appear in the contractile registry +(INDEX.a2ml). See ADR-001 for the relocation rationale and +`.machine_readable/svc/k9/README.adoc` for the full k9 security model. == Fill-In Instructions @@ -130,7 +88,6 @@ When copying this set into a new repo: 6. `Bustfile` — declare real breakage / expiry / hard-stop conditions. 7. `Intentfile` — list tracked next-actions with observable probes ([[intents]] section) AND horizon aspirations ([[wishes]] section). -8. Pair any `k9/*.k9.ncl` with a specific contractile via `paired_xfile`. == Intentfile: Commitments vs Aspirations — Two Sections, One File diff --git a/docs/CONTRACTILE-SPEC.adoc b/docs/CONTRACTILE-SPEC.adoc index 944c7f1d7..96bebae89 100644 --- a/docs/CONTRACTILE-SPEC.adoc +++ b/docs/CONTRACTILE-SPEC.adoc @@ -33,11 +33,10 @@ probes. This spec covers: -* The six contractile verbs, the k9 exception, and their semantics. +* The six contractile verbs and their semantics. * The canonical directory layout and naming rules. * The shared Nickel base (`_base.ncl`) and how verb runners inherit from it. * The `probe` contract (current legacy String form and target structured form). -* The k9 exception — trust-tier template infrastructure, not a verb contractile. * The registry (`INDEX.a2ml`) that lists all verbs. * Test fixture conventions. * CLI binding and invocation. @@ -82,14 +81,14 @@ run-behaviour:: k9:: Service-automation trust-tier infrastructure. Lives at - `.machine_readable/svc/k9/` — NOT inside `1-formats/contractiles/`. See <>. + `.machine_readable/svc/k9/` — NOT inside `1-formats/contractiles/`. See <>. Moved out of `1-formats/contractiles/` per `ADR-001-k9-relocation-to-svc.adoc` (2026-04-18); this spec's v1.1.0 language predates that ADR. == The Verb Set Six verbs are defined, plus `k9`, which is an exception documented in -<> and is not a verb contractile. The table below therefore has +<> and is not a verb contractile. The table below therefore has seven rows: six verbs and one exception. [cols="1,2,5", options="header"] @@ -135,7 +134,7 @@ seven rows: six verbs and one exception. | `k9` *(exception)* | Trust-tier automation templates -| NOT a verb contractile. See <>. +| NOT a verb contractile. See <>. |=== [[directory-layout]] @@ -146,8 +145,8 @@ seven rows: six verbs and one exception. ---- .machine_readable/contractiles/ ├── _base.ncl ← shared base (pedigree + status-core + probe-schema + run-defaults) -├── INDEX.a2ml ← registry of all active verbs (6 + k9 exception) -├── README.adoc ← human overview + k9 exception note +├── INDEX.a2ml ← registry of all active verbs (6) +├── README.adoc ← human overview │ ├── adjust/ │ ├── Adjustfile.a2ml ← declaration (data) @@ -173,13 +172,9 @@ seven rows: six verbs and one exception. │ ├── Trustfile.a2ml │ └── trust.ncl │ -└── k9 → .machine_readable/svc/k9/ ← relocated by ADR-001: k9 is svc, not a verb +└── k9/ ← relocated by ADR-001 to .machine_readable/svc/k9/ estate-wide; signpost only ---- -k9 is deliberately NOT in this tree any more. ADR-001 (accepted 2026-04-18) -moved it to `.machine_readable/svc/k9/` estate-wide: no verb directory, no -exception. The `k9/` entry is kept as a signpost only. - Each verb directory MUST contain exactly: * One `file.a2ml` (capitalised verb, e.g. `Mustfile.a2ml`). @@ -205,7 +200,7 @@ Anything else in a verb directory is human notes or archive; machines ignore it. (see <>). 4. **k9 follows none of the above.** k9 is the explicit exception - (see <>). + (see <>). == Shared Base (`_base.ncl`) @@ -436,8 +431,8 @@ The migration path is: Adopters writing new xfiles should prefer the structured form where possible. -[[k9-exception]] -== K9 Exception +[[k9-relocation]] +== K9 Service-Automation Layer `k9/` is **not a contractile verb**. It is trust-tier template infrastructure that lives at `.machine_readable/svc/k9/`, separate from the verb directories @@ -456,29 +451,23 @@ k9 provides three trust-tier templates that repos copy and instantiate: [cols="1,1,3", options="header"] |=== -| File (in `.machine_readable/svc/k9/`, ADR-001) | Trust tier | What it does +| File | Trust tier | What it does -| `template-kennel.k9.ncl.in` +| `template-kennel.k9.ncl` | Kennel | Pure data, no execution. No subprocess, no filesystem write, no network. Safe for metadata and declarative settings. -| `template-yard.k9.ncl.in` +| `template-yard.k9.ncl` | Yard | Nickel evaluation with contracts; no side effects. Used for validated config. -| `template-hunt.k9.ncl.in` +| `template-hunt.k9.ncl` | Hunt | Full execution surface. Declares side effects, supports dry-run, requires signature before estate-wide trust. |=== -The `.in` suffix is MIGRATION-1058 M3: a template is not a component and is -never loaded, so it does not carry the reserved `.k9.ncl` suffix. Instantiate -by copying to `.k9.ncl` and filling every `TODO`; the copy is then a -full K9 component, bound by the capability grant (§8.4), the `'Hunt` -`side_effects` obligation and the `signature` block (§10.1). - === Why the naming rule does not apply The one-verb-one-Verbfile rule exists to enforce a clean concern separation. @@ -519,12 +508,11 @@ When implemented, place as `.machine_readable/contractiles/contractile-meta.ncl` == Registry (`INDEX.a2ml`) `.machine_readable/contractiles/INDEX.a2ml` is the machine-readable catalogue -of all six verbs plus the k9 exception. It lists: +of all six verbs. It lists: * Verb name and one-line semantics. * The file pair (declaration + runner). -* Active vs exception status. -* Notes for exceptions. +* Active status. * The base schema location. * Meta-schema status. @@ -676,10 +664,10 @@ four classifications were wrong. | *Rival taxonomy* — conflicts | Not adoption notes. v0.1.0 Draft, RFC 2119 language, its own conformance clause. It defines a *five-tier* system — Must, Trust, Dust, *Lust*, *K9* — - against this spec's six verbs plus the k9 exception. Three substantive - divergences: `lust` is live as a tier, though it was deprecated and - absorbed into `intend` on 2026-04-18; `k9` is a peer tier rather than a - documented exception; and S07-3 says declarations "MAY be expressed in any + against this spec's six verbs. Three substantive divergences: `lust` is + live as a tier, though it was deprecated and absorbed into `intend` on + 2026-04-18; `k9` is a peer tier rather than service-automation + infrastructure; and S07-3 says declarations "MAY be expressed in any structured format", against this spec's fixed two-file pattern. Its reference implementation section sites files at `1-formats/contractiles/must/` and `1-formats/contractiles/lust/` — the wrong root, and a plausible source of the 76 @@ -801,7 +789,7 @@ mechanical move rather than a content change. === Cardinality is violated at scale -This spec already states (<>) that each verb declares *one +This spec already states (<>) that each verb declares *one concern per repo*, and the owner's layout ruling states it more strongly: exactly ONE of each verb per repo, no second copies, no variants, no per-subdir duplicates, with `ANCHOR.a2ml` the sole exception. @@ -998,9 +986,8 @@ cat lust/Lustfile.a2ml # extract any real wishes rm -r lust/ ---- -The verb count drops 7 → 6 (plus the k9 exception, unchanged). All tooling -dispatch on `lust` must be removed; consumers should read wishes from -`Intentfile.a2ml` instead. +The verb count drops 7 → 6. All tooling dispatch on `lust` must be removed; +consumers should read wishes from `Intentfile.a2ml` instead. === Intentfile ← Intendfile revert (2026-04-18)