Repository navigation
Commit f1c9bb3
docs(qa): re-point identity-auth's get-session clauses at the 401 refusal envelope, and pin them to it (#18742)
Fixes #18650
`docs/qa/platform-checklist/areas/identity-auth.json` still taught
better-auth's
retired no-session convention — HTTP `200` with a JSON `null` body — and
instructed a runner accordingly. Since #17881 landed
`refuseAnonymousSession`,
the live answer is `401` + `UNAUTHENTICATED` in the ADR-0112 refusal
envelope, so
the file asserted the **opposite** of the truth: it said a `401`
expectation
"misdescribes a correct implementation", when an implementation
answering `401`
is 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/main` by a structural
walk of
every `docs/qa/platform-checklist/areas/*.json` (by JSON node, not by
line), with
controls in both directions: positive `get-session` = 35 occurrences
across the
area files, negative nonsense token = 0. The result is **five** sites,
all in
`identity-auth.json`, all inside one item:
| node path | field |
|---|---|
| `items[4].steps[7]` | the runner instruction |
| `items[4].acceptance[4].verify` | clause 5's oracle |
| `items[4].negative[2]` | the negative |
| `items[4].source[6]` | the evidence row |
| `items[4].history[2].change` | revision 3 |
Five 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
`grep` over `RUNNER.md` / `README.md` / `SWEEP.md` / `FOLLOW-UPS.md`
finds none
either, so nothing else needs filing.
Deliberately **not** counted among the five: `get-session` statements at
other
sites 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
`AuthManager` over
the 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:
```
sign-up -> 200 cookie issued
GET /get-session (live) -> 200 {"user":{...},"session":{...}}
POST /revoke-sessions -> 200 {"status":true}
GET /get-session (revoked) -> 401 {"success":false,"error":{"code":"UNAUTHENTICATED","message":"Sign in first"}}
GET /get-session (fresh mgr) -> 200 [control, on its own engine]
```
They are the same door. `refuseAnonymousSession` keys on the **answer
shape** —
the `/get-session` path, status `200`, and a body that is exactly the
literal
`null` — 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-session` is
still not
this 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 `401` after the revoke is the **correct** answer and must
never be
filed as the defect, while a `200` carrying a user after the revoke is
no longer
the benign convention this line once described — it is the live-session
answer,
so it IS a real signal the kill did not land.
`revision 3` is left standing, **unedited**. It was right when it was
written;
its cited authority (`session-of-record.test.ts`) was inverted
underneath it
afterwards and now pins the very `401` it was cited for denying.
Rewriting or
deleting it would erase what the August judgement rested on, which is
the whole
point of a revision history. A new `revision 6` says so, and the item's
`revision` field moves 5 to 6 (`check:platform-checklist` holds those
equal).
## The pin, and the proof it can go red
`packages/plugins/plugin-auth/src/checklist-refusal-envelope-consistency.test.ts`
holds the checklist equal to the runtime's refusal envelope. The status
comes
from `ANONYMOUS_SESSION_REFUSAL_STATUS` and the code is **derived** from
it
through 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-auth` on purpose. The next behaviour change is a
diff in that
package, which puts it in the affected set; `check:platform-checklist`
is
deliberately 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 in
`scripts/cross-package-test-inputs.mjs` with matching `turbo.json`
inputs, so
neither 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). `history` is exempt from the absence leg by design — a
revision entry
has 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:
```
HEAD blob checklist = 4d00eca
HEAD blob seam = ba3aeb8
LEG A simulate the next behaviour change (401 -> 403 at the seam)
on disk BEFORE: 'ANONYMOUS_SESSION_REFUSAL_STATUS = 401' x1
on disk AFTER : 401 x0 · 403 x1
pin exit = 1 FAIL "every site naming the refusal seam states the status and code the RUNTIME emits"
+ "items[4].steps[7]: missing status 403"
+ "items[4].steps[7]: missing code PERMISSION_DENIED"
+ "items[4].acceptance[4].verify: missing status 403" (and the rest)
restored, blob matches HEAD: ba3aeb8
LEG B revert the checklist to the RETIRED assertion
on disk BEFORE: '200-with-null-body' x0
on disk AFTER : '200-with-null-body' x1
pin exit = 1 FAIL "no instructional string teaches the retired 200-plus-null convention"
+ "items[4].negative[2]"
restored, blob matches HEAD: 4d00eca
RESTORED-TREE CONTROL pin exit = 0 ; git diff HEAD over both paths empty
```
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's `exports`,
so no
rebuild 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-auth` runs `vitest run`
with no
project partition and has no `vitest-tiers.ts` (the only one in the repo
is
`packages/cli`'s). The new pin therefore lands in the package's single
tier and
is covered by the ordinary `pnpm --filter @objectstack/plugin-auth test`
run
below — nothing moved out of a tier, and no existing pin changed tier.
## Verification
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack`
derived **72** families; reconciled with `--ran` carrying every exit
code:
**72 derived, 71 run, 1 NOT-MEASURED, 0 UNRUN.**
- NOT MEASURED: `pnpm check:dual-build-cjs-loads` exit **3** =
PREREQUISITE NOT
MET (reads built output; 40 packages have no `dist/` in this worktree).
Not a
pass and not a red — CI's `Build Core` job owns it.
- Two gates went red or refused on first contact and were repaired
inside this
diff rather than argued with: `check:cross-package-test-inputs` exit 1
naming
`scripts/cross-package-test-inputs.mjs` as a path the test *names* in
prose
with 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-debt` exit 3 until its three unbuilt workspace
dependencies
were built, then exit 0 with `4 ledger entries re-measured, 53 raw tsc
errors,
none above its recorded number`.
- `pnpm lint` **repo-wide** (`eslint . --no-inline-config`, full
population, no
narrowing): exit 0. Run although `dispatch-gates` does 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
swapped
one-for-one (`session-of-record.test.ts#body` out,
`anonymous-session-refusal.ts#ANONYMOUS_SESSION_REFUSAL_STATUS` in), so
the
per-file floor is untouched.
- Dependency closure built first (`pnpm --filter
'@objectstack/plugin-auth^...'
build`), so nothing here was judged against a stale `dist`.
## Landing surface and changeset
`check-governed-merges.mjs --test` on the **final** four-path file list:
**0 of 4
hit 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-auth` publishes
`files: ["dist","README.md","CHANGELOG.md"]`; after building it, the new
test's
symbols (`checklist-refusal-envelope-consistency`, `RETIRED_CONVENTION`,
`instructionalStrings`) hit **0** across all three, while the positive
control
`ANONYMOUS_SESSION_REFUSAL_STATUS` hits `dist/index.js` and
`dist/index.mjs`.
Across the 70 published packages, **0** name `scripts` in `files[]` and
exactly
one names `src` (`packages/spec`, which this diff does not touch);
`turbo.json`
is named by none. Positive control: all 70 name `dist`.
## Acceptance notes
- `noted, not filed:` the revoke leaves one `sys_session` row behind —
the
deliberate tombstone `session-tombstone.ts` writes, not a leak. Named
here
because the probe printed the count and a later reader would otherwise
have to
re-derive it.
- `noted, not filed:` `check:platform-checklist` is not a per-PR gate.
That is a
recorded 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 further `get-session` statements in this file
describe
behaviour 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.com/claude-code)
https://claude.ai/code/session_01DvvamiacK328idtBYJBxV3
---
_Generated by [Claude
Code](https://claude.ai/code/session_01DvvamiacK328idtBYJBxV3)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 9be2b59 commit f1c9bb3
5 files changed
Lines changed: 265 additions & 11 deletions
File tree
- docs/qa/platform-checklist/areas
- packages/plugins/plugin-auth/src
- scripts
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
370 | 370 | | |
371 | 371 | | |
372 | 372 | | |
373 | | - | |
| 373 | + | |
374 | 374 | | |
375 | 375 | | |
376 | 376 | | |
| |||
393 | 393 | | |
394 | 394 | | |
395 | 395 | | |
396 | | - | |
| 396 | + | |
397 | 397 | | |
398 | 398 | | |
399 | 399 | | |
| |||
428 | 428 | | |
429 | 429 | | |
430 | 430 | | |
431 | | - | |
| 431 | + | |
432 | 432 | | |
433 | 433 | | |
434 | 434 | | |
| |||
465 | 465 | | |
466 | 466 | | |
467 | 467 | | |
468 | | - | |
| 468 | + | |
469 | 469 | | |
470 | 470 | | |
471 | 471 | | |
| |||
480 | 480 | | |
481 | 481 | | |
482 | 482 | | |
483 | | - | |
| 483 | + | |
484 | 484 | | |
485 | 485 | | |
486 | 486 | | |
| |||
492 | 492 | | |
493 | 493 | | |
494 | 494 | | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
495 | 501 | | |
496 | 502 | | |
497 | 503 | | |
| |||
0 commit comments