fix(makefile): address Story 13.3 senior-developer review findings#35
Merged
matthew-on-git merged 1 commit intomainfrom May 3, 2026
Merged
fix(makefile): address Story 13.3 senior-developer review findings#35matthew-on-git merged 1 commit intomainfrom
matthew-on-git merged 1 commit intomainfrom
Conversation
Adversarial review of Story 13.3 surfaced 12 findings (1 HIGH, 5 MEDIUM, 6 LOW). All addressed in this branch. HIGH - H1: malformed `.devrail.yml` no longer silently treated as "no plugins declared". Both resolver and verifier now wrap yq parse via a `parse_plugin_count` helper that distinguishes "yq parse failure" (exit 2 with structured `config could not be parsed by yq` error event) from "no plugins entry" (silent success). MEDIUM - M1: lockfile verifier passes the .devrail.yml source URL via `strenv()` instead of string-interpolating it into the yq query. Defends against malicious/malformed source values breaking the query expression and failing open. - M2: slug collisions detected upfront. Two plugin sources with matching `basename` (e.g. github.com/foo/credo and gitlab.com/bar/credo) would both map to the same `<plugins-dir>/credo/<rev>/` cache path, silently overwriting each other. Resolver now tracks SLUG_TO_SOURCE during the loop and emits a structured `plugin slug collision` error on the second occurrence. - M3: fetch_to_cache now performs an atomic swap. Was `rm -rf target; mv fetch_dir target` (race window where target is absent). Now `mv target target.old; mv fetch_dir target; rm target.old` with rollback on the install-new mv. Concurrent readers see either the old tree or the new — never absent or half-populated. - M4: `compute_content_hash` and `derive_slug` extracted to lib/plugin-cache.sh. Was duplicated between resolver and verifier; future drift risk eliminated. - M5: resolver invokes plugin-validator.sh on the fetched manifest before computing content_hash. Authors hit manifest violations at `make plugins-update` time, not at every subsequent `make check`. LOW - L1: fetch_to_cache comment now reflects 4 args (not 3). - L2: `derive_slug` strips `.git` suffix so https://example.com/foo.git yields cache path `<plugins-dir>/foo/<rev>/`. - L3: lockfile entries written with double-quoted YAML scalars so source URLs containing colons, brackets, or yaml-reserved chars don't break parsing. - L4: smoke test now covers `_plugins-update` no-op when no plugins declared (companion to existing `_plugins-verify` no-plugins case). - L5: idempotent-fetch test now asserts `.devrail.sha` sentinel mtime is stable across re-runs, proving no re-clone happened (was just asserting the "plugin already cached" event). - L6: smoke test exercises a `.git`-suffixed source URL. Tests: - tests/test-plugin-resolver.sh — 16/16 pass (was 11). New cases: malformed YAML, slug collision, .git suffix, no-plugins update no-op, mtime-based idempotency. - 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: cache files are written as root inside docker (mktemp -d defaults to 0700). New tests that read inside plugins-cache use a sidecar `docker run` (with `:ro` mount) instead of host-side file checks, since the host user can't traverse 0700 directories. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
52506fd to
6b479ca
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adversarial review of Story 13.3 (PR #34, in flight) surfaced 12 findings: 1 HIGH, 5 MEDIUM, 6 LOW. All addressed here. Tests expanded from 11 to 16 cases.
This PR is based on
feat/13-3-plugin-resolver(PR #34), not directly onmain. Merge order: PR #34 first, then this PR.Findings + fixes
🔴 HIGH
.devrail.ymlsilently treated as "no plugins declared" —yq ... || echo 0swallowed parse errorsparse_plugin_counthelper that distinguishes "parse failure" (exit 2, structured error) from "no plugins entry" (silent success).🟡 MEDIUM
strenv()instead of string concatenation.basenamecollide on the same cache pathSLUG_TO_SOURCEduring the resolve loop; emitplugin slug collisionerror on the second occurrence.fetch_to_cachehadrm -rf target; mv fetch_dir targetrace windowcompute_content_hashduplicated between resolver and verifierlib/plugin-cache.shalong with newderive_slughelper.make checkplugin-validator.shon the fetched manifest before recording content_hash.🟢 LOW
fetch_to_cachecomment said 3 args, takes 4.git-suffixed URLs yielded ugly slugs (devrail-plugin-elixir.git)derive_slugstrips.gityaml_quotehelper_plugins-updateno-op with no plugins.devrail.shamtime stability.git-suffixed source URLTest results (local)
```
==> Case 1-16: all 16 plugin-resolver smoke checks pass (was 11)
==> tests/test-plugin-loader.sh: 11/11 (regression-safe)
==> tests/smoke-rails.sh: 4/4 (regression-safe)
==> make _check on dev-toolchain itself: pass
```
Behaviour change for consumers
No
plugins:in.devrail.yml: zero change. Loader/resolver/verifier all no-op (same as PR #34).Has
plugins::.git-suffixed source URLs now resolve to a cleaner cache path (cache layouts that depended on the.gitsuffix would break — none exist in the wild yet because v1.10.0 is unreleased).Recommended next step
Merge PR #34 first, then this PR. After both merge, cut
v1.10.2patch release.🤖 Generated with Claude Code