Skip to content

Feat/provisioning canon - #1132

Merged
hyperpolymath merged 25 commits into
mainfrom
feat/provisioning-canon
Oct 2, 2026
Merged

hyperpolymath merged 25 commits into
mainfrom
feat/provisioning-canon

Conversation

@hyperpolymath

Copy link
Copy Markdown
Owner

Summary

Closes #

Type of change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 💥 Breaking change (would change existing behaviour)
  • 🕳️ Soundness fix (fixes a checker/proof false-negative)
  • 📖 Documentation
  • 🧹 Refactor / tech debt (behaviour-preserving)
  • ⚡ Performance
  • 🔧 Build / CI / tooling

How has this been verified?

Checklist

  • My commits are signed (git commit -S).
  • I ran the project's own checks/tests locally and they pass.
  • New files carry the correct SPDX-License-Identifier (code/config MPL-2.0,
    prose CC-BY-SA-4.0); I did not relicense existing files.
  • Docs are updated, and no public claim now overstates what the code does.
  • I have not introduced a soundness hole (or I have flagged where I might have).

Notes for reviewers

hyperpolymath and others added 23 commits September 30, 2026 17:20
Add 3-practice/provisioning/: the Provisioning Standard (prose + praxis
deed), the engine (provision.just, provision-lib.sh, provision-modes.sh,
provision-check.sh), and the minted templates (launcher, Justfile module,
mise, guix source/cargo packages + manifest + channels, SETUP, AI install
guide, three llm-warmups, README ai-install fragment, per-repo
provisioning_praxis.deed).

Launcher standard 0.5.0: every repository carries launcher.sh, profiled
by archetype; --setup/--doctor/--heal/--ai-setup call the engine directly
so a repo's own root recipe cannot shadow the canon; repo checks live in
*-local recipes and a failing doctor-local is FAIL PV-E50.

guix.scm: the licence field was a malformed ad-hoc license object
pointing at palimpsest-license; it is now (guix licenses) mpl2.0, the
licence the file's own SPDX header already declares.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UkSmyapDUmuGyyZSJmvbKy
- Idris2 and Zig are detected to depth 3 (src/abi/*.ipkg, ffi/zig/build.zig):
  234 build.zig sit at that depth across the estate and 37 repos have their
  only Idris2/Zig marker there. Zig verbs now run in the build.zig directory.
- launcher.sh is `generated`: realign re-renders it only when it carries the
  @launcher-deed block; hand-written launchers are kept and source
  provision-modes.sh.
- A minted set whose deed :repo is another repository's is inherited (e.g.
  from rsr-template-repo) and is re-minted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UkSmyapDUmuGyyZSJmvbKy
Hypatia code_safety flagged the mise install hint in provision-modes.sh
as download-then-run (CWE-494, high, new). Every mise install hint now
leads with the OS package manager (brew, Fedora COPR, winget, mise's
install docs) and gives the installer only as download, read, run --
never piped into a shell. One MISE_INSTALL_HINT in provision-lib.sh
feeds both doctor and setup messages; SETUP and the AI guide match, and
the AI guide says to show the installer to the user before running it.

The two `eval "$(mise env -s bash)"` sites now prepend `mise bin-paths`
to PATH instead, which is what they needed and needs no eval.

REGISTRY.a2ml was stale (RSR source_hash): regenerated with
scripts/build-registry.sh, which also fixes build-registry-test.sh
(9 passed, 0 failed locally).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UkSmyapDUmuGyyZSJmvbKy
A file sits at the root only when a tool needs it there. The Guix trio
and the warm-ups follow the repository's existing layout: root, or
build/ (guix) and docs/onboarding/ or docs/ (warm-ups), as
rsr-template-repo already does. provision-lib.sh owns the answer
(guix-dir, set-files); doctor, dev-shell, toolchain-refresh and
provision-check.sh all ask it, so the engine and its checker cannot
disagree about where a file lives. Both guix.scm and build/guix.scm
present is the new PV-W35.

Zig detection now reaches depth 4 (src/interface/ffi/build.zig): 74
repos keep their only build.zig there (measured 2026-09-30).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UkSmyapDUmuGyyZSJmvbKy
Found while provisioning rsr-template-repo against the canon:

- zig `test` runs only when build.zig declares a "test" step (bench and
  run already checked); bun's fallback `bun test` runs only when test files
  exist, because `bun test` exits 0 on none.
- ai-setup reads the "Just say it" sentence from the README's
  [[ai-install]] section, so the README and the recipe cannot disagree.
- __GUIX_PREFIX__ slot: SETUP and the dev warm-up name build/manifest.scm
  etc. when the Guix trio lives under build/; guix.scm templates compute
  %source-dir from their own location.
- hp_app_name falls back to the origin remote's name (worktrees).
- provision.just: doc comments on the per-language verbs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UkSmyapDUmuGyyZSJmvbKy
fmt-check was aliased to lint, which differs per language (clippy,
credo). It is now its own contract verb: cargo fmt --check, zig fmt
--check, mix format --check-formatted, gleam format --check, dune
build @fmt, or a bun "fmt-check" script; N/A elsewhere.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UkSmyapDUmuGyyZSJmvbKy
- provision-lib.sh: build/just/doctor-local.sh runs sourced in a subshell; an
  `exit` or tripped `set -e` is FAIL PV-E51 and the checks it finished still
  count (tally path baked into the EXIT trap, since set -e unwinds locals).
  Tested: normal hook 19/1/0, `exit 3` 19/0/1, `set -e; false` 19/0/1.
- provision-lib.sh: recipe tools (trivy) pinned in mise only where a recipe uses them.
- provision-modes.sh: hp_provision_or_return replaces `&& exit $?`, which
  returned success for a failing mode.
- provision.just: quote the ai-warmup audience argument.
- check-launcher-standard-currency.sh: CURRENT_VERSION 0.5.0.
- PV-E51 registered in the deed, the standard and the SETUP troubleshooting table.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UkSmyapDUmuGyyZSJmvbKy
Running provision-check.sh on a deliberately weak fixture showed three holes:
a 0-byte mise.lock passed (presence was -f), a fake guix.scm passed (the stub
test was a blacklist), and doctor and the check used different stub regexes and
different banned lists.

provision-lib.sh now owns three predicates that both call: mise-banned,
mise-lock-gaps (every mise.toml tool needs a concrete version in mise.lock,
and the file must carry sha256 checksums) and guix-stub (positive: every
package field present, manifest lists specifications, channels pin a
40-hex commit). It also gains fact verbs the generator fills templates from
(guix-gaps, tool-table, system-deps), so no second per-language table exists.

Mutants killed: empty lock, lock without a tool, versionless block, empty
version, aqua:denoland/deno, fake guix.scm, template residue, unpinned
channels. Controls pass: a real `mise lock` output, a real guix.scm.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UkSmyapDUmuGyyZSJmvbKy
Realign replaces a mise.toml that names a banned tool (PV-W23), carrying
its other [tools] entries, so the deno-to-bun ruling can execute. The
template launcher serves library/tool/theory/docs; app launchers come
from launch-scaffolder mint and source the same provisioning modes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UkSmyapDUmuGyyZSJmvbKy
guix.scm.cargo.tmpl promised that `just toolchain-refresh` regenerates the
crate inputs from Cargo.lock; nothing did. New verb `crates-scm` runs
`guix import crate --lockfile` (GUIX may name a container wrapper) and
writes build/guix/crates.scm whole or not at all: the output is accepted
only when it defines one origin per registry crate in Cargo.lock, since a
containerised guix loses its exit status. Otherwise PV-E41 and the old
file stays.

Verified on launch-scaffolder's Cargo.lock: 151/151 origins, loaded by
`guix repl` ((length %crate-inputs) = 151, origin? #t), byte-identical on
regeneration. Two mutants killed: truncated importer output (10/151) and
a failing importer (0/151) both exit 1 with PV-E41, file sha256 unchanged.

channels.scm is reclassified engine -> minted: it is re-pinned per repo,
so it is checked for a 40-hex pin, never byte-compared with the canon.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UkSmyapDUmuGyyZSJmvbKy
main took 0.4.0 -> 0.5.0 for the (js-runtime) clause (D224, #1100) while
this branch took the same number for the archetype profile and the
provisioning mode family. 0.5.0 is published with D224's meaning, so the
provisioning obligations are 0.6.0 (2026-10-01): deed :standard-version,
the currency gate's CURRENT_VERSION (its test asserts they agree), the
.adoc section, the launcher template's compliance claim and
provision-modes.sh. A consumer still citing 0.5.0 gets the gate's
non-blocking stale-version warning (standards#991), not a failure.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UkSmyapDUmuGyyZSJmvbKy
mise_lock_gaps accepted the lock when one `checksum = "sha256:` line
existed anywhere, so a tool whose artefacts carried none still passed
doctor and provision-check.sh (CodeRabbit, #1096). Every
[tools.X."platforms.P"] table now needs its own sha256, and the gap
names each tool/platform that lacks one.

A tool with no platform tables is not a gap: measured with `mise lock`,
core:rust (rustup) and cargo: (built from source) are written with no
platform tables and no checksums, while all 97 platform tables across
three real locks (just, bun, zig, gleam, shellcheck, erlang, elixir,
julia, opam, lychee) carry one. Requiring a checksum per tool would
have failed every Rust repository.

Controls: the three real locks pass; one zig/linux-x64 checksum removed
fails naming exactly that table (the previous code passed it); all
removed fails; an empty sha256 value fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UkSmyapDUmuGyyZSJmvbKy
asciidoctor processes `[[ai-install]]` as an anchor even inside
backticks, so the standard silently defined an `ai-install` id twice
(rsr-template-repo#213 failed check-adoc-renders on the same text).
`\[[ai-install]]` renders the same literal code and defines nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UkSmyapDUmuGyyZSJmvbKy
- toolchain-refresh re-pins only the `guix` channel's commit in
  channels.scm (guix_channel_commit / repin_guix_channel) instead of
  replacing the file with `guix describe` output; when the current commit
  cannot be read, or the file has no guix channel, it WARNs and leaves the
  file byte-identical.
- doctor prints its "Next:" hint on stderr, so the PASS/WARN/FAIL tally is
  the last stdout line on every outcome.
- The deed marks channels.scm minted (re-pinned per repository, never
  byte-compared) and lists build/guix/crates.scm as generated; the
  maintainer warm-up no longer claims realign overwrites launcher.sh or
  channels.scm.

Raised by CodeRabbit on hyperpolymath/launch-scaffolder#67, which vendors
this canon.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UkSmyapDUmuGyyZSJmvbKy
mise merges .tool-versions, mise.toml and .mise.toml, so a banned tool
pinned in either secondary file passed PV-W23, which read mise.toml
only. The check now reads all three, flags npm:/pipx:/pip:/go: backends
whatever the package name, and bans denojs (the 07-18 sweep's name for
deno). The deed's :banned-tools is now the same list as BANNED_TOOLS.

Controls: python in .tool-versions and npm:prettier in .mise.toml ->
mise-banned rc=1 naming both; the same configs without them -> rc=0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UkSmyapDUmuGyyZSJmvbKy
prettier = "latest" resolves to npm:prettier, the only backend the mise
registry has for it, so it installed through node while passing PV-W23.
mise_banned now asks `mise registry` (offline) and flags a bare name
whose every backend is npm:/pipx:/pip:/go:. A name with any other
backend (shfmt: aqua) or one mise does not know is not flagged, and
without mise on PATH nothing is called banned on a guess.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UkSmyapDUmuGyyZSJmvbKy
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Jonathan D.A. Jewell <6759885+hyperpolymath@users.noreply.github.com>
**Stacked on #1096.** The base branch is `feat/provisioning-canon`.
After #1096 squash-merges, run:

```
git rebase --onto main feat/provisioning-canon feat/provisioning-check-reusable
```

and retarget this PR to `main`. Until then the diff is just this gate.

## What

`.github/workflows/provisioning-check-reusable.yml`, the CI gate from
`3-practice/provisioning`. It checks the caller against the canon at the
workflow's own commit (`job.workflow_sha`), in two steps that report
separately:

| Step | Fails when |
|---|---|
| Engine files match the canon | any
`build/just/{provision.just,provision-lib.sh,provision-modes.sh,provision-check.sh}`
is missing or differs byte-for-byte |
| Provisioning set conforms | the **canon** `provision-check.sh`, run
without `--dev`, reports FAIL. It loads `provision-lib.sh` from its own
directory, so a drifted caller copy cannot vouch for itself |

- `channels.scm` is deliberately not compared byte-for-byte:
`toolchain-refresh` re-pins it per repository. `provision-check.sh`
checks its pin instead.
- `just` 1.56.0 comes from the release tarball, pinned by sha256 (the
same pin as launch-scaffolder#67). No new `uses:` is added.
- `actions.lock` gains the section by hand (checkout only). `canon.lock`
lists the reusable as `provisioning` under `[canon.workflows]`.

## Evidence (local dry run of both steps; the CI proof follows on a
throwaway caller)

| Case | cmp step | provision-check |
|---|---|---|
| rsr-template-repo #213 head (control) | pass | pass |
| mutant: `fmt-check` recipe removed | pass | **FAIL** |
| mutant: `python` added to `mise.toml` | pass | **FAIL** (banned tool +
unpinned) |
| mutant: `provision-lib.sh` changed | **FAIL** | pass |
| mutant reverted | pass | pass |

The third mutant is why there are two steps: an engine edit that leaves
conformance intact is caught only by the byte comparison.

## Known, not new

- actionlint does not know the `job.workflow_sha` context. It reports
the same thing 4 times on `allowlist-preflight-reusable.yml`.
- `gh actions-lock` gives this file the same `sha-as-ref` advisory that
every SHA-pinned workflow here carries (94 on the base, 95 with this
one).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01UkSmyapDUmuGyyZSJmvbKy

Signed-off-by: Jonathan D.A. Jewell <6759885+hyperpolymath@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


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

❤️ Share

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

@coderabbitai

coderabbitai Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

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

Signed-off-by: Jonathan D.A. Jewell <6759885+hyperpolymath@users.noreply.github.com>
@hyperpolymath
hyperpolymath enabled auto-merge (squash) October 2, 2026 01:24
@hyperpolymath
hyperpolymath disabled auto-merge October 2, 2026 01:48
@hyperpolymath
hyperpolymath enabled auto-merge (squash) October 2, 2026 01:48
@hyperpolymath
hyperpolymath disabled auto-merge October 2, 2026 02:00
@hyperpolymath
hyperpolymath enabled auto-merge (squash) October 2, 2026 02:02
@hyperpolymath
hyperpolymath merged commit f14575d into main Oct 2, 2026
49 checks passed
@hyperpolymath
hyperpolymath deleted the feat/provisioning-canon branch October 2, 2026 02:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant