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/5] 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/5] 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/5] 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/5] 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) From e2df0492bd329111d0b9de0b8d6fd7a48050a473 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Mon, 5 Oct 2026 11:23:56 +0100 Subject: [PATCH 5/5] fix(k9): update ADR-001 tree to reflect .in suffix for templates Amendment 2026-10-05 (standards#1058, MIGRATION-1058 M3) added note that templates now carry the .in suffix, but tree diagram was not updated. This commit updates the tree to match the amendment. Fixes CodeRabbit review comment on PR #1148. SPDX-License-Identifier: CC-BY-SA-4.0 Copyright (c) Jonathan D.A. Jewell --- docs/ADR-001-k9-relocation-to-svc.adoc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/ADR-001-k9-relocation-to-svc.adoc b/docs/ADR-001-k9-relocation-to-svc.adoc index 4cf045954..888c85427 100644 --- a/docs/ADR-001-k9-relocation-to-svc.adoc +++ b/docs/ADR-001-k9-relocation-to-svc.adoc @@ -67,9 +67,9 @@ k9 lives at `.machine_readable/svc/k9/` in every repo. The └── svc/ └── k9/ ├── README.adoc - ├── template-kennel.k9.ncl - ├── template-yard.k9.ncl - ├── template-hunt.k9.ncl + ├── template-kennel.k9.ncl.in + ├── template-yard.k9.ncl.in + ├── template-hunt.k9.ncl.in └── examples/ ├── ci-config.k9.ncl ├── project-metadata.k9.ncl @@ -82,8 +82,8 @@ 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. -==== +suffix. +==== `svc/` is a **directory of named service-automation subsystems**. Today it holds only `k9/`. Future service-layer infrastructure (watchers,