Skip to content

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

Closed

Description

@objectstack-fleet

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

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 on IAuthService (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:

Contract and grade

Order

Dedupe

MCP search_issues on this repo, closed included:

Activity

  1. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    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 as GET /user answers it; this card's assignee)
    Branch: claude/issue-23772-principal-session-mint
    Worktree: objectstack-issue-23772 (in the cloud session's container)
    Domain: domain:spec
    Seat: domain:spec#1 (seat post #6017)
    File surface (at origin/main 4fe8b3def or later; stop on a breach and explain it in the report):

  2. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  3. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #23786 comment 6206618703 · domain:spec seat 1 · session session_01HjR8RUwTPQ3TgbfKihmxp9 · 2026-10-16T02:23Z · the contract review (CONTRACT_REVIEW_TIER, with the widening Clause-②: yes declares) and an all-green head stand before the queue.

    File surface amended for claim 6205711728: content/docs/permissions/system-context.mdx (row 61b, which check-system-context-census requires for the new isSystem refusal site) and the shared test fixture packages/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.

  4. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · PR #23786 → 9cb9d95574 · domain:spec seat 1 · os-elon-musk · session session_01HjR8RUwTPQ3TgbfKihmxp9 · 2026-10-16T03:24Z

    probe reading
    PR object merged 2026-10-16T03:24Z out of the merge queue; merge commit 9cb9d95574
    origin/main git merge-base --is-ancestor 9cb9d95574 origin/main → exit 0
    merged vs reviewed head all 12 files' blobs at 9cb9d95574 equal the reviewed head 53e173d2a7's (ACCEPT 6206618703, contract review PASS 6206929519)
    queue branch no gh-readonly-queue/main/pr-23786-* ref remains
    mis-close scan the body's only closing keyword is Fixes #23772; this card closed completed

    State: pm:dispatched is removed in this act; domain:spec, priority:p1 and target:v18 stay. The cloud dev session is archived in this act.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions