diff --git a/.machine_readable/contractiles/adjust/examples/invalid/empty_array.a2ml b/.machine_readable/contractiles/adjust/examples/invalid/empty_array.a2ml new file mode 100644 index 0000000..b849968 --- /dev/null +++ b/.machine_readable/contractiles/adjust/examples/invalid/empty_array.a2ml @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: MPL-2.0 +# Fixture: adjust/examples/invalid/empty_array.a2ml — contractile conformance corpus (INVALID) +# Expected: `contractile adjust typecheck adjust/examples/invalid/empty_array.a2ml` → exit 3 +# Valid only under RFC-0001 O5 option (a); see conformance/manifest.a2ml +# Reason: the primary array (`requirements`) is empty + +## Requirements + +### (no items declared) diff --git a/.machine_readable/contractiles/adjust/examples/invalid/missing_id.a2ml b/.machine_readable/contractiles/adjust/examples/invalid/missing_id.a2ml new file mode 100644 index 0000000..94a6fb6 --- /dev/null +++ b/.machine_readable/contractiles/adjust/examples/invalid/missing_id.a2ml @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: MPL-2.0 +# Fixture: adjust/examples/invalid/missing_id.a2ml — contractile conformance corpus (INVALID) +# Expected: `contractile adjust typecheck adjust/examples/invalid/missing_id.a2ml` → exit 3 +# Valid only under RFC-0001 O5 option (a); see conformance/manifest.a2ml +# Reason: the requirement has no `### ` heading + +## Requirements + +- description: requirement with no id heading +- tolerance: zero violations +- corrective: Fix by hand +- severity: advisory diff --git a/.machine_readable/contractiles/adjust/examples/invalid/wrong_status.a2ml b/.machine_readable/contractiles/adjust/examples/invalid/wrong_status.a2ml new file mode 100644 index 0000000..5f80429 --- /dev/null +++ b/.machine_readable/contractiles/adjust/examples/invalid/wrong_status.a2ml @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: MPL-2.0 +# Fixture: adjust/examples/invalid/wrong_status.a2ml — contractile conformance corpus (INVALID) +# Expected: `contractile adjust typecheck adjust/examples/invalid/wrong_status.a2ml` → exit 3 +# Valid only under RFC-0001 O5 option (a); see conformance/manifest.a2ml +# Reason: `status: mostly` is outside {declared, partial, verified, failing} + +## Requirements + +### doc-language +- description: Documentation uses inclusive language +- tolerance: no new violations +- corrective: Review and rewrite flagged passages +- severity: advisory +- status: mostly diff --git a/.machine_readable/contractiles/adjust/examples/valid.a2ml b/.machine_readable/contractiles/adjust/examples/valid.a2ml new file mode 100644 index 0000000..153f5f7 --- /dev/null +++ b/.machine_readable/contractiles/adjust/examples/valid.a2ml @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: MPL-2.0 +# Fixture: adjust/examples/valid.a2ml — contractile conformance corpus (VALID) +# Expected: `contractile adjust typecheck adjust/examples/valid.a2ml` → exit 0 +# Valid only under RFC-0001 O5 option (a); see conformance/manifest.a2ml + +## Requirements + +### doc-language +- description: Documentation uses inclusive language +- tolerance: no new violations in changed passages +- corrective: Review and rewrite flagged passages +- severity: advisory diff --git a/.machine_readable/contractiles/bust/examples/invalid/empty_array.a2ml b/.machine_readable/contractiles/bust/examples/invalid/empty_array.a2ml new file mode 100644 index 0000000..10c2d26 --- /dev/null +++ b/.machine_readable/contractiles/bust/examples/invalid/empty_array.a2ml @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: MPL-2.0 +# Fixture: bust/examples/invalid/empty_array.a2ml — contractile conformance corpus (INVALID) +# Expected: `contractile bust typecheck bust/examples/invalid/empty_array.a2ml` → exit 3 +# Valid only under RFC-0001 O5 option (a); see conformance/manifest.a2ml +# Reason: the primary array (`failure_modes`) is empty + +## Failure Modes + +### (no items declared) diff --git a/.machine_readable/contractiles/bust/examples/invalid/missing_id.a2ml b/.machine_readable/contractiles/bust/examples/invalid/missing_id.a2ml new file mode 100644 index 0000000..0e6b2c7 --- /dev/null +++ b/.machine_readable/contractiles/bust/examples/invalid/missing_id.a2ml @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: MPL-2.0 +# Fixture: bust/examples/invalid/missing_id.a2ml — contractile conformance corpus (INVALID) +# Expected: `contractile bust typecheck bust/examples/invalid/missing_id.a2ml` → exit 3 +# Valid only under RFC-0001 O5 option (a); see conformance/manifest.a2ml +# Reason: the failure mode has no `### ` heading + +## Failure Modes + +- class: timeout +- description: failure mode with no id heading +- injection_probe: sleep 1 +- recovery_probe: test -f /dev/null +- expected_recovery_time_seconds: 5 +- status: declared diff --git a/.machine_readable/contractiles/bust/examples/invalid/wrong_status.a2ml b/.machine_readable/contractiles/bust/examples/invalid/wrong_status.a2ml new file mode 100644 index 0000000..b3b5d26 --- /dev/null +++ b/.machine_readable/contractiles/bust/examples/invalid/wrong_status.a2ml @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: MPL-2.0 +# Fixture: bust/examples/invalid/wrong_status.a2ml — contractile conformance corpus (INVALID) +# Expected: `contractile bust typecheck bust/examples/invalid/wrong_status.a2ml` → exit 3 +# Valid only under RFC-0001 O5 option (a); see conformance/manifest.a2ml +# Reason: `status: broken` is outside {declared, drilled, verified, failing} + +## Failure Modes + +### timeout-handled +- class: timeout +- description: Timeouts are bounded +- injection_probe: sleep 1 +- recovery_probe: test -f /dev/null +- expected_recovery_time_seconds: 5 +- status: broken diff --git a/.machine_readable/contractiles/bust/examples/valid.a2ml b/.machine_readable/contractiles/bust/examples/valid.a2ml new file mode 100644 index 0000000..d43e8c8 --- /dev/null +++ b/.machine_readable/contractiles/bust/examples/valid.a2ml @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: MPL-2.0 +# Fixture: bust/examples/valid.a2ml — contractile conformance corpus (VALID) +# Expected: `contractile bust typecheck bust/examples/valid.a2ml` → exit 0 +# Valid only under RFC-0001 O5 option (a); see conformance/manifest.a2ml + +## Failure Modes + +### disk-full-handled +- class: disk_full +- description: A full disk is detected and reported without corrupting state +- injection_probe: dd if=/dev/zero of=/tmp/fill bs=1M count=1 status=none +- recovery_probe: test -f /tmp/fill && rm -f /tmp/fill +- expected_recovery_time_seconds: 30 +- status: declared diff --git a/.machine_readable/contractiles/conformance/README.adoc b/.machine_readable/contractiles/conformance/README.adoc new file mode 100644 index 0000000..f0c32a0 --- /dev/null +++ b/.machine_readable/contractiles/conformance/README.adoc @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) 2026 Jonathan D.A. Jewell += Contractile Conformance Corpus + +Fixtures and manifest for `contractile self-test` +(`RFC-0001-contractile-cli.adoc` §Conformance tests, phase P1). + +== Layout + +[source] +---- +.machine_readable/contractiles/ +├── conformance/ +│ ├── README.adoc ← this file +│ └── manifest.a2ml ← registered fixtures: expect + reason per file +├── must/examples/ +│ ├── valid.a2ml ← MUST typecheck → exit 0 +│ └── invalid/ +│ ├── missing_id.a2ml ← MUST fail → exit 3 +│ ├── wrong_status.a2ml ← MUST fail → exit 3 +│ └── empty_array.a2ml ← MUST fail → exit 3 +└── … (same shape for trust, adjust, dust, bust, intend) +---- + +Fixtures live in the verb directories because that is where +`standards/docs/CONTRACTILE-SPEC.adoc` §Test Fixtures puts them. The manifest +lives one level up so a single file registers the whole corpus, following +`hyperpolymath/k9-ecosystem/conformance/manifest.a2ml`. + +NOTE: `must/`, `trust/`, `adjust/` and `intend/` now exist as verb directories +holding *only* `examples/`. Their declarations are still at the pre-spec flat +paths. That is deliberate: it makes the corpus spec-located without pretending +the layout migration (spec Ruling 2 and this repository's own drift) is done. + +== Status: provisional, and why that is honest + +Every fixture uses the *deployed* declaration vocabulary (`- run:`, `- probe:`, +`- tolerance:`/`- corrective:`, `- injection_probe:`), not the vocabulary the +Nickel runner schemas expect (`probe`, `target`, `reason`, …). That is a +deliberate bet recorded in the manifest: + +[quote] +____ +assumes = "RFC-0001 O5 option (a): the CLI normalises deployed declaration +fields onto the runner schemas. If O5 resolves to (b) or (c), this corpus must +be rewritten (24 invalid + 6 valid fixtures)." +____ + +If O5 resolves to "migrate the declarations" (option b), these fixtures become +wrong on purpose and are rewritten in the same PR that migrates the six +declarations. Until then they pin the *actual* corpus, which is the only corpus +whose pass/fail the estate can act on. + +== What `self-test` must do with this corpus (P1 acceptance) + +. Read `conformance/manifest.a2ml`; an unregistered fixture fails the run. +. `[[valid]]` entries: exit `0` and a per-item verdict line. +. `[[invalid]]` entries: exit `3` (input error), for the recorded `reason`. + Asserting only "non-zero" is not conformance — the reason must match. +. `[[known_gaps]]` entries: the CLI must *report* the gap (warning, or error + under `--strict`) rather than silently pass or crash. +. Two consecutive runs produce byte-identical reports (RFC-0001 §Determinism). + +== Known gaps (measured, not invented) + +See `manifest.a2ml` `[[known_gaps]]`. Summary: `adjust` has no discharge field +at all; `dust` declarations lack the schema-required `target`/`reason`; `bust` +`class` values fall outside the runner enum; `adjust` uses +`severity: advisory`, outside `severity_core`; `intend` wishes are depth-4 +headings under a depth-3 horizon group, so a flat heading scan mis-parses them; +and every schema's `id` is carried by the heading, never a field. + +== Not yet here (P1 additions) + +Per RFC-0001 §Conformance tests, phase P1 adds: `unknown_field`, `no_discharge`, +`malformed_utf8`, `escaped_path` (import escaping the repo root), permission +refusal cases (exit `4`), timeout cases (exit `6`), manifest-tamper cases +(exit `7`), and golden outputs for `text|json|a2ml`. + +== Guardrails + +* Nothing in this corpus is executed today: probes are strings in data files. + The fixtures use inert commands (`test -f`, `ls`) so that a future accidental + execution is harmless — except `bust/examples/valid.a2ml`, whose + `injection_probe` writes a 1 MiB file in `/tmp` by design; it is listed here + so that is not a surprise at P4. +* `.github/hooks/validate-a2ml.sh` validates these files in CI (structure, SPDX, + headings). Contractile-shaped files are exempt from the manifest identity + checks by design. diff --git a/.machine_readable/contractiles/conformance/manifest.a2ml b/.machine_readable/contractiles/conformance/manifest.a2ml new file mode 100644 index 0000000..3a69bf5 --- /dev/null +++ b/.machine_readable/contractiles/conformance/manifest.a2ml @@ -0,0 +1,217 @@ +# SPDX-License-Identifier: MPL-2.0 +# conformance/manifest.a2ml — contractile conformance corpus manifest +# Consumed by `contractile self-test` (RFC-0001 §Conformance tests, phase P1). +# Form follows hyperpolymath/k9-ecosystem conformance/manifest.a2ml: +# name/version/updated + per-fixture expect and reason. + +--- +id = "contractiles-conformance-corpus" +version = "0.1.0" +updated = "2026-10-03" +status = "provisional" +assumes = "RFC-0001 O5 option (a): the CLI normalises deployed declaration fields onto the runner schemas. If O5 resolves to (b) or (c), this corpus must be rewritten (24 invalid + 6 valid fixtures)." +fixtures_root = ".machine_readable/contractiles//examples/" +spec = "standards/docs/CONTRACTILE-SPEC.adoc §Test Fixtures" + +## Valid fixtures — must typecheck, exit 0 + +[[valid]] +verb = "must" +path = "must/examples/valid.a2ml" +expect = "pass" +exit_code = 0 +primary_array = "invariants" + +[[valid]] +verb = "trust" +path = "trust/examples/valid.a2ml" +expect = "pass" +exit_code = 0 +primary_array = "verifications" + +[[valid]] +verb = "adjust" +path = "adjust/examples/valid.a2ml" +expect = "pass" +exit_code = 0 +primary_array = "requirements" + +[[valid]] +verb = "dust" +path = "dust/examples/valid.a2ml" +expect = "pass" +exit_code = 0 +primary_array = "removal_candidates" + +[[valid]] +verb = "bust" +path = "bust/examples/valid.a2ml" +expect = "pass" +exit_code = 0 +primary_array = "failure_modes" + +[[valid]] +verb = "intend" +path = "intend/examples/valid.a2ml" +expect = "pass" +exit_code = 0 +primary_array = "intents" + +## Invalid fixtures — must fail, exit 3 (input error) + +[[invalid]] +verb = "must" +path = "must/examples/invalid/missing_id.a2ml" +expect = "error" +exit_code = 3 +reason = "the item has no `### ` heading" + +[[invalid]] +verb = "must" +path = "must/examples/invalid/wrong_status.a2ml" +expect = "error" +exit_code = 3 +reason = "`status: drifted` is outside the must enum {declared, verified, failing}" + +[[invalid]] +verb = "must" +path = "must/examples/invalid/empty_array.a2ml" +expect = "error" +exit_code = 3 +reason = "the primary array (`invariants`) is empty — validator-level rule, not a Nickel type error" + +[[invalid]] +verb = "trust" +path = "trust/examples/invalid/missing_id.a2ml" +expect = "error" +exit_code = 3 +reason = "the item has no `### ` heading" + +[[invalid]] +verb = "trust" +path = "trust/examples/invalid/wrong_status.a2ml" +expect = "error" +exit_code = 3 +reason = "`status: passing` is outside {declared, verified, failing}" + +[[invalid]] +verb = "trust" +path = "trust/examples/invalid/empty_array.a2ml" +expect = "error" +exit_code = 3 +reason = "the primary array (`verifications`) is empty" + +[[invalid]] +verb = "adjust" +path = "adjust/examples/invalid/missing_id.a2ml" +expect = "error" +exit_code = 3 +reason = "the requirement has no `### ` heading" + +[[invalid]] +verb = "adjust" +path = "adjust/examples/invalid/wrong_status.a2ml" +expect = "error" +exit_code = 3 +reason = "`status: mostly` is outside {declared, partial, verified, failing}" + +[[invalid]] +verb = "adjust" +path = "adjust/examples/invalid/empty_array.a2ml" +expect = "error" +exit_code = 3 +reason = "the primary array (`requirements`) is empty" + +[[invalid]] +verb = "dust" +path = "dust/examples/invalid/missing_id.a2ml" +expect = "error" +exit_code = 3 +reason = "the candidate has no `### ` heading" + +[[invalid]] +verb = "dust" +path = "dust/examples/invalid/wrong_status.a2ml" +expect = "error" +exit_code = 3 +reason = "`status: deleted` is outside {declared, proposed, approved, removed}" + +[[invalid]] +verb = "dust" +path = "dust/examples/invalid/empty_array.a2ml" +expect = "error" +exit_code = 3 +reason = "the primary array (`removal_candidates`) is empty" + +[[invalid]] +verb = "bust" +path = "bust/examples/invalid/missing_id.a2ml" +expect = "error" +exit_code = 3 +reason = "the failure mode has no `### ` heading" + +[[invalid]] +verb = "bust" +path = "bust/examples/invalid/wrong_status.a2ml" +expect = "error" +exit_code = 3 +reason = "`status: broken` is outside {declared, drilled, verified, failing}" + +[[invalid]] +verb = "bust" +path = "bust/examples/invalid/empty_array.a2ml" +expect = "error" +exit_code = 3 +reason = "the primary array (`failure_modes`) is empty" + +[[invalid]] +verb = "intend" +path = "intend/examples/invalid/missing_id.a2ml" +expect = "error" +exit_code = 3 +reason = "the wish has no `#### ` heading (wishes are depth-4 under a horizon group)" + +[[invalid]] +verb = "intend" +path = "intend/examples/invalid/wrong_status.a2ml" +expect = "error" +exit_code = 3 +reason = "`status: someday` is outside the intent enum {declared, in_progress, done, deferred, retired}" + +[[invalid]] +verb = "intend" +path = "intend/examples/invalid/empty_array.a2ml" +expect = "error" +exit_code = 3 +reason = "the primary array (`intents`) is empty; wishes alone do not satisfy `intents`" + +## Known gaps in the canonical corpus (measured 2026-10-03) + +# These are NOT fixture defects: they are places where the deployed +# declarations cannot satisfy the deployed runner schemas. They are listed +# here so `self-test` can assert the CLI reports them rather than hiding them. + +[[known_gaps]] +id = "adjust-has-no-discharge-field" +detail = "Deployed Adjustfiles carry `- tolerance:`/`- corrective:` and no `- run:`/`- probe:`. adjust.ncl requires `probe | String`. No deployed adjust declaration can typecheck until O5 rules on what an adjust discharge is." + +[[known_gaps]] +id = "dust-missing-target-reason" +detail = "dust.ncl requires `target` and `reason`; no deployed Dustfile declares either. Until migrated, the CLI must treat them as unmeasured, not invalid." + +[[known_gaps]] +id = "bust-class-enum-mismatch" +detail = "bust.ncl's class enum is {network, disk_full, oom, timeout, partial_write, panic, crash, rollback, concurrency}. Deployed Bustfiles use template_processing, synchronization, contractile_format." + +[[known_gaps]] +id = "adjust-severity-outside-severity-core" +detail = "Deployed Adjustfiles use `- severity: advisory`, which is outside severity_core {critical, high, medium, low}." + +[[known_gaps]] +id = "intend-heading-depth" +detail = "In intend/Intentfile.a2ml, intents are `### ` under `## Committed Next-Actions`, while wishes are `#### ` under a `### ` group heading. The binding table must state the depth rule; a flat heading scan mis-parses wishes." + +[[known_gaps]] +id = "id-from-heading" +detail = "Every runner schema requires `id` as a field; no deployed declaration carries one. Identity is the heading text." + diff --git a/.machine_readable/contractiles/dust/examples/invalid/empty_array.a2ml b/.machine_readable/contractiles/dust/examples/invalid/empty_array.a2ml new file mode 100644 index 0000000..9a964a6 --- /dev/null +++ b/.machine_readable/contractiles/dust/examples/invalid/empty_array.a2ml @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: MPL-2.0 +# Fixture: dust/examples/invalid/empty_array.a2ml — contractile conformance corpus (INVALID) +# Expected: `contractile dust typecheck dust/examples/invalid/empty_array.a2ml` → exit 3 +# Valid only under RFC-0001 O5 option (a); see conformance/manifest.a2ml +# Reason: the primary array (`removal_candidates`) is empty + +## Stale Files + +### (no items declared) diff --git a/.machine_readable/contractiles/dust/examples/invalid/missing_id.a2ml b/.machine_readable/contractiles/dust/examples/invalid/missing_id.a2ml new file mode 100644 index 0000000..d7dd1aa --- /dev/null +++ b/.machine_readable/contractiles/dust/examples/invalid/missing_id.a2ml @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: MPL-2.0 +# Fixture: dust/examples/invalid/missing_id.a2ml — contractile conformance corpus (INVALID) +# Expected: `contractile dust typecheck dust/examples/invalid/missing_id.a2ml` → exit 3 +# Valid only under RFC-0001 O5 option (a); see conformance/manifest.a2ml +# Reason: the candidate has no `### ` heading + +## Stale Files + +- description: removal candidate with no id heading +- run: test -f obsolete.txt +- severity: warning diff --git a/.machine_readable/contractiles/dust/examples/invalid/wrong_status.a2ml b/.machine_readable/contractiles/dust/examples/invalid/wrong_status.a2ml new file mode 100644 index 0000000..ec99c4e --- /dev/null +++ b/.machine_readable/contractiles/dust/examples/invalid/wrong_status.a2ml @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: MPL-2.0 +# Fixture: dust/examples/invalid/wrong_status.a2ml — contractile conformance corpus (INVALID) +# Expected: `contractile dust typecheck dust/examples/invalid/wrong_status.a2ml` → exit 3 +# Valid only under RFC-0001 O5 option (a); see conformance/manifest.a2ml +# Reason: `status: deleted` is outside {declared, proposed, approved, removed} + +## Stale Files + +### no-template-artifacts +- description: No generated files from template testing in root +- run: test -z "$(ls template-test-* 2>/dev/null)" +- severity: warning +- status: deleted diff --git a/.machine_readable/contractiles/dust/examples/valid.a2ml b/.machine_readable/contractiles/dust/examples/valid.a2ml new file mode 100644 index 0000000..f9ccdd3 --- /dev/null +++ b/.machine_readable/contractiles/dust/examples/valid.a2ml @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: MPL-2.0 +# Fixture: dust/examples/valid.a2ml — contractile conformance corpus (VALID) +# Expected: `contractile dust typecheck dust/examples/valid.a2ml` → exit 0 +# Valid only under RFC-0001 O5 option (a); see conformance/manifest.a2ml + +## Stale Files + +### no-template-artifacts +- description: No generated files from template testing in root +- run: test -z "$(ls template-test-* 2>/dev/null)" +- severity: warning diff --git a/.machine_readable/contractiles/intend/examples/invalid/empty_array.a2ml b/.machine_readable/contractiles/intend/examples/invalid/empty_array.a2ml new file mode 100644 index 0000000..06f2001 --- /dev/null +++ b/.machine_readable/contractiles/intend/examples/invalid/empty_array.a2ml @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: MPL-2.0 +# Fixture: intend/examples/invalid/empty_array.a2ml — contractile conformance corpus (INVALID) +# Expected: `contractile intend typecheck intend/examples/invalid/empty_array.a2ml` → exit 3 +# Valid only under RFC-0001 O5 option (a); see conformance/manifest.a2ml +# Reason: the primary array (`intents`) is empty; wishes alone do not satisfy `intents` + +## Committed Next-Actions + +### (no intents declared) diff --git a/.machine_readable/contractiles/intend/examples/invalid/missing_id.a2ml b/.machine_readable/contractiles/intend/examples/invalid/missing_id.a2ml new file mode 100644 index 0000000..cc08ae0 --- /dev/null +++ b/.machine_readable/contractiles/intend/examples/invalid/missing_id.a2ml @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: MPL-2.0 +# Fixture: intend/examples/invalid/missing_id.a2ml — contractile conformance corpus (INVALID) +# Expected: `contractile intend typecheck intend/examples/invalid/missing_id.a2ml` → exit 3 +# Valid only under RFC-0001 O5 option (a); see conformance/manifest.a2ml +# Reason: the wish has no `#### ` heading (wishes are depth-4 under a horizon group) + +## Wishes + +### Near Horizon + +- description: wish with no id heading +- horizon: near +- status: declared diff --git a/.machine_readable/contractiles/intend/examples/invalid/wrong_status.a2ml b/.machine_readable/contractiles/intend/examples/invalid/wrong_status.a2ml new file mode 100644 index 0000000..46ecaa5 --- /dev/null +++ b/.machine_readable/contractiles/intend/examples/invalid/wrong_status.a2ml @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: MPL-2.0 +# Fixture: intend/examples/invalid/wrong_status.a2ml — contractile conformance corpus (INVALID) +# Expected: `contractile intend typecheck intend/examples/invalid/wrong_status.a2ml` → exit 3 +# Valid only under RFC-0001 O5 option (a); see conformance/manifest.a2ml +# Reason: `status: someday` is outside the intent enum {declared, in_progress, done, deferred, retired} + +## Committed Next-Actions + +### docs-page +- description: Publish the CLI RFC +- probe: test -f docs/proposals/RFC-0001-contractile-cli.adoc +- status: someday diff --git a/.machine_readable/contractiles/intend/examples/valid.a2ml b/.machine_readable/contractiles/intend/examples/valid.a2ml new file mode 100644 index 0000000..119f5cb --- /dev/null +++ b/.machine_readable/contractiles/intend/examples/valid.a2ml @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: MPL-2.0 +# Fixture: intend/examples/valid.a2ml — contractile conformance corpus (VALID) +# Expected: `contractile intend typecheck intend/examples/valid.a2ml` → exit 0 +# Valid only under RFC-0001 O5 option (a); see conformance/manifest.a2ml + +## Committed Next-Actions + +### docs-page +- description: Publish the CLI RFC +- probe: test -f docs/proposals/RFC-0001-contractile-cli.adoc +- status: in_progress + +## Wishes + +### Near Horizon + +#### cross-repo-validation +- description: Tooling to validate all repos against the spec +- horizon: near +- status: declared diff --git a/.machine_readable/contractiles/must/examples/invalid/empty_array.a2ml b/.machine_readable/contractiles/must/examples/invalid/empty_array.a2ml new file mode 100644 index 0000000..a4e9b6e --- /dev/null +++ b/.machine_readable/contractiles/must/examples/invalid/empty_array.a2ml @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: MPL-2.0 +# Fixture: must/examples/invalid/empty_array.a2ml — contractile conformance corpus (INVALID) +# Expected: `contractile must typecheck must/examples/invalid/empty_array.a2ml` → exit 3 +# Valid only under RFC-0001 O5 option (a); see conformance/manifest.a2ml +# Reason: the primary array (`invariants`) is empty — validator-level rule, not a Nickel type error + +## File Presence + +### (no items declared) diff --git a/.machine_readable/contractiles/must/examples/invalid/missing_id.a2ml b/.machine_readable/contractiles/must/examples/invalid/missing_id.a2ml new file mode 100644 index 0000000..d05f03a --- /dev/null +++ b/.machine_readable/contractiles/must/examples/invalid/missing_id.a2ml @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: MPL-2.0 +# Fixture: must/examples/invalid/missing_id.a2ml — contractile conformance corpus (INVALID) +# Expected: `contractile must typecheck must/examples/invalid/missing_id.a2ml` → exit 3 +# Valid only under RFC-0001 O5 option (a); see conformance/manifest.a2ml +# Reason: the item has no `### ` heading + +## File Presence + +- description: this block carries fields but no id heading +- run: test -f LICENSE +- severity: critical diff --git a/.machine_readable/contractiles/must/examples/invalid/wrong_status.a2ml b/.machine_readable/contractiles/must/examples/invalid/wrong_status.a2ml new file mode 100644 index 0000000..a4fa6c0 --- /dev/null +++ b/.machine_readable/contractiles/must/examples/invalid/wrong_status.a2ml @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: MPL-2.0 +# Fixture: must/examples/invalid/wrong_status.a2ml — contractile conformance corpus (INVALID) +# Expected: `contractile must typecheck must/examples/invalid/wrong_status.a2ml` → exit 3 +# Valid only under RFC-0001 O5 option (a); see conformance/manifest.a2ml +# Reason: `status: drifted` is outside the must enum {declared, verified, failing} + +## File Presence + +### license-present +- description: LICENSE file must exist +- run: test -f LICENSE +- severity: critical +- status: drifted diff --git a/.machine_readable/contractiles/must/examples/valid.a2ml b/.machine_readable/contractiles/must/examples/valid.a2ml new file mode 100644 index 0000000..304f817 --- /dev/null +++ b/.machine_readable/contractiles/must/examples/valid.a2ml @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: MPL-2.0 +# Fixture: must/examples/valid.a2ml — contractile conformance corpus (VALID) +# Expected: `contractile must typecheck must/examples/valid.a2ml` → exit 0 +# Valid only under RFC-0001 O5 option (a); see conformance/manifest.a2ml + +## File Presence + +### license-present +- description: LICENSE file must exist +- run: test -f LICENSE +- severity: critical diff --git a/.machine_readable/contractiles/trust/examples/invalid/empty_array.a2ml b/.machine_readable/contractiles/trust/examples/invalid/empty_array.a2ml new file mode 100644 index 0000000..072f8af --- /dev/null +++ b/.machine_readable/contractiles/trust/examples/invalid/empty_array.a2ml @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: MPL-2.0 +# Fixture: trust/examples/invalid/empty_array.a2ml — contractile conformance corpus (INVALID) +# Expected: `contractile trust typecheck trust/examples/invalid/empty_array.a2ml` → exit 3 +# Valid only under RFC-0001 O5 option (a); see conformance/manifest.a2ml +# Reason: the primary array (`verifications`) is empty + +## Integrity Invariants + +### (no items declared) diff --git a/.machine_readable/contractiles/trust/examples/invalid/missing_id.a2ml b/.machine_readable/contractiles/trust/examples/invalid/missing_id.a2ml new file mode 100644 index 0000000..4680008 --- /dev/null +++ b/.machine_readable/contractiles/trust/examples/invalid/missing_id.a2ml @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: MPL-2.0 +# Fixture: trust/examples/invalid/missing_id.a2ml — contractile conformance corpus (INVALID) +# Expected: `contractile trust typecheck trust/examples/invalid/missing_id.a2ml` → exit 3 +# Valid only under RFC-0001 O5 option (a); see conformance/manifest.a2ml +# Reason: the item has no `### ` heading + +## Integrity Invariants + +- description: verification with no id heading +- run: test ! -f .env +- severity: critical diff --git a/.machine_readable/contractiles/trust/examples/invalid/wrong_status.a2ml b/.machine_readable/contractiles/trust/examples/invalid/wrong_status.a2ml new file mode 100644 index 0000000..9aa64c7 --- /dev/null +++ b/.machine_readable/contractiles/trust/examples/invalid/wrong_status.a2ml @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: MPL-2.0 +# Fixture: trust/examples/invalid/wrong_status.a2ml — contractile conformance corpus (INVALID) +# Expected: `contractile trust typecheck trust/examples/invalid/wrong_status.a2ml` → exit 3 +# Valid only under RFC-0001 O5 option (a); see conformance/manifest.a2ml +# Reason: `status: passing` is outside {declared, verified, failing} + +## Integrity Invariants + +### no-secrets-committed +- description: No credential files are committed +- run: test ! -f .env +- severity: critical +- status: passing diff --git a/.machine_readable/contractiles/trust/examples/valid.a2ml b/.machine_readable/contractiles/trust/examples/valid.a2ml new file mode 100644 index 0000000..fa4bd28 --- /dev/null +++ b/.machine_readable/contractiles/trust/examples/valid.a2ml @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: MPL-2.0 +# Fixture: trust/examples/valid.a2ml — contractile conformance corpus (VALID) +# Expected: `contractile trust typecheck trust/examples/valid.a2ml` → exit 0 +# Valid only under RFC-0001 O5 option (a); see conformance/manifest.a2ml + +## Integrity Invariants + +### no-secrets-committed +- description: No credential files are committed +- run: test ! -f .env +- severity: critical diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index ca1c652..3a0176a 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -7,3 +7,38 @@ Changelog], and this project adheres to https://semver.org/spec/v2.0.0.html[Semantic Versioning]. === [Unreleased] + +==== Added + +* `docs/reports/audit/contractile-cli-audit-2026-10-03.adoc` — audit of the + repository and published estate for an existing `contractile` executable + (result: none; `build/contractile.just` is generated output with no surviving + generator). +* `docs/proposals/RFC-0001-contractile-cli.adoc` (`0.1.0-draft`) — proposed + command grammar, exit codes, six-verb surface, K9/Nickel validation and probe + semantics, execution permissions, determinism, manifest/hash/receipt + compatibility, packaging, conformance tests, and a phased plan. Draft — + awaiting maintainer ratification. +* `docs/decisions/0002-contractile-cli-spec-first.adoc` — ADR (Proposed) + recording that no CLI is implemented before the specification is ratified, + that `build/contractile.just` is a frozen generated artefact, and that the + Coaptation receipt runner stays separate. +* `docs/reports/audit/contractile-estate-census-2026-10-03.adoc` — read-only + re-measurement of contractile deployment (runner coverage 24.1% overall; + 59 `_base.ncl`; 132 `pending-first-verify` sentinels; 42 `Intendfile` + survivors), replacing the spec's 2026-09-01 figures for ruling purposes. +* `docs/governance/planning/contractile-cli-ratification-worksheet.adoc` — + decision sheet for spec rulings R1–R9 and RFC choices O1–O14, with new + measured evidence for O5 (the declaration↔runner binding). +* `docs/proposals/standards-CONTRACTILE-SPEC-v1.3.0-amendment.adoc` — drafted + amendment text for `standards/docs/CONTRACTILE-SPEC.adoc` v1.3.0 (cannot be + applied from this repository): closes the rulings, adds the + declaration↔runner binding table, and retires the unverifiable CLI-location + claim. +* `docs/governance/planning/contractile-cli-p1-backlog.adoc` — issue-ready task + list for phases P1–P6 with acceptance criteria, plus the drafted + `root-allow.txt`/`CODEOWNERS` amendments. +* `.machine_readable/contractiles/conformance/` and + `/examples/{valid,invalid/*}` — the spec-mandated conformance corpus + (24 invalid + 6 valid fixtures) and its manifest, with the measured + declaration↔schema gaps recorded as `known_gaps`. diff --git a/docs/0.1-AI-MANIFEST.a2ml b/docs/0.1-AI-MANIFEST.a2ml index 7f79301..5d7ad42 100644 --- a/docs/0.1-AI-MANIFEST.a2ml +++ b/docs/0.1-AI-MANIFEST.a2ml @@ -18,6 +18,7 @@ canonical_locations: governance: "governance/" architecture: "architecture/" decisions: "decisions/" + proposals: "proposals/" theory: "theory/" practice: "practice/" developer: "developer/" diff --git a/docs/decisions/0002-contractile-cli-spec-first.adoc b/docs/decisions/0002-contractile-cli-spec-first.adoc new file mode 100644 index 0000000..906ba86 --- /dev/null +++ b/docs/decisions/0002-contractile-cli-spec-first.adoc @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) 2026 Jonathan D.A. Jewell += Architecture Decision Record: 0002-contractile-cli-spec-first + + + +# 2. Specify and ratify the `contractile` CLI before implementing it + +Date: 2026-10-03 + +## Status + +Proposed — awaiting maintainer ratification. (On acceptance, RFC-0001-contractile-cli +moves from `0.1.0-draft` to `1.0.0` and implementation phase P1 may begin.) + +## Context + +The audit `AUDIT-CLI-2026-10-03` +(`docs/reports/audit/contractile-cli-audit-2026-10-03.adoc`) established: + +- **No `contractile` executable exists** in this repository or in any published + estate repository. There is no `cli/`, no Cargo workspace, no Python package, + no executable, and no `*.py` file at all here. +- **Every in-repo reference is a forward reference**: three occurrences + (`Justfile:18`, `build/contractile.just:1,3`), none executable. +- **`build/contractile.just` is generated output.** Its header names + `contractile gen-just`; the root `Justfile` imports it as generated; no + generator exists anywhere; and 205 files in the published estate carry the + same generated header. It cannot currently be regenerated. +- **The estate enforces contractiles ad hoc.** At least three independent + re-implementations exist (generated `just` recipes; `standards/scripts/run-mustfile.sh`; + per-repo `bun scripts/contractiles.mjs`), with mutually inconsistent exit + codes, and none validates a declaration against its Nickel runner schema. +- **The normative specification is unratified and self-inconsistent where it + matters for a CLI**: `standards/docs/CONTRACTILE-SPEC.adoc` v1.2.1 is + "Draft — awaiting owner ratification" and carries nine open rulings (two-vs-four + files per verb, runner coverage, k9 relocation, WCAG baseline, cardinality, + the `MUST.contractile` family, the eNSAID taxonomy, the `contractiles-v1` + profile, the Antifile proposal). Its one positive existence claim for a CLI + cites a workstation path (`/var/mnt/eclipse/repos/reposystem/contractiles/cli/`) + inside a submodule checkout that does not contain it. +- **Declarations are executable content.** `run:`, `probe:`, `injection_probe:` + and `recovery_probe:` fields are shell strings. No ratified permission, + timeout, capability, or signing model exists for executing them. +- **The Coaptation receipt runner is a separate system** (`coapt.sh` + + `coapt.ncl`, schema `hyperpolymath.coaptation/1`) with its own pipeline, + exit conventions, and an explicit "a reading, not a decision" stance. + +Implementing a CLI in this state would freeze an unratified reading of the verb +model into a binary, add a fourth exit-code contract to the estate, and — +because probes are code — ship an unguarded execution surface. + +## Decision + +1. **Do not implement a `contractile` executable yet.** No code, no generated + fragment regeneration, and no probe execution until the specification is + ratified and the maintainer has closed or explicitly deferred the open + choices listed in RFC-0001 §"Unresolved maintainer choices". +2. **Adopt `docs/proposals/RFC-0001-contractile-cli.adoc` (`0.1.0-draft`) as the + vehicle for ratification.** It defines the proposed ownership model, command + grammar, exit-code table, six-verb surface, K9/Nickel validation and probe + semantics, safe execution permissions, deterministic-output requirements, + manifest/hash/receipt compatibility, packaging, positive/negative conformance + tests, and a phased implementation plan (P0–P6) whose first phase is this + ADR. +3. **Treat `build/contractile.just` as a frozen generated artefact.** It MUST + NOT be hand-edited. It is regenerated only by a ratified `gen-just` + implementation (RFC-0001 §gen-just, phase P5), starting in this repository. +4. **Keep the Coaptation receipt runner separate.** The future CLI MUST NOT read + or write `.machine_readable/coaptation/**`, MUST NOT emit the + `hyperpolymath.coaptation/1` schema, and MUST NOT decide anything. Any future + coupling is one-way (Coaptation may consume CLI run receipts) and requires + its own amendment. +5. **Make the exit-code contract public before any execution is possible.** + Phase P1 is limited to non-executing commands (`list`, `typecheck`, `verify`) + so the contract, conformance corpus, and golden outputs land and are tested + before a shell-out path exists in the binary. + +## Consequences + +### Positive + +- The estate's 205 generated `contractile.just` files, the six-verb vocabulary, + and the deployed Trident gain a single, reviewable executor rather than a + fourth ad-hoc re-implementation. +- Ratification forces the questions that block any conformance pass criterion + (Ruling 2 in particular) to be answered *before* the answer is baked into a + binary. +- Declarations' executable strings are treated as a permission surface from the + first line of code, not retro-fitted after an incident. +- The Coaptation system keeps its single responsibility (comparison) and its own + receipt schema; nothing in the estate gains an implicit dependency on a CLI + that does not exist yet. + +### Negative + +- No contractile enforcement improves in the near term; the ad-hoc scripts keep + running, and `build/contractile.just` stays stale until P5. +- Ratification is now on the critical path for a capability the estate has been + referencing since at least 2026-04-17. +- P1's read-only scope will look like slow progress to anyone who expected + `must run`; it is a deliberate sequencing choice. + +### Neutral + +- This ADR does not decide where the implementation lives (RFC-0001 O1), what + the crate is called (O2), or which exit-code canon wins (O3); it establishes + that those are ratification-time decisions, not implementation details. +- The audit's limitations are recorded and accepted: a single squashed commit + here, GitHub search as a partial index, and `contractiles-a2-lab` unreadable + (HTTP 403) at audit time. + +## References + +- Audit: `docs/reports/audit/contractile-cli-audit-2026-10-03.adoc` +- RFC: `docs/proposals/RFC-0001-contractile-cli.adoc` (`0.1.0-draft`) +- Specification: `hyperpolymath/standards` → `docs/CONTRACTILE-SPEC.adoc` v1.2.1 + (Status: Draft — awaiting owner ratification), Open Rulings 1–9 +- Reference Trident: `hyperpolymath/standards` and `hyperpolymath/rsr-template-repo` + → `.machine_readable/contractiles/**` +- Profile: `hyperpolymath/a2ml` → `docs/CONTRACTILES-A2ML-V1.adoc` +- Coaptation: `hyperpolymath/cicd-squabbler` → `.machine_readable/coaptation/coapt.sh` +- Signing: `hyperpolymath/standards` → `docs/SIGNING-POLICY.adoc` diff --git a/docs/governance/planning/contractile-cli-p1-backlog.adoc b/docs/governance/planning/contractile-cli-p1-backlog.adoc new file mode 100644 index 0000000..99c9e4f --- /dev/null +++ b/docs/governance/planning/contractile-cli-p1-backlog.adoc @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) += P1 Backlog: `contractile` CLI (issue-ready) +:backlog-id: BACKLOG-0001 +:date: 2026-10-03 +:status: Blocked on RATIFY-0001 — do not start work; file the issues +:toc: +:toclevels: 2 + +Task list for RFC-0001 phases P0–P6, written so each row can become one issue. +*Nothing here is startable before `RATIFY-0001` is signed* (P0), except T2, which +is a governance amendment the maintainer can make independently. + +Suggested labels per row use the repository's existing label set +(`enhancement`, `governance`, `conformance`, `ci/cd`, `packaging`, `migration`, +`priority:p0`…`p3`, `help wanted`). + +== T1 — P0 · Ratify the RFC (umbrella) + +[cols="1,5",options="header"] +|=== +| Field | Value + +| Issue title | `contractile CLI: ratify RFC-0001 and ADR-0002 (R1–R9, O1–O14)` +| Labels | `governance`, `decision`, `priority:p0`, `meta:umbrella` +| Blocked by | nothing (this is the gate) +| Deliverable | Signed `RATIFY-0001` worksheet; RFC-0001 → `1.0.0`; ADR-0002 → Accepted +| Acceptance | Every worksheet row decided or deferred-with-date; RFC version bumped; ADR status changed in the same commit +|=== + +== T2 — P0 · Governance amendments (can start now) + +[cols="1,5",options="header"] +|=== +| Field | Value + +| Issue title | `governance: allowlist + CODEOWNERS entries for the CLI implementation path` +| Labels | `governance`, `priority:p1` +| Blocked by | O1 (path choice) — draft text below assumes option (a) `cli/` +| Deliverable | One-line addition to `.machine_readable/root-allow.txt` and a CODEOWNERS rule +| Acceptance | `bash scripts/check-root-shape.sh .` still exits 0; CODEOWNERS parses (GitHub UI shows owner) +|=== + +Drafted edits (do not apply until O1 is decided): + +[source] +---- +# .machine_readable/root-allow.txt — add under "Build entry points" +cli/ # contractile CLI implementation (RFC-0001 O1(a)); remove if O1 resolves elsewhere +---- + +[source] +---- +# .github/CODEOWNERS — add +/cli/ @hyperpolymath +/.machine_readable/contractiles/ @hyperpolymath +---- + +== T3–T17 — Phases P1–P6 + +[cols="1,2,3,2,1",options="header"] +|=== +| ID | Issue title | Deliverable | Acceptance criteria | Blocked by + +| T3 +| `cli: crate skeleton, --version capability report, help topics` +| Rust workspace + binary; `--version --format=json`; `help exit-codes|probes|permissions` +| `--version` JSON parses; help topics exist; no shell-out path in the codebase (`grep -R Command::new` empty) +| O1, O2, O3 + +| T4 +| `cli: exit-code plumbing and golden-output harness` +| Exit-code table wired for `2/3/5/7`; golden files for `text|json|a2ml` +| Golden diff clean on two consecutive runs and from a different cwd/locale; CI fails on any diff +| T3 + +| T5 +| `cli: list — discover verbs from INDEX.a2ml` +| `contractile list` reading `INDEX.a2ml`, with canonical-path fallback +| Lists 6 verbs + k9 exception, tier/authority/gating from the registry; no hard-coded verb list in source; unsupported `lust` reported as drift +| T3 + +| T6 +| `cli: typecheck — declaration vs runner schema + K9 structural checks` +| `contractile typecheck […]`, ` typecheck ` +| Fixture corpus (this bundle) passes/fails exactly as the manifest says; K9 checks mirror `validate-k9.sh` reasons; hash mismatches exit 7 +| O5, T5 + +| T7 +| `cli: verify — Trident completeness, cardinality, paired_xfile` +| `contractile verify` +| Detects: partial Trident, duplicate verb dirs, floating K9 (`paid_xfile` absent), `lust/` drift, `pending-first-verify` as *unknown*; emits exit `1`/`7` per RFC +| R2, T6 + +| T8 +| `cli: self-test and corpus runner` +| `contractile self-test` + CI lane step +| Runs embedded validator self-test + the corpus manifest; unregistered fixture fails the lane +| T4, T6 + +| T9 +| `cli: probe engine (capabilities, refusal, timeout, env sanitisation)` +| Probe parsing (legacy + structured), capability ladder, `--no-probe`, `--dry-run` +| Negative tests prove no subprocess/write/network without grants; timeout kills the process group (no orphan); refusals exit `4` and are reported per item +| O4, O6, O11, T8 + +| T10 +| `cli: must run and trust verify (gating verbs)` +| `must run`, `trust verify` on the engine +| Codes `0/1/4/6` correct on fixtures; `--strict` promotes indeterminate/manual discharge to `1`; determinism goldens pass +| T9 + +| T11 +| `cli: run receipts (--receipt)` +| `hyperpolymath.contractile-run/1` writer, opt-in +| Receipt reproducible byte-for-byte; `declaration-sha256` equals manifest digest; never writes under `.machine_readable/coaptation/**` (asserted by tree snapshot test) +| O7, T10 + +| T12 +| `cli: advisory verbs (adjust check, dust find, intend run/progress/horizon)` +| Advisory command surface +| Never exits `1` without `--strict`; `intend` never gating; wish items are never probed +| T10 + +| T13 +| `cli: mutating verbs behind explicit approval` +| `adjust fix --apply`, `dust sweep --apply --approve `, `bust check|drill` +| Pre/post tree-hash proves no mutation without grants; per-item approval recorded; `drill` defaults to a scratch worktree and never touches the working tree +| O10, O11, T12 + +| T14 +| `cli: gen-just and regeneration of build/contractile.just` +| `gen-just` per the decided input contract; regenerate *this repo's* fragment only +| Regeneration byte-stable (second run = empty diff); header matches the deployed convention; no timestamps +| O9, T13 + +| T15 +| `cli: packaging (install.sh, container, SBOM, publish)` +| Installer, image, SBOM, signed release, crates.io publish +| Clean-machine install runs `self-test` green; SBOM + signature attached; `--version` reports build commit +| O2, O14, T14 + +| T16 +| `ci: wire the conformance lane and the required context` +| CI job running `contractile self-test` in this repo, then downstream via template +| Job name matches the decided context (O13); branch protection references the existing name; no PR deadlock (see `k9-contractile.yml` header) +| O13, T8 + +| T17 +| `migration: normalise this repo's six declarations to the binding` +| The canonical template's own declarations conform to A5's table +| `contractile verify` passes on this repo at the decided file cardinality; `target`/`reason`/`class` gaps from the worksheet Part C closed or recorded as variances with expiry +| T7, and the O5/R2 decisions +|=== + +== Not in this backlog (deliberately) + +* *Any probe execution against declarations in this repository* — blocked until + T9–T10 exist and O4/O11 are decided. +* *Editing `build/contractile.just` by hand* — it is generated; T14 regenerates it. +* *Coaptation changes* — `coapt.*` is owned elsewhere; T11 only guarantees + non-interference. +* *Renaming or adding CI contexts before O13* — a required context rename + silently un-gates branches. diff --git a/docs/governance/planning/contractile-cli-ratification-worksheet.adoc b/docs/governance/planning/contractile-cli-ratification-worksheet.adoc new file mode 100644 index 0000000..5d07933 --- /dev/null +++ b/docs/governance/planning/contractile-cli-ratification-worksheet.adoc @@ -0,0 +1,248 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) += Ratification Worksheet: `contractile` CLI (R1–R9, O1–O14) +:worksheet-id: RATIFY-0001 +:date: 2026-10-03 +:status: Open — for the maintainer's decision +:toc: +:toclevels: 2 +:sectnums: + +Decision sheet for `RFC-0001-contractile-cli.adoc` (`0.1.0-draft`). Every row +below blocks a phase of the RFC's P0–P6 plan; nothing is decided here. Fill the +*Decision* column, or mark *Deferred* with a date — the RFC requires one or the +other, not silence. + +Completion rule: when every row carries a decision, RFC-0001 bumps to `1.0.0`, +ADR-0002 moves *Proposed → Accepted*, and phase P1 may begin. + +== How to use this sheet + +. Part A rows (R1–R9) are estate/owner rulings carried by + `standards/docs/CONTRACTILE-SPEC.adoc`. They cannot be settled in this + repository; this sheet records the option text and the consequence for the + CLI so the answer can be transcribed to `standards` in one pass. +. Part B rows (O1–O14) are new choices raised by RFC-0001. The maintainer of + this repository can settle these. +. Part C is new evidence produced for O5: the canonical template's own + declarations, measured against the canonical runners. +. Recommendations are *non-binding* and exist so the cheap answer is visible. + +== Part A — Inherited rulings (spec `<>`, 1–9) + +[cols="1,3,3,2,2",options="header"] +|=== +| ID | Question | Options | Evidence / recommendation | Decision + +| R1 +| The 620-file (now ~131 indexed) s-expression `MUST.contractile` family: recognise as an inherited Universal Invariants layer, or sanction as a second family? +| (a) hoist the 26-clause set into `standards` and make repos reference it; (b) sanction it under a non-colliding name; (c) ignore it +| Census: 131 files, estate-uniform. (a) — 143 identical copies are drift by definition, and (c) leaves two answers to "what must hold here?" in the ~101 repos carrying both +| _[ ]_ + +| R2 +| Two files or four per verb directory (Trident)? +| (a) four (declaration + runner + `.k9.ncl` + `.manifest.a2ml`); (b) two; (c) four as target, two as transitional minimum +| Census: 42 verb manifests and 43 `must.k9.ncl` already deployed ⇒ the four-file form is the de-facto target where a system exists. (a) or (c). *This row gates `contractile verify`'s pass criterion.* +| _[ ]_ + +| R3 +| Runner coverage (~24% overall; `trust` 9.7%): build the runners, or downgrade the pairing requirement? +| (a) build; (b) declare the runner a target state; (c) build for `must`/`trust`/`bust` only +| Census: 1,276 declarations lack a runner; `bust` at 77% proves it is achievable. (a) with a deadline, or (c) if only the gating trio matters +| _[ ]_ + +| R4 +| Ratify the k9 relocation to `.machine_readable/svc/k9/` and fix the spec's layout diagram? +| (a) ratify ADR-001 and repoint the diagram; (b) revert to the old path +| (a). The CLI needs one search path; leaving both live doubles the failure surface +| _[ ]_ + +| R5 +| WCAG baseline for `adjust`: 2.1 AA (spec) or 2.2 AA (CLI record)? +| (a) 2.2 AA; (b) 2.1 AA +| (a) if the CLI record is newer; harmless for the CLI either way, but must be one string in output +| _[ ]_ + +| R6 +| Cardinality: confirm one-per-repo, and confirm `docs/templates/**` + ecosystem farms are exempt from the count? +| (a) confirm + exempt templates/farms; (b) confirm with no exemptions; (c) drop the rule +| (a). Without the exemption the rule is unenforceable as written (311 `must` files across ~271 repos measured in 2026-09) +| _[ ]_ + +| R7 +| eNSAID's five-tier taxonomy (`lust` live, `k9` as peer tier): scope it as a profile of this spec, or treat it as a separate system? +| (a) profile, `lust` dropped, paths aligned; (b) separate system, stop using "contractile"; (c) defer +| (a). The CLI must refuse `lust` dispatch either way (4 `Lustfile.a2ml` survive) +| _[ ]_ + +| R8 +| `contractiles-v1` profile: keep as field-level authority, add `adjust`/`bust`, or scope it out? +| (a) keep + add the two verbs; (b) keep, record the omission deliberately; (c) retire +| (b) is honest and cheapest; the CLI cannot implement a profile that models four of six verbs +| _[ ]_ + +| R9 +| Antifile proposal: seventh artefact, or close it? +| (a) close; (b) specify as a seventh artefact family +| (a). No `Antifile` was found deployed; leaving it open is how the estate acquired speciation +| _[ ]_ +|=== + +== Part B — New choices raised by RFC-0001 + +[cols="1,3,3,2,2",options="header"] +|=== +| ID | Question | Options | Recommendation / consequence | Decision + +| O1 +| Where does the implementation live? +| (a) this repo under `cli/` (+ `root-allow.txt` amendment); (b) sibling repo `hyperpolymath/contractile-cli`; (c) a `reposystem` subcommand; (d) an already-allowlisted path (`tools/` or `src/cli/`) +| (a) matches the spec, the 205 generated headers, and the submodule path — but needs a governance amendment. (d) is cheapest to land. Gates P1 +| _[ ]_ + +| O2 +| Crate/binary name +| (a) `contractile`; (b) `contractile-cli`; (c) `contractiles` +| (a). Verified unclaimed on crates.io 2026-10-03 (`crate does not exist`). Gates P6 only +| _[ ]_ + +| O3 +| Exit-code canon +| (a) RFC table `0–7`; (b) `0/1` + sysexits (`64` usage) as `coapt.sh` does; (c) `0/1/2` bash-style +| (a). Two codes cannot separate "contract broken" (`3`) from "you broke the contract" (`1`) from "tampered" (`7`). Gates P1's public contract +| _[ ]_ + +| O4 +| May probes execute without an explicit capability grant in a *trusted* repo? +| (a) always require `--allow-subprocess`; (b) auto-grant in signed repos with Hunt runners; (c) auto-grant for `must`/`trust` +| (a) for v1. Gates P2 +| _[ ]_ + +| O5 +| Declaration→runner binding (the normalisation table) +| (a) CLI normalises deployed declaration fields onto runner schemas via a published table; (b) migrate all declarations to schema-native fields; (c) change schemas to the deployed vocabulary +| (a). See *Part C*: the canonical template's own declarations fail (b) and (c) cannot express `adjust`/`dust` at all. Gates P2 and everything after +| _[ ]_ + +| O6 +| Probe shell +| (a) `bash -uc`; (b) `sh -c`; (c) `bash -euo pipefail -c` +| (a) — matches the Justfile's `set shell := ["bash","-uc"]`. Gates P2 +| _[ ]_ + +| O7 +| Run-receipt schema name and key spelling +| (a) `hyperpolymath.contractile-run/1`, hyphenated keys; (b) `contractile-receipt/1`, underscore keys +| (a), but confirm with `standards` to avoid colliding with the coaptation namespace. Gates P3 +| _[ ]_ + +| O8 +| May the CLI write manifests (`manifest --update`)? +| (a) never in v1; (b) behind `--apply`; (c) only with `--sign` +| (a). 132 files still carry `pending-first-verify`; writing hashes before the signing policy is settled would bless unverified claims. Gates P4 +| _[ ]_ + +| O9 +| `gen-just` input contract +| (a) derive from declarations + runner policy; (b) explicit `gen-just` block in the runner; (c) freeze the current generated file and retire `gen-just` +| (b) is declarative and testable; (a) matches the deployed header's `Source:` lines. Gates P5 +| _[ ]_ + +| O10 +| `bust drill` isolation +| (a) scratch git worktree (default); (b) temp clone; (c) container; (d) in-place with confirmation +| (a) for v1; (c) once the container image ships. Gates P4 +| _[ ]_ + +| O11 +| Signature requirement for Hunt-tier execution +| (a) refuse unsigned Hunt always; (b) warn + `--force-unsigned`; (c) signature only for CI/estate-wide use +| (a) — `k9-sign` exists, so signing is achievable; refusal must be exit `4`, never a warning-and-continue. Gates P4 +| _[ ]_ + +| O12 +| Declaration format horizon (`.a2ml` → `.deed`) +| (a) `.a2ml` only; (b) accept both; (c) `.deed` first +| (b). The estate is mid-migration (`deed-conformance.yml`); hard-coding `.a2ml` guarantees a second migration. Gates P2 +| _[ ]_ + +| O13 +| Required CI context name +| (a) reuse `K9-SVC contractile validation`; (b) new `contractile self-test` context +| (a). Census: only 3 repos carry the existing context, so reuse migrates cheaply and avoids un-gating branches (see `k9-contractile.yml` header). Gates P6 +| _[ ]_ + +| O14 +| Platform support level +| (a) Linux/CI only; (b) best-effort POSIX; (c) full cross-platform +| (a) for v1, stated in `--version` and docs. Gates P6 +| _[ ]_ +|=== + +== Part C — New evidence for O5 (measured 2026-10-03) + +The canonical template's own six declarations, read against the canonical six +runners in `standards/.machine_readable/contractiles/`. Field counts are from +this repository's `.machine_readable/contractiles/`. + +[cols="1,2,2,3",options="header"] +|=== +| Verb | Deployed declaration fields | Runner schema expects | Verdict + +| `must` +| `description` (16), `run` (16), `severity` (16), `notes` (1) +| `id`, `description`, `probe`, `status?`, `severity?`, `notes?`, `fix?` +| `run:` vs `probe:` rename; `id` is carried by the `### ` heading, not a field +| `trust` +| `description` (9), `run` (9), `severity` (9), + trust-level prose +| `id`, `description`, `probe`, `status?`, `severity?`, `notes?`, `safe_hacking` (*defaulted ⇒ optional in practice*) +| rename as `must`; no deployed declaration carries a `safe_hacking` block +| `adjust` +| `description` (8), `tolerance` (8), `corrective` (8), `severity: advisory` (8) +| `id`, `description`, `probe` (**required**), `status?`, `compliance?`, `notes?`, `fix?` +| *No deployed adjust declaration can typecheck today.* There is no discharge field at all: `tolerance`/`corrective` are not `probe`. Also `severity: advisory` is outside `severity_core` {critical, high, medium, low}. O5 must decide what an `adjust` item's discharge is (or declare `adjust` reporting-only) *and* the severity alias +| `dust` +| `description` (7), `severity` (7), `run` (6), `verification` (1), `notes` (5) +| `id`, `description`, `target` (**required**), `reason` (**required**), `probe?`, `status?`, `approver?`, `notes?` +| `run`/`verification` vs `probe`; *`target` and `reason` are required by the schema and absent from every deployed declaration* +| `bust` +| `description` (3), `class` (3), `injection_probe` (3), `recovery_probe` (3), `expected_recovery_time_seconds` (3), `status` (3), `notes` (3) +| same names ✓, but `class` is a closed enum: `network, disk_full, oom, timeout, partial_write, panic, crash, rollback, concurrency` +| Field names match — the only verb that does. *Enum mismatch:* deployed classes (`template_processing`, `synchronization`, `contractile_format`) are outside the schema enum, so deployed `bust` declarations fail on `class` +| `intend` +| `description` (7), `status` (7), `probe` (3, intents only), `horizon` (4, wishes), `notes` (2) +| `intents{id, description, probe?, status?, notes?, target_date?}` + `wishes{id, description, horizon?, why?, status?, notes?}` +| Closest match; `id` still from the heading. *Heading depth matters:* intents are depth-3 (`### `), wishes are depth-4 (`#### `) under a depth-3 horizon group — a flat scan mis-parses wishes +|=== + +Three conclusions for O5: + +. *Option (b) — migrate declarations to the schema — means editing ~1,682 + deployed files* (plus `target`/`reason`/`class` values that only the repo + maintainer can supply). Option (c) — change the schemas — cannot express + `adjust` or `dust` without inventing semantics, and would invalidate the 406 + runners that exist. +. *Option (a) requires a published normalisation table* with at least: heading → + `id`; `run:`/`probe:` → `probe`; `verification:` → manual discharge + (`unmeasured`, never silently green); `tolerance`/`corrective` → adjust's + discharge **or** explicit reporting-only status; `class` enum widened or + mapped; `target`/`reason` optional-until-migrated. +. *The normalisation table is a change to `standards`, not to a binary.* It + belongs in `CONTRACTILE-SPEC.adoc` (see the v1.3.0 amendment draft in this + bundle), so a second implementation cannot diverge from it. + +== Sign-off + +[cols="2,3",options="header"] +|=== +| Field | Value + +| Maintainer | _[ name ]_ +| Date | _[ yyyy-mm-dd ]_ +| Rows decided | _[ R1–R9: __/9 · O1–O14: __/14 ]_ +| Rows formally deferred (with date) | _[ list ]_ +| Effect on RFC-0001 | _[ 0.1.0-draft → 1.0.0 ]_ +| Effect on ADR-0002 | _[ Proposed → Accepted ]_ +| Signature | _[ signed commit / GPG / SSH ]_ +|=== diff --git a/docs/proposals/RFC-0001-contractile-cli.adoc b/docs/proposals/RFC-0001-contractile-cli.adoc new file mode 100644 index 0000000..baa7c1c --- /dev/null +++ b/docs/proposals/RFC-0001-contractile-cli.adoc @@ -0,0 +1,1014 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) += RFC-0001 — The `contractile` Command-Line Interface +:rfc-id: RFC-0001-contractile-cli +:version: 0.1.0-draft +:status: Draft — awaiting maintainer ratification +:date: 2026-10-03 +:supersedes: none +:related: standards/CONTRACTILE-SPEC.adoc v1.2.1; ADR-0002-contractile-cli-spec-first; AUDIT-CLI-2026-10-03 +:toc: +:toclevels: 3 +:sectnums: +:icons: font + +[IMPORTANT] +==== +*This document proposes; it does not decide.* Nothing here is normative until +the maintainer ratifies it (see <>). Until then, the words +MUST/SHOULD/MAY describe *the proposal*, and no implementation work, generated +file regeneration, or probe execution is authorised by this text. + +Audited state at the time of writing (`AUDIT-CLI-2026-10-03`): no `contractile` +executable exists in this repository or in any published estate repository; the +only in-tree references are forward references; `build/contractile.just` is +generated output with no surviving generator. +==== + +== Summary + +Define, under change control, a single command-line tool — `contractile` — that +is the executor for the six-verb contractile vocabulary +(`intend / must / trust / adjust / dust / bust`) and for K9 trust-tier +components, with: + +* an explicit command grammar and exit-code contract; +* declaration→runner validation (A2ML declaration against a Nickel runner + schema), with a clearly bounded, capability-gated probe execution model; +* deterministic, reproducible output and receipts; +* manifest/hash verification compatible with the deployed `.manifest.a2ml` + form, and provenance compatible with the Coaptation receipt's hash convention; +* a packaging and conformance-test story that can ship in this repository. + +The Coaptation receipt runner (`coapt.sh` / `coapt.ncl`) is *not* part of this +proposal and MUST remain a separate tool (<>). + +== Motivation + +The audit found the vocabulary deployed and the executor absent: + +* 205 files in the published estate are generated by `contractile gen-just`; + none of them can be regenerated today. +* The canonical Trident ships a runner (`.ncl`) + K9 component + (`.k9.ncl`) + manifest (`.manifest.a2ml`) per verb, but runner + coverage is 2–10% per verb, and no executor exists in any case. +* Enforcement is re-implemented per repository in bash/bun/just, with three + different exit-code conventions and no schema validation. +* The normative specification that would resolve this (`standards/docs/CONTRACTILE-SPEC.adoc`, + Status: *Draft — awaiting owner ratification*) names a CLI that lives at a + workstation path (`/var/mnt/eclipse/repos/reposystem/contractiles/cli/`) and + is not present in any published artifact. +* Contractile declarations carry executable shell strings. Executing them from + repository content is remote code execution by design; the estate has no + ratified capability model for it. + +== Non-goals + +* *Not* a replacement for the Coaptation comparator or its receipts + (<>). +* *Not* a general task runner: it does not replace `just`, and it does not own + the repository's canonical Justfile. At most it regenerates a clearly marked + generated fragment (<>). +* *Not* a package manager, a formatter, or an AI agent runtime. +* *Not* a decision authority. Like `coapt`, the CLI reports; re-writing a + contractile, dropping an anchor, or deleting content remains a human act + (`dust sweep --apply` requires per-item approval, <>). +* *Not* a new source of truth for the verb model. Where this RFC and the + ratified `CONTRACTILE-SPEC.adoc` disagree, the spec wins. + +[[ratification]] +== Status, versioning and ratification + +=== This document + +[cols="1,3",options="header"] +|=== +| Field | Value + +| RFC id | `RFC-0001-contractile-cli` +| Version | `0.1.0-draft` (pre-ratification). Bump to `1.0.0` on ratification; any + normative change thereafter bumps the minor version and requires an ADR. +| Status | *Draft — awaiting maintainer ratification* +| Ratifier | The repository owner/maintainer (see <>) +| Gate to ratification | Every item in <> is either decided or + explicitly deferred with a named owner; the ADR (`0002-contractile-cli-spec-first`) + moves from *Proposed* to *Accepted*. +| Compatibility target | `CONTRACTILE-SPEC.adoc` ≥ v1.2.1; `contractiles-v1` profile + (`a2ml/docs/CONTRACTILES-A2ML-V1.adoc`) for field-level validation where it + applies +|=== + +=== Version negotiation + +* The CLI MUST print a machine-readable version line + (`contractile 0.1.0 (runner-schema 1.0.0)`) and a full + `--version --format=json` object including the supported `_base.ncl` + `schema_version`, the supported declaration grammar versions, and the build + commit. +* The CLI MUST refuse (exit `5`, see <>) to execute a runner whose + `pedigree.schema_version` is newer than the highest version it supports, rather + than guessing. +* Runtime behaviour changes that alter output for unchanged inputs require a + minor version bump and a changelog entry; changes to the exit-code table + require an ADR and a major version bump. + +[[ownership]] +== Ownership and change control + +[cols="2,2,2",options="header"] +|=== +| Concern | Owner | Change control + +| The verb model, layout, naming, provenance fields | `standards` (`CONTRACTILE-SPEC.adoc`) — currently unratified; spec changes are owner decisions | Spec version bump + ADR for breaking changes +| The CLI implementation, its grammar and exit codes | This repository's maintainer (agent allocations per `0-AI-MANIFEST.a2ml`: Rust/Idris2/Zig/CI → CLAUDE) | ADR + RFC version bump for grammar/exit-code changes +| Declaration files in this repository (`.machine_readable/contractiles/**`) | This repository's maintainer | Normal PR review; amendments per spec §"How changes … are made" (variance with `expires`, or formal amendment + ADR) +| K9 trust tiers and signature policy | `standards` (`SIGNING-POLICY.adoc`, `svc/k9`) | Signature policy changes are owner rulings +| Coaptation receipts and the comparator | The Coaptation runner (`coapt.sh`/`coapt.ncl`), wherever deployed | Owned outside this repository; the CLI may not write its schema +| Generated Justfile fragment | `contractile gen-just` (this CLI, once ratified) | Never hand-edited (see <>) +|=== + +Rules: + +. The CLI MUST NOT invent verb semantics. A behaviour that the ratified spec + does not define is not implemented by default; it is proposed as an RFC + amendment. +. The CLI MUST NOT read or write `.machine_readable/coaptation/**` by default + (<>). +. Where a repository claims K9 enforcement, each K9 component MUST declare a + `paired_xfile` (spec §k9-exception); the CLI's `verify` gate MUST report a + floating K9 component as non-conformant. +. This repository's `CODEOWNERS` MUST be updated to cover the implementation + path before the first commit of executable code (see <> O1 on + where that path is). + +== Command grammar + +=== Shape + +[source,abnf] +---- +contractile = GLOBAL *SP ( verb-command / meta-command ) *SP +verb-command = VERB *SP VERB-SUBCOMMAND *SP *ARG +verb-subcommand = "run" / "check" / "verify" / "typecheck" / "list" + / "fix" / "find" / "sweep" / "drill" / "progress" + / "horizon" / "probe" / "explain" +meta-command = "list" / "typecheck" / "verify" / "explain" / "self-test" + / "gen-just" / "version" / "help" +VERB = "intend" / "must" / "trust" / "adjust" / "dust" / "bust" +---- + +Conventions: + +* A bare `contractile` prints help and exits `2` (usage error), not `0` — a + silent success would let a mis-wired CI job pass. +* `contractile ` with no subcommand runs the verb's *default action* + (table below). This is a deliberate convenience; `--dry-run` MUST be accepted + by every default action. +* Unknown verb, unknown subcommand, or an unknown flag is a usage error + (exit `2`), never a silent no-op. +* `--` terminates option parsing; everything after it is passed to the probe + environment only for `bust drill` (<>). + +=== Global options + +[cols="2,3",options="header"] +|=== +| Option | Meaning + +| `--repo-root PATH` | Operate on a repository root (default: `git rev-parse --show-toplevel` from cwd; error if neither is available) +| `--declarations-dir PATH` | Override `.machine_readable/contractiles`; exists for `contractiles-v1`-layout repos; recorded in the receipt +| `--format a2ml\|json\|text` | Report format. Default from `run.report_format` (currently `"a2ml"`), else `text` +| `--json` | Alias for `--format=json` +| `--dry-run` | Parse, validate, plan; execute nothing +| `--strict` | Promote advisory findings (warnings, `pending-first-verify`, low-severity failures) to exit-code failures +| `--timeout SECONDS` | Default probe timeout; runner `timeout_seconds` wins if smaller; `0` = runner/no timeout disallowed under `--strict` +| `--allow-network` / `--allow-write` / `--allow-subprocess` | Capability grants (<>). Deny by default +| `--receipt[=PATH]` | Write a run receipt (<>). Never implied +| `--no-probe` | Never execute a probe; report every probe as `unmeasured` +| `--jobs N` | Concurrency. Default `1`; with `--jobs >1`, output MUST still be emitted in stable order +| `-q` / `-v` | Quiet (machine-only stdout) / verbose (annotations to stderr) +| `--color auto\|always\|never` | Default `auto`; `never` is mandatory under `--format=json` +|=== + +=== Verb command surface + +Every non-italic row below is a command the spec's `<>` table +already names (reordered by verb: the spec lists `must`, then `bust`, `adjust`, +`dust`, `intend`, `trust`). The italic rows are additions proposed here and +marked for ruling. + +[cols="2,2,2",options="header"] +|=== +| Invocation | Action | Gate + +| `contractile must run` +| Read the `must` declaration, evaluate each invariant, emit a per-item + verdict, exit non-zero if any blocking item failed +| gating (exit `1`) + +| `contractile must typecheck ` +| Validate an xfile against `must.ncl`'s `schema`; do not run probes +| non-executing + +| `contractile bust check` +| List declared failure modes + recovery status +| reporting + +| `contractile bust drill` +| Inject declared failures, verify recovery paths. *Requires explicit + `--drill` + write capability; see <>* +| gating, opt-in + +| `contractile adjust check` +| Run all probes, list violations +| advisory (exit `1` only under `--strict`) + +| `contractile adjust fix` +| Apply deterministic fixes where defined. *`--apply` required; dry-run default* +| mutating, opt-in + +| `contractile dust find` +| List removal candidates +| reporting + +| `contractile dust sweep [--apply]` +| Dry-run, or actual removal gated behind `--apply` + per-item approval +| mutating, opt-in + +| `contractile intend run` +| Print status table (both sections: `[[intents]]` + `[[wishes]]`) +| never gating + +| `contractile intend progress` +| Diff declared-vs-observed (intents) +| reporting + +| `contractile intend horizon` +| Group wishes by target-horizon (`near` / `mid` / `far`) +| reporting + +| `contractile trust verify` +| Run all verifications (read-only) +| gating (exit `1`) + +| `contractile trust probe` +| Run declared safe-hacking probes. *Scoped to the repo under test; requires + `--allow-network`/`--allow-write` per declared capability* +| gating, opt-in + +| _`contractile trust typecheck `_ +| _As `must typecheck`, against `trust.ncl`_ +| _non-executing_ + +| _`contractile list`_ +| _Enumerate declaration items with their `id`, `status`, `severity` and + discharge kind, without executing anything_ +| _non-executing_ + +| _`contractile explain .`_ +| _Explain one item: source file, line, probe string, discharge kind, why it + did or did not gate_ +| _non-executing_ +|=== + +=== Meta-commands + +[cols="2,3",options="header"] +|=== +| Command | Behaviour + +| `contractile list` | Discover verbs from `INDEX.a2ml` (never a hard-coded list; spec §Registry). Emits verb, status (`active`/`exception`), tier, authority, gating, and the resolved file paths +| `contractile typecheck […]` | Validate declaration(s) against their runner schema without executing probes; exit `0` iff all valid +| `contractile verify` | Verify the Trident and the manifest: files present exactly once per verb per repo (Ruling 6), `paired_xfile` cross-references resolve, manifest `sha256`/`size_bytes` match, K9 components are not floating, no `lust/` drift. Exit `7` on hash mismatch, `1` on structural non-conformance +| `contractile self-test` | Run the CLI's own conformance corpus (<>) and report; used by CI and by packaging smoke tests +| `contractile gen-just` | Regenerate the generated Justfile fragment (<>) +| `contractile version` / `--version` | Version and capability report (<>) +| `contractile help []` | Usage; `help exit-codes`, `help probes`, `help permissions` must exist +|=== + +[[exit-codes]] +== Exit codes + +The estate currently has at least three exit-code conventions: `0/1` +(`contractiles-v1` profile), `0/1/2` (`run-mustfile.sh`, `check-root-shape.sh`), +and `64` for usage errors (`coapt.sh`). A CLI cannot be scripted against three. +This RFC proposes one table, and flags the choice as O3 in <>. + +[cols="1,2,3",options="header"] +|=== +| Code | Meaning | Notes + +| `0` | Success: every gating item passed (or no gating items exist) | +| `1` | Verdict failure: at least one *blocking* item failed (`must`, `trust`, `bust`; or an advisory verb run with `--strict`) | +| `2` | Usage error: unknown verb/subcommand/flag, missing argument, bare invocation, no repository root | Matches `run-mustfile.sh`'s existing use of `2` for a setup-class failure +| `3` | Input error: declaration or runner missing, unreadable, unparseable, or schema-invalid | Distinct from `1` so CI can say "the contract is broken" vs "you broke the contract" +| `4` | Refused: a capability was required but not granted, or a Hunt-tier probe is unsigned / out of scope | *Refusal is an error, never a silent skip* +| `5` | Tool error: unsupported runner schema version, internal error, unreachable dependency | Bug-class; always includes a diagnostic +| `6` | Indeterminate: probe timed out, was killed, or could not be evaluated | With `--strict`, indeterminate becomes `1` +| `7` | Integrity failure: manifest hash/size mismatch, tampered K9 component, or `paired_xfile` that does not resolve | Tamper-class, deliberately distinct from `1` +|=== + +Additional rules: + +. Exit code is *authoritative*; stdout is a report. `--format=json` MUST include + the exit code in the payload so a caller that lost the process status can + still determine the verdict. +. A gating verb MUST NOT return `0` if any item was skipped, unmeasured, or + refused — that is `6`/`4`, and under `--strict` it is `1`. +. Advisory verbs (`adjust`, `dust`, `intend`) MUST NOT return `1` without + `--strict`. (Spec: `adjust` "advisory — continue-with-warnings"; + `intend` "never blocks"; `dust` destructive actions gated behind `--apply`.) +. `--dry-run` MUST still return the code the real run *would* return, or `0` + with an explicit `dry-run` field; it MUST NOT return `0` silently. (Recommendation: + return the would-be code, and mark the receipt `dry_run = true`.) +. Codes are stable across patch versions; a new code requires an ADR. + +== The six verbs + +Per-verb contract the CLI must implement. `cwd` for probes is the repository +root unless the declaration says otherwise (<>). `severity` vocabulary +comes from `_base.ncl` `severity_core` (`critical/high/medium/low`); `must` +uses a subset. + +[cols="1,2,2,1,2",options="header"] +|=== +| Verb | Declaration / runner | Primary action, default | Authority | Item schema (verb runner `schema`) + +| `must` +| `must/Mustfile.a2ml` + `must/must.ncl` +| `run` +| blocking, hard gate +| `invariants[] { id, description, probe|String, status ∈ {declared,verified,failing}, severity ∈ {critical,high,medium}, notes?, fix? }` + +| `trust` +| `trust/Trustfile.a2ml` + `trust/trust.ncl` +| `verify` +| blocking, hard gate +| `verifications[] { id, description, probe, status, severity ∈ {critical,high,medium,low}, notes? }` + + `safe_hacking { scope, allowed_probe_classes[], probes[] }` + +| `adjust` +| `adjust/Adjustfile.a2ml` + `adjust/adjust.ncl` +| `check` +| advisory (`--strict` to gate); `fix` mutates only with `--apply` +| `requirements[] { id, description, probe, status ∈ {declared,partial,verified,failing}, compliance?, notes?, fix? }` + +| `dust` +| `dust/Dustfile.a2ml` + `dust/dust.ncl` +| `find` +| reporting; `sweep --apply` is a destructive, per-item-approved act +| `removal_candidates[] { id, description, target, reason, probe?, status ∈ {declared,proposed,approved,removed}, approver?, notes? }` + +| `bust` +| `bust/Bustfile.a2ml` + `bust/bust.ncl` +| `check` +| blocking, opt-in destruction (`drill`) +| `failure_modes[] { id, description, class ∈ {network,disk_full,oom,timeout,partial_write,panic,crash,rollback,concurrency}, injection_probe, recovery_probe, expected_recovery_time_seconds, status ∈ {declared,drilled,verified,failing}, notes? }` + +| `intend` +| `intend/Intentfile.a2ml` + `intend/intend.ncl` +| `run` +| never gating (report only) +| `intents[] { id, description, probe?, status ∈ {declared,in_progress,done,deferred,retired}, notes?, target_date? }` and + `wishes[] { id, description, horizon ∈ {near,mid,far}, why?, status ∈ {declared,in_progress,achieved,abandoned}, notes? }` +|=== + +Notes and sub-verb semantics: + +. *`intend`/`Intentfile` is the one irregular verb–noun pair* (spec §Naming rule 3). + Verb string is always `intend`; file is always `Intentfile.a2ml`; runner is + `intend.ncl`. The deprecated `lust` verb MUST NOT be dispatched; a discovered + `lust/` directory is drift and `verify` reports it. +. *`k9` is not a verb* and MUST NOT be accepted as a verb subcommand. Its + validation is exposed as `verify` (and as the K9 half of `typecheck`); + trust-tier templates live at `.machine_readable/svc/k9/` per ADR-001. +. *`bust drill`* is the only command whose default may alter repository state. + Proposed policy: it runs inside a scratch copy (git worktree or temp clone) + and MUST NOT touch the working tree unless `--in-place` is passed with both + `--apply` and an explicit confirmation. The declaration's + `injection_probe` strings MUST be treated as destructive code. +. *`dust sweep --apply`* MUST require per-item approval + (`--approve ` repeated, or an interactive confirmation that records each + id); a bare `--apply` is a usage error. +. *`adjust fix`* may only run a declaration-supplied `fix` when the runner's + `run` policy permits mutation and `--apply` is given; otherwise every fix is + listed and not executed. +. `must` and `trust` MUST NOT mutate anything, even with `--apply`; mutation is + not a capability those verbs can obtain. + +=== Declaration field vocabulary — the binding gap (blocking) + +The estate currently has three vocabularies for the *same* concept: + +[cols="1,1,1",options="header"] +|=== +| Source | Field | Example + +| xfiles (`Mustfile.a2ml` in this repo and `standards`) +| heading + `- run:` +| `### license-present` … `- run: test -f LICENSE` +| runner schema (`must.ncl` et al.) +| `id` + `probe` +| `invariants[] { id \| String, probe \| String }` +| `contractiles-v1` profile +| `checks[].description` + `checks[].run` +| JSON emission has `checks` +|=== + +Nothing in the published estate defines how the first maps onto the second. +*This RFC does not silently invent that mapping*; it proposes the following and +flags it as O5 (the single highest-risk choice in this document): + +. The CLI reads the *declaration file's* field vocabulary (`- run:`, `- probe:`, + `- injection_probe:`, `- recovery_probe:`, `- verification:`), because that is + what 100% of deployed xfiles use. +. The CLI *validates* the resulting item record against the runner's `schema` + after normalising names via a single, published, versioned mapping table + (declared in the spec, not in the binary). +. Unknown fields in a declaration are a `typecheck` error under `--strict` and + a warning otherwise; unknown *discharge* kinds (a heading block with neither + `run:` nor `verification:`) are always errors (mirrors + `check-mustfile-structure.sh`). +. If the mapping table is not ratified, `run` is not implemented — `typecheck` + and `list` only. + +[[k9-nickel]] +== K9 and Nickel validation semantics + +=== Pipeline (per verb) + +[cols="1,2,2",options="header"] +|=== +| Stage | Check | Failure code + +| 0. Resolve | Locate the verb directory from `INDEX.a2ml`; if `INDEX.a2ml` is absent, fall back to the canonical path; never guess a flat path silently (warn + record) | `3` +| 1. Presence | Declaration + runner exist; exactly one of each (cardinality per Ruling 6) | `3` / `1` under `verify` +| 2. Manifest | If `.manifest.a2ml` exists, recompute `sha256` and `size_bytes` per listed file | `7` +| 3. K9 | For each `*.k9.ncl`: `K9!` magic on the first non-empty line; `pedigree` present with `name`/`version`; `leash ∈ {Kennel,Yard,Hunt}`; Hunt requires a `signature`/`signature_required` field | `3` (structure) / `7` (signature) +| 4. Nickel | `nickel typecheck` the runner; then evaluate the runner and apply its `schema` to the parsed declaration | `3` +| 5. Leash | The runner's `pedigree.security` capability set must be consistent with what the probes declare/need. A `Kennel` runner declaring a subprocess probe is a runner defect | `3` +| 6. Execute | Only when a `run`-class command and the required capability grants are present (<>) | `1`/`4`/`6` +|=== + +Nickel invocation rules: + +* The CLI MUST use the estate-pinned Nickel version (mise/`.tool-versions`) and + MUST record that version in `--version` output and in receipts. +* The CLI MUST NOT `import` untrusted Nickel from outside the repository root. + Import cycles, absolute imports, and imports escaping the root are input + errors (`3`). +* Evaluating a runner is *Yard-tier* work: pure, no side effects. The CLI never + evaluates runner code as a substitute for executing a probe, and never treats + a successful `nickel export` as evidence that an invariant holds. +* The validator meta-schema (spec §validator-meta-schema) is DEFERRED; until it + lands, the CLI's stage-3/4/5 checks are the closest available substitute and + MUST be documented as such rather than as spec conformance. + +=== K9 tiers and what the CLI may do + +[cols="1,2,3",options="header"] +|=== +| Tier | Semantics | CLI behaviour + +| Kennel | Pure data; no subprocess, no writes, no network | Validate only. Never execute anything from a Kennel component +| Yard | Nickel evaluation with contracts; no side effects | `typecheck`, `list`, `verify`; schema evaluation allowed +| Hunt | Full execution surface; declares side effects; signature before estate-wide trust | Execution requires an explicit capability grant *and* (for estate-wide use) a verified signature. Unsigned Hunt ⇒ exit `4`, never a warning-and-continue +|=== + +[[probes]] +== Probe semantics + +=== Accepted probe forms + +[cols="1,1,2",options="header"] +|=== +| Form | Status | Semantics + +| `- run: ` / `- probe: ` (legacy String) +| deployed, currently universal +| Execute as a shell command; pass iff the exit status is `0`. Invalid: empty string, non-UTF-8, NUL +| Structured probe (spec `probe_schema`) +| target, declared in `_base.ncl`, *not yet accepted by any implementation* +| `{ command, timeout_seconds = 300, allowed_exit_codes = [0], permission_class = 'read_only }` +| `- verification: ` (manual discharge) +| deployed (`standards/scripts/run-mustfile.sh`) +| Never executed, never silently green: reported as `MANUAL`/`unmeasured`. Blocks the gate under `--strict` (proposal; the bash precedent counts it but does not fail) +| `- fix: ` / `dust` removal actions +| deployed as data +| Mutation; only under `--apply` (+ `--approve ` for dust), never for `must`/`trust` +|=== + +Migration rule (proposed): accept both forms from day one; emit +`legacy_probe = true` in the receipt; when all declarations in a repo use the +structured form, warn no longer applies. The reverse — requiring structured +probes immediately — is rejected because it would make 100% of deployed +declarations invalid. + +=== Execution rules + +. Probes run with `cwd` = repository root, `argv = ["bash", "-uc", command]` + (matching the Justfile's `set shell := ["bash","-uc"]`), and a sanitised + environment (<>). The shell choice is O6. +. `allowed_exit_codes` (structured form) is authoritative; the legacy form is + exactly `[0]`. An exit status outside the allowed set is a failure, not a + crash: report, count, continue. +. Timeout: `min(runner timeout_seconds, --timeout)` with a hard default of + `300s`; on timeout the item is `indeterminate` (`6` → `1` under `--strict`). + The process group is killed, not just the shell. +. Output: by default the CLI captures probe stdout/stderr for the report with + a size cap (proposal: 64 KiB per stream) and does not stream it + (`-v` streams to stderr). Captured output is *not* part of the deterministic + body (<>) unless `--capture` is given, because it is environment + dependent. +. Ordering: items are evaluated in `(verb, declaration order)` unless `--jobs >1`; + reporting order is always stable regardless of execution order. +. A probe that cannot even be started (missing `bash`, exec denial) is + `indeterminate` (`6`), never `pass`. +. The CLI MUST NOT rewrite declarations based on probe results. Status + transitions (`declared → verified`) are recorded in receipts and in the + Coaptation provenance picture, not patched into the xfile by the CLI + (amendment is a human act; see spec §"How changes … are made"). + +[[safe-execution]] +== Safe execution permissions + +Contractile declarations are code. The default posture is therefore +*deny-by-default, refuse-loudly, never-silently-skip*. + +=== Capability ladder + +[cols="1,1,1,1",options="header"] +|=== +| Capability | Kennel | Yard | Hunt | CLI grant + +| Read repository files | yes | yes | yes | implicit +| Evaluate Nickel | no | yes | yes | implicit (Yard+) +| Spawn a subprocess (read-only probes) | no | no | yes | `--allow-subprocess` (implicit for Hunt unless `--strict-capabilities`) +| Write inside the repository | no | no | declared | `--allow-write` + verb policy + `--apply` +| Network | no | no | declared | `--allow-network` +| Destructive injection (`bust drill`) | no | no | declared | `--allow-write` + `--drill` + scratch-worktree default +| Estate-wide trust | n/a | n/a | signed | verified K9 signature +|=== + +=== Rules + +. The *effective* capability set is the intersection of: the runner's declared + `pedigree.security` flags, the verb's policy (<>: `must`/`trust` + are read-only), and the operator's flags. Intersection — never union. +. A probe whose required capability is not in the effective set is *refused* + (exit `4`), reported per item, and counted as unmeasured. It MUST NOT be + skipped with a `pass`, and MUST NOT be reported as `unmeasured` without the + reason string. +. Environment sanitisation (proposal): `PATH` inherited; `HOME` set to the real + home (some toolchains need it) but `XDG_*`, `GIT_*`, `SSH_*`, `AWS_*`, + `GITHUB_TOKEN`, `CI`-secret-like variables stripped unless the runner + declares them in an explicit `env_allowlist`. `LC_ALL=C`, `TZ=UTC`, + `NODE_OPTIONS` unset, `PYTHONHASHSEED` unset/`0`, `RUST_BACKTRACE` unset. +. No `eval`, no shell interpolation of declaration content *into* the CLI's own + commands — declaration strings are only ever passed as the single argument of + the probe shell. +. Unsigned Hunt-tier components MUST NOT execute, and `--force-unsigned` if + ever added is an owner-level ruling, not an implementation convenience + (see O11). +. `--no-probe` must be available on every run-class command and is the + recommended default in CI for untrusted PRs. +. Timeouts, process-group kill, output caps, and refusal semantics are + *conformance-tested* (<>), not merely documented. + +[[determinism]] +== Deterministic output + +Determinism is what makes a receipt verifiable and a regression test possible. +The 205 generated files exist because a generator once behaved reproducibly +enough to be trusted across repositories; the CLI must meet the same bar. + +Requirements (proposal): + +. *Inputs → output.* For identical repository content (declaration bytes, runner + bytes, commit), identical CLI version, and identical flags, stdout MUST be + byte-identical across runs, machines, working directories and locales. +. *No ambient data.* No wall-clock times, durations, PIDs, hostnames, user + names, absolute paths outside the repository, terminal widths, or + filesystem iteration order may appear in a report. Timestamps are allowed + only in a receipt's `[provenance]` field *if* explicitly requested by + `--stamp`; the default receipt is stamp-free so its hash is reproducible. +. *Stable ordering.* Verbs in canonical order + `intend, must, trust, adjust, dust, bust`; items sorted by declaration order + (not by hash/insertion/parallel completion); JSON object keys sorted; one + finding per line; LF line endings; UTF-8; no ANSI when + `--color=never`/`--format=json`. +. *Locale/timezone pinning.* The CLI process and child probes get `LC_ALL=C`, + `LANG=C`, `TZ=UTC`. +. *Probe output.* Captured probe output is environment-dependent and is excluded + from the deterministic body by default; when included (`--capture`), the + receipt MUST record that it is non-reproducible. +. *Paths.* Paths are printed relative to `--repo-root`, with `/` separators. + Absolute paths appear only in diagnostics on stderr. +. *Hashes.* Any digest in output is lowercase hex `sha256` over raw bytes + (no normalisation, no BOM stripping), matching `.manifest.a2ml`. +. *Golden tests.* Every verb ships golden stdout for its fixture corpus + (<>), and CI fails on any diff. `--format=json` and + `--format=a2ml` are both golden-tested. + +[[manifest-hash-receipt]] +== Manifest, hash and receipt compatibility + +=== What already exists (must be read, not redefined) + +* `.manifest.a2ml` — per-file `role`, `path`, `sha256`, `size_bytes`, + `notes`; a `[cross_refs]` block (`runner_paired_xfile`, `k9_paired_xfile`, + `k9_paired_runner`); `[signed_by]`; `[[history]]`. Deployed values are + currently the sentinel `pending-first-verify`. The CLI reads this format; it + does not own it. +* `INDEX.a2ml` — the verb registry (`id`, `version`, `spec`, `base_schema`, + `meta_schema_status`, `[[verbs]]` with `trident`/`file_pair`, `manifest`, + `status`, `tier`, `authority`, `gating`, `cardinality`). Consumers SHOULD + discover verbs here rather than hard-coding the list (spec §Registry). +* Coaptation provenance — `contractiles = "intend@ must@ trust@ + adjust@ dust@ bust@"` with `@`-prefixed short hashes. + +=== Rules for the CLI + +. *Read-only by default.* The CLI MUST NOT write manifests. A `manifest --update` + verb may be proposed later; it is out of scope for v1 and blocked on the + signing policy + Ruling 2 (O8). +. *Verification semantics.* `pending-first-verify` is *unknown*, not *valid*: + `verify` reports it as a warning and exits `0`; under `--strict` it is `7` + (integrity not established). A hash mismatch is always `7`, even without + `--strict`. Absent manifest = warning (not an error) unless the repo's + `INDEX.a2ml` declares a `manifest` for that verb, in which case it is `1`. +. *Trident completeness.* `verify` refuses partial publication: a declaration + without its runner (or a K9 component without `paired_xfile`) is + non-conformant. (`must.k9.ncl` states this is the intended CLI behaviour.) +. *Hash identity.* Digest = `sha256` of the file's raw bytes, lowercase hex. + This is the same identity the Coaptation provenance line uses, so a + contractile file has exactly one identity across both systems. + +[[receipts]] +=== Run receipts (new, CLI-owned) and the Coaptation boundary + +[[coaptation-boundary]] +The Coaptation receipt is a *comparison* artefact: descriptiles (facts) against +contractiles (set-points), producing a band (`green|amber|red`) and a proposed +action. The CLI's receipt is an *execution* artefact: what was run, with what +status, under what capabilities. Conflating them would put a comparator inside +an executor. + +[cols="1,1,1",options="header"] +|=== +| Aspect | CLI run receipt (proposed) | Coaptation receipt (existing) +| Schema | `hyperpolymath.contractile-run/1` | `hyperpolymath.coaptation/1` +| Owner | This CLI | `coapt.*` runner +| Location | `--receipt=PATH`; default `.machine_readable/contractiles/receipts/-.a2ml` (deterministic filename; no date, so a re-run overwrites the same path) | `.machine_readable/coaptation/receipts/latest.a2ml` +| Content | verb, resolved files + digests, per-item verdicts, capability grants, refusals, CLI version, runner schema version, `dry_run` | coverage, readings per verb, band, proposed action +| Determinism | stamp-free by default; hash of inputs in the filename | deterministic text; no stamp +| Relationship | CLI MUST NOT read or write `coaptation/**` | coapt MAY consume CLI receipts in a future revision; the dependency is one-way and opt-in +|=== + +Proposed run-receipt shape (illustrative; final field names are O7): + +[source,a2ml] +---- +[receipt] +schema = "hyperpolymath.contractile-run/1" +verb = "must" +verdict = "fail" # pass | fail | refused | indeterminate | dry-run +exit-code = 1 +cli = "contractile 0.1.0" +runner-schema = "1.0.0" +nickel = "1.10.0" +dry-run = false + +[inputs] +declaration = "must/Mustfile.a2ml" +declaration-sha256 = "<64 hex>" +runner = "must/must.ncl" +runner-sha256 = "<64 hex>" +k9 = "must/must.k9.ncl" +k9-sha256 = "<64 hex>" +manifest = "must/must.manifest.a2ml" +manifest-status = "pending-first-verify" # verified | pending | absent | mismatch + +[capabilities] +granted = ["read_only", "subprocess"] +refused = [] + +[verdicts] +license-present = "pass" +spdx-headers = "fail" # severity=critical → gating + +[provenance] +contractiles = "must@" +coaptation = "not-consulted" # explicit: this receipt makes no comparison claim +---- + +Rules: + +. A receipt MUST NOT be written unless `--receipt` is given (no surprise + files in a repository). +. Receipt content MUST be reproducible: no stamps, no durations, no absolute + paths. If a caller needs a timestamp or an append-only ledger, that is a + separate artefact outside this CLI's scope. +. The CLI MUST NOT emit the string `hyperpolymath.coaptation/1` or write into + `.machine_readable/coaptation/`, ever; a conformance test enforces this. +. A run receipt MAY include `probe-stdout-captured = true` when `--capture` + was used, and MUST then be marked non-reproducible. + +[[gen-just]] +== Generated Justfiles (`gen-just`) + +* `build/contractile.just` is generated output (audit F2). The generation + contract must be specified before the fragment is regenerated: + * *Inputs:* the resolved declaration set plus the runner `run`/`legacy` + policy (and, for `must`/`trust`, per-item recipes), or a declared + `gen-just` block in the runner — O9. + * *Output:* one fragment with a stable header + (`# Auto-generated by: contractile gen-just` + + `# Source directory: ` + `# Re-generate with: contractile gen-just --dir `), + byte-identical for identical inputs, no timestamps. + * *Recipes:* one recipe per declared item where the item has a `run:`/ + `probe:` discharge, named `-` (sanitised), aggregated by a + `-check` recipe; advisory items must not fail the aggregate by + default. + * *Policy:* generation is opt-in (`gen-just --write`); the default prints to + stdout (diff-friendly). No repository is force-updated. The fragment is + never hand-edited; repos that hand-edited it are drift and are reported by + `verify` once a manifest hash exists for it. +* Until `gen-just` is ratified and implemented, `build/contractile.just` MUST + be treated as frozen, and its `import?` in the Justfile stays as-is. + +[[packaging]] +== Packaging and distribution + +Precedents in the estate: `a2ml` and `k9-svc` are crates.io crates; `k9-sign` +ships `install.sh` with `--user`/`--system`/`--prefix`/`--build-only` and a +Clap-based binary; the container tier is Chainguard/Wolfi; Guix was retired +estate-wide (2026-06-01) and must not be reintroduced; `mise`/`.tool-versions` +pin toolchains; SBOM + signing come from `container/ct-build.sh` and `k9-sign`. + +Proposal: + +[cols="1,2",options="header"] +|=== +| Channel | Requirement + +| Source | Rust workspace (spec's stated shape) with `clap` for the grammar. The CLI is the *only* binary; no daemon +| Crate | Name `contractile` (availability MUST be checked; the name is currently unclaimed on crates.io and the repo name is free) — O2. Binary name MUST equal crate name +| Version pinning | Crate version maps to spec/runner-schema compatibility in `Cargo.toml` metadata; `--version` reports both +| `install.sh` | Provided, modelled on `k9-sign/install.sh` (`--user` default, `--system`, `--prefix`, `--build-only`); verifies a checksum before install +| Container | Publish a `contractile` image from the same Wolfi base as `container/Containerfile`; `contractile` as ENTRYPOINT; SBOM emitted by `ct-build.sh` +| Signing | Release artefacts signed per `SIGNING-POLICY.adoc`; the CLI's own K9/container artefacts are signed with `k9-sign` where applicable +| Dependencies | Minimal, pinned, vendored lockfile; no network at build beyond the registry; `cargo audit`/`deny` in CI +| Reference-not-duplicate | Downstream repos MUST call the installed binary; vendoring a copy of the CLI into a repo is drift (per `plasma-parser-writer/docs/contractiles.adoc`, the estate's one measured instance of the rule working) +| Guix | *Not* packaged via Guix (retired policy). Do not add a Guix package for the CLI +| Source-of-truth docs | `docs/` in this repo; spec in `standards`; a `docs/man/contractile.1` man page may be generated but is not the contract +|=== + +`--version` output (machine-readable requirement): + +[source,json] +---- +{"name":"contractile","version":"0.1.0","build_commit":"", + "runner_schema":"1.0.0","declaration_grammars":["a2ml-0.1","deed-1.0"], + "nickel":"1.10.0","capabilities":["read_only","subprocess","write","network"]} +---- + +[[conformance]] +== Conformance tests (positive and negative) + +Required for *every* verb, mirroring the estate's two existing conformance +lanes (`k9-ecosystem/conformance/**` for K9 fixtures; +`standards/1-formats/deed/tools` + `deed-conformance.yml` for the corpus → +self-test → lint-everything pattern). + +=== Fixture corpus + +Spec-mandated layout (`CONTRACTILE-SPEC.adoc` §Test Fixtures), extended: + +[source] +---- +.machine_readable/contractiles// +├── file.a2ml +├── .ncl +└── examples/ + ├── valid.a2ml # MUST typecheck → exit 0 + └── invalid/ + ├── missing_id.a2ml # MUST fail → exit 3 + ├── wrong_status.a2ml # enum violation → exit 3 + ├── empty_array.a2ml # empty primary array → exit 3 + ├── unknown_field.a2ml # warn (non-strict) / exit 3 (--strict) + ├── no_discharge.a2ml # heading with neither run: nor verification: → exit 3 + ├── malformed_utf8.a2ml # exit 3 + └── escaped_path.a2ml # import/path escaping the repo root → exit 3 +---- + +Positive (must pass): + +[cols="1,2",options="header"] +|=== +| Test | Assertion + +| `typecheck examples/valid.a2ml` (each verb) | exit `0` +| `list` (each verb) | exit `0`; stable text; includes every declared id +| `must run` on a repo whose probes all pass | exit `0`; verdict lines per item +| `intend run` on a repo with a failing intent | exit `0` (never gating) +| `adjust check` with violations, no `--strict` | exit `0`; violations reported +| `--format=json` on every verb | valid JSON; sorted keys; `exit-code` field present +| Receipt round-trip | receipt's `declaration-sha256` equals the manifest's `sha256` for the same file +| Determinism | two runs, byte-identical stdout and receipt; run in `/tmp` symlink and from a subdirectory; `LC_ALL=tr_TR.UTF-8` must not change bytes +| `--jobs 8` | same report bytes as `--jobs 1` +| Dry-run | `dust sweep --dry-run` reports candidates, mutates nothing, exit code = would-be code +| K9 | valid Kennel/Yard/Hunt fixtures pass; a Hunt fixture with a valid signature passes; `verify` reports `paired_xfile` resolution +|=== + +Negative (must fail *with the right code*): + +[cols="1,2",options="header"] +|=== +| Test | Assertion + +| `contractile` with no arguments | exit `2`; usage on stderr +| `contractile lust run` | exit `2` (no such verb); message names the deprecation +| `contractile must frobnicate` | exit `2` +| Missing declaration | exit `3` +| Runner missing | exit `3` (and `verify` exit `1` for a partial Trident) +| `nickel typecheck` failure in the runner | exit `3` +| Runner `schema_version` newer than supported | exit `5` +| Probe requiring network without `--allow-network` | exit `4`; no subprocess spawned +| Probe requiring write without `--allow-write` | exit `4`; filesystem unchanged (checked with a pre/post tree hash) +| K9 file missing `K9!` magic / unknown leash / Hunt without signature | exit `3` (`7` for signature) — mirror the K9 corpus's named reasons +| Manifest hash mismatch (mutated byte in a declared file) | exit `7` +| Floating K9 component (no `paired_xfile`) | exit `1` from `verify` +| `lust/` directory present | reported by `verify`; never dispatched +| Timeout (probe sleeps past `--timeout`) | exit `6` non-strict, `1` strict; child process group killed (assert no orphan) +| `--strict` with a `verification:`-only item | exit `1` (manual discharge is not evidence) +| `--format=json` with ANSI colour | impossible: `--json` forces `--color=never` +| Coaptation separation | the CLI never creates/modifies `.machine_readable/coaptation/**`; asserted by snapshotting the tree before/after every test +| `build/contractile.just` | `verify` reports it as generated; no test writes it +|=== + +=== Harness requirements + +. `contractile self-test` runs the embedded corpus (validator self-test, like + `deed_lint.py --self-test`) and returns `0`/`1`. +. A CI lane (this repository and, via the template, downstream) runs: self-test + → corpus (valid must pass, invalid must fail) → lint every committed + declaration in the repository → determinism check. +. Every fixture is registered in a manifest file (S-expression or A2ML, + following `k9-ecosystem/conformance/manifest.a2ml`) stating `expect` and + `reason`; an unregistered fixture fails the lane. +. Negative tests assert the *specific* exit code, not just "non-zero". + +== Prioritized implementation plan + +Sequencing is a hard gate, not a schedule. No phase may begin before its entry +criteria are met; no phase may be skipped. Phase P0 has no code. + +[cols="1,2,2,2",options="header"] +|=== +| Phase | Deliverable | Entry criteria | Exit criteria + +| *P0 — Ratify* +| Rulings closed (<>); RFC → `1.0.0`; ADR-0002 → Accepted; `CODEOWNERS` updated; implementation location decided +| This audit + RFC merged +| Every open choice decided or explicitly deferred with an owner; *no code written* + +| *P1 — Skeleton (no execution)* +| Crate skeleton; grammar/help/`--version`; `list` from `INDEX.a2ml`; `typecheck`; `verify` (files/cardinality/manifest hashes/K9 structural); `self-test` harness; exit-code table implemented for `2/3/5/7`; golden tests for text|json|a2ml +| P0 exit +| `contractile list/typecheck/verify` pass the positive corpus and the negative `2/3/5/7` cases; zero probe execution possible in code (no shell-out path exists yet) + +| *P2 — Probe engine (gated)* +| Probe parsing (legacy + structured), capability ladder, refusal, timeouts, process-group kill, sanitised env, `--no-probe`, `--dry-run`; `must run`, `trust verify` +| P1 exit + O5/O6 decided +| Gating verbs produce correct codes `0/1/4/6`; negative permission tests prove no subprocess/write/network without grants; determinism goldens pass + +| *P3 — Advisory verbs and reporting* +| `adjust check`, `dust find`, `intend run/progress/horizon`, ` list`; advisory-vs-strict policy; run receipts (`--receipt`) +| P2 exit +| Advisory verbs never gate without `--strict`; receipt determinism + hash round-trip tests pass; Coaptation-separation test passes + +| *P4 — Mutating verbs (opt-in only)* +| `adjust fix --apply`, `dust sweep --apply --approve `, `bust check`/`drill` in scratch worktree +| P3 exit + O10/O11 decided (sandboxing, signatures) +| Pre/post tree-hash tests prove no mutation without grants; per-item approval recorded; drill runs never touch the working tree by default + +| *P5 — Generated Justfiles* +| `gen-just` per <>; regenerate `build/contractile.just` **in this repository only**; mark generated files +| P4 exit + O9 decided +| Regeneration is byte-stable; a second regeneration is a no-op diff; the generated header matches the deployed convention + +| *P6 — Packaging and estate rollout* +| `install.sh`, container image, SBOM, signing, crate publish (name per O2), `docs/man`, template/standards wiring so downstream repos get the lane +| P5 exit + O2/O13 decided +| A clean-machine install from the published artefact runs `self-test` green; downstream template references the installed binary (reference-not-duplicate) +|=== + +Dependencies between open choices and phases (the critical path): + +* O5 (declaration→schema binding) gates P2 and everything after it. +* O3 (exit-code canon) gates P1's public contract (but not its internals). +* O1 (implementation home) and O2 (crate name) gate P1 and P6 respectively. +* O8 (manifest write policy) and O11 (signature policy) gate P4. +* Ruling 2 (two vs four files per verb) gates P1's `verify` pass criterion. + +== Unresolved maintainer choices + +Each item blocks something. Recommendations are offered only to make the choice +cheap; none is decided here. + +=== Inherited from the specification (`CONTRACTILE-SPEC.adoc` Open Rulings) + +[cols="1,3,2",options="header"] +|=== +| ID | Question | Blocks + +| R1 | The 620-file s-expression `MUST.contractile` family: recognise as inherited Universal Invariants and hoist, or sanction as a second family? | Scope of `verify`; whether the CLI must read `.contractile` at all +| R2 | Two files or four per verb directory (Trident)? | `verify` pass criterion; whether `.k9.ncl`/`.manifest.a2ml` are required +| R3 | Runner coverage 2–10%: build the runners, or downgrade the pairing requirement? | Whether `run` is an error (`3`) or a warning when a runner is absent +| R4 | Ratify the k9 relocation to `.machine_readable/svc/k9/` and fix the spec's layout diagram | Where the CLI looks for K9 components +| R5 | WCAG 2.1 AA or 2.2 AA for `adjust` | `adjust` defaults and reporting text +| R6 | Cardinality enforcement one-per-repo; are template copies and ecosystem farms exempt? | `verify`'s duplicate rule +| R7 | eNSAID's five-tier taxonomy (`lust` live, `k9` as peer tier): profile of this spec, or separate system? | Whether the CLI must tolerate non-canonical declarations +| R8 | `contractiles-v1` profile: keep as field-level authority, add `adjust`/`bust`, or scope it? | Which field set `typecheck` enforces +| R9 | Antifile proposal: seventh artefact or close it? | Whether `list` reports a seventh verb family +|=== + +=== New choices this RFC cannot make + +[cols="1,3,2,2",options="header"] +|=== +| ID | Question | Options | Recommendation (non-binding) + +| O1 | Where does the implementation live? | (a) this repo under `cli/` (spec's claim) — requires a `root-allow.txt` amendment; (b) sibling repo `hyperpolymath/contractile-cli`; (c) a subcommand of `reposystem`; (d) under an already-allowlisted `src/cli/` or `tools/` | (a) *if* the root allowlist is amended as a governance act, because the spec, the generated files, and the submodule path all point here. (d) is the cheapest to land today; (b) is cleanest for release cadence +| O2 | Crate/binary name and namespace | `contractile` (verified unclaimed: crates.io returns `crate does not exist`, 2026-10-03); `contractile-cli`; `contractiles` (repo name, but the repo is not the binary) | `contractile` for both crate and binary; check name availability and reserve before P6 +| O3 | Exit-code canon | (a) the table in <>; (b) collapse to `contractiles-v1`'s `0/1` + sysexits for usage (e.g. `64`); (c) `0/1/2` only (bash precedent) | (a). CI can distinguish "contract broken" from "you broke the contract" only with more than two codes; (c) loses the tamper signal +| O4 | Whether `probe` strings may ever be executed without an explicit grant in a *trusted* repo | (a) always require `--allow-subprocess`; (b) auto-grant inside a signed repo with Hunt-tier runners; (c) auto-grant for `must`/`trust` only | (a) for v1 — simplest to reason about, and the CI default (`--no-probe` for untrusted PRs) makes it practical +| O5 | Declaration→runner schema binding (field vocabulary + normalisation) | (a) CLI reads xfile fields, maps via a published versioned table; (b) change xfiles to match runner schemas; (c) change runner schemas to match xfiles | (a) — (b)/(c) both invalidate the deployed corpus +| O6 | Probe shell | (a) `bash -uc` (matches Justfile `set shell := ["bash","-uc"]`); (b) `sh -c` (POSIX, fewer features); (c) `bash -euo pipefail -c` | (a); the estate's recipes already assume bash +| O7 | Run-receipt schema name and field spelling | `hyperpolymath.contractile-run/1` vs `.../contractile-receipt/1`; hyphenated vs underscore keys (the a2ml profile uses hyphens; runner Nickel uses underscores) | Confirm with `standards` so the receipt name does not collide; hyphens to match A2ML convention +| O8 | May the CLI ever write manifests? | (a) never (v1); (b) `manifest --update` behind `--apply`; (c) `manifest --update --sign` only | (a) for v1; revisit with the signing policy +| O9 | `gen-just` input contract | (a) derive from declarations + runner `run` policy; (b) explicit `gen-just` block in the runner; (c) keep the current generated file frozen and deprecate `gen-just` | (b) — declarative, testable, no invention; but (a) matches the 205 deployed headers +| O10 | `bust drill` isolation | (a) scratch git worktree (default); (b) temp clone; (c) container; (d) in-place with confirmation | (a) for v1; (c) once the container tier publishes the CLI +| O11 | Signature requirement for Hunt-tier execution | (a) refuse unsigned Hunt always; (b) warn + allow with `--force-unsigned`; (c) require signature only for estate-wide/CI use | (a) for v1 — `k9-sign` exists, so signing is achievable +| O12 | Declaration format horizon | (a) `.a2ml` only; (b) accept `.a2ml` and `.deed`; (c) `.deed` first | (b) — the estate is mid-migration (`deed-conformance.yml`), and hard-coding `.a2ml` guarantees a second migration +| O13 | Required CI context name for the lane | e.g. `K9-SVC contractile validation` (matches the existing required context in `standards`) vs a new `contractile self-test` context | Reuse the existing required context so no branch deadlocks; never rename a required context without a coordinated change (the `k9-contractile.yml` header documents this failure mode) +| O14 | Windows/macOS support level | (a) Linux/CI only; (b) best-effort POSIX; (c) full cross-platform | (a) for v1; state it in `--version` and docs, so nobody discovers it by failure +|=== + +== Consequences + +=== Positive + +* One executor for the vocabulary, one exit-code contract, one receipt format. +* Schema validation (Nickel) becomes real for the first time in the estate: a + declaration that does not match its runner fails the gate as `typecheck`. +* Capability-gated probes convert "declaration as remote code execution" into a + reviewable, testable permission surface. +* `gen-just` becomes reproducible; 205 generated files stop being fossils. +* The Coaptation provenance line gains a partner: hashes the CLI verifies + (`--receipt`) are the same hashes the comparator cites. + +=== Negative + +* Two more artefacts to maintain (CLI + receipt schema), and a compatibility + matrix against the spec. +* Making the runner mandatory (R3) will fail many repos that today only carry + declarations — that is the point, but it needs a migration story and a + transitional `--strict=off` period. +* Determinism requirements constrain future features (no timestamps, no + streaming default, sorted output), and will feel restrictive in interactive + use. +* The declaration→schema binding (O5) is a genuine invention risk: if the + normalisation table is wrong, the CLI will reject valid deployed files. + +=== Neutral + +* `build/contractile.just` stays frozen until P5; its stale recipes remain + inert in the meantime. +* This RFC is independent of the `.a2ml` → `.deed` migration, but O12 must be + decided before P2 to avoid hard-coding the legacy extension. +* The CLI does not decide anything. Every gate it applies is a statement about + the repository; the response (variance, amendment, anchor drop) stays human. + +== References + +. `hyperpolymath/standards` — `docs/CONTRACTILE-SPEC.adoc` v1.2.1 (2026-09-01), Status: Draft — awaiting owner ratification. Verb set, layout, `_base.ncl`, probe contract, k9 exception, registry, CLI binding, deployment reality, Open Rulings 1–9. +. `hyperpolymath/standards` — `.machine_readable/contractiles/**` (the reference Trident: `_base.ncl`, `INDEX.a2ml`, six verb directories with `.ncl`, `.k9.ncl`, `.manifest.a2ml`), and `scripts/run-mustfile.sh`, `scripts/check-mustfile-structure.sh` (the de-facto `must run`), `.github/workflows/k9-contractile.yml`. +. `hyperpolymath/rsr-template-repo` — `.machine_readable/contractiles/**` (same Trident shape, deployed by template). +. `hyperpolymath/a2ml` — `docs/CONTRACTILES-A2ML-V1.adoc` (the `contractiles-v1` profile: required sections/fields, JSON emission, exit codes `0/1`); `1-formats/deed/spec/DEED-GRAMMAR-SPEC.adoc` and `1-formats/deed/tools/{deed_lint.py,a2ml_to_deed.py}` (conformance-lane precedent; `.a2ml` → `.deed` migration). +. `hyperpolymath/k9-ecosystem` — `conformance/{manifest.a2ml,valid/*,invalid/*}` (fixture-manifest form), `k9-sign/{install.sh,Cargo.toml,src/main.rs}` (packaging precedent), `deno/.machine_readable/self-validating/template-{kennel,yard,hunt}.k9.ncl` and this repository's `.machine_readable/self-validating/` copies (tier semantics). +. `hyperpolymath/cicd-squabbler` (+ `metadatastician/*`) — `.machine_readable/coaptation/coapt.sh`, `coapt.ncl` (the Coaptation receipt: schema `hyperpolymath.coaptation/1`, provenance hash line, exit `64`). +. This repository — `.machine_readable/contractiles/**` (declarations), `build/contractile.just`, `Justfile`, `.github/hooks/validate-{a2ml,k9}.sh`, `scripts/{validate-template,check-root-shape}.sh`, `.machine_readable/root-allow.txt`. +. `hyperpolymath/standards` — `docs/SIGNING-POLICY.adoc` (who signs what), `docs/ADR-001-k9-relocation-to-svc.adoc`, `.github/workflows/deed-conformance.yml`. +. Audit: `docs/reports/audit/contractile-cli-audit-2026-10-03.adoc` (this repository). +. ADR: `docs/decisions/0002-contractile-cli-spec-first.adoc` (this repository). + +== Document history + +[cols="1,1,2",options="header"] +|=== +| Version | Date | Change + +| `0.1.0-draft` | 2026-10-03 | Initial draft. Written after `AUDIT-CLI-2026-10-03` established that no `contractile` executable exists in the repository or the published estate, and that `build/contractile.just` is generated output. Awaiting maintainer ratification. +|=== diff --git a/docs/proposals/standards-CONTRACTILE-SPEC-v1.3.0-amendment.adoc b/docs/proposals/standards-CONTRACTILE-SPEC-v1.3.0-amendment.adoc new file mode 100644 index 0000000..02561c8 --- /dev/null +++ b/docs/proposals/standards-CONTRACTILE-SPEC-v1.3.0-amendment.adoc @@ -0,0 +1,314 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) += Amendment Draft: `CONTRACTILE-SPEC.adoc` v1.2.1 → v1.3.0 +:amend-id: STANDARDS-AMEND-0001 +:target: hyperpolymath/standards · docs/CONTRACTILE-SPEC.adoc +:date: 2026-10-03 +:status: Draft — cannot be applied until the worksheet rows are decided +:toc: +:toclevels: 2 +:sectnums: + +Ready-to-paste amendment text for the normative specification. Written here +because *this repository cannot push to `hyperpolymath/standards`*; applying it +is a `standards` maintainer action. + +Rules for applying: each amendment gives the anchor section, the exact text to +find, and the replacement text. Where a ruling is still open, the amendment +carries a *[REQUIRES R#]* marker with the variants drafted — do not paste a +variant whose ruling is undecided. + +== How to apply + +[source,bash] +---- +git clone git@github.com:hyperpolymath/standards.git && cd standards +git checkout -b docs/contractile-spec-v1.3.0 +# apply A1–A10 below to docs/CONTRACTILE-SPEC.adoc +git commit -S -m "docs(contractile): v1.3.0 — close rulings, add declaration↔runner binding, bound the CLI claim" +gh pr create --base main --title "docs(contractile): CONTRACTILE-SPEC v1.3.0" \ + --body-file /path/to/this-amendment.md +---- + +Note for whoever applies it: `docs/CONTRACTILE-SPEC.adoc` has a byte-identical +mirror in `ideas-to-alphas/standards/`. Per the spec's own authority table the +mirror is "a copy, not a fork — de-duplicate it; do not reconcile it". Update +the mirror in the same PR or delete the reference to it. + +== A1 — Version, status, and the unratified-CLI claim + +*Anchor:* the header block. + +[source] +---- +BEFORE +v1.2.1, 2026-09-01 +:status: Draft — awaiting owner ratification +---- + +[source] +---- +AFTER +v1.3.0, 2026-XX-XX +:status: Draft — rulings 1–9 closed; awaiting owner ratification of the CLI binding (§Declaration↔runner binding) +---- + +Append to the version history: + +[source] +---- +=== v1.3.0 — 2026-XX-XX + +Rulings 1–9 closed per the owner's decision (see §Open Rulings, now §Rulings +closed). Adds §Declaration↔runner binding: the normalisation table between the +deployed A2ML declaration vocabulary and the Nickel runner schemas. Bounds the +CLI claim: no `contractile` implementation is published as of 2026-10-03; the +invocation table in §CLI Binding is now explicitly a *specification* of the +required interface, not a description of a shipped tool. Re-measured deployment +(replacing the 2026-09-01 figures) is recorded in §Deployment Reality. +---- + +== A2 — §Preamble: the CLI location claim (the important correction) + +*Anchor:* ¶ 3 of §Preamble and Scope. + +[source] +---- +BEFORE +The `contractile` CLI (located at +`reposystem/contractiles/cli/`) reads both files together, validates the +declaration against the runner's schema contract, and executes configured +probes. +---- + +[source] +---- +AFTER +A `contractile` CLI reads both files together, validates the declaration +against the runner's schema contract, and executes configured probes. + +CAUTION: *No `contractile` implementation is published in the estate as of +2026-10-03.* The previously cited location (`reposystem/contractiles/cli/`, a +workstation path inside a submodule checkout of `hyperpolymath/contractiles`) +does not exist in any published repository; audit `AUDIT-CLI-2026-10-03` and +RFC-0001 in `hyperpolymath/contractiles` record the search. §CLI Binding is +therefore normative for the interface and *vacuously descriptive* of nothing +shipped. Enforcement presently happens in ad-hoc shell/bun re-implementations; +see §Deployment Reality. +---- + +== A3 — §Directory Layout: fix the k9 fossil and ring-fence file cardinality + +*Anchor:* the layout diagram in §Directory Layout. Replace the `k9/` branch +(carried over from before ADR-001) with nothing, and add the note below the +diagram. + +[source] +---- +AFTER the diagram, replacing the note that follows it: + +NOTE: `k9/` does NOT live in this tree. Per ADR-001 (2026-04-18) it lives at +`.machine_readable/svc/k9/`. The `k9/` branch that appeared in the v1.0.0–v1.2.1 +diagram was a fossil; it is removed here. [REQUIRES R4 to ratify; variant (b) +keeps the branch and annotates it deprecated.] + +Files per verb directory: [REQUIRES R2 — paste one of] +(a) exactly four: `file.a2ml`, `.ncl`, `.k9.ncl`, + `.manifest.a2ml` (the Trident); +(b) exactly two: `file.a2ml`, `.ncl`; +(c) four as the target, two as the transitional minimum, with conformance + reported against (b) until a named date. +---- + +== A4 — §Naming Rules: add the identity rule + +Append as rule 5: + +[source] +---- +5. **Item identity is carried by the heading, not a field.** Every declared + item is introduced by a heading `### `; the `id` used by the runner + schemas is that heading text. Declarations MUST NOT repeat it as an `id` + field. (Consequence: the normalisation table in §Declaration↔runner binding + maps heading → `id` for all six verbs.) +---- + +== A5 — NEW SECTION: §Declaration↔runner binding + +Insert after §Naming Rules. This is the amendment that unblocks the CLI +(RFC-0001 O5) and is the only place a second implementation can be held to the +same reading. + +[source] +---- +[[declaration-runner-binding]] +== Declaration↔runner binding + +The declarations (A2ML) and the runners (Nickel) were authored independently and +do not yet share a field vocabulary. This section is the normative translation. +A conforming implementation MUST apply this table; it MUST NOT invent extra +aliases. + +[cols="1,1,2,2",options="header"] +|=== +| Verb | Declaration carries | Runner schema field | Binding rule + +| all | `### ` heading | `id` | heading text, verbatim +| must, trust | `- description:` | `description` | copied +| must, trust | `- run:` | `probe` | copied into `probe`; `status` defaults to `declared` +| must, trust | `- severity:` | `severity` | copied; default `critical` (must) / `high` (trust) +| trust | `- verification:` | — | *manual discharge*: never executed, reported `unmeasured`, gates only under `--strict` +| adjust | `- tolerance:` | `probe` (probe REQUIRED by schema) | *[REQUIRES O5 decision]* either (i) `tolerance` is descriptive and `adjust` items are reporting-only unless a `probe` is present, or (ii) `tolerance` MUST be accompanied by a `probe` and the schema gains a default +| adjust | `- corrective:` | `fix` | copied into `fix` +| adjust | `- severity: advisory` | `severity` | *outside `severity_core` {critical, high, medium, low}*: implementations MUST accept `advisory` as an alias for `low` until the enum is extended (spec change, not an implementation choice) +| adjust | (no discharge field) | — | deployments have `tolerance`/`corrective` and no `probe`; see the row above +| dust | `- run:` / `- verification:` | `probe` | as must/trust +| dust | `- target:` / `- reason:` | `target` / `reason` (both REQUIRED) | *declarations predating this spec carry neither*; implementations MUST treat them as `unmeasured` rather than invalid until the migration date in §Migration Notes +| bust | `- class:` | `class` enum | deployed values outside the enum (`template_processing`, `synchronization`, `contractile_format`) MUST be accepted as `other` until the runner enum is extended; the extension is a spec change, not an implementation choice +| bust | `- injection_probe:` / `- recovery_probe:` | same names | copied +| intend | `- probe:`, `- status:` (intents) | `probe`, `status` | copied; `status` values are the intent enum +| intend | `- horizon:`, `- status:` (wishes) | `horizon`, `status` | copied; wishes MUST NOT be probed. *Heading depth is part of the grammar:* intents are `### ` under `## Committed Next-Actions`; wishes are `#### ` under a `### ` group. A flat heading scan mis-parses wishes as horizons — implementations MUST respect depth +|--- +|=== + +A declaration field with no row in this table is an error under `--strict` and +a warning otherwise. A *discharge* (run/probe/verification) is the only thing +that can make an item `verified`; a `tolerance`, `corrective` or other +descriptive field never can. +---- + +== A6 — §Probe Contract: accept both forms and bound execution + +Append after the "Current form (legacy)" and "Target form (structured)" blocks: + +[source] +---- +=== Acceptance and execution rules + +1. Implementations MUST accept both the legacy `probe | String` form and the + structured `probe_schema` form. The migration in the next subsection is + *additive*; nothing in the deployed corpus is invalidated by it. +2. `allowed_exit_codes` (structured) is authoritative; the legacy form is + exactly `[0]`. An unexpected exit status is a failure of the item, never a + crash of the runner. +3. `permission_class` is a *lower bound*. An implementation MUST refuse + (non-zero, item reported as refused) rather than run, downgrade, or skip a + probe whose declared class exceeds the granted capability set. Open refusal + is the only conforming behaviour. +4. Every probe MUST be bounded by `timeout_seconds` with a default of 300; on + expiry the implementation MUST kill the whole process group and report the + item *indeterminate* — never `pass`. +5. A `verification:` item is a manual discharge. Implementations MUST NOT + execute it and MUST NOT report it as verified; it is `unmeasured`. +6. Deterministic output: for identical inputs, an implementation's report MUST + be byte-identical. Wall-clock times, durations, PIDs, hostnames and locale + formatting are not permitted in the default report. +---- + +== A7 — §CLI Binding: mark the table normative, pin exit codes, refuse `lust` + +*Anchor:* the `contractile` invocation table and the note that follows it. +Insert before the table: + +[source] +---- +NOTE: As of 2026-10-03 the invocations below describe the *required* interface. +No implementation is published (see §Preamble); a conformance claim against +this section is a claim about an implementation's behaviour, not about a file +in a repository. +---- + +Replace the closing NOTE with the exit-code canon and the `lust` rule: + +[source] +---- +=== Exit codes + +Conforming implementations MUST distinguish at least: `0` success; `1` verdict +failure (a blocking item failed); `2` usage error; `3` input error (declaration +or runner missing/unparseable/schema-invalid); `4` refused (capability not +granted, or unsigned Hunt-tier component); `5` tool error (unsupported runner +schema version, internal error); `6` indeterminate (probe timed out or could +not be evaluated); `7` integrity failure (manifest hash mismatch or tampered +component). A gating verb MUST NOT return `0` while any item is skipped, +refused or unmeasured. + +*`lust` MUST NOT be dispatched.* The verb was retired 2026-04-18; an +implementation receiving `contractile lust …` exits `2` and names the +replacement (`intend`). `lust/` directories are drift and MUST be reported. +---- + +== A8 — §Registry: manifest and hash semantics + +Append to §Registry (`INDEX.a2ml`): + +[source] +---- +=== Manifest verification semantics + +A `.manifest.a2ml` lists `sha256` and `size_bytes` per file. Implementations +MUST apply these readings: + +* digest = `sha256` over the file's raw bytes, lowercase hex (the same identity + used by the coaptation provenance line); +* a matching entry verifies; a mismatching entry is an *integrity failure*; +* the sentinel `pending-first-verify` means *unverified*, not valid: report it + as a warning, and as a failure only under strict mode; +* an absent manifest is a warning, unless `INDEX.a2ml` declares one for that + verb, in which case it is a structural failure. + +Writing manifests is out of scope until the signing policy settles; an +implementation MUST NOT rewrite `sha256` values as a side effect of a check. +---- + +== A9 — §Deployment Reality: re-measured, with the method difference stated + +Replace the opening method sentence with: + +[source] +---- +Method: v1.2.1 measured a local disk with `find`, including duplicate checkout +trees. The 2026-10-03 re-measurement (`CENSUS-2026-10-03`, in +`hyperpolymath/contractiles`) used the GitHub code-search index, public +repositories only. The two corpora are not identical; both are reported rather +than merged. + +Headline 2026-10-03: runner coverage 24.1% overall (`must` 16.4%, `trust` 9.7%, +`adjust` 45.7%, `dust` 26.3%, `bust` 77.1%, `intend` 16.9%); 59 `_base.ncl`; +56 `INDEX.a2ml`; 132 files still carrying `pending-first-verify`; 42 +`Intendfile.a2ml` and 4 `Lustfile.a2ml` survivors; the required check context +`K9-SVC contractile validation` present in 3 repositories. +---- + +== A10 — §Open Rulings → §Rulings closed + +Replace §Open Rulings (1–9, excluding any row still deferred) with: + +[source] +---- +== Rulings closed + +Ruling 1 (the MUST.contractile family): [decision] +Ruling 2 (files per verb directory): [decision] +Ruling 3 (runner coverage): [decision] +Ruling 4 (k9 relocation): [decision — ADR-001 ratified, diagram fixed in v1.3.0] +Ruling 5 (WCAG baseline): [decision] +Ruling 6 (cardinality and exemptions): [decision] +Ruling 7 (eNSAID taxonomy): [decision] +Ruling 8 (contractiles-v1 profile): [decision] +Ruling 9 (Antifile): [decision] + +Each row records the date and authority of the decision; a decision without a +date is not a ruling and MUST NOT be transcribed here. +---- + +== Cross-references + +* `hyperpolymath/contractiles` — `docs/proposals/RFC-0001-contractile-cli.adoc`, + `docs/decisions/0002-contractile-cli-spec-first.adoc`, + `docs/governance/planning/contractile-cli-ratification-worksheet.adoc`, + `docs/reports/audit/contractile-estate-census-2026-10-03.adoc`. +* Audit: `AUDIT-CLI-2026-10-03` (same repository). +* Signing: this amendment PR must be signed per + `standards/docs/SIGNING-POLICY.adoc` (interactive agents: SSH signing; + unattended writers: API/`signed-push`). diff --git a/docs/reports/audit/contractile-cli-audit-2026-10-03.adoc b/docs/reports/audit/contractile-cli-audit-2026-10-03.adoc new file mode 100644 index 0000000..8af2e9e --- /dev/null +++ b/docs/reports/audit/contractile-cli-audit-2026-10-03.adoc @@ -0,0 +1,426 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) += Audit: Does a `contractile` executable exist? +:audit-id: AUDIT-CLI-2026-10-03 +:date: 2026-10-03 +:status: Final (audit) — informs RFC-0001-contractile-cli +:toc: +:toclevels: 3 +:sectnums: + +Question put to this audit: *is there an existing `contractile` executable — +in this repository or anywhere in the estate — that the repository's own +references can be resolved against?* The audit also enumerates the shell and +Python entrypoints that could be mistaken for it, and checks whether +`build/contractile.just` is generated output. + +== Verdict + +* *No `contractile` executable exists in this repository.* There is no `cli/` + directory, no Cargo workspace, no Python package, no `bin/`, and no + executable named `contractile` (tracked or ignored). +* *No published implementation was found in the estate.* No GitHub repository, + no crates.io crate, and no CI workflow invokes a `contractile` binary. + The name appears in this repository only in generated-file headers and in a + Justfile comment (three occurrences, all aspirational). +* *`build/contractile.just` is generated output.* The evidence is unanimous and + is recorded in <>. +* *The de-facto enforcement in the estate is ad hoc.* Every CI gate that claims + to enforce a contractile today either checks file presence or re-implements a + verb in bash/bun/just, with mutually inconsistent exit codes. +* *A normative specification exists but is unratified, and it names a CLI that + is not in any published artifact.* `standards/docs/CONTRACTILE-SPEC.adoc` + v1.2.1 (Status: Draft — awaiting owner ratification) is the authority; its + `<>` section cites a local workstation path for the CLI. + +Therefore the pre-condition for coding is unmet: the correct next artefacts are +an RFC (proposed) and an ADR (proposed), not an implementation. See +`docs/proposals/RFC-0001-contractile-cli.adoc` and +`docs/decisions/0002-contractile-cli-spec-first.adoc`. + +== Method and evidence classes + +[cols="1,3",options="header"] +|=== +| Class | What was examined + +| In-repo (byte-level) +| `find`/`grep` over the working tree at commit + `c8ae4645bb3c17ecaa3a9f27496daffdbb6ef9c4` (2026-10-01), excluding `.git/`; + file modes; `sha256sum` of the artefacts cited in <>. + +| In-repo (history) +| `git log --all`, `git branch -a`, deleted-file search across all refs. This + checkout carries a single squashed commit, so history cannot refute earlier + existence; it can only fail to confirm it. + +| Published ecosystem +| GitHub code/repo search and repository tree listings (API), plus crates.io. + Repository contents were read at their `main` branch on 2026-10-03. + +| Specifications +| `standards/docs/CONTRACTILE-SPEC.adoc` (v1.2.1, 2026-09-01), + `a2ml/docs/CONTRACTILES-A2ML-V1.adoc`, `ensaid-spec/spec/07-contractiles.adoc`, + `.machine_readable/coaptation/coapt.{sh,ncl}` (cicd-squabbler), + `standards/.machine_readable/contractiles/**` (the Trident reference), + `k9-ecosystem/conformance/**`, `standards/1-formats/deed/tools/**`. +|=== + +== In-repository findings + +[[f1-none]] +=== F1 — No executable of any kind named `contractile` + +The tree contains 18 executable files and 21 files with a shebang; none is +`contractile`, and none lives in a `bin/` or `cli/` directory (no `bin/` exists). +There is no `Cargo.toml`, no `pyproject.toml`, no `setup.py`, no +`requirements*.txt`, and no `*.py` file anywhere in the repository. Python is +present only as a mise-managed toolchain entry (`mise.toml`), never as a +program surface. + +[[f2-generated]] +=== F2 — `build/contractile.just` is generated; do not hand-edit it + +Four independent lines of evidence: + +. The file's own header (lines 1–4) reads: ++ +[source] +---- +# Auto-generated by: contractile gen-just +# Source directory: contractiles +# Re-generate with: contractile gen-just --dir contractiles +# SPDX-License-Identifier: MPL-2.0 +---- +. The root `Justfile` imports it as generated (`Justfile:17–19`: + `# Import auto-generated contractile recipes (must-check, trust-verify, etc.)` + / `# Re-generate with: contractile gen-just` / `import? "build/contractile.just"`). +. No generator exists. Nothing in this repository, and nothing found in the + published estate, implements `contractile gen-just`; the file cannot be + regenerated today, and its recipes are therefore frozen at an unknown vintage. +. The generated recipes do not match the declarations they claim to derive from: + the generated `must-check` aggregates `must-license-present`, + `must-readme-present`, `must-spdx-headers`, `must-no-banned-files`, and cites + `Source: Dustfile.a2ml` / `Source: Intentfile.a2ml` / `Source: Mustfile.a2ml` — + sources that are not the declarations in `.machine_readable/contractiles/` + (which use `### ` blocks with `- run:` fields). The file is a fossil of an + earlier generator input shape. + +The same generated header and the same `--dir` invocation appear *205 times* +across the published estate (GitHub code search, 2026-10-03), including +`hyperpolymath/ephapax`, `hyperpolymath/echidna`, +`hyperpolymath/k9-ecosystem/{deno,ex,gleam,rs,pandoc}/contractile.just`, and +`hyperpolymath/kitchenspeak/build/contractile.just`. Two variants of `--dir` +occur (`contractiles` and `.machine_readable/contractiles`), which is consistent +with a real generator that once ran on a workstation and was never published. + +*Consequence:* `build/contractile.just` is a build artefact with no build. +It must be treated as read-only, marked as such, and regenerated only by the +ratified `gen-just` implementation (RFC-0001 §gen-just). Until then, editing it +by hand is drift. + +[[f3-shell]] +=== F3 — Shell entrypoints exist, but none is the CLI + +[cols="2,3",options="header"] +|=== +| Path | What it actually does + +| `.github/hooks/validate-a2ml.sh` +| Validates `.a2ml`/`.deed` manifests (identity/version/attestation heuristics). + Not contractile-aware; explicitly *exempts* contractile-shaped files. + +| `.github/hooks/validate-k9.sh` +| Validates `K9!` magic, `pedigree`, leash ∈ {kennel,yard,hunt}, SPDX header. + The closest thing in-tree to a K9 validator, and a de-facto reference for + CLI K9 checks. + +| `scripts/validate-template.sh` +| RSR skeleton compliance (required files/dirs). Exit 0/1/2. + +| `scripts/check-root-shape.sh` +| Root allowlist enforcement against `.machine_readable/root-allow.txt`. + Exit 0/1/2. + +| `scripts/invariant-path.sh` +| Overlay tooling for the Invariant Path. +| `build/setup.sh` +| Developer environment bootstrap. +| `container/ct-build.sh`, `container/entrypoint.sh` +| Container build/sign pipeline and service entrypoint. +| `.machine_readable/scripts/**`, `session/**`, `tests/**`, `benches/**` +| Maintenance, forge sync, session dispatch, test harnesses. +|=== + +None of these dispatches on the six verbs, and none reads a `.ncl` +runner. + +[[f4-norunners]] +=== F4 — This repository has no contractile runners at all + +`find . -name '*.ncl'` returns 8 files; **all eight** are K9 templates +(`.machine_readable/self-validating/*.k9.ncl` and `.../examples/*.k9.ncl`). +There is no `_base.ncl`, no `INDEX.a2ml`, no `.ncl` runner, no +`.k9.ncl`, and no `.manifest.a2ml` anywhere in this repository. + +The declarations are also at the pre-spec flat paths +(`.machine_readable/contractiles/Mustfile.a2ml`, `Trustfile.a2ml`, +`Adjustfile.a2ml`, `Intentfile.a2ml`, plus `bust/` and `dust/` subdirectories) — +matching the spec's "flat, no verb dir" drift bucket (158 deployments measured), +not the canonical `.machine_readable/contractiles//` layout. + +This is worth stating plainly: *the repository that names the vocabulary is not +itself conformant to the layout in the specification that defines it, and is +behind the downstream template it seeded* (`rsr-template-repo` and +`standards` both ship the full four-file Trident per verb, including +`.manifest.a2ml` and `_base.ncl`). + +[[f5-references]] +=== F5 — Every in-repo `contractile` reference is a forward reference + +Three occurrences, none executable: + +[cols="2,3",options="header"] +|=== +| Location | Text + +| `Justfile:18` | `# Re-generate with: contractile gen-just` +| `build/contractile.just:1` | `# Auto-generated by: contractile gen-just` +| `build/contractile.just:3` | `# Re-generate with: contractile gen-just --dir contractiles` +|=== + +`.machine_readable/contractiles/Bustfile.a2ml` additionally documents a CLI +grammar in a comment (`contractile bust check`, `contractile bust drill`) that +nothing implements. + +[[f6-hash]] +=== F6 — Artefact hashes for the record + +[[hashes]] +[cols="3,1",options="header"] +|=== +| Path | sha256 + +| `build/contractile.just` | `86e4b7b6afc9b3510ed51b041aeabe09d9d95016be8d78e3288bee6b940fdb52` +| `Justfile` | `2ce2099ba1e32c210a895d18a557ff478371668a5fba8552137f7d384e369303` +| `.machine_readable/contractiles/Justfile` | `089b1e331a4645e7d85c11ce606b4d46b78902fa0a0a291348df008d30a1018f` +| `.machine_readable/contractiles/Mustfile.a2ml` | `0b13359322909f36c7e22eb4169472e54a80d020a021bdb986be248228719c34` +| `.machine_readable/contractiles/Trustfile.a2ml` | `80c66721189b9e4c5181d11143df45546b6f4006aaaff8d4c253001fc2db2fe7` +|=== + +NOTE: `Justfile` and `.machine_readable/contractiles/Justfile` have *different* +inodes, while `Dustfile.a2ml` asserts they are hardlinks. Git cannot preserve +hardlinks, so that check is unsatisfiable in any clone; recorded here because a +future `contractile dust` implementation must decide whether to report it. + +== Ecosystem findings + +[[e1-norepo]] +=== E1 — No `contractile` binary, crate, or repository is published + +|=== +| Probe | Result + +| GitHub repo `hyperpolymath/contractile` | 404 (does not exist) +| GitHub repo `hyperpolymath/descriptiles` | 404 (the counterpart is not a repo — matches README.adoc) +| crates.io search `contractile` | 0 matching crates; the exact name returns `crate \`contractile\` does not exist`. The estate's published sibling tooling is `a2ml` and `k9-svc` +| GitHub code search `filename:contractile` + `path:bin` | 0 results +| GitHub code search `"name = \"contractile\""` (Cargo package) | 0 relevant results (2 unrelated hits) +| GitHub code search `contractile must run` | 51 hits, **all inside `.ncl` header comments and one spec**, never a workflow invocation +| GitHub code search `contractile --version` | 1 hit: `standards/docs/CONTRACTILE-SPEC.adoc` (the spec's own note) +| GitHub code search `contractile` in `.github/workflows/` | hits are README/spec/comment text or the word "contractile" in job names — no binary invocation +|=== + +[[e2-spec-claim]] +=== E2 — The spec names a workstation path, not a published artifact + +`CONTRACTILE-SPEC.adoc` `<>` states: + +[quote] +____ +The `contractile` CLI lives at `/var/mnt/eclipse/repos/reposystem/contractiles/cli/`. +It is a Rust workspace (`Cargo.toml` + `crates/`). … NOTE: The CLI was confirmed +present at `reposystem/contractiles/cli/` on 2026-04-17. +____ + +Every other invocation of `contractile` in the estate repeats that path +(e.g. `must.ncl`: "the `contractile` CLI (at +/var/mnt/eclipse/repos/reposystem/contractiles/cli/)"). `hyperpolymath/reposystem` +*does* exist publicly, and its root carries `contractiles` as a **git submodule** +pointing at `git@github.com:hyperpolymath/contractiles.git` — i.e. at *this* +repository. This repository has no `cli/` directory in any ref. + +So the one positive existence claim in the specification is unverifiable from +anything published: it describes a path on a specific workstation's mount, +inside a submodule checkout that does not carry the directory. Either the CLI +existed in an unpublished local checkout and was lost, or it never existed and +the note records a directory listing of an empty/expected path. The audit cannot +distinguish these; the operational consequence is identical — *nothing in the +estate can run it.* + +`hyperpolymath/contractiles-a2-lab` (the spec's "design successor" document home, +v2.0.0 `CONTRACTILE-CYBERNETIC-DESIGN.adoc`) returned HTTP 403 under the current +credentials and could not be read. If a CLI implementation or a design draft +lives there, it is invisible to the estate's tooling and to this audit; that is +itself a finding. + +[[e3-trident-exists]] +=== E3 — The runner layer *does* exist and is well specified — it just has no executor + +`hyperpolymath/standards` and `hyperpolymath/rsr-template-repo` both ship the +complete Trident per verb: + +[source] +---- +.machine_readable/contractiles/ +├── _base.ncl # pedigree_schema, status_core_doc, probe_schema, run_defaults +├── INDEX.a2ml # registry; consumers SHOULD discover verbs here +├── /file.a2ml # declaration (data) +├── /.ncl # runner (schema + run policy) +├── /.k9.ncl # K9 trust-tier component +└── /.manifest.a2ml # sha256 + size_bytes per file, cross_refs, signed_by +---- + +The runner files explicitly document the CLI binding they expect, e.g. +`must/must.ncl`: + +[source,nickel] +---- +# CLI: `contractile must run` → reads Mustfile.a2ml, evaluates each check, +# emits pass/fail verdict per item, exits non-zero if any failed. +---- + +and `must/must.k9.ncl` adds an enforcement clause: "Trident completeness is a hard +precondition … the contractile CLI's verify gate refuses partial publication." + +Deployments measured by the spec: 28 `_base.ncl`, 28 `INDEX.a2ml`, and runner +coverage of 2–10% per verb (`bust` at 63%). So the estate has, at most, ~28 +repositories with a contractile *system* and zero with an executor. + +[[e4-defacto]] +=== E4 — What actually runs today (the substitutes a CLI would replace) + +[cols="2,3",options="header"] +|=== +| Mechanism | Behaviour / exit codes + +| `just must-check` / `just trust-verify` (this repo, and every repo carrying the generated file) +| Generated `@echo` stubs plus 3–4 real shell checks. Exit 1 on any failing recipe; no per-item verdict, no severity policy, no report. + +| `standards/scripts/run-mustfile.sh` +| The estate's best `must run` approximation: parses `### ` + `- run:` / + `- verification:` / `- severity:`, executes `- run:` with `bash -c`, + treats `critical|high` failures as blocking, lower as warning. + Exit `0` pass · `1` blocking failure · `2` file missing. + +| `standards/scripts/check-mustfile-structure.sh` +| Structural half: every check must carry severity + a means of discharge. + +| `cloudguard-cli`, `Exnovation.jl`, `JuliaPackage-Reuse-Audit.jl` workflows +| Bash loops asserting six *presence* checks (`Must Trust Dust Lust Adjust Intend`) in the *wrong* root, plus an SPDX grep. One still lists the **deprecated `lust` verb** and omits verb directories entirely. + +| `metadatastician/*` `ci.yml` +| Runs `bun scripts/contractiles.mjs` — a per-repo bespoke re-implementation that executes `- run:` probes and fails on `critical` breaches. + +| `.github/hooks/validate-k9.sh` +| K9 validation (magic, pedigree, leash, signature field). Exit 0/1. + +| `standards/.github/workflows/deed-conformance.yml` + `1-formats/deed/tools/deed_lint.py` +| The estate's *conformance-lane* pattern: `--self-test`, `--fixtures` (valid must pass, invalid must fail), then lint every committed file. Exit 0 iff all conform. +|=== + +Three properties follow, and they are the reason a CLI is worth building at all: +every substitute has a *different* exit-code contract; none validates a +declaration against its runner's Nickel schema; and none produces a +machine-readable receipt. + +[[e5-coaptation]] +=== E5 — The Coaptation receipt runner is a separate system and must stay separate + +`coapt.sh` + `coapt.ncl` (e.g. `hyperpolymath/cicd-squabbler`, +`metadatastician/*`) implement a distinct pipeline — *extract-clauses → +extract-facts → Nickel comparator → receipt* — whose output is the +descriptile↔contractile comparison: + +[source,a2ml] +---- +[receipt] +schema = "hyperpolymath.coaptation/1" +band = "green|amber|red" +proposed-action = "..." + +[provenance] +contractiles = "intend@ must@ trust@ adjust@ dust@ bust@" +descriptiles = "clade@ state@ ecosystem@ agentic@ anchor@" +---- + +It writes to `.machine_readable/coaptation/receipts/latest.a2ml`, uses +`exit 64` for usage errors, and explicitly refuses to make decisions +("SITREP only — decides nothing"; an anchor drop is "a HUMAN AUTHORITY ACT"). +Its provenance line is the estate's existing *hash-of-declarations* convention +and is the compatibility target for any contractile run receipt. + +*The CLI must not absorb it.* RFC-0001 §"Receipts and the Coaptation boundary" +proposes a one-way, opt-in relationship only. + +[[e6-spec-vs-practice]] +=== E6 — Specification status: normative-but-unratified, and self-inconsistent in the places that block a CLI + +[cols="2,2",options="header"] +|=== +| Question the CLI must answer | Current state + +| How many files per verb directory? | Spec §Layout says *two*; the owner's layout ruling and every deployed Trident say *four*. Spec flags this as Open Ruling 2; "a conformance audit cannot state a pass criterion until this is settled". +| Exit codes | `standards` spec says nothing; `a2ml/docs/CONTRACTILES-A2ML-V1.adoc` (the only exit-code authority) fixes `0` success / `1` validation failure, and models only four files (no `adjust`, no `bust`); `run-mustfile.sh` adds `2` = file missing; `coapt.sh` uses `64`. +| Field vocabulary for declarations | Three live vocabularies: xfiles use `- run:` / `- probe:` / `- injection_probe:`+`- recovery_probe:`; runner schemas use `probe \| String` and an `id` key that the A2ML files do not carry as a field; the a2ml profile uses `checks[].run` with `Parameters` sections. Nothing defines the A2ML→Nickel binding. +| Probe form | `probe \| String` is legacy-but-deployed; structured `probe_schema` exists only in `_base.ncl` with a TODO. Migration is explicitly deferred "when the CLI is updated to accept both forms". +| Knowledge of the format migration | The estate is mid-migration from `.a2ml` to `.deed` (`standards/1-formats/deed/**`, `deed-conformance.yml`); contractile declarations remain `.a2ml` (and the a2ml docs are now hosted in the renamed `a2ml` repo). +| WCAG baseline for `adjust` | 2.1 AA (spec) vs 2.2 AA (CLI record) — Open Ruling 5. +| `gen-just` | Referenced by 205 generated files and every importing Justfile; specified nowhere. No input format, no rule for recipe naming, no determinism requirement. +|=== + +== Consequences of the gap, and the risk of coding first + +. *Nothing can gate on a spec it cannot read.* A `Mustfile.a2ml` check with + `- severity: critical` is advisory prose until a runner executes it; today the + only executor is a 100-line bash script in one repository + (`standards/scripts/run-mustfile.sh`) that no other repository invokes. +. *Absence is not neutral: it is being filled by forks.* At least three + independent verb re-implementations are already deployed. Each new one is a + fresh exit-code contract, and the estate's records show how that ends — + `lust` still checked for in workflows nine months after deprecation. +. *Coding before ratification would manufacture a fourth contract.* The spec's + own open rulings (1–9) include the conformance pass-criterion, the verb + directory cardinality, and the k9 relocation. A CLI implemented against an + unratified reading would freeze that reading into a binary, and the estate + would then have to migrate both the declarations and the tool. +. *The one hard safety question is unanswered.* Contractile declarations carry + shell strings that a CLI executes (`run:`, `probe:`, `injection_probe:`). + There is no published rule for capability gating, timeouts, sandboxing, or + signature requirements on those strings. Executing them by default — before a + ratified permission model exists — is remote code execution from repository + content, by design. That is precisely the "do not execute arbitrary probes + before the spec is ratified" condition. + +== Limits of this audit + +* The checkout carries a single squashed commit, and no tags; earlier history + (if the CLI was ever tracked here) is not recoverable from this clone. +* GitHub code search is a partial index; "no results" is evidence, not proof of + absence. Files in unindexed/private repositories, and local checkouts under + `/var/mnt/eclipse/**`, were not observable. +* `hyperpolymath/contractiles-a2-lab` returned 403; `a2ml-rs` and `k9-rs` + (crates' declared homes) return 404, which suggests repository renames that + predate this audit. Both are recorded rather than resolved. + +== Recommended next artefacts + +. `docs/proposals/RFC-0001-contractile-cli.adoc` — the versioned proposal + (grammar, exit codes, probe and permission semantics, determinism, receipts, + packaging, conformance tests, phased plan). *Status: Draft — awaiting + maintainer ratification.* +. `docs/decisions/0002-contractile-cli-spec-first.adoc` — the ADR recording + the decision to specify-and-ratify before implementation, and to keep the + Coaptation receipt runner separate. *Status: Proposed.* +. No implementation work, no `build/contractile.just` edits, and no probe + execution until both are ratified (RFC §"Unresolved maintainer choices"). diff --git a/docs/reports/audit/contractile-estate-census-2026-10-03.adoc b/docs/reports/audit/contractile-estate-census-2026-10-03.adoc new file mode 100644 index 0000000..a826bea --- /dev/null +++ b/docs/reports/audit/contractile-estate-census-2026-10-03.adoc @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) += Estate Census: Contractile Deployment, 2026-10-03 +:census-id: CENSUS-2026-10-03 +:date: 2026-10-03 +:method: GitHub code search API (authenticated), read-only +:status: Final — evidence for the R1–R9 / O1–O14 rulings +:toc: +:toclevels: 3 + +Re-measurement of the contractile deployment, requested so that the +ratification worksheet's rulings are taken against current numbers rather than +the specification's 2026-09-01 snapshot. *Read-only:* nothing was executed from +any repository; every figure comes from an index query. + +== Method and its limits + +* Each row is one authenticated GitHub code-search query. `filename:X` counts + files named `X`; free-text queries count files containing that string. +* Counts are *index-approximate*: GitHub caps result sets, deduplicates by file + blob, and indexes only public repositories. Private repos, unindexed forks, + and locally-mounted trees (`/var/mnt/eclipse/**`) are invisible here. +* This census is therefore *not comparable* to the spec's 2026-09-01 census, + which ran `find` over a local disk **including duplicate checkout trees** (the + spec flags that in `standards/scripts/check-manifest-ply.sh`). Both are + reported side by side, labelled. +* Queries were spaced to respect the code-search rate limit; ~28 queries total. + +== Declarations vs runners (the teeth measurement, re-run) + +[cols="1,1,1,1,1,1",options="header"] +|=== +| Verb | Declarations (`file.a2ml`) | Runners (`.ncl`) | Coverage +| Spec 2026-09-01 (local `find`) | Spec coverage | Change + +| `must` | 311 | 51 | *16.4%* | 555 / 26 | 4% | +12.4 pp +| `trust` | 537 | 52 | *9.7%* | 805 / 24 | 2% | +7.7 pp +| `adjust` | 116 | 53 | *45.7%* | 244 / 25 | 10% | +35.7 pp +| `dust` | 247 | 65 | *26.3%* | 393 / 24 | 6% | +20.3 pp +| `bust` | 175 | 135 | *77.1%* | 183 / 117 | 63% | +14.1 pp +| `intend` | 296 | 50 | *16.9%* | 475+36 / 24 | 5% | +11.9 pp +| *total* | *1,682* | *406* | *24.1%* | 2,655 / 240 | 9% | +15.1 pp +|=== + +Reading it honestly: + +. *The pairing claim is still aspirational.* ~1 in 4 declarations has a runner + in the public index. Under any reading of Ruling 3, "runners get built" is the + larger half of the estate's remaining work. +. *Every verb improved* on the 2026-09-01 measure, and `adjust` most of all + (10% → 45.7%). The direction is consistent with the recovery that `bust` + already demonstrated; the gap is a deployment failure, not intrinsic. +. *The `trust` figure is still skewed* by ecosystem farms (the spec measured 238 + of 805 `Trustfile.a2ml` in `developer-ecosystem` alone); a file count is not + an adoption count. +. Caveat on the runner column: `filename:.ncl` can in principle match a + non-contractile file of the same name. The sample lists are estate repos + (`standards`, `rsr-template-repo`, `k9-ecosystem`, `echidna`, `gossamer`, + `the-nash-equilibrium`, …), so the error term is small and toward + over-counting, which weakens the finding rather than inflating it. + +== Infrastructure (the "system" layer) + +[cols="2,1,1",options="header"] +|=== +| Artefact | Count, 2026-10-03 | Spec 2026-09-01 + +| `_base.ncl` | 59 | 28 +| `INDEX.a2ml` (registry) | 56 | 28 +| `.manifest.a2ml` exact-name (`must.manifest.a2ml`) | 42 | not measured +| Files literally named `.manifest.a2ml` | 133 | not measured +| Files carrying the `trident_version` marker | 598 (69 repos) | not measured +| K9 components (`must.k9.ncl`) | 43 | not measured +| Files declaring `paired_xfile` | 285 (29 repos) | not measured +| *`pending-first-verify` sentinel occurrences* | *132* | not measured +|=== + +Two findings that change decisions: + +. *Roughly 28 repositories now have a contractile system, not necessarily ~28 — + `_base.ncl` and `INDEX.a2ml` have each doubled* (28 → 59/56). Ruling 4 (k9 + relocation) and Ruling 8 (`contractiles-v1` scope) now apply to about twice + as many systems as the spec measured. +. *The hash convention is deployed but unpopulated.* 42 per-verb manifests exist, + and 132 files still carry `sha256 = "pending-first-verify"`. That sentinel is + exactly the input to the CLI's `verify` semantics + (RFC-0001 §"Manifest, hash and receipt compatibility"): `pending` is *unknown*, + not *valid*. Any ruling that treats presence-of-manifest as integrity will be + wrong for 132 files on day one. + +== Drift still measurable + +[cols="2,1,1",options="header"] +|=== +| Drift class | Count, 2026-10-03 | Spec 2026-09-01 + +| `Intendfile.a2ml` survivors (pre-2026-04-18 name) | 42 files / 22 repos | 36 / 20 +| `Lustfile.a2ml` survivors (retired verb) | 4 | not counted +| `MUST.contractile` s-expression family | 131 | 155 +| Canonical layout for `must` (`.machine_readable/contractiles/must/Mustfile.a2ml`) | 97 of 311 = *31%* | 22% +| Repos holding the generated `contractile gen-just` fragment | 18 (205 files) | not measured +|=== + +* *`Intendfile` drift is growing, not shrinking* (36 → 42). The spec's migration + note is not being applied faster than new copies accumulate. +* *`Lustfile.a2ml` still exists in 4 files* nine months after retirement. Any CLI + must refuse to dispatch `lust` rather than tolerating it (RFC-0001 §Exit codes, + `2`). +* *Layout conformance improved* (22% → 31%) but ~214 `must` declarations still + sit outside the canonical path — the cheapest remaining estate-wide fix. + +== CI wiring + +[cols="2,1",options="header"] +|=== +| Query | Result + +| `"K9-SVC contractile validation"` (the required status context named in `standards/.github/workflows/k9-contractile.yml`) | 4 files, 3 repos: `hyperpolymath/panll`, `hyperpolymath/standards`, `metadatastician/burble` +|=== + +The estate's one *required* contractile context exists in three repositories. +Everything else that claims contractile enforcement does so with bespoke bash +(see audit E4). This makes O13 (CI context name) cheap to decide and cheap to +deploy through the template: there is almost no installed base to disturb. + +== What this changes for the rulings + +* *R3 (build runners or downgrade the pairing claim):* still open, and now + quantified as 1,276 missing runners rather than "90%+ missing". Building them + is a mechanical campaign (`bust` at 77% is the proof); downgrading the claim + remains defensible for `trust`. +* *R2 (two files or four per verb):* unchanged in shape — but note that 42 verb + manifests and 43 `must.k9.ncl` components are already deployed, i.e. the + four-file Trident is the *de facto* target for the repos that have a system. +* *R4 (k9 relocation):* with 59 `_base.ncl` systems, a wrong k9 search path costs + twice what the spec assumed. +* *R6 (cardinality):* the 311 `must` declarations across far fewer repos means + duplicates remain structurally guaranteed; the exemption question for + `docs/templates/**` and ecosystem farms is still the blocker. +* *O8 (manifest writes):* 132 unverified sentinels mean the CLI's *read* path + matters more than any write path; keeping the CLI read-only in v1 costs + nothing and avoids blessing unverified hashes. +* *O13 (CI context):* reuse `K9-SVC contractile validation`; 3 repos to migrate, + and the `k9-contractile.yml` header already documents why a required context + must never be renamed. + +== Reproduction + +Queries and counts are reproducible from the raw capture; the full label → +query → count map used to build the tables above is +`CENSUS-2026-10-03` in the ratification bundle (PR body). Representative +queries: + +[source,bash] +---- +gh api -X GET 'search/code?q=filename%3AMustfile.a2ml' --jq .total_count # 311 +gh api -X GET 'search/code?q=filename%3Amust.ncl' --jq .total_count # 51 +gh api -X GET 'search/code?q=filename%3A_base.ncl' --jq .total_count # 59 +gh api -X GET 'search/code?q=%22pending-first-verify%22' --jq .total_count # 132 +---- + +NOTE: `path:contractiles` was found to be an unreliable qualifier during this +census (it zeroed a query that `filename:` answers correctly). Do not add it; +prefer `filename:.ncl` and filter repositories by inspection.