Skip to content

plugin-auth: an OAuth consent binds the consenting user's current organization, the access token carries it as organization_id, and verifyMcpAccessToken returns it (slice 2 of #24137, ruled A at 6232069920) #24166

Closed

Description

@objectstack-fleet

Carries item 5, second slice, of #24137 (plugin-auth's consent binding and the claim); #24137 keeps the runtime read and its pins. Its first upstream slice is #24165 (the packages/spec contract type).

Blocked-by: #24165

Filing gate ③, a maintainer-directed task: ruled at 6232069920 on #24137, letter A, the maintainer's word 「同意」, recorded by the triage seat. Filed by domain:cli seat 1 (#6024) · os-sales · session session_01BVm1pAXVXQ3PjC42j1kPFf. ⛔ Not a claim.

What the ruling binds, for this slice (verbatim from 6232069920)

  • "Binding: at consent, plugin-auth records the consenting user's current organization through the installed OAuth provider's declared seam (postLogin.consentReferenceId / customAccessTokenClaims). The access token carries it as the claim organization_id."
  • "Existing consents re-confirm once. Until then, an old refresh family has global grants only. ⛔ No C1 underneath A as a fallback (no dual path)."
  • The execution plan: "plugin-auth's consent binding and the claim. Services seat, declared cross-lane."

The change, located by #24137's dev on origin/main c40506aac (its os-dev-report)

  • packages/plugins/plugin-auth/src/auth-manager.ts, the oauthProvider options. Today they are allowDynamicClientRegistration, allowUnauthenticatedClientRegistration, clientRegistrationDefaultResources, consentPage, loginPage, resources, schema and scopes; there is no postLogin and no customAccessTokenClaims.
    • postLogin.consentReferenceId records the consenting session's active organization, which becomes the consent's and the refresh family's reference_id (measured null today).
    • customAccessTokenClaims emits it as organization_id. It is not an RFC 9068 reserved name, which the installed provider would strip.
  • verifyMcpAccessToken (about :6786) returns it as the organizationId member spec(contracts): the verified MCP access token result carries organizationId, the organization an OAuth consent bound (slice 1 of #24137, ruled A at 6232069920) #24165 declares.
  • Rider, domain:engine (declared cross-lane by the claimant): the reference_id field description on sys_oauth_consent, sys_oauth_refresh_token and sys_oauth_access_token in packages/platform-objects changes from "caller-supplied correlation identifier" to the bound organization, with any bundled translation regenerated by the repo's tooling.
  • Pins: a real-flow pin in plugin-auth (the authorize → consent → token flow the dev drove): the token carries organization_id equal to the consenting session's active organization, the consent row's reference_id is set, and a token minted without a consent-time organization carries none.

Reader who acts

The domain:services seat (plugin-auth; this card self-labels domain:services under the filer exception, so triage spot-checks the route). #24137, the runtime read, waits on this card and on #24165.

Dedupe

The same query as #24165 (MCP search_issues, closed included, 4 hits; #16418 the control): none is this binding.

Dedupe words: OAuth consent reference id organization; customAccessTokenClaims organization_id plugin-auth; postLogin consentReferenceId active organization.

Activity

  1. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    Upstream slice #24165 is ACCEPTed and queued as PR #24189 (contract review PASS 6237222227) · domain:spec seat 1 · os-project-manager · session session_01TyRhWbVbbQpQJTDbW8VahJ · 2026-10-17T17:10Z. ⛔ Not a claim, ⛔ no label change, no reply owed.

    When it lands, IAuthService.verifyMcpAccessToken's result type carries organizationId?: string. Two notes for this card:

    • Return type. AuthManager.verifyMcpAccessToken's own return type (packages/plugins/plugin-auth/src/auth-manager.ts, about :6788) still spells the three-member shape. It is not typed against the interface, so this card extends it when it starts returning the organization.
    • No M2M case. The contract's TSDoc names the member absent only for a token minted before consents bound an organization. The implementer's refusal of a client_credentials token (sub equal to client_id or azp) stays as it is.

    Generated by Claude Code

  2. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    Unlock: pm:blocked → pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-17T18:02Z

    Triage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 · 2026-10-17T18:49Z
    Session: session_01Nk17Z26X5DB59iQztDBADH (the dispatching seat; the dev runs mode:subagent in this session's container and shares its session ID, so its identity is the branch)
    Account: marchtian (the seat's linked user as GET /user answers it; the card's assignee from this act)
    Branch: claude/issue-24166-oauth-consent-organization
    Worktree: objectstack-issue-24166
    Domain: domain:services
    Seat: domain:services#1 (seat post #6021)
    File surface (at origin/main a237d7f496 or later; stop on a breach and explain it in the report):


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 24166,
      "status": "done",
      "branch": "claude/issue-24166-oauth-consent-organization",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/24218",
      "session": "session_01Nk17Z26X5DB59iQztDBADH (mode:subagent: the dispatching seat's id, shared; this run's identity is the branch)",
      "premise_still_valid": true,
      "summary": "Implemented ruling 6232069920 A's plugin-auth slice. AuthManager's oauthProvider options gain postLogin { page: the consent page, shouldRedirect: () => false, consentReferenceId: the consenting session's activeOrganizationId or undefined } and customAccessTokenClaims ({ referenceId }) => organization_id when a reference exists; verifyMcpAccessToken's own return type gains organizationId?: string and returns the claim, refusing a token whose organization_id is present but not a non-empty string (fail-closed, this server never mints one; flagged for the seat's contract review since the claim did not name it). All four seat premises held on the installed @better-auth/oauth-provider@1.7.3 dist: the hook value becomes the consent row's and the refresh family's reference_id and reaches customAccessTokenClaims on code exchange and refresh; organization_id is not a reserved claim; the hook sees only { user, session, scopes }; and the provider's own consent lookup filters by reference whenever the hook answers one, so a null-reference consent re-prompts once with no extra wiring. platform-objects: the reference_id description on sys_oauth_consent / sys_oauth_refresh_token / sys_oauth_access_token names the bound organization (en rows regenerated by pnpm i18n:extract; the zh-CN/ja-JP/es-ES values re-translated by hand, see deviations); sys_oauth_application.reference_id unchanged because the provider writes it only from clientReference, which plugin-auth does not pass. Clause-② measured on built declarations BASE a237d7f496 vs head: plugin-auth dist/index.d.ts and .d.mts add exactly one declaration line, organizationId?: string; at head line 3222 inside verifyMcpAccessToken's result (head 3218-3223, BASE 3208-3212), plus docblock prose; platform-objects' four .d.ts/.d.mts files byte-identical (md5 equal). PR #24218 is draft, assignee marchtian, body first line Fixes #24166, Clause-② line at line start in body and changeset; CI was in progress at report time.",
      "tests": "Final head 81b7b99c2, every run through scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT set (shared-box seconds). (1) pnpm --filter @objectstack/plugin-auth typecheck then pnpm --filter @objectstack/plugin-auth exec vitest run --maxWorkers=2: 'check:test-typecheck: OK', 'Test Files 157 passed (157)', 'Tests 3084 passed | 10 skipped (3094)', 'VERDICT command-exit 0' (the same counts at 45d646ada before the main merge). (2) pnpm --filter @objectstack/platform-objects typecheck then its vitest run --maxWorkers=2 --passWithNoTests: 'check:test-typecheck: OK', 'Test Files 73 passed (73)', 'Tests 1136 passed (1136)', 'VERDICT command-exit 0'. (3) New real-flow pin src/auth-manager.oauth-consent-organization.test.ts (real AuthManager options booted in a real better-auth server with the real provider and organization plugin; DCR, sign-up, organization, authorize, consent, token, refresh), verbose at 1371aa7a3: 5/5 passed - claim and both reference_id rows equal the session's org and verifyMcpAccessToken returns organizationId; refresh keeps the consent's org after the session switches; no active org gives no reference, no claim, no organizationId member; organization_id/organizationId/reference_id/referenceId authorize params change nothing recorded; a null-reference consent is honoured with no org (control), re-prompts once under an org, does not prompt again, and the old refresh family still mints no claim. Plus 3 verifier claim-shape pins in src/auth-manager.mcp-oauth.test.ts. (4) First typecheck run was red (TS2339 getJwks on the inferred api type in the new test), fixed in 45d646ada, then green. (5) Ablation once via node scripts/ablation-replace.mjs (subject imported from ./auth-manager source, no dist leg owed): anchor 'consentReferenceId: ({ session }) => consentOrganizationOf(session),' x1 -> x0, replacement '() => undefined' x0 -> x1, blob ab0876f238ad -> 8422b44fd87d; result 'Tests 4 failed | 1 passed (5)' (direction as predicted: the no-org case asserts absence and stays green); restore 'blob == HEAD (ab0876f238ad) and git diff HEAD is empty'; VERDICT command-exit 1 for the ablated run.",
      "gates": "Local: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths) on head 81b7b99c2, merge base 8259ea03e, 12 paths, +578/-19: 69 commands, sorted set difference against the seat's 69 empty. All 69 run, exit codes captured before any pipe (redirect then EXIT=$?): 68 exit 0 first pass; pnpm check:dual-build-cjs-loads first exit 3 PREREQUISITE NOT MET (packages outside the diff had no dist), re-run after turbo run build --filter='!@objectstack/docs' (75/76 cached) exit 0 '111 published require entry point(s) across 69 package(s) load'. Verdict lines: 'check-i18n-bundles: OK (10 package(s) — all bundles in sync, no undeclared authoring keys)', 'check-i18n-stale-fill: OK (11 bundle set(s) — no new stale fills, 0 baselined)', 'check-nul-bytes: OK', 'check-adr-0087-registration: this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen)'. --ran reconciliation exit 0: '69 derived famil(ies) accounted for — 69 run, 0 NOT-MEASURED'. check-platform-checklist not owed (no exported symbol renamed or removed); check:dev-prereqs and check:skill-frame-freshness not owed (no packages/spec or frame edit). CI (PR #24218 check-runs on 81b7b99c2, read once right after pr_create): success - Check PR Size, Check Documentation Links, filter, 'No other open PR may claim the same issue', 'No other open PR may claim the same single-writer path', 'Part-of PR must not also close its card', 'The card this PR closes must claim this branch'; skipped - Build Docs, Console Pin Gate, Dogfood Regression Gate (matrix), Dogfood Verify CLI, Packed-tarball smoke, Temporal Conformance; in_progress - Build Core, Test Core (1-6/6), every Lint and Type Check lane, Check Changeset, Governed Surface Queue Guard, Dogfood Regression Gate, Auto Label. NOT MEASURED: every in_progress family, reason: CI had not concluded at report time; convergence is the PM's.",
      "mcp_calls": "0",
      "api_writes": "2 relay strokes (seat side: 2 x POST /repos/objectstack-ai/objectstack/dispatches via scripts/pm/fleet-write/dispatch.mjs and scripts/pm/post-stamped.mjs), executed as objectstack-fleet[bot]: (1) pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft forced) + POST /repos/objectstack-ai/objectstack/issues/24218/assignees ['marchtian'], run 39063992981 success, body read back identical (11601 bytes); (2) this os-dev-report = POST /repos/objectstack-ai/objectstack/issues/24166/comments. No labels written (no skip-changeset: the PR carries a changeset; the dispatch named no other label). git push (not REST): the empty probe, then 5 pushes of the branch up to 81b7b99c2. Reads: REST single-card GETs of #24166, #24137, #24165 and their comments, GET pulls/24218, GET commits/81b7b99c2/check-runs.",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: #24137 · noted, not filed — the installed provider ranks a resource row's customClaims above customAccessTokenClaims (introspect-CbhhXT0E.mjs:229-262), so an organization_id in sys_oauth_resource.custom_claims on the MCP resource would replace the consent-bound organization on every MCP token. Boundary note (configuration the runtime honours, not one it drops), so outside class c. Dedupe words: oauth resource custom_claims organization_id precedence; customAccessTokenClaims override resource policy claims; MCP token organization claim admin override",
        "carrier: #24137 · noted, not filed — premise 4 reading: vetOrganizationClaim (packages/core/src/security/resolve-authz-context.ts:762-770) keeps a claim with no membership read under single or no posture; the runtime read will reuse that session arm, so under single a former member's OAuth token keeps resolving in its bound organization, exactly as a REST session's claim does. Read-only inference, no public door measured. Dedupe words: vetOrganizationClaim single posture membership; OAuth organization_id membership recheck; former member agent token organization"
      ],
      "files_changed": [
        ".changeset/24166-oauth-consent-organization.md",
        "packages/platform-objects/src/apps/translations/en.objects.generated.ts",
        "packages/platform-objects/src/apps/translations/es-ES.objects.generated.ts",
        "packages/platform-objects/src/apps/translations/ja-JP.objects.generated.ts",
        "packages/platform-objects/src/apps/translations/zh-CN.objects.generated.ts",
        "packages/platform-objects/src/identity/sys-oauth-access-token.object.ts",
        "packages/platform-objects/src/identity/sys-oauth-consent.object.ts",
        "packages/platform-objects/src/identity/sys-oauth-refresh-token.object.ts",
        "packages/plugins/plugin-auth/src/auth-manager.mcp-oauth.test.ts",
        "packages/plugins/plugin-auth/src/auth-manager.oauth-consent-organization.test.ts",
        "packages/plugins/plugin-auth/src/auth-manager.ts",
        "packages/plugins/plugin-auth/src/auth-schema-config.ts"
      ],
      "line_budget": {
        "additions": 578,
        "deletions": 19,
        "basis": "git diff three-dot vs merge base 8259ea03e, 12 files, as dispatch-gates counts it"
      },
      "deviations": [
        "Translated-locale values: the reference_id help leaf on the three objects was re-translated by hand in zh-CN, ja-JP and es-ES (9 leaves). AGENTS.md's documentation-guardrails row makes translated-locale values hand-written; the claim's 'never by hand' was read as binding the generated en rows, which were regenerated by pnpm i18n:extract (the generator then kept the new translations, source-hash companions unchanged). Without it the three locales would keep serving 'caller-supplied correlation identifier'. The seat may revert those 9 leaves if it reads the clause otherwise.",
        "auth-schema-config.ts (one doc comment on the oauthConsent mapping that this change made false) is outside the claim's named files but inside the dispatch's expected landing site packages/plugins/plugin-auth/src/**.",
        "verifyMcpAccessToken refuses a token whose organization_id is present but not a non-empty string: not in the claim's text, chosen as the method's existing fail-closed posture over reading it as absent (a lenient consumer read). Only an admin-written resource custom claim could produce such a token. Named for the seat's contract review, which may weigh it against the widening arm.",
        "origin/main merged once before opening (8259ea03e, AGENTS.md multi-agent section 10: rest, service-settings and dogfood only), then rebuilt and re-measured. The merge commit carries git's default message with no trailer pair.",
        "Builds that could outrun the foreground cap ran as nohup os-verify-lock.sh jobs and were waited on in the foreground with tail --pid, never polled; all have exited. The BASE side of the Clause-② measurement was built in a second, detached worktree at a237d7f496 (objectstack-issue-24166-base), since removed.",
        "test:repo (vitest repo project) not run locally: the diff changes none of its tests and the derived list does not name it; CI's Test Core runs it.",
        "The harness attribution reminder asks for a model-named Co-Authored-By trailer; commits carry AGENTS.md's model-free pair (Claude-Session + Co-authored-by: Claude) instead."
      ]
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat record on the dev report · domain:services seat 1 (#6021) · marchtian · session_01Nk17Z26X5DB59iQztDBADH · 2026-10-17T19:54Z. PR #24218, head 81b7b99c2c. Amends claim 6238919385: same session, same branch. ⛔ Not a new claim.

    • Premises, re-read by the seat in the published @better-auth/oauth-provider@1.7.3 (dist/authorize-*.mjs, dist/introspect-*.mjs, the lockfile pin):
      • postLogin.consentReferenceId is called with { user, session, scopes } only, both at authorize and at consent accept. The value becomes the consent's referenceId.
      • The consent lookup filters on referenceId whenever the hook answers one, so a consent stored with a null reference re-prompts once under an organization.
      • shouldRedirect: () => false adds no redirect.
      • customAccessTokenClaims receives referenceId.
      • All four premises hold as the dev measured them.
    • Accepted deviations:
      • The nine hand-translated zh-CN / ja-JP / es-ES leaves follow AGENTS.md's row for *.generated.ts: translated-locale values are hand-written, and only en is regenerated. That is what the claim meant, and check:i18n-bundles and check:i18n-stale-fill are green.
      • auth-schema-config.ts's doc comment is a text this change made false.
      • The verifier refuses a present-but-malformed organization_id. This is weighed in the contract review of record, owed on the final head.
    • Not yet accepted: patch round 1 is out to the dev.
      1. The changeset ("The caller vets it against a current membership on every request.") and verifyMcpAccessToken's new docblock ("the caller vets it against a current membership on every request") promise more than the contract and the code give. IAuthService.verifyMcpAccessToken's TSDoc says the caller vets the claim "by the rule that vets a session's active organization (vetOrganizationClaim, @objectstack/core)". The dev's own reading, in this PR's Acceptance notes, is that this rule reads no membership under single or with no posture. Both sentences are to say what the contract says. The changeset ships to the CHANGELOG, and an authorization guarantee stated wider than it is must not ship.
    • File surface, amended to the diff: packages/plugins/plugin-auth/src/auth-schema-config.ts (one doc comment) and packages/platform-objects/src/apps/translations/{en,zh-CN,ja-JP,es-ES}.objects.generated.ts (the three reference_id leaves each).
    • The dev's two notes go to finding(mcp/oauth): an OAuth-token MCP agent acting for an ordinary user is denied every object operation — the D10 delegator is resolved with no active organization, so the user's organization-scoped grants do not count #24137 (the runtime read). They are posted there at ACCEPT, as one comment:
      • a resource row's customClaims outranks customAccessTokenClaims;
      • vetOrganizationClaim reads no membership under single.

    Generated by Claude Code

  6. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 24166,
      "status": "done",
      "branch": "claude/issue-24166-oauth-consent-organization",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/24218",
      "session": "session_01Nk17Z26X5DB59iQztDBADH (mode:subagent: the dispatching seat's id, shared; this run's identity is the branch)",
      "premise_still_valid": true,
      "summary": "Patch round 1 per seat record 6239991736. I read it before editing: it amends claim 6238919385 and accepts the four premises and the three deviations. Two sentences now state what IAuthService.verifyMcpAccessToken's TSDoc states: the caller vets the claim on every request by the rule that vets a session's active organization (vetOrganizationClaim, @objectstack/core). One is the changeset's last sentence under AuthManager.verifyMcpAccessToken. The other is the matching sentence in verifyMcpAccessToken's docblock in auth-manager.ts (commit 099568c28; 3 insertions, 2 deletions). Neither restates a membership guarantee, and no other added line in the PR diff says 'membership' (grep over the diff against the merge base: zero hits). The changeset keeps Clause-②: yes (widening), plugin-auth minor and platform-objects patch. Nothing else changed. I recreated the worktree from the pushed branch (81b7b99c2) and merged origin/main twice with plain merges, no rebase and no force: a0f3c8304 brought a06f0d414 (metadata-protocol, rest, objectql, runtime) and 1e4146df8 brought a19b2a7a5 (a platform-objects dashboard). Neither merge touched a generated file, so os-regen-merge.sh was not needed and os-regen-pending was empty. The new head is 1e4146df8. b7f29ddfe (cli only) landed after that and is not merged. The derivation names its scripts/check-cli-test-child-env.mjs as changed upstream, but that family is not in this card's 69. PR #24218 stays draft with assignee marchtian. I wrote no labels: documentation, size/l, tests and tooling come from the labeler. pr_body_append below is for the seat to append. I did not PATCH the PR body.",
      "tests": "Head 1e4146df8. Every run went through scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT set (shared-box seconds), after a post-merge turbo run build --filter='!@objectstack/docs' (76 tasks, 35 cached, VERDICT command-exit 0). (1) pnpm --filter @objectstack/plugin-auth typecheck: 'check:test-typecheck: OK — ... 10 file(s) / 88 error(s) / 23 pinned signature(s) held'. (2) pnpm --filter @objectstack/plugin-auth exec vitest run --maxWorkers=2: 'Test Files 157 passed (157)', 'Tests 3084 passed | 10 skipped (3094)'. (3) The new pin, verbose: 5 of 5 passed (593, 157, 133, 130 and 157 ms). Runs 1 to 3 shared one lock hold: 'VERDICT command-exit 0', 378 s. (4) The merge brought a platform-objects change, so I also ran pnpm --filter @objectstack/platform-objects exec vitest run --maxWorkers=2 --passWithNoTests: 'Test Files 74 passed (74)', 'Tests 1139 passed (1139)', 'VERDICT command-exit 0'. No ablation: the change is prose only, as the patch order says.",
      "gates": "Local: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, no paths, on head 1e4146df8 (merge base a19b2a7a5, 12 paths, +579/-19). It derived 69 commands; the sorted set difference against the seat's 69 is empty. I ran all 69, capturing each exit code before any pipe (redirect, then EXIT=$?). All 69 answered exit 0. check:dual-build-cjs-loads was measured this time, not refused: '111 published require entry point(s) across 69 package(s) load'. Other verdict lines: 'check-i18n-bundles: OK (10 package(s) — all bundles in sync, no undeclared authoring keys)', 'check-i18n-stale-fill: OK (11 bundle set(s) — no new stale fills, 0 baselined)', 'check-nul-bytes: OK (scanned 12070 text file(s)...)', 'check-adr-0087-registration: this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen)'. --ran exit 0: '69 derived famil(ies) accounted for — 69 run, 0 NOT-MEASURED'. CI on 1e4146df8, from PR #24218's check runs read once after the push (39 runs): success for Lint & Repo Gates, TypeScript Type Check, Build Core, Governed Surface Queue Guard, Check Changeset, Test Core 1/6, 2/6, 5/6 and 6/6, every Lint and Type Check lane, Dogfood Regression Gate, Auto Label, Check PR Size, Check Documentation Links, Flag docs affected by code changes, filter, and the four claim and closing guards; skipped for Build Docs, Console Pin Gate, Dogfood Regression Gate (matrix), Dogfood Verify CLI, Packed-tarball smoke and Temporal Conformance; in_progress for Test Core (3/6) and (4/6). NOT MEASURED: those two Test Core shards, because CI had not concluded when I read it.",
      "mcp_calls": "0",
      "api_writes": "1 relay stroke this round: POST /repos/objectstack-ai/objectstack/dispatches via scripts/pm/post-stamped.mjs, executed as objectstack-fleet[bot] as POST /repos/objectstack-ai/objectstack/issues/24166/comments (this os-dev-report). I made no PR body PATCH, no label write and no assignee write. git push is not REST: 2 pushes this round, 099568c28 with merge a0f3c8304, then 1e4146df8. Reads: GET issues/comments/6239991736, GET commits/1e4146df8/check-runs, GET pulls/24218.",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: #24137 · noted, not filed — unchanged from round 0. The seat posts both notes to #24137 at ACCEPT: a resource row's customClaims outranks customAccessTokenClaims, and vetOrganizationClaim reads no membership under single."
      ],
      "pr_body_append": "## Patch round 1 (seat record `6239991736`)\n\n- Two sentences now say what the `IAuthService.verifyMcpAccessToken` contract says, and no more: the caller vets the organization on every request by the rule that vets a session's active organization (`vetOrganizationClaim`, `@objectstack/core`). They are the changeset's last sentence under `AuthManager.verifyMcpAccessToken`, and the matching sentence in `verifyMcpAccessToken`'s docblock (commit `099568c28`). Neither restates a membership guarantee, because that rule reads no membership under `single` or with no posture (see the acceptance notes). Nothing else changed: the changeset keeps `Clause-②: yes (widening)`, `@objectstack/plugin-auth` minor and `@objectstack/platform-objects` patch.\n- `origin/main` merged twice (`a0f3c8304`, `1e4146df8`), with no generated file in either merge. The head is `1e4146df8`. `b7f29ddfe` (cli only) landed afterwards and is not merged.\n- Re-measured on `1e4146df8` through the verify lock:\n  - `pnpm --filter @objectstack/plugin-auth typecheck`: `check:test-typecheck: OK`.\n  - `vitest run --maxWorkers=2`: `Test Files 157 passed (157)`, `Tests 3084 passed | 10 skipped (3094)`.\n  - The pin file, verbose: 5 of 5 passed.\n  - `platform-objects` `vitest run`: `Test Files 74 passed (74)`, `Tests 1139 passed (1139)`.\n  - Every run ended `VERDICT command-exit 0`.\n- Gates: `dispatch-gates --commands --repo objectstack-ai/objectstack` on `1e4146df8` (12 paths, +579 / -19) derives the same 69 commands. All 69 answered exit 0. `--ran` reports `69 run, 0 NOT-MEASURED`.\n- No ablation, since the change is prose only.",
      "files_changed": [
        ".changeset/24166-oauth-consent-organization.md",
        "packages/plugins/plugin-auth/src/auth-manager.ts"
      ],
      "line_budget": {
        "additions": 3,
        "deletions": 2,
        "basis": "this round's commit 099568c28 alone; the whole PR is now +579/-19 over 12 files against merge base a19b2a7a5"
      },
      "deviations": [
        "I did not merge origin/main a third time. b7f29ddfe (cli, plus scripts/check-cli-test-child-env.mjs) landed after I merged 1e4146df8. It touches none of this diff's packages, and its family is not in the 69 this card derives. The PR's merge ref and the queue validate the join.",
        "The two merge commits keep git's default message with no trailer pair.",
        "The PR body still carries the round-0 numbers. The seat appends pr_body_append, as the patch order asks; I did not PATCH the body."
      ]
    }

    Generated by Claude Code

  7. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #24218 comment 6240523832 · domain:services seat 1 (#6021) · session_01Nk17Z26X5DB59iQztDBADH · 2026-10-17T20:28Z. Head 1e4146df8a, round 0 and patch round 1; contract review PASS 6240503112. It lands through the merge queue once every check on its head is green. Fixes #24166 closes this card at the merge. The dev's two notes for the runtime read are on #24137 at 6240513333.


    Generated by Claude Code

  8. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:services seat 1 (#6021) · marchtian · session_01Nk17Z26X5DB59iQztDBADH · 2026-10-17T21:04Z.

    Landed: PR #24218 (Fixes #24166) merged through the merge queue as 99e9d2957e at 2026-10-17T21:02Z. That commit is on origin/main (git merge-base --is-ancestor exit 0; control 04fd88a9b0 exit 0, negative control e594280be9 exit 1). The queue squashed it, and all 12 of its files are byte-identical to the PR head 1e4146df8a. Contract review PASS 6240503112, ACCEPT 6240523832.

    Probe on origin/main Reading
    consentReferenceId: ({ session }) => consentOrganizationOf(session) plugin-auth/src/auth-manager.ts:4116
    const OAUTH_ORGANIZATION_CLAIM = 'organization_id' auth-manager.ts:1368
    auth-manager.oauth-consent-organization.test.ts present

    What an MCP client gets:

    • An OAuth consent records the consenting user's active organization.
    • The access tokens minted from it carry organization_id, and verifyMcpAccessToken returns it as organizationId.
    • A consent stored before this change re-prompts once when the user authorizes in an organization.
    • A user with no active organization gets a token with no claim.

    Next, elsewhere: #24137's runtime read now has both halves it waited on, #24165 and this card. The dev's two notes for it are at 6240513333. Unlocking #24137 is the triage scan's.

    The card closed completed at the merge. pm:dispatched is cleared and the assignee released 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

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions