Uh oh!
There was an error while loading. Please reload this page.
Hold vendor version stamps to a shape that survives the pin moving, instead of sweeping them a fifth time - #13979
Conversation
📓 Docs Drift CheckThis PR changes 4 package(s): 6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 43 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 70f4ef316d73529bf9e805dbeabd3c7c948b845c && git checkout 70f4ef316d73529bf9e805dbeabd3c7c948b845c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin eb649cb8bc3e17ac0302384eff90067f0f51b838 b4ec970c875b0e2d22ad6d29192a79c1a44a31b1 && git checkout -B drift-repro eb649cb8bc3e17ac0302384eff90067f0f51b838 && git merge --no-ff b4ec970c875b0e2d22ad6d29192a79c1a44a31b1
node scripts/docs-audit/affected-docs.mjs --json eb649cb8bc3e17ac0302384eff90067f0f51b838
|
ACCEPT — ⛔ My mechanism hypothesis would have been a defect factory, and the report proves it with countsI wrote Zone 2 B as "something that makes the next drift detectable — a gate deriving the installed version and checking stamps against it."A pin-equality gate cannot be built honestly here:
⇒ My hypothesis, implemented as stated, produces a false attestation 130 times and breaks a neighbouring gate. ⛔ It was not a near-miss. ⭐⭐ What it built instead — and the sentence that carries the whole design
The gate holds PHRASING, ⛔ not version equality. It reds on exactly that shape and nothing else; the 130-strong drift is measured and reported, never enforced. ⭐ The consequence is the class fix the card demanded: an anchored stamp stays green at every future bump, so the per-bump population falls from "every stamp naming the old version" — unbounded, and observed growing 29 → 95 across four hand sweeps — to "stamps written as live readings", held at zero. ⇒ That answers "why will this class not reappear" structurally. ⛔ A fifth sweep would not have. Zone 1 rule 1 — held exactly, and the split is the proof⛔ No stamp was restamped without a measurement. The 18 live-stale sites went to 0 by two different routes:
And on the bucket I predicted but could not name: "the card's predicted 'may have changed at 1.7.2' bucket is real but NOT separable by reading — it needs re-running each measurement upstream. That is the 130; I did not guess at them." ⇒ ⭐ Exactly the instruction: ⛔ if you cannot tell which bucket, leave it — do not guess. Zone 2 D — my warning paid off, one of three
Three measurements inside the gate's own design worth keeping
⭐ The gate family caught the dev's own bug
The docs population — counted, ⛔ not touched, and the obvious fix measured as insufficient0 ⭐⭐ And the important measurement: adding Rulings on the two open questionsQ1 — does #13940 close here? ⇒ B, as recommended. The class is shut and that was the card's ask; but "unverified" was the card's own stated failure direction and it survives this PR by design in the 130. ⛔ Option C would re-commission the sweep this lane just ruled against. ⇒ Card closes on this PR; the residue is filed as its own card, to be paid down per surface as each is next touched, ⛔ never as a batch. Q2 — docs its own card? ⇒ A, as recommended, and the measurement above is why: C is not merely weaker, it is measurably insufficient, and B understates a live customer-facing attestation. Filed, excluding Gates37 derived, 37 run,
Governed-surface checkDiff: one new Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Part of #13940.
Part of, not a closing keyword — deliberately, and stated as the dispatch asked. Theproducer of this class is shut here; the card's 93-line population is not restamped, and
must not be. 130 stamps still name a version other than the resolved one, by design: they
are anchored historical facts, and rewriting them without re-measuring is the one outcome
worse than leaving them. Whether that closes the card is the PM's triage call, not this
PR's.
What the recurrence actually is
Four hand sweeps (29 stamps, +3, +2, now 93) with the population growing means the
sweep was trailing a producer rather than converging on a remainder. The producer is not
"stamps name an old version". A stamp naming an old version is frequently true:
That was true when written, is true now, and will be true after 1.9.0. Nothing rots.
The producer is the other shape — a sentence fusing a permanent fact with a live
reading of a value that moves:
the installedis a present-tense claim about this tree. When the pin moves, that sentenceis not stale, it is false. Every bump falsifies every live-reading stamp at once, and
only a person reading them can tell. That is why the remedy kept being a person.
This is the same defect PR #13962 repaired one level up, in a docblock that froze a reading
of
cases.length. Its remedy is this one: stop freezing a live value; anchor the fact towhen it was true.
Zone 2 A — the triage, before touching anything
The card's hypothesis that the 93 are "not one kind" holds. Measured by
--jsonon thisbranch (249 stamp sites,
better-authresolved to1.7.2frompnpm-lock.yaml):historicalunattributedbetter-sqlite3 13.0.3,@better-auth/utils 0.4.2, peer ranges)live-stalecurrentfixtureThe third bucket the card predicted — "may have changed at 1.7.2, needs a real check" — is
real but is not separable by reading. Distinguishing "still true on 1.7.2" from "changed
underneath" requires re-running each measurement against the vendor. That is the 130, and
this PR deliberately does not guess at them.
What was done per bucket, and why each restamp is honest
18 live-stale sites repaired. 16 by scoping, 2 by re-verification.
Scoping (16).
the installed better-auth 1.7.1becomesthe then-installed better-auth 1.7.1. This re-measures nothing and claims nothing new: it removes a false present-tenseassertion and leaves exactly the measurement that was actually made. Honest because the
sentence now says only what someone actually observed, and true for good.
Re-verification (2). These two were genuinely re-measured against the installed 1.7.2
today, so restamping them attests to a measurement that was really taken:
packages/cli/src/commands/init.ts— read off the installed manifest:@better-auth/scim@1.7.2peers@better-auth/utilsat exactly0.4.2andbetter-callat exactly
1.4.0, unchanged from 1.7.1. Restamped to1.7.2and dated.packages/plugins/plugin-auth/src/auth-schema-config.ts—SCIMOptionsin the installed@better-auth/scim@1.7.2dist/index.d.mtsdeclares six members (connections,authentication,managedConnections,identity,projection,compatibility) and noschema/modelName/fields. Enumerated with a positive control (the block reallywas captured: 18 lines, 6 members found). Restamped to
1.7.2and dated.Every other stamp keeps its version. No stamp was restamped without a measurement.
Zone 2 B — the mechanism, measured
The obvious gate was designed, measured against the real corpus, and rejected. "Hold
every version stamp equal to the resolved pin" cannot be built honestly here:
took;
payload inside
check-prerelease-pin-watch.mjswhose version is arbitrary input provingthe watcher notices a stable release. A pin-equality sweep corrupts another gate's test
input.
So the shipped gate holds phrasing, not version equality:
scoping the present tense. Today: 0.
surface without being handed a remedy that manufactures claims.
The consequence is the whole point: once a stamp is anchored it stays green at every
future bump. The per-bump population falls from "every stamp naming the old version"
(unbounded, 29 → 93) to "stamps written as live readings", which the gate holds at zero.
scripts/check-vendor-version-stamps.mjs— 5,590 files, no build, no install (the pinresolves statically from
pnpm-lock.yaml), 33 self-test assertions, wired intolint.yml.It matches prose and specifier spellings, across all five packages, which the card
required: #10188 recorded that a
better-auth@-only comment-vs-pin gate misses prose, andon this corpus the specifier spelling is the minority. Detection is co-occurrence in a
window (the
check-corpus-claim-drift.mjsdesign), sobetter-auth 1.7.1,`@better-auth/sso@1.7.1`,better-auth (1.7.1)and versions split across a line breakare all reached.
Three findings worth recording, each from measuring rather than assuming:
monotonically (198 at width 1, 249 at 4, 343 at 20) — a plateau was never going to exist.
The failing set is 0 at every width from 1 to 20: the verdict is window-insensitive,
because a live-reading claim and the version it names are always the same clause. The
header says so instead of implying a calibration that was not performed.
@better-auth/utils@0.4.2as abetter-authstamp (substring) andbetter-auth 1.7.1 behaves identically on 13.0.3 and on 12.11.1as three better-auth stamps. A package namenow claims the first following version only, must stand alone, and ranges (
^12.0.0) arenot stamps at all.
@better-auth/utilsis genuinely in this tree at both
0.4.2and0.5.0, so a stamp saying0.4.2is rightabout one copy. Ambiguity makes it unjudgeable — a true answer; picking the highest is a
false red.
Zone 2 D — are the gate-script stamps load-bearing?
One is, and it is the strongest argument against the pin-equality design.
check-prerelease-pin-watch.mjs:943— LOAD-BEARING.'better-auth': { versions: ['1.7.0', '1.7.1'] }is a synthetic registry fixture driving that gate's own--self-test.Not an attestation about this tree at all. Untouched.
check-route-envelope.mjs:795— thenote:string is parsed, butvendorWireNoteConformsonly asserts it containsvendor:,reader:andpartner:. Theversion substring is not load-bearing. Untouched (it is anchored prose).
check-cli-test-child-env.mjs:22— header prose anchored to e2e tests that spawn a realos serveunder vitest inheritTEST=true, which makes better-auth silently skip origin/CSRF validation regardless ofNODE_ENV#11267. Not load-bearing.Untouched.
check-prerelease-pin-watch.mjs:22— header narrative. Not load-bearing. Untouched.Re-derived counts
The card measured 95 stamps / 56 files on a pre-merge branch. On
origin/mainataee1fd9ecthe card's own command still reads 95. On this branch it reads 93 in 56files excluding this PR's new gate file — the two removed are the re-verified restamps.
Note the card's command now reads 129 including the gate file: 36 of those lines are the
gate's own documentation and fixtures. Stated so nobody reads the growth as new drift.
The
content/docs/**population — counted, not fixed, and not editedThe card's command scopes to
--include='*.ts' --include='*.mjs' --include='*.mts', so thecustomer-facing copy of this defect was never in its population. No
content/docs/**fileis touched by this PR. It was measured, with the hits printed rather than only counted:
Running this gate's own detector (imported — the entry guard makes that safe) over 405
.md/.mdxfiles undercontent/docs: 15 stamp sites — 4historical, 11unattributed, 0live-stale.Two lines in one file are worth citing, because they are the triage buckets standing side by
side in customer-facing copy rather than a taxonomy invented for this PR:
content/docs/permissions/authentication.mdx:853— "measured on 1.7.1, whereaddMemberbuilds its endpoint with no path". An attestation in exactly the sense this PR is
about, shipped to readers, now naming a version that is not installed.
content/docs/permissions/authentication.mdx:1217— "the stable1.7.0/1.7.1releases renamed it back to
accountId". Correctly frozen: a statement about whatthose releases did. Rewriting it to 1.7.2 would make it false.
And the measurement found a limit in this gate worth stating plainly, because it changes
where the mechanism belongs. Adding
content/docstoROOTSwould not be sufficient::853is seen but landsunattributed—better-authsits ~90 characters earlier, on theprevious line, past
CLAIM_GAP. Reported, never judged.:1217is not seen at all: no watched package name occurs within the window (measured:zero
better-authmentions in lines 1213-1221).Prose spreads a package name and its version much further apart than code does, so a docs
lane needs a wider claim gap or MDX-aware attribution — a separate piece of work, with its
own false-positive budget over 405 files. That is an argument about where the mechanism
belongs and how far it reaches, not about whether one is affordable.
⇒ Recommended as its own card, not folded in here.
content/docs/releases/**isrelease-owned and fenced unconditionally by AGENTS.md, so any such card must exclude it by
construction.
Verification
pnpm check:vendor-version-stamps— its own printed verdict:OK — 5590 file(s) scanned,249 version stamp(s); resolved 1.7.2; 130 name(s) a different version. Self-test:33 checks pass.grep counts, 0 injected / 1 removed), gate went red with exactly 1 finding; restored
via
git checkout HEAD --with an absolute path, restore proved byte-identical bygit hash-objectagainst the HEAD blob (e0df265d), andgit diff HEADclean.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstackat
b4ec970c8, all 37 run;comm -23derived-minus-run is empty. 33 exit 0. Four exitnon-zero as an explicit PREREQUISITE NOT MET —
check:i18n,check:i18n-coverage,check:type-check-debt,check:dual-build-cjs-loadseach print that they need a builtclosure and that nothing was measured; CI builds before running them. Recorded as NOT
MEASURED, not as passes.
pnpm check:ratchet-remedy-authorityrun explicitly (it cannot be path-derived): green,181 scripts swept. This gate offers no ratchet, ledger or baseline, so it needs noauthority marker and owes no
CONTROLentry.check:entry-guardcaught a real defect in the new gate — it exports its analysisfunctions and ran its top level on import. Now behind one
isEntrypointsite; importing itis inert (proved: import prints only the export list).
pnpm exec eslint --no-inline-configon the 16 changed lintable files: 0 errors, 0warnings, count read from
--format json(16 files). Repo-wide lint is CI's, and thisnarrowing is safe to declare:
eslint.config.mjs:328records noparserOptions.projectand no typed rules, so this diff cannot move the verdict on any untouched file.
packages/is a comment except oneledger
why:string, and both gates that parse that table (check:dispatcher-error-vocabulary,check:route-envelope) are green. eslint parsed all 16 edited files, so no syntax breakage.Changeset
None, plus
skip-changeset— precedent and the workflow's own prescription.lint.ymlcalls a PR that edits a CI-internal script "the textbook
skip-changesetcase — such a PRreleases nothing". Every
packages/change here is a comment; nothing publishes. The fourmost recent scripts-only gate commits on
maincarry zero changesets.Generated by Claude Code
Generated by Claude Code