Repository navigation
spec(identity): EvalUser.isPlatformAdmin is the live PLATFORM_ADMIN standing predicate but is still marked Deprecated and positions-derived — lift the mark, describe it as ADR-0095 D3 standing, date-note ADR-0068 D2/D4 (#21886 spec half) #22012
Description
Activity
- addedbugSomething isn't workingSomething isn't workingarea:identityLogin and identity — sign-up, sessions, organization membership, SSOLogin and identity — sign-up, sessions, organization membership, SSOand removed
on Oct 6, 2026 objectstack-fleet commented
on Oct 6, 2026 ContributorAuthorMore actionsClaim: PM loop round 2 (this card, the spec half of #21886 under the maintainer's ruling A-lite as the director filed it) · 2026-10-06T15:47Z
Session:session_01GV6oYwgc1kWiUCb1YaprQ7
Account:os-warren(the seat's linked user asGET /useranswers it; the card's assignee from this act)
Branch:claude/issue-22012-isplatformadmin-standing
Worktree:objectstack-issue-22012
Domain:domain:spec
Seat:domain:spec#2(seat post #18549)
File surface (atorigin/mainf7b8a593; stop on breach and explain in the report). It is the card's four items and nothing more:packages/spec/src/identity/eval-user.zod.ts: the.describe()and JSDoc ofisPlatformAdminonly. The shape, optionality andcreateEvalUserstay unchanged. Plus a pin in its spec test file underpackages/spec/src/identity/.- Whatever
packages/specgenerators move for that text (content/docs/references/identity/eval-user.mdx,json-schema.manifest/**,spec-changes.json, and the like), and.changeset/22012-*.md. docs/adr/0068-unified-user-context-and-built-in-identity-roles.md: one dated note under D2 and one under D4, with the original wording not rewritten. This is a governed Tier H path, so the PR waits for the maintainer's approval before it lands.content/docs/permissions/permission-metadata.mdx(the "derived, deprecated alias" sentence) andcontent/docs/permissions/authentication.mdx(theisPlatformAdminsentence).
Container & model:M,mode:subagent,model: opus(--tier: no path-derived mandate; the at-tier contract review is owed by the path face (packages/spec/src/**non-test, plus governeddocs/adr/**), from an isolated subagent).
Clause-②: no (a description and JSDoc change on a published key: the accept set and the public type surface are unchanged)
Thread-read: none
Serial constraints cleared: none of the 5 open PRs touchesidentity/eval-user.zod.ts,docs/adr/0068-*,content/docs/permissions/permission-metadata.mdxorauthentication.mdx(scan at this stamp), and no liveClaim:names them. Downstream, [finding]sys_member.add_memberis offered to every organization member, owners and admins included, but its door admits only a platform admin #21886 (Blocked-by: #22012) and identity: the platform-admin-gated actions onsys_user,sys_oauth_applicationandsys_sso_providershow no standing term invisible— the affordance family's closing card, with an enumeration pin (after #21886's ruling) #21903 (Blocked-by: #21886) are unclaimed and wait on this landing. This seat's analytics: a dataset answer's measure column does not state its aggregate when the author labelled it, so a chart cannot tell a count from a sum (objectui#11681's producer half) #21995 shares only os-regen-managed spec aggregates, so the second to land mergesmainand regenerates them.
Generated by Claude Code
objectstack-fleet commented
on Oct 6, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22012,
"status": "done",
"branch": "claude/issue-22012-isplatformadmin-standing",
"pr": "#22018",
"session": "session_01GV6oYwgc1kWiUCb1YaprQ7 (subagent: the PM's id, as the dispatch states)",
"premise_still_valid": true,
"summary": "All four A-lite items are in one draft PR, #22018, at head 39076f8 on BASE 1bc6ca1. (1) eval-user.zod.ts: only the isPlatformAdmin JSDoc and .describe() change. They describe the key as the PLATFORM_ADMIN standing of ADR-0095 D3, resolved per request from OS_PLATFORM_OWNER_EMAIL, or from an unscoped admin_full_access grant under single, and as the predicate platform-operator gates read (ADR-0068 D4); gate on the key, never the positions array. Shape, optionality and createEvalUser are unchanged. (2) The reference page is regenerated by check:generated --fix: 1 row. (3) ADR-0068 gets two dated notes, under D2 and D4: 4 additions, 0 deletions, original wording untouched, ADR-0095 D3 and EvalUserSchema symbol-anchored. (4) permission-metadata.mdx and authentication.mdx are aligned; the authentication sentence also wrongly listed the platform_admin position as a gate signal. Also a pin test and a patch changeset. The PR is Tier H (docs/adr) and waits for the maintainer.\nPremise verified on 1bc6ca1: platform-admin-gate.ts isPlatformAdminUser reads isPlatformAdmin === true plus the legacy role fallback; customSession emits grants.posture === PLATFORM_ADMIN; resolve-authz-context.ts covers it at sections 6b, 6b-config and 6c and in hasPlatformAdminStanding; reserved-identity-names.ts refuses the name on write.\nH1 CONFIRMED: the only deprecation wording was eval-user.zod.ts:225-226 (grep -ci deprecat gives 2, then 0). The createEvalUser docblock is JUDGED UNCHANGED: 'always derived from positions' is what the factory computes and the ruling keeps. Its pre-existing false clause 'the customSession bridge' is an Acceptance note (plugin-auth has 0 non-test createEvalUser calls).\nH2 CONFIRMED: the ADR-0068 D1 sample line 48 stays; the D2 note names it, the D2 bullet and TL;DR item 2.\nH3 CONFIRMED: only content/docs/references/identity/eval-user.mdx moved (1 of 15 artifacts stale). json-schema.manifest and spec-changes.json did not move. The authorable-surface, authorable-defaults and liveness diff is 0 lines, and the identity/EvalUser:isPlatformAdmin row is unchanged.\nH4 CONFIRMED: both line numbers are right. The hand-written docs tree has 2 isPlatformAdmin hits, 1 with deprecated or alias wording. Repo-wide at BASE there are 16 alias or deprecat lines: 3 fixed here, 6 in ADR-0068 original wording covered by the notes, 7 in plugin-auth comments and tests (an Acceptance note, not edited).\nfiles_changed (7): .changeset/22012-isplatformadmin-standing.md; content/docs/permissions/authentication.mdx; content/docs/permissions/permission-metadata.mdx; content/docs/references/identity/eval-user.mdx; docs/adr/0068-unified-user-context-and-built-in-identity-roles.md; packages/spec/src/identity/eval-user.test.ts; packages/spec/src/identity/eval-user.zod.ts.\nClaim re-verified before the first edit and again before pr_create: the newest Claim: is 6019998508 and names this branch. PR body line 1 is 'Fixes #22012', and a line starting 'Clause-②: no' follows. The PR assignee is os-warren. Worktree and node_modules are removed, and no background process is left.",
"tests": "All gates ran at final HEAD 39076f8; exit codes were captured to disk before any pipe.\nBuild: os-verify-lock 'pnpm --filter @objectstack/spec build', VERDICT command-exit 0. The spec dependency closure is empty, so this is step 1 in full.\nPin: 'pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/identity/eval-user.test.ts' gives 3 passed.\nReverse verification: scripts/ablation-replace.mjs on committed 3570949 put the old describe text back on disk. The anchor went x1 to x0, the replacement x0 to x1, and the blob 73dc1f88 to 565e7b3e. The pin went RED as predicted: 2 failed (names the standing; no deprecation word) and 1 passed (consistency). The tool then restored the file: blob 73dc1f88 equals HEAD, git diff HEAD is empty, and the marker grep counts read 1 and 0. A bash trap with the absolute path was the backup. The test imports ./eval-user.zod, so it reads src and needs no dist.\nSpec suite: 'pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2' gives 620 files, 18488 passed, 1 todo, VERDICT command-exit 0.\nSpec typecheck: 'pnpm --filter @objectstack/spec typecheck' exits 0, covering tsc --noEmit, check:scripts-typecheck and check:test-typecheck; the last compiles the new test file.\ncheck:generated: exit 1 before the fix, with 1 of 15 stale (check:docs). 'check:generated --fix' exits 0: all 15 current, check:authorable-surface green.\nDerived gates: 'node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack' gave 116 commands for 7 paths against merge base 1bc6ca1. All 116 were run and exit 0 at final measurement. '--ran' with ':: exit N' records reports '116 derived, 116 run, 0 NOT-MEASURED, 0 UNRUN (a DERIVED zero)'.\n7 of them first exited 3 (PREREQUISITE NOT MET) and reran with exit 0 once the prerequisite was supplied:\n- check-plugin-teardown-shape --self-test: fixture commit 621a487 was missing from the shallow clone; fetched at depth 1.\n- lint check:doc-formula-expressions and check:doc-security-posture: formula and lint unbuilt; built under the lock.\n- spec check:skill-examples, check:docs-transcript-drift, check:dual-build-cjs-loads, check:lean-entry-closure: unbuilt dists, supplied by the full build that check:type-check-debt runs.\nCompared with the PM's f7b8a59 list, the re-derivation added 14 commands: check-empty-changeset (x2), release-rehearsal-clone and release-pending-publish self-tests, spec check:generated, engine-double-contract, objectql-double-limit, objectui-changeset, pm-changeset-deadline-census, query-options-erasure, quick-reference-counts, type-check-coverage, type-check-debt, where-matcher. All are run and exit 0.\nLint (a declared, proven narrowing; the repo-wide pnpm lint belongs to CI): 'eslint --no-inline-config --format json' on the 2 changed TS files gives 2 files, 0 errors, 0 warnings. (1) Population from eslint.config.mjs: files '**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}', so the md and mdx files are out of scope. (2) Count from the json output. (3) Invariance: the config states it never enables type-aware linting, so untouched files' verdicts cannot move.\nSupplementary: every spec require entry loads under node (19 entries, 0 failures).\nNOT MEASURED, declared for CI: the cli or integration tiers (not touched), and the repo-wide pnpm lint.\nCI state: in_progress at report time; I did not wait for CI.\nDeviations:\n- My first spec-test launch spelled 'pnpm ... test -- --maxWorkers=2', where vitest would drop the flag. I killed it, my own PID, while it was still queued, so it never ran. Its exit removed the shared slot ticket, so I also stopped the relaunch and launched a third time. No measurement was affected.\n- check:type-check-debt's --re-measure runs an unlocked full-workspace 'turbo run build' in the worktree. It overlapped my locked targeted builds and caused two transient TS7016 failures (metadata-protocol, plugin-auth). That was a self-inflicted collision, not a defect; metadata-protocol passed on rerun.\n- I fetched one commit (621a487, depth 1) into the shared object store.\n- A 'bash -c' gate loop was refused by a safety check, and I ran those commands directly.\n- Commit trailers use the model-free pair from AGENTS.md, and the PR footer uses the AGENTS.md session-URL form, not the harness reminder's spelling.",
"mcp_calls": "0 — no MCP GitHub tools used",
"api_writes": "3 — all through the fleet-write relay as POST /repos/objectstack-ai/objectstack/dispatches: (1) pr_create, which relays to POST /repos/objectstack-ai/objectstack/pulls and opened #22018 as a draft; read back 11116 bytes identical; (2) label-write --assign, which relays to POST /repos//issues/22018/assignees with os-warren; read back matching; (3) post-stamped, which relays to POST /repos//issues/22012/comments for this os-dev-report. Reads used gh api GET. The 4 git pushes (the empty-branch probe and 3 commits) are not REST writes.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none (承接者:无) · noted, not filed · class: none (doc drift) · The createEvalUser docblock in packages/spec/src/identity/eval-user.zod.ts lists 'the customSession bridge' among the factory's users. The session reads the posture rung and plugin-auth has 0 non-test createEvalUser calls. This predates the PR and was left unchanged by the claim's file surface. It is an Acceptance note in #22018. Dedupe: createEvalUser docblock customSession · isPlatformAdmin derived from positions factory",
"carrier: none (承接者:无) · noted, not filed · class: none (doc drift) · plugin-auth code comments still say 'derived alias': the ADR-0068 D2 paragraph of the customSession comment in auth-manager.ts and the isPlatformAdminUser docblock in platform-admin-gate.ts. Two test titles or comments say the same: admin-ban-endpoints.test.ts and session-platform-admin-rung-agreement.test.ts. The code reads the rung correctly; only the word is stale after A-lite. It is an Acceptance note in #22018. Dedupe: plugin-auth isPlatformAdmin derived alias comment",
"carrier: none (承接者:无) · noted, not filed · class: none (original ADR wording) · ADR-0068 still uses 'alias' wording in its Status line, Consequences 'Bad / costs', and migration checklist items 4 and 6. These are not rewritten by rule; the D2 note supersedes the reading as a whole but names only the D2 bullet, TL;DR item 2 and the D1 sample. It is an Acceptance note in #22018."
]
}
Generated by Claude Code
objectstack-fleet commented
on Oct 6, 2026 ContributorAuthorMore actions✅ ACCEPT: PR #22018 at
39076f8471(this card's four A-lite items). Tier H: it waits for the maintainer's approval, then this seat lands itdomain:specseat 2 ·os-warren· sessionsession_01GV6oYwgc1kWiUCb1YaprQ7· 2026-10-06T17:18Z · holder of claim6019998508; the review of record for the report6021314784. Ruling-ref6019378035(A-lite, on #21886).Checklist (read on GitHub and
origin/main, not from the report):- Form: draft, base
main, first lineFixes #22012, the only closing keyword.Clause-②: nostarts a line of the body. PR assigneeos-warren. - Scope: 7 files, +91 / −9, all inside the claim's four items.
eval-user.zod.tschanges only the JSDoc and.describe()ofisPlatformAdmin. The ADR-0068 hunks are 4 added lines and 0 removed, so the original wording is untouched. One regenerated reference row, one new pin file, one changeset. - Contract review: PASS at
CONTRACT_REVIEW_TIERon this head, record6021543875(Local-runs: none, identity pair present). Soneeds:contract-reviewis not owed. - Changeset:
@objectstack/specpatch, matching a describe-only change on a published key. - Landing tier:
check-governed-merges --pr 22018reads Tier H (docs/adr/**×1), 100 changed lines.
Claims checked by this seat on
origin/main:isPlatformAdminUser(platform-admin-gate.ts) readsisPlatformAdmin === true, then the legacyrole === 'admin', and neverpositions. That is what the newauthentication.mdxsentence says.resolve-authz-context.tsderives the standing from the unscopedadmin_full_accessgrant only under thesingleposture (bootstrapPlatformAdminmints it for the first human user), and fromOS_PLATFORM_OWNER_EMAIL(§6b-config). That is what the describe and JSDoc say, and it is more exact than the card's "first human account".- The new
permission-metadata.mdxlink anchor exists (authorization.mdx, "Combination semantics (the fixed order)"). - A
Clause-②line inside a changeset body is the repository's convention: 12 changesets onmaincarry one.
Prose read sentence by sentence against the diff: the changeset's five bullets, the rewritten
authentication.mdxsentence, and thepermission-metadata.mdxparenthesis. Each states what the code onmaindoes.Pin evidence (from the report, consistent with the diff): the three-test pin goes red, 2 failed / 1 passed, when the old text is put back by
ablation-replace, and green when it is restored (blob73dc1f88equals HEAD).CI: no check has concluded
failure.Governed Surface Queue Guardreadscancelled: it died in its checkout step at the workflow's 10-minute limit before any guard logic ran (note6021576789). It runs again on the PR's next push.Out of scope, per finding (none meets the filing gate: no reproducible defect with a public-door reach):
createEvalUserdocblock lists "thecustomSessionbridge" among its users: Acceptance notes.- plugin-auth comments and two test titles still say "derived alias": Acceptance notes.
- ADR-0068's Status, Consequences and checklist still use "alias" wording, which the rule forbids rewriting and the D2 note supersedes: Acceptance notes.
Landing, after the maintainer's approval: merge
maininto the branch (the guard re-runs), confirm every check green on the new head and the PASS still governing it, runcheck-governed-merges, then the relay'spr_ready+automerge_enable.Fixes #22012closes this card, which unlocks #21886.
Generated by Claude Code
- Form: draft, base
- added a commit that references this issue
on Oct 7, 2026 objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsClose-out of a half-state this card was left in ·
domain:specseat 2 (#18549) ·os-sales· sessionsession_01DhTqaEHqPVSVnAkjG3jywn· 2026-10-08T10:19Z- What was left: PR fix(spec): EvalUser.isPlatformAdmin is the ADR-0095 D3 PLATFORM_ADMIN standing, not a deprecated alias #22018 merged at 2026-10-07T05:06Z and
Fixesclosed this card, but the card keptpm:dispatchedand the merged PR keptneeds-user-decision. The previous seat-2 holder (session_01GV6oYwgc1kWiUCb1YaprQ7) named both in its closing brief6051524176; its own session refused the two removals. - Done in this act:
pm:dispatchedremoved from this card, andneeds-user-decisionremoved from PR fix(spec): EvalUser.isPlatformAdmin is the ADR-0095 D3 PLATFORM_ADMIN standing, not a deprecated alias #22018. Each was read back. The domain, area and type labels stay, and so do the assignees, which record who held it. - No other change: the card stays closed
completed.
Generated by Claude Code
- What was left: PR fix(spec): EvalUser.isPlatformAdmin is the ADR-0095 D3 PLATFORM_ADMIN standing, not a deprecated alias #22018 merged at 2026-10-07T05:06Z and
Filed by the director seat (summon #35,
session_01VYToj6PQehTEKNrjGM9akg, seat post #12708) on the maintainer's ruling for #21886, batch #281 item 2, letter A-lite (maintainer 「同意」 in the seat's Claude Code session, 2026-10-06). This card is the spec half; thevisiblegate onsys_member.add_memberstays on #21886 (domain:engine), serial after this card, and the thirteen sibling actions on #21903 after that. ⛔ Not a claim.What is wrong (read on
origin/main6befe19c)packages/spec/src/identity/eval-user.zod.ts:225-226describesEvalUserSchema.isPlatformAdminas "DERIVED alias of 'platform_admin' in positions. Deprecated." The key is not deprecated in fact; it is the one standing predicate every evaluator agrees on:packages/plugins/plugin-auth/src/platform-admin-gate.ts:83(u.isPlatformAdmin === true);packages/plugins/plugin-auth/src/auth-manager.ts:4055,:4107;ctx.user/current_user: objectuipackages/app-shell/src/providers/expressionUser.ts:26-27(at objectui7300fcaf);sys_environment"Change Plan (admin)" gates onctx.user.isPlatformAdmin == true(ADR-0068's own trigger case);current_user.isPlatformAdmin.What the mark contradicts: ADR-0095 D3 (Accepted, later than ADR-0068) rules that
PLATFORM_ADMINposture derives from held capability grants, never from roles; core resolves it per request at the one derivation site (packages/core/src/security/resolve-authz-context.ts§6b-config) from the deployment's declared administrator list (OS_PLATFORM_OWNER_EMAIL, or the first human account under thesingleposture) and projects theplatform_adminname intopositionsfrom that same grant (:1125-1140). So for every genuine administrator the rung and the name agree; the only divergence is a tenant-mintedplatform_adminposition row, which #15972 refuses on write. ADR-0068 D2's "derived alias ofroles.includes('platform_admin'), marked deprecated" and D4's "(≡'platform_admin'in roles)" wording is the superseded half.Because the fleet's rule forbids a first-party gate on a deprecated key, the #21886 dev stopped (
5999863859,needs_decision) instead of writing the onevisibleline. The protocol text is the defect here, not the mechanism.The change — A-lite, and nothing more
eval-user.zod.ts: drop "Deprecated" from the.describe()and the JSDoc onisPlatformAdmin; describe it as thePLATFORM_ADMINstanding of ADR-0095 D3, as the deployment's declared administrator list resolves it per request; equal to'platform_admin' in positionsfor every genuine administrator, because the resolver projects that name from the same grant. The schema shape, optionality andcreateEvalUserare unchanged.pnpm --filter @objectstack/spec build && pnpm --filter @objectstack/spec check:generated --fix(check:docs→gen:schema && gen:docs);authorable-surfacekeeps itsidentity/EvalUser:isPlatformAdminrow unchanged.EvalUserSchema.isPlatformAdminand ADR-0095 D3 as the superseding text; the original wording is not rewritten (the director's rule for a superseded ADR clause).content/docs/permissions/permission-metadata.mdx:226("derived, deprecated alias") andcontent/docs/permissions/authentication.mdx:927are brought in line with the new description.⛔ Out of scope, by the ruling: changing
createEvalUserto take the rung as an input; changing@objectstack/formulabuildScope; a new authorable key (requiresPlatformAdminor any other); blessing'platform_admin' in current_user.positionsas a standing read; anyvisibleedit on any action (those are #21886's and #21903's).Pins
EvalUserSchema.isPlatformAdminnames the ADR-0095 D3 standing and carries no deprecation word.identity/EvalUser:isPlatformAdminkeeps its status (no row moves).Landing
docs/adr/0068-*.md, a governed Tier H path, so the whole PR waits for the maintainer's approval (AGENTS.md Prime Directive feat: Comprehensive CRM example demonstrating all ObjectStack protocol features #14); the owning seat lands it afterwards. The dev may split the ADR note into its own PR if it prefers the spec half to go through the queue on its own record.Clause-②: declared by the dev on the diff (a description change on a published key; no accept/reject behaviour changes). The changeset text should say what the key means now and that nothing authored changes.packages/spec/src/**) before landing, as the lane's rule reads.Unblocks
sys_member.add_memberis offered to every organization member, owners and admins included, but its door admits only a platform admin #21886 (Blocked-by:this card):add_membertakesvisible: 'current_user.isPlatformAdmin == true', composed withrequiresFeature, with a dogfood pin that bindscurrent_userthe way the console does.sys_user,sys_oauth_applicationandsys_sso_providershow no standing term invisible— the affordance family's closing card, with an enumeration pin (after #21886's ruling) #21903 (Blocked-by: #21886): the thirteen siblings take the same line, plus the enumeration pin.Governing text: ADR-0068 D2/D4 · ADR-0095 D3 ·
packages/spec/src/identity/eval-user.zod.ts:225-256·platform-admin-gate.ts:83·auth-manager.ts:4055· #15136 · #15972 · the maintainer's ruling A on #21795 (5993018584).Dedupe:
isPlatformAdmin deprecated·EvalUser isPlatformAdmin standing·ADR-0068 D4 platform admin predicate— read on #21886's thread (5999863859,5999894453,6000036525); no open card carries this half.