Skip to content

fix(app-shell): Setup's permission-set and position holders offer Add and Remove only under the grant the server asks (objectui#12082) - #12685

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-12082-setup-assignment-census
Oct 16, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-12082-setup-assignment-census

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #12082

Clause-②: no

The census's last four unmapped write sites are Setup's permission-set and position holders. They are graded on measured reach, per triage's leg-B rule on the card. All four are reachable without the grant, so each becomes mapped to an existing row of the map. WRITE_SITES now holds no unmapped entry, which is the card's done-when. Reviewer note (e) of the previous round's contract review rides along.

Reach, measured per site

Triage's likely case was that Setup's own gate is the boundary. Measured, it is not.

  • The four sites. addUsers and removeUser in AssignedUsersSection write sys_user_permission_set rows. addHolders and removeHolder in PositionHoldersSection write sys_user_position rows. None read a grant.
  • Where they mount. The permission-set editor (PermissionMatrixEditPage, environment scope, not embedded) and the position editor (ResourceEditPage's ENVIRONMENT_SECTIONS). A third mount is the sys_permission_set record page (RecordPermissionAssignmentsRenderer). The catalog scope is documented as "a VIEW scope, never an authority" (catalog-scope.ts).
  • Setup's own gate, read on objectstack main at 570f01d27. The Setup app asks requiredPermissions: ['setup.access']. Its Access Control entries Positions and Permission Sets, contributed by plugin-security, carry no requiredPermissions. They link to /apps/setup/metadata/position?scope=environment and /apps/setup/metadata/permission?scope=environment.
  • Who reaches them without the grant. The shipped organization_admin set lists setup.access in its systemPermissions. Its objects pin sys_user_permission_set and sys_user_position to allowRead: true with every write bit false. objectstack pins that in objects/rbac-objects.test.ts as organization_admin is read-only on global RBAC tables to prevent privilege escalation. auto-org-admin-grant.ts grants that set, or its derived wall-less variant organization_admin_no_bypass (same per-object entries), to every organization owner or admin member.
  • The server's answer. The security middleware runs the delegated-administration gate first. Then it runs its step "2. CRUD permission check" for every caller, through checkObjectPermission. OPERATION_TO_PERMISSION maps insert to allowCreate and delete to allowDelete. resolveObjectPermission answers a set's explicit entry before its '*' wildcard. So that administrator's insert or delete of a grant or holding row is refused with PERMISSION_DENIED. The walled variant passes the delegated gate as a tenant admin and stops at step 2. The wall-less variant already stops at the gate.
  • The client is told the same. /me/permissions keeps that explicit entry, because the super-user fold (foldSuperUserWildcardGrants) skips an object the set names. MePermissionsProvider answers can() from the explicit entry before '*'.
  • The objects' posture. Both objects are managedBy: 'system-data' (full CRUD by bucket default), and their apiMethods include create and delete. No admin-only posture refuses the write before the grant does.
  • Live HTTP measurement: NOT MEASURED. Reason: there is no built objectstack stack in this container. The reading above comes from objectstack main source and objectstack's own pin.

What changes

  • The two sections read two existing rows of AFFORDANCE_GRANTS. Each row is asked of the section's assignment object, with the schema useMetadata().objects registers and with usePermissions().
    • Add reads relatedNew: it creates a junction row under the item, as a related list's Add existing with a link field does.
    • Remove reads relatedRowDelete: it deletes one.
    • A closed row withholds the Add button, the picker that button opens, and each row's Remove. The holders still list.
    • The permission set's empty state follows Add (patch round, head cf08306). It says No users assigned yet. under every grant. Its sentence Click "Add user" to assign. (zh 点击「添加用户」来分配。) is said only where Add is offered. The component's own copy table splits the empty state into the two sentences, and metadata-admin/i18n.ts is unchanged. PositionHoldersSection's empty copy names no button and stays.
    • With no permission provider, every grant reads open, as before. Gating inside each component covers every mount.
  • The census. It grades the four sites mapped, with the reading as the reason. UNMAPPED_BACKLOG is empty. The unmapped helper lost its last caller and retires. The freeze still turns any new unmapped entry red.
  • No published value widens. No row is added to AFFORDANCE_GRANTS, and no export, prop or type changes. So the claim's predicted Clause-②: no holds as measured.
  • Docs.
    • packages/core/README.md: the reuse sentence names both sections and their rows, and the backlog sentence says the pin now holds it empty.
    • packages/app-shell/README.md: the Setup catalog section says the holders' Add and Remove read the grant, which replaces "Assigning holders stays writable".
    • PositionHoldersSection's docblock no longer says a tenant administrator assigns under a wall.
  • Changeset. .changeset/12082-setup-holders-grant.md declares @object-ui/app-shell: patch. plugin-form changes one test file, and core only its README.

Reviewer note (e)

RecordDetailView.feedGrant-12082.test.tsx gains one case. It mounts the record page with the registered sys_comment and sys_comment_reaction schemas in the page's objects: their managedBy, userActions and enable blocks, as objectstack's plugin-audit declares them. The reaction object's effective operation set comes from the spec's own resolveEffectiveApiMethods and effectiveOperationsArray, applied to that enable block. Under a full grant the composer, the reaction add and the take-back are each offered, so the registered policy closes nothing the feed offers.

Tests and gates, at head 7e9eed3

The head is the merge of origin/main dcd1041 into 4754b38. Locally, only the targeted subset ran. The farm is CI's.

  • Suites. pnpm exec vitest run --maxWorkers=2 over 43 files: Test Files 43 passed (43), Tests 373 passed (373), exit 0. The files are:
    • the census pin;
    • the new setupHoldersGrant-12082.test.tsx;
    • the existing AssignedUsersSection and PositionHoldersSection suites;
    • every PermissionMatrixEditor.* suite;
    • the environment-scope and permission-reading ResourceEditPage, catalog and gate suites in metadata-admin, plus PermissionAdvancedFacets.adminScopeAnchor from the merge;
    • the two feed suites, useApproverDirectory.contractEnvelope-5945, and app-shell's ratchet suites.
  • Build. turbo run build --filter='@object-ui/app-shell...': 29 tasks successful, exit 0.
  • Type-check. pnpm --filter @object-ui/app-shell type-check exit 0. pnpm --filter @object-ui/plugin-form type-check exit 0. Each runs tsc --noEmit && tsc -p tsconfig.test.json, and that test program includes src/**/*.test.tsx.
  • Lint. eslint on the five changed source and test files: 0 errors. One warning sits on an added line: the pre-existing onSelectRecords any, re-indented under canAdd. The files are byte-identical between 4754b38 and 7e9eed3.
  • Lock-free gates.
    • node scripts/check-changeset-presence.mjs: exit 0.
    • pnpm check:new-line-citations: exit 0, VERDICT new-cross-file-line-citations: 0 new citation(s).
    • A raw control-byte scan of the changed files: none.
  • pnpm check:readme-exports: exit 1, a partial reading. It could not judge 72 self-imports. All of them sit in packages outside the built closure (cli, plugin-ai, plugin-gantt, plugin-map, plugin-markdown, plugin-timeline). None of its findings names packages/core/README.md or packages/app-shell/README.md. CI's README Export Check gives the whole-tree verdict.
  • Not run locally. The rest of the 510-file metadata-admin directory, and the rest of the farm. A first run over the whole directory was stopped by me before reporting, to free the shared lock. It is NOT MEASURED.

Patch round, head cf08306

The seat's browser check of 7e9eed3 measured one gap: the empty state still told a caller without the create grant to click a button it no longer drew.

  • main was not merged. git merge-tree --write-tree HEAD origin/main against 15d8ca6 reported no conflict.
  • Then the seat synced main into the PR (relay pr_update_branch, head c463195, a merge of main e6175b5). objectui#12699 had replaced every eager-closure ceiling, so the budget gauge on cf08306 read its ceilings as superseded (6217803311).
  • Tests: 5 suites, Tests 83 passed (83). setupHoldersGrant-12082 ran 18 tests, including the four empty-state cases (en and zh, create closed and create open).
  • Gates: @object-ui/app-shell closure build 0, type-check 0, eslint 0 errors, check-changeset-presence 0, check:new-line-citations 0.
  • Ablation A4: the instruction rendered unconditionally gave Tests 2 failed | 16 passed (18), exactly the two closed-grant cases. The restore was proved: blob == HEAD, and git diff HEAD empty.

Ablations (one-time, not committed)

Each ran through objectstack's scripts/ablation-replace.mjs in wrap mode, at 4754b38. In each, the anchor hit once and the mutation landed (the blob changed). After the run, the restore was proved: the blob matches HEAD and git diff HEAD is empty.

  • A1. AssignedUsersSection's canAdd set to true. Red as expected: 4 of the section's 7 cases fail (the organization administrator, read-only, delete-only, and operation-set cases). So does the census's every mapped site names rows of the map, of its own verb, each read by one of the files it names. Overall: Tests 5 failed | 58 passed (63).
  • A2. PositionHoldersSection's canRemove set to true. Red as expected: 4 cases (the organization administrator, read-only, create-only, and operation-set cases) plus the same census assertion. Overall: Tests 5 failed | 58 passed (63).
  • A3. The new note (e) case's registered sys_comment schema given userActions: { create: false }. Red as expected: only the new case fails, on expect(composer(view)).not.toBeNull() (Tests 1 failed | 18 passed (19)). That shows the case reads the registered schemas from the page's objects. The first A3 attempt was a no-op and is void: its replacement carried its own anchor, so the tool refused and restored.

Acceptance notes

  • The object grant only. Each section reads the object grant, which is a necessary condition of the write. It does not read the delegated-administration gate, which can still refuse a holder of the grant: no covering adminScope, a business unit outside the scope's subtree, or a set off its allowlist. Every row of the map has the same object-verdict posture.
  • A dead mount. RecordPermissionAssignmentsRenderer mounts on the sys_permission_set record page. objectstack main no longer registers that object (its migration entry catalog-row-objects-retired), so that mount cannot load against a current server. It is read here only because the gate sits inside the shared component. It is not filed and has no carrier.
  • A contradiction in objectstack, reported to the seat and not filed here. The migration entry walled-catalog-tenant-writes-retired says, in its replacement text, that an organization's administrators assign the catalog to their members through sys_user_position and sys_user_permission_set, "which are unchanged". The shipped organization_admin set is read-only on both. This PR follows the set the server enforces.
  • Partial grants. A grant with create but not delete gets Add and no Remove, and the reverse, as the split cases pin.

Generated by Claude Code

… and Remove only under the grant the server asks (objectui#12082)

The census's last four unmapped write sites, graded on measured reach.
Setup's own gate is not the boundary: its Positions and Permission Sets
entries ask only the app's setup.access, which objectstack's shipped
organization administrator set holds while it is read-only on both
assignment objects, and the server asks the object grant of every caller
at its CRUD step. So each section reads the related list's rows, asked of
its assignment object: Add reads relatedNew (a junction row created),
Remove reads relatedRowDelete (one deleted). A closed row withholds the
Add button, its picker and each row's Remove; the holders still list.

The census grades the four sites mapped and its frozen backlog is empty.
The record feed pin gains one case with the registered sys_comment and
sys_comment_reaction schemas in the page objects (contract review note e).

Claude-Session: https://claude.ai/code/session_01Vk15bz8xHtKGUfXfCm16h4
Co-authored-by: Claude <noreply@anthropic.com>
…tup-assignment-census

Claude-Session: https://claude.ai/code/session_01Vk15bz8xHtKGUfXfCm16h4
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 1 pending changeset(s) describe a file this change touches

Their bodies publish verbatim into the CHANGELOG at the next release, so this is a request to re-read them against your diff — addressed here because you are the one seat that can answer it without re-deriving anything.

⛔ Nothing here blocks, and nothing here is a verdict on your change. This gate exits 0, is not a required context, and judges name resolution, never meaning: it asked whether a pending body names a file you touched. "Is this sentence still true?" is the one question it will not answer, and the one you are being asked to answer.

.changeset/11806-login-server-unreachable.md

  • names packages/app-shell/README.md → packages/app-shell/README.md — edited by this change

    packages/app-shell/README.md documents the two new states in its sign-up table. Its useSignUpOffer recipe for hosts now asks decideSignUpOffer only once the read has answered, and answers unreachable for a failed read.

Read the paragraph, not the line: both false halves of the objectui#8617 claim sat in one paragraph, and correcting either alone would have left it asserting the same wrong thing.

If a claim did go false, correct the body. That is precedented and prose-only, frontmatter untouched; check-changeset-overwrite.mjs will report the correction as its own case 2 ("correcting a declaration on purpose … legitimate"), which is the intended shape — one gate asks for the read, the other records the write.

Not covered, stated so nobody reads this as more: a born-false claim that spells no line address at all (objectui#9495 coordinated one by ORDINAL — "a grep finds that member first" — and deciding that means reading what the sentence means), a claim spelled as a symbol or a package rather than a backticked file name, and a file named ambiguously.

Compared the checked-out tree with e6175b57f (merge-base with origin/main): 7 file(s) changed outside .changeset/, read against 584 pending declaration(s) that publish a body (604 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 70 chunks) 3118.1 KB 3119.4 KB
Main entry chunk (gzip) 65.8 KB 350 KB
Entry file index-BfMA4jq0.js —
Status PASS —

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 21.19KB 7.76KB
app-shell (runtime-config.js) 22.59KB 7.89KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 20.39KB 6.02KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.62KB 0.83KB
auth (authStyles.js) 5.27KB 1.86KB
auth (createAuthClient.js) 41.19KB 11.12KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.26KB 8.03KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (commentAuthorship.js) 1.39KB 0.78KB
collaboration (index.js) 1.93KB 0.85KB
collaboration (useCollaborationTranslation.js) 6.50KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 597.83KB 144.29KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 248.20KB 69.22KB
fields (index.js) 290.12KB 74.14KB
i18n (LocalizationContext.js) 2.92KB 1.42KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.52KB 2.39KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 36.87KB 9.88KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 40.67KB 11.85KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 15.35KB 5.51KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.82KB 2.38KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.04KB 3.92KB
plugin-calendar (index.js) 54.94KB 16.05KB
plugin-charts (index.js) 84.97KB 23.36KB
plugin-chatbot (index.js) 201.89KB 48.13KB
plugin-dashboard (index.js) 146.15KB 39.55KB
plugin-designer (index.js) 233.95KB 50.08KB
plugin-detail (index.js) 263.26KB 69.57KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 180.92KB 46.73KB
plugin-gantt (index.js) 178.36KB 45.08KB
plugin-grid (index.js) 270.08KB 74.80KB
plugin-kanban (index.js) 59.42KB 18.48KB
plugin-list (index.js) 131.40KB 33.50KB
plugin-map (index.js) 27.07KB 8.96KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 46.66KB 13.13KB
plugin-timeline (index.js) 39.21KB 11.85KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 93.43KB 23.94KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 12.07KB 3.68KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.07KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.81KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 23.87KB 7.83KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 1.13KB 0.65KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 5.78KB 2.70KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (node-slots.js) 7.18KB 2.34KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-approval-decision.js) 0.20KB 0.20KB
types (record-components.js) 0.20KB 0.19KB
types (record-panels.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.26KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.48KB 3.50KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

…nly where Add is offered

AssignedUsersSection's empty state read "No users assigned yet. Click
"Add user" to assign." (and its zh twin) to a caller whose create grant on
sys_user_permission_set is closed, so it named a button the section no
longer renders for them. The copy table splits the empty state into the
statement and the click-Add instruction, in en and zh, and the instruction
is rendered only under canAdd. The statement shows either way.

setupHoldersGrant-12082 pins it in both locales: a set nobody holds, under
a delete-only grant (create closed) shows the statement and no instruction,
and under a create-only grant shows both, beside the Add button it names.

Claude-Session: https://claude.ai/code/session_01Vk15bz8xHtKGUfXfCm16h4
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Console Performance Budget — gauge not trustworthy

The eager closure was measured, but one of the ceilings it is measured against no longer means what it names, so this run carries no pass/fail verdict for the performance budget.

This is not a budget violation. Nothing grew: the half marked below is a verdict about the gauge, and a ceiling that has stopped measuring anything can neither clear a bundle nor condemn one.

Step Outcome
Build packages success
Check console performance budget failure

Which half objected:

Eager-closure half Verdict
Aggregate closure ceiling ✅ pass
Per-chunk ceilings ✅ pass
Per-chunk membership (declared packages) ✅ pass
Ceiling sensitivity (headroom) ✅ pass
Ceiling freshness (checkout vs. base branch) ⚠️ superseded ceiling

⚠️ A superseded ceiling is neither a size regression nor a drifted gauge. The bundle was measured correctly and the ceilings on the base branch are correct — but this checkout predates a change to them, so the verdicts above were weighed against numbers that are no longer in force. Nothing grew. Update this branch onto the base branch and let Bundle Analysis run again; do not widen a ceiling to clear it. The Check console performance budget step log names each superseded constant with both values (objectui#6245).

Reason: The entry chunk measured 66.0 KB, but the eager-closure half of this gate returned no trustworthy VERDICT: the report could not be read, a ceiling has drifted out of range of the regression it must catch, or (objectui#6245) a ceiling was replaced on the base branch after this checkout was made. The step log says which. This is not a passing budget — and it is not a size regression either.

See the workflow run for details.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 21.19KB 7.76KB
app-shell (runtime-config.js) 22.59KB 7.89KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 20.39KB 6.02KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.62KB 0.83KB
auth (authStyles.js) 5.27KB 1.86KB
auth (createAuthClient.js) 41.19KB 11.12KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.26KB 8.03KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (commentAuthorship.js) 1.39KB 0.78KB
collaboration (index.js) 1.93KB 0.85KB
collaboration (useCollaborationTranslation.js) 6.50KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 597.81KB 144.28KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 248.20KB 69.22KB
fields (index.js) 290.25KB 74.19KB
i18n (LocalizationContext.js) 2.92KB 1.42KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.52KB 2.39KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 36.87KB 9.88KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 40.67KB 11.85KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 15.35KB 5.51KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.82KB 2.38KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.04KB 3.92KB
plugin-calendar (index.js) 54.94KB 16.05KB
plugin-charts (index.js) 84.97KB 23.36KB
plugin-chatbot (index.js) 201.89KB 48.13KB
plugin-dashboard (index.js) 146.15KB 39.55KB
plugin-designer (index.js) 233.95KB 50.08KB
plugin-detail (index.js) 263.43KB 69.61KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 180.92KB 46.73KB
plugin-gantt (index.js) 178.36KB 45.08KB
plugin-grid (index.js) 270.08KB 74.80KB
plugin-kanban (index.js) 59.42KB 18.48KB
plugin-list (index.js) 131.40KB 33.50KB
plugin-map (index.js) 27.07KB 8.96KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 46.66KB 13.13KB
plugin-timeline (index.js) 39.21KB 11.85KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 93.43KB 23.94KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 12.07KB 3.68KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.07KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.81KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 23.87KB 7.83KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 1.13KB 0.65KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 5.78KB 2.70KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (node-slots.js) 7.18KB 2.34KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-approval-decision.js) 0.20KB 0.20KB
types (record-components.js) 0.20KB 0.19KB
types (record-panels.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.26KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.48KB 3.50KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 70 chunks) 3118.6 KB 3744.1 KB
Main entry chunk (gzip) 66.0 KB 350 KB
Entry file index-CrQON4my.js —
Status PASS —

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 21.19KB 7.76KB
app-shell (runtime-config.js) 22.59KB 7.89KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 20.39KB 6.02KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.62KB 0.83KB
auth (authStyles.js) 5.27KB 1.86KB
auth (createAuthClient.js) 41.19KB 11.12KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.26KB 8.03KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (commentAuthorship.js) 1.39KB 0.78KB
collaboration (index.js) 1.93KB 0.85KB
collaboration (useCollaborationTranslation.js) 6.50KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 597.81KB 144.28KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 248.20KB 69.22KB
fields (index.js) 290.25KB 74.19KB
i18n (LocalizationContext.js) 2.92KB 1.42KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.52KB 2.39KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 36.87KB 9.88KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 40.67KB 11.85KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 15.35KB 5.51KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.82KB 2.38KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.04KB 3.92KB
plugin-calendar (index.js) 54.94KB 16.05KB
plugin-charts (index.js) 84.97KB 23.36KB
plugin-chatbot (index.js) 201.89KB 48.13KB
plugin-dashboard (index.js) 146.15KB 39.55KB
plugin-designer (index.js) 233.95KB 50.08KB
plugin-detail (index.js) 263.43KB 69.61KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 180.92KB 46.73KB
plugin-gantt (index.js) 178.36KB 45.08KB
plugin-grid (index.js) 270.08KB 74.80KB
plugin-kanban (index.js) 59.42KB 18.48KB
plugin-list (index.js) 131.40KB 33.50KB
plugin-map (index.js) 27.07KB 8.96KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 46.66KB 13.13KB
plugin-timeline (index.js) 39.21KB 11.85KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 93.43KB 23.94KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 12.07KB 3.68KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.07KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.81KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 23.87KB 7.83KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 1.13KB 0.65KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 5.78KB 2.70KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (node-slots.js) 7.18KB 2.34KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-approval-decision.js) 0.20KB 0.20KB
types (record-components.js) 0.20KB 0.19KB
types (record-panels.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.26KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.48KB 3.50KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 — ACCEPT · domain:ui seat 3 · session_01Vk15bz8xHtKGUfXfCm16h4 · 2026-10-16T14:54Z

Head reviewed: c463195e526524c618cff3f888e1befa3af5f9b2.

  • Before it: round 1 4754b38, then its merge of main dcd1041 (7e9eed3), the patch round cf08306, and the seat's main sync (relay pr_update_branch, a merge of main e6175b5).
  • Card objectui#12082; claim 6215179482, Clause-②: no. The PR adds no AFFORDANCE_GRANTS row and changes no export, prop or type, so the prediction holds as measured.
  • No contract review is owed: the four faces are untouched, so the gates are CI plus the seat's own read.

Implemented-by: claude/issue-12082-setup-assignment-census
Reviewed-by: session_01Vk15bz8xHtKGUfXfCm16h4

1. Shape.

  • Draft PR against main. The body's first line is Fixes #12082, and the body has no other closing keyword (scanned in full).
  • 8 files, +545/−97.
  • Changeset .changeset/12082-setup-holders-grant.md: @object-ui/app-shell: patch.
  • No content/docs/releases/.
  • Every path is inside the claim's surface (dev report 6216723500, deviations).
  • The census now grades the card's last four unmapped entries mapped, and UNMAPPED_BACKLOG is empty. That is the card's done-when, so Fixes is right.

2. Premises measured (the seat's browser check, on 7e9eed3 against main efa9cb4).

  • Backend: objectstack 66d456d01 examples/app-crm, objectstack dev --seed-admin --fresh (Tenancy: single). Consoles at 1280 px.
  • Principals, read back from GET /api/v1/auth/me/permissions:
    • the platform admin;
    • an org admin made by update-member-role {role: "admin"}, auto-granted organization_admin_no_bypass;
    • a user holding organization_admin directly;
    • a plain member.
surface principal main: Add / Remove / list PR: Add / Remove / list
permission set crm_sales_user platform admin yes / yes / 1 row yes / yes / 1 row
permission set either org admin yes / yes / 1 row no / no / 1 row
position sales_rep platform admin yes / yes / 1 row yes / yes / 1 row
position either org admin yes / yes / 1 row no / no / 1 row
  • Reach on main. As either org admin, all four writes the console sends were refused: POST and DELETE on sys_user_permission_set and on sys_user_position, each 403 PERMISSION_DENIED.
    • The no-bypass org admin was stopped at the delegated-administration gate (ADR-0090 D12).
    • The organization_admin holder was stopped at the CRUD step.
    • No row changed.
  • Positive control on the PR build. As the platform admin, Add and Remove both succeed: 201 / 200 on each object, and the list moves 1 → 2 → 1.
  • This fills the dev's NOT MEASURED live reach.
  • Patch round (cf08306). The one gap the check found was the permission set's empty state saying "Click "Add user" to assign." with no Add button drawn. The instruction now shows only under canAdd.
    • Pinned in setupHoldersGrant-12082: four cases, en and zh, create closed and create open.
    • Ablation A4 (instruction unconditional) reads Tests 2 failed | 16 passed (18): exactly the closed cases. Restore proved.
    • With Add open, the copy is byte-identical to main's.
  • The seat's sync merged main only, so the readings carry to this head.

3. Gates (check-runs pinned to this head).

  • 36 runs: 33 success, 3 skipped (Test (coverage), its matrix row, dependabot), 0 red.
  • Among the successes: Lint, Type Check, Repo Gates, Test (shard 1/16) … (16/16), Test (dist pins), Build & E2E, Build Docs, Bundle Analysis (budget PASS, 6217968970: eager closure 3118.6 KB of 3744.1 KB), Live E2E (informational), the changeset checks, Line Citation Gate and Governed Surface Queue Guard.
  • Two earlier reds belong to superseded heads:
    • Test on cf08306 was its shards, cancelled by the sync push;
    • Bundle Analysis on cf08306 was the gauge's "superseded ceiling" (6217803311) after objectui#12699 raised every ceiling.
  • changeset-claim-re-read names .changeset/11806-login-server-unreachable.md for packages/app-shell/README.md. That body describes the README's sign-up table; this PR edits the Setup catalog section. The claim stays true, so nothing needs correcting.

4. Accepted deviations.

  • Each section reads the object grant, which is a necessary condition of the write. It does not read the delegated-administration adminScope, which can still refuse a holder of the grant. That is the same object-verdict posture as every row of the map.
  • The narrowed local suite: CI ran the rest on all 16 shards.
  • Prose checked sentence by sentence against the diff:
    • the changeset's "Add creates a junction row under the item, so it reads the relatedNew row", "Remove deletes one, so it reads relatedRowDelete", "A closed row withholds its affordance … The holders still list", "With no permission provider mounted every grant reads open" and "No row is added to AFFORDANCE_GRANTS";
    • the app-shell README's "so that administrator sees the holders with no Add and no Remove" (measured above);
    • the core README's reuse sentence.

5. Out of scope (where each went).

6. Landing.

  • Not governed: check-governed-queue-guard.mjs --test over the 8 paths reads NOT GOVERNED.
  • 642 lines changed, under the 3000-line threshold.
  • Clause-②: no, no contract face.
  • Every check is green or an expected skip.
  • ⇒ The seat lands it: relay pr_ready + automerge_enable, through the merge queue.
  • On MERGED, Fixes closes objectui#12082. The seat then strips pm:dispatched and reads closed_by.

Generated by Claude Code

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

Labels

Projects

None yet

2 participants