Repository navigation
fix(app-shell): Setup's permission-set and position holders offer Add and Remove only under the grant the server asks (objectui#12082) - #12685
Conversation
… 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>
|
changeset-claim-re-read
|
✅ Console Performance Budget
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
Size Limits
|
…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>
|
| 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) |
⚠️ 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 letBundle Analysisrun again; do not widen a ceiling to clear it. TheCheck console performance budgetstep 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
✅ Console Performance Budget
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
Size Limits
|
席位核对 — ACCEPT ·
|
| 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:POSTandDELETEonsys_user_permission_setand onsys_user_position, each 403PERMISSION_DENIED.- The no-bypass org admin was stopped at the delegated-administration gate (ADR-0090 D12).
- The
organization_adminholder 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 undercanAdd.- 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.
- Pinned in
- The seat's sync merged
mainonly, 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 GateandGoverned Surface Queue Guard. - Two earlier reds belong to superseded heads:
Testoncf08306was its shards, cancelled by the sync push;Bundle Analysisoncf08306was the gauge's "superseded ceiling" (6217803311) after objectui#12699 raised every ceiling.
changeset-claim-re-readnames.changeset/11806-login-server-unreachable.mdforpackages/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
relatedNewrow", "Remove deletes one, so it readsrelatedRowDelete", "A closed row withholds its affordance … The holders still list", "With no permission provider mounted every grant reads open" and "No row is added toAFFORDANCE_GRANTS"; - the
app-shellREADME's "so that administrator sees the holders with no Add and no Remove" (measured above); - the
coreREADME's reuse sentence.
- the changeset's "Add creates a junction row under the item, so it reads the
5. Out of scope (where each went).
- The objectstack migration text says organization admins assign holders, while the shipped
organization_adminsets are read-only and the server answers 403. → filed security: the 18 migration text says organization admins assign positions and permission sets to their members; the shipped organization_admin sets are read-only on both assignment objects and the server refuses objectstack#23960, with the measured table. - A plain member opens Setup's item pages by direct URL, and the permission-set section draws its refused read as "No users assigned yet" (both builds). → filed objectui#12706.
- The permission-set section's
catch {}swallows a failed Remove. A caller holding the delete grant but refused by the delegated gate would see nothing. No named producer of that grant shape exists. → Acceptance notes. RecordPermissionAssignmentsRendererstill mounts on the retiredsys_permission_setrecord page (dev, read from source). → Acceptance notes.
6. Landing.
- Not governed:
check-governed-queue-guard.mjs --testover 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,
Fixescloses objectui#12082. The seat then stripspm:dispatchedand readsclosed_by.
Generated by Claude Code
Fixes #12082
Clause-②: no
The census's last four
unmappedwrite 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 becomesmappedto an existing row of the map.WRITE_SITESnow holds nounmappedentry, 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.
addUsersandremoveUserinAssignedUsersSectionwritesys_user_permission_setrows.addHoldersandremoveHolderinPositionHoldersSectionwritesys_user_positionrows. None read a grant.PermissionMatrixEditPage, environment scope, not embedded) and the position editor (ResourceEditPage'sENVIRONMENT_SECTIONS). A third mount is thesys_permission_setrecord page (RecordPermissionAssignmentsRenderer). The catalog scope is documented as "a VIEW scope, never an authority" (catalog-scope.ts).mainat570f01d27. The Setup app asksrequiredPermissions: ['setup.access']. Its Access Control entriesPositionsandPermission Sets, contributed by plugin-security, carry norequiredPermissions. They link to/apps/setup/metadata/position?scope=environmentand/apps/setup/metadata/permission?scope=environment.organization_adminset listssetup.accessin itssystemPermissions. Itsobjectspinsys_user_permission_setandsys_user_positiontoallowRead: truewith every write bit false. objectstack pins that inobjects/rbac-objects.test.tsasorganization_admin is read-only on global RBAC tables to prevent privilege escalation.auto-org-admin-grant.tsgrants that set, or its derived wall-less variantorganization_admin_no_bypass(same per-object entries), to every organization owner or admin member.checkObjectPermission.OPERATION_TO_PERMISSIONmapsinserttoallowCreateanddeletetoallowDelete.resolveObjectPermissionanswers a set's explicit entry before its'*'wildcard. So that administrator's insert or delete of a grant or holding row is refused withPERMISSION_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./me/permissionskeeps that explicit entry, because the super-user fold (foldSuperUserWildcardGrants) skips an object the set names.MePermissionsProvideranswerscan()from the explicit entry before'*'.managedBy: 'system-data'(full CRUD by bucket default), and theirapiMethodsincludecreateanddelete. No admin-only posture refuses the write before the grant does.mainsource and objectstack's own pin.What changes
AFFORDANCE_GRANTS. Each row is asked of the section's assignment object, with the schemauseMetadata().objectsregisters and withusePermissions().relatedNew: it creates a junction row under the item, as a related list's Add existing with a link field does.relatedRowDelete: it deletes one.cf08306). It saysNo users assigned yet.under every grant. Its sentenceClick "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, andmetadata-admin/i18n.tsis unchanged.PositionHoldersSection's empty copy names no button and stays.mapped, with the reading as the reason.UNMAPPED_BACKLOGis empty. Theunmappedhelper lost its last caller and retires. The freeze still turns any newunmappedentry red.AFFORDANCE_GRANTS, and no export, prop or type changes. So the claim's predictedClause-②: noholds as measured.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/12082-setup-holders-grant.mddeclares@object-ui/app-shell: patch.plugin-formchanges one test file, andcoreonly its README.Reviewer note (e)
RecordDetailView.feedGrant-12082.test.tsxgains one case. It mounts the record page with the registeredsys_commentandsys_comment_reactionschemas in the page'sobjects: theirmanagedBy,userActionsandenableblocks, as objectstack'splugin-auditdeclares them. The reaction object's effective operation set comes from the spec's ownresolveEffectiveApiMethodsandeffectiveOperationsArray, applied to thatenableblock. 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
7e9eed3The head is the merge of
origin/maindcd1041into4754b38. Locally, only the targeted subset ran. The farm is CI's.pnpm exec vitest run --maxWorkers=2over 43 files:Test Files 43 passed (43),Tests 373 passed (373), exit 0. The files are:setupHoldersGrant-12082.test.tsx;AssignedUsersSectionandPositionHoldersSectionsuites;PermissionMatrixEditor.*suite;ResourceEditPage, catalog and gate suites inmetadata-admin, plusPermissionAdvancedFacets.adminScopeAnchorfrom the merge;useApproverDirectory.contractEnvelope-5945, and app-shell's ratchet suites.turbo run build --filter='@object-ui/app-shell...': 29 tasks successful, exit 0.pnpm --filter @object-ui/app-shell type-checkexit 0.pnpm --filter @object-ui/plugin-form type-checkexit 0. Each runstsc --noEmit && tsc -p tsconfig.test.json, and that test program includessrc/**/*.test.tsx.onSelectRecordsany, re-indented undercanAdd. The files are byte-identical between4754b38and7e9eed3.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).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 namespackages/core/README.mdorpackages/app-shell/README.md. CI'sREADME Export Checkgives the whole-tree verdict.metadata-admindirectory, 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
cf08306The seat's browser check of
7e9eed3measured one gap: the empty state still told a caller without the create grant to click a button it no longer drew.mainwas not merged.git merge-tree --write-tree HEAD origin/mainagainst15d8ca6reported no conflict.maininto the PR (relaypr_update_branch, headc463195, a merge ofmaine6175b5). objectui#12699 had replaced every eager-closure ceiling, so the budget gauge oncf08306read its ceilings as superseded (6217803311).Tests 83 passed (83).setupHoldersGrant-12082ran 18 tests, including the four empty-state cases (en and zh, create closed and create open).@object-ui/app-shellclosure build 0, type-check 0, eslint 0 errors,check-changeset-presence0,check:new-line-citations0.Tests 2 failed | 16 passed (18), exactly the two closed-grant cases. The restore was proved: blob == HEAD, andgit diff HEADempty.Ablations (one-time, not committed)
Each ran through objectstack's
scripts/ablation-replace.mjsin wrap mode, at4754b38. In each, the anchor hit once and the mutation landed (the blob changed). After the run, the restore was proved: the blob matchesHEADandgit diff HEADis empty.AssignedUsersSection'scanAddset totrue. 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'severy 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).PositionHoldersSection'scanRemoveset totrue. 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).sys_commentschema givenuserActions: { create: false }. Red as expected: only the new case fails, onexpect(composer(view)).not.toBeNull()(Tests 1 failed | 18 passed (19)). That shows the case reads the registered schemas from the page'sobjects. 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
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.RecordPermissionAssignmentsRenderermounts on thesys_permission_setrecord page. objectstackmainno longer registers that object (its migration entrycatalog-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.walled-catalog-tenant-writes-retiredsays, in its replacement text, that an organization's administrators assign the catalog to their members throughsys_user_positionandsys_user_permission_set, "which are unchanged". The shippedorganization_adminset is read-only on both. This PR follows the set the server enforces.Generated by Claude Code