Skip to content

feat(k9): normative Nickel contract, canonical validator, conformance suite (#1058, D173) - #1143

Merged
hyperpolymath merged 5 commits into
mainfrom
arena/01a10407-standards
Oct 4, 2026
Merged

hyperpolymath merged 5 commits into
mainfrom
arena/01a10407-standards

Conversation

@hyperpolymath

Copy link
Copy Markdown
Owner

Implements ruling D173 on #1058: K9 needs a Nickel contract, not an ABNF.

What this adds

1-formats/k9/spec/K9-CONTRACT-SPEC.adoc Normative spec v1.0.0 — envelope, dialects, versioned contract, closed leash set, default-deny capabilities, the five Hunt preconditions, signature semantics, four conformance layers. 23 rule ids.
spec/contract/k9_contract.ncl The machine-readable contract.
tools/k9-validate.sh Canonical validator, layered L0 envelope / L1 structural / L2 Nickel / L3 crypto.
tools/fixtures/ 5 positive, 21 negative controls.
spec/MIGRATION-1058.adoc Measured baseline + M1–M7 + 7 suggested issues.

Deliberately no k9.abnf — a component body is a Nickel term, so a whole-file grammar would be a drifting restatement of a language this estate does not own. The envelope is specified as three octets plus a first-significant-line table.

Two distinctions made load-bearing

  • Presence is not verification (§10.4). The Hunt signature precondition is satisfiable by 'Verified only; 'Present_Unverified is false, and no input turns "no verifier ran" into "verified".
  • A flag is a request that must be paid for (§8.4). allow_network / allow_filesystem_write / allow_subprocess now require net.fetch / fs.write / process.spawn in the grant.

Hunt is not weakened: all five preconditions, always, no configurable subset, no self-granted evidence. Two items make 'Hunt harder to reach, not easier.

Validators aligned

  • .githooks/validate-k9.sh was implementing its own format — it grepped for a line beginning contract, which 0 of 30 tracked K9 files have, so it exited 1 with 30 errors on a clean tree. It now delegates to the canonical validator and owns only commit policy.
  • .githooks/validate-lint-format.sh excluded nothing, so it ran nickel typecheck on *.k9.ncl — which ci-pipeline.yml documents as dying "at 1:3 on the !". Now aligned with CI.
  • k9-contractile.yml installs Nickel pinned + sha256-verified (same pin as ci-pipeline.yml) and runs --self-test, the fixtures --strict, and the corpus.

Verification

Check Result
--self-test 24/24 assertions
--fixtures 5 positive, 19 negative asserted, 0 failures
--strict without nickel exits 3 — refuses to report a conformance result
Local hook, full tree exit 0 — 5 conforming, 25 grandfathered
Old hook, same 30 files exit 1, 30 errors
check-standards-map.sh GATE D PASSED
SPDX gate over fixtures 25/25, and a headerless .ncl elsewhere still fails

L2 (Nickel semantics) had never run in the sandbox where this was written — no nickel binary is obtainable there. This PR's CI run is the first time K9 semantics have been checked in this repository.

Refs #1058 · ruling D173

… suite (#1058, D173)

Ruling D173: "K9 needs a Nickel contract, not an ABNF." Implements it.

Standards
- 1-formats/k9/spec/K9-CONTRACT-SPEC.adoc v1.0.0 — normative. File envelope,
  dialect rules, versioned contract, closed leash set, default-deny capability
  model, the five Hunt preconditions, signature semantics, four conformance
  layers. 23 rule ids, indexed in Appendix A.
- spec/contract/k9_contract.ncl — the machine-readable contract.
- SPEC.adoc — points at the contract as normative; names the component/repo
  pedigree collision instead of leaving two shapes called "pedigree".

Deliberately no k9.abnf: a component body IS a Nickel term, so a whole-file
grammar would be a drifting restatement of a language we do not own. The
envelope is specified as three octets plus a first-significant-line table.

Two distinctions made load-bearing rather than prose
- Presence is not verification (10.4): the Hunt `signature` precondition is
  satisfiable by 'Verified only. 'Present_Unverified is false. No input turns
  "no verifier ran" into "verified".
- A flag is a request that must be paid for (8.4): allow_network/fs_write/
  subprocess now REQUIRE net.fetch/fs.write/process.spawn in the grant. A
  component asking for the network while granting itself nothing is invalid.
Hunt is otherwise unchanged: all five preconditions, always, no subset.

Validators aligned
- tools/k9-validate.sh — canonical. Layered L0 envelope / L1 structural /
  L2 Nickel / L3 crypto, so a lexical check cannot report a higher layer's
  authority. A check that could not run is SKIPPED, never a pass; --strict
  fails the run rather than reporting green over nothing.
- .githooks/validate-k9.sh — was its own format: it grepped for a line
  beginning `contract`, which 0 of 30 tracked K9 files have, so it exited 1
  with 30 errors on a clean tree. Now delegates and owns only commit policy.
- .githooks/validate-lint-format.sh — excludes *.k9.ncl from the bare nickel
  typecheck, matching ci-pipeline.yml (2 staged .ncl in, 1 out).
- k9-contractile.yml — installs Nickel pinned+sha256 (same pin as
  ci-pipeline.yml) and runs --self-test, the fixtures --strict, and the corpus.

Fixtures: 5 positive, 21 negative. Each negative names its rule and layer and
the runner asserts it was rejected BY that rule AT that layer, so a fixture
cannot pass for the wrong reason. 20 of 23 rules have a control.

Migration: spec/MIGRATION-1058.adoc. Baseline measured — 30 tracked K9 files,
5 conforming, 25 not. .machine_readable/k9-contract-debt.txt grandfathers them
shrink-only: fixing a file forces its entry out, and editing a listed file
removes its protection.

Not yet run: L2. No nickel binary is obtainable in the preparation sandbox
(release-asset host TLS-refused, no cargo to build the codeload tarball), so
this commit's L2 result comes from the workflow_dispatch run of the job added
here.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 39 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 5cebfa02-2c2f-40e4-a711-6207797f1ef5
📥 Commits

Reviewing files that changed from the base of the PR and between 5c300a8 and b206161.

📒 Files selected for processing (38)
  • .githooks/validate-k9.sh
  • .githooks/validate-lint-format.sh
  • .githooks/validate-spdx.sh
  • .github/workflows/k9-contractile.yml
  • .machine_readable/k9-contract-debt.txt
  • 1-formats/k9/.gitattributes
  • 1-formats/k9/SPEC.adoc
  • 1-formats/k9/spec/K9-CONTRACT-SPEC.adoc
  • 1-formats/k9/spec/MIGRATION-1058.adoc
  • 1-formats/k9/spec/contract/k9_contract.ncl
  • 1-formats/k9/tools/README.adoc
  • 1-formats/k9/tools/fixtures/invalid/L0-K9-E001-bad-magic.k9.ncl
  • 1-formats/k9/tools/fixtures/invalid/L0-K9-E002-nul-byte.k9.ncl
  • 1-formats/k9/tools/fixtures/invalid/L0-K9-E003-crlf.k9.ncl
  • 1-formats/k9/tools/fixtures/invalid/L0-K9-E004-no-spdx.k9.ncl
  • 1-formats/k9/tools/fixtures/invalid/L0-K9-E005-unclaimed-body.k9.ncl
  • 1-formats/k9/tools/fixtures/invalid/L0-K9-S012-library-with-pedigree.ncl
  • 1-formats/k9/tools/fixtures/invalid/L0-K9-S014-stray-leash.ncl
  • 1-formats/k9/tools/fixtures/invalid/L1-K9-S001-no-pedigree.k9.ncl
  • 1-formats/k9/tools/fixtures/invalid/L1-K9-S002-wrong-major.k9.ncl
  • 1-formats/k9/tools/fixtures/invalid/L1-K9-S003-todo-component-type.k9.ncl
  • 1-formats/k9/tools/fixtures/invalid/L1-K9-S004-unknown-leash.k9.ncl
  • 1-formats/k9/tools/fixtures/invalid/L1-K9-S005-missing-name.k9.ncl
  • 1-formats/k9/tools/fixtures/invalid/L1-K9-S006-unknown-capability.k9.ncl
  • 1-formats/k9/tools/fixtures/invalid/L1-K9-S007-ungranted-flag.k9.ncl
  • 1-formats/k9/tools/fixtures/invalid/L1-K9-S008-hunt-signature-not-required.k9.ncl
  • 1-formats/k9/tools/fixtures/invalid/L1-K9-S009-hunt-no-signature-block.k9.ncl
  • 1-formats/k9/tools/fixtures/invalid/L1-K9-S010-hunt-empty-side-effects.k9.ncl
  • 1-formats/k9/tools/fixtures/invalid/L1-K9-S011-recipes-at-yard.k9.ncl
  • 1-formats/k9/tools/fixtures/invalid/L1-K9-S013-dangling-import.k9.ncl
  • 1-formats/k9/tools/fixtures/invalid/L2-K9-N001-two-segment-version.k9.ncl
  • 1-formats/k9/tools/fixtures/invalid/L2-K9-N001-wrong-field-type.k9.ncl
  • 1-formats/k9/tools/fixtures/valid/extension-capability.k9.ncl
  • 1-formats/k9/tools/fixtures/valid/hunt-fully-granted.k9.ncl
  • 1-formats/k9/tools/fixtures/valid/kennel-data.k9.ncl
  • 1-formats/k9/tools/fixtures/valid/library-base.ncl
  • 1-formats/k9/tools/fixtures/valid/yard-typed-config.k9.ncl
  • 1-formats/k9/tools/k9-validate.sh
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@@ -0,0 +1,9 @@
K9!
arena-agent and others added 2 commits October 4, 2026 01:23
The three K9 steps now tee into $GITHUB_STEP_SUMMARY. Step conclusions were
already readable through the check-run API; the step bodies were not, and the
log blob host is not reachable from every machine that needs the result. The
PR now carries the verdict itself.

No behavioural change to the gates: each step still exits with the validator's
own status via PIPESTATUS[0], and the run scripts drop -e so a failing
validator reaches the tee instead of aborting before it.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
The step summaries written last commit turned out not to be readable: the
check-run API returns an empty output.summary for Actions jobs, and the log
blob hosts are unreachable from the sandbox that needs the result. So the
verdict is now posted to the PR itself, edited in place on re-runs.

Adds pull-requests:write at the job level (the workflow-level grant stays
contents:read) and runs with always(), because a failing gate is exactly when
the detail is needed.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
@github-actions

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

K9 contract conformance

run https://github.com/hyperpolymath/standards/actions/runs/37167980969

K9 contract self-test

== the bash mirrors cannot drift from the normative contract ==
ok   leash_levels mirrors k9_contract.ncl
ok   core_capabilities mirrors k9_contract.ncl
ok   contract_version mirrors k9_contract.ncl
ok   schema_major mirrors k9_contract.ncl
== capability arithmetic (§8) ==
ok   capability_ok fs.read accepted
ok   capability_ok rollback.apply accepted
ok   capability_ok x-acme.gpu.alloc accepted
ok   capability_ok x-acme rejected
ok   capability_ok x-.gpu rejected
ok   capability_ok fs.delete rejected
ok   capability_ok  rejected
== the extractor ==
ok   extracts pedigree.security.leash
ok   extracts pedigree.component_type
ok   extracts pedigree.metadata.name
ok   pedigree leash is not reported as top-level leash
ok   required_capabilities for a quiet component
ok   required_capabilities follows allow_network
== the envelope strip keeps line numbers (§3.6) ==
ok   line 1 becomes a comment
ok   line count is preserved
ok   schema_version stays on line 5
== L3: signature presence is not verification (§10) ==
ok   no verifier -> K9-C001 is SKIPPED, never a pass
ok   the skip states presence does not authorise 'Hunt
ok   verifier accepts -> verdict 'Verified, no K9-C001 finding
ok   verifier refuses -> K9-C001 error, verdict 'Rejected

self-test: all assertions passed

K9 conformance fixtures

== positive controls (must pass) ==
ERROR   K9-N002 [L2] 1-formats/k9/tools/fixtures/valid/extension-capability.k9.ncl: the normative contract does not typecheck: error: unbound identifier `Record`     ┌─ /home/runner/work/standards/standards/1-formats/k9/spec/contract/k9_contract.ncl:416:14     │ 
FAIL extension-capability.k9.ncl should conform (exit 1)
ERROR   K9-N002 [L2] 1-formats/k9/tools/fixtures/valid/hunt-fully-granted.k9.ncl: the normative contract does not typecheck: error: unbound identifier `Record`     ┌─ /home/runner/work/standards/standards/1-formats/k9/spec/contract/k9_contract.ncl:416:14     │ 
SKIPPED K9-C001 [L3] 1-formats/k9/tools/fixtures/valid/hunt-fully-granted.k9.ncl: signature block present but no verifier ran (set K9_SIG_VERIFIER); verdict is 'Present_Unverified, which does NOT authorise 'Hunt
FAIL hunt-fully-granted.k9.ncl should conform (exit 1)
ERROR   K9-N002 [L2] 1-formats/k9/tools/fixtures/valid/kennel-data.k9.ncl: the normative contract does not typecheck: error: unbound identifier `Record`     ┌─ /home/runner/work/standards/standards/1-formats/k9/spec/contract/k9_contract.ncl:416:14     │ 
FAIL kennel-data.k9.ncl should conform (exit 1)
ERROR   K9-N002 [L2] 1-formats/k9/tools/fixtures/valid/library-base.ncl: the normative contract does not typecheck: error: unbound identifier `Record`     ┌─ /home/runner/work/standards/standards/1-formats/k9/spec/contract/k9_contract.ncl:416:14     │ 
FAIL library-base.ncl should conform (exit 1)
ERROR   K9-N002 [L2] 1-formats/k9/tools/fixtures/valid/yard-typed-config.k9.ncl: the normative contract does not typecheck: error: unbound identifier `Record`     ┌─ /home/runner/work/standards/standards/1-formats/k9/spec/contract/k9_contract.ncl:416:14     │ 
FAIL yard-typed-config.k9.ncl should conform (exit 1)

== negative controls (must fail, by the named rule) ==
ok   L0-K9-E001-bad-magic.k9.ncl (rejected by K9-E001 at L0)
ok   L0-K9-E002-nul-byte.k9.ncl (rejected by K9-E002 at L0)
ok   L0-K9-E003-crlf.k9.ncl (rejected by K9-E003 at L0)
ok   L0-K9-E004-no-spdx.k9.ncl (rejected by K9-E004 at L0)
ok   L0-K9-E005-unclaimed-body.k9.ncl (rejected by K9-E005 at L0)
ok   L0-K9-S012-library-with-pedigree.ncl (rejected by K9-S012 at L0)
ok   L0-K9-S014-stray-leash.ncl (rejected by K9-S014 at L0)
ok   L1-K9-S001-no-pedigree.k9.ncl (rejected by K9-S001 at L1)
ok   L1-K9-S002-wrong-major.k9.ncl (rejected by K9-S002 at L1)
ok   L1-K9-S003-todo-component-type.k9.ncl (rejected by K9-S003 at L1)
ok   L1-K9-S004-unknown-leash.k9.ncl (rejected by K9-S004 at L1)
ok   L1-K9-S005-missing-name.k9.ncl (rejected by K9-S005 at L1)
ok   L1-K9-S006-unknown-capability.k9.ncl (rejected by K9-S006 at L1)
ok   L1-K9-S007-ungranted-flag.k9.ncl (rejected by K9-S007 at L1)
ok   L1-K9-S008-hunt-signature-not-required.k9.ncl (rejected by K9-S008 at L1)
ok   L1-K9-S009-hunt-no-signature-block.k9.ncl (rejected by K9-S009 at L1)
ok   L1-K9-S010-hunt-empty-side-effects.k9.ncl (rejected by K9-S010 at L1)
ok   L1-K9-S011-recipes-at-yard.k9.ncl (rejected by K9-S011 at L1)
ok   L1-K9-S013-dangling-import.k9.ncl (rejected by K9-S013 at L1)
ok   L2-K9-N001-two-segment-version.k9.ncl (rejected by K9-N001 at L2)
ok   L2-K9-N001-wrong-field-type.k9.ncl (rejected by K9-N001 at L2)

fixtures: 5 positive, 21 negative (0 needing nickel), 5 failure(s)

# always(): a failing gate is exactly when the detail is needed, and a
# skipped step would publish nothing.
if: ${{ always() && github.event_name == 'pull_request' }}
env:
@hyperpolymath
hyperpolymath marked this pull request as ready for review October 4, 2026 01:28
…uld not fail

The first CI run of this branch produced the first Nickel verdict this corpus
has ever had, and it found two defects — both in this PR's own code.

1. `Record` is not a Nickel type. Component's three open fields named it, so
   k9_contract.ncl failed with `unbound identifier` and every L2 verdict
   downstream was void. All 5 positive controls failed. Now `{ _ : Any }`.

2. Negative-control attribution matched the rule id in the FILENAME. A control
   is named `L2-K9-N001-…`, and the human finding line echoes the path, so
   grepping that output for `K9-N001` succeeded no matter which rule fired.
   With the contract broken, both L2 controls were rejected by K9-N002 and
   both still reported `ok`. The suite exists to catch gates that cannot fire;
   this was one. Attribution now reads the structured findings and requires an
   `error`-severity finding whose rule AND layer both match the filename.

   A broken contract is also called out by name rather than reported as "wrong
   rule": when K9-N002 fires, no L2 control proved anything.

self-test gains a block asserting the predicate itself — a rule id present only
in a path is not attributed, and a skipped finding cannot satisfy a control.
24 assertions become 29. The first draft of that block asserted E001 fires
once; it fires twice (bad magic also leaves the body unclaimed), so the
well-formedness count is compared rather than fixed at 1.

Still unverified locally: L2. No nickel binary is obtainable in this sandbox,
so the fixtures' L2 half is asserted by the workflow run of this commit.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

Autopilot could not be updated. Open Coding to check access and billing.

@hyperpolymath
hyperpolymath enabled auto-merge (squash) October 4, 2026 01:30
@sonarqubecloud

sonarqubecloud Bot commented Oct 4, 2026

Copy link
Copy Markdown

@hyperpolymath
hyperpolymath merged commit b3075e0 into main Oct 4, 2026
44 of 49 checks passed
@hyperpolymath
hyperpolymath deleted the arena/01a10407-standards branch October 4, 2026 01:31
hyperpolymath pushed a commit that referenced this pull request Oct 4, 2026
Two follow-ups to what landed in #1143, both about being able to READ a
failure rather than about the gate:

- A whole-report ::error never reached the check-run API. Annotations are now
  emitted per FAIL/ERROR line, capped at 20, each short enough to survive.
- The publish step tried to PATCH its previous comment and failed, pinning the
  PR to a stale report for two runs. It now always posts; stacked reports are
  cheaper than no result.

The K9 fixtures step is red on main as of b3075e0 and this is the change that
makes the reason legible.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
hyperpolymath added a commit that referenced this pull request Oct 4, 2026
Follow-up to #1143. The K9 fixtures step is **red on `main` as of
`b3075e0`** and nothing about that is currently readable: the log blob
host is unreachable from the sandbox, the step summary is not exposed by
the check-run API, and the report comment was pinned to a stale run
because the publish step failed trying to PATCH it.

This PR changes no gate. It changes only whether a failure can be read.

- **One annotation per failure.** A whole-report `::error` never reached
`check-runs/{id}/annotations`. Annotations are now emitted per
`FAIL`/`ERROR` line (capped at 20), each short enough to survive the
parser.
- **Always post the report.** The publish step's PATCH branch failed and
left the PR showing the report from two runs earlier. It now always
creates a comment; each names its run.

## Why this matters beyond convenience

The first CI run of #1143 was the **first time any tool in this estate
ran Nickel over a K9 file**, and it immediately found two defects in
that PR's own code:

1. `k9_contract.ncl` named a `Record` type that does not exist in
Nickel. The contract failed with `unbound identifier`, so every L2
verdict downstream was void and all 5 positive controls failed.
2. Negative-control attribution grepped the human-readable finding line,
which echoes the **file path** — and a control is named `L2-K9-N001-…`.
So the assertion "rejected by K9-N001" succeeded no matter which rule
fired. With the contract broken, both L2 controls were rejected by
`K9-N002` and both still printed `ok`. A gate that cannot fire is the
defect class #49 and #64 established; this suite was built to prevent it
and contained one.

Both are fixed in #1143. What is *not* yet established is whether the
fixtures pass at L2 with the contract repaired — the step is still red,
and this PR is how that gets read.

`self-test` now asserts the attribution predicate itself (29
assertions): a rule id present only in a filename is not attributed, and
a skipped finding cannot satisfy a control.

---------

Signed-off-by: Jonathan D.A. Jewell <6759885+hyperpolymath@users.noreply.github.com>
Co-authored-by: arena-agent <arena-agent@users.noreply.github.com>
Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
hyperpolymath added a commit that referenced this pull request Oct 4, 2026
, D173) (#1145)

**`K9-SVC contractile validation` is green.** Run
[`37169542241`](https://github.com/hyperpolymath/standards/actions/runs/37169542241),
reconfirmed on `76b841e`:

```
K9 normative contract typecheck  ok
K9 contract self-test            ok   30 assertions
K9 conformance fixtures          ok   5 positive, 21 negative, 0 failures
K9 corpus conformance            ok   5 conforming, 25 grandfathered
```

This is the first time any tool in this estate has run Nickel over a K9
file. It found **seven defects, every one of them in #1143's own code**,
and not one was visible without a `nickel` binary — which is not
obtainable in the sandbox where this was written.

| Defect | Effect |
|---|---|
| `Record` used as a type | Not a Nickel type. The contract did not
typecheck, so every L2 verdict was void and all five positive controls
failed. |
| `Any` used as its replacement | Also not a type; the dynamic type is
`Dyn`. Same failure, one run later. |
| `let doc = …` in the L2 driver | `doc` is a Nickel keyword (metadata,
`x \| doc "…"`). Parse error at the identifier. |
| `default = { … }` in two fixtures | `default` is the default-value
marker keyword. Nickel's `Ident` admits only `or`, `as`, `include`. |
| **L2 ran only `nickel typecheck`** | Documented as *"typechecks the
program but does not run it"*. Contracts apply when a value flows, so
the predicate behind `schema_version` never fired and **both L2 negative
controls were accepted**. |
| **Attribution grepped free text** | A control's filename contains its
rule id, so "rejected by K9-N001" matched the *path* and succeeded no
matter which rule fired — a gate that could not fail, in the suite built
to prevent them. |
| `std.string.substring i 1 s` | 1.18.0's `substring` is `(start, end,
str)`, not a length. It returned `""` past index 0, so `is_semver_of`
rejected `"1.0.0"` and every component control with it. |

Four are the same lesson: the contract and driver were written against
Nickel as *remembered* rather than as *shipped*. Three are gates that
reported success without doing the work.

## What changed here

- `{ _ : Any }` → `{ _ : Dyn }`; `doc` → `k9_doc`; `default` →
`default_recipe`.
- **L2 now evaluates** (`nickel export --format json`) as well as
typechecks — spec §12.2.
- Attribution reads structured findings and requires an `error` whose
rule **and** layer match.
- A `K9 normative contract typecheck` step ahead of the fixtures, so a
broken contract reports as a broken contract instead of five
"non-conforming" files.
- Failures publish as per-line annotations and a PR comment, because the
log blob host is unreachable from here.
- self-test: 24 → **30 assertions**, including a Nickel-keyword guard
over the contract and all 26 fixtures (verified in both directions:
planting `doc = "planted"` turns it red).

Ground truth came from the 1.18.0 source tarball over `codeload`, which
*is* reachable: `parser/src/lexer.rs` for the keyword list and the
`Ident` production, `core/stdlib/std.ncl` for all nine `std` signatures
this contract uses.

## Still open

The corpus step is pinned to `--layer L1` so it can run in a pre-commit
hook, so the **25 grandfathered files have never been through Nickel**.
`nickel format --check` has never run on a `.k9.ncl` body anywhere. Both
are M7 in `spec/MIGRATION-1058.adoc`, which now records measured results
instead of predictions.

---------

Co-authored-by: arena-agent <arena-agent@users.noreply.github.com>
Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants