From 9410a6a480a77d8de6a4b6c076d372d415048b0a Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 2 Jun 2026 06:22:35 +0000 Subject: [PATCH 1/2] docs(013): RFC 0001 for the skillrig mise backend plugin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a research spike and an RFC for a `skillrig` mise backend plugin that co-installs multiple backing CLIs from one origin monorepo (issue #23). Key finding: mise 2026.4.12 (PR #9093) fixes only the install-scheduler dedup (Layer A), not version resolution (Layer B). Under a strict-semver tag policy the origin must use build-metadata streams (v0.5.0+iii), which SemVer precedence collapses and version_prefix cannot select -- so native mise cannot give independent versioning + co-location together. A backend plugin whose BackendListVersions owns version listing is the only option that can, which is the capability justification for the plugin. - specledger/013-mise-backend/research: the spike (4 open questions answered) - docs/rfcs/0001: the RFC -- origin [[binaries]] contract, the three Lua hooks, auth, verification depth, template + CLI integration, phasing; bootstraps the separate skillrig/mise-skillrig repo - docs/ARCHITECTURE-v0 §8b: correction pointer (tag_regex -> version_prefix; #9093 is scheduler-only; build-metadata streams remain native-unresolvable) --- docs/ARCHITECTURE-v0.md | 11 + docs/rfcs/0001-mise-skillrig-backend.md | 377 ++++++++++++++++++ .../2026-06-02-mise-backend-plugin.md | 257 ++++++++++++ 3 files changed, 645 insertions(+) create mode 100644 docs/rfcs/0001-mise-skillrig-backend.md create mode 100644 specledger/013-mise-backend/research/2026-06-02-mise-backend-plugin.md diff --git a/docs/ARCHITECTURE-v0.md b/docs/ARCHITECTURE-v0.md index 748b905..b2b660b 100644 --- a/docs/ARCHITECTURE-v0.md +++ b/docs/ARCHITECTURE-v0.md @@ -295,6 +295,17 @@ The CLI can *offer* to write the matching `mise.toml` stanza (helpful), but inst ### 8b. mise GitHub-backend realities (grounded) — and why release-please-per-binary is load-bearing +> **Correction (2026-06-02, RFC 0001 + issue #23 validation).** Parts of this section are +> superseded. (i) mise has **no `tag_regex`** — per-stream selection uses **`version_prefix`** +> (a *leading*-prefix stripper only). (ii) The single-binary-per-entry *install collision* was +> a **scheduler** dedup bug, **fixed in mise 2026.4.12 (PR #9093)**; co-installing multiple +> binaries from one repo now works *for prefix or single-release shapes*. (iii) **But** a +> strict-semver tag policy forces **build-metadata** streams (`v0.5.0+iii`), which SemVer +> precedence collapses and `version_prefix` cannot select — so those remain natively +> unresolvable, which is why the **`skillrig` mise backend plugin** is justified on capability. +> See [`docs/rfcs/0001-mise-skillrig-backend.md`](rfcs/0001-mise-skillrig-backend.md) and the +> spike under `specledger/013-mise-backend/`. The grounded prose below is retained for history. + Verified against mise's GitHub backend docs (current as of early 2026). Three findings shape the template: **(1) The one-binary-per-entry limit — the constraint that shapes the origin's release strategy.** mise's GitHub backend installs **a single binary per tool entry**; it does **not** natively fetch multiple binaries from one release (confirmed: `mise use github:org/repo` installs only the first asset and skips the rest; the community workaround is a per-tool postinstall `curl` for extra assets). This collides with the naive reading of "one monorepo origin with several backing CLIs in `cmd/`" if that monorepo cuts **one release containing all binaries**. The resolution — and it happens to be the pattern already chosen for versioning hygiene — is **per-CLI tagged release streams via release-please** (`oxid-v1.4.0`, `foo-v2.1.0`, …) rather than one monolithic release. So: diff --git a/docs/rfcs/0001-mise-skillrig-backend.md b/docs/rfcs/0001-mise-skillrig-backend.md new file mode 100644 index 0000000..386e56b --- /dev/null +++ b/docs/rfcs/0001-mise-skillrig-backend.md @@ -0,0 +1,377 @@ +# RFC 0001 — The `skillrig` mise backend plugin + +**Status:** Draft for review +**Author:** generated from issue [#23](https://github.com/skillrig/cli/issues/23) +**Spike:** [`specledger/013-mise-backend/research/2026-06-02-mise-backend-plugin.md`](../../specledger/013-mise-backend/research/2026-06-02-mise-backend-plugin.md) +**Relates to:** `docs/ARCHITECTURE-v0.md` §8 (backing-CLI provisioning), §8b (mise realities), §13 vNext +**Bootstraps:** a **new, separate repository** — `skillrig/mise-skillrig` (the plugin is Lua, not Go; it does **not** live in `skillrig/cli`) + +> **Scope note (pre-release marker, per `CLAUDE.md`).** No backward compatibility is planned. +> This RFC defines a new artifact and a new convention-versioned origin contract; it may +> change freely until it ships. + +--- + +## 1. Summary + +skillrig origins are **co-located monorepos**: one repo holds an org's agent skills *and* the +private backing CLIs those skills require (`cmd/`), released by the same pipeline so a skill +and its tool version, release, and are vendored/verified as one unit (`ARCHITECTURE-v0` §1). +A skill declares its tool via `metadata.x-skillrig.requires`; provisioning of the binary is +delegated to **mise** (`ARCHITECTURE-v0` §8 / R17 — *"skillrig declares and verifies, mise +installs"*). + +This RFC specifies a **mise backend plugin** named `skillrig` so that N backing CLIs in one +origin become N **distinct** mise tools, addressed `skillrig:@`, each tracking +its own independent release stream: + +```toml +# consumer mise.toml +"skillrig:iii" = "latest" +"skillrig:console" = "0.2.0" +``` + +It is a separate, independently-released repo. This document also defines the **origin-side +contract** (a convention-versioned `[[binaries]]` block) the plugin depends on, and the +changes to the **origin template** and **`skillrig` CLI** that make the three pieces work +together. + +## 2. Motivation — why native mise is not enough *for this origin* + +The naive plan ("one monorepo, consume each CLI via mise's `github` backend") fails, and the +obvious fixes don't apply under the origin's tag policy. There are **two independent layers**: + +- **Layer A — install scheduler.** mise once keyed install jobs by `@`, + so two aliases pointing at `github:org/repo@` deduped into one job (only one + binary installed). **Fixed in mise 2026.4.12 (PR #9093)** by re-keying to + `@`. *Necessary, and we require it — but it only fixes Layer A.* +- **Layer B — version resolution.** PR #9093 explicitly *"does not touch version listing or + resolution."* The origin's **tag policy enforces strict semver**, which **forbids** prefix + tags (`iii-v0.1.0` is not valid semver) but **permits** build-metadata tags (`0.1.0+iii` + is). Per SemVer 2.0.0, *build metadata MUST be ignored for precedence* — so `0.5.0+iii` and + `0.2.0+console` are indistinguishable to mise's resolver: the version set collapses, + `latest` for one stream resolves to the max across **all** streams, and `version_prefix` + (a *leading*-prefix stripper — there is **no** `tag_regex`/suffix selector) cannot pick a + `+iii` suffix. + +The full native design space (spike Finding 1b): + +| Option | Independent versions | Co-location | Strict-semver tags | Native mise | +|---|:---:|:---:|:---:|:---:| +| (a) prefix streams `iii-v*` + `version_prefix` | ✅ | ✅ | ❌ forbidden by policy | ✅ | +| (b) build-metadata streams `0.5.0+iii` | ✅ | ✅ | ✅ | ❌ **broken (Layer B)** | +| (c) one release, all binaries, `asset_pattern` | ❌ | ✅ | ✅ | ✅ (post-#9093) | +| (d) separate repo per CLI | ✅ | ❌ | ✅ | ✅ | +| (e) **`skillrig` backend plugin** | ✅ | ✅ | ✅ | ✅ (plugin owns listing) | + +Only **(e)** satisfies *independent versioning + co-location + strict-semver* together, +because a backend plugin's `BackendListVersions` hook **owns version listing** and can map +build-metadata tags to per-tool version streams — the one thing native mise structurally +cannot do. That is the capability justification for this plugin. + +## 3. Goals / non-goals + +**Goals** +- Co-install any number of an origin's backing CLIs as distinct `skillrig:` mise tools, + each tracking its own build-metadata release stream, on a strict-semver origin. +- Drive resolution from the **origin's own metadata** (zero per-consumer `asset_pattern` + boilerplate); the consumer writes only `skillrig: = ""`. +- **Checksum-verify** every downloaded binary against the origin's published checksums. +- Resolve a GitHub token centrally (handles the private-origin keyring-404 gotcha). +- Keep skillrig's "no extra architecture" promise: a small published plugin + a metadata + block in the origin. **No registry/index service.** + +**Non-goals (v1)** +- Replacing mise. skillrig still *declares + verifies*; mise *installs* (R17). +- Binding the binary to the skill's `treeSha`/`commit` (tamper-evidence parity) — **v2**. +- SLSA/GPG attestation — **v2** (origin ships sha256 checksums today). +- Public-CLI provisioning (`terraform`, `gh`) — those stay on mise's stock backends. +- Windows-first support — best-effort; Linux/macOS are the v1 targets. + +## 4. How the three pieces fit together + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ ORIGIN MONOREPO my-org/my-skills (stood up from the origin template) │ +│ │ +│ cmd/iii/ cmd/console/ ... ── goreleaser builds per-CLI assets │ +│ release-please (per-package) ── cuts tags v0.5.0+iii , v0.2.0+cons. │ +│ releases: iii_0.5.0_linux_amd64.tar.gz + checksums.txt (per stream) │ +│ │ +│ .skillrig-origin.toml ─ [[binaries]] : stream selector + asset template│ +│ index.json ─ generated by `skillrig index`; mirrors binaries│ +└───────────────┬───────────────────────────────────────────────────────────┘ + │ (1) plugin fetches index.json/.skillrig-origin.toml + tags + assets + │ authenticated via MISE_GITHUB_TOKEN / GITHUB_TOKEN / gh + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ mise + skillrig backend plugin (skillrig/mise-skillrig, Lua) │ +│ BackendListVersions → list tags, filter by `+iii`, return clean semver │ +│ BackendInstall → resolve tag, pick asset, sha256-verify, extract │ +│ BackendExecEnv → put bin on PATH │ +└───────────────┬───────────────────────────────────────────────────────────┘ + │ (2) + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ CONSUMER REPO │ +│ mise.toml: "skillrig:iii" = "latest" ← written by `skillrig add` │ +│ .skillrig/config.toml: origin = "my-org/my-skills" │ +│ skill's SKILL.md: metadata.x-skillrig.requires: [{tool: iii, ...}] │ +└───────────────────────────────────────────────────────────────────────────┘ +``` + +1. **Origin template** ships the release pipeline that produces per-stream tags + assets + + `checksums.txt`, *and* the `[[binaries]]` metadata describing them (§5). +2. **The plugin** reads that metadata to resolve/install each tool (§6). +3. **The `skillrig` CLI** auto-wires the consumer's `mise.toml` when a vendored skill requires + a binary, and reuses its own origin resolution + token resolver (§7). + +## 5. The origin-side contract (`[[binaries]]`) — OQ2 + +The per-binary stream + asset conventions do **not** exist in the origin metadata today. We +add them as a **convention-versioned** block in `.skillrig-origin.toml`, mirrored into the +generated `index.json` so the plugin can fetch a single file. This is `skillrig/cli` + +origin-template work, shared by the plugin and any native-stanza generator (AP-04 — one +contract, many readers). + +```toml +# .skillrig-origin.toml (origin repo root) +skillrig-convention = 1 +origin = "my-org/my-skills" + +[[binaries]] +name = "iii" # → mise tool name `skillrig:iii` +stream = "+iii" # semver BUILD-METADATA suffix identifying this stream +asset = "iii_{version}_{os}_{arch}.tar.gz" # {version}=semver core, {os}/{arch} mapped +checksums = "checksums.txt" # asset in the same release; sha256 filename lines +bin = "iii" # executable path inside the archive (post strip) + + # optional: map mise RUNTIME tokens → this asset's tokens (defaults: linux/darwin, amd64/arm64) + [binaries.platforms.linux-x64] + os = "linux" + arch = "amd64" + [binaries.platforms.darwin-arm64] + os = "darwin" + arch = "arm64" + +[[binaries]] +name = "console" +stream = "+console" +asset = "console_{version}_{os}_{arch}.tar.gz" +checksums = "checksums.txt" +bin = "console" +``` + +`skillrig index` (already the catalog generator) emits the same data into `index.json`: + +```jsonc +{ + "skillrigConvention": 1, + "origin": "my-org/my-skills", + "binaries": [ + { "name": "iii", "stream": "+iii", + "asset": "iii_{version}_{os}_{arch}.tar.gz", + "checksums": "checksums.txt", "bin": "iii", + "platforms": { "linux-x64": {"os":"linux","arch":"amd64"}, "...": {} } } + ], + "skills": { /* unchanged */ } +} +``` + +**Convention versioning (R5e).** The plugin reads `skillrigConvention` and fails clearly +against an incompatible origin rather than mis-resolving. v1 understands convention `1`. + +## 6. The plugin — design (OQ1) + +**Repo / naming.** New repo **`skillrig/mise-skillrig`**; mise backend name `skillrig` +(tools addressed `skillrig:`); the plugin name need not match the repo. Installed via +`mise plugin install skillrig https://github.com/skillrig/mise-skillrig` (or registered in the +mise plugin registry for the short name). **Released on its own cadence**, independent of the +`skillrig` CLI. + +**Repo layout (bootstrap target).** + +``` +skillrig/mise-skillrig/ +├── metadata.lua # plugin name, version, author +├── hooks/ +│ ├── backend_list_versions.lua # BackendListVersions +│ ├── backend_install.lua # BackendInstall +│ └── backend_exec_env.lua # BackendExecEnv +├── lib/ +│ ├── origin.lua # resolve origin + fetch index.json/.skillrig-origin.toml +│ ├── github.lua # authed GitHub API (tags, releases, asset download) +│ ├── stream.lua # build-metadata stream parse/filter/sort +│ └── checksum.lua # sha256 verify against checksums.txt +├── mise-tasks/ # test/lint tasks +├── .github/workflows/ # CI + release +└── README.md +``` + +**Origin resolution (inside the plugin).** The plugin must know which origin to read. +Precedence (mirrors the CLI for consistency): +`ctx.options.origin` (per-tool in `mise.toml`) → `SKILLRIG_ORIGIN` env → error with a fix. + +```toml +# explicit per-tool origin (when not using SKILLRIG_ORIGIN) +[tools."skillrig:iii"] +version = "latest" +origin = "my-org/my-skills" +``` + +### 6.1 `BackendListVersions` — the load-bearing hook + +Owns version listing, which is exactly why native mise can't do this. It fetches the origin's +tags, keeps only those whose build metadata matches the tool's `stream`, strips the `+stream` +suffix, and returns clean semver cores ascending. + +```lua +-- hooks/backend_list_versions.lua +function PLUGIN:BackendListVersions(ctx) + local meta = origin.binary_meta(ctx.tool) -- from index.json, by ctx.tool + local tags = github.list_tags(origin.repo()) -- authed; e.g. {"v0.5.0+iii","v0.2.0+console"} + local stream = meta.stream -- "+iii" + local versions = {} + for _, tag in ipairs(tags) do + local core, build = stream.parse(tag) -- "0.5.0", "iii" (strips leading v) + if build == stream.suffix(meta.stream) then -- belongs to THIS stream + versions[#versions + 1] = core + end + end + stream.sort_semver_asc(versions) -- ascending; mise applies no extra sort + return { versions = versions } +end +``` + +`latest` then naturally resolves to the max **within the stream** — the behavior native mise +cannot produce for build-metadata tags. + +### 6.2 `BackendInstall` — resolve, download, verify, extract + +```lua +-- hooks/backend_install.lua +function PLUGIN:BackendInstall(ctx) + local meta = origin.binary_meta(ctx.tool) + local tag = "v" .. ctx.version .. meta.stream -- "0.5.0" + "+iii" → "v0.5.0+iii" + local rel = github.release_by_tag(origin.repo(), tag) -- authed + + local plat = meta.platforms[RUNTIME.osType .. "-" .. RUNTIME.archType] or stream.default_plat() + local name = stream.render(meta.asset, { -- "iii_0.5.0_linux_amd64.tar.gz" + version = ctx.version, os = plat.os, arch = plat.arch, + }) + + local asset = github.find_asset(rel, name) + local file = github.download(asset, ctx.download_path) + + -- checksum verify (mise does NOT verify for custom backends — the plugin must) + local sums = github.download(github.find_asset(rel, meta.checksums), ctx.download_path) + checksum.verify_sha256(file, name, sums) -- abort on mismatch + + archive.extract(file, ctx.install_path, { strip = "auto" }) + return {} +end +``` + +### 6.3 `BackendExecEnv` — PATH + +```lua +-- hooks/backend_exec_env.lua +function PLUGIN:BackendExecEnv(ctx) + return { env_vars = { { key = "PATH", value = ctx.install_path .. "/bin" } } } +end +``` + +(If `bin` is at the archive root rather than `bin/`, `BackendInstall` normalizes it into +`install_path/bin/` so this hook stays trivial.) + +## 7. Auth (the keyring-404 gotcha) and CLI integration + +**Token resolution.** mise's own github-token precedence already centralizes this for the +plugin's GitHub API calls: `MISE_GITHUB_TOKEN` → `GITHUB_API_TOKEN` → `GITHUB_TOKEN` → +`settings.github.credential_command` (host via `MISE_CREDENTIAL_HOST`) → native OAuth → +`github_tokens.toml` → gh `hosts.yml` → `git credential fill`. The documented gotcha — +**mise cannot read a gh token stored in the OS keyring** (only `hosts.yml`) — is handled by +documenting either `MISE_GITHUB_TOKEN=$(gh auth token)` or a `credential_command`. The plugin +reuses this resolver via mise's HTTP/token helpers; it introduces **no new credential +surface** (consistent with `ARCHITECTURE-v0` §2b — no write credential anywhere). + +**`skillrig` CLI auto-wiring (in this repo, `skillrig/cli`).** When `skillrig add ` +vendors a skill whose `metadata.x-skillrig.requires` names a binary sourced from the origin, +the CLI writes the matching `mise.toml` stanza: + +```toml +"skillrig:iii" = ">=0.4.0" # from requires[].version +``` + +This is the one piece of plugin support that belongs in the Go CLI (it owns `add`, origin +resolution, and the lock). It is gated behind the same dry-run/force discipline as `add`. + +## 8. Verification depth (OQ3) + +- **v1 — checksum-only.** sha256 against the origin's `checksums.txt` (already published). + This matches what mise's native `github` backend offers, re-implemented in the plugin + because mise does not verify for custom backends. +- **v2 — provenance / treeSha parity.** Bind the binary's release tag/commit to the + `treeSha`/`commit` the skill's lock entry already records, so a backing CLI is tamper-evident + to the same standard as the skill that required it. Deferred behind a real trigger + (`ARCHITECTURE-v0` §13 vNext); needs the lock schema to carry a binary reference. +- **SLSA/GPG** — not a current github-backend feature and not in v1; revisit with v2. + +## 9. Origin template changes + +The batteries-included template (`ARCHITECTURE-v0` §2d) gains: +1. **Per-package release-please** config emitting **build-metadata** tags (`v+`) + per CLI in `cmd/`, plus **goreleaser** producing `name_{version}_{os}_{arch}.tar.gz` + archives and a `checksums.txt` per release. +2. A populated **`.skillrig-origin.toml` `[[binaries]]`** block (§5) and the `index.yml` + workflow regenerating `index.json` (binaries + skills) on merge. +3. Docs: require **mise ≥ 2026.4.12** (Layer A), install the `skillrig` plugin, and set + `SKILLRIG_ORIGIN` / token. A worked `mise.toml` example. + +> If an adopting org *can* relax its tag policy, the template should note that **prefix +> streams + `version_prefix`** (option (a)) work on stock mise with no plugin — the plugin is +> the answer specifically for the **strict-semver + independent-versioning** org. + +## 10. Alternatives considered + +- **(a) Prefix tag streams + native `version_prefix`.** Cleanest natively, but the origin's + strict-semver tag policy forbids non-semver prefix tags. Viable only if the policy relaxes. +- **(c) One monolithic release with all binaries + `asset_pattern`.** Works on mise 2026.4.12, + but sacrifices **independent versioning** — every CLI bumps together. Rejected. +- **(d) Separate repo per CLI.** Cleanest for mise, but breaks the **co-location** that lets a + skill and its CLI ship/verify as one unit (`ARCHITECTURE-v0` §1). Rejected. +- **Native-stanza generator only** (`skillrig add` writes `[tool_alias]`+`asset_pattern`). + Attractive (no new repo), but inherits Layer B — it cannot generate a working stanza for + build-metadata streams. Useful as a *complement* for prefix-stream origins, not a substitute. +- **skillrig pulls binaries itself** (`ARCHITECTURE-v0` §13 vNext). Re-absorbs the job given to + mise (R17) and means owning cross-OS/arch selection + cache. The plugin keeps mise as the + installer. Rejected for now. + +## 11. Open questions + +1. **Plugin↔origin metadata fetch:** raw `index.json` over the GitHub API vs. a contents API + call for `.skillrig-origin.toml`; caching policy within a mise run. +2. **`{os}/{arch}` defaults:** the default RUNTIME→asset token mapping before any + `[binaries.platforms]` override (e.g. `x64`→`amd64`, `darwin`→`darwin`/`macos`). +3. **Archive shapes:** `strip = "auto"` heuristics for single-binary tarballs vs. nested dirs; + raw (un-archived) asset support. +4. **mise registry submission:** short name `skillrig` vs. installing by git URL for v1. +5. **convention bump policy:** does the plugin support conventions `N` and `N-1`? +6. **v2 treeSha binding:** lock schema for a binary reference and how `verify`/`doctor` check it. + +## 12. Phasing + +- **P0 (this RFC + spike):** decision recorded; `[[binaries]]` contract drafted; §8b corrected. +- **P1 — origin contract:** implement `[[binaries]]` in `.skillrig-origin.toml` + `skillrig + index` emission + template release pipeline (build-metadata tags + checksums). *(in + `skillrig/cli` + origin template)* +- **P2 — plugin v1:** bootstrap `skillrig/mise-skillrig`; three hooks; build-metadata stream + resolution; checksum verify; auth; tests against a fixture origin. +- **P3 — CLI auto-wiring:** `skillrig add` writes `skillrig:` stanzas from `requires`. +- **P4 (v2):** treeSha/provenance binding; SLSA; registry submission. + +## References + +See the spike: `specledger/013-mise-backend/research/2026-06-02-mise-backend-plugin.md` +(mise discussions #9074/#8266, PR #9093, mise github-backend / backend-plugin-development / +github-tokens docs, SemVer 2.0.0 build-metadata precedence). diff --git a/specledger/013-mise-backend/research/2026-06-02-mise-backend-plugin.md b/specledger/013-mise-backend/research/2026-06-02-mise-backend-plugin.md new file mode 100644 index 0000000..98c5e92 --- /dev/null +++ b/specledger/013-mise-backend/research/2026-06-02-mise-backend-plugin.md @@ -0,0 +1,257 @@ +# Research: mise backend for skillrig — multi-binary co-install from one origin monorepo + +**Date**: 2026-06-02 +**Context**: Issue [#23](https://github.com/skillrig/cli/issues/23) proposes a `skillrig` +mise **backend plugin** (vfox-style, Lua) so that an origin monorepo shipping N backing +CLIs in `cmd/` can be consumed as N distinct mise tools (`skillrig:jira`, `skillrig:tfc`, +…). The trigger was a validated collision: mise's stock `github` backend keyed a tool by +`owner/repo` and tracked one version per tool, so co-installing 2+ release streams from one +repo collapsed into a single tool. This spike answers the issue's four open questions and +the prior `ARCHITECTURE-v0 §8b` claims, to decide **whether** to build the plugin and, if +so, the contract it depends on. +**Time-box**: ~45 min (web + docs). + +## Question + +1. **OQ4 (gate):** Does a *current* mise (`asset_pattern` + aliases, ≥2026.5) cover + multi-binary-from-one-repo **natively**, before we commit to a plugin? +2. **OQ2:** Origin-metadata contract — does `.skillrig-origin.toml` / `index.json` already + carry per-binary stream + asset conventions, or do we add a `[[binaries]]` section? +3. **OQ3:** Verification depth for v1 — checksum-only vs. full provenance / treeSha tie-in. +4. **OQ1:** Plugin home / name & its own release cadence. + +Plus: validate the backend-plugin hook surface and the auth gotcha the issue documents. + +--- + +## Findings + +### Finding 1 — PR #9093 fixes the **scheduler**, not version resolution — partial fix only — *high confidence* + +> **Correction (after reviewing the PR, 2026-06-02).** An earlier draft of this finding +> claimed mise 2026.4.12 fully resolves the multi-binary case. That is **wrong** — it +> conflated two independent layers. The fix is necessary but **not sufficient** for the +> origin's actual tag scheme (see Finding 1b). + +There are **two layers**, and PR #9093 touches only the first: + +- **Layer A — install scheduler dedup.** The old dependency-graph node key was + `@`, so two aliases resolving to the same + `github:owner/repo@` collapsed into one install job. **PR #9093** re-keys nodes to + `@`, so distinct aliases get distinct scheduler slots and each + installs with its own options (`asset_pattern`, `bin_path`, `postinstall`). The PR + description explicitly states it **"does not touch version listing or resolution."** Shipped + in **mise 2026.4.12** (discussion #9074, *"tested with 2026.4.12, works OK ✅"*). +- **Layer B — version listing / resolution.** *Unchanged by #9093.* This is where the + origin's tag scheme breaks (Finding 1b). + +The native multi-asset pattern (discussion #8266) — which #9093 makes work — is `[tool_alias]` ++ per-platform `asset_pattern`: + +```toml +[tool_alias] +guest-init = "github:jingkaihe/matchlock" + +[tools."github:jingkaihe/matchlock"] +version = "latest" +platforms.linux-x64 = { asset_pattern = "matchlock-linux-amd64" } + +[tools.guest-init] +version = "latest" +platforms.linux-x64 = { asset_pattern = "guest-init-linux-amd64" } +``` + +This works **when both binaries ship in one release at one version** (each alias picks its +asset by pattern). It does **not** give *independent* version streams — see Finding 1b. + +### Finding 1b — semver **build-metadata** streams are structurally unresolvable natively — *high confidence (by spec)* + +The origin's constraint: its **tag policy enforces strict semver**, which *forbids* prefix +tags (`iii-v0.1.0` is not valid semver — leading non-numeric) but *permits* **build-metadata** +tags (`0.1.0+iii` is valid semver). So the org expresses independent CLI streams as +`v0.5.0+iii`, `v0.2.0+console`, etc. + +Per **SemVer 2.0.0**: *"Build metadata MUST be ignored when determining version precedence … +two versions that differ only in the build metadata have the same precedence."* Every +semver-respecting resolver (npm, Helm, and mise's `latest`/range resolution) treats +`0.5.0+iii` and `0.5.0+console` as the **same version**. Consequences for the github backend: + +- The listed version set collapses to `{0.5.0, 0.2.0}` with **no stream identity**. +- `latest` for the `console` alias resolves to `0.5.0` (the max across **all** streams), then + looks for a `console-*` asset in the `0.5.0+iii` release — **which doesn't contain it** → + install fails / wrong binary. +- mise's `version_prefix` only strips a **leading** prefix; there is **no** `version_suffix` + or `tag_regex`, so a `+iii` **suffix** cannot select a stream. +- Even *exact* pins (`= "0.5.0+iii"`) are brittle: precedence-based matching treats build + metadata as equal, and it defeats the point of tracking `latest` per stream. + +**So the native design space is:** + +| Option | Independent versions? | Co-location? | Strict-semver tags? | Native mise works? | +|---|---|---|---|---| +| (a) prefix streams `iii-v*` + `version_prefix` | ✅ | ✅ | ❌ (forbidden by policy) | ✅ | +| (b) build-metadata streams `0.5.0+iii` | ✅ | ✅ | ✅ | ❌ **broken (Layer B)** | +| (c) one release, all binaries, `asset_pattern` | ❌ | ✅ | ✅ | ✅ (post-#9093) | +| (d) separate repo per CLI | ✅ | ❌ | ✅ | ✅ | +| (e) **custom `skillrig:` backend plugin** | ✅ | ✅ | ✅ | ✅ (plugin owns listing) | + +**Implication — this *flips* the earlier conclusion.** A dedicated `skillrig:` backend plugin +is justified on **capability**, not merely ergonomics: its `BackendListVersions` hook **owns +version listing**, so it can parse the `+iii` build metadata, filter tags to that stream, and +present a clean per-tool version list — the one thing native mise structurally cannot do for +build-metadata streams under a strict-semver tag policy. Options (a)/(c)/(d) each sacrifice +something the architecture wants (policy compliance / independent versioning / co-location). + +### Finding 2 — `tag_regex` does not exist; the real knob is per-tool `version_prefix` — *high confidence* + +The issue (and `ARCHITECTURE-v0 §8b`) assume a `tag_regex` option keyed per tool. The +github-backend docs describe **no** `tag_regex`. Per-stream tag selection in a monorepo is +done with **`version_prefix`**: + +```toml +[tools] +"github:my-org/my-skills" = { version = "latest", version_prefix = "jira-v" } +``` + +So a build-metadata stream (`1.7.0+jira`) is *not* natively selectable, but a **prefix** +stream (`jira-v1.7.0`) is — via `version_prefix`. This matters: it means the **prefix tag +convention** (which release-please already produces per-package) is the natively-supported +one, and the origin template should standardize on it. + +### Finding 3 — backend-plugin hook surface (vfox-style) — *high confidence* + +Three Lua hooks under `hooks/`, addressed `plugin:tool`: + +| Hook | ctx fields | returns | +|---|---|---| +| `BackendListVersions(ctx)` | `ctx.tool`, `ctx.options` | `{versions = {…ascending semver…}}` | +| `BackendInstall(ctx)` | `ctx.tool`, `ctx.version`, `ctx.install_path`, `ctx.download_path`, `ctx.options` | `{}` | +| `BackendExecEnv(ctx)` | `ctx.tool`, `ctx.version`, `ctx.install_path`, `ctx.options` | `{env_vars = {{key="PATH", value=install_path.."/bin"}}}` | + +- `RUNTIME` injects `osType` / `archType` / `envType` (gnu|musl) for asset selection. +- **The plugin owns download + extraction + verification** into `install_path`; mise does + **not** checksum-verify for a custom backend (unlike the first-party `github` backend, + which does sha256 + `mise.lock`). So a plugin must *re-implement* checksum verification + that the native `github` backend gives for free. +- A backend plugin is a **git repo** addressed `plugin:tool`; the plugin name need not match + the repo. Installed via `mise plugin install `; optional PR to the mise + registry for a short name. There is a `jdx/mise-backend-plugin-template` (hooks + + `metadata.lua` + CI + lint). + +### Finding 4 — auth: the gotcha is real, but mise has first-class hooks — *high confidence* + +The issue's claim is confirmed: mise reads gh's token from `hosts.yml` **but cannot read a +gh token stored in an OS keyring/credential-helper** (the common macOS case), so it falls +back to anonymous and a private origin's `/releases` returns **404**. mise's documented +token precedence (github.com): + +1. `MISE_GITHUB_TOKEN` → 2. `GITHUB_API_TOKEN` → 3. `GITHUB_TOKEN` → +4. `settings.github.credential_command` → 5. native GitHub OAuth → +6. `github_tokens.toml` (per-host) → 7. gh CLI `hosts.yml` → 8. `git credential fill`. + +- The **enterprise-clean path is `credential_command`** (host passed via + `MISE_CREDENTIAL_HOST`), e.g. `op read 'op://Private/GitHub Token/credential'` — works for + the *native* github backend too, no plugin required. +- **Correction to §8b:** the doc already self-corrected that env vars precede + `credential_command`; this spike confirms env-vars-first (1–3 above 4). + +> Net: "central auth" is **not** a plugin-only value-add — `credential_command` + +> `MISE_GITHUB_TOKEN` already centralize it for the native backend. The keyring-404 is fixed +> by *documenting* `MISE_GITHUB_TOKEN=$(gh auth token)` or a `credential_command`, with or +> without a plugin. + +### Finding 5 — what a plugin buys (after the Finding 1b correction) — *high confidence on #1, medium on rest* + +The plugin's value is now led by **capability** (Finding 1b), with ergonomics following: + +1. **Resolves build-metadata streams — the capability native mise lacks.** Under a + strict-semver tag policy, `BackendListVersions` is the *only* place that can map + `0.5.0+iii` / `0.2.0+console` tags to distinct per-tool version lists. Native mise cannot + (Layer B / SemVer precedence). This is the load-bearing justification. +2. **Zero per-consumer boilerplate.** Even where native co-install *could* work (prefix + streams), it needs the consumer to hand-author per-platform `asset_pattern` + + `version_prefix` for *every* binary and to *know* each asset-name template. A `skillrig:` + backend learns stream + asset template from the **origin's own metadata**, so the consumer + writes only `skillrig:jira = "latest"`. +3. **One addressing scheme** (`plugin:tool`) instead of `[tool_alias]` indirection. +4. **Tamper-evidence parity with skills** — tie the binary's release tag/commit to the + `treeSha`/`commit` skillrig already records in `.skillrig/skills-lock.json`. + +…against real costs: a **separate Lua repo + release cadence**, and **re-implementing** +download/checksum that the native `github` backend provides for free. + +### Finding 6 — verification depth options — *medium confidence* + +- Native `github` backend: sha256 `checksum` + `mise.lock` lockfile; SLSA/GPG **not** + documented for the github backend (the issue's "optionally SLSA/attestation" is + aspirational, not a current github-backend feature). +- A custom backend must implement its own checksum verify (Finding 3). The origin already + ships `_checksums.txt` per release (per the issue's evidence), so checksum-verify is cheap. +- **Provenance/treeSha tie-in** (bind binary tag↔skill `commit`) is the genuinely + skillrig-specific guarantee, but it's strictly more than mise gives natively and belongs in + a later phase. + +--- + +## Decisions + +- **OQ4 — PARTIALLY resolved; plugin still justified.** PR #9093 (mise 2026.4.12) fixes the + install **scheduler** dedup (Layer A) — so multi-asset-from-one-release and same-version + distinct aliases now work. It does **not** touch version **resolution** (Layer B). The + origin's strict-semver tag policy forces **build-metadata** streams (`0.5.0+iii`), which + SemVer precedence collapses — natively unresolvable, and no `version_prefix`/`tag_regex` + can select a `+` suffix. So native mise covers the *easy* shapes but **not** the origin's + actual one. **The plugin is justified on capability** (its `BackendListVersions` owns + listing), not just ergonomics. The earlier "RESOLVED — ergonomics only" conclusion was + **wrong** and is corrected here (Findings 1, 1b). +- **OQ1.** If built, the plugin lives in its **own git repo** (e.g. `skillrig/mise-skillrig`), + backend name `skillrig` (used as `skillrig:`), released on its **own cadence** + independent of the CLI, optionally registered in the mise plugin registry. It is **not** + Go code in `skillrig/cli`. +- **OQ2.** The per-binary stream + asset conventions are **not** in `index.json`/origin + config today. They must be added as a **convention-versioned** contract (a `[[binaries]]` + / `tools` block in `.skillrig-origin.toml`, surfaced into `index.json`). This is + **`skillrig/cli` + origin-template work regardless** of plugin-vs-native, because both the + plugin *and* a native-stanza generator read the same metadata. +- **OQ3.** v1 = **checksum-only** (sha256 against the origin's `_checksums.txt`), matching + what the native backend already does; **provenance/treeSha binding is v2** behind a real + trigger. +- **§8b correction needed.** `tag_regex` → `version_prefix` (a *leading*-prefix stripper, no + suffix/regex selector exists); the one-binary-per-entry framing is **partly** superseded by + 2026.4.12 (scheduler only); "central auth" is achievable natively via + `credential_command`/`MISE_GITHUB_TOKEN`. + +## Recommendations + +1. **Build the dedicated `skillrig:` backend plugin** in its own repo — it is the only option + that satisfies *independent versioning + co-location + strict-semver tag policy* + simultaneously (Finding 1b, option (e)). The RFC commits to it as the new repo's reason to + exist. Its `BackendListVersions` parses build-metadata streams; `BackendInstall` + downloads + checksum-verifies the per-tool asset; `BackendExecEnv` puts it on PATH. +2. **Land the origin-metadata contract first**, in `skillrig/cli` + the origin template: + a **convention-versioned** `[[binaries]]` block in `.skillrig-origin.toml` surfaced into + `index.json`, carrying each binary's **stream selector** (build-metadata tag suffix) + + **asset-name template** per os/arch + checksum source. Both the plugin and any + `skillrig add` auto-wiring read this one contract (AP-04). +3. **Document the native fallbacks honestly** in the RFC (options (a)/(c)/(d)) so adopters who + *can* relax the tag policy to prefix streams, or accept monolithic releases, aren't forced + onto the plugin. The plugin is the answer **for the strict-semver + independent-versioning + org**; it is not the only way to consume an origin's CLIs. +4. **OQ3 verification:** checksum-only for v1 (origin already ships `_checksums.txt`); + treeSha/commit binding to `.skillrig/skills-lock.json` is v2. +5. **Correct `ARCHITECTURE-v0 §8b`** per the decisions above and pin the convention-version + that carries the new `[[binaries]]` block; **require mise ≥ 2026.4.12** (Layer A fix) in + the template. + +## References + +- Issue #23 — skillrig/cli (this repo). +- jdx/mise discussion **#9074** + **PR #9093** — multi-binary dedup fix (2026.4.12). +- jdx/mise discussion **#8266** — `tool_alias` + `asset_pattern` multi-binary pattern. +- mise docs — *GitHub Backend* (`asset_pattern`, `version_prefix`, `platforms`, `checksum`, + `mise.lock`, `api_url`). +- mise docs — *Backend Plugin Development* (hooks, ctx fields, `plugin:tool`, distribution) + + `jdx/mise-backend-plugin-template`. +- mise docs — *GitHub Tokens* (token precedence; keyring-404; `credential_command` / + `MISE_CREDENTIAL_HOST`). +- vfox docs — backend/plugin authoring (`RUNTIME` object). From 648847cc0ebe35c27be1922da477b6620a107e38 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 5 Jun 2026 09:16:10 +0000 Subject: [PATCH 2/2] =?UTF-8?q?docs(013):=20finalize=20RFC=200001=20?= =?UTF-8?q?=E2=80=94=20plugin=20built;=20reconcile=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The skillrig/mise-skillrig backend plugin RFC 0001 specifies is now built, published, and validated end-to-end. Fold the conclusion back into the docs and address the two Qodo review findings. Qodo findings resolved: - Stale tag_regex guidance: neutralize remaining tag_regex references in ARCHITECTURE §8b / open-Q15 / §13 vNext and ROADMAP vNext. tag_regex does not exist; stream selection is owned by the plugin's BackendListVersions (not version_prefix, which also cannot select build-metadata streams). - SLSA/GPG conflict: qualify the §8b claim as native-github-backend-only and aspirational; mise does NOT verify custom backends, so a skillrig: tool is checksum-only (sha256) in v1; SLSA is plugin-side v2. Merged the conclusion (plugin built) into: - RFC 0001: status -> Accepted/built; folded the four implementation divergences (no [[binaries]] required / convention-driven; address is skillrig://; tags 1.7.0+jira no leading v + per-tool _checksums.txt; origin field is an identity label, not the coordinate); reconciled hooks, verification, template, and phasing. - README: new "Backing CLIs (mise backend)" section linking the plugin + RFC. - ARCHITECTURE-v0 §8b/§13 and the spike: built-status notes. Roadmap (CLI next step) clearly defined: v1 "mise backend integration — CLI side" = RFC P1 ([[binaries]] block + index.json emission for metadata-driven resolution) + P3 (skillrig add auto-wiring of skillrig: entries). --- README.md | 21 ++ docs/ARCHITECTURE-v0.md | 44 ++-- docs/ROADMAP.md | 6 +- docs/rfcs/0001-mise-skillrig-backend.md | 240 +++++++++++------- .../2026-06-02-mise-backend-plugin.md | 5 + 5 files changed, 212 insertions(+), 104 deletions(-) diff --git a/README.md b/README.md index 91d848e..befc66f 100644 --- a/README.md +++ b/README.md @@ -251,3 +251,24 @@ origin = 'my-org/my-skills@staging' Unknown keys are ignored on read, so config added by later versions will not break this one. The full, extended `config.toml` structure is documented on the project docs website; see also [docs/design/cli.md](docs/design/cli.md) for the CLI design contract. + +## Backing CLIs (mise backend) + +A skill can require a backing CLI built in the same origin monorepo (`metadata.x-skillrig.requires`). +`skillrig` itself never installs binaries — provisioning is delegated to [mise](https://mise.jdx.dev). +For origins that ship **multiple** CLIs released on independent version streams, the +**[`skillrig/mise-skillrig`](https://github.com/skillrig/mise-skillrig)** mise backend plugin +lets you co-install them as distinct tools from one repo: + +```sh +mise plugin install skillrig https://github.com/skillrig/mise-skillrig +export MISE_GITHUB_TOKEN=$(gh auth token) +mise use skillrig:my-org/our-skills/jira@1.7.0 +mise use skillrig:my-org/our-skills/tfc@latest # two tools, one repo, distinct versions +``` + +This works where native mise cannot — independent versioning under a strict-semver +(build-metadata) tag policy. The design rationale, the origin contract, and the planned +`skillrig` CLI integration (metadata-driven resolution + `skillrig add` auto-wiring) are in +[**RFC 0001**](docs/rfcs/0001-mise-skillrig-backend.md) and tracked in +[docs/ROADMAP.md](docs/ROADMAP.md). diff --git a/docs/ARCHITECTURE-v0.md b/docs/ARCHITECTURE-v0.md index b2b660b..ed66898 100644 --- a/docs/ARCHITECTURE-v0.md +++ b/docs/ARCHITECTURE-v0.md @@ -295,31 +295,43 @@ The CLI can *offer* to write the matching `mise.toml` stanza (helpful), but inst ### 8b. mise GitHub-backend realities (grounded) — and why release-please-per-binary is load-bearing -> **Correction (2026-06-02, RFC 0001 + issue #23 validation).** Parts of this section are -> superseded. (i) mise has **no `tag_regex`** — per-stream selection uses **`version_prefix`** -> (a *leading*-prefix stripper only). (ii) The single-binary-per-entry *install collision* was -> a **scheduler** dedup bug, **fixed in mise 2026.4.12 (PR #9093)**; co-installing multiple -> binaries from one repo now works *for prefix or single-release shapes*. (iii) **But** a -> strict-semver tag policy forces **build-metadata** streams (`v0.5.0+iii`), which SemVer -> precedence collapses and `version_prefix` cannot select — so those remain natively -> unresolvable, which is why the **`skillrig` mise backend plugin** is justified on capability. +> **Correction (2026-06-05, RFC 0001 — plugin now BUILT & validated).** Parts of this +> section are **superseded** by the shipped [`skillrig/mise-skillrig`](https://github.com/skillrig/mise-skillrig) +> plugin, proven end-to-end co-installing two CLIs from one private monorepo. The prose below +> is **retained for history**; where it conflicts, the following wins: +> - **No `tag_regex` exists.** Every `tag_regex` mention below (and in §12 open-Q15 and the +> roadmap) is **outdated and must not be implemented**. Worse than a terminology slip: a +> per-tool `github:[tag_regex=…]` config **empirically cannot co-install** from a +> monorepo — mise keys a github-backend tool by `owner/repo` and tracks one version per tool, +> so all N entries collapse into a single install. The working mechanism is the **plugin's +> `BackendListVersions` owning version listing** (not `version_prefix`, which also cannot +> select build-metadata streams). +> - **The install collision (single-binary-per-entry) was a *scheduler* dedup bug**, fixed in +> mise 2026.4.12 (PR #9093) — necessary but **not sufficient**: it does not touch version +> *resolution*. A strict-semver tag policy forces **build-metadata** streams (e.g. +> `1.7.0+jira`), which SemVer precedence collapses — natively unresolvable, which is the +> capability reason the plugin exists. +> - **Finding (3)'s SLSA/GPG claim applies to the *native* github backend only, and is +> aspirational/unverified.** mise does **not** verify anything for a *custom* backend, so a +> `skillrig:` tool is **checksum-only (sha256) in v1**; SLSA/attestation is plugin-side v2. +> > See [`docs/rfcs/0001-mise-skillrig-backend.md`](rfcs/0001-mise-skillrig-backend.md) and the -> spike under `specledger/013-mise-backend/`. The grounded prose below is retained for history. +> spike under `specledger/013-mise-backend/`. Verified against mise's GitHub backend docs (current as of early 2026). Three findings shape the template: **(1) The one-binary-per-entry limit — the constraint that shapes the origin's release strategy.** mise's GitHub backend installs **a single binary per tool entry**; it does **not** natively fetch multiple binaries from one release (confirmed: `mise use github:org/repo` installs only the first asset and skips the rest; the community workaround is a per-tool postinstall `curl` for extra assets). This collides with the naive reading of "one monorepo origin with several backing CLIs in `cmd/`" if that monorepo cuts **one release containing all binaries**. The resolution — and it happens to be the pattern already chosen for versioning hygiene — is **per-CLI tagged release streams via release-please** (`oxid-v1.4.0`, `foo-v2.1.0`, …) rather than one monolithic release. So: - **release-please-per-binary is not just nice versioning — it is what makes mise consumption work at all** while preserving co-location. State this in the template explicitly. -- Because mise's `github:org/repo` shorthand tracks a repo's *latest release*, a monorepo with interleaved tag streams needs **per-tool tag filtering** (mise's version/tag-regex options) so each backing CLI tracks only its own prefix (`oxid-v*`). This is fiddly enough that **the template should generate the correct `mise.toml` stanza per backing CLI** — a concrete, valuable template job, not something each adopting org should reverse-engineer. -- Rejected alternative: **separate repo per CLI** (`github:my-org/oxid`) is cleanest for mise (each tracks its own latest, no tag-regex), but it **breaks the co-location** that lets a skill and its CLI ship together — so it fights the design. Monorepo-with-tag-streams is the chosen path; see open question on exactly how the template stamps `tag_regex`. +- Because mise's `github:org/repo` shorthand tracks a repo's *latest release*, a monorepo with interleaved tag streams needs **per-tool stream selection**. *(Superseded — see the correction above: there is no `tag_regex`, and the native option that does exist (`version_prefix`) cannot select build-metadata streams. Stream selection is now owned by the `skillrig/mise-skillrig` plugin's `BackendListVersions` hook.)* +- Rejected alternative: **separate repo per CLI** (`github:my-org/oxid`) is cleanest for mise (each tracks its own latest), but it **breaks the co-location** that lets a skill and its CLI ship together — so it fights the design. Monorepo-with-per-stream-tags is the chosen path; the `skillrig` plugin (not any `tag_regex` config) makes it work. **(2) Auth resolution is well-defined and matches R18.** mise resolves a GitHub token from (in order) the **env vars first** (`MISE_GITHUB_TOKEN` / `GITHUB_TOKEN`), then `github.credential_command`, then `github_tokens.toml`, then gh CLI `hosts.yml`, then git credential fill. The **`credential_command`** hook (runs a shell command per host to fetch a token — 1Password/Vault/secret-manager friendly) is the clean **enterprise auth path**, and it's host-aware for GitHub Enterprise. In CI, `jdx/mise-action` uses `${{ github.token }}` by default, so private-repo fetches in the org's own Actions need no extra plumbing. This is the same git/token auth skillrig already relies on (R5b) — no new credential surface. > **Correction (S3, 2026-05-31).** An earlier draft of this section claimed mise's precedence put `github.credential_command` *highest* (above the env vars). That is inaccurate — mise reads the env vars first. This does **not** affect skillrig, which never uses mise's `credential_command` and resolves its own fetch token directly via `os.exec`: `GH_TOKEN` env → `GITHUB_TOKEN` env → `gh auth token --hostname ` (exit 0 + non-empty = token; non-zero or `gh`-absent = skip, not fatal). skillrig injects that token per fetch via `git -c http.extraHeader="Authorization: Basic "` — **never** in the clone URL (avoids history/process-listing leakage). `git credential fill` is deferred (the `gh` path already covers the keyring tokens mise reads from `hosts.yml`); GitHub Enterprise is deferred behind the `hostname` seam of `ResolveGitHubToken(hostname string)`. -**(3) mise can verify what it pulls.** mise supports optional **SLSA provenance verification** and **GPG asset verification** for github-backend tools. This complements skillrig's own posture: skillrig verifies the *skill* (tree SHA + approval), mise can verify the *backing binary* (SLSA/GPG) — layered supply-chain integrity, neither owning the other's job. +**(3) mise can verify what it pulls — *for the native github backend only*.** mise is documented to support optional **SLSA provenance** and **GPG asset verification** for *native* `github`-backend tools (aspirational/unverified here). **Crucially, this does NOT extend to a custom backend:** mise performs no checksum/SLSA/GPG verification for `plugin:tool` installs, so a `skillrig:` tool's integrity is whatever the plugin implements. The shipped plugin is **checksum-only (sha256 vs the per-tool `_checksums.txt`) in v1**; SLSA/attestation is plugin-side **v2**. skillrig verifies the *skill* (tree SHA + approval); the plugin verifies the *backing binary* (checksum now, provenance later) — layered integrity, neither owning the other's job. -> **Net:** the co-located-monorepo origin is viable through mise, but only via per-CLI tagged releases + per-tool tag filtering, with the template generating the config. The "one big release with all binaries" shape does **not** work with mise today. +> **Net (revised):** the co-located-monorepo origin is viable, but for **build-metadata stream tags** under a strict-semver policy it works **only via the `skillrig/mise-skillrig` plugin**, not native mise config. The "one big release with all binaries" shape installs post-2026.4.12 but loses independent versioning. See RFC 0001. > **Reference design (from OpenClaw study):** OpenClaw's `openclaw skills list --eligible` is the closest prior art to `skillrig verify`'s prereq check — it filters to skills *actually runnable in the current environment*, treating "missing dependency or auth error" as the disqualifier. Adopt its shape: a skill is "eligible" iff every `[[requires]]` resolves (present + version satisfies) AND any private source is authenticable (R18). **`doctor`** (not `verify`) returns the eligible/ineligible partition with per-skill reasons — eligibility is a runtime-agent concern, not the content-integrity gate (§2). Study OpenClaw's dependency-declaration schema before finalizing the `[[requires]]` fields. @@ -445,8 +457,8 @@ Recommendation to pressure-test: **Option B for the core, borrow `gh skill`'s cl 11. **Skillset / bundle grouping** (from Skilldex) — should the skill **manifest** (`SKILL.md` frontmatter) support grouping a skill with its backing CLI + shared assets (vocab/templates/reference docs) as one coherently-versioned, co-vendored unit? Relevant to skills whose behavior depends on shared context. Likely v1. 12. **Third scope tier?** (from Skilldex's global/shared/project) — is a "shared" tier (team-wide, multi-repo, not user-global) needed, or do project + global suffice for v0? (Leaning: two tiers for v0.) 13. **`bump --pr` invocation policy** — enforce CI-only by token scoping so human agent sessions don't carry PR-create rights. -14. **Origin convention versioning** (§2d, R5e) — where the `skillrig-convention` version lives (in `index.json`? a top-level `.skillrig-origin.toml`?), the compatibility policy (binary supports conventions N and N-1?), and how the template's release workflow stamps it. The contract surface is small now; pin it before the first external org adopts. -15. **Template `mise.toml` generation for co-located CLIs** (§8b) — exactly how the template stamps per-tool `tag_regex`/version filters so each backing CLI in the monorepo tracks its own release stream, and whether to generate one shared `mise.toml` or per-skill stanzas keyed off each skill's `[[requires]]`. +14. **Origin convention versioning** (§2d, R5e) — where the `skillrig-convention` version lives (in `index.json`? a top-level `.skillrig-origin.toml`?), the compatibility policy (binary supports conventions N and N-1?), and how the template's release workflow stamps it. *(Partially settled by the plugin: it best-effort-checks `skillrigConvention` from `index.json` (currently `1`) and deliberately **never** checks the origin **name** — that is an identity label that legitimately differs from the hosting coordinate. See RFC 0001 §6.)* +15. **Template `mise.toml` generation for co-located CLIs** (§8b) — ~~how the template stamps per-tool `tag_regex`/version filters~~ **RESOLVED by the `skillrig/mise-skillrig` plugin** (RFC 0001): consumers address `skillrig://@` and the plugin owns stream selection — no `tag_regex` (which does not exist) and no per-tool `mise.toml` filter generation. Open follow-up is the CLI's **`skillrig add` auto-wiring** of those `skillrig:` entries from each skill's `[[requires]]` (RFC 0001 P1/P3; see `docs/ROADMAP.md`). --- @@ -482,7 +494,7 @@ The smallest thing that delivers the core promise ("the skill your agent runs is ### vNext — candidates needing a trigger Each only justified if its trigger fires; recorded so they aren't silently assumed. -- **skillrig pulls backing binaries itself (skills + CLIs in one fetch).** *Trigger:* mise's one-binary-per-release limit + `tag_regex` fiddliness (§8b) proves painful enough that skillrig orchestrating a co-located skill+CLI fetch is worth owning. *Tension:* this partially re-absorbs the binary-provisioning job deliberately delegated to mise (R17) — so it needs a real pain signal, not just "it'd be neat." Would also mean owning cross-OS/arch asset selection, checksum/SLSA verification of binaries, and a cache — non-trivial surface. +- **skillrig pulls backing binaries itself (skills + CLIs in one fetch).** *Status:* the multi-binary-from-one-monorepo gap is now solved out-of-process by the **[`skillrig/mise-skillrig`](https://github.com/skillrig/mise-skillrig)** backend plugin (built & validated; RFC 0001), so this trigger has **not** fired — mise (via the plugin) still installs. *Tension (unchanged):* owning the fetch re-absorbs the job delegated to mise (R17) — so it needs a real pain signal, not just "it'd be neat." Would also mean owning cross-OS/arch asset selection, checksum/SLSA verification of binaries, and a cache — non-trivial surface. - **Convention contract v2+** — evolving the origin contract (§2d, R5e) as conventions change; requires the v0 convention-version mechanism to already be in place. - **MCP surface for agents** — expose `verify`/`search` as MCP tools; must dispatch to the same `skillcore` (§2), never a parallel implementation. - **Client-tier differentiation** (strict/enterprise vs. lean) — a *deployment* concern layered on the same architecture (private-Pages on/off, `doctor` auth strictness, auto-merge policy, risk-score hard-gating per D6), not a requirements change (D4, §10). diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 863cda8..3365ac6 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -58,6 +58,10 @@ resolver — AP-04 / AP-06) and layers thin commands on top. ## v1 — governance + ergonomics (once usage justifies) +- **mise backend integration — CLI side (RFC 0001 P1 + P3).** The [`skillrig/mise-skillrig`](https://github.com/skillrig/mise-skillrig) backend plugin now ships and consumes origins **convention-driven** (it derives stream/asset/checksum names from the goreleaser convention `___.tar.gz` + `_checksums.txt` and reads the binary from each tag's build metadata) — so it needs **no** origin-side schema to work. This roadmap item makes that resolution **metadata-driven and auto-wired** from `skillrig/cli`: + - **P1 — origin metadata contract (optional override, not a prerequisite).** Add a convention-versioned **`[[binaries]]`** block to `.skillrig-origin.toml` and **emit it into `index.json`** from `skillrig index` (stream selector + asset template + checksum filename + per-os/arch platform map). The plugin prefers this metadata when present and falls back to the convention otherwise. One contract, two readers (plugin + CLI) — AP-04. + - **P3 — `skillrig add` auto-wiring.** When `add` vendors a skill whose `metadata.x-skillrig.requires` names a binary sourced from the origin, write the matching `skillrig://` entry (+ version constraint) into the consumer's `mise.toml`, under the same dry-run/force discipline as `add`. Replaces the rejected "template stamps per-tool `tag_regex`" idea (§8b open-Q15 — `tag_regex` does not exist). + - Requires **mise ≥ 2026.4.12** (the install-scheduler fix, PR #9093) on consumers. See [`docs/rfcs/0001-mise-skillrig-backend.md`](rfcs/0001-mise-skillrig-backend.md) §5/§7/§12 and the spike under `specledger/013-mise-backend/`. - **Audit mode** `doctor --audit`: classify on-disk skills as OK / policy-violation / orphan (§9b). - **External-source allowlist** with graded allowance levels (blocked / advisory / approved / pinned-only), enforced in `doctor` as a deterministic offline lookup (R27, §9b). - **Risk-signal surfacing** (e.g. Snyk) — advisory, human-facing, online-only, behind a swappable provider interface; **never** in `verify` (R29, §9b). @@ -73,7 +77,7 @@ resolver — AP-04 / AP-06) and layers thin commands on top. Each is justified only if its trigger fires; recorded here so they aren't silently assumed. -- **skillrig pulls backing binaries itself (skills + CLIs in one fetch).** *Trigger:* mise's one-binary-per-release limit + `tag_regex` fiddliness (§8b) proves painful enough to own. *Tension:* re-absorbs the binary-provisioning job deliberately delegated to mise (R17) — needs a real pain signal, plus cross-OS/arch asset selection, checksum/SLSA verification, and a cache. +- **skillrig pulls backing binaries itself (skills + CLIs in one fetch).** *Trigger:* the mise-delegated path proves painful enough to own. *Status update:* the multi-binary-from-one-monorepo gap is now solved **out-of-process** by the **[`skillrig/mise-skillrig`](https://github.com/skillrig/mise-skillrig) backend plugin** (built & validated; RFC 0001), so this vNext item's trigger has **not** fired — mise (via the plugin) still installs. *Tension (unchanged):* owning the fetch re-absorbs the job delegated to mise (R17) — needs a real pain signal, plus cross-OS/arch asset selection, checksum/SLSA verification, and a cache. - **Convention contract v2+** — evolving the origin contract (§2d, R5e); requires the v0 convention-version mechanism already in place. - **MCP surface for agents** — expose `verify`/`search` as MCP tools; MUST dispatch to the same `skillcore` (§2), never a parallel implementation. - **Client-tier differentiation** (strict/enterprise vs. lean) — a *deployment* concern layered on the same architecture (private-Pages on/off, `doctor` auth strictness, auto-merge policy, risk-score hard-gating per D6), not a requirements change (D4, §10). diff --git a/docs/rfcs/0001-mise-skillrig-backend.md b/docs/rfcs/0001-mise-skillrig-backend.md index 386e56b..0b76606 100644 --- a/docs/rfcs/0001-mise-skillrig-backend.md +++ b/docs/rfcs/0001-mise-skillrig-backend.md @@ -1,14 +1,30 @@ # RFC 0001 — The `skillrig` mise backend plugin -**Status:** Draft for review +**Status:** ✅ **Accepted — plugin BUILT, published, and validated end-to-end (2026-06-05).** +**Plugin repo:** [`skillrig/mise-skillrig`](https://github.com/skillrig/mise-skillrig) (public) **Author:** generated from issue [#23](https://github.com/skillrig/cli/issues/23) **Spike:** [`specledger/013-mise-backend/research/2026-06-02-mise-backend-plugin.md`](../../specledger/013-mise-backend/research/2026-06-02-mise-backend-plugin.md) **Relates to:** `docs/ARCHITECTURE-v0.md` §8 (backing-CLI provisioning), §8b (mise realities), §13 vNext -**Bootstraps:** a **new, separate repository** — `skillrig/mise-skillrig` (the plugin is Lua, not Go; it does **not** live in `skillrig/cli`) +**Bootstrapped:** the separate **`skillrig/mise-skillrig`** repo (the plugin is Lua, not Go; it does **not** live in `skillrig/cli`) > **Scope note (pre-release marker, per `CLAUDE.md`).** No backward compatibility is planned. -> This RFC defines a new artifact and a new convention-versioned origin contract; it may -> change freely until it ships. +> This RFC defines a new artifact and an (optional) convention-versioned origin contract; it +> may change freely. + +> **Implementation status (2026-06-05).** The plugin specified here is **built and validated**: +> two backing CLIs co-installed from one private monorepo as distinct, independently-versioned +> tools — the capability native mise cannot provide. Verified with 54 offline unit tests, +> `stylua` + `lua-language-server` clean, CI green on ubuntu + macOS, an adversarial +> multi-lens review, and a live two-tool co-install. **§5–§8 and §12 below have been +> reconciled with what actually shipped** — the four divergences are called out inline as +> *“Shipped:”* notes. Quickstart: +> ```sh +> mise settings experimental=true +> mise plugin install skillrig https://github.com/skillrig/mise-skillrig +> export MISE_GITHUB_TOKEN=$(gh auth token) +> mise use skillrig:my-org/our-skills/jira@1.7.0 +> mise use skillrig:my-org/our-skills/tfc@latest # two tools, one repo, distinct versions +> ``` --- @@ -22,19 +38,30 @@ delegated to **mise** (`ARCHITECTURE-v0` §8 / R17 — *"skillrig declares and v installs"*). This RFC specifies a **mise backend plugin** named `skillrig` so that N backing CLIs in one -origin become N **distinct** mise tools, addressed `skillrig:@`, each tracking -its own independent release stream: +origin become N **distinct** mise tools, addressed `skillrig://@`, +each tracking its own independent release stream: ```toml # consumer mise.toml -"skillrig:iii" = "latest" -"skillrig:console" = "0.2.0" +"skillrig:my-org/our-skills/jira" = "1.7.0" +"skillrig:my-org/our-skills/tfc" = "latest" # two tools, one repo, distinct versions ``` -It is a separate, independently-released repo. This document also defines the **origin-side -contract** (a convention-versioned `[[binaries]]` block) the plugin depends on, and the -changes to the **origin template** and **`skillrig` CLI** that make the three pieces work -together. +> **Shipped (divergence #2):** the GitHub coordinate is embedded **in the address** +> (`skillrig://`), not a bare `skillrig:` plus a separate `origin` +> option. A bare `skillrig:` + an `origin` option / `SKILLRIG_ORIGIN` env also works as a +> shorthand. This avoids per-tool `origin` config and matches the origin's +> `docs/BINARY-DISTRIBUTION.md` §4. + +It is a separate, independently-released repo. This document also defines an **optional +origin-side contract** (a convention-versioned `[[binaries]]` block) and the changes to the +**origin template** and **`skillrig` CLI** that make the three pieces work together. + +> **Shipped (divergence #1):** the `[[binaries]]` block is **not required**. The plugin is +> **convention-driven** — it derives stream/asset/checksum names from the goreleaser +> convention and reads the binary from each tag's build metadata, so it works against an +> origin that has **no** `[[binaries]]` block. The block (P1, §5/§12) is an *optional +> metadata-driven override*, not a prerequisite. ## 2. Motivation — why native mise is not enough *for this origin* @@ -120,31 +147,42 @@ cannot do. That is the capability justification for this plugin. └───────────────────────────────────────────────────────────────────────────┘ ``` -1. **Origin template** ships the release pipeline that produces per-stream tags + assets + - `checksums.txt`, *and* the `[[binaries]]` metadata describing them (§5). -2. **The plugin** reads that metadata to resolve/install each tool (§6). +1. **Origin template** ships the release pipeline that produces per-stream build-metadata tags + (`1.7.0+jira`) + `___.tar.gz` assets + per-tool `_checksums.txt`; + optionally the `[[binaries]]` metadata describing them (§5, P1). +2. **The plugin** resolves/installs each tool — convention-driven, preferring `[[binaries]]` + metadata when present (§6). 3. **The `skillrig` CLI** auto-wires the consumer's `mise.toml` when a vendored skill requires - a binary, and reuses its own origin resolution + token resolver (§7). - -## 5. The origin-side contract (`[[binaries]]`) — OQ2 - -The per-binary stream + asset conventions do **not** exist in the origin metadata today. We -add them as a **convention-versioned** block in `.skillrig-origin.toml`, mirrored into the -generated `index.json` so the plugin can fetch a single file. This is `skillrig/cli` + -origin-template work, shared by the plugin and any native-stanza generator (AP-04 — one -contract, many readers). + a binary, and reuses its own origin resolution + token resolver (§7, RFC P3). + +## 5. The origin-side contract (`[[binaries]]`) — OQ2 — **optional override (P1)** + +> **Shipped (divergence #1 & #3).** The plugin does **not** need this block. It is +> **convention-driven**, deriving everything from the goreleaser release convention the origin +> template already produces: +> - **Tags** are `+` — **no leading `v`**, build metadata is the **bare tool +> name**: real tags are `1.7.0+jira`, `0.0.6+tfc` (the §-examples below previously wrote +> `v0.5.0+iii`; corrected here). Prefix streams `-v` are also supported. +> - **Assets** are `___.tar.gz`. +> - **Checksums** are **per-tool** `_checksums.txt` (e.g. `jira_checksums.txt`), **not** a +> single shared `checksums.txt`. +> +> The `[[binaries]]` block below is therefore the **P1 metadata-driven override** — adopt it +> when an origin diverges from the convention or wants explicit control; the plugin prefers it +> when present and falls back to the convention otherwise. One contract, two readers (plugin + +> CLI) — AP-04. ```toml -# .skillrig-origin.toml (origin repo root) +# .skillrig-origin.toml (origin repo root) — OPTIONAL skillrig-convention = 1 -origin = "my-org/my-skills" +origin = "foo-org-sports/local-devops-scripts" # identity LABEL — see divergence #4 below [[binaries]] -name = "iii" # → mise tool name `skillrig:iii` -stream = "+iii" # semver BUILD-METADATA suffix identifying this stream -asset = "iii_{version}_{os}_{arch}.tar.gz" # {version}=semver core, {os}/{arch} mapped -checksums = "checksums.txt" # asset in the same release; sha256 filename lines -bin = "iii" # executable path inside the archive (post strip) +name = "jira" # → mise tool `skillrig://jira` +stream = "+jira" # semver BUILD-METADATA suffix (bare tool name; no leading v on tags) +asset = "jira_{version}_{os}_{arch}.tar.gz" # {version}=semver core, {os}/{arch} mapped +checksums = "jira_checksums.txt" # PER-TOOL checksums asset; sha256 filename lines +bin = "jira" # executable path inside the archive (post strip) # optional: map mise RUNTIME tokens → this asset's tokens (defaults: linux/darwin, amd64/arm64) [binaries.platforms.linux-x64] @@ -155,36 +193,45 @@ bin = "iii" # executable path inside the archive (post str arch = "arm64" [[binaries]] -name = "console" -stream = "+console" -asset = "console_{version}_{os}_{arch}.tar.gz" -checksums = "checksums.txt" -bin = "console" +name = "tfc" +stream = "+tfc" +asset = "tfc_{version}_{os}_{arch}.tar.gz" +checksums = "tfc_checksums.txt" +bin = "tfc" ``` -`skillrig index` (already the catalog generator) emits the same data into `index.json`: +> **Shipped (divergence #4): `origin` is an identity *label*, not the hosting coordinate.** The +> demo origin's files say `origin = "foo-org-sports/local-devops-scripts"` while it is hosted +> at `so0k/skillrig-origin-demo`. The plugin therefore keys off the **address coordinate** +> (`skillrig://`) and verifies only `skillrigConvention` — it **never** +> checks the origin name. + +When the override is present, `skillrig index` emits it into `index.json`: ```jsonc { "skillrigConvention": 1, - "origin": "my-org/my-skills", + "origin": "foo-org-sports/local-devops-scripts", // identity label only "binaries": [ - { "name": "iii", "stream": "+iii", - "asset": "iii_{version}_{os}_{arch}.tar.gz", - "checksums": "checksums.txt", "bin": "iii", + { "name": "jira", "stream": "+jira", + "asset": "jira_{version}_{os}_{arch}.tar.gz", + "checksums": "jira_checksums.txt", "bin": "jira", "platforms": { "linux-x64": {"os":"linux","arch":"amd64"}, "...": {} } } ], "skills": { /* unchanged */ } } ``` -**Convention versioning (R5e).** The plugin reads `skillrigConvention` and fails clearly -against an incompatible origin rather than mis-resolving. v1 understands convention `1`. +**Convention versioning (R5e).** The plugin best-effort-reads `skillrigConvention` from +`index.json` (currently `1`) and fails clearly against an incompatible origin rather than +mis-resolving. It **never** checks the origin **name** (divergence #4 — that field is an +identity label that legitimately differs from the hosting coordinate). ## 6. The plugin — design (OQ1) -**Repo / naming.** New repo **`skillrig/mise-skillrig`**; mise backend name `skillrig` -(tools addressed `skillrig:`); the plugin name need not match the repo. Installed via +**Repo / naming.** Repo **[`skillrig/mise-skillrig`](https://github.com/skillrig/mise-skillrig)** +(public, shipped); mise backend name `skillrig`; tools addressed +**`skillrig://`** (the plugin name need not match the repo). Installed via `mise plugin install skillrig https://github.com/skillrig/mise-skillrig` (or registered in the mise plugin registry for the short name). **Released on its own cadence**, independent of the `skillrig` CLI. @@ -208,15 +255,21 @@ skillrig/mise-skillrig/ └── README.md ``` -**Origin resolution (inside the plugin).** The plugin must know which origin to read. -Precedence (mirrors the CLI for consistency): -`ctx.options.origin` (per-tool in `mise.toml`) → `SKILLRIG_ORIGIN` env → error with a fix. +**Origin resolution (inside the plugin).** The origin coordinate is normally **embedded in the +address** (`skillrig://`), so no extra config is needed. For the bare +`skillrig:` shorthand, precedence (mirrors the CLI): `ctx.options.origin` (per-tool in +`mise.toml`) → `SKILLRIG_ORIGIN` env → error with a fix. (Recall: this `/` is the +**hosting coordinate**, distinct from the origin's `origin` identity-label field — divergence +#4.) ```toml -# explicit per-tool origin (when not using SKILLRIG_ORIGIN) -[tools."skillrig:iii"] +# preferred: coordinate in the address (no per-tool origin config) +"skillrig:my-org/our-skills/jira" = "1.7.0" + +# shorthand: bare tool + explicit origin +[tools."skillrig:jira"] version = "latest" -origin = "my-org/my-skills" +origin = "my-org/our-skills" ``` ### 6.1 `BackendListVersions` — the load-bearing hook @@ -228,13 +281,12 @@ suffix, and returns clean semver cores ascending. ```lua -- hooks/backend_list_versions.lua function PLUGIN:BackendListVersions(ctx) - local meta = origin.binary_meta(ctx.tool) -- from index.json, by ctx.tool - local tags = github.list_tags(origin.repo()) -- authed; e.g. {"v0.5.0+iii","v0.2.0+console"} - local stream = meta.stream -- "+iii" + local meta = origin.binary_meta(ctx.tool) -- coordinate+bin from the address; index.json optional + local tags = github.list_tags(meta.repo) -- authed; real tags e.g. {"1.7.0+jira","0.0.6+tfc"} local versions = {} for _, tag in ipairs(tags) do - local core, build = stream.parse(tag) -- "0.5.0", "iii" (strips leading v) - if build == stream.suffix(meta.stream) then -- belongs to THIS stream + local core, build = stream.parse(tag) -- "1.7.0", "jira" (no leading v; bare tool name) + if build == meta.bin then -- build metadata == this tool's stream versions[#versions + 1] = core end end @@ -252,22 +304,22 @@ cannot produce for build-metadata tags. -- hooks/backend_install.lua function PLUGIN:BackendInstall(ctx) local meta = origin.binary_meta(ctx.tool) - local tag = "v" .. ctx.version .. meta.stream -- "0.5.0" + "+iii" → "v0.5.0+iii" - local rel = github.release_by_tag(origin.repo(), tag) -- authed + local tag = ctx.version .. "+" .. meta.bin -- "1.7.0" + "+jira" → "1.7.0+jira" (no leading v) + local rel = github.release_by_tag(meta.repo, tag) -- authed - local plat = meta.platforms[RUNTIME.osType .. "-" .. RUNTIME.archType] or stream.default_plat() - local name = stream.render(meta.asset, { -- "iii_0.5.0_linux_amd64.tar.gz" - version = ctx.version, os = plat.os, arch = plat.arch, - }) + local plat = meta.platform() -- RUNTIME os/arch → asset tokens + local name = meta.asset(ctx.version, plat) -- "jira_1.7.0_linux_amd64.tar.gz" local asset = github.find_asset(rel, name) local file = github.download(asset, ctx.download_path) - -- checksum verify (mise does NOT verify for custom backends — the plugin must) - local sums = github.download(github.find_asset(rel, meta.checksums), ctx.download_path) + -- checksum verify (mise does NOT verify for CUSTOM backends — the plugin must). + -- checksums asset is PER-TOOL: "_checksums.txt", e.g. "jira_checksums.txt". + local sums = github.download(github.find_asset(rel, meta.bin .. "_checksums.txt"), ctx.download_path) checksum.verify_sha256(file, name, sums) -- abort on mismatch archive.extract(file, ctx.install_path, { strip = "auto" }) + -- normalize the binary into install_path/bin/ so BackendExecEnv stays trivial return {} end ``` @@ -295,22 +347,25 @@ documenting either `MISE_GITHUB_TOKEN=$(gh auth token)` or a `credential_command reuses this resolver via mise's HTTP/token helpers; it introduces **no new credential surface** (consistent with `ARCHITECTURE-v0` §2b — no write credential anywhere). -**`skillrig` CLI auto-wiring (in this repo, `skillrig/cli`).** When `skillrig add ` -vendors a skill whose `metadata.x-skillrig.requires` names a binary sourced from the origin, -the CLI writes the matching `mise.toml` stanza: +**`skillrig` CLI auto-wiring (in this repo, `skillrig/cli` — RFC P3).** When `skillrig add +` vendors a skill whose `metadata.x-skillrig.requires` names a binary sourced from the +origin, the CLI writes the matching `mise.toml` stanza, embedding the origin coordinate in the +address (divergence #2): ```toml -"skillrig:iii" = ">=0.4.0" # from requires[].version +"skillrig:my-org/our-skills/jira" = ">=0.4.0" # from requires[].version ``` This is the one piece of plugin support that belongs in the Go CLI (it owns `add`, origin -resolution, and the lock). It is gated behind the same dry-run/force discipline as `add`. +resolution, and the lock). It is gated behind the same dry-run/force discipline as `add`. See +the roadmap entry "mise backend integration — CLI side" in `docs/ROADMAP.md`. ## 8. Verification depth (OQ3) -- **v1 — checksum-only.** sha256 against the origin's `checksums.txt` (already published). - This matches what mise's native `github` backend offers, re-implemented in the plugin - because mise does not verify for custom backends. +- **v1 — checksum-only (shipped).** sha256 against the origin's **per-tool** + `_checksums.txt`. mise performs **no** verification for a *custom* backend (unlike the + native `github` backend), so the plugin implements this itself — a `skillrig:` tool gets no + mise-side SLSA/GPG today regardless of native-backend support. - **v2 — provenance / treeSha parity.** Bind the binary's release tag/commit to the `treeSha`/`commit` the skill's lock entry already records, so a backing CLI is tamper-evident to the same standard as the skill that required it. Deferred behind a real trigger @@ -320,17 +375,21 @@ resolution, and the lock). It is gated behind the same dry-run/force discipline ## 9. Origin template changes The batteries-included template (`ARCHITECTURE-v0` §2d) gains: -1. **Per-package release-please** config emitting **build-metadata** tags (`v+`) - per CLI in `cmd/`, plus **goreleaser** producing `name_{version}_{os}_{arch}.tar.gz` - archives and a `checksums.txt` per release. -2. A populated **`.skillrig-origin.toml` `[[binaries]]`** block (§5) and the `index.yml` - workflow regenerating `index.json` (binaries + skills) on merge. +1. **Per-package release-please** config emitting **build-metadata** tags `+` + (e.g. `1.7.0+jira` — **no leading `v`**, build metadata is the **bare tool name**) per CLI + in `cmd/`, plus **goreleaser** producing `___.tar.gz` archives and a + **per-tool** `_checksums.txt` per release. +2. *(Optional, P1)* a populated **`.skillrig-origin.toml` `[[binaries]]`** block (§5) and the + `index.yml` workflow regenerating `index.json` (binaries + skills) on merge — for + metadata-driven resolution. The plugin works without it (convention-driven). 3. Docs: require **mise ≥ 2026.4.12** (Layer A), install the `skillrig` plugin, and set - `SKILLRIG_ORIGIN` / token. A worked `mise.toml` example. + `MISE_GITHUB_TOKEN` / `SKILLRIG_ORIGIN`. A worked `mise.toml` example using + `skillrig://` addressing. > If an adopting org *can* relax its tag policy, the template should note that **prefix > streams + `version_prefix`** (option (a)) work on stock mise with no plugin — the plugin is -> the answer specifically for the **strict-semver + independent-versioning** org. +> the answer specifically for the **strict-semver + independent-versioning** org. (The plugin +> supports prefix streams `-v` too.) ## 10. Alternatives considered @@ -361,14 +420,21 @@ The batteries-included template (`ARCHITECTURE-v0` §2d) gains: ## 12. Phasing -- **P0 (this RFC + spike):** decision recorded; `[[binaries]]` contract drafted; §8b corrected. -- **P1 — origin contract:** implement `[[binaries]]` in `.skillrig-origin.toml` + `skillrig - index` emission + template release pipeline (build-metadata tags + checksums). *(in - `skillrig/cli` + origin template)* -- **P2 — plugin v1:** bootstrap `skillrig/mise-skillrig`; three hooks; build-metadata stream - resolution; checksum verify; auth; tests against a fixture origin. -- **P3 — CLI auto-wiring:** `skillrig add` writes `skillrig:` stanzas from `requires`. -- **P4 (v2):** treeSha/provenance binding; SLSA; registry submission. +- **P0 — RFC + spike — ✅ done.** decision recorded; §8b corrected. +- **P2 — plugin v1 — ✅ done (out of order).** [`skillrig/mise-skillrig`](https://github.com/skillrig/mise-skillrig) + shipped: three hooks; build-metadata + prefix stream resolution; per-tool checksum verify; + auth; 54 unit tests; CI green ubuntu+macOS; validated against a **real** origin. Built + **convention-driven**, so it did **not** require P1 first. +- **P1 — origin metadata contract (now an *optional enhancement*, the CLI next step) — ⬜.** + Tracked on the CLI roadmap (`docs/ROADMAP.md`, v1 "mise backend integration — CLI side"): + add the convention-versioned **`[[binaries]]`** block to `.skillrig-origin.toml`, **emit it + into `index.json`** from `skillrig index` (so resolution can be *metadata-driven* instead of + convention-only), and align the origin template's release pipeline (build-metadata tags + + per-tool `_checksums.txt`). The plugin prefers this metadata when present, else falls + back to the convention. *(in `skillrig/cli` + origin template)* +- **P3 — CLI `add` auto-wiring — ⬜.** `skillrig add` writes `skillrig://` + stanzas into the consumer's `mise.toml` from each skill's `requires`. *(in `skillrig/cli`)* +- **P4 (v2) — ⬜.** treeSha/provenance binding; SLSA; mise registry submission. ## References diff --git a/specledger/013-mise-backend/research/2026-06-02-mise-backend-plugin.md b/specledger/013-mise-backend/research/2026-06-02-mise-backend-plugin.md index 98c5e92..c3b956f 100644 --- a/specledger/013-mise-backend/research/2026-06-02-mise-backend-plugin.md +++ b/specledger/013-mise-backend/research/2026-06-02-mise-backend-plugin.md @@ -10,6 +10,11 @@ repo collapsed into a single tool. This spike answers the issue's four open ques the prior `ARCHITECTURE-v0 §8b` claims, to decide **whether** to build the plugin and, if so, the contract it depends on. **Time-box**: ~45 min (web + docs). +**Outcome (2026-06-05)**: spike conclusion **confirmed by implementation** — the +[`skillrig/mise-skillrig`](https://github.com/skillrig/mise-skillrig) plugin was built and +validated end-to-end against a real origin (two CLIs co-installed from one monorepo as +distinct, independently-versioned tools). See RFC 0001 (`docs/rfcs/0001-mise-skillrig-backend.md`) +for the as-built design and the four RFC↔implementation divergences folded back in. ## Question