From bd2840810db3204fac3626238bfd3e17a4a947bc Mon Sep 17 00:00:00 2001 From: Andreas Trawoeger Date: Wed, 7 Oct 2026 23:28:35 +0200 Subject: [PATCH 1/2] feat(nodejs): assert the pnpm pin <-> allowBuilds key-form coupling (#5) The allowBuilds KEY FORM a consumer must use for a GIT-SPECIFIER dependency is version-coupled: the bare name passes on pnpm 10.33.4 and fails on 10.34.6, the fully-qualified URL key does the reverse, so no single form works at both. A wrong pair hard-fails the consumer's profile install at IMAGE BUILD time. The coupling was invisible from this repo entirely: the pin lived here, the key form lived in the consumer, and nothing asserted they agree. - `PNPM_ALLOWBUILDS_GIT_KEY_FORM` plus the two MEASURED probe versions now sit beside `PNPM_VERSION`, with the measured matrix in the comment; - a `check:` step asserts the pin, the form and the probes agree, so a bump that forgets them is RED instead of a hard-failed consumer build. It accepts only the two probed versions: any other pin must RE-MEASURE the form and record it. The band boundary is deliberately NOT extrapolated from the two endpoints, because only those two were measured; - the skill body mirrors the rule, since it is the corpus projection source. 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, which is what this change makes assertable from the pin owner's side. Proving the pair end to end needs a CONSUMER build; this repo ships no image and no bed, so what ran here is `charly box validate` plus the check itself executed against its shipped values and four drift mutations (see the PR body). Assisted-by: DeepSeek Harness deepseek-v4.1-flash (analysed on a live system) --- charly.yml | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/charly.yml b/charly.yml index 804246d..dda5d77 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,34 @@ 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. + # No `${...}` anywhere: a check step's command goes through charly's variable + # substitution, which would read `${X}` as a charly variable and SKIP the step + # on an unresolved one. `var:` values are exported as ENV before this step, so + # plain `$NAME` reads them through the shell and always runs. + check: the pinned pnpm version's allowBuilds git-key form matches the recorded measurement + exit_status: 0 + command: | + node -e ' + const [version, form, bare, url] = ["PNPM_VERSION", "PNPM_ALLOWBUILDS_GIT_KEY_FORM", "PNPM_ALLOWBUILDS_PROBED_BARE", "PNPM_ALLOWBUILDS_PROBED_URL"].map((k) => process.env[k] ?? ""); + if (!version || !form || !bare || !url) { console.error("the pnpm pin band vars are not all exported"); process.exit(1); } + const measured = new Map([[bare, "bare"], [url, "url"]]); + const want = measured.get(version); + if (want === undefined) { + console.error(`pnpm ${version} 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 (form !== want) { + console.error(`pnpm ${version} requires the allowBuilds git-key form "${want}", but PNPM_ALLOWBUILDS_GIT_KEY_FORM is "${form}" (opencharly/layer-nodejs#5)`); + process.exit(1); + } + console.log(`pnpm ${version}: allowBuilds git-key form "${form}" matches the recorded measurement`); + ' nodejs-skill: skill: @@ -167,6 +218,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 From 9ae1184bc59a9216ba9cd47b093e1db153f06c73 Mon Sep 17 00:00:00 2001 From: Andreas Trawoeger Date: Wed, 7 Oct 2026 23:33:43 +0200 Subject: [PATCH 2/2] fix(nodejs): the coupling check must not contain a `${...}` token anywhere The reviewer is right, and this is the exact class the step exists to prevent. The new check's `command:` used JavaScript template literals (`${version}`, `${form}`, `${want}`). 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 -- and `version`, `form` and `want` are not declared charly vars. So the step would have been a SILENT NO-OP in the real runner, disabling the very assertion this change adds, while the comment above it claimed "No `${...}` anywhere". Both the defect and the false claim are corrected here. - the messages are now built by string CONCATENATION, so the command contains no `$` character at all: `grep -c '$'` over the shipped command returns 0, and with no token to resolve the substitution path cannot skip the step; - the missing-var guard now names WHICH var is missing instead of a generic message; - the comment states the rule and the grep that proves it, so the claim is checkable rather than asserted. Measured on the shipped entity: `charly box validate` -> 0 warnings, 0 errors; the step executes and passes at the pin, and both drift mutations (an unmeasured pin; the form moved with the pin) still exit 1 with their own message. Assisted-by: DeepSeek Harness deepseek-v4.1-flash (analysed on a live system) --- charly.yml | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/charly.yml b/charly.yml index dda5d77..0cad567 100644 --- a/charly.yml +++ b/charly.yml @@ -152,27 +152,36 @@ nodejs: # 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. - # No `${...}` anywhere: a check step's command goes through charly's variable - # substitution, which would read `${X}` as a charly variable and SKIP the step - # on an unresolved one. `var:` values are exported as ENV before this step, so - # plain `$NAME` reads them through the shell and always runs. + # + # 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 [version, form, bare, url] = ["PNPM_VERSION", "PNPM_ALLOWBUILDS_GIT_KEY_FORM", "PNPM_ALLOWBUILDS_PROBED_BARE", "PNPM_ALLOWBUILDS_PROBED_URL"].map((k) => process.env[k] ?? ""); - if (!version || !form || !bare || !url) { console.error("the pnpm pin band vars are not all exported"); process.exit(1); } - const measured = new Map([[bare, "bare"], [url, "url"]]); - const want = measured.get(version); + 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 ${version} 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).`); + 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 (form !== want) { - console.error(`pnpm ${version} requires the allowBuilds git-key form "${want}", but PNPM_ALLOWBUILDS_GIT_KEY_FORM is "${form}" (opencharly/layer-nodejs#5)`); + 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 ${version}: allowBuilds git-key form "${form}" matches the recorded measurement`); + console.log("pnpm " + values[0] + ": allowBuilds git-key form \"" + values[1] + "\" matches the recorded measurement"); ' nodejs-skill: