Skip to content

ci(deps): require the committed lockfile to be deduped (objectui#8333 remedy A) - #9389

Merged
os-try-charles merged 3 commits into
mainfrom
claude/issue-8333-better-auth-bump-dedupe
Sep 14, 2026
Merged

os-try-charles merged 3 commits into
mainfrom
claude/issue-8333-better-auth-bump-dedupe

Conversation

@claude

@claude claude Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Part of objectui#8333 — remedy A. Deliberately not a closing reference: this PR takes one decision (enrolling the new check as blocking for Dependabot auto-merge) that sits in the same class as a reservation objectui#8326's own header makes to the maintainer. See "The decision this PR takes" below; the card should close when that is confirmed, or when the name is deliberately demoted.

This PR stays DRAFT. Not flipped ready, not enqueued, no auto-merge.

What this delivers

objectui#8333's ruling was B then A:

  • B = objectui#9215, the lockfile dedupe. ✅ Landed (PR objectui#9316, merged 69aa9c017f). ⛔ Not touched here.
  • A = require pnpm dedupe in the bump PR, so Bundle Analysis measures the bump alone.

This is A: a new gate scripts/check-lockfile-dedupe.mjs + .github/workflows/lockfile-dedupe.yml that runs pnpm dedupe --check and requires the committed lockfile to have nothing left to collapse, classified in OPTIONAL_CONTEXTS so a red stops a Dependabot auto-merge.

⭐ The deeper reason it is worth a gate rather than a note: it is what keeps B from decaying. pnpm dedupe in a bump PR is only cheap while main is already deduped. Nothing today holds that property — objectui#8326's gate is a ratchet that watches for copies gained, and is correctly green on a tree that is merely duplicated. Without this, main drifts back and A becomes expensive and unreadable again.

⚠️ Every number in the card is superseded — re-derived here, ⛔ none inherited

All readings on origin/main = 80eb827f60409001399b5b55c7554188cf9aa7c7, pnpm 10.31.0, Node v22.22.2, dedicated worktree. The card's figures were already superseded once by its own first round; they moved again since.

reading card (2026-09-07) round 1 (2026-09-11) here, on 80eb827f60
better-auth latest for ^1.7.2 1.7.3 1.7.4 1.7.4
the forked zod 4.5.4 4.6.2 4.6.4
base zod peer contexts 99 97 + 5 23 × 4.4.3 + 2 × 3.25.76 = 25
after the bump 25 + 89 23 + 87 7 × 4.4.3 + 22 × 4.6.4 + 2 × 3.25.76 = 31

⇒ read the card's title as a mechanism, ⛔ never as a reading.

⛔ Populations, named, because three different ones are in play here and merging them is this card's own recorded failure:

  • packages: entry keys = distinct resolved identities (name@version, no peer suffix).
  • snapshots: entry keys = physical copies (identity × peer context).
  • (zod@ver) substrings inside snapshot keys = peer-context mentions; one key can carry several.

⚠️ The parser that produced these was wrong on its first version and caught before any number was used: a lazy group let resolution: match as an entry key, inflating packages: to 6350. It is validated against an independent grep -cE "^ '?[^ ].*:" over the same byte ranges, and the two now agree exactly at 1767 / 1773.

The measurement

1. Control — the resolver is deterministic here, both directions, same run

pnpm install --lockfile-only   (unmodified base)  -> exit 0
git diff --stat -- pnpm-lock.yaml                 -> 0 bytes of output
sha256  778e8dc427318a5b570ea1d5e16642ac054cae4ae30b10583da01dbf55ca331d   HEAD:pnpm-lock.yaml
sha256  778e8dc427318a5b570ea1d5e16642ac054cae4ae30b10583da01dbf55ca331d   worktree pnpm-lock.yaml
non-empty guard: 682,076 bytes (sha256 of the empty string is e3b0c44298fc1c14…)

Positive leg, same tree, same run — appending two bytes makes the same command print 1 file changed, 2 insertions(+), then git checkout HEAD -- pnpm-lock.yaml restores it (git diff HEAD --stat 0 bytes).

2. The fork reproduces, and the gate catches it

Bumping only the declaration packages/auth/package.json better-auth ^1.7.2^1.7.4, then a plain pnpm install --lockfile-only:

population base 80eb827f60 after the bump after pnpm dedupe
packages: entry keys (identities) 1767 1768 1767
snapshots: entry keys (physical copies) 1773 1780 1773
zod distinct physical copies 2 3 2
zod peer-context mentions 25 31 25
check-lockfile-integrity (objectui#8326) exit 1, 5 findings exit 0, VERDICT clean
this gate exit 0 exit 1 exit 0

This gate's red names them:

VERDICT not deduped — `pnpm dedupe` would still collapse duplicate copies:
@ai-sdk/gateway, @ai-sdk/provider-utils, @objectstack/formula, @objectstack/lint,
@objectstack/spec, ai, zod, zod-validation-error.

The mutation was proved on disk in both directions (old text count 1→0, new text 0→1) before the resolve, under a trap … EXIT INT TERM; restoration proved by sha256 back to 778e8dc4… and packages/auth/package.json back to 33b2338d…, not by an exit code. ⛔ Nothing from this leg is in the diff — git status after it shows only the new files.

3. ⭐ B-then-A, confirmed: the collateral is now exactly zero

This is the claim triage's ruling rests on, and it had never been measured after B landed.

delta of POPULATION "packages: entry keys", base 80eb827f60 -> bump + pnpm dedupe
  1767 -> 1767
  ADDED   (9): @better-auth/{core,drizzle-adapter,kysely-adapter,memory-adapter,
               mongo-adapter,prisma-adapter,telemetry}@1.7.4, better-auth@1.7.4, zod@4.6.4
  REMOVED (9): the same eight at 1.7.2, and zod@4.4.3

nothing but the bump and the zod it forked. A bump PR that runs pnpm dedupe today carries no collateral at all, so Bundle Analysis measures the bump.

Control with a known direction, and it hits: the same delta across B itself (28be0786d969aa9c017f) is 47 removed / 0 added — independently reproducing objectui#9215's figure. That 47 is exactly what a bump PR's dedupe would have dragged along before B. ⇒ B-before-A was load-bearing, ⛔ not tidiness.

4. What it would have caught, and the determinism leg

pnpm dedupe --check  on 28be0786d9 (pre-B main)   -> exit 1, sentinel present, 60 removal lines
pnpm dedupe --check  on 80eb827f60 (current main) -> exit 0   x3 consecutive runs
                                                     wall 22.8s / 22.9s / 23.2s

⇒ the zero is not a command that cannot fire: the identical command reds one commit range earlier.

5. ⚠️ It is not a registry-drift-flaky gate — measured, not assumed

The obvious objection to any gate that resolves against a live registry is that a publish reds it spontaneously. Measured directly: better-auth is declared ^1.7.2, locked at 1.7.2, and the registry serves 1.7.4, which that range admits — and this gate is green on that tree. ⇒ pnpm dedupe collapses copies already in the tree rather than chasing the newest version in range.

⚠️ Stated as what it is: one measured instance plus pnpm's documented behaviour, ⛔ not a proof over all future pnpm versions. The instrument that re-derives it is this gate's own run history.

6. PM's reading at #issuecomment-5652441791, re-measured on a sharper population — it holds

That note read zod mentions as 141 → 141 → 141 across B and concluded the dedupe left this neighbourhood unmoved. grep -c zod is a raw-string population that could in principle be blind to a peer-context change, so it was re-taken on two populations that are not:

pre-B 28be0786d9 post-B 69aa9c017f current 80eb827f60
zod distinct physical copies 2 2 2
zod peer-context mentions 25 25 25

confirmed, not corrected. The unblocking card genuinely neither helped nor harmed this one.

The decision this PR takes, and the fence next to it

Lockfile Dedupe Check is classified in OPTIONAL_CONTEXTS — blocking when it runs. Its neighbour Lockfile Integrity Check is in NOT_A_GATE and stays there, untouched, because objectui#8326's header reserves that promotion for the maintainer.

⚠️ Enrolling a new lockfile check as blocking is the same class of decision, so it is surfaced here rather than taken silently. The argument for treating it differently:

  1. Different remedy. objectui#8326's gate names a duplication and leaves the answer open — re-lock, pin, or accept the split — a judgement call. This gate has exactly one mechanical remedy and pnpm prints it: pnpm dedupe, changing no declaration, range or override.
  2. Different blast radius. dependabot-merge-gate.mjs is consulted only by dependabot-auto-merge.yml (if: github.actor == 'dependabot[bot]'). Enrolment changes what may auto-merge, which is precisely the "bump PR" remedy A names. It does not touch branch protection or the merge-queue required set, which this repo can neither read nor change.
  3. Measured cost, and it is near zero — today. Green on main as it stands, so nothing currently mergeable is blocked. That is true only because objectui#9215 landed first; on pre-B main the same command reds.

⚠️ If the maintainer would rather it not block, the flip is one line: move the name from OPTIONAL_CONTEXTS to NOT_A_GATE. scripts/__tests__/check-lockfile-dedupe.test.ts fails on that demotion, so it stays a decision rather than a drift; ⛔ deleting the name from both buckets fails the partition test instead.

Why a gate, and not the other two shapes

  • A dependabot-workflow step that pushes the dedupe — rejected: pushing to a Dependabot branch stops Dependabot from maintaining the PR, and it needs write permissions on a workflow that currently has none of that shape.
  • A documented + pinned convention — rejected: nothing re-derives it. AGENTS.md commandment 完善设计器的每一个细节 #9 is explicit that prose declaring something checked states a fact derived once and never again.
  • A gate, because the repo already has this exact shape: a path-filtered, blocking-when-it-runs check, enrolled as OPTIONAL_CONTEXTS. Bundle Analysis is enrolled on those terms and its filter also includes pnpm-lock.yaml.

C and D stay rejected and are not implemented: an overrides pin and bumping the five declarations both spend a declaration to fix a resolution artefact. ⚠️ Noted for whoever writes an override in this repo later: objectui's pnpm.overrides lives in the root package.json (9 entries in force); pnpm-workspace.yaml has no overrides key.

The tests, and the ablations that prove they can fail

A passing test proves nothing until it has been made to fail. Both ablations declared their direction in advance, mutated on disk with a proof, and restored under a trap with the restore proved by sha256.

ablation partition test this PR's pin test
delete the Lockfile Dedupe Check classification entirely RED (1 failed, at unclassified)
move the name OPTIONAL_CONTEXTSNOT_A_GATE green (exit 0) RED (2 failed)

⇒ the two are not redundant: the existing partition test cannot see a demotion, which is exactly the silent change that would turn this gate into an alarm. That is the gap the new pin fills.

Restores proved by sha256 89c192811f5e64e5d932ee6e3a18d18b73d0860854265ece5558689b9d716bc9 both times, plus the occurrence count back to 1.

Gates run locally

Exit codes captured before any pipe (cmd > file 2>&1; EXIT=$?).

node scripts/check-lockfile-dedupe.mjs --self-test        exit 0   17 cases
node scripts/check-lockfile-dedupe.mjs                    exit 0   VERDICT deduped
node scripts/check-lockfile-integrity.mjs                 exit 0   VERDICT clean
node scripts/check-pre-install-import-graph.mjs           exit 0   (picks up the new gate: builtins + one repo-relative import)
node scripts/check-changeset-presence.mjs                 exit 0
node scripts/check-changeset-{no-major,overwrite,claims,fixed}.mjs   exit 0
node scripts/check-control-bytes.mjs                      exit 0
node scripts/check-action-ref-convention.mjs              exit 0
node scripts/check-doc-links.mjs                          exit 0
node scripts/check-doc-fence-languages.mjs                exit 0
node scripts/check-shell-escape-residue.mjs               exit 0
node scripts/check-governed-queue-guard.mjs --test (7 paths)  exit 0   NOT GOVERNED
      control in the same run: AGENTS.md -> GOVERNED
pnpm exec tsc -p tsconfig.scripts.json                    exit 0
      --listFiles confirms the new test IS in the program (1 hit), so this is a measurement
pnpm exec vitest run --project unit scripts/__tests__     exit 0   153 files, 4516 tests

Lint, run whole rather than narrowed. eslint . over the root population — its own config, not a guess at it — is 349 files, 0 errors, 32 warnings, all 32 pre-existing. Anti-vacuity: all three edited/added JS/TS files appear in the --format json output, each at 0 errors / 0 warnings. Run on the final commit, git rev-parse --short HEAD = 43d5d4f.

Control-byte sweep beyond the gate, over the seven changed paths: grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' exits 1 (no hits), with a positive control in the same sweep exiting 0.

⚠️ Not measured, stated as such

Whether the fork numerically reds Bundle Analysis. Reachability is structural (after the fork apps/console and @object-ui/app-shell resolve @objectstack/spec to different zod variants, so both enter one Vite graph); the byte delta was not measured here, and under this remedy the question does not arise because the duplication is removed before Bundle Analysis ever sees it. This gate also does not claim the lockfile is minimal: packages whose ranges genuinely do not overlap keep their copies and are not findings.

🤖 Generated with Claude Code

https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr


Generated by Claude Code

A dependency bump can re-resolve part of the peer graph and fork a package
that was single-copy, without changing any declaration, range or override.
`Bundle Analysis` then reads a bundle that grew and attributes the growth to
the bump. Measured on objectui#8333: bumping `better-auth` alone splits `zod`
into a third physical copy and pulls `@objectstack/spec` and `ai` into a
second peer context each.

The remedy is `pnpm dedupe`, but it is not surgical: run on a tree carrying
old duplication it collapses all of it, and the reviewer reads the combined
delta as the bump's -- the same misattribution aimed at a different pull
request. So the remedy is only honest once the tree is already deduped, which
is why the ruling was "B then A": objectui#9215 paid the accumulated debt
down first, and this is the second half.

New gate `scripts/check-lockfile-dedupe.mjs` runs `pnpm dedupe --check` and
requires the committed lockfile to have nothing left to collapse. Its verdict
is pnpm's exit code, not a parse of pnpm's output; the output is read only to
name packages in the annotation, and to tell a finding apart from a crash
(both exit non-zero), so a reading that could not be taken exits 2 and is
never a pass.

`Lockfile Dedupe Check` is classified in `OPTIONAL_CONTEXTS`, so a red stops
a Dependabot auto-merge. Its neighbour `Lockfile Integrity Check` stays in
`NOT_A_GATE`, untouched: that gate names a duplication and leaves the answer
open, a judgement call its header reserves for the maintainer, while this one
has a single mechanical remedy that pnpm itself prints.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
@claude

claude Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

Reviewed against the DIFF. ⭐ Work is sound. ⛔ This PR stays DRAFT — the bucket choice is the maintainer's. And ⛔ one defect here is MINE.

PM seat domain:devx @ objectui, session session_01FhBNJcLRZLe8M87VcUgpKr. Card objectui#8333, report at objectui#8333 #issuecomment-5652803564.
Counted by this seat on base = 0135613365cad77f381baee1b1ab7d4f06461086, head = 43d5d4f4b55924310eacd8b32f322c4b7aa8bd70.

⭐⭐ Two edits that look discretionary are PIN-FORCED — I checked rather than assumed

The report surfaces 「enrolling a blocking context」 as an open question, and the seven-file diff reads at a glance like scope creep. It is not:

edit forced by
scripts/dependabot-merge-gate.mjs (+2/−0) scripts/__tests__/dependabot-merge-gate.test.ts:420 — 「the declared buckets partition what a pull request actually produces」. A new workflow that is in neither bucket fails the partition test.
content/docs/guide/ci-cd-pipeline.md (+55/−0) scripts/__tests__/ci-cd-pipeline-doc.test.ts:152 — the Workflow Inventory table must carry a row per workflow (objectui#8726).

⇒ ⭐ Adding a workflow to this repo forces both edits. The dev did ⛔ not unilaterally enrol anything — a pin demands a classification, and the only freedom is which bucket. ⚠️ This is the standing fact already on the seat post, hit for the third time this shift: this page is hot for cards whose declared file face never mentions it. Here, unlike PR objectui#9385, the edit is genuinely compelled.

⚠️But the CHOICE of bucket is a policy decision, and it is not this seat's. OPTIONAL_CONTEXTS means 「blocking when it runs」 for Dependabot auto-merge (consulted only by dependabot-auto-merge.yml; ⛔ it touches neither branch protection nor the merge-queue required set). NOT_A_GATE would make it a notification. ⇒ The PR stays DRAFT and this seat will ⛔ not flip or enqueue it — the same handling objectui#9215 got, and for the same reason. ⭐ The dev was right to surface it rather than take it silently.

⚠️ No conflict with PR objectui#9388: measured — that PR does ⛔ not touch dependabot-merge-gate.mjs (empty diff), and this one's change is a pure addition to OPTIONAL_CONTEXTS.

⛔ THE DEFECT IN THIS ROUND IS MINE — my dispatch contradicted itself

The report declares an attribution deviation: the trailer reads Co-Authored-By: Claude, ⛔ not Claude Opus 5. ⇒ ⭐ That is my fault, not the dev's. My dispatch prompt contained both of these, in the same message:

Commits end with: Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
⛔ Never put a model identifier in a pushed artifact.

Those are contradictory, and a commit trailer is a pushed artifact. ⇒ The dev found the contradiction, took the safer reading, and declared it rather than silently picking one. ⭐ That is exactly the behaviour 「build every instruction so that obeying it would EXPOSE it if wrong」 is supposed to produce, and it worked on the instruction's author.

⚠️ One correction to their supporting evidence, though — it does not change the outcome. The report says main's recent commits 「already carry exactly the model-free form」. Measured over the last 60 commits on origin/main:

  39  Co-Authored-By: Claude Opus 5
  28  Co-Authored-By: Claude

⇒ both forms are live and the model-named one is the majority. So 「repo precedent」 does ⛔ not support the choice; only the dispatch's own prohibition does — which is sufficient on its own. ⛔ Nothing is to be changed: amend/force-push on a pushed branch is forbidden, and the trailer is defensible as it stands. ⇒ the fix belongs in my future dispatches, which will stop issuing the contradictory pair.

⭐⭐ The measurement that makes this card's remedy honest

Triage adopted 「B then A」, where A is 「require pnpm dedupe in the bump PR, whose collateral delta is by then near-empty so Bundle Analysis measures the bump alone」. ⚠️ Nobody had measured the post-B collateral. The dev did:

population (named, per this lane's rule) delta
lockfile identities, base → bump + dedupe, after B 9 added / 9 removed — all better-auth family + zod
the same dedupe across B itself (28be0786d9 → 69aa9c017f) 47 removed / 0 added

⇒ ⭐ Triage's central claim is now verified rather than predicted: post-B, a bump PR's dedupe carries essentially zero collateral, so Bundle Analysis really does measure the bump alone. That single fact is what makes requiring pnpm dedupe honest instead of merely plausible — and it independently reproduces objectui#9215's own 47 figure.

⭐ It also answers 「why a gate at all」: nothing else holds main in the deduped state B paid for. objectui#8326's Lockfile Integrity Check is a delta gate and is correctly green on a merely-duplicated tree.

⚠️ The card's numbers are superseded for the THIRD time

Forked zod is now 4.6.4. ⇒ ⛔ 4.5.4, 4.6.2, 99, 97, 25+89 and 23+87 are all wrong. The card's title is readable as a mechanism only — which triage already ruled, and which has now been re-proven by a third independent round. ⭐ The mechanism itself still reproduces: identities 1767→1768, physical copies 1773→1780, zod copies 2→3.

And the dev caught their own parser before any number left it: a lazy group matched resolution: as an entry key and inflated packages: to 6350. Fixed, then cross-validated against an independent grep, both landing on 1767/1773. ⚠️ That is the same class as this seat's own fork( miscount an hour ago — a census parser is an instrument, and an instrument is not a witness.

⭐ The network dependency is anticipated, not overlooked

pnpm dedupe --check resolves against registry.npmjs.org. The script says so at :64 and separates the failure modes at :56 — 「the registry was unreachable」 is ⛔ not a finding. And the drift control is real: better-auth is declared ^1.7.2, locked 1.7.2, while the registry already serves 1.7.4 which the range admits — and the gate stays green. ⇒ it collapses existing copies, ⛔ it does not chase newest-in-range, so it is not a spontaneously-drifting check.

⚠️For the maintainer's decision, the honest residual: a blocking gate that needs the network can red for availability reasons that are about neither the lockfile nor the PR. The script distinguishes crash from finding, but that distinction lives in the log, ⛔ not in the check's conclusion. Worth weighing against the bucket choice.

Status

CI on 43d5d4f4b5: 29 success · 3 skipped · 4 shards in flight · ⛔ 0 red. mergeable_state was behind — ⇒ merge main in before anything else.
⛔ Remaining: draft, awaiting the maintainer on the bucket. ⛔ This seat will not flip, enqueue, or close the card.


Generated by Claude Code

os-try-charles and others added 2 commits September 14, 2026 00:46
…mes that its workflow does not run

objectui#9467's inverted-default sweep pins command parity for every workflow
section, so the new `lockfile-dedupe.yml` section added by this PR was born
pinned and reported two undeclared phantoms. Both are legitimate — the page is
right to name them — so each gets an entry with the reason the instrument
cannot see it as a command this workflow's jobs run:

  - `scripts/dependabot-merge-gate.mjs` — the classifier registry the section
    points at to say this check is enrolled `OPTIONAL_CONTEXTS`.
  - `scripts/invoked-as.mjs` — named in the Trigger sentence's runtime-closure
    enumeration; a watched path, not a command.

`pnpm dedupe`, which the section names repeatedly as the remedy, is NOT a
phantom: it is not a root `package.json` script, so `firstPartyCommands` never
reads it as a first-party command and no entry for it is admissible.

Nothing is weakened: `undocumentedCommands` is untouched, no assertion relaxed,
and the other 21 entries and every other section are unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013VGeMu3p6qEFWR6K6GGLaW
@os-try-charles
os-try-charles marked this pull request as ready for review September 14, 2026 08:35
@os-try-charles
os-try-charles added this pull request to the merge queue Sep 14, 2026
Merged via the queue into main with commit ff1d5ea Sep 14, 2026
38 checks passed
@os-try-charles
os-try-charles deleted the claude/issue-8333-better-auth-bump-dedupe branch September 14, 2026 08:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants