Repository navigation
fix(client): the anonymous /get-session statements say 401 UNAUTHENTICATED, and the test double stops modelling 200 null - #18642
Conversation
… the shipped 401 #17881 (374d9d3) moved the anonymous `GET /api/v1/auth/get-session` wire answer from `200` + the literal `null` to the declared ADR-0112 refusal envelope (401, `UNAUTHENTICATED`). Three present-tense statements in the SDK and one test double still described the retired shape. - `normalizeSessionResponse`'s `/auth/*` wire table no longer lists the anonymous `200 null` row among the bodies the helper is handed; the current answer is stated separately, with the reason it never reaches the lift. - The closing `!body`-guard paragraph no longer claims that guard carries the anonymous answer. - `auth.me()`'s docblock says the anonymous call REJECTS rather than resolving outside the declared type. - `organization-get-active-member-addressing.test.ts`: the `signedIn: false` leg modelled an answer the runtime stopped producing, so it could not fail for the reason it existed. The double now serves the refusal envelope and case ⑥ pins that the refusal arrives on request ONE, discriminating on the code (`UNAUTHENTICATED` vs `list-members`' `UNAUTHORIZED`). Claude-Session: https://claude.ai/code/session_01DvvamiacK328idtBYJBxV3 Co-authored-by: Claude <noreply@anthropic.com>
…ssion statements The corrected `auth.me()` TSDoc is emitted into the shipped declarations (`dist/index.d.ts`, `dist/index.d.mts`) and into `dist/index.js` / `dist/index.mjs`, all under the package's published `files[]`, so this publishes and cannot take `skip-changeset`. Measured on the built artifact with `getActiveMember` as the lit control. Claude-Session: https://claude.ai/code/session_01DvvamiacK328idtBYJBxV3 Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 1 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 — 15 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 efb84a03e18a2da1aaab70b88cf4b5419d699d74 && git checkout efb84a03e18a2da1aaab70b88cf4b5419d699d74
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 32be735e5bdfa3be0211fb07d9347ed618ad1881 81a05648d87e408f152efc9b67f7b164f7f6faf5 && git checkout -B drift-repro 32be735e5bdfa3be0211fb07d9347ed618ad1881 && git merge --no-ff 81a05648d87e408f152efc9b67f7b164f7f6faf5
node scripts/docs-audit/affected-docs.mjs --json 32be735e5bdfa3be0211fb07d9347ed618ad1881
|
…UTHENTICATED on request one, and a pin holds them there (objectstack-ai#18810) Fixes objectstack-ai#18651 Clause-②: no `objectstack-ai#17881` (`374d9d3afa`) landed `plugin-auth`'s `refuseAnonymousSession`, which converts better-auth's `200` + the literal JSON `null` on `GET /api/v1/auth/get-session` into the declared ADR-0112 refusal envelope — HTTP `401`, `code: UNAUTHENTICATED` — before it leaves the process. Three present-tense statements in and around `organizations.getActiveMember` still described the retired shape, wrong on two axes at once: the CODE (`UNAUTHORIZED` vs `UNAUTHENTICATED`) and the REQUEST the refusal arrives on (the second, `list-members`, vs the first, `/get-session` itself). `packages/client/src/index.ts` changes **comments only** — verified mechanically: of every line the diff touches in that file, **zero** are outside a comment. ## The three, located by SYMBOL | the statement | what it taught | what it says now | |:--|:--|:--| | step 1 of the two-request list | the retired VALUE — "the literal `null` for an anonymous one" | the signed-in arm keeps its `(measured)` tag; the anonymous answer is stated separately from the producer, including that step 2 never reaches the wire | | the anonymous bullet of the 2026-09-09 drive's delta list | the retired CODE — "still gets `401 UNAUTHORIZED`, thrown from the `list-members` request" | RE-ANCHORED, not restamped: the drive's own row stays in the past tense and today's answer is stated from the producer — the disposition objectstack-ai#18642 used on this family's sibling statements | | the inline comment on the `userId` read | the retired VALUE and the wrong REQUEST | an anonymous caller never reaches that line; the `\| null` annotation and the `?? ''` fallback are explained as the defensive branch they always were | ## ⭐ The seat's "naming is not teaching" fence SURVIVED contact, and is load-bearing The repaired prose still contains `200`, `null` and `UNAUTHORIZED` — it has to, because it names the retired convention as the thing that was CONVERTED and as the drive row that was SUPERSEDED. A "mentions both 200 and null" filter reads the repaired file as defective. So the pin does not count mentions: it matches the three retired SENTENCES and proves it can see them by running the same matchers over a verbatim pre-repair control corpus. The fence earned its keep on a real near-miss, below. ## The complete-set question the card left open — answered with a reading The card recorded that it had ⛔ not swept for sibling statements. Every `anonymous` / `UNAUTHORIZED` / `UNAUTHENTICATED` hit in `packages/client/src` was opened, not counted: - `index.ts` `normalizeSessionResponse` docblock and `auth.me()` — already repaired by objectstack-ai#18642. Correct today. Untouched. - `index.ts` security-admin docblock ("Anonymous callers are denied unconditionally server-side") and the `/ai/*` docblock ("An anonymous caller is refused 401 first") — different routes, **true under both wire answers**. Untouched. - `index.ts` `adoptRotatedSessionToken` — `401 UNAUTHORIZED` (objectstack-ai#16534) is a DELETED session presenting a stale bearer token on a non-`/get-session` route. Not this seam. Untouched. - `organization-get-active-member-addressing.test.ts` transcript row `200 null`, marked `SUPERSEDED, see below` in the file's own header, and the `list-members` `401 UNAUTHORIZED` arm that objectstack-ai#18642 deliberately KEPT because it discriminates on a DIFFERENT code. Naming, not teaching. Untouched. - ⭐ `auth-rotated-session-token.test.ts` `principalFor` — "`null` for anonymous, never a status code". **This is the near-miss.** A mention filter flags it; a reading clears it. It describes `auth.api.getSession()`, better-auth's **JS API**, and `anonymous-session-refusal.ts`'s own module header rules that seam out in as many words: *"⛔ It does not touch better-auth's JS API. `auth.api.getSession()` … still answers `null` for an anonymous caller, because that is a function return value and not an HTTP answer. Only the wire shape moves."* TRUE today. Untouched. ⇒ **Three is three.** Three statements TEACH the retired convention; every other candidate in the package either is already repaired, is true under both wire answers, or names the convention as retired. ## The pin — FAILS before, passes after `packages/client/src/organization-get-active-member-anonymous-statements.test.ts`. A source-text assertion over the `getActiveMember` region, located by SYMBOL and never by line number, with its matchers shown FIRING against a verbatim pre-repair control corpus — a matcher that has never fired cannot tell "absent" from "unmatchable", which is how this family's own defect was nearly written off (the filing seat's grep for `Anonymous -> null` missed the file's Unicode arrow). **Ablation** — one-off, fix committed first, mutation proved on disk, restored and proved restored, under a `trap … EXIT INT TERM` with absolute paths. This pin reads `packages/client/src/index.ts` as TEXT through `readFileSync` on `./index.ts`; it does not resolve its subject through `dist`, so no dist preflight applies and the on-disk proof is the grep pair plus the blob hash. - **Mutation** — `git checkout HEAD^ -- packages/client/src/index.ts`. On-disk proof: corrected-sentence count `1 -> 0`, retired-sentence count `0 -> 1`, blob `102b25a4d` (HEAD) `->` `2f920cf4f` (mutated). Not a no-op. - **RED leg** — exit 1, `Tests 7 failed | 1 passed (8)`. It fails on all three retired matchers AND on all four "states today's answer" assertions. The one PASS is section 1, which only asserts the region was found — by design, so a red here cannot be mistaken for a broken locator. - **Restore leg** — `git checkout HEAD -- packages/client/src/index.ts` (point-named `HEAD`, never a bare `git checkout --`, which would take the mutation back out of the index). Restored blob `102b25a4d` **equals** the HEAD blob; `git diff HEAD` on that path is **empty**. - **GREEN leg** — exit 0, `Tests 8 passed (8)`. ## Readings Local runs, at `fdca8ef2be` unless noted. Heavy runs went through `scripts/pm/os-verify-lock.sh`;⚠️ every wall-clock absolute below is a SHARED-BOX figure. | run | verdict | |:--|:--| | `pnpm --filter '@objectstack/client^...' build --concurrency=2` (dependency closure) | **exit 0** | | `pnpm --filter @objectstack/client build` | **exit 0** (`check-dts-emitted: 1/1 declaration file(s) present`) | | `pnpm --filter @objectstack/client typecheck` (`tsc --noEmit` + `check:test-typecheck`) | **exit 0** — test layer compiles, 0 files / 0 errors / 0 pinned signatures | | `pnpm --filter @objectstack/client test` (whole package) | **exit 0** — `Test Files 46 passed (46)`, `Tests 545 passed (545)`. 46 is the whole on-disk population (`tests/integration/**` is excluded by this package's config; 1 file there); the new pin is one of the 46 | | ablation RED / GREEN | **exit 1** (`7 failed \| 1 passed`) / **exit 0** (`8 passed`) | | `eslint . --no-inline-config` repo-wide | **exit 0** — **6840** files linted (count read from `--format json`), **0 errors, 0 warnings**. Both changed files are in that population. Ran at `fdca8ef2be`, the final commit; 101s wall | | `scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` | 59 derived families, **59 run, 0 UNRUN** — reconciled back through `--ran` with exit codes recorded | Every exit code above was captured BEFORE any pipe (`cmd > file 2>&1; EXIT=$?`). ### Gate families — the two that are not a plain green - `pnpm check:dual-build-cjs-loads` — **exit 3**, its own PREREQUISITE-NOT-MET code: *"Run `pnpm build` first. ⛔ This is NOT a pass: nothing was measured."* A repo-wide build is CI's run, not this card's. **NOT MEASURED**, declared. `dispatch-gates --ran` classified it the same way from the recorded code. - `pnpm --filter @objectstack/spec run check:skill-examples` — **exit 1 on the sweep pass, exit 0 on a clean re-run** (`258 prose examples type-check across 3 surface(s)`). Reported as observed rather than as a single number. This gate does read `@objectstack/client` TSDoc examples; the changed docblocks carry no marked code block, so the population is unchanged. ### Tier reading, BOTH directions The dispatch warned that `packages/cli/vitest-tiers.ts` treats `new ObjectQL(` as a KERNEL signal and that a new pin can move a whole file out of its tier. **Measured, and it does not reach this card** — see the falsification below. - **Into `packages/cli`'s tiers:** its population is derived by `testFilesOnDisk(pkgRoot)` walking `packages/cli`. Measured: **262** files, **49** integration / **213** unit. The new pin is **absent**; no `packages/client` path is in that population at all. ⭐ Control lit — a real `packages/cli` file (`src/adr-0048-app-split.test.ts`) IS in it, so the enumeration was pointed at something. - **Inside `packages/client`:** there is **no tier partition to move within**. `packages/client/vitest.config.ts` has **0** occurrences of `vitest-tiers`, `integrationTestFiles`, `unitTestFiles` or `projects`; its only split is `exclude: ['tests/integration/**']`, and the pin is under `src/`. Separately, the pin contains **0** occurrences of `new ObjectQL(`. ⭐ Control lit — `new ObjectQL(` DOES occur in three sibling files in that same directory (`auth-get-session-envelope.test.ts` among them), so the grep can find it. - `pnpm check:tier-file-adoption` — **exit 0**. ### The changeset question was MEASURED, not inferred `AGENTS.md:1064-1066` — `skip-changeset` is for a diff that publishes nothing from any released package. `@objectstack/client`'s `files[]` is `["dist","README.md","CHANGELOG.md"]`, and `getActiveMember` is a member of the exported `ObjectStackClient`, so its TSDoc is emitted into the shipped artifacts. Measured on the built `dist` at `13e09a5e3c`: | artifact | corrected sentence | retired sentence | lit control (`getActiveMember`) | |:--|:--|:--|:--| | `dist/index.d.ts` | 1 | 0 | 2 | | `dist/index.d.mts` | 1 | 0 | 2 | | `dist/index.js` | 1 | 0 | 3 | | `dist/index.mjs` | 1 | 0 | 3 | ⇒ this publishes ⇒ **`patch` changeset, `skip-changeset` refused by measurement.** ##⚠️ Four premises falsified 1. **The card's line numbers were already stale.** `:3818` / `:3847` / `:3880` at filing are `:3833` / `:3862` / `:3895` at `origin/main` — a uniform **+15** shift. Everything here is located by symbol. 2. **The card's ⛔-unverified `:4494` is not a fourth defect.** Under that same +15 it resolves to `auth.me()`'s `⚠️ Anonymous REJECTS` docblock — the statement PR **objectstack-ai#18642 already repaired**. It is correct today. The card's open question is closed, and nothing is owed there. 3. **`*.d.cts` is the wrong second artifact for this package.** The dispatch's fence said TSDoc ships into `dist/*.d.ts` **and** `*.d.cts`. `@objectstack/client` emits **no `.d.cts` and no `.cjs`**: its CJS pair is `index.js` + `index.d.ts` and its ESM pair is `index.mjs` + `index.d.mts`. A `*.d.cts` probe here would have measured an **absent file** and returned a zero about nothing. The measurement above names the four artifacts that exist. 4. **The tier warning does not reach this card.** `vitest-tiers.ts` exists **only** in `packages/cli` — it is the single `vitest-tiers*` file in the repo outside that package's own fixtures and partition pin. `packages/client` has no tier system, so no pin placed there can move a file between tiers. Measured both directions above rather than argued. ## Acceptance notes — out of scope, noted, not filed - `packages/client/src/auth-rotated-session-token.test.ts`, `principalFor`'s docblock describes an `auth.api.getSession()` call in HTTP wording ("a 200 and a JSON `null`") for a call that never goes over HTTP. The claim it is making is TRUE — the JS API returns `null` and no status, which is exactly why a status assertion is blind there — so this is wording looseness in a test file that publishes nothing, ⛔ not an error and ⛔ not a defect class. **Carrier: the next PR that touches that file**, which is objectstack-ai#16534's territory and where the wording lives. - `getActiveMember`'s `@throws` tag lists the falsy-id throw and the 200-with-no-row throw and does not list the anonymous rejection. That gap **predates objectstack-ai#17881** — an anonymous caller threw before it too, through the `list-members` 401 and the same shared `fetch` wrapper — so objectstack-ai#17881 did not falsify it. It is a missing member, ⛔ not an error, and adding to a published `@throws` tag is a contract statement this card's fence does not cover. **Carrier: none known.** Neither meets class (a), (b) or (c), so neither is filed. --- _Generated by [Claude Code](https://claude.ai/code/session_01DvvamiacK328idtBYJBxV3)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #18139
#17881(374d9d3afa) moved the anonymousGET /api/v1/auth/get-sessionwire answer from200+ the literal JSONnullto the declared ADR-0112 refusal envelope — HTTP401,code: UNAUTHENTICATED.@objectstack/clientreaches the server over the wire, so that is exactly what it sees. This PR pulls the SDK's own statements and one test double back onto it. No behaviour moves:packages/client/src/index.tschanges comments only (verified mechanically — every changed line in that file is inside a comment).Half 1 — the SDK's statements
Three present-tense statements, none carrying a rev or a date, so none read as history:
/auth/*wire table onnormalizeSessionResponse(located by symbol, not by line). Its anonymous-> 200 nullrow is removed from the list of bodies the helper is handed — it is not handed that body at all now, because the sharedfetchwrapper throws on the non-2xx one frame out. The current answer is stated separately, anchored to the producer.!body-guard paragraph in the same docblock, which claimed that guard "carries the anonymous answer" and that closing the gap "needs the published return annotation to widen".#17238ruled the opposite: the producer moved andSessionResponseSchemais untouched. The guard's own code is byte-identical; it is now described as the defensive branch it is.auth.me()'s docblock, which said the route "serves the literalnullat 200 and it is returned as-is". It now says the anonymous call REJECTS, with the code and status a caller catches.Half 2 — the phantom, which is the half that mattered
Triage's grading is quoted verbatim and is what this half answers:
The disposition chosen: assert the refusal, not drop the leg — justified from the tree. The file's own header states its charter: "Only the socket is faked — every status, code and row shape below is a transcript line from the drive". Under that charter the anonymous row is not optional, it is stale, so it is re-anchored rather than deleted. Dropping the leg would delete the only coverage that
getActiveMemberdoes not invent a client-side diagnostic for an anonymous caller — a property that is still real and is now sharper, because the refusal arrives on request ONE instead of request TWO.What changed in the fixture:
/get-sessionarm serves401 {"success":false,"error":{"code":"UNAUTHENTICATED","message":"Sign in first"}}— shaped from the producer (refuseAnonymousSessionderives the code throughstandardErrorCodeForHttpStatus(401)and takes the message fromPLATFORM_ADMIN_REFUSAL_MESSAGES[401]), not restamped onto a drive that was never re-run. The header says so in as many words; every other row is still the 2026-09-08 drive, untouched.code: UNAUTHENTICATED+httpStatus: 401and pinsurlsas the WHOLE list[AUTH + '/get-session'], so a silent extra request cannot hide behind a length check.401 UNAUTHORIZEDarm is KEPT although the SDK no longer reaches it, and that is load-bearing: it answers a different code from the/get-sessionseam, so a regression that swallowed the first refusal and walked on fails on the VALUE, not merely on a request count. Case ⑥ drives it directly as a guard-the-guard, in the file's existing ⑦/⑧ style.Ablation — the proof it can now fail for the reason it exists
One-off, committed first, mutation proved on disk, restored and proved restored.
return json(200, null);. On-disk proof: refusal-arm grep count1 -> 0, retired-arm grep count0 -> 1, blob48459424a(HEAD) vs19eb96dcb(mutated) — not a no-op.Tests 1 failed | 7 passed (8)—⑥ ... AssertionError: expected 'UNAUTHORIZED' to be 'UNAUTHENTICATED'. It fails on the CODE, which is the discrimination this design is for.git checkout HEAD -- pathunder anEXIT INT TERMtrap; restored blob48459424aequals the HEAD blob,git diff HEADempty,git status --porcelainempty.Test Files 1 passed (1) · Tests 8 passed (8).The premise the dispatch asked me to falsify — FALSIFIED
The card's dedupe was a recent-window search and said so. Swept repo-wide for
get-session,200 null, "literalnull" and "literal JSONnull" with controls (a token known present returned 8 hits; a nonsense token returned 0 at exit 1). The:1474table was not the only present-tense statement left. Six more were found; three are fixed here (the two above plus the guard paragraph) and three are listed in Acceptance notes because they are out of this PR's face.The card's declared-unmeasured item — now MEASURED
The card wrote: "Whether
auth.me()'s rejection path is covered anywhere today. I did not measure it." Measured: it IS covered, twice.packages/client/src/client.test.tscarries "me() rejects the anonymous refusal rather than resolving outside its type" assertingcode: UNAUTHENTICATED/httpStatus: 401, andpackages/client/src/auth-get-session-envelope.test.tsblock ③ ("anonymous is REFUSED") carries the real-server twin. Both were reversed by#17238and say so in their own comments. Nothing in this PR depends on that coverage; the reading is reported because the card asked for it.Clause-②
Clause-②: no. Three readings, from the measured diff:
packages/client/src/index.tschanges comments only; the test file and the changeset export nothing. The package's public surface is byte-identical.ERROR_CODE_LEDGER/StandardErrorCode: untouched — the diff names neither, at zero occurrences.UNAUTHENTICATEDis an existing STANDARD member that#17881already derives viastandardErrorCodeForHttpStatus; nothing is minted here..zod.tsin the diff (zero paths), no schema key moves, nothing previously admitted is refused and nothing new is admitted.The expected direction held: this is a pull-back — the runtime already answers 401 and the SDK's self-description was lagging. Nothing widens.
Tests
All readings taken in a dedicated worktree cut from
origin/mainat681ebe424; the union below was re-run at final HEAD81a05648d.pnpm --filter @objectstack/client typecheck— exit 0. Note the two halves: the roottsc --noEmitprogram does NOT include the edited test file (--listFilescount 0, withsrc/index.tsat 1 as the lit control); the test layer is covered by the script's second half,check:test-typecheck, which reports "@objectstack/client's test layer compiles under packages/client/tsconfig.test.json; 0 file(s) / 0 error(s)".pnpm --filter @objectstack/client test—Test Files 43 passed (43) · Tests 524 passed (524).pnpm --filter '@objectstack/client^...' build— exit 0 (the dependency closure; the first test run was a false red from an unbuilt@objectstack/spec, not from this diff).node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 59 families. Reconciled with--ran: 59 derived, 58 run, 1 NOT-MEASURED, 0 UNRUN.pnpm check:dual-build-cjs-loads— exit 3,PREREQUISITE NOT MET, 34 packages have nodist; it needs a fullpnpm build, which does not fit a foreground turn here. Its own text says "This is NOT a pass: nothing was measured." CI'sBuild Corecovers it.pnpm --filter @objectstack/spec run check:skill-examplesfirst exited 1 on the same prerequisite shape (packages/client-reactunbuilt). Built that package and re-ran: exit 0, 258 prose examples across 3 surfaces.pnpm check:cross-package-test-inputs— exit 1, and it is NOT from this diff. It flagspackages/cli/test/init-created-files-summary.e2e.test.tsdescending frompackages/spec/dist/. Control: that file andscripts/check-cross-package-test-inputs.mjsandturbo.jsonare all byte-identical toorigin/main(blob-for-blob), while a file this PR did change differs — so the verdict is a property oforigin/main, not of these two paths. The walk is a static source property (join(SPEC_PKG, 'dist')at that file's line 115). Filed below rather than fixed here.pnpm lintrepo-wide (eslint . --no-inline-config) — exit 0. Run whole, not narrowed, so no narrowing needs proving.81a05648d:check-empty-changeset,check-changeset-no-major,check-adr-0087-registration,check:objectui-changeset,check:pm-changeset-deadline-census,check:type-check-coverage,check:type-check-debt,check:published-files,check:tier-file-adoption,check:nul-bytes,check:test-source-alias— all exit 0.Changeset, measured rather than assumed
skip-changesetdoes not apply and was not requested.@objectstack/client's publishedfiles[]is["dist","README.md","CHANGELOG.md"], andauth.me()is a member of the exportedObjectStackClient, so its TSDoc is emitted into the shipped declarations. Measured on the built artifact: the corrected sentence is present indist/index.d.ts,dist/index.d.mts,dist/index.jsanddist/index.mjs; the retired sentence is absent fromdistafterwards;getActiveMembercarried as the lit control, found in the same four files. Hence apatchchangeset.Acceptance notes
Neighbour PR.
#18429(card#17274) also editspackages/client/src/index.tsand was open and unmerged when this branch was cut. Its measured hunks are at 3671-3689, 3962-3967, 3976-3983 (organizations.invitations.resend); this PR's edits are the/auth/*docblock near 1474 and theauth.me()docblock near 4437. The regions do not intersect — which guarantees textual mergeability and nothing more.Three same-defect statements deliberately NOT fixed here, because they sit in the
organizations.*region this PR is fenced out of. All three are ingetActiveMember's docblock and body inpackages/client/src/index.ts, and all three now misdescribe the runtime:{ user, session }envelope for a signed-in caller and the literalnullfor an anonymous one (measured)" — step 1 of the docblock's two-request explanation.401 UNAUTHORIZED, thrown from thelist-membersrequest by the same session middleware" — the refusal now comes from the/get-sessionrequest withUNAUTHENTICATED, one request earlier.null, and the request below is then refused 401 by the session middleware before the filter is ever read."They are the same defect class as this card and the fix is mechanical, but the in-place-fix criteria do not all hold:
packages/client/src/index.tsis held by another live claim (#18429), and the dispatch fences this PR out of that region by name. Handed back for filing with dedupe wordsgetActiveMember,get-session,anonymous,UNAUTHENTICATED,#17881. Reviewer, read this PR's case ⑥ together with that docblock: they now disagree, and the docblock is the one that is wrong.Two more, outside
packages/client, handed back for filing:docs/qa/platform-checklist/areas/identity-auth.json(two clauses) instructs a QA runner that "better-auth's no-session convention is HTTP 200 with a JSON null body, so a 401 expectation misdescribes a correct implementation" and thatget-sessionis not a valid oracle. Since#17881a 401 is exactly what a correct implementation answers, so a runner following this verbatim scores a correct platform as defective. It even citessession-of-record.test.tsas its authority — and that file was updated by#17238and now says the opposite in its own helper docblock. This is the highest-value item in the sweep. Dedupe words:identity-auth.json,get-session,oracle,JSON null body,revoke-user-sessions..changeset/client-get-session-envelope-and-refresh-read.md(pending, unreleased) closes with "Two answers stay outside the declared type and are not addressed here: the anonymousnull, which would need the published return annotation to widen" — present-tense, and it ships verbatim into@objectstack/client'sCHANGELOG.mdat the next release. Its transcript block above it is anchored to a dated better-auth 1.7.2 drive and reads as history; only the closing paragraph is the problem. Not touched here because it is another PR's release input. Dedupe words:changeset,get-session,anonymous null,return annotation,#16760.Noted, not filed —
packages/adapters/hono/src/hono-auth-mount-basepath.test.tscarries aGET /api/v1/auth/get-session -> 200 nullrow in an AFTER transcript. It is anchored to a dated maintainer ruling and a named drive, so it reads as a measurement record rather than a present-tense claim, and no assertion in that file depends on it. Who would carry it: that adapter's next#16025-area edit. If nobody touches that file, nobody is misled by it, because the file's own cases assert a relation and never that body.Cleanup: the worktree used for this work is removed; no dev server was started and no temporary artifact is left in the tree.
Generated by Claude Code