Repository navigation
Description
Filed while moving the better-auth family 1.7.1 to 1.7.2 for #13715. Out of that
card's scope, recorded rather than fixed there. Not assigned.
What
95 version-stamped attestations across 56 files still name better-auth 1.7.1
(or @better-auth/scim@1.7.1, @better-auth/oauth-provider@1.7.1) as the
version they were measured against. Once #13715 lands, the installed family is
1.7.2 and none of those 95 names a version that is installed any more.
Measured on the branch, excluding CHANGELOGs and the override file itself:
$ grep -rn '1\.7\.1' --include='*.ts' --include='*.mjs' --include='*.mts' . \
--exclude-dir=node_modules --exclude-dir=dist | grep -i better-auth | grep -v CHANGELOG | wc -l
95
Spread over five packages and three gate scripts, so it is not one carrier:
| location | stamps |
|---|---|
packages/plugins/plugin-auth/src |
55 |
packages/platform-objects/src |
14 |
packages/cli (src + test) |
12 |
packages/runtime/src/dispatcher-error-vocabulary.ts |
4 |
packages/create-objectstack/src |
2 |
scripts/check-prerelease-pin-watch.mjs, check-route-envelope.mjs, check-cli-test-child-env.mjs |
4 |
Typical shapes: "Bound from better-auth 1.7.1's own MySQL schema", "better-auth
1.7.1's BASE_ERROR_CODES member", "vendor: better-auth (1.7.1) — the body is
byte-identical to that release's own handler return", "better-auth 1.7.1 reads
TEST directly".
Why it is a finding and not cosmetics
Each of these is an attestation: it claims a behaviour was measured against a
named version, and that provenance is the only thing standing behind bounds,
error vocabularies and byte-identical envelope claims that nothing else derives.
When the named version is not the installed one, the claim is no longer
verifiable by reading it — the reader cannot tell a still-true stamp from one
that upstream changed under it.
This has now recurred four times
#10073 (29 stamps naming 1.7.0-rc.2 / 1.6.20 after the ^1.7.1 bump),
#10188 (three more outside that card's two-string scope), and #11362 (two more,
in platform-objects rather than plugin-auth) were each fixed by hand, and each
time the next card found more. #13715's 1.7.2 move is the fourth round, and the
population has grown from 29 to 95. #10188's own title records the shape of the
mechanical answer that was contemplated and not built: a better-auth@-only
comment-vs-pin gate would still miss stamps written as prose.
So the interesting question is probably not "re-stamp these 95" but "why does a
hand sweep keep being the remedy". A gate that holds every version stamp equal
to the resolved pin — matching prose and specifier spellings, across all five
packages, not just plugin-auth — would make the next family bump mechanical.
What was checked, so this is not read as broader than it is
The two stamped claims that #13715 actually depends on were re-measured against
1.7.2 and are unchanged: @better-auth/scim@1.7.2 still peers better-call at
an exact 1.4.0 and @better-auth/utils at an exact 0.4.2, and
better-auth@1.7.2 still carries the stale optional better-sqlite3@^12.0.0
peer. The scaffold peerDependencyRules pins in packages/cli/test/init.test.ts
therefore stay green. The other 90-odd stamps were not re-measured — that
is the work this records, and the reason a patch bump does not make them false
so much as unverified.
Severity
Low, and deliberately filed rather than triaged from here: severity judged at
filing time has been unreliable in both directions. No behaviour is wrong today.
Generated by Claude Code
Generated by Claude Code
Activity
claude commentedon Aug 31, 2026
Claim —
domain:devxexecution PM seat (#6023), sessionsession_01Pk26oZ12t5N1hwGW1m1MgC. Branchclaude/issue-13940-better-auth-stamp-drift.Generated by Claude Code
os-project-manager commentedon Aug 31, 2026
os-dev-report
{ "issue": 13940, "status": "done", "branch": "claude/issue-13940-better-auth-stamp-drift", "pr": "https://github.com/objectstack-ai/objectstack/pull/13979", "premise_still_valid": true, "summary": "The card's premise holds and is live: installed better-auth family is 1.7.2, and the card's own command still reads 95 stamps naming 1.7.1 on origin/main at aee1fd9ec. Delivered the MECHANISM, not the sweep, per triage. ⭐ THE PM'S MECHANISM HYPOTHESIS IS PARTLY FALSIFIED, with counts: a gate that 'holds every version stamp equal to the resolved pin' CANNOT be built honestly here. Measured — 130 of the 249 stamp sites are anchored historical facts ('[#11374] Bound from better-auth 1.7.1's own MySQL schema') that are TRUE and must never be restamped, and 3 more are synthetic FIXTURE data, including a self-test payload inside check-prerelease-pin-watch.mjs. Pin-equality would fabricate 130 measurements and corrupt another gate's test input. So the shipped gate holds PHRASING, not version equality. The real producer is a sentence fusing a permanent fact with a LIVE READING of a moving value: 'the installed better-auth 1.7.1' is not stale after a bump, it is FALSE. The gate reds on exactly that and on nothing else; drift (130) is measured and REPORTED, never enforced. Consequence: an anchored stamp stays green at every future bump, so the per-bump population falls from 'every stamp naming the old version' (unbounded, 29 -> 95) to 'stamps written as live readings', held at zero. Also repaired the 18 live-stale sites — 16 by scoping ('the then-installed X', which re-measures nothing) and 2 by REAL re-verification against installed 1.7.2 (scim@1.7.2 still peers @better-auth/utils at exactly 0.4.2 and better-call at exactly 1.4.0; SCIMOptions on 1.7.2 declares six members and no schema/modelName/fields, enumerated with a positive control). No stamp was restamped without a measurement. New gate: scripts/check-vendor-version-stamps.mjs, wired into lint.yml, no build and no install (pin resolves statically from pnpm-lock.yaml).", "triage_table": { "historical": "130 — names a non-resolved version but makes NO claim about this tree (carries a date, an issue ref, or is a plain fact about a named release). LEFT ALONE; permanently true. Example: platform-objects sys-session.object.ts:253 '[#11374] Bound from better-auth 1.7.1's own MySQL schema'.", "unattributed": "114 — a version the gate cannot attribute to a package it resolves (better-sqlite3 13.0.3, @better-auth/utils 0.4.2, peer RANGES like ^12.0.0). Counted, never judged.", "live-stale": "18 -> 0 — present-tense installedness claim naming a version that is not installed; FALSE today, not merely stale. All 18 repaired. Example: auth-schema-config.ts:956 'still true of the installed stable @better-auth/scim@1.7.1'.", "current": "2 — names the resolved 1.7.2 after genuine re-verification today.", "fixture": "3 — synthetic test input, not an attestation. DECLARED inline (vendor-stamp:fixture), not rewritten.", "not_separable": "The card's predicted third bucket ('may have changed at 1.7.2, needs a real check') is real but NOT separable by reading — telling 'still true on 1.7.2' from 'changed underneath' requires re-running each measurement against the vendor. That is the 130, and I deliberately did not guess at them." }, "mechanism_verdict": "FEASIBLE and affordable, but NOT in the shape hypothesised. Cost: one 5,590-file static text scan, no build, no install, 33 self-test assertions, ~700 lines including header. Detection is co-occurrence in a window (the check-corpus-claim-drift.mjs design), so it reaches prose AND specifier spellings across all five packages — required, because #10188 recorded that a better-auth@-only comment-vs-pin gate misses prose, and on this corpus the specifier spelling is the MINORITY. Three measured design findings: (1) the window sweep has NO plateau (198 sites at width 1, 249 at 4, 343 at 20) but the FAILING set is 0 at every width 1..20, so the width is window-insensitive and not a calibration — the header says so rather than implying one; (2) attribution is the half a naive detector gets wrong — a first cut read @better-auth/utils@0.4.2 as a better-auth stamp by substring and read 'better-auth 1.7.1 behaves identically on 13.0.3 and on 12.11.1' as three better-auth stamps; a name now claims the first following version only, must stand alone, and ranges are not stamps; (3) @better-auth/utils really is installed at BOTH 0.4.2 and 0.5.0, so it resolves to NOTHING rather than to a guess.", "gate_script_stamps_load_bearing": "YES — one of the three, and it is the strongest argument against pin-equality. check-prerelease-pin-watch.mjs:943 ('better-auth': { versions: ['1.7.0','1.7.1'] }) is a synthetic npm-registry FIXTURE driving that gate's own --self-test; its version is arbitrary input proving the watcher notices a stable release, not an attestation about this tree. UNTOUCHED. check-route-envelope.mjs:795 — the note: string IS parsed, but vendorWireNoteConforms only asserts it contains 'vendor:', 'reader:' and 'partner:', so the version substring is NOT load-bearing; untouched (anchored prose). check-cli-test-child-env.mjs:22 and check-prerelease-pin-watch.mjs:22 — header prose anchored to #11267 / #3002; not load-bearing; untouched.", "recounts": "Card's own command on origin/main at aee1fd9ec: 95 lines / 56 files — UNCHANGED from the card, so its figures had not moved. On this branch: 93 lines / 56 files excluding the new gate file (the 2 removed are the re-verified restamps). ⚠ Including the gate file the same command reads 129, because 36 of its lines are the gate's own documentation and fixtures — stated so nobody reads that as new drift. The gate's own census sees 249 stamp SITES (a superset: it also reaches stamps whose version and package name sit on different lines, which the card's same-line grep cannot see).", "tests": "All numbers from HEAD b4ec970c8 (final commit), and the union was run AFTER it. (1) pnpm check:vendor-version-stamps — its own printed verdict line: 'check:vendor-version-stamps: OK — 5590 file(s) scanned' / '249 version stamp(s); resolved 1.7.2; 130 name(s) a different version'; self-test prints '33 checks pass'. (2) ABLATION from the COMMITTED state, with a trap and absolute paths: reverted one repaired stamp in vendor-admin-refusal-envelope.ts; mutation PROVED on disk by grep counts (then-installed count 0 as expected, installed count 1 as expected) — not by an editor exit code; gate then exited 1 printing '1 live-reading stamp(s) name a version that is not installed'. Restore leg proved BYTE-IDENTICAL: git hash-object = e0df265ddf19b9b9b799f55af856fca02de107b1 = the HEAD blob, and git diff HEAD clean. No build/dist involved (pure static scan), so no dist preflight applies. (3) GATES: 37 derived by 'node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack' at b4ec970c8 (stderr confirms provenance: objectstack-ai/objectstack, this checkout); all 37 RUN; comm -23 derived-minus-run is EMPTY. 33 exit 0. Four exit non-zero as an explicit PREREQUISITE NOT MET printing 'nothing was measured' — check:i18n, check:i18n-coverage, check:type-check-debt, check:dual-build-cjs-loads all require a built closure; recorded as NOT MEASURED, never as passes; CI builds before them. (4) MANDATORY pnpm check:ratchet-remedy-authority run explicitly (it cannot be path-derived): green, '181 scripts swept ... Control corpus: 27 hand-classified scripts, set-equality audited both ways'. My gate offers NO ratchet/ledger/baseline, so it needs no authority marker and owes no CONTROL entry. (5) check:entry-guard CAUGHT A REAL DEFECT in my new gate — it exports its analysis functions and ran its top level on import; now behind ONE isEntrypoint site, and import proved inert (importing prints only the export list). (6) eslint --no-inline-config on the 16 changed lintable files: 0 errors, 0 warnings, file count 16 read from --format json. Repo-wide lint DECLARED-NARROWED and safe: eslint.config.mjs:328 records no parserOptions.project and no typed rules, so this diff cannot move any untouched file's verdict. (7) NO package test run, DECLARED: every changed line under packages/ is a comment except one ledger why: string, and both gates parsing that table (check:dispatcher-error-vocabulary, check:route-envelope) are green; eslint parsed all 16 edited files so there is no syntax breakage. All gate exit codes captured by redirect-then-capture, never through a pipe. Heavy runs went through scripts/pm/os-verify-lock.sh (slot issue-13940; VERDICT lines: held 278s then 267s, waited 0s then 88s).", "mcp_calls": "4 — issue_read(get), issue_read(get_comments), create_pull_request, add_issue_comment. The label write and both read-backs went over repo-scoped REST, which answered 200 on this seat (additive POST .../labels, then an authoritative read-back: ['size/l','skip-changeset'] — the size labeler's label survived).", "open_questions": [ { "question": "The 130 anchored stamps still name a non-resolved version. They are honest and the gate holds them green forever, but nobody has re-measured those behaviours against 1.7.2. Should that be a follow-up card, and does #13940 close on this PR?", "options": [ "A. Close #13940 on this PR and file nothing. The triage ruling was 'modify the class, not the instances', and the class is shut: no new live-reading stamp can land.", "B. Close #13940 and file a separate, lower-priority card to re-verify the 130 against 1.7.2, paid down as each surface is next touched rather than in one sweep.", "C. Keep #13940 open until the 130 are re-verified." ], "recommendation": "B. A is slightly too clean — 'unverified' was the card's own stated failure direction, and it survives this PR by design. C re-creates the sweep the lane just ruled against, and it would sit open indefinitely because re-verification is per-surface upstream work nobody can batch honestly. B records the residue without commissioning a fifth sweep. I did NOT file it myself: it is the deliberately-deferred half of THIS card rather than an independent finding, so filing it unilaterally risks duplicating #13940. Say the word and I will file it." } ], "out_of_scope_findings": [] }Generated by Claude Code
os-project-manager commentedon Aug 31, 2026
os-dev-report
Addendum to the report above — answering the PM note on the
content/docspopulation. Nothing else in that report changes; PR #13979 body now carries this section too.{ "issue": 13940, "status": "done", "addendum": "content/docs population — COUNTED, not fixed, not edited", "docs_population_counted": "YES — counted, with hits printed. 0 content/docs files are in this PR's diff (verified: git diff --name-only aee1fd9ec..HEAD matches nothing under content/). Method: imported this PR's own detector (the entry guard makes importing it inert) and ran it over 405 .md/.mdx files under content/docs. Result: 15 stamp sites — 4 historical, 11 unattributed, 0 live-stale. The naive same-line grep the card uses reads only 5 on the same tree, and MISSES authentication.mdx:853 entirely — the PM's method warning applies to the card's command as well as to a hand-composed pipeline.", "two_buckets_side_by_side": "CONFIRMED, and worth citing. content/docs/permissions/authentication.mdx:853 'its own — measured on 1.7.1, where addMember builds its endpoint with no path' is an attestation in exactly this PR's sense, customer-facing, now naming a version that is not installed. authentication.mdx:1217 'the stable 1.7.0 / 1.7.1 releases renamed it back to accountId' is CORRECTLY FROZEN — a statement about what those releases did; rewriting it to 1.7.2 would make it false. The buckets are real, not a taxonomy invented for this PR, and both now appear in the PR body as evidence.", "mechanism_placement_finding": "⭐ Adding content/docs to the gate's ROOTS would NOT be sufficient, and this is a Zone 2 B result about WHERE the mechanism belongs and HOW FAR it reaches — not about affordability. Measured on the two cited lines: (a) :853 IS seen but lands 'unattributed' because 'better-auth' sits ~90 characters earlier on the previous line, past CLAIM_GAP=60 — so it is reported and never judged; (b) :1217 is NOT SEEN AT ALL — measured zero 'better-auth' mentions within lines 1213-1221, so no watched package name falls in the window. Prose separates a package name from its version much further than code does. A docs lane therefore needs a wider claim gap or MDX-aware attribution, with its own false-positive budget over 405 files. Not folded in here.", "should_docs_be_its_own_card": "YES — recommend filing. Reasons: (1) it is customer-facing, which is a different and arguably higher-severity surface than the internal comments this card covers; (2) it needs a different detector tuning, not just one more ROOT entry, so it is real work rather than a one-line extension; (3) today it is NOT urgent — 0 live-stale on the docs corpus, and the one live attestation (:853) is reported-but-unjudged rather than false-in-the-gate's-terms. ⛔ Any such card MUST exclude content/docs/releases/** by construction: it is release-owned and fenced unconditionally by AGENTS.md, and 3 of the 4 'historical' docs sites live in v15.mdx / v17.mdx. I did NOT file it — the PM said they would.", "scope_discipline": "⛔ No content/docs file edited, under any framing. content/docs/releases/v17.mdx not touched. The gate's ROOTS are unchanged (packages, scripts, apps, examples), so this PR does not silently start scanning docs either.", "mcp_calls": "5 cumulative — issue_read(get), issue_read(get_comments), create_pull_request, add_issue_comment, add_issue_comment(this addendum). The PR body update and all read-backs went over repo-scoped REST (PATCH 200, full body read-back verified: all sections present, tail intact)." }Generated by Claude Code
claude commentedon Aug 31, 2026
Residue filed, per the two rulings in the ACCEPT above:
^1.7.2. Paid down per surface as each is next touched; a batch sweep or a pin-equality gate is refused there, with this PR's own counts as the reason.content/docscoverage, carrying the measurement that addingcontentto the gate'sROOTSis measurably insufficient —content/docs/permissions/authentication.mdx:853lands unattributed pastCLAIM_GAP = 60, and:1217is not seen at all. Excludescontent/docs/releases/**by construction.Both are
finding, unassigned and ungraded — the triage seat grades them. This card closes on this PR.Generated by Claude Code
claude commentedon Aug 31, 2026
Closed as completed by the
domain:devxexecution PM seat (#6023), on PR #13979 — merged715fcef4at 22:27:18Z.Part of #13940, not a closing keyword, and that was the right call at the time it was written: the dev could not close a card whose 130-stamp population it had just ruled must not be restamped. The close is this seat's, under the Q1 ruling in the ACCEPT above:What actually shut, and what did not
^1.7.2. Filed as [finding] 130 anchored better-auth 1.7.1 measurements are unverified against the installed 1.7.2 — pay down per surface, never as a batch #13980, to be paid down per surface as each is next touched, ⛔ never as a batch.content/docs. Filed as [finding] The vendor-version-stamp gate does not cover content/docs, and adding it to ROOTS is measurably insufficient #13981, carrying the measurement that addingcontentto the gate'sROOTSis measurably insufficient, not merely weak.⇒ ⭐ The card asked "why does a hand sweep keep being the remedy". It now has a structural answer, and a fifth sweep would not have produced one.
pm:dispatchedstripped in the same write as the close.domain:devx·priority:p2·toolingretained — attribution is not state.Generated by Claude Code