Repository navigation
feat(nodejs): assert the pnpm pin <-> allowBuilds key-form coupling (#5) - #6
Conversation
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)
Review — BLOCKHead SHA: Change class: config — candy Blocks
Verdict: BLOCK ollama-cloud/deepseek-v4.1-flash — action-review. |
…where
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)
Review — PASSHead SHA: Security screen: PASS — single candy-config file; no scope mismatch (the Checklist: All applicable rules PASS or NA. The prior head's sole defect is cured: the new Comment intake: 6047243077 (@github-actions[bot], head Tool-limited (rule 4): the body's assertion that charly exports Verdict: PASS ollama-cloud/deepseek-v4.1-flash — action-review. |
Summary
opencharly/layer-nodejs#5. The pinnedPNPM_VERSIONand a consumer'sallowBuildsKEY FORMare a coupled pair, and the coupling was invisible from this repo: the pin lived here, the
key form lived in the consumer (
pod-dsh), and nothing asserted they agree. A wrong pairhard-fails the consumer's
dsh-webprofile install at image build time.The rule is now declared beside the pin and asserted by the candy's own check, which is this
repo's functional evidence.
PNPM_ALLOWBUILDS_GIT_KEY_FORM,PNPM_ALLOWBUILDS_PROBED_BAREandPNPM_ALLOWBUILDS_PROBED_URLjoinPNPM_VERSIONinvar:, with the measured matrix in thecomment.
check:step asserts the pin, the form and the probes agree. A bump across the bandnow goes RED on this repo instead of hard-failing a consumer's build. It accepts only the
two versions that were MEASURED: any other pin fails loudly and asks the author to re-measure
and record — the band boundary is deliberately not extrapolated from two endpoints.
nodejs-skill:body mirrors the rule, because the skill entity is the corpus projectionsource (this repo's own rule: a behaviour change not mirrored in the skill leaves the corpus
stale).
Declared values, quoted from
var:in this head:Correction to the first revision of this PR (the previous head
bd28408). That revisionshipped the check with JavaScript template literals (
${version},${form},${want}) insideits
command:, and its comment claimed "No${...}anywhere". Both were wrong. A check step'scommand goes through charly's variable substitution, which reads
${X}as a charly variable andSKIPS the step when it cannot resolve one;
version/form/wantare not declared charlyvars, so the step would have been a silent no-op — disabling the very assertion this change
adds, which is the exact class it exists to prevent. The reviewer caught it. The fix is in the
new head: the messages are built by string concatenation, and the command now contains no
$character at all, so the substitution path has no token to act on and the step cannot be skipped.
The comment states the rule and the grep that proves it.
How tested
This repo ships no image and no bed (its
AGENTS.md: "this repo has no per-repo candy gateand ships only
.github/workflows/tag-on-merge.yml", and "the candy'splan:check:steps arethe functional evidence"), so the owning evidence is the entity validating plus the changed check
executing. Both, on this head
9ae1184bc59a9216ba9cd47b093e1db153f06c73.charlyhere is theworktree-local build
2026.280.1725in the sibling session worktree:The check is extracted from the committed entity and executed with exactly the
var:valuesthis head declares, so the output comes from the shipped artifact rather than a transcription:
The
grep -c '$'returning 0 is the point of the correction above: the shipped commandcarries no substitution token, so the
${...}-skips-the-step failure mode is eliminated byconstruction rather than argued.
grepexits 1 on zero matches, which is expected here.The check is falsifiable: it was executed twice more with one drift each, and each fails with
its own message and exit 1:
That covers both polarities of the pair (the pin moving and the form moving) and both shapes of
drift (an unmeasured pin, and a measured pin paired with the wrong form).
What did NOT run, and the owning follow-on. Proving the pair end to end needs a CONSUMER
build (the
dsh-webprofile install, e.g. thecheck-dsh-podbed inpod-dsh), which cannot bedriven from here: this repo ships no image for
charly check box, and the consumer pin can onlyadvance to a merged ref (Policy B). The step was therefore executed as the runner executes it
— the whole
command:block, verbatim, underbashwith thevar:values exported as ENV — butNOT through a container build of a consuming image, and I am not claiming otherwise. The follow-on
is the standard producer sequence: this PR merges and
tag-on-mergemints the CalVer tag, then themarketplace corpus is regenerated from the new tag (
layer-nodejsrefs bump) and the umbrella'smarketplacegitlink advances. Until then the projected corpus carries the OLDER, still-trueskill body — that is the designed behaviour of the corpus, not a divergence.
Rulebook compliance
/charly-coder:nodejs(the owning skill) and/charly-image:layer(thecandy-authoring reference) loaded before any entity or plan-step edit, as this repo's
AGENTS.mdmandates;/charly-internals:git-workflowand/charly-internals:strict-policyloaded for the landing.
divergence that changed the disposition of this issue, so it is corrected on the thread rather
than silently: the issue's premise was true when filed (2026-10-07T13:35Z) against
pod-dsh#24'stree, and
pod-dsh#28(merged 20:25Z, tagv2026.280.2025) then vendored the four git-hostedplugins as prebuilt
file:tarballs, so the profile's ownpnpm-workspace.yamlnow records thecoupling as "moot here now that no git dep remains". The SPECIFIC pod-dsh harm is therefore gone;
the general rule is not, and the producer side carried none of it. (b) The first revision of this
PR shipped the check with
${...}template literals, which would have made it a silent no-op,while its comment claimed otherwise — the correction in
## Summaryrecords it and the new headfixes it. No warnings:
charly box validatereports0 warnings, 0 errors.moves in the same commit, and there is no
TODO, no shim and no follow-up PR inside this repo.The consumer leg that remains is a MERGE-SEQUENCED pin bump, which Policy B forbids doing any
earlier — it cannot be part of this landing by construction, and it is named above rather than
parked.
check:step owns the assertion; the declared constants live once,in
var:, and are read by the step rather than restated. The rule is written once in the candyand mirrored once in the skill body, which is this repo's required projection pair, not a copy.
the declared values; every drift has a named failure. The
var:values are read throughprocess.env[...](exported as ENV before the step) and the messages are built by concatenation,so the shipped
command:contains no$character at all — the documented${...}check-step substitution trap, which silently SKIPS the step and trades a red check for an absent
one, has nothing to act on. That is proven by
grep -c '$'-> 0 above, not asserted.into the skill, and the rule stated is exactly the one the step enforces.
assertion, and the claim-keyed sweep for the retired wording is not applicable because no
previous claim is contradicted; the skill section is extended in place rather than duplicated.
git status --shortclean after both commits; commits usedgit commit -F <file>throughout, never--no-verify; no force-push, no history rewrite, andthe fix commit is append-only (the branch was never amended after the push).
own declared values and produced its own failure message on two independent drifts, each with
exit 1, plus the zero-
$proof that the step cannot be skipped.charly box validateand the check ran against the real entity and thereal
node; no mock, stub or fake of any boundary. The one thing not driven is the consumerprofile install, and it is named as NOT RUN above rather than simulated.
the committed
charly.yml(via a YAML parse), not a copy of its text in the PR.charlyused forbox validateis the worktree-local build./charly/bin/charly (2026.280.1725)from the sibling session worktree, not a shared install.candy's
charly.yml(avar:block, onecheck:step, and the skill body), with no image, noservice, no pin and no gitlink in this repo. The owning evidence for a candy change is its
check:steps, and the changed one ran live. The fresh-rebuild consumer proof is themerge-sequenced follow-on named above.
Change classification
Non-
.mdconfig change class:git diff --stat $(git merge-base origin/main HEAD)..HEADlistscharly.ymlonly (1 file, 83 insertions, 0 deletions), sodocumentation reviewedis forbidden.R10 gate: this repo has no per-repo gate; the owning evidence is the entity validating plus the
changed
check:step executed live — both pasted above. Attribution tier: analysed on a livesystem — the changed runtime path ran live with retained output, and the full consumer gate did
not pass (it cannot run from this repo).
Relates to #5.
Assisted-by: DeepSeek Harness deepseek-v4.1-flash (analysed on a live system)