Skip to content

feat(makefile): plugin resolver and lockfile (Story 13.3)#34

Merged
matthew-on-git merged 1 commit intomainfrom
feat/13-3-plugin-resolver
May 3, 2026
Merged

feat(makefile): plugin resolver and lockfile (Story 13.3)#34
matthew-on-git merged 1 commit intomainfrom
feat/13-3-plugin-resolver

Conversation

@matthew-on-git
Copy link
Copy Markdown
Contributor

Summary

Implements make plugins-update and _plugins-verify for the v1.10.x plugin architecture. Builds on Story 13.2's loader (PR #31, v1.10.0) + review fixes (PR #33, v1.10.1).

What's in the PR

`scripts/plugin-resolver.sh`

For each plugin in `.devrail.yml`:

  • Resolve `rev:` to an immutable SHA via `git ls-remote` (SHA passthrough for 40-char hex; tag-to-SHA via ls-remote, peeled form preferred for annotated tags).
  • Reject branch refs with a clear error event.
  • Fetch the tree to `${DEVRAIL_PLUGINS_DIR:-/opt/devrail/plugins}///` (matches the path Story 13.2's loader reads).
  • Compute deterministic `content_hash` (LC_ALL=C, find+sort+sha256sum, excludes `.git/` + `.devrail.sha`).
  • Write `.devrail.lock` atomically via temp+rename. Sorted by source for deterministic diffs.
  • Idempotent — second update on the same rev/sha is a no-op.

`scripts/plugin-lockfile-verify.sh`

Fast verification used as `_plugins-load` prereq on every `make check`:

  • Cross-check every yml plugin entry has a matching lock entry with the same rev.
  • Re-compute `content_hash` on the cached tree, compare to recorded hash → detects tag-rebase tampering.
  • No-op when `.devrail.yml` has no `plugins:` (v1.9.x/v1.10.x regression-safe).

Makefile

  • New public `plugins-update` target.
  • New internal `_plugins-update` (resolver) and `_plugins-verify` (verifier).
  • `_plugins-load` prereq updated: `_check-config → _plugins-verify → _plugins-load`.

Fixtures + tests

  • `tests/fixtures/plugin-repos/elixir-v1/` + `elixir-v1-tampered/` — fixture trees (no `.git/`); harness initialises git per case.
  • `tests/test-plugin-resolver.sh` — 11-case smoke.
  • `tests/test-plugin-loader.sh` — updated integration cases to write a matching `.devrail.lock` via a new `write_matching_lockfile` helper.

Acceptance criteria from Story 13.3

  • AC 1 — `make plugins-update` resolves rev via `git ls-remote`; SHA passthrough; tag-to-SHA; branch refs rejected
  • AC 2 — `.devrail.lock` written with `source/rev/sha/schema_version/content_hash`; sorted; top-level `schema_version: 1`
  • AC 3 — clone lands at `///`; idempotent re-fetch; atomic via temp+rename
  • AC 4 — `_plugins-verify` enforces yml↔lock consistency + content_hash; structured errors with remediation hints
  • AC 5 — missing lockfile when plugins declared exits 2; absence is fine when no plugins declared
  • AC 6 — unreachable source emits structured error; lockfile NOT partially written
  • AC 7 — `tests/test-plugin-resolver.sh` covers all 11 cases via local-filesystem git fixtures (no network)

Test plan

  • Image rebuilds cleanly
  • `tests/test-plugin-resolver.sh` — 11/11
  • `tests/test-plugin-loader.sh` — 11/11 (regression-safe with new `_plugins-verify` prereq)
  • `tests/smoke-rails.sh` — 4/4 (regression-safe)
  • `make _check` on dev-toolchain itself — pass
  • CI green on this PR

Implementation note

git 2.38+ blocks the `file://` protocol from non-interactive contexts (CVE-2022-39253 mitigation). Resolver passes `-c protocol.file.allow=always` to `ls-remote`/`fetch`/`remote add`. Production plugins use `https://` or `ssh://` and aren't affected; only the local-fixture test harness exercises this code path.

Out of scope (next stories)

  • Story 13.4 — extended-image build pipeline (`Dockerfile.devrail` generation)
  • Story 13.5 — execution loop (runs plugin commands during `_lint`/etc.)
  • Story 13.6 — v1.11.0 release with all of the above

🤖 Generated with Claude Code

Implements `make plugins-update` and `_plugins-verify` for the v1.10.x
plugin architecture. Builds on Story 13.2's loader + review fixes.

Components:

- scripts/plugin-resolver.sh — for each plugin in `.devrail.yml`,
  resolve `rev:` to an immutable SHA via `git ls-remote`, fetch the
  tree to the rev-aware cache path Story 13.2's loader reads, compute
  deterministic content_hash, and write `.devrail.lock` atomically.
  Branch refs are rejected; SHA passthrough for 40-char hex; tag-to-SHA
  via ls-remote (peeled form preferred for annotated tags). Idempotent.
- scripts/plugin-lockfile-verify.sh — fast verification used as
  `_plugins-load` prereq on every `make check`. Detects rev mismatch,
  missing lockfile, missing cached tree, and tag-rebase tampering.
  No-op when no plugins declared.
- Makefile — new `plugins-update` public + `_plugins-update` and
  `_plugins-verify` internal targets. `_plugins-load` prereq updated:
  _check-config → _plugins-verify → _plugins-load.
- tests/fixtures/plugin-repos/elixir-v1/ + elixir-v1-tampered/ —
  fixture trees; harness initialises git per case.
- tests/test-plugin-resolver.sh — 11-case smoke covering: SHA
  passthrough, tag→SHA, branch rejection, lockfile determinism,
  idempotent fetch, lockfile mismatch, tampering detection, missing
  lockfile, no-plugins regression, unreachable source, atomic-lockfile.
- tests/test-plugin-loader.sh — integration cases now write a matching
  .devrail.lock via a write_matching_lockfile helper.

CI: new "Plugin resolver smoke test" step in ci.yml.
Docs: CHANGELOG [Unreleased] Added entry; STABILITY.md row extended.

Test results (local against freshly built image):
- tests/test-plugin-resolver.sh — 11/11 pass
- tests/test-plugin-loader.sh — 11/11 pass (regression-safe)
- tests/smoke-rails.sh — 4/4 pass (regression-safe)
- make _check on dev-toolchain itself — pass

Implementation note: file:// URLs in tests need
`-c protocol.file.allow=always` because git 2.38+ blocks the file
protocol from non-interactive contexts. Production plugins use
https/ssh; only the local-fixture test harness needs the override.

Scope boundary: stops at "lockfile written and verified". Build
pipeline (13.4) and execution loop (13.5) are out of scope.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@matthew-on-git matthew-on-git merged commit 7705db8 into main May 3, 2026
3 checks passed
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