Skip to content

Commit d5ccfb4

Browse files
fix(changeset): date the pending client entry's closing register — all three 'not addressed here' clauses had been answered (#18765)
Fixes #18652 `.changeset/client-get-session-envelope-and-refresh-read.md` is **pending release input**, not a note: `changeset version` copies it verbatim into `packages/client/CHANGELOG.md`, which is in `@objectstack/client`'s published `files[]` and ships in the npm tarball. AGENTS.md's release-owned table states the deadline in its own words — *"Your PR's input is its **changeset**, on a hard, unwatched deadline: the release that consumes it deletes that input and publishes the sentence."* ## ⚠️ This PR corrects a pending changeset it did not author — read this first `check:empty-changeset`'s real scan **reds on this PR by design** and asks for exactly this paragraph. Its own text names the two classes and their opposite remedies; this is the **DELIBERATE CORRECTION** class, and the gate says *"this gate stays red either way, and staying red is what puts the decision in front of a person instead of routing around it."* - **The note:** `.changeset/client-get-session-envelope-and-refresh-read.md`, introduced by `5de93728a` (PR #17237, **merged** 2026-09-09T21:43:50Z — confirmed at the tree, so there is no open author to defer to). - **What changed under it:** its closing paragraph was an **undated, present-tense** register of what that change left undone, and all three of its clauses had since been falsified on `origin/main` by other cards' landings (readings below). - **⛔ Not the collision class.** No changeset filename was drawn or overwritten here; this PR adds none. ⛔ Do not restore the file from the merge base — that republishes three false sentences. - ⛔ **I have deliberately NOT applied `skip-changeset`.** It would exempt the `changeset-check` job **wholesale** (`lint.yml`'s own note on the self-test split says so), which is the one label that would silence this refusal. Suppressing it is the routing-around the gate forbids, so the label decision is left to a human. ## Readings — measured on this checkout, ⛔ not relayed from the citing cards Each clause, the commit that falsified it, and its ancestry on `origin/main` (`git merge-base --is-ancestor SHA origin/main`, where **exit 0 is self-proving**): | clause, as it read | measured now | falsified by | ancestor? | |:--|:--|:--|:--| | the anonymous `null` "would need the published return annotation to widen" | the **producer** moved instead; the annotation is untouched | `374d9d3afa` (#17881), 2026-09-12 | exit 0 | | `SessionUser.image` "declared `z.string().optional()`" | `image: z.string().nullish().describe('Avatar URL')` — `packages/spec/src/api/auth.zod.ts` | `0e51278f3` (#18501, for #17235), 2026-09-16 | exit 0 | | `auth.login` / `auth.register` "normalize into `data` but set no `success`" | both run `normalizeSessionResponse`, which returns `{ success: true, ...body, data }` | `01388fe81` (#17791, for #17234), 2026-09-12 | exit 0 | **Clause 1, at the definition rather than a call site.** `packages/plugins/plugin-auth/src/anonymous-session-refusal.ts:124` `refuseAnonymousSession` keys on the **answer shape** and on nothing else — three early returns: `isGetSessionPath(endpointPath)`, `response.status !== 200`, and `body.trim() !== ANONYMOUS_BODY` where `ANONYMOUS_BODY = 'null'` (compared as **text**, so `'0'` / `'""'` / `'false'` are left alone). It never reads how the caller became anonymous, so *never signed in*, *unknown cookie* and *revoked session* all convert alike. `ANONYMOUS_SESSION_REFUSAL_STATUS = 401`, and it is wired at `auth-manager.ts:5679`, the one seam every vendor route passes through. ⇒ the anonymous answer is not a `null` outside the declared type; it is a **rejection**, which a `Promise` of `SessionResponse` annotation already permits. ## What the diff does, and the one design choice in it Each clause is **kept as what it recorded**, anchored to when it was written (*"sat outside … when this change was written"*), with the landing that answered it beside it. Two reasons that shape rather than a straight fact-swap: 1. A fact-swap re-arms the same defect — the census's own header names the cause: *"prose does not re-measure itself"*. A dated clause cannot be falsified again. 2. It is the same rule the card's own fence applies one paragraph up: the better-auth **1.7.2** transcript is correct **because** it is dated and attributed. ⛔ That transcript is untouched — verified by needle (`better-auth 1.7.2` and `(anonymous) -> 200 null` both still present at lines 10 and 14). ## Scope note — clauses 2 and 3 were outside the card's fence Card #18652 fenced scope to clause 1 and marked clause 2 **not re-measured, in either direction**; clause 3 it did not mention. I measured both and they are false too, so fixing clause 1 alone was not available: the sentence enumerates (*"**Two** answers stay outside the declared type"*), so a clause-1-only repair would have had to **newly author** the surviving false clauses into release input — strictly worse than what was there. Same defect class, same sentence, same file, no new verification surface. ⇒ folded in, declared here, and reported separately to the dispatching seat. Strip the last two rows if the seat disagrees; the diff is one paragraph. ## Tests / gates — the pin question, answered with three readings ⛔ **NOT MEASURED: nothing in this repo can pin changeset prose**, and it is not an untried idea — the repo has ruled against building it. Three measurements, none of them my own instrument: 1. **Step 49, `check:pm-changeset-deadline-census`** — its header: *"REPORT-ONLY: it fails nothing and gates nothing."* It measures **path presence**, never prose; its own blind-spot list says *"`window-open` means the FILE exists, never that the card's sentence about it is still correct."* Live run on this checkout: `#18652 → verdict "window-open", assertsPending "pending changeset", inListing true`, tally `{window-open: 11, consumed: 3}`, `falsifiedAssertions: []`. The row is **identical before and after this diff** (I amend, not delete). ⇒ reachable, but structurally unable to fail. Not a pin. 2. **Step 122, `check:changeset-gate-self-tests`** — `lint.yml`'s own note: *"The SELF-TEST halves only — the real scans stay in pr-automation.yml's `changeset-check`."* `dispatch-gates` scores self-test-only families *"checker-health only … NOT a PR verdict."* Green here and green without this diff. Not a pin. 3. **Building one is forbidden right now.** `changeset-deadline-census.mjs`: *"⛔ This file is deliberately NOT the enforcement half … report-only first, the census is the deliverable, expansion only when the census reads zero including its blind spot."* The census reads **3 exposed**, not zero. Independently, `.changeset/**` is a ruled scan exemption — *"a changeset is that record before it is compiled into a CHANGELOG"* (`packages/objectql/src/action-owner-key-single-source.test.ts`, `NOT_A_STALE_MENTION`). And the file is consumed and deleted at the next release, so a pin reading that path becomes a phantom check by construction. ⭐ The one reachable instrument that **does** respond to this diff is a third the roster reading did not name — `check-empty-changeset.mjs`'s real scan — and its polarity is **inverted**: it is green without this change and red with it, on the **act**, not the prose. That is the human-confirmation gate above, not a pin. **Derived sweep** — `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at `aefbf5927`, all 18 derived commands plus `check:changeset-fixed` (flagged ⛔ *roster under `.changeset`*) run locally: ``` adr0087 --base 0 no-major --base 0 closing-parity 0 comment-mask-corpus 0 adr0087 --self 0 no-major --self 0 closing-parity 0 driver-memory-census 0 empty-changeset --self 0 release-rehearsal --self 0 changeset-gate-self-tests 0 pm-changeset-deadline-census 0 nul-bytes 0 objectui-changeset 0 published-files 0 refd-timer-probe 0 watch-hint-literal 0 changeset-fixed 0 empty-changeset --base 1 <- THE DESIGNED RED, declared above ``` - **NOT MEASURED: `check:rerun-safety-verdict`.** `dispatch-gates` flagged a stale tree; the roster delta across it is exactly this one new whole-tree family (`95b21b33b`, #18746). It does not exist in this checkout, so `pnpm` exited 254 (script-not-found) — ⛔ that is not a red gate and not a pass. It is self-test-only and reads only its own fixtures, so it cannot judge this diff either way. - **No control characters**: `grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'` over the changed file exits 1 (clean), beyond `check:nul-bytes`. - **No `pnpm lint` narrowing claimed, and no build/test**: this diff compiles nothing and is read by no test. `turbo`'s graph is not consulted because the path is in no package. ## Does this diff owe a changeset of its own? — measured, ⛔ not assumed **No, and adding one would be a defect.** `@objectstack/client`'s published `files[]` is `["dist","README.md","CHANGELOG.md"]`; `.changeset/**` is not in it, and this diff moves no `dist` byte. The published text that *does* move is this very entry — so the amended changeset **is** the release input. A second changeset would emit a second CHANGELOG bullet correcting the first, which is the shape AGENTS.md forbids by name: *"Factual error in a released entry → amend that entry in a dedicated docs-only PR, ⛔ never an erratum in a later entry."* Gate readings agree: `check-changeset-no-major --base` 0, `check-adr-0087-registration --base` 0, `check-empty-changeset --base` prints *"✓ No empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added)"*. ⛔ `major` does not exist in this launch window and nothing here is breaking. Clause-②: no No key moves, no accept set widens or narrows, no export changes, no error code or `ERROR_CODE_LEDGER` entry moves, and no `packages/spec` path is touched. The diff is prose inside one pending changeset; zero lines of shipped code change. ## ⛔ Reported, not touched: the open Version Packages PR **#17076 `chore: version packages` is OPEN and already renders this entry** — at its head `1c0ce1713` the changeset is **absent** (contents API `404`, against a `200` positive control on `.changeset/config.json`) and the paragraph is compiled into `packages/client/CHANGELOG.md:506`. ⛔ I did not touch that PR, ran no release, and merged nothing. ⭐ **But an open Version Packages PR does not mean the window has closed, and the dispatch order's reading that it does is falsified by the repo's own workflow.** That branch is a **derived artefact, regenerated from scratch**: - `release.yml`'s `version-pr` job is `if: github.event_name == 'schedule' || (… workflow_dispatch && inputs.refresh_version_pr)` — ⛔ **not** `push`; the file's own note: *"this job regenerates the PR from scratch, so the newest run's result is the one that was wanted anyway"*, and *"renders changesets that are already committed on main, so lateness costs nothing."* - The mechanism, quoted in that file from changesets/action v1: `git reset --hard SHA` → `pnpm run version` → `git push … --force`. - Corroborated at the object: #17076 was created 2026-09-09 and carries **one** commit, `1c0ce1713`, authored by `github-actions[bot]` at 2026-09-17T18:14:46Z, whose parent `e77a23f02` is an **ancestor of `origin/main`** (exit 0) and only 7 commits behind it. Created eight days before the commit it holds ⇒ force-rebuilt. ⇒ The window closes when a **release consumes** the changeset (AGENTS.md's wording), i.e. when #17076 is merged and published — a human-only act (`release.yml`: *"TWO LANES, ONE INVARIANT: ONLY A HUMAN PUBLISHES."*). Until then this entry is still amendable at one paragraph, and the next 6-hourly tick re-renders #17076 from the corrected text with no action on that PR. The deadline is real and this PR is inside it. --- 🤖 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)_ --- ### Landing note added by the dispatching seat (#6024) — clearing a red that was the seat's, not this PR's The earlier red on `The card this PR closes must claim this branch` was caused by the **claim comment's shape**, not by anything in this diff: the seat's claim on #18652 opened `**CLAIM · …**`, and `scripts/check-closing-target-claim.mjs` selects a comment by a line beginning `Claim:` plus a **separate** `Branch:` directive line. Fixed on the card (`5720097499`), measured before and after — `check-clause2-carriers --pair 18765` went from `EXIT=4 / claim.selected: none` to `EXIT=0 / 1 comment(s) in the pool`, both carriers reading `no`. That guard is body- and comment-scoped and re-fires only on a `pull_request` or `merge_group` event, so **this edit is the event that re-judges it** — ⛔ not an empty commit, which the deliverer correctly refused, and ⛔ not a push, since the seat has ruled that all three clauses stay and no further code change is owed. ⚠️ The two remaining reds are **by design** and are not touched by this edit: `check-empty-changeset` asks in its own words for a human to confirm the DELIBERATE CORRECTION class, and `pr-automation.yml`'s `changeset-check` counts only **added** changesets (this PR amends one and adds zero). The ruling request is on #18652 (`5720153848`). ⛔ `skip-changeset` is deliberately NOT applied — it exempts that job wholesale and would silence the refusal that exists to put this decision in front of a person. --- _Generated by [Claude Code](https://claude.ai/code)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 9364e1b commit d5ccfb4

1 file changed

Lines changed: 18 additions & 5 deletions

File tree

‎.changeset/client-get-session-envelope-and-refresh-read.md‎

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,21 @@ inert as it was.
5757
`token.signature` form interchangeably, so a client that held the signed form
5858
stays signed in across the call.
5959

60-
Two answers stay outside the declared type and are **not** addressed here: the
61-
anonymous `null`, which would need the published return annotation to widen, and
62-
`SessionUser.image`, declared `z.string().optional()` against a route that
63-
serves `null` (#17235). The sibling `auth.login` / `auth.register`, which
64-
normalize into `data` but set no `success`, are #17234.
60+
Three answers sat outside the declared type when this change was written and
61+
are **not** addressed by it. Each has since been answered on its own card, so a
62+
caller reading this entry does not have to code around any of them:
63+
64+
- the **anonymous** `/get-session` answer, recorded above as `200 null`. It no
65+
longer needs the published return annotation to widen, because the producer
66+
moved instead: since #17881 `plugin-auth`'s `refuseAnonymousSession` converts
67+
better-auth's `200` plus the literal JSON `null` into the declared ADR-0112
68+
refusal — HTTP `401` with `code: UNAUTHENTICATED` — before it leaves the
69+
process. The SDK's shared `fetch` wrapper throws on any non-2xx, so an
70+
anonymous `auth.me()` **rejects** rather than resolving outside its own type.
71+
Ruled by #17238: the producer moved and `SessionResponseSchema` is untouched.
72+
- `SessionUser.image`, then declared `z.string().optional()` against a route
73+
that serves `null` (#17235). It is now declared `z.string().nullish()`, so
74+
the `"image": null` every `/auth/*` session body carries parses.
75+
- the sibling `auth.login` / `auth.register`, which then normalized into `data`
76+
but set no `success` (#17234). They now run this entry's own lift, which
77+
fills `success` as well as `data`.

0 commit comments

Comments
 (0)