Skip to content

refactor(plugin-hono-server): the /auth/me door calls the one request-to-ExecutionContext composition, resolveRequestExecutionContext - #24437

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-23217-s5-hono-door
Oct 19, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-23217-s5-hono-door

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #23217

Clause-②: no
Ruling-ref: 6257816853

Basis for the Clause-② line: ruling record 6227928430's "later slices Clause-②: no", and ruling record 6257816853 item 1. One answer of the /auth/me door changes: a session read that carries session.userId and no user.id was answered as signed out and is now signed in as that user, the answer the four converted doors already give. The ruling records that no producer yields that read, in this repository or in cloud's control plane. No export or published surface of @objectstack/plugin-hono-server moves.

Slice 5 of #23217, the last door, under ruling record 6257816853 (batch #367 item 2, slice 5 letter A): the /auth/me door, makeExecutionContextResolver in packages/plugins/plugin-hono-server/src/current-user-endpoints.ts, calls the one resolveRequestExecutionContext (@objectstack/core) with its existing parameters and adopts the composition's reading of who a session names. @objectstack/core's function is not touched. There is no new named parameter (B), no door-local copy (C) and no strict reading for everyone (D). With this slice all five doors call the one composition. Storage's ADR-0069 gate reader stays a hand-written door fact under its parity pin, as ruling record 6257816853 item 2 (letter (i)) closes it.

What changed

The door

makeExecutionContextResolver keeps its door facts and calls the function once. Its parameters are slice 1's row for this door, which report 6255071471 re-measured on the hand-written door over 18 wirings:

parameter the /auth/me door passes what the hand-written door did
anonymous 'fail-closed' a request with no userId got no context, and each route answered its own signed-out body
authGate 'withhold' never carried; these routes are allow-listed for a gated user
credentials 'session-only' headers: undefined to the resolver, so no key was extracted and no admission ran
sessionApi 'required' no auth service, no api or getApi(), or no getSession: no context
session 'request' its own read under the in-process rule (inProcessSessionReadInput)
tenancy undefined no posture, so the session's claimed organization stood
localization, requestLocale 'withhold', undefined none; the endpoints read the cascade themselves
accessToken 'withhold' never carried
oauth, onAdmission undefined none

These door facts stay at the door:

  • the per-request ctx.getService('auth') read and the guarded getObjectQL();
  • the kernel-resolver seam (resolveRequestContext, which picks the locator the resolver is built over);
  • the fail-closed catch with rethrowAuthzStoreUnavailable, which also answers the function's propagated getApi() rejection as no context, as the door always did;
  • the perf-disclosure gate. It now reads the assembled context. isPerfDisclosurePrincipal reads isSystem, principalKind and posture, which carry false, 'human' and the same posture, so its value is unchanged.

resolveAuthzContext, assembleExecutionContext, userAuthGateSourceOf, apiKeyLifecyclePolicySourceOf, inProcessSessionReadInput and IAuthService left the door's imports. resolveRequestExecutionContext and the RequestAuthService type came in. The door's comments now give each parameter's reason where the hand-written composition gave each omission's.

The one changed answer (ruling record 6257816853 item 1)

The hand-written door's readSession dropped a session read with no user.id (return session?.user?.id ? session : null;). The composition reads user.id ?? session.userId (packages/core/src/security/resolve-authz-context.ts). The three routes now answer signed in as that user, instead of signed out, when the auth service's session read carries session.userId with no user, with a user that has no id, or with user.id: null. REST, the runtime dispatcher, the settings routes and the storage download already gave that answer. An empty-string user.id still names nobody, at every door. No producer yields the shape. better-auth's session read returns no session when the session row joins no user. plugin-auth's customSession passes the read through. Cloud's control plane uses the same plugin-auth read (ruling record 6257816853, read at cloud 9099bd1ea646).

Readers moved in the same PR

  • packages/core/src/security/authz-store-unavailable.test.ts:
    • The hono row of TRANSPORT_LEDGER left, because the file no longer holds resolveAuthzContext({. 7 rows in 6 packages remain.
    • The composition's comment names the fifth door.
    • The scanner CONTROL's floor went from 8 to 7, with the reason in its comment. The floor is the ledger's size. Set equality already fails a walk that loses any listed file. The floor and the known-member assertion refuse the one case set equality cannot see: a broken walk and a gutted ledger together. Every door now calls the composition, so no later conversion moves it.
  • scripts/check-single-authz-resolver.mjs: DELEGATORS gained the hono file (resolveRequestExecutionContext). The gate reads all 6 entry points delegate.
  • packages/runtime/src/security/request-execution-context.conformance.test.ts:
    • A HONO_ROW. Its drive registers the current-user endpoints on a HonoHttpServer over the world's services plus a metadata service and a security service. It sends GET /api/v1/auth/me/permissions and reads the context the route hands to security.resolvePermissionSetsForContext.
    • expectedPrincipal learned credentials: a request carrying no session resolves nobody at a session-only door, so an API key alone admits nobody there.
    • One request joined the six: a session read naming its user by session.userId alone. It holds all five doors to one reading of who a session names, and all five resolve u_ses. It sits before the last request, which the CONTROLs read by position.
    • The hono CONTROL tells apart anonymous, authGate, accessToken, localization, requestLocale and credentials. For tenancy it adds a session claiming an organization its user is not a member of, under the walled posture: the claim stands at this door, and the walled posture drops the claim and keeps the caller. It also states that sessionApi is not observable at a session-only door: with no session read the door reads no credential at all, so 'optional' admits nobody either.
  • No alias: runtime's KNOWN_UNALIASED_TEST_IMPORTS entry already lists @objectstack/plugin-hono-server. The row reads the door from dist/, and check:test-source-alias stays green.
  • Core's module docblock table (request-execution-context.ts), read row by row against the converted door: no row turns false. Every row already names the /auth/me endpoints for the value this door passes. Not edited.
  • scripts/slot-lookup-baseline.json does not name the hono file. Not touched.
  • The 14 hono test files that import ./current-user-endpoints: none reads its source text. The 11 that fake a session read answer with user.id or with nothing, so none moved.

The pins

  • packages/plugins/plugin-hono-server/src/current-user-endpoints.request-context-door.pin.test.ts (ruling record 6227928430 item 2) boots the real HonoServerPlugin through init(), start() and its kernel:ready hooks. It drives the three real routes and reads each body and what the security service received. 9 rows:
    • five ADR-0096 rows: no credential, an unknown cookie, a throwing session read, an API key alone, and no auth service. Each gets every route's own signed-out body, and the security service is never asked;
    • one ADR-0069 row: a held session is served on these allow-listed routes, and its context carries no authGate;
    • one outage row: all three routes answer the host's 503 SERVICE_UNAVAILABLE;
    • the ruled reading: a session.userId-only read is served as that user;
    • a CONTROL: an ungated session is served, and its context carries no bearer, locale or time zone, although the session carries a bearer, the request a language and the host a settings service.
  • packages/plugins/plugin-hono-server/src/current-user-endpoints.resolver-wirings.pin.test.ts is the earlier dev's 18-wiring parity probe, committed. Each wiring pins who the door names: no context, the caller with its organization, or the outage. Each also requires the composition, under the parameters this door declares, to give the same answer. The one declared difference is the door's own catch, and the file's CONTROL shows the composition rejecting with getApi down there. The three session.userId-only wirings assert the composition's answer, signed in as that user. The other 15 assert their unchanged answers.

Evidence

All on a shared container; lock seconds are shared-box readings.

  • Before, on the hand-written door (door blob c0e104d2c, main 8baeda520, pins at 348b11ba7): the two pin files read Tests 4 failed | 25 passed (29). The four failures are exactly the ruled-reading rows (three wirings and the route-level row), where the door answered no context and { authenticated: false }.
  • After, on the converted door (blob e7cc167d5): the whole hono suite reads Test Files 34 passed (34), Tests 422 passed (422), both pins included.
  • ABLATION, declared direction RED, at 86cd71673 (door blob e7cc167d5): two legs in one verify-lock call, through scripts/ablation-replace.mjs. Each leg rebuilt @objectstack/plugin-hono-server and proved its marker in dist/ with scripts/ablation-dist-preflight.mjs, because the conformance row reads the door from dist/. Each restore was rebuilt and preflighted with --absent.
    • Leg 1 reverts the door to its hand-written composition: the import block through makeExecutionContextResolver replaced by main's text. Marker session?.user?.id ? session : null, present in 2 built files. Wirings pin Tests 3 failed | 17 passed (20), route pin Tests 1 failed | 8 passed (9), conformance Tests 1 failed | 12 passed (13). Each failure is a ruled-reading row or the session.userId-only request.
    • Leg 2 changes credentials: 'session-only' to 'api-key-and-session'. Marker credentials: "api-key-and-session" in dist/. Wirings pin 2 failed | 18 passed (20): the API key alone, and the key beside a session. Route pin 1 failed | 8 passed (9): the API key alone. Conformance 3 failed | 10 passed (13): the API key, the ex-member's key, and the hono CONTROL.
    • Each restore: the blob equals HEAD (e7cc167d5), git diff HEAD is empty, and the dist marker is absent after the rebuild. git status --porcelain is empty at the end. Observed direction equals declared.

Tests (HEAD 2f7f09a8e, after merging origin/main e22defb13)

  • @objectstack/plugin-hono-server: Test Files 34 passed (34), Tests 422 passed (422). typecheck exit 0 (test layer 0 / 0 / 0), and tsc -p tsconfig.test.json --listFiles includes both new pins.
  • @objectstack/runtime: the conformance test reads Test Files 1 passed (1), Tests 13 passed (13) (7 requests, the identity test, 5 CONTROLs). typecheck exit 0 (test layer 27 files / 190 errors / 68 signatures held, none in the conformance file). The rest of the runtime suite was not run locally: the conformance file is the only runtime file touched, and CI's Test Core runs the suite.
  • @objectstack/core: src/security/ reads Test Files 43 passed (43), Tests 961 passed (961); the ledger file reads 36, which is 37 minus the hono row. typecheck exit 0 (test layer 4 / 4 / 4 held). Core's source is untouched.
  • Build after the merge: turbo run build --filter=!@objectstack/docs --concurrency=2, 76 successful, 76 total.

Gates (HEAD 2f7f09a8e)

  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths) derived 90 commands. Each ran with its exit code captured before any pipe, and all exited 0. pnpm check:type-check-debt ran under the verify lock: exit 0, none above its recorded number.
  • --ran reads 90 derived, 90 run, 0 NOT-MEASURED, 0 UNRUN, exit 0.
  • Among the derived commands:
    • check:authz-resolver: all 6 entry points delegate;
    • check:test-source-alias;
    • check:slot-lookup: 100 unswept site(s) in 25 file(s), none new;
    • check:cross-package-test-inputs, check:route-envelope, check:dual-build-cjs-loads (on the built tree), check:nul-bytes, check:adr-0087-registration, check:empty-changeset and check:pm-dispatch-gates.
  • The declared wide population, all 10 families, all exit 0: check:init-service-contract, check:live-db-isolation, check:meta-type-normalized, check:optional-error-sink, check:resume-authority-declared, check:runner-env-posture, check:settings-bind-window, check:startup-registry-verdict, check:verify-stand-in and check:wildcard-fallthrough.
  • Edited gate script: node scripts/check-single-authz-resolver.mjs exit 0, and --self-test exit 0.
  • pnpm exec eslint --no-inline-config over the five changed .ts / .mjs files: exit 0, no output.

The card's acceptance, measured (HEAD 2f7f09a8e)

git grep -c -F over the five door files:

line rest runtime settings storage hono
resolveAuthzContext( 0 0 0 0 0
assembleExecutionContext( / assembleExecutionContextOrGuest( 0 0 0 0 0
classifyAdmissionTenancyPosture( 0 0 1 1 0
inProcessSessionReadInput( 0 0 0 1 0
resolveApiKeyAdmission( 0 0 0 1 0
resolveRequestExecutionContext( 1 1 1 1 1
  • The hono door reaches 0 copies and adopts the composition's reading.
  • Storage's three hits are its ADR-0069 gate reader, the acceptance line's one exception (copy + parity pin): the key arm (resolveApiKeyAdmission(, about :1283), the gate reader's posture read (resolveAdmissionTenancyPosture, about :1432), and buildGetSession (about :973), the session read the gate reader shares with the upload session resolver.
  • The settings hit (resolveAdmissionTenancyPosture, about :469) feeds the settings SERVICE's own tenancyPosture option, not the HTTP door. The door calls the composition (slice 3), and that method's docblock records why the resolution stayed there.
  • The ADR-0096 refusal and the ADR-0069 allow-list read the same on this door before and after: 8 of the route pin's 9 rows were green on both trees. The ninth is the ruled reading.

Acceptance notes

  • This PR closes the card on the reading above. If the seat reads the settings service-option hit or storage's buildGetSession as outside the acceptance line's one exception, the first line should read Part of #23217 instead. The seat writes that change from the report, since this body is written once.
  • Out of lane: packages/core/src/security/authz-store-unavailable.test.ts (declared on [PM seat] domain:engine — 🟢 os-project-manager #6367, comment 6254881518) and scripts/check-single-authz-resolver.mjs (declared on [PM seat] domain:spec — ⏳ vacant #6017, comment 6254886923). Every other path is in domain:cli or is the changeset.
  • Nothing filed: no defect, contract violation or authoring trap turned up outside this slice.

Generated by Claude Code

…e one composition, before the door moves

The door itself is unchanged in this commit; the next one converts it.

Claude-Session: https://claude.ai/code/session_01SYEJqDL3NXxrJRBVqaMu9T
Co-authored-by: Claude <noreply@anthropic.com>
…-to-ExecutionContext composition, resolveRequestExecutionContext

Claude-Session: https://claude.ai/code/session_01SYEJqDL3NXxrJRBVqaMu9T
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/xl documentation Improvements or additions to documentation tests tooling labels Oct 19, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

2 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 9 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json db1f1fd20fba543408b209746e566b48373a4622 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 7786652b9443656cf3f4073c7f76c09d06fb04fe — the merge of head 2f7f09a8efc21a980cf375cfa36519d7e852440c into base db1f1fd20fba543408b209746e566b48373a4622, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 7786652b9443656cf3f4073c7f76c09d06fb04fe && git checkout 7786652b9443656cf3f4073c7f76c09d06fb04fe
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin db1f1fd20fba543408b209746e566b48373a4622 2f7f09a8efc21a980cf375cfa36519d7e852440c && git checkout -B drift-repro db1f1fd20fba543408b209746e566b48373a4622 && git merge --no-ff 2f7f09a8efc21a980cf375cfa36519d7e852440c

node scripts/docs-audit/affected-docs.mjs --json db1f1fd20fba543408b209746e566b48373a4622

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT (lane review) · domain:cli seat 1 (seat post #6024) · zhuangjianguo · session session_01SYEJqDL3NXxrJRBVqaMu9T · 2026-10-19T03:00Z · on head 2f7f09a8e · #23217 slice 5, the last door (claim 6257852620, report 6258657763). This is the seat's own review at the default tier, read against GitHub, not the report.

  1. Shape:
    • Draft against main, Fixes #23217, a line-initial Clause-②: no with its basis (6227928430's "later slices Clause-②: no" and 6257816853 item 1).
    • 7 files, +851 / −153 (1,004 changed lines, under 3,000). One patch changeset on @objectstack/plugin-hono-server that names the one ruled answer change. Not governed.
  2. The door, read on the diff (ruling 6257816853 item 1):
    • makeExecutionContextResolver calls resolveRequestExecutionContext once, with the existing parameters slice 1 measured for this door.
    • The readSession user.id guard is gone.
    • It keeps the per-request auth read, the kernel-resolver seam, the perf-disclosure gate (now read off the assembled context, same value) and the catch with rethrowAuthzStoreUnavailable.
    • Five composition imports leave. @objectstack/core's function is not edited. No new parameter (B), no door-local copy (C), no strict reading (D).
  3. Readers:
    • core's TRANSPORT_LEDGER loses the hono row, and its scanner floor goes from 8 to 7 with the reasoning in its comment.
    • DELEGATORS gains the hono file, and its --self-test exits 0.
    • The runtime conformance test gains HONO_ROW, credentials in expectedPrincipal, a seventh request (a session.userId-only read, resolved as u_ses at all five doors) and a hono CONTROL.
    • Core's docblock table was read row by row; no row turns false.
  4. Pins and reverse verification:
    • The 18-wiring parity probe is committed as current-user-endpoints.resolver-wirings.pin.test.ts, and there is a route-level ADR-0096 / ADR-0069 pin on the booted plugin.
    • Measured before the door moved: exactly the ruled-reading rows failed, 4 failed | 25 passed.
    • Two ablation legs, dist-preflighted: the door reverted, and credentials widened. Each turned its pins and the conformance row red as declared. Each restore was proven: blob == HEAD, git diff HEAD empty.
  5. Tests and gates:
    • hono: 34 files, 422 tests. conformance: 13. core src/security: 43 files, 961.
    • The three typechecks hold their ledgers, and eslint exits 0.
    • dispatch-gates: 90 derived, 90 run, 0 NOT-MEASURED. check:type-check-debt ran under the lock. check:authz-resolver reports "all 6 entry points delegate".
    • CI on this head was running at this stamp, with none red.
  6. The dev's open question (Fixes or Part of): A, Fixes #23217. Over the five door files, resolveAuthzContext( and assembleExecutionContext(OrGuest)( read 0, and the hono door reads 0 on every line. The remaining hits are:
  7. Deviations accepted: the seventh conformance request and the extra route row, which let the revert leg go red; the second ablation leg; and main left unmerged after pr_create because the paths are disjoint. The commits carry the model-free trailers.
  8. Cross-lane: [PM seat] domain:engine — 🟢 os-project-manager #6367 6254881518 (core readers) and [PM seat] domain:spec — ⏳ vacant #6017 6254886923 (scripts/**).
  9. Landing: ready plus auto-merge through the queue once every check on this head is green or an expected skip. The card closes on the merge.

Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/xl tests tooling

Projects

None yet

2 participants