You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
sys_user.role field help text tells administrators to "grant platform-admin standing with an unscoped admin_full_access assignment" — false on a walled deployment since PR #19136 (split from #19145, carrier 4) #19875
Split from #19145 (carrier 4) by the triage seat (session_01Tw7jnJinGHvoGSi8aFkhPJ, seat post #6015), at the domain:devx seat's request (#19145 comment 5796140596): the carrier lives in packages/platform-objects, outside domain:devx's packages.
Path: access-security
The defect
packages/platform-objects/src/identity/sys-user.object.ts:704 (origin/mainb940f32a56) — the role field's description:
Legacy better-auth role scalar (admin, user, …). ObjectStack no longer writes it (ADR-0068 D2) — grant platform-admin standing with an unscoped admin_full_access assignment in sys_user_permission_set.
This is metadata, so it reaches the Setup / Studio UI as the field's help text — the surface an administrator is most likely to read while trying to do exactly this. Since PR #19136 (74832b68f2, merged 2026-09-19, #11663 L5), an unscoped admin_full_access assignment no longer confers platform-admin standing under a walled posture. On a walled deployment the help text prescribes a remedy that silently does nothing.
A description change on a platform object is a published-metadata text change — the taker checks which generated artefacts and pins carry the string and updates them through their generators.
Claim: PM loop round 21
Session: session_01TEhopqrWQYBycZzyJHpAZr
Branch: claude/issue-19875-sys-user-role-help-text
Worktree: objectstack-issue-19875
Domain: domain:engine
Seat: domain:engine#1
File surface: packages/platform-objects/src/** (the sys_user.role description, plus any other description the sweep finds prescribing the unscoped admin_full_access grant), the generated artefacts and pins that carry those strings (regenerated through their generators, never by hand), one .changeset/*.md (stop on breach; explain in the report). ⛔ content/docs/** and docs/qa/** carriers are #19145's (domain:devx) and are not edited here
Container & model: S, mode:subagent, model: opus (no path-derived mandate; default judgment tier — the wording must be true per tenancy posture without pre-empting #11979)
Clause-②: no
Thread-read: none
Serial constraints cleared: #19145's in-flight claim (domain:devx) covers carriers 1 and 2 only (content/docs/permissions/authorization.mdx and docs/qa/platform-checklist/areas/access-security.json) and names packages/platform-objects only to exclude it; no other in-flight claim or open PR touches packages/platform-objects/src/identity/sys-user.object.ts.
Written 2026-09-23T14:56Z. North Star clause 3 read this act: product P0/P1 are open. This card is a product defect: help text an administrator reads in Setup prescribes a remedy that silently does nothing on a walled deployment. It is not a tooling or contract-hygiene card, so it is dispatchable at p2.
Why Clause-②: no: a description text correction on a platform object; no accept set widens and no public surface moves.
os-dev-report
{
"issue": 19875,
"status": "done",
"branch": "claude/issue-19875-sys-user-role-help-text",
"pr": "#19892",
"session": "session_01TEhopqrWQYBycZzyJHpAZr (the PM's session; this subagent shares it, and its identity is the branch)",
"premise_still_valid": true,
"summary": "A1, measured on origin/main 3bd221d. Under the single posture, platform-admin standing comes from EITHER an unscoped admin_full_access row in sys_user_permission_set (packages/core/src/security/resolve-authz-context.ts §6b: hasPlatformAdminGrant is set when ps.name === ADMIN_FULL_ACCESS && unscopedUserPsIds.has(ps.id) && !legacyGrantAnchorRetired, where legacyGrantAnchorRetired = postureEnforcesWall(resolveTenancyPosture()), and postureEnforcesWall in packages/spec/src/security/tenancy-posture.ts is posture !== 'single') OR a verified email listed in OS_PLATFORM_OWNER_EMAIL (the same file's §6b-config: configConfersPlatformAdmin = platformAdminConfig.emails.length > 0 && matchesConfiguredPlatformAdmin(getUserRow(), ...), with no posture gate; matchesConfiguredPlatformAdmin and resolvePlatformAdminEmails are in packages/core/src/security/platform-admin.ts). So under single, an unscoped admin_full_access assignment is still enough. Under group and isolated (walled), only the OS_PLATFORM_OWNER_EMAIL verified-email match confers standing. The unscoped row still grants the set's capabilities but no standing. Existing pins in resolve-authz-context.platform-admin-config.test.ts already cover this: 'the legacy grant row is an anchor under single, and NOWHERE else' and 'a CONFIG anchor resolves under BOTH postures'. New help text, verbatim: 'Legacy better-auth role scalar (admin, user, …). ObjectStack no longer writes it (ADR-0068 D2). To grant platform-admin standing, list the user's verified email in OS_PLATFORM_OWNER_EMAIL; under the single tenancy posture an unscoped admin_full_access assignment in sys_user_permission_set also confers it.' Implemented as a description-only change in packages/platform-objects/src/identity/sys-user.object.ts. The en bundle was regenerated with pnpm i18n:extract. The existing #15188 pin in platform-objects.test.ts was extended, because it stayed green on the old sentence. One patch changeset added. Draft PR #19892 opened, with its body read back byte-identical. Worktree and node_modules removed. CI was in_progress at report time (31 check runs: 7 success, 3 skipped, 20 in_progress, 1 queued). The assignee was already set to huangyiirene when I started, and I did not touch it.",
"tests": "At final head ed9dc25, all through os-verify-lock (slot issue-19875-r21). pnpm --filter @objectstack/platform-objects test: 'Test Files 54 passed (54) / Tests 883 passed (883)', VERDICT command-exit 0. pnpm --filter @objectstack/platform-objects typecheck: tsc x2 plus 'check:test-typecheck: OK', VERDICT command-exit 0. Build: turbo build of the platform-objects and cli closures, then the full workspace build ('73 successful, 73 total', exit 0) for the dist-reading gates. Ablation of the updated pin via node scripts/ablation-replace.mjs (wrap mode around vitest run src/platform-objects.test.ts). It restored the old description in sys-user.object.ts: anchor x1 to x0, replacement x0 to x1, blob ca076aa169ea to 7f8718e29dfc. Result: 'Tests 1 failed | 126 passed (127)', failing on the #15188 test with "expected 'Legacy better-auth role scalar (admin…' to contain 'OS_PLATFORM_OWNER_EMAIL'". The expected direction was red, and red is what happened. Restore: blob after restore == blob at HEAD (ca076aa169ea), git diff HEAD empty, git status --porcelain empty. No dist involved: the suite imports the object from src/. Old-string absence: git grep -c -F 'grant platform-admin standing with an unscoped admin_full_access assignment' -- 'packages/' gives 0 hits (exit 1). The control git grep -c -F 'verified email in OS_PLATFORM_OWNER_EMAIL; under the single tenancy posture' -- 'packages/' gives 2 hits (exit 0), in sys-user.object.ts and en.objects.generated.ts. Whole tree: the old string remains only in .changeset/sys-user-role-help-posture-split.md (this PR, quoted as the old text) and in the pending .changeset/sys-user-role-prose-retired-action.md (history of PR #17102). Narrowed lint at ed9dc25: eslint --no-inline-config --format json over the 3 changed .ts files gave 3 files, 0 errors, 0 warnings, exit 0. Population: eslint --print-config resolves a config for each file, and the JSON carries no ignored-file notice. Invariance: eslint.config.mjs enables no type-aware linting (no parserOptions.project) for any file, so this diff cannot move any untouched file's verdict. Repo-wide pnpm lint is left to CI.",
"gates": {
"head": "ed9dc25cdd",
"derivation": "node scripts/pm/dispatch-gates.mjs --commands (no paths) on ed9dc25: 60 commands, change set = 4 paths vs merge base 3bd221d, not stale",
"commands_and_exit_codes": [
"node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0",
"node scripts/check-adr-0087-registration.mjs --self-test :: exit 0",
"node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0",
"node scripts/check-changeset-no-major.mjs --self-test :: exit 0",
"node scripts/check-ci-filter-parity.mjs :: exit 0",
"node scripts/check-closing-keyword-parity.mjs :: exit 0",
"node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0",
"node scripts/check-comment-mask-adoption.mjs :: exit 0",
"node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0",
"node scripts/check-comment-mask-corpus.mjs :: exit 0",
"node scripts/check-empty-changeset.mjs --base origin/main :: exit 0",
"node scripts/check-empty-changeset.mjs --self-test :: exit 0",
"node scripts/check-keyed-text-bounds.mjs :: exit 0",
"node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0",
"node scripts/check-platform-object-tenancy-census.mjs :: exit 0",
"node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0",
"node scripts/check-plugin-teardown-shape.mjs :: exit 0",
"node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0",
"node scripts/check-registry-log-declared.mjs :: exit 0",
"node scripts/check-registry-log-declared.mjs --self-test :: exit 0",
"node scripts/check-rest-log-spy-declared.mjs :: exit 0",
"node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0",
"node scripts/check-system-context-census.mjs :: exit 0",
"node scripts/check-system-context-census.mjs --self-test :: exit 0",
"node scripts/check-undeclared-dep-imports.mjs :: exit 0",
"node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0",
"node scripts/docs-audit/check-affected-docs.mjs :: exit 0",
"node scripts/docs-audit/check-drift-comment.mjs :: exit 0",
"node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0",
"pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0",
"pnpm check:changeset-gate-self-tests :: exit 0",
"pnpm check:cross-package-test-inputs :: exit 0",
"pnpm check:doc-authoring :: exit 0",
"pnpm check:driver-memory-census :: exit 0",
"pnpm check:dts-closure :: exit 0",
"pnpm check:dual-build-cjs-loads :: exit 0",
"pnpm check:engine-double-contract :: exit 0",
"pnpm check:gitlink-declared :: exit 0",
"pnpm check:i18n :: exit 0",
"pnpm check:i18n-stale-fill :: exit 0",
"pnpm check:issue-citations :: exit 0",
"pnpm check:lean-entry-closure :: exit 0",
"pnpm check:logger-receiver-detach :: exit 0",
"pnpm check:nul-bytes :: exit 0",
"pnpm check:objectql-double-limit :: exit 0",
"pnpm check:objectui-changeset :: exit 0",
"pnpm check:org-identifier :: exit 0",
"pnpm check:page-declaration-shape :: exit 0",
"pnpm check:pm-changeset-deadline-census :: exit 0",
"pnpm check:published-files :: exit 0",
"pnpm check:query-options-erasure :: exit 0",
"pnpm check:refd-timer-probe :: exit 0",
"pnpm check:slot-lookup :: exit 0",
"pnpm check:sourcemap-no-sources-content :: exit 0",
"pnpm check:test-source-alias :: exit 0",
"pnpm check:tier-file-adoption :: exit 0",
"pnpm check:type-check-coverage :: exit 0",
"pnpm check:type-check-debt :: exit 0",
"pnpm check:watch-hint-literal :: exit 0",
"pnpm check:where-matcher :: exit 0"
],
"rerun_note": "pnpm check:dual-build-cjs-loads and pnpm check:i18n first exited 3 (PREREQUISITE NOT MET: no dist/). Their stated prerequisites were built, then both re-ran and exited 0. The record above carries the re-run codes. check:dts-closure, check:sourcemap-no-sources-content and check:lean-entry-closure were re-run on the full build: exit 0, sweeping 73 and 69 built packages.",
"ran_verdict": "node scripts/pm/dispatch-gates.mjs --ran ran.list: exit 0 — 'Run reconciliation — 60 derived, 60 run, 0 NOT-MEASURED, 0 UNRUN.'",
"issue_citations_live": "node scripts/check-issue-citations.mjs: exit 0 — 'no issue citations added against 3bd221d (2 file(s) read)'",
"package": "platform-objects test exit 0 (883/883); typecheck exit 0",
"not_measured": "The path-scheduled CI jobs (Test Core, Temporal Conformance, Dogfood Regression Gate, Dogfood Verify CLI, Build Core) and the lint.yml type-check lanes that dispatch-gates lists outside its derived set. These are CI's, and were in_progress at report time."
},
"files_changed": [
"packages/platform-objects/src/identity/sys-user.object.ts (role.description)",
"packages/platform-objects/src/apps/translations/en.objects.generated.ts (regenerated by pnpm i18n:extract; the other 10 files the run wrote came out byte-identical)",
"packages/platform-objects/src/platform-objects.test.ts (existing #15188 pin extended: OS_PLATFORM_OWNER_EMAIL and a single qualifier)",
".changeset/sys-user-role-help-posture-split.md (@objectstack/platform-objects patch)"
],
"deviations": [
"Merged origin/main into the branch once (merge commit ed9dc25, 3 commits touching objectql, metadata and scripts/pm/close-cards.mjs; no rebase, no force push), because the first gate derivation reported STALE TREE on scripts/pm/close-cards.mjs. The re-derived list was byte-identical (60 commands). origin/main has since moved by one docs-only commit, and I did not merge it.",
"Updated an existing pin rather than adding none. platform-objects.test.ts's #15188 test only asserted toContain('sys_user_permission_set') and toContain('admin_full_access'), and both survive in the new single clause, so it stayed green on the old sentence. I added toContain('OS_PLATFORM_OWNER_EMAIL') and toContain('single'), which are named subjects, not wording. The ablation above proves the pin can fail.",
"Did not edit the pending .changeset/sys-user-role-prose-retired-action.md (PR #17102), which also carries the old string and the claim that standing 'comes from an unscoped admin_full_access grant'. The file surface was one new changeset. See out_of_scope_findings.",
"Commit trailers use the model-free pair from AGENTS.md (Co-authored-by: Claude). The PR body ends with AGENTS.md's session-URL footer, not the harness reminder's model-named or emoji form. AGENTS.md outranks that reminder.",
"No label writes, per the dispatch. The PR is left draft."
],
"mcp_calls": "0",
"api_writes": "2. Each is one repository_dispatch to the fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches) that executed as objectstack-fleet[bot]: (1) pr_create, i.e. POST /repos/objectstack-ai/objectstack/pulls, draft, which opened #19892; (2) the os-dev-report comment, i.e. POST /repos//issues/19875/comments. Plus git push (not REST). Reads: REST GETs only.",
"open_questions": [],
"out_of_scope_findings": [
"class: a · packages/platform-objects/src/apps/translations/{zh-CN,ja-JP,es-ES}.objects.generated.ts sys_user.role.help still tell the reader to use the retired 'Set Platform Role' action. zh-CN reads '平台级角色(admin、user 等)。通过“设置平台角色”操作设置。'; ja-JP and es-ES say the same. The action was retired in #9968, and the #15188 change (PR #17102) updated only en. These are hand-written, legacy-trusted leaves with no source-hash entry, so check:i18n and check:i18n-stale-fill never report them. Reachable today on the sys_user.role field in Setup under those locales. Not the unscoped-grant prescription, so not fixed here · dedupe words: 'Set Platform Role translation', 'sys_user.role help zh-CN', 'legacy-trusted translation stale', '设置平台角色'",
"class: a · #19145 scope, not in its carrier list: content/docs/permissions/permission-sets.mdx 'Who holds admin_full_access' says the unscoped grant row 'still confers standing', that 'either one is sufficient', and that it 'logs a pointer, once per process'. That is false under a wall since PR #19136, and the pointer was deleted on every posture. Same docs class as #19145's carrier 1 (authorization.mdx). Route it into #19145 (content/docs belongs to domain:devx); not edited here · dedupe words: 'permission-sets.mdx either one is sufficient', 'logs a pointer once per process', 'Who holds admin_full_access'",
"Listed, not edited: #19145 carriers 1 and 2 (content/docs/permissions/authorization.mdx 'either of which is sufficient'; docs/qa/platform-checklist/areas/access-security.json) and carrier 3 (packages/runtime/src/domains/automation.ts RUN_LIFECYCLE_DENY_MESSAGE, split to domain:cli)",
"carrier: none · packages/platform-objects/src/identity/sys-user.object.ts, the role field's readonly comment '(platform admin: sys_user_permission_set / admin_full_access)', and plugin-security sys-user-position.object.ts, the reserved-identity comment about bootstrapPlatformAdmin writing the grant row. Both are source comments, true under single and incomplete under a wall, and not served. Noted in the PR's Acceptance notes, not filed",
"carrier: none · .changeset/sys-user-role-prose-retired-action.md (pending, PR #17102) quotes the old sentence and says standing 'comes from an unscoped admin_full_access grant'. It is a history record that will compile into the same release as this PR's changeset, which supersedes it. Noted, not filed"
]
}
domain:engine#1, session_01TEhopqrWQYBycZzyJHpAZr, written 2026-09-23T15:50Z. Every reading below was taken against GitHub and origin/main in this act, ⛔ not from the dev's report.
What confers platform-admin standing today (measured, then written)
Every posture: a verified email listed in OS_PLATFORM_OWNER_EMAIL (resolve-authz-context.ts §6b-config; no posture gate).
The new help text says exactly that, and does not describe #11979's future change on single.
Review
check
reading
head
ed9dc25cdd
PR shape
draft → main; first body line Fixes #19875; no other closing keyword; Clause-②: no at column 0, agreeing with the patch changeset
scope
4 files: one description literal in sys-user.object.ts, its en leaf regenerated with pnpm i18n:extract (byte-equal to the source after unescaping), the existing #15188 pin extended (it stayed green on the old sentence), one changeset
ablation
restoring the old sentence reds the extended pin
old string
0 hits under packages/**; it survives only in this PR's changeset (quoted as the old text) and the pending PR #17102 changeset
governed
NOT governed (check-governed-merges --pr 19892)
contract review
PASS on this head (5798003286)
CI
every check success or a roster skip on this head before ready
Source comments incomplete under a wall (sys-user.object.tsrole readonly comment; sys-user-position.object.ts; packages/types/src/env.ts "The single posture never consults this") → Acceptance notes (none is served).
Landing: ready → queue on this head once CI is fully green.
Activity
objectstack-fleet commentedon Sep 23, 2026
Claim: PM loop round 21
Session:
session_01TEhopqrWQYBycZzyJHpAZrBranch:
claude/issue-19875-sys-user-role-help-textWorktree:
objectstack-issue-19875Domain:
domain:engineSeat:
domain:engine#1File surface:
packages/platform-objects/src/**(thesys_user.roledescription, plus any other description the sweep finds prescribing the unscopedadmin_full_accessgrant), the generated artefacts and pins that carry those strings (regenerated through their generators, never by hand), one.changeset/*.md(stop on breach; explain in the report). ⛔content/docs/**anddocs/qa/**carriers are #19145's (domain:devx) and are not edited hereContainer & model:
S,mode:subagent,model: opus(no path-derived mandate; default judgment tier — the wording must be true per tenancy posture without pre-empting #11979)Clause-②: no
Thread-read: none
Serial constraints cleared:
#19145's in-flight claim (domain:devx) covers carriers 1 and 2 only (content/docs/permissions/authorization.mdx and docs/qa/platform-checklist/areas/access-security.json) and names packages/platform-objects only to exclude it; no other in-flight claim or open PR touches packages/platform-objects/src/identity/sys-user.object.ts.Written 2026-09-23T14:56Z. North Star clause 3 read this act: product P0/P1 are open. This card is a product defect: help text an administrator reads in Setup prescribes a remedy that silently does nothing on a walled deployment. It is not a tooling or contract-hygiene card, so it is dispatchable at p2.
Why
Clause-②: no: a description text correction on a platform object; no accept set widens and no public surface moves.Generated by Claude Code
objectstack-fleet commentedon Sep 23, 2026
os-dev-report
{
"issue": 19875,
"status": "done",
"branch": "claude/issue-19875-sys-user-role-help-text",
"pr": "#19892",
"session": "session_01TEhopqrWQYBycZzyJHpAZr (the PM's session; this subagent shares it, and its identity is the branch)",
"premise_still_valid": true,
"summary": "A1, measured on origin/main 3bd221d. Under the single posture, platform-admin standing comes from EITHER an unscoped admin_full_access row in sys_user_permission_set (packages/core/src/security/resolve-authz-context.ts §6b: hasPlatformAdminGrant is set when ps.name === ADMIN_FULL_ACCESS && unscopedUserPsIds.has(ps.id) && !legacyGrantAnchorRetired, where legacyGrantAnchorRetired = postureEnforcesWall(resolveTenancyPosture()), and postureEnforcesWall in packages/spec/src/security/tenancy-posture.ts is posture !== 'single') OR a verified email listed in OS_PLATFORM_OWNER_EMAIL (the same file's §6b-config: configConfersPlatformAdmin = platformAdminConfig.emails.length > 0 && matchesConfiguredPlatformAdmin(getUserRow(), ...), with no posture gate; matchesConfiguredPlatformAdmin and resolvePlatformAdminEmails are in packages/core/src/security/platform-admin.ts). So under single, an unscoped admin_full_access assignment is still enough. Under group and isolated (walled), only the OS_PLATFORM_OWNER_EMAIL verified-email match confers standing. The unscoped row still grants the set's capabilities but no standing. Existing pins in resolve-authz-context.platform-admin-config.test.ts already cover this: 'the legacy grant row is an anchor under single, and NOWHERE else' and 'a CONFIG anchor resolves under BOTH postures'. New help text, verbatim: 'Legacy better-auth role scalar (admin, user, …). ObjectStack no longer writes it (ADR-0068 D2). To grant platform-admin standing, list the user's verified email in
OS_PLATFORM_OWNER_EMAIL; under thesingletenancy posture an unscopedadmin_full_accessassignment insys_user_permission_setalso confers it.' Implemented as a description-only change in packages/platform-objects/src/identity/sys-user.object.ts. The en bundle was regenerated with pnpm i18n:extract. The existing #15188 pin in platform-objects.test.ts was extended, because it stayed green on the old sentence. One patch changeset added. Draft PR #19892 opened, with its body read back byte-identical. Worktree and node_modules removed. CI was in_progress at report time (31 check runs: 7 success, 3 skipped, 20 in_progress, 1 queued). The assignee was already set to huangyiirene when I started, and I did not touch it.","tests": "At final head ed9dc25, all through os-verify-lock (slot issue-19875-r21). pnpm --filter @objectstack/platform-objects test: 'Test Files 54 passed (54) / Tests 883 passed (883)', VERDICT command-exit 0. pnpm --filter @objectstack/platform-objects typecheck: tsc x2 plus 'check:test-typecheck: OK', VERDICT command-exit 0. Build: turbo build of the platform-objects and cli closures, then the full workspace build ('73 successful, 73 total', exit 0) for the dist-reading gates. Ablation of the updated pin via node scripts/ablation-replace.mjs (wrap mode around vitest run src/platform-objects.test.ts). It restored the old description in sys-user.object.ts: anchor x1 to x0, replacement x0 to x1, blob ca076aa169ea to 7f8718e29dfc. Result: 'Tests 1 failed | 126 passed (127)', failing on the #15188 test with "expected 'Legacy better-auth role scalar (admin…' to contain 'OS_PLATFORM_OWNER_EMAIL'". The expected direction was red, and red is what happened. Restore: blob after restore == blob at HEAD (ca076aa169ea), git diff HEAD empty, git status --porcelain empty. No dist involved: the suite imports the object from src/. Old-string absence: git grep -c -F 'grant platform-admin standing with an unscoped
admin_full_accessassignment' -- 'packages/' gives 0 hits (exit 1). The control git grep -c -F 'verified email inOS_PLATFORM_OWNER_EMAIL; under thesingletenancy posture' -- 'packages/' gives 2 hits (exit 0), in sys-user.object.ts and en.objects.generated.ts. Whole tree: the old string remains only in .changeset/sys-user-role-help-posture-split.md (this PR, quoted as the old text) and in the pending .changeset/sys-user-role-prose-retired-action.md (history of PR #17102). Narrowed lint at ed9dc25: eslint --no-inline-config --format json over the 3 changed .ts files gave 3 files, 0 errors, 0 warnings, exit 0. Population: eslint --print-config resolves a config for each file, and the JSON carries no ignored-file notice. Invariance: eslint.config.mjs enables no type-aware linting (no parserOptions.project) for any file, so this diff cannot move any untouched file's verdict. Repo-wide pnpm lint is left to CI.","gates": {
"head": "ed9dc25cdd",
"derivation": "node scripts/pm/dispatch-gates.mjs --commands (no paths) on ed9dc25: 60 commands, change set = 4 paths vs merge base 3bd221d, not stale",
"commands_and_exit_codes": [
"node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0",
"node scripts/check-adr-0087-registration.mjs --self-test :: exit 0",
"node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0",
"node scripts/check-changeset-no-major.mjs --self-test :: exit 0",
"node scripts/check-ci-filter-parity.mjs :: exit 0",
"node scripts/check-closing-keyword-parity.mjs :: exit 0",
"node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0",
"node scripts/check-comment-mask-adoption.mjs :: exit 0",
"node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0",
"node scripts/check-comment-mask-corpus.mjs :: exit 0",
"node scripts/check-empty-changeset.mjs --base origin/main :: exit 0",
"node scripts/check-empty-changeset.mjs --self-test :: exit 0",
"node scripts/check-keyed-text-bounds.mjs :: exit 0",
"node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0",
"node scripts/check-platform-object-tenancy-census.mjs :: exit 0",
"node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0",
"node scripts/check-plugin-teardown-shape.mjs :: exit 0",
"node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0",
"node scripts/check-registry-log-declared.mjs :: exit 0",
"node scripts/check-registry-log-declared.mjs --self-test :: exit 0",
"node scripts/check-rest-log-spy-declared.mjs :: exit 0",
"node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0",
"node scripts/check-system-context-census.mjs :: exit 0",
"node scripts/check-system-context-census.mjs --self-test :: exit 0",
"node scripts/check-undeclared-dep-imports.mjs :: exit 0",
"node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0",
"node scripts/docs-audit/check-affected-docs.mjs :: exit 0",
"node scripts/docs-audit/check-drift-comment.mjs :: exit 0",
"node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0",
"pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0",
"pnpm check:changeset-gate-self-tests :: exit 0",
"pnpm check:cross-package-test-inputs :: exit 0",
"pnpm check:doc-authoring :: exit 0",
"pnpm check:driver-memory-census :: exit 0",
"pnpm check:dts-closure :: exit 0",
"pnpm check:dual-build-cjs-loads :: exit 0",
"pnpm check:engine-double-contract :: exit 0",
"pnpm check:gitlink-declared :: exit 0",
"pnpm check:i18n :: exit 0",
"pnpm check:i18n-stale-fill :: exit 0",
"pnpm check:issue-citations :: exit 0",
"pnpm check:lean-entry-closure :: exit 0",
"pnpm check:logger-receiver-detach :: exit 0",
"pnpm check:nul-bytes :: exit 0",
"pnpm check:objectql-double-limit :: exit 0",
"pnpm check:objectui-changeset :: exit 0",
"pnpm check:org-identifier :: exit 0",
"pnpm check:page-declaration-shape :: exit 0",
"pnpm check:pm-changeset-deadline-census :: exit 0",
"pnpm check:published-files :: exit 0",
"pnpm check:query-options-erasure :: exit 0",
"pnpm check:refd-timer-probe :: exit 0",
"pnpm check:slot-lookup :: exit 0",
"pnpm check:sourcemap-no-sources-content :: exit 0",
"pnpm check:test-source-alias :: exit 0",
"pnpm check:tier-file-adoption :: exit 0",
"pnpm check:type-check-coverage :: exit 0",
"pnpm check:type-check-debt :: exit 0",
"pnpm check:watch-hint-literal :: exit 0",
"pnpm check:where-matcher :: exit 0"
],
"rerun_note": "pnpm check:dual-build-cjs-loads and pnpm check:i18n first exited 3 (PREREQUISITE NOT MET: no dist/). Their stated prerequisites were built, then both re-ran and exited 0. The record above carries the re-run codes. check:dts-closure, check:sourcemap-no-sources-content and check:lean-entry-closure were re-run on the full build: exit 0, sweeping 73 and 69 built packages.",
"ran_verdict": "node scripts/pm/dispatch-gates.mjs --ran ran.list: exit 0 — 'Run reconciliation — 60 derived, 60 run, 0 NOT-MEASURED, 0 UNRUN.'",
"issue_citations_live": "node scripts/check-issue-citations.mjs: exit 0 — 'no issue citations added against 3bd221d (2 file(s) read)'",
"package": "platform-objects test exit 0 (883/883); typecheck exit 0",
"not_measured": "The path-scheduled CI jobs (Test Core, Temporal Conformance, Dogfood Regression Gate, Dogfood Verify CLI, Build Core) and the lint.yml type-check lanes that dispatch-gates lists outside its derived set. These are CI's, and were in_progress at report time."
},
"files_changed": [
"packages/platform-objects/src/identity/sys-user.object.ts (role.description)",
"packages/platform-objects/src/apps/translations/en.objects.generated.ts (regenerated by pnpm i18n:extract; the other 10 files the run wrote came out byte-identical)",
"packages/platform-objects/src/platform-objects.test.ts (existing #15188 pin extended: OS_PLATFORM_OWNER_EMAIL and a
singlequalifier)",".changeset/sys-user-role-help-posture-split.md (@objectstack/platform-objects patch)"
],
"deviations": [
"Merged origin/main into the branch once (merge commit ed9dc25, 3 commits touching objectql, metadata and scripts/pm/close-cards.mjs; no rebase, no force push), because the first gate derivation reported STALE TREE on scripts/pm/close-cards.mjs. The re-derived list was byte-identical (60 commands). origin/main has since moved by one docs-only commit, and I did not merge it.",
"Updated an existing pin rather than adding none. platform-objects.test.ts's #15188 test only asserted toContain('sys_user_permission_set') and toContain('admin_full_access'), and both survive in the new single clause, so it stayed green on the old sentence. I added toContain('OS_PLATFORM_OWNER_EMAIL') and toContain('
single'), which are named subjects, not wording. The ablation above proves the pin can fail.","Did not edit the pending .changeset/sys-user-role-prose-retired-action.md (PR #17102), which also carries the old string and the claim that standing 'comes from an unscoped admin_full_access grant'. The file surface was one new changeset. See out_of_scope_findings.",
"Commit trailers use the model-free pair from AGENTS.md (Co-authored-by: Claude). The PR body ends with AGENTS.md's session-URL footer, not the harness reminder's model-named or emoji form. AGENTS.md outranks that reminder.",
"No label writes, per the dispatch. The PR is left draft."
],
"mcp_calls": "0",
"api_writes": "2. Each is one repository_dispatch to the fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches) that executed as objectstack-fleet[bot]: (1) pr_create, i.e. POST /repos/objectstack-ai/objectstack/pulls, draft, which opened #19892; (2) the os-dev-report comment, i.e. POST /repos//issues/19875/comments. Plus git push (not REST). Reads: REST GETs only.",
"open_questions": [],
"out_of_scope_findings": [
"class: a · packages/platform-objects/src/apps/translations/{zh-CN,ja-JP,es-ES}.objects.generated.ts sys_user.role.help still tell the reader to use the retired 'Set Platform Role' action. zh-CN reads '平台级角色(admin、user 等)。通过“设置平台角色”操作设置。'; ja-JP and es-ES say the same. The action was retired in #9968, and the #15188 change (PR #17102) updated only en. These are hand-written, legacy-trusted leaves with no source-hash entry, so check:i18n and check:i18n-stale-fill never report them. Reachable today on the sys_user.role field in Setup under those locales. Not the unscoped-grant prescription, so not fixed here · dedupe words: 'Set Platform Role translation', 'sys_user.role help zh-CN', 'legacy-trusted translation stale', '设置平台角色'",
"class: a · #19145 scope, not in its carrier list: content/docs/permissions/permission-sets.mdx 'Who holds admin_full_access' says the unscoped grant row 'still confers standing', that 'either one is sufficient', and that it 'logs a pointer, once per process'. That is false under a wall since PR #19136, and the pointer was deleted on every posture. Same docs class as #19145's carrier 1 (authorization.mdx). Route it into #19145 (content/docs belongs to domain:devx); not edited here · dedupe words: 'permission-sets.mdx either one is sufficient', 'logs a pointer once per process', 'Who holds admin_full_access'",
"Listed, not edited: #19145 carriers 1 and 2 (content/docs/permissions/authorization.mdx 'either of which is sufficient'; docs/qa/platform-checklist/areas/access-security.json) and carrier 3 (packages/runtime/src/domains/automation.ts RUN_LIFECYCLE_DENY_MESSAGE, split to domain:cli)",
"carrier: none · packages/platform-objects/src/identity/sys-user.object.ts, the role field's readonly comment '(platform admin: sys_user_permission_set / admin_full_access)', and plugin-security sys-user-position.object.ts, the reserved-identity comment about bootstrapPlatformAdmin writing the grant row. Both are source comments, true under single and incomplete under a wall, and not served. Noted in the PR's Acceptance notes, not filed",
"carrier: none · .changeset/sys-user-role-prose-retired-action.md (pending, PR #17102) quotes the old sentence and says standing 'comes from an unscoped admin_full_access grant'. It is a history record that will compile into the same release as this PR's changeset, which supersedes it. Noted, not filed"
]
}
Generated by Claude Code
objectstack-fleet commentedon Sep 23, 2026
ACCEPT — round 21 · PR #19892 ·
Fixesdomain:engine#1,session_01TEhopqrWQYBycZzyJHpAZr, written 2026-09-23T15:50Z. Every reading below was taken against GitHub andorigin/mainin this act, ⛔ not from the dev's report.What confers platform-admin standing today (measured, then written)
OS_PLATFORM_OWNER_EMAIL(resolve-authz-context.ts§6b-config; no posture gate).singleonly: an unscopedadmin_full_accessrow insys_user_permission_setalso confers it (§6b; retired under a wall by PR feat(security)!: retire the walled legacy platform-admin grant dual read and its deprecation log (#11663 L5) #19136). Underisolated/groupthat row still grants the set's capabilities, but no standing.The new help text says exactly that, and does not describe #11979's future change on
single.Review
ed9dc25cddmain; first body lineFixes #19875; no other closing keyword;Clause-②: noat column 0, agreeing with thepatchchangesetdescriptionliteral insys-user.object.ts, itsenleaf regenerated withpnpm i18n:extract(byte-equal to the source after unescaping), the existing#15188pin extended (it stayed green on the old sentence), one changesetpackages/**; it survives only in this PR's changeset (quoted as the old text) and the pending PR #17102 changesetcheck-governed-merges --pr 19892)successor a roster skip on this head before readyOut-of-scope findings — dispositions:
zh-CN/ja-JP/es-ESvalues ofsys_user.rolehelp still point at the retired "Set Platform Role" action, invisible to the i18n gates → filed platform-objects: the zh-CN, ja-JP and es-ES help text for sys_user.role still points at the retired Set Platform Role action, and no i18n gate sees it (no source-hash entry) #19897.content/docs/permissions/permission-sets.mdx"Who holds admin_full_access" states the pre-feat(security)!: retire the walled legacy platform-admin grant dual read and its deprecation log (#11663 L5) #19136 rule → routed to [finding] the platform-admin derivation becomes posture-dependent when #18336 lands, and two shipped documents still say it is not —authorization.mdx's "either of which is sufficient" and three access-security checklist lines naming the retired pointer #19145: PR docs(permissions,qa): the unscoped admin_full_access anchor confers platform-admin standing under single only #19876 (Part of #19145) already rewrites that section, and thedomain:devxseat added the file to [finding] the platform-admin derivation becomes posture-dependent when #18336 lands, and two shipped documents still say it is not —authorization.mdx's "either of which is sufficient" and three access-security checklist lines naming the retired pointer #19145's claim (5796745302)..changeset/sys-user-role-prose-retired-action.md(PR fix(platform-objects): pointsys_user.role's prose at the path that exists, not the retired Set Platform Role action #17102) quotes the retired sentence; the foreign-changeset rule forbids editing it here, and the two notes read as before and after → Acceptance notes.sys-user.object.tsrolereadonly comment;sys-user-position.object.ts;packages/types/src/env.ts"Thesingleposture never consults this") → Acceptance notes (none is served).Landing: ready → queue on this head once CI is fully green.
Generated by Claude Code
fix(platform-objects): sys_user.role help text names the platform-adm…
fix(platform-objects): translate sys_user.role help text away from th…