Repository navigation
Description
Split out of #9364 at triage (2026-08-17): the error-path half of that card is queued as mechanical envelope conformance; THIS card carries the half that is a genuine wire-contract ruling and must not be decided by a dev mid-PR.
The question
Three route families answer bare payloads (no success flag, fields at top level) and are read that way by SPAs before first paint / before authentication:
POST/GET /api/v1/runtime/config(packages/cloud-connection/src/runtime-config-plugin.ts) — objectuiapp-shell/src/runtime-config.tsreadsbody.cloudUrl/body.features/body.brandingoff the top level;packages/plugins/plugin-hono-server/src/current-user-endpoints.ts— nine{ authenticated, userId, … }bodies, read bare by the Account/Console SPAs;plugin-auth's/bootstrap-status— three{ hasOwner: … }bodies, same consumption shape.
scripts/check-route-envelope.mjs (third gate surface, added by #9267) counts all of them as unenveloped; the counters are pinned. Today they are neither enveloped nor ruled exempt — the state #9364 names as the one worth ending.
Options
A. Envelope them — coordinated breaking wire change: server flips + objectui SPA readers flip, with a compatibility story for consoles/runtimes on different versions (contract-first split: spec/server first, Blocked-by: objectui halves).
B. Rule them exempt — a written ruling that discovery/bootstrap endpoints read before authentication are outside BaseResponseSchema; each file gets an exempt entry with the reason in the gate. Precedent exists: hmr-routes.ts already carries exactly such an exempt-with-reason.
Four-prism block
- Platform long-term coherence — A maximizes envelope uniformity but buys a version-skew compatibility obligation on the least-versionable seam (pre-auth bootstrap). B draws a principled boundary ("pre-auth bootstrap surfaces are bare by design") rather than an accidental one; a written boundary with per-file reasons is coherent, an unruled gap is not. Either ends the "neither" state.
- Measured business pull — zero user-facing pull for A: no consumer wants these enveloped (the only readers are our SPAs, which read them bare today and would have to be changed to tolerate both shapes through a skew window). The pull is for the gap to be closed on paper, which B does at ~zero migration cost.
- AI-agent error-resistance — the envelope's value is highest where agent-authored apps consume responses; these three surfaces are consumed by our own shells, not by authored apps. A closed
exemptlist with mandatory reasons is itself a loud contract; silent non-conformance (status quo) is the only wrong answer. - Startup scope discipline — A is a cross-repo breaking migration with a skew story, for surfaces only we read; B is a bounded ruling plus gate entries. Remove/declare-small beats maintain-a-migration.
Recommendation: B — rule pre-auth discovery/bootstrap payloads outside BaseResponseSchema, add exempt entries with reasons for exactly these three files (closed list; any NEW pre-auth bare surface needs its own exempt entry to pass the gate, so the boundary stays enumerated), and record the ruling where the gate's docs point. If the maintainer instead wants A, it must be contract-first split (spec/server first, objectui halves Blocked-by:) and version-skew handling named before any dispatch.
Refs: #9364 (worklist + measurements) · #9267 (gate surface) · #3843 (envelope guard founding class) · hmr-routes.ts exempt precedent.
Activity
os-zhuang commentedon Aug 17, 2026
Maintainer ruling recorded — maintainer, 2026-08-17, batch acceptance of the PM decision-inbox recommendations, verbatim: 「同意」. Four-dimension analysis delivered in-session; provenance: PM session
session_01NYgmGheCzM6NrHZN436Cxf.Ruled: B — pre-auth discovery/bootstrap payloads are outside
BaseResponseSchemaby design. The three route families (runtime/config, current-user endpoints, bootstrap-status) getexemptentries with the written reason inscripts/check-route-envelope.mjs, following thehmr-routes.tsprecedent. The boundary stays enumerated: any NEW pre-auth bare surface must carry its own exempt-with-reason entry to pass the gate. Record the ruling where the gate's docs point.Rationale on record: the envelope's value is where authored apps consume responses; these surfaces are read only by our own shells, and A would buy a version-skew compatibility obligation on the least-versionable seam for zero measured pull.
Label
needs-user-decision→pm:queue(the exempt-entries edit) in the same stroke.Generated by Claude Code
os-zhuang commentedon Aug 18, 2026
Claim: PM seat dispatch (post-ruling)
Session:
session_01NYgmGheCzM6NrHZN436CxfBranch:
claude/issue-9389-preauth-envelope-exemptScope: implement ruling B as recorded above —
exemptentries WITH written reasons inscripts/check-route-envelope.mjsfor exactly the three pre-auth route families (runtime-config, current-user endpoints, bootstrap-status), following thehmr-routes.tsprecedent; record the ruling where the gate's docs point. ⛔ No route behavior changes — the payloads stay bare; this is the gate learning the boundary, not the boundary moving. The closed-list property must hold: a NEW pre-auth bare surface still fails until it carries its own exempt-with-reason.Routing authority: maintainer ruling, 2026-08-17 「同意」 batch.
Label
pm:queue→pm:dispatchedin the same stroke.Generated by Claude Code
chore(lint): record the ruled pre-auth exemption in the route-envelop…
os-zhuang commentedon Aug 18, 2026
{ "issue": 9389, "status": "done", "branch": "claude/issue-9389-preauth-envelope-exempt", "pr": "https://github.com/objectstack-ai/objectstack/pull/9455", "premise_still_valid": true, "summary": "Ruling B implemented in scripts/check-route-envelope.mjs only — one file, +351/-69, no route behaviour change. The three pre-auth families move from ratchet (#9364 tracked drift) to exempt with the written reason, stating the boundary the ruling drew (read before authentication, by our own shells, before an envelope reader exists) and explicitly disclaiming the two misreadings ('legacy' / 'these are fine'). ONE DESIGN JUDGMENT FOR PM REVIEW: on this surface an exempt stays COUNTED rather than becoming a file-level waiver. auth-plugin.ts builds 49 bodies of which only 3 are ruled (the other 46 include the conformant /auth/public-config body), so a file-level exempt would stop asserting anything about the file — I measured that alternative and it passes green with an unruled 4th bare pre-auth body live (ablation 3). Counting keeps the ruling's own 'boundary stays enumerated' clause true at body granularity, not just file granularity. Supporting rules added: ratchet+exempt mutually exclusive; an exempt pinning nothing is refused; widening a ruled boundary is marked MAINTAINER-ONLY per the #8435 convention rather than offered as an author's fix. Ruling recorded in the script header — the gate names no doc location of its own (content/docs/references/ is generated; the hmr-routes.ts precedent's reasoning lives in that prose), and the PR says so. Measured correction to the card's wording: current-user-endpoints.ts is not nine '{ authenticated, userId }' bodies — six are '{ authenticated, ... }' and three are '{ apps }' from /me/apps; the reason text says that rather than inheriting the old note. The other three #9364 ratchets (adapter.ts, adapters/hono, cli serve.ts) are untouched — the ruling does not reach them.", "tests": "All at final commit 7a9b9ffde, git status --porcelain empty. Gate derivation run verbatim: 'node scripts/pm/dispatch-gates.mjs --tier scripts/check-route-envelope.mjs' (no path-derived tier mandate) and 'node scripts/pm/dispatch-gates.mjs scripts/check-route-envelope.mjs' -> names exactly one family, 'pnpm check:route-envelope [lint.yml]'. UNION RUN AT 7a9b9ffde: (1) check:route-envelope self-test '✓ check-route-envelope self-test passed', audit exit 0 '11 module(s) audited, 161 hand-built bodies: 5 conformant, 3 ratcheted, 3 exempt'; (2) check:ratchet-remedy-authority — THE ADDITION BEYOND THE DERIVED LIST, implicated because this diff hands the author a registry-expanding remedy: self-test OK, sweep OK and unchanged from baseline (6 marked / 3 refused / 80 excluded, control corpus set-equality both ways); this gate stays 'excluded' by the sweep's own classification so no CONTROL entry is owed; (3) check:nul-bytes OK, 6124 files, plus a targeted grep -naP control-byte self-scan of the changed file, clean. REVERSE VERIFICATION, direction predicted before running and observed as predicted in all three legs: (1) a bare pre-auth body in a NEW unlisted file -> predicted RED, observed RED exit 1 'NOT DECLARED ... a CLOSED list of files, so a new one does not inherit the ruling by resembling it'; (2) a 4th bare body inside the ruled auth-plugin.ts -> predicted RED, observed RED exit 1 'unenveloped: found 4, declared 3 ... A ruled exemption is a CLOSED list ... MAINTAINER-ONLY'; (3) the same 4th body under the NAIVE file-level exempt (built from origin/main's gate) -> predicted GREEN silent pass, observed GREEN exit 0 with the exempt line printed as if all were well. Both mutated files restored; porcelain empty afterwards. Seven new self-test cases drive the extracted pure auditPluginRouteModule() so the closed-list property is asserted on the TABLE, not just the scanner: unlisted new file, second body in a ruled file (incl. the MAINTAINER-ONLY marking), exact-count pass, below-count fail, ratchet/exempt exclusivity, exemption-over-nothing, and the untouched tracked-drift diagnostic. NO package build or test was run and none is owed — the diff touches no package source, so there is no affected package and no build closure; stating that rather than reporting a suite I did not run. Deps: the gate imports only node:fs/path/url plus typescript, so no pnpm install was done — typescript was symlinked into the worktree and removed at teardown.", "open_questions": [ { "question": "The ruling says the three families 'get exempt entries with the written reason, following the hmr-routes.ts precedent'. hmr-routes.ts is a whole-file exemption that asserts nothing; applied literally to auth-plugin.ts that would retire the audit of 46 unrelated bodies and let the next bare body land in silence (measured: ablation 3 passes green). I implemented exempt as a COUNTED classification instead — same reason string, counters still pinned and ticking down only. Confirming this reads the ruling as intended rather than exceeding it.", "options": [ "A (implemented) — counted exempt: the reason plus a pinned count, so the boundary is a closed list of BODIES; a 4th bare body in a ruled file goes red until a maintainer widens the ruling", "B — literal file-level exempt like hmr-routes.ts: nothing asserted for the three files at all; simpler and matches the precedent's shape exactly, but the ruling's own 'boundary stays enumerated' clause then holds only against NEW FILES, not new bodies in the ruled ones" ], "recommendation": "A, because the ruling's load-bearing clause is the enumeration, and B measurably loses it inside the ruled files: with B the gate certifies auth-plugin.ts green while an unruled bare pre-auth body is live in it — the 'neither enveloped nor ruled exempt' state the card exists to end, re-entered one file at a time. A costs nothing at the wire (no route behaviour changed either way) and keeps the widening act where the ruling put it, with the maintainer." } ], "out_of_scope_findings": [] }Notes not filed as issues, deliberately:
check-ratchet-remedy-authorityclassifies this gate asexcludedeven though itsdiagnostics now offer a registry-expanding remedy. I did not file this: that
detector's header states it under-matches on purpose and treats a false positive as
worse than a miss, and the pre-existing "declare the CURRENT counts plus a
ratchet"text was equally excluded before this PR — so there is no measured defect, only a
documented design property. The
⛔ MAINTAINER-ONLYmarking is carried anyway becauseit is true for the reader.
{ authenticated, userId, … }bodies" is imprecise (six are{ authenticated, … }, three are{ apps }). Corrected in the gate's reason text andentry comments rather than filed — the card is being closed by this PR.
Generated by Claude Code
Generated by Claude Code
os-zhuang commentedon Aug 18, 2026
Carry-forward parked here before dispatch, so it cannot be lost between cards.
domain:cliPM seat (#6024), sessionsession_012WKSnqAaoqtW3QX7SSf1Vk. ⛔ Not a claim — this card stayspm:queue, unassigned, serial behind #9364 (same file,scripts/check-route-envelope.mjs).Release condition: PR #9456 merges. It is ACCEPTed and enqueued (
d2ccb2f79); the moment it lands, this card is the nextdomain:clidispatch.Verified at line level on the #9456 branch, not inferred: after it merges,
scripts/check-route-envelope.mjsstill carries:537—ratchet: '#9364 (envelope /api/v1/runtime/config)':542—ratchet: '#9364 (envelope the bare plugin-route payloads)':563—ratchet: '#9364 (envelope the bare plugin-route payloads)'covering exactly the three files this card rules on —
runtime-config-plugin.ts,current-user-endpoints.ts,auth-plugin.ts. #9364 was fenced away from them on purpose, so it correctly did not touch their entries; but merging it closes #9364 and leaves three live pointers naming a closed issue.Retargeting them is part of this card's work, alongside the ruled
exempt-with-reason entries. Under the maintainer's ruling (2026-08-17, B) these three surfaces are outsideBaseResponseSchemaby design, so the honest end state is anexemptentry carrying the written reason — not a ratchet pointing at anything. Confirm on the merged ref rather than from this comment; line numbers in that file moved once already tonight.(Other
#9364mentions in the same file are past-tense explanatory comments — historical record, correct as-is. ⛔ Do not "fix" those.)Also inherited: a sibling card that is deliberately NOT in your scope
#9436 —
packages/adapters/hono's two bare{ data }discovery bodies. Same pre-auth bare-payload fork this card rules on, but a different consumer population: SDKs and codegen read that mount's discovery, not the Console SPA. It sits outside this card's deliberately closed three-file list, so #9364 retargeted its gate entry to #9436 (Blocked-by #9389) instead of stretching your scope. ⛔ Do not absorb it — the closed list is the ruling's boundary, and widening it here would decide by accident what the ruling decided on purpose.Ruling recap for whoever picks this up
Maintainer, 2026-08-17, verbatim 「同意」 (relayed by PM session
session_01NYgmGheCzM6NrHZN436Cxf): option B — pre-auth discovery/bootstrap payloads are outsideBaseResponseSchemaby design; the three route families getexemptentries with the written reason, following thehmr-routes.tsprecedent. The boundary stays enumerated: any NEW pre-auth bare surface must carry its own exempt-with-reason entry to pass the gate. Option A (envelope them) was declined — it buys a version-skew compatibility obligation on the least-versionable seam for zero measured pull.Generated by Claude Code
os-zhuang commentedon Aug 18, 2026
⛔ Correction by the author: my carry-forward note above was written to a card that was ALREADY CLAIMED, and I did not re-read before writing
domain:cliPM seat (#6024), sessionsession_012WKSnqAaoqtW3QX7SSf1Vk. Correcting within the hour, because the note as written misdescribes this card's state and could mislead whoever reads it next.What I asserted at 01:58:25Z: that this card "stays
pm:queue, unassigned, serial behind #9364", with its release condition being PR #9456's merge.What was already true at 01:34:29Z — twenty-four minutes earlier: this card was claimed by PM session
session_01NYgmGheCzM6NrHZN436Cxfunder the maintainer direct-dispatch channel, branchclaude/issue-9389-preauth-envelope-exempt, and was in flight. It landed as PR #9455 at 02:16:04Z and this card is closedcompleted.So every state claim in that note was wrong, and it was wrong at the moment I wrote it.
The cause, stated plainly: I read this card at ~01:0xZ to settle its
[Decision]-title-versus-pm:queue-label question, and then wrote to it at 01:58Z from that hour-old reading. The claim arrived in between. This lane already carries the lesson "read a card's full comment thread before writing a dispatch brief" — banked after a brief contradicted a ruling sitting in the comments. This is the same failure one step over: re-read before WRITING to a card, not only before dispatching from it. A cross-seat claim is exactly the kind of fact that appears in the gap.⛔ No harm reached the work — the note was advisory, this seat never claimed the card, and the other session's dispatch and landing were unaffected. But an incorrect state assertion sitting on a closed card is the kind of thing a future seat reads as history.
What survives from that note, re-verified on
origin/mainjust now rather than restated:ratchet: '#9364 …'pointers are still present, atscripts/check-route-envelope.mjs:599,:605,:611. But after chore(lint): record the ruled pre-auth exemption in the route-envelope gate #9455's restructuring (+351/−69) they name the entries PR fix(api): envelope the plugin-mounted Hono error paths — six refusal bodies stop speaking the pre-#3675 dialect #9456 removes — the adapter refusals, the hono adapter bodies, the serve host-resolution refusal — not the three pre-auth bare-payload files this card ruled on. So that carry-forward is fix(api): envelope the plugin-mounted Hono error paths — six refusal bodies stop speaking the pre-#3675 dialect #9456's own and is being handled there; it was never a debt this card owed.{ data }discovery bodies lose their ratchet owner when #9364 lands #9436 remains correctly outside this card's closed three-file list — same pre-auth fork, different consumer population (SDKs and codegen read that mount's discovery, not the Console SPA). That part of the note stands.One consequence worth flagging to whoever owns the queue next: #9455 landing while #9456 was in flight left PR #9456 with a real merge conflict in that shared gate script. It is being resolved by re-deriving the counters on top of main's structure, ⛔ not by picking hunks — a blind resolution there could silently restore a counter or drop one of this card's
exemptentries.Generated by Claude Code
feat(adapters): envelope the hono adapter's two discovery bodies (#9436)
chore(lint): record the ruled pre-auth exemption in the route-envelop…
feat(adapters): envelope the hono adapter's two discovery bodies (obj…
docs(objectql): re-anchor the dead tracker citations to the commits a…