Repository navigation
docs(qa): re-point identity-auth's get-session clauses at the 401 refusal envelope, and pin them to it - #18742
Merged
os-support-ai merged 4 commits intoSep 17, 2026
Conversation
…usal envelope The five sites in `docs/qa/platform-checklist/areas/identity-auth.json` that taught better-auth's retired no-session convention (HTTP 200 + a JSON `null` body) were inverted by #17238/#17881: `refuseAnonymousSession` now converts that answer into `401` + `UNAUTHENTICATED` in the ADR-0112 refusal envelope. A runner following the file scored the correct implementation as defective, and the remedy the negative pointed at was undoing an auth tightening. Four instructional sites re-pointed (the step, clause 5's verify, the third negative, the evidence row). `revision 3` is left standing and unedited -- it records what the August judgement rested on -- and a new `revision 6` states that its cited authority was inverted afterwards. Measured on this checkout rather than relayed: live session 200 with { user, session }; after revoke-sessions, 401 UNAUTHENTICATED; fresh-manager control still 200. The refusal covers the REVOKED path, not only the never-signed-in one, because the seam keys on the answer shape. Claude-Session: https://claude.ai/code/session_01DvvamiacK328idtBYJBxV3 Co-authored-by: Claude <noreply@anthropic.com>
…ssion refusal envelope `checklist-refusal-envelope-consistency.test.ts` holds `docs/qa/platform-checklist/areas/*.json` equal to this package's own `anonymous-session-refusal.ts`: the status is read from `ANONYMOUS_SESSION_REFUSAL_STATUS` and the code is derived from it through ADR-0112's status map, so neither is spelled a second time. Three legs: ALIGNMENT (every site naming the seam states the status and code the runtime emits), ABSENCE (no instructional string teaches the retired `200` + JSON `null` convention, tree-scoped over every area file), and FLOOR (each instructional family still carries an aligned statement, so the first two cannot be satisfied by deleting the guidance). `history` is exempt from the absence leg by design -- a revision entry has to be able to quote what it corrected. It lives in `plugin-auth` because the next behaviour change is a diff here, which puts this package in the affected set; `check:platform-checklist` is deliberately not a per-PR gate. The escaping read is declared in `scripts/cross-package-test-inputs.mjs` with matching `turbo.json` inputs so neither scoping layer replays a cached green over it. Claude-Session: https://claude.ai/code/session_01DvvamiacK328idtBYJBxV3 Co-authored-by: Claude <noreply@anthropic.com>
`check:cross-package-test-inputs` went red naming `scripts/cross-package-test-inputs.mjs` as a path `checklist-refusal-envelope-consistency.test.ts` names but no glob covers. Same "named rather than read" class the plugin-auth entry already carries for `check-published-files.mjs` and `check-cross-package-test-inputs.mjs`, and settled the same way its own note prescribes: declare the file rather than reword the prose to dodge the collector. Mirrored into `turbo.json`. Claude-Session: https://claude.ai/code/session_01DvvamiacK328idtBYJBxV3 Co-authored-by: Claude <noreply@anthropic.com>
Contributor
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
…back tally `check-ci-filter-parity --self-test` holds a per-card ledger of the globs a rollback of `crosspkg` to its pre-#10015 list leaves uncovered, judged over the LIVE declaration table — the assertion's own note says a declaration under a new root "moves it and is recorded here by name". This branch added one, so the tally moves 21 -> 22 and gains its entry. Which of this branch's two new declarations moved it was MEASURED, not inferred: rebuilding the uncovered set against `origin/main`'s table and against this branch's gives 21 vs 22, and the single added member is `docs/qa/platform-checklist/areas/*.json`. The sibling declaration `scripts/cross-package-test-inputs.mjs` moves nothing — the rollback keeps `scripts/**`, which covers it. The by-name assertion therefore names the QA-checklist glob. Before: 1 of 47 assertions failed, verdict never reached. After: 48 assertions, verdict reached, success line printed. Claude-Session: https://claude.ai/code/session_01DvvamiacK328idtBYJBxV3 Co-authored-by: Claude <noreply@anthropic.com>
os-support-ai
marked this pull request as ready for review
September 17, 2026 18:44
This was referenced Sep 17, 2026
os-support-ai
deleted the
claude/issue-18650-identity-auth-checklist-retired-200-null
branch
September 17, 2026 19:12
This was referenced Sep 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #18650
docs/qa/platform-checklist/areas/identity-auth.jsonstill taught better-auth'sretired no-session convention — HTTP
200with a JSONnullbody — andinstructed a runner accordingly. Since #17881 landed
refuseAnonymousSession,the live answer is
401+UNAUTHENTICATEDin the ADR-0112 refusal envelope, sothe file asserted the opposite of the truth: it said a
401expectation"misdescribes a correct implementation", when an implementation answering
401is today the correct one. A runner following it recorded correct behaviour as a
defect, and the remedy its negative pointed at was undoing an auth tightening.
Clause-②: no
The two premises, measured here rather than relayed
① The count. Re-derived on today's
origin/mainby a structural walk ofevery
docs/qa/platform-checklist/areas/*.json(by JSON node, not by line), withcontrols in both directions: positive
get-session= 35 occurrences across thearea files, negative nonsense token = 0. The result is five sites, all in
identity-auth.json, all inside one item:items[4].steps[7]items[4].acceptance[4].verifyitems[4].negative[2]items[4].source[6]items[4].history[2].changeFive is what the card said and five is what the sweep found — reported because it
was measured, not because it was expected. The card's declared-unswept question
is answered in the same pass: 0 other area files carry the convention, and
grepoverRUNNER.md/README.md/SWEEP.md/FOLLOW-UPS.mdfinds noneeither, so nothing else needs filing.
Deliberately not counted among the five:
get-sessionstatements at othersites that describe behaviour without teaching the convention (a post-sign-out
read "no longer returns the user"; a pre-2FA read "does not return an
authenticated user"; the post-remove avatar read). Those stay true under both
wire answers.
② The behaviour. Driven on this checkout through a real
AuthManageroverthe in-memory engine this package's better-auth suites use, because the card's
first declared act was to drive the revoked path specifically — #17881 is
described in terms of an anonymous caller and nobody had checked they were the
same door:
They are the same door.
refuseAnonymousSessionkeys on the answer shape —the
/get-sessionpath, status200, and a body that is exactly the literalnull— never on how the caller became anonymous, so "never signed in","unknown cookie" and "revoked session" all convert. The control on a separate
manager rules out the revoke having poisoned the harness. The probe was a
throwaway; it is not in this diff.
What changed
Four instructional sites re-pointed at the live answer. The substantive
advice is kept and re-founded rather than deleted:
get-sessionis still notthis clause's oracle, but no longer because its status cannot discriminate (it
now can) — because the clause's contract is an immediate kill on a PROTECTED
request, and one auth-route seam's status is not proof the authorization layer
refuses the target's in-flight protected reads. That reason does not move with
the wire.
The negative was the most dangerous of the five and is now explicit in both
directions: a
401after the revoke is the correct answer and must never befiled as the defect, while a
200carrying a user after the revoke is no longerthe benign convention this line once described — it is the live-session answer,
so it IS a real signal the kill did not land.
revision 3is left standing, unedited. It was right when it was written;its cited authority (
session-of-record.test.ts) was inverted underneath itafterwards and now pins the very
401it was cited for denying. Rewriting ordeleting it would erase what the August judgement rested on, which is the whole
point of a revision history. A new
revision 6says so, and the item'srevisionfield moves 5 to 6 (check:platform-checklistholds those equal).The pin, and the proof it can go red
packages/plugins/plugin-auth/src/checklist-refusal-envelope-consistency.test.tsholds the checklist equal to the runtime's refusal envelope. The status comes
from
ANONYMOUS_SESSION_REFUSAL_STATUSand the code is derived from itthrough ADR-0112's own status map — neither is spelled a second time, so the pin
cannot agree with a runtime that has moved.
It lives in
plugin-authon purpose. The next behaviour change is a diff in thatpackage, which puts it in the affected set;
check:platform-checklistisdeliberately not a per-PR gate (its own header records that ruling), so a pin
sitting beside the docs would be judged by where the docs live. The escaping read
into
docs/qa/platform-checklist/areas/is declared inscripts/cross-package-test-inputs.mjswith matchingturbo.jsoninputs, soneither of CI's scoping layers replays a cached green over it.
Three legs, because any one alone is satisfiable by a file that says nothing:
ALIGNMENT (every site naming the seam states the runtime's status and code),
ABSENCE (no instructional string in any area file teaches the retired
convention), FLOOR (each instructional family still carries an aligned
statement).
historyis exempt from the absence leg by design — a revision entryhas to be free to quote what it corrected, which is exactly what this card was
told to preserve.
Ablation, two legs, mutation shown on disk before each run and the restore
verified by blob hash, never by exit code:
Leg A also settles the resolution question the dist-preflight rule exists for:
the mutated constant is reached through a relative source import
(
./anonymous-session-refusal), not through a dependency'sexports, so norebuild sits between the mutation and the red — and the red arriving without one
is the evidence, not an assumption. Leg B's subject is a JSON file read at
runtime, with no build in its path at all.
Tier
Measured, not assumed:
packages/plugins/plugin-authrunsvitest runwith noproject partition and has no
vitest-tiers.ts(the only one in the repo ispackages/cli's). The new pin therefore lands in the package's single tier andis covered by the ordinary
pnpm --filter @objectstack/plugin-auth testrunbelow — nothing moved out of a tier, and no existing pin changed tier.
Verification
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 72 families; reconciled with
--rancarrying every exit code:72 derived, 71 run, 1 NOT-MEASURED, 0 UNRUN.
pnpm check:dual-build-cjs-loadsexit 3 = PREREQUISITE NOTMET (reads built output; 40 packages have no
dist/in this worktree). Not apass and not a red — CI's
Build Corejob owns it.diff rather than argued with:
check:cross-package-test-inputsexit 1 namingscripts/cross-package-test-inputs.mjsas a path the test names in prosewith no glob covering it (the "named rather than read" class this entry already
carries for two sibling scripts — declared, per its own note that declaring is
cheaper than rewording prose to dodge the collector), now exit 0; and
check:type-check-debtexit 3 until its three unbuilt workspace dependencieswere built, then exit 0 with
4 ledger entries re-measured, 53 raw tsc errors, none above its recorded number.pnpm lintrepo-wide (eslint . --no-inline-config, full population, nonarrowing): exit 0. Run although
dispatch-gatesdoes not name this family.pnpm --filter @objectstack/plugin-auth test: 112 files, 2365 tests passed.pnpm --filter @objectstack/plugin-auth typecheck: exit 0 —test layer compiles under tsconfig.test.json; 10 files / 94 errors / 23 pinned signatures held(unchanged ledger).pnpm check:platform-checklist: exit 0 —15 areas, 264 items; symbol anchors 577/633 resolved, 17 file floors held. The evidence row's anchor was swappedone-for-one (
session-of-record.test.ts#bodyout,anonymous-session-refusal.ts#ANONYMOUS_SESSION_REFUSAL_STATUSin), so theper-file floor is untouched.
pnpm --filter '@objectstack/plugin-auth^...' build), so nothing here was judged against a staledist.Landing surface and changeset
check-governed-merges.mjs --teston the final four-path file list: 0 of 4hit the register — NOT governed, ordinary queue landing. Control in the other
direction: the same tool answers 1 of 1 GOVERNED for
.claude/skills/pm-dispatch/SKILL.md.skip-changeset, measured rather than assumed.plugin-authpublishesfiles: ["dist","README.md","CHANGELOG.md"]; after building it, the new test'ssymbols (
checklist-refusal-envelope-consistency,RETIRED_CONVENTION,instructionalStrings) hit 0 across all three, while the positive controlANONYMOUS_SESSION_REFUSAL_STATUShitsdist/index.jsanddist/index.mjs.Across the 70 published packages, 0 name
scriptsinfiles[]and exactlyone names
src(packages/spec, which this diff does not touch);turbo.jsonis named by none. Positive control: all 70 name
dist.Acceptance notes
noted, not filed:the revoke leaves onesys_sessionrow behind — thedeliberate tombstone
session-tombstone.tswrites, not a leak. Named herebecause the probe printed the count and a later reader would otherwise have to
re-derive it.
noted, not filed:check:platform-checklistis not a per-PR gate. That is arecorded maintainer decision, stated in the script's own header, and it is the
reason this card's pin had to live in a package rather than beside the docs —
not a gap to file. Successor: whoever next moves a checklist invariant.
noted, not filed:five furtherget-sessionstatements in this file describebehaviour without teaching the convention and stay true under both wire
answers, so they were left alone rather than swept along. Successor: whoever
next re-points this item.
🤖 Generated with Claude Code
https://claude.ai/code/session_01DvvamiacK328idtBYJBxV3
Generated by Claude Code