Repository navigation
auth(spec,plugin-auth): a short-lived principal-session mint, an optional IAuthService member with its AuthManager implementation, the seam #22269's print service consumes (S2 of #22269) #23772
Copy link
Copy link
Closed
Labels
domain:specenhancementNew feature or requestNew feature or requestpriority:p1High: required for production / M2High: required for production / M2target:v18
Description
Filing gate ④: an execution card cut from #22269 (card ③ of #8346's B′ split). Triage split it at the domain:services seat's retriage request 6205245677, taking the seat's option (ii), "split along the lanes". ⛔ Not a claim.
Reader who acts: the domain:spec seat. This is a vertical seam: the contract member in packages/spec/src/contracts/auth-service.ts, its plugin-auth implementation, and the pins, all in one PR under the spec seat's contract review.
Governing text
- export: PDF / print document generation — re-planned for v18, superseding the #1301 NOT_PLANNED closure #8346's ruling B′ (
6051470224), M2: the render service renders the page "under the requesting user's principal (a short-lived credential; no system principal)". - print page ③ of #8346: a render service with one headless-Chromium driver renders a print page under the requesting user's principal and archives the PDF as a sys_file (M2) #22269's seat rulings (
6059363716):- OQ1 A: "The mint is declared as an optional
IAuthServicemember, implemented byAuthManager." - OQ9 A: "Human principals only. The rest are refused
PERMISSION_DENIED/403, and anauthGateprincipal is refused too."
- OQ1 A: "The mint is declared as an optional
- The placement ruling
6204149204(maintainer 「22269 同意」): the credential may go to a remote browser endpoint, and the pins apply to both placements.
What lands
Sizes from the design report 6059301379, row 7_size.S2, about 560–640 lines in all:
packages/spec/src/contracts/auth-service.ts: one optional member onIAuthService(about +25), its test (about +15), and the api-surface regen.packages/plugins/plugin-auth:src/principal-session-mint.ts(new, about 150);auth-manager.ts(about +30);- the tests (new, about 320).
- The changesets.
Pins
From the report and the seat review:
- The better-auth refresh trap, on both lanes. A minted session is never extended by the session-renewal path (
updateAge). - The TTL is short and bounded.
- The revoke path. The consumer (print page ③ of #8346: a render service with one headless-Chromium driver renders a print page under the requesting user's principal and archives the PDF as a sys_file (M2) #22269) revokes in
finally; this card pins the revoke it calls. - The refusals, before any mint:
- a non-human principal gets
PERMISSION_DENIED/403; - an
authGateprincipal is refused.
- a non-human principal gets
- Cookie names under
cookiePrefixand__Secure-. - Measured fact to build on: today no API mints a session for a user outside a better-auth endpoint. The only
internalAdapter.createSessioncallers areadmin-impersonate-endpoint.ts:228andimpersonation-bearer-rotation.ts:251. Impersonation acts as a different principal, so it is not this mint.
Contract and grade
Clause-②: yes. A newIAuthServicemember and a publicAuthManagermethod. A contract-tier review is owed before the queue.- p1 +
target:v18, inherited from print page ③ of #8346: a render service with one headless-Chromium driver renders a print page under the requesting user's principal and archives the PDF as a sys_file (M2) #22269: the maintainer named PDF for 18.0 GA.
Order
- This card is built in parallel with print page ③ of #8346: a render service with one headless-Chromium driver renders a print page under the requesting user's principal and archives the PDF as a sys_file (M2) #22269.
- Its PR lands first. print page ③ of #8346: a render service with one headless-Chromium driver renders a print page under the requesting user's principal and archives the PDF as a sys_file (M2) #22269's PR reads the member and lands after it.
Dedupe
MCP search_issues on this repo, closed included:
short-lived principal session mint IAuthService member AuthManager print render credential: 0 hits;principal session mint: 0 hits;- the control,
print render service headless Chromium: print page ③ of #8346: a render service with one headless-Chromium driver renders a print page under the requesting user's principal and archives the PDF as a sys_file (M2) #22269 only.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
domain:specenhancementNew feature or requestNew feature or requestpriority:p1High: required for production / M2High: required for production / M2target:v18
Activity
objectstack-fleet commentedon Oct 16, 2026
Claim: PM loop round 2 · 2026-10-16T01:09Z
Session:
session_01HjR8RUwTPQ3TgbfKihmxp9(the dispatching seat; the dev runs as a cloud session the seat post names)Account:
os-elon-musk(the seat's linked user asGET /useranswers it; this card's assignee)Branch:
claude/issue-23772-principal-session-mintWorktree:
objectstack-issue-23772(in the cloud session's container)Domain:
domain:specSeat:
domain:spec#1(seat post #6017)File surface (at
origin/main4fe8b3defor later; stop on a breach and explain it in the report):packages/spec/src/contracts/auth-service.ts: one optionalIAuthServicemember, the short-lived principal-session mint and its revoke, with its test and the api-surface regen.packages/plugins/plugin-auth/src/principal-session-mint.ts(new),auth-manager.ts(theAuthManagerimplementation), and their tests.@objectstack/specminor,@objectstack/plugin-authminor).admin-impersonate-endpoint.ts,impersonation-bearer-rotation.ts).Container & model:
M,mode:cloud(the maintainer's word in this session's chat: 「batch 调到 5, 可以开云卡」),model: default tier(dispatch-gates --tier --repo objectstack-ai/objectstack: no path-derived mandate; the contract review owed before the queue is atCONTRACT_REVIEW_TIERby face,packages/spec/src/**, plus the widening the line below declares)Clause-②: yes
Responsibility: n/a — not a defect card (an execution card cut from print page ③ of #8346: a render service with one headless-Chromium driver renders a print page under the requesting user's principal and archives the PDF as a sys_file (M2) #22269)
Thread-read: none
Ruling-ref: export: PDF / print document generation — re-planned for v18, superseding the #1301 NOT_PLANNED closure #8346 ruling B′
6051470224(M2: "under the requesting user's principal (a short-lived credential; no system principal)"); print page ③ of #8346: a render service with one headless-Chromium driver renders a print page under the requesting user's principal and archives the PDF as a sys_file (M2) #22269 seat rulings6059363716(OQ1 A, OQ9 A); print page ③ of #8346: a render service with one headless-Chromium driver renders a print page under the requesting user's principal and archives the PDF as a sys_file (M2) #22269 placement ruling6204149204(maintainer 「22269 同意」).Serial constraints cleared: the file lists of all open PRs and the claims of all 24 open
pm:dispatchedcards were read in this act; none namesauth-service.ts,auth-manager.tsor a principal-session mint. print page ③ of #8346: a render service with one headless-Chromium driver renders a print page under the requesting user's principal and archives the PDF as a sys_file (M2) #22269 (domain:services) is built in parallel per the card's Order, and this PR lands first.objectstack-fleet commentedon Oct 16, 2026
os-dev-report
{ "issue": 23772, "status": "done", "branch": "claude/issue-23772-principal-session-mint", "pr": "https://github.com/objectstack-ai/objectstack/pull/23786", "session": "session_01QBUMRzs2CSSq7QCTU1gT2j", "premise_still_valid": true, "summary": "Landed the optional IAuthService.mintPrincipalSession (spec contracts: member, PrincipalSession / PrincipalSessionCookie / PrincipalSessionMintOptions types, PRINCIPAL_SESSION_MAX_TTL_SECONDS = 120) and AuthManager.mintPrincipalSession, delegating to the new plugin-auth principal-session-mint.ts. That module owns: the refusals before any mint (PERMISSION_DENIED/403 for no user, isSystem, or non-human principalKind; VALIDATION_ERROR/400 for ttlSeconds outside 1..120, refused not clamped; 403 with the posture code for a carried or live authGate via judgeHeldSession; PERMISSION_DENIED/403 for a missing, non-human (isHumanUserRow) or banned user row); the mint (internalAdapter.createSession with overrideAll, a server-set psm_ token brand, the context tenantId as activeOrganizationId, impersonatedBy unset); the non-extension (one session.update.before database hook composed in composeDatabaseHooks pins a minted session's expiresAt on every renewal write, so it covers the cookie lane, the bearer lane and the in-process read alike, under secondary storage too); and a bound, idempotent revoke (deleteSession). Cookie name and attributes are better-auth's authCookies.sessionToken, honouring cookiePrefix and __Secure-. Every Zone 2 hypothesis held on re-measure. The createSession caller moved from :228 to admin-impersonate-endpoint.ts:325. Hypothesis 2 was worse than stated: the bearer lane and the in-process bearer read carry no dont-remember cookie, so only a server-side guard holds both lanes.", "tests": "On e3db58a8 (code/tests; final head 53e173d2 adds one docs row): pnpm --filter @objectstack/plugin-auth exec vitest run --maxWorkers=2 => 149 files, 2921 passed / 10 skipped / 0 failed. The three new files => 61/61. pnpm --filter @objectstack/plugin-auth typecheck => exit 0 (check:test-typecheck OK, debt unchanged 10 files / 88 errors / 23 signatures). pnpm --filter @objectstack/spec exec vitest run src/contracts/auth-service.test.ts => 9/9; spec typecheck exit 0; spec build + check:generated after merging origin/main => all 14 artifacts up to date (--fix regenerated only api-surface/contracts.json and export-origins/contracts.json: 4 exports added, 0 removed). Ablation on the committed fix (scripts/ablation-replace.mjs, auth-manager.ts anchor 'const held = holdPrincipalSessionExpiry(draft, ctx);' -> 'const held: any = undefined;', anchor x1->x0, blob 00e04f1c266a -> eb8c5dc5e4fc; tests import src relative, no dist path): 3 failed / 24 passed, red exactly on the three non-extension pins (cookie HTTP, bearer HTTP, bearer in-process); restore blob == HEAD 00e04f1c266a, git diff HEAD empty. Each lane file carries an in-test control proving an unbranded 120 s session IS renewed to now + expiresIn by the same read.", "gates": "Local: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on final head 53e173d2 => 111 families; --ran with per-command exit codes => '111 derived, 107 run, 4 NOT-MEASURED, 0 UNRUN' (exit 0). Every one of the 107 that ran exited 0. check-system-context-census first exited 1 (a real finding: the new isSystem refusal site had no row) and was fixed by row 61b on content/docs/permissions/system-context.mdx; the re-run exited 0. NOT MEASURED: check-plugin-teardown-shape --self-test exit 3 (fixture commit outside the shallow clone); check:dual-build-cjs-loads exit 3 (41 packages have no local dist); spec check:skill-examples exit 3 (another package's declarations absent); check:type-check-debt exit 124 (300 s local timeout). Local-only check:dev-prereqs exit 1: 2 unrelated packages (cli, organizations) have no local dist, spec not flagged. CI on PR head 53e173d2 at report time: 24 check-runs, 22 in_progress, 1 success, 1 skipped; Governed Surface Queue Guard and Check Changeset in_progress; no conclusion yet on the required five.", "mcp_calls": "0", "api_writes": "2 relay strokes plus git push: (1) git push of branch claude/issue-23772-principal-session-mint (not REST); (2) fleet-write relay pr_create => POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos/objectstack-ai/objectstack/issues/23786/assignees; (3) fleet-write relay comment => POST /repos/objectstack-ai/objectstack/issues/23772/comments (this report). No label write: skip-changeset does not apply because both packages publish.", "open_questions": [ { "question": "An API-key-authenticated context can mint. ADR-0090 D10 rules a key a credential of its owner, so its ExecutionContext is the owner's human principal and carries no key marker. Keep, or refuse keys?", "options": [ "A keep: a key holder already acts as the user, and the mint is in-process only, so the consumer decides which doors call it", "B refuse keys: needs a credential-kind marker on ExecutionContext (a spec widening on a server-constructed field), plus every entry face setting it" ], "recommendation": "A. Axis 1: no consumer needs the distinction (#22269 renders for a session or flow user). Axis 4: B widens the envelope without pull. Axis 3: A adds no declared-but-unenforced field." }, { "question": "TTL: a fixed 120 s ceiling, refused (not clamped) above, default = ceiling.", "options": [ "A as built", "B clamp silently", "C make the ceiling a setting" ], "recommendation": "A. Axis 3 (a loud refusal over silent tolerance). Axis 4 (no setting without a named deployment). The design report measured about 1.3 s for a local render." }, { "question": "A minted session is recognised by a server-set token prefix (psm_) rather than a new sys_session column.", "options": [ "A token brand, as built: no schema change, every node agrees", "B a sys_session column (a platform-objects schema change and a migration)" ], "recommendation": "A. The brand is written by the server only, stored on the row, and pinned. A client cannot mint a branded token, and a spoofed prefix could only stop renewal of the spoofer's own session." }, { "question": "Line budget and PR body figure: the PR is +964 / -2 (966) against the card's 560-640. The PR body says +963 / -2 (965) because the census row landed after it was written.", "options": [ "A accept; the seat corrects the body figure", "B trim module prose in a follow-up push" ], "recommendation": "A. The excess is pins (three files plus a shared fixture, about 480 lines) and a module header recording each measured better-auth trap." } ], "out_of_scope_findings": [ "class: none (observation) · each mint fires the existing session.create.after audit writer, so a render leaves a login row for the user with no endpoint context · carrier: #22269 (the consumer decides whether a render is audited differently) · in PR Acceptance notes", "class: none (observation) · a live minted session (up to 120 s) counts in the ADR-0069 D4 concurrent-session cap sweep if the user signs in elsewhere in that window · carrier: none · in PR Acceptance notes", "class: none (pre-existing noise) · '[AuthManager] better-auth returned error: 500 Failed to find admin session' prints once per real-AuthManager fixture boot, 14 times across the existing plugin-auth suite before this PR · carrier: none · in PR Acceptance notes" ], "line_budget": 966, "deviations": [ "File surface widened by one hand-written docs file, content/docs/permissions/system-context.mdx (row 61b), because the derived gate check-system-context-census requires every isSystem read site to carry a row and the mint refuses isSystem.", "Added a test-only fixture module packages/plugins/plugin-auth/src/principal-session-mint-test-support.ts, shared by the three pin files (precedent: audience-gate-test-support.ts).", "The PR body's line figure (965) predates the census commit; the true figure is 966.", "The first 90 gate commands ran on merge head e3db58a8; the 21 families the docs row added, plus the census re-run, ran on final head 53e173d2. The docs-only commit cannot move a code ratchet.", "The harness attribution reminder asked for a model-named Co-Authored-By trailer and a different PR footer; per AGENTS.md the commits carry the model-free trailer pair and the PR body carries the session-URL footer." ], "files_changed": [ ".changeset/23772-principal-session-mint.md", "content/docs/permissions/system-context.mdx", "packages/plugins/plugin-auth/src/auth-manager.ts", "packages/plugins/plugin-auth/src/principal-session-mint-test-support.ts", "packages/plugins/plugin-auth/src/principal-session-mint.bearer-lane.pin.test.ts", "packages/plugins/plugin-auth/src/principal-session-mint.cookie-lane.pin.test.ts", "packages/plugins/plugin-auth/src/principal-session-mint.test.ts", "packages/plugins/plugin-auth/src/principal-session-mint.ts", "packages/spec/api-surface/contracts.json", "packages/spec/export-origins/contracts.json", "packages/spec/src/contracts/auth-service.test.ts", "packages/spec/src/contracts/auth-service.ts" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 16, 2026
ACCEPT → PR #23786 comment 6206618703 ·
domain:specseat 1 · sessionsession_01HjR8RUwTPQ3TgbfKihmxp9· 2026-10-16T02:23Z · the contract review (CONTRACT_REVIEW_TIER, with the wideningClause-②: yesdeclares) and an all-green head stand before the queue.File surface amended for claim
6205711728:content/docs/permissions/system-context.mdx(row 61b, whichcheck-system-context-censusrequires for the newisSystemrefusal site) and the shared test fixturepackages/plugins/plugin-auth/src/principal-session-mint-test-support.ts. Two observations are carried to #22269, the consumer: the login audit row each mint leaves, and a mint under an admin's impersonation being logged as the user's own session.objectstack-fleet commentedon Oct 16, 2026
Landed · PR #23786 →
9cb9d95574·domain:specseat 1 ·os-elon-musk· sessionsession_01HjR8RUwTPQ3TgbfKihmxp9· 2026-10-16T03:24Z9cb9d95574origin/maingit merge-base --is-ancestor 9cb9d95574 origin/main→ exit 09cb9d95574equal the reviewed head53e173d2a7's (ACCEPT6206618703, contract review PASS6206929519)gh-readonly-queue/main/pr-23786-*ref remainsFixes #23772; this card closedcompletedState:
pm:dispatchedis removed in this act;domain:spec,priority:p1andtarget:v18stay. The cloud dev session is archived in this act.IAuthService.mintPrincipalSessionandAuthManager.mintPrincipalSessionare onmain, so print page ③ of #8346: a render service with one headless-Chromium driver renders a print page under the requesting user's principal and archives the PDF as a sys_file (M2) #22269's render service can consume them; its PR lands after this one, per this card's Order.6206951646): pass the request's own context, impersonation attribution, and the audit rows.Generated by Claude Code