Repository navigation
deps(plugin-auth): lift the better-auth family from 1.7.3 to 1.7.12 — security fixes published after the 2026-10-01 deferral #25096
Description
Filing-gate class: ③ directed task.
Acting reader: the triage seat grades this card (domain / area / priority); the seat it names dispatches one os-dev to land the lift as one PR.
Dedup (open and closed): semantic better-auth security advisory magic link OAuth state oauth-provider consent upgrade 1.7.12 gave 4 hits and bump better-auth family 1.7.4 1.7.7 1.7.12 upgrade gave 5. All are closed and none is in scope. The last family move was #17454 (to 1.7.3, via #17440). #21094 deferred the next one (below).
Filed from session session_01GMBuQthe2LLGteJbhzXhvF.
Request (verbatim)
单独开一个 issue,把全家升到 1.7.12
Why now: the 2026-10-01 deferral measured 1.7.6, not the fixes
#21094 recorded the maintainer's deferral, verbatim:
按你的推荐:关掉21029立卡,better-auth先放着,21024授权你落地
That deferral covered Dependabot #21029, which proposed 1.7.6. The critical fix below first ships in 1.7.7, published 2026-09-30. The other fixes listed below land in 1.7.8 to 1.7.12. None of them was in front of that decision. The maintainer decides whether this card overrides it, and triage should put that question to them before dispatch.
What 1.7.4 to 1.7.12 fixes, read against what plugin-auth mounts
Read on origin/main 1862d8d.
Advisories
| Advisory | Fixed in | Exposure here |
|---|---|---|
| GHSA-965c-763c-88jm (critical): an OAuth state value is accepted as a Magic Link token | 1.7.7 | Conditional. Preconditions per the advisory: Magic Link on with plain token storage (the plugin default), at least one social or Generic OAuth provider, and database-backed OAuth state (the default when a database is configured). magicLink is opt-in: pluginConfig.magicLink ?? false in auth-manager.ts. plugin-auth sets none of storeToken, storeStateStrategy or storeIdentifier, so a deployment that enables magicLink alongside socialProviders or oidcProviders meets every precondition. The cloud repo's source does not enable magicLink; its live config is unmeasured. |
| GHSA-r4xp-prcw-77qf (high): OAuth Proxy | 1.7.7 | Not exposed: plugin-auth mounts no oauthProxy. |
| GHSA-mx9r-x6ww-qjw9 (high): personal SSO providers | @better-auth/sso 1.7.3 |
Already fixed at the current pin. |
| GHSA-q84f-53jg-9ppm (high): device approval | 1.7.0-rc.3 | Already fixed at the current pin. |
Changelog security fixes in packages we mount
@better-auth/oauth-provider(mounted whenresolveOidcProviderEnabledsays so; it followsOS_MCP_SERVER_ENABLED):- fix(oauth-provider): keep consent separate per reference better-auth/better-auth#11801 (1.7.10): approving consent without a reference id no longer overwrites an organization's stored consent, which could grant scopes without a prompt.
- fix(oauth-provider): keep admin changes made during a CIMD refresh better-auth/better-auth#11830 (1.7.11): a CIMD metadata refresh no longer reverts admin changes made during the fetch (disabled client,
skipConsent, scopes). - fix(oauth-provider): keep approved resources refreshable after a narrowed device poll better-auth/better-auth#11815 (1.7.10): device-code refresh across approved resources.
- fix(db): keep derived replay-marker ids under uuid and serial ids better-auth/better-auth#11625 (1.7.8): replay protection under
generateId: "uuid". plugin-auth sets nogenerateId, but the dev should confirm which IDs the adapter actually generates. - fix(oauth-provider): return invalid_client for an unknown client at the token endpoint better-auth/better-auth#11176, fix(oauth-provider): treat unverifiable JWT access tokens as invalid better-auth/better-auth#11596 and fix(oauth-provider): return 400 invalid_grant on PKCE verifier mismatch better-auth/better-auth#11724 (1.7.10): client-auth, invalid-JWT and PKCE error handling.
@better-auth/sso:- fix(sso): verify OIDC ID tokens with the shared step better-auth/better-auth#11935 (1.7.12): OIDC ID tokens without
exp/iatare refused. - fix(sso): reject mismatched OIDC issuers better-auth/better-auth#11438 (1.7.8): a provider's issuer must exactly match its discovery document.
- fix(sso): verify OIDC ID tokens with the shared step better-auth/better-auth#11935 (1.7.12): OIDC ID tokens without
@better-auth/scimfix(scim): match attribute names case-insensitively better-auth/better-auth#11644 (1.7.8): attributes sent in non-standard case (Active: false) were ignored, so a deprovisioned user could stay active.- core:
- fix(password): revoke sessions and write the new password atomically better-auth/better-auth#11626 (1.7.8): password reset and change revoke sessions in one transaction.
- fix(password): consume the reset token inside the reset transaction better-auth/better-auth#11646 (1.7.8): a failed reset no longer consumes the link or OTP.
- fix(jwt): verify tokens with the stored key's algorithm better-auth/better-auth#11925 (1.7.12): the JWT plugin verifies with the stored key's algorithm.
Behaviour changes the lift must measure, not assume
- 1.7.7 fix(auth): isolate verification records and encryption purposes better-auth/better-auth#11494: Magic Link and OAuth-state verification rows now carry
magic-link:/auth-state:identifier prefixes.- In-flight Magic Link, OAuth, SAML and account-linking flows fail after the upgrade.
implicit-account-linking.tswrites its ownsys_verificationrows under its own identifier namespace. Confirm that it neither collides with nor reads the new prefixes.
- 1.7.8 fix(custom-session): propagate session lookup errors better-auth/better-auth#11391:
customSessionnow throws when the session lookup fails, instead of treating the user as signed out. plugin-auth mountscustomSession. - 1.7.8 chore: bump
better-callbetter-auth/better-auth#11540 (cookie attributes per RFC 6265bis), fix(custom-session): forward session cookie cleanup when session is null better-auth/better-auth#10591 (stale-cookie clearing withcustomSession) and fix(session): fall back to database for bulk session lookups better-auth/better-auth#11718 (storeSessionInDatabasesession switching). plugin-auth sets sessionstoreInDatabasewhenever a host suppliessecondaryStorage. - 1.7.7 fix(organization): refresh active organization after email sign-in better-auth/better-auth#11375: the active organization refreshes after sign-in when a session hook selects it. plugin-auth stamps
activeOrganizationIdinsession.create.before. @better-auth/oauth-provider1.7.9 fix(oauth-provider): validate DPoP against public URL better-auth/better-auth#10799: the DPoPhtuvalue is now derived from the publicbaseURL.@better-auth/oauth-provider1.7.10 fix(oauth-provider): return 400 invalid_grant on PKCE verifier mismatch better-auth/better-auth#11724: a PKCE verifier mismatch moves from 401 to 400. Any test that pins the old status needs updating to the new one.- Export surface: this family has removed public exports in a patch release before (1.7.3, see the overrides note in
pnpm-workspace.yaml).pnpm check:vendor-export-contractis the gate. - Re-measure the version attestations: 32 lines across 19 files under
packages/plugins/plugin-auth/srcname1.7.3. Re-measure each claim on 1.7.12 and update its version stamp. A claim that no longer holds is reported, not quietly re-stamped.
Acceptance
- One commit lifts all 16 pins. The 11 family overrides in
pnpm-workspace.yamland the 5 exact ranges inpackages/plugins/plugin-auth/package.jsonall move to1.7.12, exact, not caret. - Lockfile regenerated with the repo's tooling, never by hand.
- Gates green:
pnpm check:override-consistencypnpm check:vendor-export-contract- the plugin-auth suite
- the auth dogfood suites
- Each behaviour change above is either re-measured, with a test or dogfood run cited in the PR body, or reported as a fork with the differing measurement.
- A
patchchangeset for@objectstack/plugin-auth, because the declared range reaches consumers. Its operator notes say:- in-flight Magic Link / OAuth / SAML flows must restart after the upgrade;
- an OIDC SSO provider whose issuer does not exactly match its discovery document must be reconfigured.
Out of scope
- The 1.8.0 betas.
- Org invite links: 1.7.4 to 1.7.12 add none. The organization plugin's only changes are fix(organization): refresh active organization after email sign-in better-auth/better-auth#11375, fix(organization): published types fail with skipLibCheck: false better-auth/better-auth#11637 and fix(db): use static zod lookup in toZodSchema so locales tree-shake better-auth/better-auth#10866.
- The cloud pin: the cloud repo picks the lift up on its standing
.objectstack-shabump.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 21, 2026
Triage: graded p1 ·
domain:services·area:identity·security·pm:queueTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-21T09:57Z. ⛔ Not a claim.plugin-authisdomain:services's (pm-dispatch lane table). One PR lifts the whole family, as the card asks.Generated by Claude Code
objectstack-fleet commentedon Oct 21, 2026
Claim: PM loop round 31 · 2026-10-21T10:15Z
Session:
session_011TdZ8GjixNZFLz1xJD9zzKAccount:
os-justin(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-25096-better-auth-1-7-12Worktree:
objectstack-issue-25096Domain:
domain:services(triage6313201609routes this one-PR family lift to the lane; the root files below are declared on the cross-domain exception path)Seat:
domain:services#1File surface:
pnpm-workspace.yaml: the better-auth family's override targets and their comment notes only;pnpm-lock.yaml: regenerated by the repo's tooling only, never edited by hand;packages/plugins/plugin-auth/package.json: the five better-auth family ranges only;packages/plugins/plugin-auth/src/**: the version attestations, and only the code and tests a measured behaviour change of the lift requires;plugin-authonly where it pins a status the lift moves, each one named in the report;.changeset/25096-*.md.⛔ No other dependency moves. ⛔ No 1.8.0 beta. ⛔
packages/spec/**, ⛔packages/objectql/**. ⛔ No new override shape: the selector rule inpnpm-workspace.yaml's header stands. (Stop on breach; explain in the report.)Container & model:
M,mode:subagent,model: default (opus), quoting the --tier output "no path-derived mandate … floor sonnet · default opus · ceiling fable"Clause-②: no (narrowing)
Responsibility:
the better-auth family is pinned at 1.7.3, below published security fixes in packages plugin-auth mounts | the maintainer's word on this card asks the whole family lifted to 1.7.12 | every deployment of plugin-authThread-read: 6313201609
Serial constraints cleared:
Serial dispatch: this is the seat's one card in flight (PR #25090 landed as 3782b2df6e). No open PR touches pnpm-lock.yaml, pnpm-workspace.yaml or plugin-auth/package.json; PR #25099 adds one new plugin-auth test file, disjoint; the Version Packages PR #24876 regenerates itself. No in-flight claim in any lane declares these files.Why
no (narrowing): the lift brings refusals that 1.7.3 did not make. An OIDC SSO provider whose issuer does not exactly match its discovery document, and an ID token withoutexporiat, are refused. A PKCE verifier mismatch moves from 401 to 400. In-flight Magic Link, OAuth, SAML and account-linking flows fail across the upgrade. So this ships as a BREAKING minor of@objectstack/plugin-authunder the amended 「甲」 (6303865025), not the card'spatch, with a!title, its ADR-0087 disposition and the full dogfood battery on the final head. If the dev measures any change that WIDENS what is accepted, it names it in its report, and the seat rules the arm before landing.Generated by Claude Code
objectstack-fleet commentedon Oct 21, 2026
os-dev-report
{
"issue": 25096,
"status": "blocked",
"branch": "claude/issue-25096-better-auth-1-7-12",
"pr": "#25109",
"session": "session_011TdZ8GjixNZFLz1xJD9zzK — the parent's harness-stamped id (subagent run); it is the session the relay envelope and every commit trailer carry",
"premise_still_valid": true,
"summary": "Done in full except one gate: all 16 pins are lifted to exact 1.7.12 in one commit (75aaf74) and the lockfile is regenerated by pnpm install. Only the family and what 1.7.12 itself declares moved (better-call 1.4.4, @better-auth/utils 0.5.0 as the single copy, @better-fetch/fetch 1.3.2). The better-sqlite3 12 peer copy, its 20 build-only packages, @cloudflare/workers-types, @opentelemetry/semantic-conventions and two stale duplicates left the lockfile. Nothing was added outside the family. The export surface is intact (vendor-export-contract PASS). Every behaviour change on the card was measured on a real ObjectQL + SQL stack (table in the PR body). The one suite the lift turned red was the route ledger (1.7.12 publishes POST /oauth2/client/rotate-keys; measured 403 because allowClientKeyRotation is unset), now ledgered. Two new pins with ablations cover the customSession failure answer (now 500 FAILED_TO_GET_SESSION, was 401 UNAUTHENTICATED 'Sign in first') and single-use reservations on the ObjectQL adapter. Of the 32 attestation lines, 19 were restamped, 12 kept (facts about the 1.7.3 release that still hold) and 1 flagged and reported instead of restamped. The changeset is a BREAKING minor in the #24813 shape. BLOCKED on one thing only the seat can grant: check-vendor-version-stamps stays red on two sites the lift falsifies that sit outside the claimed file surface, packages/spec/src/identity/organization.zod.ts:95 (a hard stop) and packages/cli/src/commands/migrate/account-issuer.ts:71. Both claims re-measured true on 1.7.12; the proposed text is in open_questions.",
"tests": "plugin-auth on the lift alone (75aaf74):
Test Files 1 failed | 176 passed (177),Tests 1 failed | 3375 passed | 10 skipped; the red was auth-route-ledger.conformance (rotate-keys), fixed in 9ffd7eb. Final head 89b9aff (origin/main 2e242bb merged in before the final runs):pnpm --filter @objectstack/plugin-auth exec vitest run --maxWorkers=2givesTest Files 179 passed (179),Tests 3383 passed | 10 skipped (3393), lock VERDICT command-exit 0. plugin-auth typecheck exit 0 (check:test-typecheck: OK, debt ledger unchanged at 88/23). New pins: session-read-failure-answer.test.ts 4/4 and verification-reservation-single-use.test.ts 3/3. Ablation 1 (reservation, via scripts/ablation-replace.mjs WRAP, src path with no build between): the adapter insert was mutated to drop the forced id; anchor 1 to 0, blob 5ed0db896088 to cbd1309c37a7;Tests 2 failed | 1 passed (3)('expected 3hj6lgd1y2K1GdHC to be RhMSgVct...', 'expected true to be false'); restored with blob == HEAD and an empty git diff HEAD. Ablation 2 (customSession, a vendor dist mutation because the behaviour is the vendor's): the hard link into the shared pnpm store was broken first (inode 4096254 to 1565215), then the 1.7.7 catch was reinjected; on disk anchor 0, injected 1, sha256 b2e60e53 to 25e2b179;Tests 2 failed | 2 passed (4)(HTTP 'expected 401 to be 500', body UNAUTHENTICATED 'Sign in first'; in process 'promise resolved null'); restored under a trap with sha256 back to b2e60e53, and the store inode still hashes b2e60e53. One-shot probes (a throwaway vitest file run at 3477726, not committed): ids 32-char base62; magic-link:/auth-state: prefixes disjoint from account-unlinked: (exact and prefix reads return the tombstone only, the sweep spares the vendor rows); the OAuth state value at /magic-link/verify gives 302 ?error=INVALID_TOKEN with no session cookie, while the real link gives 302 with a session cookie; PKCE mismatch 400 invalid_grant, match 200; DPoP htu = public base accepted from a proxied host, htu = request URL refused invalid_dpop_proof; rotate-keys 403 access_denied; an over-long password at sign-in 400 PASSWORD_TOO_LONG and it still counts toward lockout (0 to 1 to 2); cookies HttpOnly; SameSite=Lax; Path, no Secure; a stale cookie is cleared with Max-Age=0 on three cookies; with a host secondaryStorage, verification.storeInDatabase true, session.storeSessionInDatabase unset, 0 sys_session rows; a no-database store gives 'Model sys_user not found'; the sso verify-domain record as instructed gives 502 and the bare token 204.","gates": "Local subset at final head 89b9aff, each exit captured before any pipe:
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(exit 0) derived 86 families. All 86 ran under the lock, every one exit 0.--ranreconciliation: '86 derived famil(ies) accounted for — 86 run, 0 NOT-MEASURED (a DERIVED zero)'. The first run at becfb99 hadpnpm check:dual-build-cjs-loadsexit 3 (PREREQUISITE NOT MET, no full build); it was measured 0 on the final run after the dogfood closure build. Named in the dispatch:pnpm check:vendor-export-contractexit 0 ('VERDICT: PASS — vendor export contract (installed workspace), 3 edge(s) verified');pnpm check:override-consistencyexit 0 ('9 published-manifest declaration(s) ... all resolve to their override targets');node scripts/check-vendor-version-stamps.mjsexit 1, red only on packages/cli/src/commands/migrate/account-issuer.ts:71 and packages/spec/src/identity/organization.zod.ts:95 (out of surface, see open_questions; CI's 'Vendor version stamps' step in lint.yml will read the same). check-platform-checklist not owed (no export renamed or removed). Local-only families (check:dev-prereqs scan, check:skill-frame-freshness) not owed: no packages/spec build input and no four-axis frame file touched. CI check-runs on PR 25109: in_progress when this report was written; CI owns them.","dogfood_battery": "89b9affe00 · os-verify-lock: VERDICT command-exit 0 · held the lock 765s · Test Files 286 passed | 3 skipped (289) · Tests 2387 passed | 18 skipped (2405) · no failures. Command:
pnpm --filter @objectstack/dogfood test, no OS_TEST_SHARD and no path, afterpnpm --filter '@objectstack/dogfood^...' build(exit 0).","mcp_calls": "0 — no MCP GitHub tool was called",
"api_writes": "3, within budget: (1) git push of claude/issue-25096-better-auth-1-7-12, not a REST write: the empty probe push plus 7 pushes of the same branch, one of them a
--force-with-leasepush (see deviations); (2) relay stroke fw-20261021T114443Z-ddc3f9 via POST /repos/objectstack-ai/objectstack/dispatches, op pr_create, which executed POST /repos/objectstack-ai/objectstack/pulls (draft) and POST /repos//issues/25109/assignees (os-justin); read back as '20838 byte(s) sent, 20838 stored — identical'; (3) relay stroke via POST /repos/objectstack-ai/objectstack/dispatches, op comment, which executed POST /repos//issues/25096/comments (this report). No label write, no assignee write on the card, no PATCH of the PR body.","deviations": [
"A throwaway probe test file was swept into one commit by a directory-wide git add and pushed. About a minute later it was removed by amending and pushing with --force-with-lease pinned to that commit. All five AGENTS.md section-3 conditions held: a claude/issue-* branch this worktree created, one author, no PR yet, and a lease on the last pushed sha. The file held measurement code and no secret. [Seat redaction under the #9387 ruling: the orphaned commit's id is held privately.]",
"Ablation 2 wrote to an installed vendor file under node_modules/.pnpm. It broke the hard link to the shared pnpm store first, so the store was never written (proved by hashing the original inode afterwards), and it restored the file's bytes under a trap.",
"origin/main (3 commits: plugin-security, platform-objects, objectql) was merged before the PR opened, per AGENTS.md section 10. None of those commits touches the lockfile, the workspace file or plugin-auth. All final readings are on the merge head."
],
"open_questions": [
{
"question": "File surface: may this PR edit the two out-of-surface lines that check-vendor-version-stamps now reds? (1) packages/spec/src/identity/organization.zod.ts:95 'Both halves measured against the installed better-auth 1.7.3, so fallback A applies'. This is packages/spec, a hard stop, and TSDoc on a schema property, so
pnpm --filter @objectstack/spec check:generatedmust be read after it. (2) packages/cli/src/commands/migrate/account-issuer.ts:71, the operator-facing description '...is ONE account under the key better-auth 1.7.3 restored'. Both claims re-measured on 1.7.12: the organization plugin's schema.mjs is byte-identical and the adapter factory's transformOutput still iterates the declared fields only; the account key is still (provider_id, account_id).","options": [
"A: widen this PR's surface for those two lines. spec: append '(re-measured on 1.7.12 for #25096: the organization plugin schema is byte-identical and transformOutput still projects declared fields only)' after the 1.7.3 reading. cli: drop the version from the runtime string ('...is ONE account under the (provider_id, account_id) key better-auth restored'), which no future lift can falsify. Then re-run the gate and spec check:generated. Cost: two lines, one spec regeneration check. Work: small.",
"B: scope instead of re-measure ('the then-installed better-auth 1.7.3'), the gate's cheapest honest repair. Cost: the spec docblock keeps a reading of a version no longer installed. Work: same size as A.",
"C: a separate PR for the two lines. Cost: this PR cannot go green until that one lands, and the stamp step reds on main the moment this lifts alone. Work: one more PR and one more review."
],
"recommendation": "A, because the lift is what falsifies both lines, the claims are re-measured true rather than merely scoped, and the version-free cli wording removes that site from every future lift."
},
{
"question": "Measured widening, for the seat to rule the arm: oauth-provider 1.7.12 judges a DPoP proof's htu against the configured public base URL plus the request path, not against the URL the request reached the server under. Behind a host-rewriting proxy it now ACCEPTS a proof bound to the public URL (1.7.3 refused it) and refuses one bound to the internal URL (1.7.3 accepted it). Does the declared
Clause-②: no (narrowing)stand?","options": [
"A: keep
no (narrowing). The widening is confined to deployments whose request host differs from the configured base URL, and it admits only proofs bound to this server's own configured URL. RFC 9449 defines htu as the URI the client addressed, so the new reading is the correct one. The changeset already states both halves.","B: treat the release as widening too and split the declaration. Cost: Clause-② admits one arm, so this means a second changeset and PR, or re-arming the whole lift. Work: medium."
],
"recommendation": "A, because the widening is the RFC-correct binding to the server's own public URL and is the mirror of a narrowing on the same request, not a new acceptance class. The changeset names it explicitly, so no reader is surprised."
},
{
"question": "Design choice after GHSA-965c-763c-88jm (not built): should plugin-auth also set an explicit token-storage option, given 1.7.12 closes the advisory on its own (measured: the precondition set no longer yields the exposure)? The four axes are pasted from pm-dispatch SKILL.md as the dispatch gave them.",
"options": [
"A: leave the vendor default (plain token storage). Cost: none. Work: none.",
"B: hard-code
magicLink({ storeToken: 'hashed' }), with no new knob. Cost: one option line plus a pin, and one more invalidation of in-flight Magic Links unless it rides this same release, which already invalidates them. Work: small.","C: set
verification.storeIdentifier: 'hashed'for every verification row. Cost: plugin-auth reads its own rows by raw identifier (unlink-tombstone exact and starts_with reads, the self-registration grant claim, the reset-password lookup), so a hashed identifier silently breaks the user-deletion prefix sweep, and live rows would need migrating. Work: large."],
"recommendation": "A. Real business need: Magic Link is opt-in (pluginConfig.magicLink ?? false), the cloud source does not enable it, and no measured deployment does; sys_verification.identifier is already
internal, so the token is off every generic read and hashing only guards against direct store access; nobody measured asks for B or C. Long-term soundness: the defect was the vendor's and is fixed there, while C fights plugin-auth's own identifier-keyed rows, a workaround shape. Keeping AI from writing it wrong: A and B add no authorable surface if hard-coded, a configurable option would be a knob declared without effect, and C adds silent breakage of the prefix reads. Startup focus: A is the zero-growth answer, B has no pull, C spreads. If a real deployment turns Magic Link on, B is the follow-up, hard-coded and never a knob."}
],
"out_of_scope_findings": [
"class: a · reach: public door POST /api/v1/auth/sso/verify-domain, the vendor route that plugin-auth's POST /api/v1/auth/admin/sso/verify-domain bridge re-dispatches to, measured with DNS answered by a double: the TXT value that POST /api/v1/auth/admin/sso/request-domain-verification tells the operator to publish (
_better-auth-token-PROVIDER=TOKEN) answers 502 DOMAIN_VERIFICATION_FAILED and the bare token answers 204; the bridge leg itself was not run · evidence: sys_verification.identifier isinternal, so internalAdapter.findVerificationValue hands better-authidentifier: undefined(the store holds it), and @better-auth/sso builds the expected record as IDENTIFIER=VALUE, i.e.undefined=TOKEN. The same vendor code is in 1.7.3, so this is pre-existing and not caused by the lift. The internal-field-readback.ts census that says no flow readsidentifierback is flagged in place, not restamped. Landing: register-sso-provider.ts dnsRecordValue vs the identifier read-back seam · dedupe words: sso verify-domain TXT, DOMAIN_VERIFICATION_FAILED, dnsRecordValue, sys_verification identifier internal, request-domain-verification","class: a (user-visible misleading text) · reach: POST /api/v1/auth/sign-in/magic-link measured: the auth.magic_link mail goes out with expiresInMinutes 10 and its copy says 'This link expires in 10 minutes', while the stored verification row lives 300 s, because magicLink() is mounted without expiresIn and the vendor default is 300 s. Unchanged by the lift · evidence: auth-manager.ts sendMagicLink
expiresInMinutes: 10; plugin-email auth-templates magic_link copy; probe lifetimeSec 300 · dedupe words: magic link expires minutes, expiresInMinutes, magicLink expiresIn, auth.magic_link lifetime","carrier: 承接者:无 · noted, not filed: a host secondaryStorage together with the embedded OAuth provider (on by default with the MCP surface) makes the auth instance throw at build (vendor BetterAuthError requiring session.storeSessionInDatabase). It is loud, the same on 1.7.3, and the existing secondaryStorage suites set oidcProvider false. Recorded in the PR's Acceptance notes only."
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 21, 2026
Seat rulings on PR #25109's open questions ·
domain:servicesseat 1 ·session_011TdZ8GjixNZFLz1xJD9zzK· 2026-10-21T11:53Z@objectstack/specor@objectstack/clinamed, they ride aspatch(prose only). Neither is BREAKING.htubinding): A.Clause-②: no (narrowing)stands. The proof is now bound to this server's own configured public URL, which is RFC 9449's reading. That is the mirror of the narrowing on the same request, not a new acceptance class, and it comes with the family lift the maintainer asked for by name. The changeset already names both halves, and it keeps naming them.secondaryStoragenote stays in the PR's Acceptance notes.Generated by Claude Code
objectstack-fleet commentedon Oct 21, 2026
Claim amendment ·
domain:servicesseat 1 ·session_011TdZ8GjixNZFLz1xJD9zzK· 2026-10-21T11:53Z · amends claim 6313539804.Clause-②: no (narrowing)
File surface, widened by exactly two lines outside this lane, declared to their lanes (
6315228904on #6017,6315240630on #22648):packages/spec/src/identity/organization.zod.ts: the one TSDoc paragraph whose better-auth 1.7.3 stamp the lift falsifies. A re-measurement clause is appended, and nothing else inpackages/specmoves;packages/cli/src/commands/migrate/account-issuer.ts: the version number leaves the command'sdescriptionsentence, and nothing else moves.Both are owed because
scripts/check-vendor-version-stamps.mjsreads them, and the dev re-measured each claim true on 1.7.12. The rest of the claim stands: the hold-offs,Clause-②: no (narrowing), and the full dogfood battery on the final head.Generated by Claude Code
objectstack-fleet commentedon Oct 21, 2026
os-dev-report
{
"issue": 25096,
"status": "done",
"round": "patch round 1 (seat rulings 6315255713: Q1 A, Q2 A, Q3 A; claim amendment 6315268175, verified as the newest amendment and naming both files before any edit)",
"branch": "claude/issue-25096-better-auth-1-7-12",
"pr": "#25109",
"session": "session_011TdZ8GjixNZFLz1xJD9zzK — the parent's harness-stamped id (subagent run)",
"premise_still_valid": true,
"summary": "The two out-of-surface stamps are fixed exactly as ruled, and check-vendor-version-stamps now exits 0. packages/spec/src/identity/organization.zod.ts keeps its 1.7.3 reading in the updatedAt TSDoc and appends the 1.7.12 re-measurement clause; nothing else in packages/spec moved, and spec check:generated is green after a fresh spec build. In packages/cli/src/commands/migrate/account-issuer.ts the description sentence now reads '...is ONE account under the (provider_id, account_id) key better-auth restored...'; nothing else moved, and no test pins that sentence. The existing changeset names @objectstack/spec and @objectstack/cli as patch, text only. plugin-auth stays the BREAKING minor, and the DPoP htu FROM/TO still names both halves. origin/main was merged before the edits; every reading below is on the final head 8c4bf64.",
"tests": "Final head 8c4bf64, all under the lock. plugin-auth:
pnpm --filter @objectstack/plugin-auth exec vitest run --maxWorkers=2givesTest Files 180 passed (180),Tests 3422 passed | 10 skipped (3432); typecheck exit 0 (check:test-typecheck: OK, debt 88/23 unchanged). cli (the diff touches packages/cli; the unit layer is owed locally, integration is declared to CI):pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2givesTest Files 321 passed (321),Tests 4746 passed (4746);pnpm --filter @objectstack/cli typecheckexit 0 (check:test-typecheck: OK, 28/6 held). A pin of the description: none exists (repo-wide git grep for the sentence and for the command's description finds only the source; the cli tests that name account-issuer assert its output, not its description). spec:pnpm --filter @objectstack/spec buildexit 0, thenpnpm --filter @objectstack/spec check:generatedexit 0 ('All 14 generated artifacts are up to date', check:docs and check:api-surface included). No ablation: this round changes comment and description text only and pins no behaviour.","gates": "Final head 8c4bf64.
node scripts/check-vendor-version-stamps.mjsexit 0 ('check:vendor-version-stamps: OK — 10187 file(s) scanned').pnpm check:vendor-export-contractexit 0 ('VERDICT: PASS — vendor export contract (installed workspace), 3 edge(s) verified').pnpm check:override-consistencyexit 0 (9 published-manifest declarations resolve to their override targets).node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackexit 0, deriving 108 families (spec and cli now in the change set). All 108 ran under the lock with exits captured before any pipe.pnpm check:dual-build-cjs-loadsfirst exited 3 (PREREQUISITE NOT MET: 8 packages outside the closures had no dist); those 8 were then built and the gate re-run exit 0, and its recorded line carries that answer.--ran: '108 derived famil(ies) accounted for — 108 run, 0 NOT-MEASURED (a DERIVED zero)'. CI check-runs for 8c4bf64: CI owns them; not read here.","dogfood_battery": "8c4bf6489b · os-verify-lock: VERDICT command-exit 0 · held the lock 751s · Test Files 286 passed | 3 skipped (289) · Tests 2387 passed | 18 skipped (2405) · no failures.
pnpm --filter @objectstack/dogfood test, no shard and no path, afterpnpm --filter '@objectstack/dogfood^...' --filter '@objectstack/cli^...' --filter @objectstack/cli build(exit 0).","mcp_calls": "0 — no MCP GitHub tool was called",
"api_writes": "2 this round, within the round's budget: (1) one fast-forward git push of claude/issue-25096-better-auth-1-7-12, 89b9aff to 8c4bf64 (the merge 2847684 and the edit commit 8c4bf64), not a REST write; (2) one relay stroke via POST /repos/objectstack-ai/objectstack/dispatches, op comment, which executed POST /repos//issues/25096/comments (this report). No PR body write, no label or assignee write. Reads only otherwise (GET of the card's comments).",
"notes": [
"origin/main moved by 2 commits (ae58b26, 03c12a5) while the final runs were in flight. I did not merge them: the PR is open, and AGENTS.md section 10 merges main after opening only on a conflict, a main-caused red or a queue removal. A local
git merge-tree --write-tree HEAD origin/mainis clean (exit 0). That reading is driver-aware and is not GitHub's mergeability verdict.","git add was path-explicit for every file this round, and nothing was probed inside the worktree's tracked tree."
],
"pr_body_append": [
"## Patch round 1 (seat rulings
6315255713, claim amendment6315268175)","",
"-
packages/spec/src/identity/organization.zod.ts: theupdatedAtTSDoc keeps its 1.7.3 reading and appends the re-measurement: "(re-measured on 1.7.12 for #25096: the organization plugin schema is byte-identical andtransformOutputstill projects declared fields only)". Nothing else inpackages/specmoves.pnpm --filter @objectstack/spec check:generatedafter a fresh spec build: all 14 generated artifacts up to date.","-
packages/cli/src/commands/migrate/account-issuer.ts: thedescriptionnow reads "...is ONE account under the (provider_id, account_id) key better-auth restored...", with no version, so a later lift cannot falsify it. No test pins the sentence. cliunitlayer:Test Files 321 passed (321),Tests 4746 passed (4746); clitypecheckexit 0.","- Changeset:
@objectstack/specand@objectstack/cliride aspatch, text only and not breaking. plugin-auth stays the BREAKINGminor, and the DPoPhtuFROM / TO still names both halves (ruling Q2).","- With this round, the two out-of-surface stamp reds named under Attestations are resolved:
node scripts/check-vendor-version-stamps.mjsexits 0 (check:vendor-version-stamps: OK — 10187 file(s) scanned).","-
origin/mainwas merged atba244f1b48before the edits (merge28476848b5). Final head:8c4bf6489b.","- Final head readings:
pnpm check:vendor-export-contractPASS (3 edges);pnpm check:override-consistency(9 declarations resolve to their targets); plugin-authTest Files 180 passed (180),Tests 3422 passed | 10 skipped (3432),typecheckexit 0;dispatch-gates.mjs --commands108 derived, 108 run, 0 NOT-MEASURED, every exit 0 (--ran); full dogfood battery (no shard, no path)Test Files 286 passed | 3 skipped (289),Tests 2387 passed | 18 skipped (2405), lock verdict command-exit 0."],
"open_questions": [],
"out_of_scope_findings": []
}
Generated by Claude Code
objectstack-fleet commentedon Oct 21, 2026
os-dev-report
{
"issue": 25096,
"status": "blocked",
"round": "patch round 2 (CI red: Test Core 4/6, job 119207996576, on 8c4bf64)",
"branch": "claude/issue-25096-better-auth-1-7-12",
"pr": "#25109",
"session": "session_011TdZ8GjixNZFLz1xJD9zzK — the parent's harness-stamped id (subagent run)",
"premise_still_valid": true,
"summary": "Reproduced and root-caused; the lift exposes the red, and the fix sits outside the claimed surface, so I stopped and changed nothing. One test fails at 8c4bf64: packages/services/service-datasource/src/tests/sqlite-driver-fallback-file-open-failure.test.ts, 'resolveSqliteDriver in development: a native addon that cannot load still steps down to wasm (#2229)' / 'an addon that was never built (no code) steps down to wasm SQLite', with Error: Cannot find module 'bindings'. Its fixture realNotBuiltFailure() resolves better-sqlite3 from driver-sql (13.0.3) and requires
bindingsrelative to it. better-sqlite3 13.0.3 does not depend onbindings(its only dependency is node-addon-api; it loads its addon with a plain require of prebuilds/PLATFORM.node, else build/Release/better_sqlite3.node). On origin/mainbindings@1.5.0existed only as a dependency of better-sqlite3@12.11.1, the optional peer copy auto-installed for better-auth 1.7.3, and the test reached it through pnpm's hidden hoist (node_modules/.pnpm/node_modules/bindings, present on main, absent on the PR). 1.7.12 dropped that peer, so the phantom left. The test was modelling better-sqlite3 12's loader on a workspace that already resolves 13.0.3. The producer is fine: it classifies by error code and reads no message, and better-sqlite3 13's own never-built error (MODULE_NOT_FOUND) steps down to wasm, measured. The decision needed is in open_questions.","tests": "Fresh worktree at 8c4bf64, all under the lock:
pnpm install --frozen-lockfileexit 0;pnpm --filter '@objectstack/service-datasource^...' buildexit 0;pnpm --filter @objectstack/service-datasource testexit 1 withTest Files 1 failed | 52 passed (53)andTests 1 failed | 929 passed (930); the one failure is named in summary, 'Error: Cannot find module bindings', require stack .../better-sqlite3@13.0.3/node_modules/better-sqlite3/package.json, at the test file line 98. Main side, a second worktree at origin/main ae58b26, same three commands: exit 0, 0, 0, withTest Files 53 passed (53)andTests 930 passed (930). node_modules/.pnpm/node_modules/bindings is a link to bindings@1.5.0 on main and absent on the PR. In main's lockfile the onlybindingsedge is under better-sqlite3@12.11.1 (the better-auth 1.7.3 peer variant); better-sqlite3@13.0.3 lists only node-addon-api. Two one-shot probes, run outside the worktree's tracked tree: (1) better-sqlite3 13.0.3's lib with no build and no prebuilds, opened, throws code MODULE_NOT_FOUND, message "Cannot find module 'PKG/build/Release/better_sqlite3.node'"; (2) that MODULE_NOT_FOUND, injected at the knex better-sqlite3 client's acquireRawConnection exactly as the test's failNativeAddonLoad does, through service-datasource's built dist resolveSqliteDriver({ dev: true }) at 8c4bf64, answers engine 'sqlite-wasm'. So the producer's step-down covers 13.x's real never-built error.","gates": "Not re-run: nothing tracked moved this round (git status clean in the worktree; no commit, no push). The round-1 list on 8c4bf64 stands as reported in round 1 of the patch (108 derived, 108 run, 0 NOT-MEASURED), and the dogfood battery is unchanged for the same head. CI's Test Core 4/6 red is this test, reproduced locally above.",
"dogfood_battery": "n/a this round — no file moved; the last battery is on 8c4bf64 (Test Files 286 passed | 3 skipped (289), Tests 2387 passed | 18 skipped (2405))",
"mcp_calls": "0 — no MCP GitHub tool was called",
"api_writes": "1 this round: one relay stroke via POST /repos/objectstack-ai/objectstack/dispatches, op comment, which executed POST /repos//issues/25096/comments (this report). No push this round (nothing to push), no PR body write, no label or assignee write.",
"open_questions": [
{
"question": "The red is a test fixture that leaned on a phantom dependency the lift removes. The fix is outside the claimed surface: packages/services/service-datasource/src/tests/sqlite-driver-fallback-file-open-failure.test.ts (and optionally two docblock sentences in packages/services/service-datasource/src/sqlite-driver-fallback.ts that describe the never-built error as
bindings' 'Could not locate the bindings file', which better-sqlite3 13 no longer throws). How should it land?","options": [
"A1: widen this PR's surface by that one test file (plus, if wanted, the two docblock sentences, comment only), declared to the service-datasource lane. realNotBuiltFailure() produces better-sqlite3 13's own never-built error, a require of a missing build/Release/better_sqlite3.node that throws MODULE_NOT_FOUND, and the case asserts that code instead of
toBeUndefined(), renamed accordingly. The leg still drives the producer with a REAL load error, the file's stated rule. Measured: this error steps down to wasm on the current producer. Cost: one test file, about 15 lines; the round-1 gate list and the dogfood battery re-run on the new head. Work: small.","A2: the same fix as its own PR in the service-datasource lane, landing first. It is correct on main as it stands (main already resolves better-sqlite3 13.0.3; the test is green there only through the phantom). This PR then merges main and goes green unchanged. Cost: one more PR, one more review and CI round, and a serial dependency. Work: small plus coordination.",
"B: keep the phantom alive by declaring
bindingsas a devDependency of @objectstack/service-datasource. Cost: a package.json and lockfile change outside the surface. The leg would keep asserting the error of a loader better-sqlite3 13 does not use, a check about an addon path the workspace's driver never takes. Not recommended.","C: an override or packageExtensions entry that re-adds
bindingsor better-sqlite3 12. Cost: a new override shape the claim forbids, and a package nothing in the resolved driver uses. Not recommended."],
"recommendation": "A1. The lift is what exposes it, so landing the fixture fix with the lift keeps one CI round and one review, and the fix is measured correct (13.x's real never-built error steps down). Real business need: the step-down protects development installs where the native addon does not load, and 13.x's actual failure shape is the one a developer hits. Long-term soundness: the test stops depending on a package nobody declares. Keeping AI from writing it wrong: a fixture that resolves a phantom dependency passes until an unrelated lockfile change, the trap this red demonstrates. Startup focus: one test file, no new surface. A2 is the alternative if the seat prefers lane purity over one round."
}
],
"pr_body_append": [
"## Patch round 2: the Test Core 4/6 red (job
119207996576)","",
"- The failing test:
packages/services/service-datasource/src/__tests__/sqlite-driver-fallback-file-open-failure.test.ts, "an addon that was never built (no code) steps down to wasm SQLite",Error: Cannot find module 'bindings'. Reproduced at8c4bf6489b:Test Files 1 failed | 52 passed (53),Tests 1 failed | 929 passed (930).origin/mainae58b26f9c, same commands:Test Files 53 passed (53),Tests 930 passed (930).","- Root cause: the fixture requires
bindingsrelative tobetter-sqlite313.0.3, which does not depend on it (onlynode-addon-api; it loads its addon with a plainrequire). Onmain,bindings@1.5.0existed only underbetter-sqlite3@12.11.1, the optional peer copy auto-installed for better-auth 1.7.3, and the test reached it through pnpm's hidden hoist. 1.7.12 dropped that peer (see Lockfile: what moved), so the phantom left.","- The producer is unaffected:
resolveSqliteDriverclassifies by error code, not message. better-sqlite3 13's own never-built error isMODULE_NOT_FOUND(Cannot find module '.../build/Release/better_sqlite3.node'), and a probe drove it through the builtresolveSqliteDriver({ dev: true }), which stepped down tosqlite-wasm.","- Fix: outside this PR's claimed surface; the seat's ruling decides where it lands (open question on #25096)."
],
"out_of_scope_findings": []
}
Generated by Claude Code
objectstack-fleet commentedon Oct 21, 2026
Seat ruling on PR #25109's round-2 open question ·
domain:servicesseat 1 ·session_011TdZ8GjixNZFLz1xJD9zzK· 2026-10-21T13:08ZA1. The Test Core (4/6) red is a
service-datasourcetest fixture that reachedbindingsthrough pnpm's hidden hoist, a dependency nothing declares. Onmainit existed only under the better-sqlite3 12 peer copy that better-auth 1.7.3 pulled in, and the lift drops that copy. The fixture lands with the lift. It now produces better-sqlite3 13's own never-built error (MODULE_NOT_FOUND) and asserts that code, so the leg still drives the producer with a real load error. The producer is unchanged: its step-down was measured on 13.x's real error.packages/services/*), so no cross-lane declaration is owed. objectql:os migrate applyon the stock showcase prints an ERROR that its federated objects "keep their REST routes and keep rendering in the UI" — a migrate run serves neither, and the same boot denied the external datasource by policy on purpose #25079's claim only reads another file of that package, so the two do not intersect.sqlite-driver-fallback.tsthat describe the never-built error asbindings' message are corrected in the same round (comment only), so the file does not state an error 13.x no longer throws.The claim amendment for the widened surface follows.
Generated by Claude Code
objectstack-fleet commentedon Oct 21, 2026
Claim amendment ·
domain:servicesseat 1 ·session_011TdZ8GjixNZFLz1xJD9zzK· 2026-10-21T13:09Z · amends claim 6313539804 (after amendment 6315268175).Clause-②: no (narrowing)
File surface, widened within this lane by:
packages/services/service-datasource/src/__tests__/sqlite-driver-fallback-file-open-failure.test.ts: the never-built fixture and its one case only;packages/services/service-datasource/src/sqlite-driver-fallback.ts: the two docblock sentences that namebindings' never-built message, comment only. ⛔ No code in that file moves.The rest of the claim and of amendment
6315268175stands. The round-1 gate list and the full dogfood battery are re-owed on the new final head.Generated by Claude Code
objectstack-fleet commentedon Oct 21, 2026
Transition note from
domain:specseat 1 (seat post #6017) · sessionsession_01VsqhiSrBjB1wJfRNcHPeXN· 2026-10-21T13:27Z. ⛔ Not a claim, ⛔ no label change, no reply owed.PR #25114 (#25105, maintainer ruling A) landed on
mainasf2b6cf230b.Check Changesetnow grades the changeset level on a second line-initial declaration in the PR body, besideClause-②::A kind (
added/narrowed/semantic) owes at least one moved package atminor;noneowespatchon every introduced entry, and a non-protocol breaking change keeps its BREAKING banner and ADR-0087 disposition atpatch. A PR whose body has no readable kind, over an entry the kind would grade, readsnot-measured-material(red) on its next run. One PR-body line clears it (no push needed; the gate re-reads onedited). The same line in each changeset body feeds the release-notes grouping and the ADR-0087 gate'snarrowedsignal. The rule text is AGENTS.md item 3 andpr-automation.yml"WHICH LEVEL"; the claim template and os-dev definition learn the line in #25115.Your open PRs that introduce a changeset with no
Metadata-protocol:line, read 2026-10-21T13:27Z, with the implementing dev's and this seat's reading as a lead, not a verdict (the declaration is yours):none; aminorentry then readsnone-minorand is regradedpatch, keeping its BREAKING banner and ADR-0087 disposition.Generated by Claude Code
objectstack-fleet commentedon Oct 21, 2026
os-dev-report
{
"issue": 25096,
"status": "blocked",
"round": "patch round 3 (ruling A1 6316865808; claim amendment 6316873193, verified as the newest amendment and naming both files before any edit)",
"branch": "claude/issue-25096-better-auth-1-7-12",
"pr": "#25109",
"session": "session_011TdZ8GjixNZFLz1xJD9zzK — the parent's harness-stamped id (subagent run)",
"premise_still_valid": true,
"summary": "Round 3's fix is done and green at final head 1a28ccb. realNotBuiltFailure() now makes better-sqlite3 13's own never-built error: a require() of build/Release/better_sqlite3.node, made from the installed package against a module root that was never built, which Node answers MODULE_NOT_FOUND. It needs no phantom dependency. The case is renamed '... never built (MODULE_NOT_FOUND) ...' and asserts that code, and it still drives the producer with that real load error. The two sqlite-driver-fallback.ts docblock sentences now describe the 13.x error; no code moved. service-datasource is named patch (text only) in the existing changeset, because that docblock ships in dist/index.d.ts (measured). One ablation leg went red as required. BLOCKED on one new ruling, raised by main itself: f2b6cf2 (ruling A on #25105, landed 13:08 UTC, after my merge base) makes Check Changeset read a
Metadata-protocol:line from the PR body and ties the level to it. Measured on a throwaway merge of this head with origin/main, the PR reds on the level axis as it stands, and the only green combination contradicts the dispatch's 'BREAKING minor' ruling. Details and the measured fix are in open_questions.","tests": "Final head 1a28ccb (origin/main ae58b26 merged before the edits, merge 9dcf97c), all under the lock.
pnpm --filter @objectstack/service-datasource test:Test Files 53 passed (53),Tests 930 passed (930). The fixed file alone:Tests 9 passed (9). plugin-authvitest run:Test Files 180 passed (180),Tests 3422 passed | 10 skipped (3432); plugin-auth typecheck exit 0 (check:test-typecheck: OK). Ablation, on the committed fix c428137, through scripts/ablation-replace.mjs WRAP; the case reaches the producer through a relative src import, so no build sits between. Leg 1 was a NO-OP and its reading is void: it madeisSqliteFileOpenFailuretreat MODULE_NOT_FOUND as a file-open code (anchor 1 to 0, blob cd3fd64524ff to b863ecc7a63d) and the case stayed green (Tests 9 passed (9)). That classifier never sees the load error's own code: the probe failure reaches it as the driver's DATABASE_ERROR wrapper with the load error as its cause. Leg 2 re-anchored at the step-down decision itself:if (isSqliteFileOpenFailure(err)) throw err;became one that also re-throws whenerr.cause.codeorerr.codeis MODULE_NOT_FOUND (anchor 1 to 0, blob cd3fd64524ff to 1a2eac89b8b5). Result:Tests 1 failed | 8 passed (9); the renamed case failed with the driver's raw-statement refusal re-thrown instead of a wasm step-down, and the ERR_DLOPEN_FAILED case stayed green. Restored: blob cd3fd64524ff == HEAD,git diff HEADempty,git status --porcelainempty. Dist check for the changeset: afterpnpm --filter @objectstack/service-datasource build, the new never-built sentence is in dist/index.d.ts and dist/index.d.cts, and no dist file carries the old 'Could not locate the bindings file' text.","gates": "Final head 1a28ccb, each exit captured before any pipe:
node scripts/check-vendor-version-stamps.mjsexit 0 ('check:vendor-version-stamps: OK — 10188 file(s) scanned');pnpm check:vendor-export-contractexit 0 ('VERDICT: PASS — vendor export contract (installed workspace), 3 edge(s) verified');pnpm check:override-consistencyexit 0 (9 published-manifest declarations resolve to their targets);node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackexit 0, 108 families, all run under the lock, every exit 0;--ran: '108 derived famil(ies) accounted for — 108 run, 0 NOT-MEASURED (a DERIVED zero)'. Those gates run from this head's tree, which predates f2b6cf2. The level axis below was measured with origin/main's own scripts on a throwaway merge. CI check-runs for 1a28ccb: CI owns them; not read here.","dogfood_battery": "1a28ccb438 · os-verify-lock: VERDICT command-exit 0 · held the lock 767s · Test Files 286 passed | 3 skipped (289) · Tests 2387 passed | 18 skipped (2405) · no failures.
pnpm --filter @objectstack/dogfood test, no shard and no path, after a closure build (dogfood^..., cli..., service-datasource... and the packages check:dual-build-cjs-loads reads) that exited 0.","mcp_calls": "0 — no MCP GitHub tool was called",
"api_writes": "2 this round, within budget: (1) git push, fast-forward only, 8c4bf64 to 1a28ccb (two pushes of the same branch during the round: the merge plus the fix, then the changeset commit), not a REST write; (2) one relay stroke via POST /repos/objectstack-ai/objectstack/dispatches, op comment, which executed POST /repos//issues/25096/comments (this report). No PR body write, no label or assignee write.",
"notes": [
"The fixture edit also updated the test file's header clause that described the fixture ('...or the
bindingspackage's own not built error' became '...or the MODULE_NOT_FOUND better-sqlite3's own loader meets when its addon was never built'). It also dropped the now-unuseddirnameimport. Both are part of the never-built fixture. Nothing else in the file moved.","origin/main moved by one more commit (f2b6cf2) after my merge. Per AGENTS.md section 10 I did not chase it; a local merge-tree with it is clean. It is the commit behind the open question below.",
"The level-axis readings were taken in a throwaway detached worktree, holding this head merged with origin/main plus one local probe commit regrading the changeset. It was never pushed and was removed afterwards (with --force, because it held only that probe commit and its staged probe edit, both meant to be discarded)."
],
"open_questions": [
{
"question": "Main's f2b6cf2 (maintainer ruling A on #25105) now ties the changeset level to a new
Metadata-protocol:declaration, read line-initially from the PR body (and carried in the changeset body for check:adr-0087-registration):added,narrowedorsemantictakes at least minor;nonetakes patch, and a non-protocol breaking change ships patch while keeping its BREAKING banner and ADR-0087 disposition. This lift changes nothing a metadata author can write, so its kind isnone. Measured with origin/main's scripts on a throwaway merge of 1a28ccb with origin/main (merge base f2b6cf2). (1) The current PR body:check-changeset-no-major.mjs --base origin/main --eventexits 1, 'LEVEL AXIS: NOT MEASURED ... the PR body carries no Metadata-protocol: line'. (2) The body plus a line-startMetadata-protocol: none: exit 1, none-minor, 'metadata-protocol declares none while a changeset entry is graded minor or above: @objectstack/plugin-auth: minor'. (3) The body plus that line, with the changeset regraded to@objectstack/plugin-auth: patchand aMetadata-protocol: noneline added under its Clause-② line: exit 0, 'LEVEL AXIS: ... no (narrowing) and metadata-protocol none ... no changeset entry graded minor or above ... a non-protocol change at patch'. check:adr-0087-registration passes both ways. The dispatch ruled 'BREAKING minor' before ruling A existed. Which applies?","options": [
"A: follow ruling A. In the changeset, plugin-auth goes from minor to patch,
Metadata-protocol: noneis added under the Clause-② line, and the banner sentence 'shipped as minor under the launch-window convention' is replaced by one stating patch under ruling A. The BREAKING banner, FROM/TO, the ADR-0087 disposition and both DPoP halves stay. The seat adds a line-startMetadata-protocol: noneto the PR body underClause-②: no (narrowing). That red clears on the edited event with no push. The changeset change is one commit, path-explicit, within the surface. Cost: three changeset lines plus one PR-body line. Only .changeset/ moves, so under the seat's round-2 rule the changeset gates re-run and no dogfood battery is re-owed. Work: small.","B: keep the dispatch's BREAKING minor and declare a kind to earn it (
Metadata-protocol: narrowed). Cost: a false declaration, since no authorable key, value or type is narrowed; the lift narrows auth endpoint answers, not the metadata protocol. Not recommended.","C: hold the PR until the maintainer rules on how in-flight breaking minors migrate to ruling A. Cost: the lift and its security fixes wait. Work: none now."
],
"recommendation": "A, because ruling A is the maintainer's newer word, scoped exactly to this case ('a non-protocol change, widening or breaking, ships patch and keeps its BREAKING banner and ADR-0087 disposition'). The measured combination is the only green one, and B would declare a metadata change that does not exist, the declared-but-false shape the four axes refuse. I have not made the changeset change: the dispatch's 'BREAKING minor' is an explicit ruling, and choosing between it and ruling A is the seat's."
}
],
"pr_body_append": [
"## Patch rounds 2 and 3: the Test Core 4/6 red (job
119207996576) and its fix","",
"- The failing test:
packages/services/service-datasource/src/__tests__/sqlite-driver-fallback-file-open-failure.test.ts, "an addon that was never built (no code) steps down to wasm SQLite",Error: Cannot find module 'bindings'. It reproduced at8c4bf6489b(Tests 1 failed | 929 passed (930)) and was green onorigin/mainae58b26f9c(Tests 930 passed (930)).","- Root cause: the fixture required
bindingsrelative tobetter-sqlite313.0.3, which does not depend on it: its only dependency isnode-addon-api, and it loads its addon with a plainrequire. Onmain,bindings@1.5.0existed only underbetter-sqlite3@12.11.1, the optional peer copy auto-installed for better-auth 1.7.3, and the test reached it through pnpm's hidden hoist. 1.7.12 dropped that peer (see Lockfile: what moved), so the phantom left. The producer was never affected:resolveSqliteDriverclassifies by error code, not message.","- Fix (round 3, seat ruling A1, claim amendment
6316873193):realNotBuiltFailure()now produces better-sqlite3 13's own never-built error: arequire()ofbuild/Release/better_sqlite3.node, made from the installed package, against a module root that was never built, which Node answersMODULE_NOT_FOUND. No phantom dependency. The case is renamed "an addon that was never built (MODULE_NOT_FOUND) steps down to wasm SQLite" and asserts that code, still driving the producer with a real load error. The twosqlite-driver-fallback.tsdocblock sentences that namedbindings' "Could not locate the bindings file" now describe the 13.x error; no code moved.@objectstack/service-datasourcerides the changeset aspatch, text only (the docblock ships indist/index.d.ts).","- Ablation (on
c428137808,scripts/ablation-replace.mjsWRAP, relativesrcimport, no build between). A first leg atisSqliteFileOpenFailurewas a no-op for this case: the classifier sees the driver'sDATABASE_ERRORwrapper, not the load error's code. Re-anchored at the step-down decision (if (isSqliteFileOpenFailure(err)) throw err;also re-throwing on aMODULE_NOT_FOUNDcode or cause):Tests 1 failed | 8 passed (9), the renamed case red and theERR_DLOPEN_FAILEDcase green. Restore: blobcd3fd64524ffequals HEAD andgit diff HEADis empty.","- Final head
1a28ccb438(origin/mainae58b26f9cmerged, merge9dcf97cae7): service-datasourceTest Files 53 passed (53),Tests 930 passed (930); plugin-authTest Files 180 passed (180),Tests 3422 passed | 10 skipped (3432),typecheckexit 0;check-vendor-version-stampsOK;check:vendor-export-contractPASS;check:override-consistencygreen;dispatch-gates.mjs --commands108 derived, 108 run, 0 NOT-MEASURED, every exit 0; full dogfood batteryTest Files 286 passed | 3 skipped (289),Tests 2387 passed | 18 skipped (2405)."],
"out_of_scope_findings": []
}
Generated by Claude Code
objectstack-fleet commentedon Oct 21, 2026
Seat ruling on PR #25109's round-3 open question ·
domain:servicesseat 1 ·session_011TdZ8GjixNZFLz1xJD9zzK· 2026-10-21T14:01ZA. The maintainer's ruling A on #25105, landed as PR #25114 (
f2b6cf230b, 13:08Z), is newer than this card's dispatch and governs it.AGENTS.mdnow reads: "a non-protocol change, widening or breaking, shipspatchand keeps its BREAKING banner and ADR-0087 disposition". This lift changes nothing a metadata author can write, so its kind isMetadata-protocol: none.@objectstack/plugin-authmoves fromminortopatch, and a line-startMetadata-protocol: nonegoes under itsClause-②: no (narrowing)line. The banner sentence statespatchunder ruling A. The BREAKING banner, FROM / TO, the ADR-0087 disposition and both DPoP halves stay.Metadata-protocol: noneunder theClause-②line.Clause-②: no (narrowing)stands. Only its "BREAKING minor, not the card's patch" sentence is superseded: the level ispatch, BREAKING-bannered.narrowedwould declare a metadata change that does not exist. Not C: the security fixes would wait on a question the maintainer has already answered..changeset/moves in this round, so the dogfood battery recorded on1a28ccb438stands, and the changeset gates are re-read.Generated by Claude Code
objectstack-fleet commentedon Oct 21, 2026
os-dev-report
{
"issue": 25096,
"status": "done",
"round": "patch round 4, changeset only (ruling A 6317990662: maintainer ruling A on #25105, f2b6cf2, AGENTS.md item 3, governs)",
"branch": "claude/issue-25096-better-auth-1-7-12",
"pr": "#25109",
"session": "session_011TdZ8GjixNZFLz1xJD9zzK — the parent's harness-stamped id (subagent run)",
"premise_still_valid": true,
"summary": "The changeset is regraded as ruled, in one commit ea86e48 that touches only .changeset/25096-better-auth-1-7-12.md. @objectstack/plugin-auth moves from minor to patch, while spec, cli and service-datasource stay patch. A line-start
Metadata-protocol: nonesits directly underClause-②: no (narrowing). The banner sentence now reads 'shipped aspatchunder ruling A: it changes nothing a metadata author can write, and a non-protocol breaking change shipspatchwhile keeping this BREAKING banner and its ADR-0087 disposition.' The BREAKING banner, every FROM / TO, the ADR-0087 marker and both DPoP halves are byte-unchanged; the full diff is those three lines. origin/main was not merged, as ruled. Both changeset gates exit 0 with origin/main's scripts against the live PR body.","tests": "No test or code moved this round, so none was re-run; the round-3 readings on 1a28ccb stand (service-datasource 53/53 files and 930/930 tests, plugin-auth 180/180 files, dogfood 286 passed | 3 skipped files).
git diff --name-only 1a28ccb438 ea86e48563names only .changeset/25096-better-auth-1-7-12.md.","gates": "Changeset gates, read with origin/main's own scripts on a throwaway merge of ea86e48 with origin/main f2b6cf2 (detached, never pushed,
pnpm install --frozen-lockfileunder the lock, removed after with a clean tree). The event is the live PR body, fetched from REST, carrying line-startClause-②: no (narrowing)andMetadata-protocol: none.node scripts/check-changeset-no-major.mjs --base origin/main --event EVENTexit 0: '✓ This diff introduces nomajorbump.', '✓ LEVEL AXIS: this PR declares clause-②no (narrowing)and metadata-protocolnone, and it introduces no changeset entry gradedminoror above and moves no metadata-protocol baseline — a non-protocol change atpatch, its BREAKING banner and ADR-0087 disposition (if any) untouched', '✓ Protocol lockstep: PROTOCOL_VERSION major 18'.node scripts/check-adr-0087-registration.mjs --base origin/mainexit 0: '✓ check-adr-0087-registration: 1 declared-breaking changeset(s), each carrying an ADR-0087 disposition.' On the head ea86e48:node scripts/check-vendor-version-stamps.mjsexit 0, 'check:vendor-version-stamps: OK — 10187 file(s) scanned.' CI check-runs for ea86e48: CI owns them; not read here.","dogfood_battery": "n/a this round — only .changeset/ moved; the round-3 battery on 1a28ccb stands (Test Files 286 passed | 3 skipped (289), Tests 2387 passed | 18 skipped (2405), lock verdict command-exit 0)",
"mcp_calls": "0 — no MCP GitHub tool was called",
"api_writes": "2 this round, within budget: (1) one fast-forward git push, 1a28ccb to ea86e48, not a REST write; (2) one relay stroke via POST /repos/objectstack-ai/objectstack/dispatches, op comment, which executed POST /repos//issues/25096/comments (this report). No PR body write, no label or assignee write. Reads only otherwise (GET of the PR for its live body).",
"open_questions": [],
"pr_body_append": [
"- Patch round 4 (ruling A,
6317990662):ea86e48563regrades@objectstack/plugin-authfromminortopatchunder the maintainer's ruling A (a non-protocol breaking change shipspatchand keeps its BREAKING banner and ADR-0087 disposition) and addsMetadata-protocol: noneunder the changeset'sClause-②line; spec, cli and service-datasource staypatch, and nothing outside.changeset/moved. With origin/main's scripts against this body:check-changeset-no-major.mjsexit 0 (level axis:noneatpatch) andcheck-adr-0087-registration.mjsexit 0;check-vendor-version-stamps.mjsexit 0 on the head."],
"out_of_scope_findings": []
}
Generated by Claude Code
objectstack-fleet commentedon Oct 21, 2026
ACCEPT → PR #25109 comment
6318873521·domain:servicesseat 1 ·session_011TdZ8GjixNZFLz1xJD9zzK· 2026-10-21T14:45ZGenerated by Claude Code
fix(plugin-auth)!: lift the better-auth family from 1.7.3 to 1.7.12 (#…
objectstack-fleet commentedon Oct 21, 2026
Landed ·
domain:servicesseat 1 ·session_011TdZ8GjixNZFLz1xJD9zzK· 2026-10-21T15:22Zadded_to_merge_queue2026-10-21T14:49Z →merged2026-10-21T15:21Zorigin/mainby content03966ceae0(one parent,386f60f8fb), 24 files.plugin-auth/package.jsoncarries the five family ranges at exact1.7.12State change in this stroke:
pm:dispatchedoff and the assignee cleared. The card closedcompletedon the merge.What changes: the better-auth family moves from 1.7.3 to 1.7.12 (16 exact pins and a regenerated lockfile), carrying the security fixes published since, GHSA-965c-763c-88jm among them. It ships as a BREAKING-bannered
patchwithMetadata-protocol: none, under the maintainer's ruling A on #25105. The operator notes are in the changeset: in-flight Magic Link, OAuth and SAML flows restart, and an OIDC SSO issuer must match its discovery document exactly. Contract review6318796768(PASS), ACCEPT6318873521.Follow-ups, all filed for triage: #25111 (SSO domain verification), #25112 (Magic Link lifetime text) and #25123 (
init.tsscaffold peer ledger,domain:cli).Generated by Claude Code