diff --git a/charly.yml b/charly.yml index 804246d..0cad567 100644 --- a/charly.yml +++ b/charly.yml @@ -58,6 +58,29 @@ nodejs: components: main var: PNPM_VERSION: "10.33.4" + # The allowBuilds KEY FORM a consumer's pnpm-workspace.yaml must use for a + # GIT-SPECIFIER dependency is VERSION-COUPLED, and the coupling is invisible: + # the two forms fail on each other's pin, so there is no form that works at + # both. MEASURED in the real build image (Fedora 43, node 22.22.2) against + # pod-dsh's dsh-web profile — opencharly/layer-nodejs#5: + # + # allowBuilds key form pnpm 10.33.4 pnpm 10.34.6 + # bare name "@perrylink/dsh-github": true PASS ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED + # URL key "@perrylink/dsh-github@https://codeload.github.com/...": true + # ERR_PNPM_INVALID_VERSION_UNION PASS + # + # So a pin bump across that band MUST re-measure the form and move BOTH + # constants below in the SAME change; the `check:` step beneath makes + # forgetting it RED instead of a hard-failed consumer profile install. The + # band boundary is deliberately NOT extrapolated from the two probed + # endpoints (only those two were measured) — a new pin must re-measure and + # record, which is the whole point. Consumer status: pod-dsh vendored its + # git-hosted plugins as prebuilt `file:` tarballs (pod-dsh#28, merged + # 2026-10-07), so no git dep remains THERE today — the rule still binds any + # consumer that pins one. + PNPM_ALLOWBUILDS_GIT_KEY_FORM: bare + PNPM_ALLOWBUILDS_PROBED_BARE: "10.33.4" + PNPM_ALLOWBUILDS_PROBED_URL: "10.34.6" plan: - run: | command=case "${BUILD_ARCH}" in @@ -123,6 +146,43 @@ nodejs: package: package: nodejs installed: true + - # The pin <-> allowBuilds-key-form coupling, ASSERTED rather than remembered + # (opencharly/layer-nodejs#5). A pin bump across the measured band silently + # hard-fails a consumer's profile install at image build time; this step makes + # that a RED check on the bump instead. It asserts only what was MEASURED (the + # two probed versions), never an extrapolated boundary: any other pin fails + # loudly and asks the author to re-measure and record the form here. + # + # ZERO `${...}` and ZERO `$` ANYWHERE in this command, deliberately: a check + # step's command goes through charly's variable substitution, which reads + # `${X}` as a charly variable and SKIPS the whole step when it cannot resolve + # one — trading a red check for a silently absent one, which is exactly the + # silent-no-op class this assertion exists to prevent. `var:` values are + # exported as ENV before this step, so the script reads them with + # `process.env[...]` and the messages are built by CONCATENATION, never by a + # template literal. The grep that proves it: `grep -c '\$'` over this command + # returns 0, so substitution has no token to act on and the step cannot be + # skipped. + check: the pinned pnpm version's allowBuilds git-key form matches the recorded measurement + exit_status: 0 + command: | + node -e ' + const keys = ["PNPM_VERSION", "PNPM_ALLOWBUILDS_GIT_KEY_FORM", "PNPM_ALLOWBUILDS_PROBED_BARE", "PNPM_ALLOWBUILDS_PROBED_URL"]; + const values = keys.map((k) => process.env[k] || ""); + const missing = keys.filter((k) => !process.env[k]); + if (missing.length > 0) { console.error("these pnpm pin band vars are not exported: " + missing.join(", ")); process.exit(1); } + const measured = new Map([[values[2], "bare"], [values[3], "url"]]); + const want = measured.get(values[0]); + if (want === undefined) { + console.error("pnpm " + values[0] + " is UNMEASURED for the allowBuilds git-key form. Bump only with a measurement: run a consumer profile that installs a git-specifier dep with a build script and record which form passes, then move PNPM_ALLOWBUILDS_GIT_KEY_FORM and the PROBED_* pair together (opencharly/layer-nodejs#5)."); + process.exit(1); + } + if (values[1] !== want) { + console.error("pnpm " + values[0] + " requires the allowBuilds git-key form \"" + want + "\", but PNPM_ALLOWBUILDS_GIT_KEY_FORM is \"" + values[1] + "\" (opencharly/layer-nodejs#5)"); + process.exit(1); + } + console.log("pnpm " + values[0] + ": allowBuilds git-key form \"" + values[1] + "\" matches the recorded measurement"); + ' nodejs-skill: skill: @@ -167,6 +227,29 @@ nodejs-skill: binary on `/usr/local/bin` is on the default system PATH for every user (incl. root — Immich runs its pnpm build as root). Immich and other pnpm consumers use this pnpm to drive their build. + + ### The pin and a consumer's `allowBuilds` KEY FORM are a COUPLED PAIR + + pnpm >= 10 blocks a dependency's build/install scripts unless it is allowlisted, + and for a **git-specifier** dependency the KEY FORM of that allowlist entry is + version-coupled: the two forms fail on each other's pin, so no single form works + at both. MEASURED in the real build image (Fedora 43, node 22.22.2) against + pod-dsh's `dsh-web` profile — `opencharly/layer-nodejs#5`: + + | `allowBuilds` key form | pnpm `10.33.4` | pnpm `10.34.6` | + |---|---|---| + | bare name — `"@perrylink/dsh-github": true` | PASS | `ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED` | + | fully-qualified URL key — `"@perrylink/dsh-github@https://codeload.github.com///.git": true` | `ERR_PNPM_INVALID_VERSION_UNION` | PASS | + + A wrong pair hard-fails the consumer's profile install at IMAGE BUILD time. So + `PNPM_VERSION`, `PNPM_ALLOWBUILDS_GIT_KEY_FORM` and the two PROBED_* constants move + together, and the candy's own + `check: the pinned pnpm version's allowBuilds git-key form matches the recorded measurement` + makes a bump that forgets them RED instead of a hard-failed consumer build. Only the + two probed versions are accepted: a bump to any other pin must RE-MEASURE the form and + record it. (pod-dsh vendored its git-hosted plugins as prebuilt `file:` tarballs in + pod-dsh#28, so no git dep remains there today — the rule still binds any consumer + that pins one.) ## Usage