Skip to content

feat(app-shell,fields,console): Setup's positions and permission sets read the registry, through the metadata-admin pages' environment scope (part of objectui#7611) - #12089

Merged
objectstack-fleet[bot] merged 10 commits into
mainfrom
claude/issue-7611-setup-catalog-registry
Oct 11, 2026
Merged

objectstack-fleet[bot] merged 10 commits into
mainfrom
claude/issue-7611-setup-catalog-registry

Conversation

@objectstack-fleet

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

Copy link
Copy Markdown
Contributor

Part of #7611

Clause-②: yes

  • The claim (6095693499) declared no provisionally and said it becomes yes if the public surface widens. It widens by one export: @object-ui/app-shell gains ENVIRONMENT_SCOPE_QUERY, a string constant. No accept set narrows. The changeset is minor.

Updated at round 2 (bc7a86bb6f): the sections below describe this head. The round reports are 6104663917 and 6106225321; the contract record is 6106517489.

Draft by dispatch (objectui AGENTS.md §9 ②). The epic PM of objectstack#15194 moves this PR after objectstack#15204 stage 1 has merged and been measured. Several halves of the card need server or spec changes first; they are listed below with the gate each one waits on. Size: 2,084 changed lines at round 2 (additions plus deletions, tests included), 26 files, against 206505c. One PR, under the 3,000-line budget.

What this PR does

The Setup catalog for positions and permission sets reads the registry. It reuses the metadata-admin list and editors in an environment scope (?scope=environment) instead of adding a page family, as ruling 6094171670 asks ("Build it by reuse, not by new pages"):

  • The list (…/metadata/position?scope=environment, …/metadata/permission?scope=environment) shows every item GET /api/v1/meta/TYPE serves, the platform's own sets included. The Studio list still shows one project package's slice. Every link the list emits keeps the scope, and so does the editor's breadcrumb.
  • Gated for Setup (#22621 → A). A caller without manage_metadata gets no create affordance and a read-only editor, and the page says why. Under single the reason is that the platform administrator defines these items and the caller's organization assigns them. Under a wall the reason is that the operator defines them in Studio. Both editors (generic and permission matrix) now apply this caller gate wherever they render, because the metadata door refuses that caller's save in every scope. The lock banner's "New" offer follows the same gate.
  • Holders. In the scope, the permission-set editor shows the set's holders (AssignedUsersSection) and the position editor shows the position's holders (PositionHoldersSection, new). Both read and write assignment rows by the item's name.
  • AssignedUsersSection reads by name. Grants come from the permission_set name column. The positions that distribute a set come from the registry's position definitions (permissionSets, the shape on stage 1's branch). The section no longer reads sys_position_permission_set or sys_position rows.
  • The recipient picker (sharing rules) lists the position registry through the console's metadata store, and no longer reads sys_position rows.
  • The console's system/roles, system/positions and system/permissions go to the catalog list instead of the object pages.
  • The active switch. The list has an active column and an active/inactive filter, through one seam, catalog-activation.ts, described next.

The active switch writes the activation ledger

The switch shows the state of sys_metadata_activation (no row means active), read through the data door the way Setup's packaged-actions section already reads it. It writes through objectstack's stage-2c door, POST /api/v1/security/_activation/:type/:name with { enabled }. It reads and writes no catalog row, and there is no row-flag fallback. The door's refusals reach the page in the server's own words. The audience anchors everyone and guest show a disabled switch with the reason, read from the spec's AUDIENCE_ANCHOR_POSITIONS. Capabilities have no switch.

On objectstack main, the door still refuses an item saved through the metadata door (503, "Package is required"). The fix is objectstack#15204 stage 2d (finding F5). Until it lands, that refusal shows on the page.

Measured against a running objectstack showcase (main d85615dd, fresh database)

Read Result
GET /api/v1/meta/permission 17 items. Equal to the 17 sys_permission_set rows. No item carries active, managedBy or _lock.
GET /api/v1/meta/position 16 items. Equal to the 16 sys_position rows. No item carries active or is_default.
GET /api/v1/meta/capability 2 items. There are 11 sys_capability rows. The nine platform capabilities (manage_metadata, manage_users, setup.access and the others) exist only as rows.
Platform admin PUT /meta/permission/NAME 200. The set is projected to a row (active: true, managed_by: admin).
Platform admin PUT /meta/position/NAME 200. No row is created (the S7 write-through runs from row to definition only).
Organization admin GET /meta/permission, /position, /capability 200 for all three.
Organization admin PUT /meta/permission/…, /meta/position/… 403 FORBIDDEN, "Saving a metadata item requires the manage_metadata capability."
Organization admin, the activation door 403 PERMISSION_DENIED (round 2, on 490cb6d9). The console no longer PATCHes the catalog row.
Grant by name: POST sys_user_permission_set with only permission_set 400 VALIDATION_FAILED. permission_set_id is still required.
Assignment by name to a registry-only position 400 VALIDATION_FAILED on d85615dd. 201 since objectstack stage 2a (round 2, on 490cb6d9).

The organization-admin persona was the showcase's auditor demo user, promoted to org admin (manage_org_users, setup.access, setup.write; no manage_metadata).

Browser verification (objectui at this branch's head, Vite on a private port, proxied to the showcase above)

Platform administrator, single:

  • system/permissions lands on /apps/setup/metadata/permission?scope=environment. The list has 18 rows; every link carries the scope, New is offered, and no read-only reason shows.
  • Create: catalog New, name and label, Save (a draft), then the matrix editor's Save makes it live. GET /meta/permission/c9_ui_set answers 200 and the row is projected. Edit: granting Read on showcase_account and saving answers {"showcase_account":{"allowRead":true}}. Deactivate and reactivate (round 2, on 490cb6d9): the switch posts to the activation door. One ledger row reads off and the holder loses the set; switching on returns it. The catalog row is untouched.
  • A packaged set opens locked, with Clone offered and no Save.
  • Positions: system/positions lands on the position catalog (17 rows). A position created from New (draft, then Publish) is live in the registry. The auditor page lists its holders. Assign by name: adding the phone persona landed {"position":"auditor"} in sys_user_position.
  • On a registry-only position (round 2, on 490cb6d9), adding a holder answers 201. The switch shows the door's F5 refusal until objectstack stage 2d lands.

Organization administrator: the list states the reason, offers no New, and its switches are disabled. The set editor has no Save and states the reason, and so does the position editor.

Requests the catalog made: /meta/permission, /meta/position, /meta/*/NAME/layers, and, for the switch, /data/sys_metadata_activation and the activation door. No request fetched a merged list. The matrix editor's capability picker still reads /data/sys_capability; see the gates below.

NOT MEASURED: a walled posture (group / isolated). The showcase runs single, and the walled reason text is covered only by a unit test.

What this PR cannot do yet: the server and spec halves, each with its gate

  1. The active switch: done. The door landed in objectstack stage 2c. Its remainder, for items saved through the metadata door, is objectstack#15204 stage 2d.
  2. Capabilities (the Capabilities page and CapabilityMultiSelectField) still read sys_capability. The registry has served 11 of 11 since objectstack#22669, so the server half is met. The capability move stays in part 2.
  3. Granting a permission set by name. The section reads by name, but a new grant still carries permission_set_id. That is one row lookup, resolveGrantRowId, at add time. Gate: the grant door accepts permission_set without permission_set_id.
  4. Assigning a registry-only position: done on the server since objectstack stage 2a (201, measured at round 2).
  5. Approver pickers and the approver directory (approverIdentity, useApproverDirectory, FlowReferenceField, flow-node-config) and the decision-output position picker (decisionOutputParams). These follow the spec: APPROVER_VALUE_BINDINGS.position is { source: 'record', object: 'sys_position', valueField: 'name' }, and DecisionOutputDef says a position output collects sys_position record ids. Contract-first: the spec changes first, then these readers. Gate: a registry binding in @objectstack/spec, plus a name-valued position decision output.
  6. The Setup navigation (nav_positions, nav_permission_sets and nav_capabilities, all type: 'object' in plugin-security) still opens the object pages until stage 8. The catalog URLs for stage 8 are /apps/setup/metadata/position?scope=environment and /apps/setup/metadata/permission?scope=environment. The object pages' special cases stay until then, because those pages remain reachable: the RecordDetailView assignments slot, recordDelete's reset copy, data-objectstack's facet-widget stamp and PermissionFacetLink. They become dead code at stage 8 and leave in a cleanup paired with it.
  7. Clone to customize still runs the clone_permission_set row action, which stage 8 deletes. Rebuilding it on the metadata door means deciding which keys a clone carries. adminScope is ruled out. isDefault would silently add the copy to the everyone baseline. That decision is raised in the report rather than made here.
  8. The position → permission-set binding editor needs a form field. Stage 1 as landed declares permissionSets in position.form.ts as a tags field. This PR builds the read side against that shape (positionsDistributing); what remains for the editor is a registry picker widget.

Cloud: the .objectui-sha hold this PR needs

cloud consumes objectui main on framework 56bf27affb (the v17 line). I read that commit's source. It has no security-catalog.ts, no builtin-positions.ts registration and no position write-through. Positions are seeded and declared into rows only (bootstrap-builtin-positions, bootstrap-declared-positions). So GET /api/v1/meta/position there serves the declared positions only: no built-in position and no organization-created position. The recipient picker and the position catalog would show cloud's organizations fewer positions than they see today. Hold: cloud's .objectui-sha must not move past this PR's merge commit until cloud moves to v18 (after C7).

objectui#7205

This PR does not close it. The Setup catalog pages are no longer ObjectViews, so a column-header click there no longer persists an org-wide overlay. That was this re-route's share of the card. The persistence in ObjectView's sort handler is untouched, and it is still live for every other object, so objectui#7205 remains open.

objectstack#11753

That card answers 404 on REST and on the web, while its neighbours answer 200, so it has been destroyed. What survives is its record in objectstack: the CHANGELOG entry for ActionParamSchema.carryOver (spec half, #11992) and ADR-0126 §7.1. objectui never rendered carryOver. Its only producers are the five clone_permission_set facet params, which stage 8 deletes with the action. A metadata-door clone copies the whole definition and collects only a name and a label, so it needs no carry-over param. Once stage 8 lands, carryOver has no producer; that is reported for the PM.

Reader census (37 files at base 023f00d4, tests, docs and Markdown excluded)

  • Changed (reads): AppContent.tsx (three routes), AssignedUsersSection.tsx, RecipientPickerField.tsx.
  • Read, unchanged, and gated: CapabilityMultiSelectField.tsx (gate 2); approverIdentity.ts, useApproverDirectory.ts, decisionOutputParams.ts, FlowReferenceField.tsx and flow-node-config.ts (gate 5); permission-set-clone-dispatch.ts, PermissionMatrixEditor.tsx's clone path and its four i18n.ts clone strings (gate 7).
  • Object-page special cases that stay until stage 8 (gate 6): RecordDetailView.tsx, RecordPermissionAssignmentsRenderer.tsx, recordDelete.ts, data-objectstack/src/index.ts and PermissionFacetLink.tsx.
  • Text only (comments, docblocks and one component-input description): ObjectView.tsx, capabilityLint.ts, fields/src/index.tsx, MetadataFieldsPage.tsx, InlineFieldInput.tsx, RelatedList.tsx, plugin-detail/src/index.tsx, record-related-list.tsx, ObjectForm.tsx and field-types.ts, plus all ten locale files (11 hits, every one a comment).

Verification

Measured at the branch head named in the report.

  • Type-check (type-check, hyphenated): @object-ui/fields, @object-ui/app-shell and @object-ui/console all pass, each after building its dependency closure.
  • New and updated tests (vitest, run from the repo root):
    • ResourceListPage.environmentScope-7611 9/9; ResourceEditPage.setupCatalog-7611 5/5; PermissionMatrixEditor.setupCatalog-7611 5/5;
    • PositionHoldersSection 4/4; catalog-activation 5/5; AssignedUsersSection and its envelope test 9/9;
    • RecipientPickerField 11/11; AppContent.systemHubRoutes 22/22.
  • Full suites of the three affected packages: see the report's tests field.
  • Ablation (each mutation committed first, landed on disk with its anchor count 1 → 0, restored from HEAD and proven by blob hash and an empty git diff HEAD):
    • removing the environment-scope bypass in the list turned 5 of 9 list pins red;
    • removing canAuthor from the editor's canWrite turned exactly the caller-gate pin red (1 failed, 4 passed).
  • Gates passed: check:new-line-citations (0 new), check:control-bytes, check:i18n-keys, check:i18n-dead-keys, check:side-effects-array, check:unreferenced-sources, check:vi-mock-specifiers, check:vi-mock-inherit, check:vi-mock-override-shape, check:test-path-roots, check:doc-fences, check:doc-types and check:docs-route-closure. The changeset gates pass too: presence (21 published source files, 1 changeset) and no-major.
  • NOT MEASURED, because a precondition was not met (unbuilt @object-ui/cli and @object-ui/plugin-ai, outside this diff's closure): check:readme-exports (the entries it could judge had 0 wrong-path and 0 fabricated) and check:doc-snippets. Also not run locally: pnpm lint and the eager-closure budget, which needs a console production build. Both run in CI.

Acceptance notes

  • In the Setup catalog, a position or permission set created with New is saved as a draft first. The permission matrix's Save, or the position editor's Publish, makes it live. Today's Setup object pages wrote a live row in one step.
  • The permission list registration dropped its managedBy "Source" column. The registry serves no such key, so it read "Custom" for every set, the platform's own included. The list's own Source badge (Artifact or Runtime, from _packageId and _provenance) remains.
  • Until stage 1 lands, a set's "via position" holders are empty on main. On main the bindings are junction rows, which this section no longer reads.

Generated by Claude Code

…in list and editors in their environment scope (objectui#7611)

ADR-0131 D3/D7: positions and permission sets live in the environment
registry, and Setup lists that registry. Built by reuse: the metadata-admin
list and editors gain an environment scope (`?scope=environment`) instead of a
new page family.

- the list lists every registry item of the type, re-gated for Setup: create
  only for `manage_metadata` holders, a posture-aware read-only reason for
  everyone else, an active switch and a status filter behind one row-state
  seam (`catalog-activation.ts`) pending the activation ledger;
- the editors are read-only with a reason for a caller without
  `manage_metadata`; in the environment scope the permission-set editor shows
  the set's holders and the position editor shows the position's holders;
- the set's holders are read by name: grants by the `permission_set` column,
  distributing positions from the registry's `permissionSets`;
- the recipient picker lists the `position` registry;
- the console's `system/{roles,positions,permissions}` land on the catalog.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rerax7QTjKMPCUZxQUtPFR
…r gate, the row-state seam and the holders sections (objectui#7611)

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rerax7QTjKMPCUZxQUtPFR
…, the locked item's New offer follows the caller gate, and the permission list drops a Source column the registry never fills (objectui#7611)

Found driving the Setup catalog in a browser against objectstack main:

- `position` renders through its designer preview, so the environment
  section is placed under the designer as a bounded strip;
- the lock banner's "New" offer is shown only to a caller who may author;
- the `permission` registration's `managedBy` column read "Custom" for every
  set, the platform's own included: the registry serves no such key. The
  list's own Source badge, read from `_packageId` / `_provenance`, stays.

Adds the changeset for the Setup catalog work.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rerax7QTjKMPCUZxQUtPFR
…ir environment scope (objectui#7611)

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

github-actions Bot commented Oct 10, 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 b7cd5c891 (merge-base with origin/main): 25 file(s) changed outside .changeset/, read against 312 pending declaration(s) that publish a body (318 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 291 chunks) 3169.8 KB 3204.6 KB
Main entry chunk (gzip) 73.7 KB 350 KB
Entry file index-6ZrUdHAk.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) 20.05KB 7.41KB
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) 18.15KB 5.40KB
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.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
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.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
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) 587.83KB 141.44KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 240.39KB 67.09KB
fields (index.js) 270.05KB 68.44KB
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.26KB 11.71KB
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) 14.32KB 5.17KB
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) 53.43KB 15.54KB
plugin-charts (index.js) 84.72KB 23.27KB
plugin-chatbot (index.js) 201.52KB 47.99KB
plugin-dashboard (index.js) 144.20KB 38.95KB
plugin-designer (index.js) 233.53KB 49.80KB
plugin-detail (index.js) 249.19KB 65.68KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 177.30KB 45.92KB
plugin-gantt (index.js) 179.17KB 45.07KB
plugin-grid (index.js) 249.43KB 69.15KB
plugin-kanban (index.js) 52.77KB 16.56KB
plugin-list (index.js) 120.09KB 30.26KB
plugin-map (index.js) 27.24KB 9.03KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 39.06KB 11.80KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 91.93KB 23.24KB
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.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
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-components.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

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 98d46ebadbb195624045fd8d6f03595383eb7b81
Local-runs: none

PR #12089 (draft by dispatch, Part of #7611) against main at 023f00d4: 25 files, +1,799 / −136 (1,935 changed lines, under the 3,000-line budget). No governed-surface path is in the file list. Inputs: the card body and its 14 comments (rulings 6094171670 and 6095024693, the claim 6095693499 and its amendment 6095720536, the os-dev report 6097735087 and the seat's answers 6097763840), the PR body, file list and net diff, and the 43 check-runs on the head, read 2026-10-10T13:09Z: 40 success, 3 skipped (the coverage job, its shard matrix and dependabot), 0 failures. Lint, Type Check, Test shards 1/8 through 8/8, Build & E2E, Changeset Declaration, Changeset Bump Policy, Changeset Fixed Group Check, README Export Check, Doc Snippet Type Check, Line Citation Gate and Governed Surface Queue Guard are all success; the Console Performance Budget comment reads PASS at 3,169.8 KB against a 3,204.6 KB eager-closure budget.

① Derived judgments

Each public-surface or accept-set change the diff implies, and whether it is right.

  1. @object-ui/app-shell gains one export, ENVIRONMENT_SCOPE_QUERY (a string constant from catalog-scope.ts, declared in the package entry and documented in the README). Widening. Everything else new in catalog-scope.ts, catalog-activation.ts and PositionHoldersSection.tsx stays module-internal: isEnvironmentScope, environmentScopeSuffix, SETUP_CATALOG_TYPES, readCatalogRowStates, writeCatalogActive, positionsDistributing and the section itself reach no package entry. Right, and it is the one symbol the PR body's widening names.
  2. The metadata-admin URLs accept ?scope=environment on the list, the editor and the create route, and every link the pages emit carries it on (item links, the create link, the lock banner's New, the breadcrumb). A URL accept-set widening. Any other scope value falls through to the package scope silently, which is the convention the existing ?package= parameter already follows. Right.
  3. The editors' write affordance now also asks the caller tier: MetadataResourceEditPage and PermissionMatrixEditPage fold useCanAuthorMetadata() into canWrite / writable in every scope, Studio included, and say why (engine.edit.capabilityReadOnly). This narrows no accept set the server honours: the hook's own ADR-0066 contract names manage_metadata as the capability every metadata-authoring surface ultimately requires, it fails open on an unknown answer, and the PR measured the door's 403 for an organization administrator. Right. For the reader: the pins cover the environment scope; the Studio reach of the same gate rests on the hook's contract, not on a pin in this PR.
  4. The list's create affordance and empty-state hint read canCreate (the type tier, and in the environment scope the caller tier), with the posture-aware reason (engine.catalog.readOnly.single / .walled, chosen by postureHasOrgWall). Right, and it is the #22621 → A parity gate as ruled: the platform administrator authors; an organization administrator reads and is told why.
  5. The built-in permission resource registration drops its managedBy "Source" column and adds description. The registry serves no managedBy, so the column read "Custom" for every set, the platform's own included: a claim the runtime did not deliver. Right in substance (the provenance badge from _packageId / _provenance remains). The PR body's acceptance notes carry it; the changeset body does not. A CHANGELOG omission, noted, not a contract fault.
  6. @object-ui/fields RecipientPickerField: the position kind lists the position registry through MetadataCtx and stores the machine name, as before; it no longer queries sys_position. Props and the stored value are unchanged, so no accept set narrows. The one behaviour change for a host: with no metadata store mounted, the kind degrades to the plain text input instead of a row list. The changeset states it in those words. Right: one source, no merged list (ADR-0131 D7), and the degrade keeps the stored name editable.
  7. @object-ui/console: system/roles, system/positions and system/permissions forward to the catalog (…/metadata/position?scope=environment, …/metadata/permission?scope=environment) instead of the object pages, with the same prefix and search/hash handling as SystemObjectRedirect; the zero-app branch now reaches a page instead of the "No Apps Configured" guard. Right; the object pages stay reachable by their own URLs until stage 8.
  8. AssignedUsersSection reads by name: direct grants by the permission_set column, distributing positions from the registry's permissionSets (stage 1's shape), never sys_position_permission_set or sys_position. One row read remains, on ADD only (resolveGrantRowId), because the grant door still requires permission_set_id (measured 400); the create then carries both keys. Right against the measured contract, with two consequences the PR states: via-position holders are empty on main until stage 1 lands, and the add path breaks the day the sys_permission_set table goes unless the follow-up lands first (③).
  9. catalog-activation.ts keeps today's switch behaviour behind one seam: sys_permission_set / sys_position rows read with $select id,name,active, the flag written through the data door, which is what the Setup object pages' Activate and Deactivate actions write today. The ledger has no door for these types yet (measured: the two existing doors fix flow and action), and the resolver still reads the row flag, so a ledger-wired switch would flip a bit nothing reads. An item with no row shows a dash and the reason; a refused read shows a question mark, never a guessed "active". Right, and it is the one module that changes when the server's door lands.
  10. PositionHoldersSection is a plain data page over sys_user_position by position name: read, add by name, remove, and the server's refusal in its own words (measured: a registry-only position is refused by name today). Right; it matches the card's "assignment pages stay data pages, write the item by name".

Not implied by the diff, stated so nobody reads silence as a judgment: no @object-ui/i18n locale-pack key is added (the new strings live in metadata-admin's own i18n.ts and the sections' inline copy), no exported component prop changes, and no server contract changes.

② Semver level

  • The diff publishes source in exactly three released packages: apps/console (@object-ui/console), packages/app-shell, packages/fields. The changeset .changeset/7611-setup-catalog-registry.md names those three at minor. Matches. The docs edits (content/docs/guide/console-architecture.md, the app-shell README) need no entry.
  • The PR body's Clause-② line reads yes, no arm. Well-formed (at most one arm; none is required). yes takes at least minor: right. The widening is ① item 1 (plus the URL parameter, ① item 2). No (narrowing): ① item 6 changes behaviour for a provider-less host but narrows no accepted input or value, and ① item 3 narrows nothing the server honours. So no breaking changeset, no migration text and no ADR-0087 disposition marker are owed. No major (objectui's fixed group never declares one); skip-changeset is correctly not used.
  • The claim's provisional no (6095693499) became yes in the PR body for the stated reason, which is the procedure the claim itself set. Right.
  • Gate conclusions on the head: Changeset Declaration, Changeset Bump Policy, Changeset Fixed Group Check and Changeset Overwrite Report all success. The advisory Changeset Claim Re-read (6096110894) names .changeset/11806-login-server-unreachable.md because it cites packages/app-shell/README.md; this diff adds a new "The Setup catalog" section to that README and leaves the sign-up table and the useSignUpOffer recipe untouched, so that pending claim stays true. Answered, no correction needed.

③ Boundary flags

Every dev flag (the report's deviations, NOT MEASURED items, open_questions, out_of_scope_findings) and the seat's answers (6097763840), each answered here or escalated with its carrier.

Deviations

  1. Clause-② no → yes: answered, right (②).
  2. One PR instead of two: answered. 1,935 changed lines, one changeset, one unit.
  3. Part of, not Fixes: answered, right. The card stays open for the follow-up objectui PR the seat names.
  4. Build-first items measured as not buildable and left unchanged (capability select; approver, decision-output and flow-inspector readers; object-page special cases; clone dispatch): answered by 6097763840. The capability select after objectstack#22669 (stage 6b-1a) lands; the approver and decision-output readers after the spec card (Q2 → A); the special cases, nav entries and carryOver at stage 8 with a paired cleanup; the clone after the server door (Q1 → A). Escalated to the epic lane's follow-up PR.
  5. Binding editor not built: Q3 → A (a spec form line plus a registry multi-picker widget), after stage 1. Escalated to the spec seat's queue, as 6097763840 routes it.
  6. managedBy column dropped; the lock banner's New gated on the caller: answered, right (① item 5). The CHANGELOG omission is noted for the dev's follow-up, not blocking.
  7. The verify lock held 1h03m by a broad local run: a process deviation with no contract effect. Escalated to the epic PM's round report.
  8. Required test checks named as 4 shards: answered by the head's check-runs, 8 shards, all success.
  9. Create is draft-then-live (the matrix Save, or Publish) where the object pages wrote a live row in one step: answered. Inside the parity gate as ruled (the platform administrator creates, edits, deactivates and reactivates from the same Setup page, browser-measured, with the list, the filters, the matrix editor and the switch intact). It is a UX change against "as today", so it goes to the maintainer through the epic PM's round report, where the veto window applies.

NOT MEASURED, and what answers each

  • A walled posture (group / isolated): the walled reason text is pinned by a unit test only; the read-only state itself is the same caller gate under every posture. Accepted for a single showcase; escalated: the stage-1 re-measurement this PR already owes should include one walled read if a walled fixture is available.
  • check:readme-exports and check:doc-snippets locally: answered by README Export Check and Doc Snippet Type Check, both success on the head.
  • pnpm lint and the eager-closure budget: answered by Lint success and the Console Performance Budget PASS (3,169.8 KB of 3,204.6 KB; the headroom is 34.8 KB, worth a glance on the follow-up).
  • A set's holders list after a grant is not in the PR body's browser section (the position holders are); the by-name read rests on the dev's reading that the grant door stamps permission_set from the id, pinned by the section's unit tests. Escalated: add it to the stage-1 re-measurement.

open_questions: all three answered A by the epic PM in 6097763840 on governing text (ADR-0126 §7.1; ADR-0131 D4; #22006 B), none changing this PR. Q1, the clone door lands as an objectstack stage before stage 8. Q2, the by-name position binding goes to the spec seat and blocks stage 8. Q3, the permissionSets form field and widget ride with Q2. Answered; the maintainer's veto window applies.

out_of_scope_findings: each placed by 6097763840 inside the ruled cutover (#22601 B). The capability registry serving 2 of 11 → objectstack#22669. The activation door for permission / position behind refuseUngrantedActivationWrite, and the resolver's consult point → objectstack#15204 stage 2. position-catalog-refusal resolving names in rows → stage 2. Grant by name alone → stage 8 / C7b. Spec bindings, Setup nav entries, object-page special cases and carryOver → stage 8. The cloud .objectui-sha hold → the maintainer via the round report. Escalated, carriers named.

Landing order this record relies on (the PR is draft by dispatch; the epic PM moves it):

  • after objectstack#15204 stage 1 merges and the holders read is re-measured against permissionSets;
  • before the cutover's table-retiring PR, together with objectstack's .objectui-sha bump. The objectui follow-up that moves catalog-activation.ts onto the ledger door and resolveGrantRowId off the row must also land before that table-retiring PR, or the catalog's switch and grant-add break on that day;
  • cloud's .objectui-sha does not move past this PR's merge commit until cloud is on v18 (measured on the v17 framework: the meta door lists no built-in or organization-created positions there);
  • objectui#7205 stays open (blocked on objectstack#15206); this PR removes only the catalog pages' share of it, as its body says.

Implemented-by: claude/issue-7611-setup-catalog-registry
Reviewed-by: session_01Rerax7QTjKMPCUZxQUtPFR

VERDICT: PASS


Generated by Claude Code

…ctui#7611 stage-1 patch round)

Claude-Session: https://claude.ai/code/session_01Rerax7QTjKMPCUZxQUtPFR
Co-authored-by: Claude <noreply@anthropic.com>
…write sites this branch adds (objectui#7611)

Merging main brought objectui#12082's write census, which counts every
DataSource write call site in the console tree. This branch adds three:
the position holders' add and remove (sys_user_position) and the catalog
switch's row write (catalog-activation's writeCatalogActive). Each is
entered as `unmapped`, the same entry the census gives the sibling
permission-set assignment section (AssignedUsersSection): none reads a
CRUD grant on its object. The switch is offered on the caller's
manage_metadata gate.

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 291 chunks) 3171.4 KB 3204.6 KB
Main entry chunk (gzip) 74.1 KB 350 KB
Entry file index-DUQFBmLx.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) 20.05KB 7.41KB
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) 18.15KB 5.40KB
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.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
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.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
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) 586.66KB 141.23KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 240.39KB 67.09KB
fields (index.js) 270.05KB 68.44KB
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.26KB 11.71KB
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) 14.32KB 5.17KB
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) 53.66KB 15.64KB
plugin-charts (index.js) 84.72KB 23.27KB
plugin-chatbot (index.js) 201.52KB 47.99KB
plugin-dashboard (index.js) 144.20KB 38.95KB
plugin-designer (index.js) 233.53KB 49.80KB
plugin-detail (index.js) 249.19KB 65.68KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 177.59KB 46.00KB
plugin-gantt (index.js) 179.17KB 45.07KB
plugin-grid (index.js) 249.43KB 69.15KB
plugin-kanban (index.js) 53.23KB 16.71KB
plugin-list (index.js) 120.09KB 30.25KB
plugin-map (index.js) 27.24KB 9.03KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 39.06KB 11.80KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 92.11KB 23.27KB
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.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
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-components.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

…say what objectstack's ADR-0131 stage 1 changed (objectui#7611)

Stage 1 (objectstack bfc15d27) moved the authorization resolver's
deactivation read onto the activation ledger: it reads neither catalog
row's `active` column. The catalog switch still writes that row flag
(the ledger door for `permission` and `position` is objectstack#15204
stage 2c, not landed), so on a stage-1 framework the switch changes the
flag and nothing else. catalog-activation.ts said the resolver still
reads the row flag; it now says which half moved and which has not.
The changeset and the app-shell README say the same, and the changeset
says that a set lists no holders through a position on a framework from
before stage 1.

The capability exclusion's stated reason (the registry served the
package-declared capabilities only) stopped holding when objectstack#22669
declared the platform's capabilities as metadata; catalog-scope.ts, the
README and a test comment now name the remaining reason, the follow-up.

No code path changes.

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 291 chunks) 3171.4 KB 3204.6 KB
Main entry chunk (gzip) 74.1 KB 350 KB
Entry file index-DUQFBmLx.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) 20.05KB 7.41KB
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) 18.15KB 5.40KB
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.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
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.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
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) 586.66KB 141.23KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 240.39KB 67.09KB
fields (index.js) 270.05KB 68.44KB
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.26KB 11.71KB
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) 14.32KB 5.17KB
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) 53.66KB 15.64KB
plugin-charts (index.js) 84.72KB 23.27KB
plugin-chatbot (index.js) 201.52KB 47.99KB
plugin-dashboard (index.js) 144.20KB 38.95KB
plugin-designer (index.js) 233.53KB 49.80KB
plugin-detail (index.js) 249.19KB 65.68KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 177.59KB 46.00KB
plugin-gantt (index.js) 179.17KB 45.07KB
plugin-grid (index.js) 249.43KB 69.15KB
plugin-kanban (index.js) 53.23KB 16.71KB
plugin-list (index.js) 120.09KB 30.25KB
plugin-map (index.js) 27.24KB 9.03KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 39.06KB 11.80KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 92.11KB 23.27KB
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.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
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-components.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

os-dev-report
{
"issue": 7611,
"status": "done",
"branch": "claude/issue-7611-setup-catalog-registry",
"pr": "objectui#12089",
"head": "4472538c604d29397cc345a8d9e28b32420732d2",
"session": "session_01Rerax7QTjKMPCUZxQUtPFR (a subagent of the epic PM, so this is the parent's session)",
"round": "The post-stage-1 patch round: landing condition 1 of the ACCEPT 6097840521, and the Landed record 6104351995 on objectstack#15204. The PR body is unchanged under the one-write rule. Its corrections are in corrected_claims below.",
"premise_still_valid": {
"all": true,
"stage_1_squash_bfc15d275ba3_on_objectstack_main": true,
"positions_declare_permissionSets_and_the_registry_serves_them": true,
"resolver_reads_definitions_and_the_activation_ledger_and_no_row_flag": true,
"ledger_door_for_permission_and_position_not_landed": true,
"item_9_the_resolver_still_reads_the_row_flag_is_now_false": true,
"a_walled_fixture_exists": false
},
"premise_evidence": "(1) In the objectstack-c9-main worktree (detached at origin/main, which is bfc15d27 itself), git merge-base --is-ancestor bfc15d275ba3 HEAD exits 0. (2) GET /api/v1/meta/position answers 16 items, 9 of which carry permissionSets, for example auditor with [showcase_auditor] and everyone with [showcase_member_default, member_default]. (3) At bfc15d27, readDisabledCatalogNames in resolve-authz-context.ts reads sys_metadata_activation for the position and permission types. Its docblock: the catalog ROW's active column is not read here at all. The switch measurement below agrees. (4) At bfc15d27 the only _activation route is POST /actions/_activation/:object/:action, and the flow toggle is still the flow door. Stage 2c (objectstack#22736) is not on main. (6) No walled fixture exists; see measurements.walled_read.",
"summary": "I merged objectui main into the PR branch (merge 6e5635d, no text conflicts). The merge carried one semantic conflict: the write census from objectui#12082 counts every DataSource write site in the tree, and it went red on 3 sites this branch adds. I entered those 3 sites, then corrected the stale stage-1 sentences in the changeset, the app-shell README and two code comments. Measured in a browser against a showcase booted from objectstack bfc15d27, using the console built from this branch: (1) a set's holders now include the holders of a position that declares the set in permissionSets; (2) a set granted through the section's add path appears in that set's holders list, read by name; (3) the catalog switch now writes a flag that decides nothing. The walled read is NOT MEASURED because no walled fixture exists. catalog-activation.ts was not moved.",
"merge": {
"merge_commit": "6e5635d1ba27615313517911136a8b6829db562e (parents 98d46eb and objectui main 5330afd)",
"text_conflicts": "none (packages/app-shell/src/views/metadata-admin/i18n.ts was the only file both sides touched, and it auto-merged)",
"semantic_conflict": "objectui#12082's write census (affordanceGrantMap-12082.test.tsx, merged in 5af42db) failed with 3 unlisted sites: PositionHoldersSection addHolders create, PositionHoldersSection removeHolder delete, and catalog-activation writeCatalogActive update. Commit 47a3631 enters each one as unmapped, which is the same entry the census already gives the sibling AssignedUsersSection add and remove. None of the three reads a CRUD grant on its object, and the switch is offered on the caller's manage_metadata gate. The census then passed 33/33. This adds 3 entries to objectui#12082's unmapped backlog."
},
"size": "1,956 changed lines (+1,820 / -136) in 26 files, measured as git diff --shortstat from the new merge base 5330afd to 4472538. That is under 3,000. It was 1,935 lines in 25 files against 023f00d. The census entries and the comment, README and changeset corrections account for the difference.",
"measurements": {
"setup": "The showcase was booted with objectstack dev --seed-admin --fresh on a private port, from the objectstack-c9-main worktree at bfc15d27, after building the showcase's dependency closure there. The objectui console ran under Vite from this branch's worktree on a private port, proxied to that showcase. The browser was Playwright with chromium from /opt/pw-browsers, signed in as the seeded platform admin. Request and response pairs were captured from the page's own network traffic. The screenshots are in the session scratchpad and are described in words here.",
"distributed_sets": "PASS. On /apps/setup/metadata/permission/showcase_auditor?scope=environment, the Assigned Users section under the matrix lists one row: Ada Auditor (demo), auditor.demo@example.com, with the badge 'via position auditor'. This is the 'empty until stage 1 lands' case from contract record item 8. Its reads: GET /api/v1/meta/position answered 200, with auditor carrying permissionSets [showcase_auditor]. GET /api/v1/data/sys_user_position filtered on position in [auditor] answered 200 with 1 record (usr_showcase_auditor_demo). GET /api/v1/data/sys_user_permission_set filtered on permission_set = showcase_auditor answered 200 with 0 records, so the holder arrives through the position alone. GET sys_user by id answered Ada Auditor (demo). Request census on that page: 45 API requests, none naming sys_position or sys_position_permission_set. The position page /apps/setup/metadata/position/auditor?scope=environment lists Position holders 1: Ada Auditor (demo); its 42 requests also name neither table. Screenshot 1: the set editor with the holders row under the matrix. Screenshot 2: the position editor with its holders section.",
"set_holders_after_grant": "PASS. On /apps/setup/metadata/permission/showcase_contributor?scope=environment, the section first read 'Assigned Users 0, No users assigned yet'. Then Add user, tick Mei Phone (demo), Confirm. The pairs: (a) resolveGrantRowId sent GET /api/v1/data/sys_permission_set with top 1, select id and filter name = showcase_contributor, and got 200 with records [id ps_mv36j5e2yq0raeof]. (b) The grant door: POST /api/v1/data/sys_user_permission_set with body permission_set_id ps_mv36j5e2yq0raeof, permission_set showcase_contributor and user_id usr_showcase_phone_demo. It answered 201 with record 2Qqxo362Ze5rxfGt: permission_set showcase_contributor, organization_id org_mv36j3rhrjdbz5nu, granted_by the admin. (c) The by-name re-read: GET /api/v1/data/sys_user_permission_set with top 500 and filter permission_set = showcase_contributor answered 200 with exactly that record. The section then read 'Assigned Users 1: Mei Phone (demo), direct, phone.demo@example.com'. After a full page reload, the same by-name GET returned the same record and the list was the same. The grant takes effect: Mei's GET /api/v1/auth/me/permissions (signed in with the demo persona password) then lists permissionSets [showcase_contributor, showcase_member_default, member_default].",
"walled_read": "NOT MEASURED. No walled fixture exists. For a group or isolated posture, objectstack serve imports @objectstack/organizations from the host app, and no example on bfc15d27 declares it (app-showcase, app-crm, app-todo and app-multi-package checked). A walled boot therefore fails fast (ADR-0093 D5). The only boot past that is OS_ALLOW_DEGRADED_TENANCY=1, which is the explicitly unwalled state, so a read there would not be a walled read. The walled reason text stays pinned by its unit test only, as on the earlier head."
},
"switch_result": "As expected, nothing changes. On /apps/setup/metadata/permission?scope=environment as the platform admin, the showcase_contributor switch read 'Active — click to deactivate'. Clicking it sent PATCH /api/v1/data/sys_permission_set/ps_mv36j5e2yq0raeof with active false, which answered 200 and left the row at active false. GET sys_metadata_activation answered 0 rows. The holder (Mei, holding the set by the direct grant above) then read GET /api/v1/auth/me/permissions twice, once on a fresh sign-in and once on her existing session. Both answers are byte-identical to her answer before the switch (jq -S compare, cmp exit 0): permissionSets still include showcase_contributor, and showcase_task still allows edit. This is not a stale cache: sys_permission_set is in GRANTS_CACHE_WATCHED_OBJECTS at bfc15d27, so the PATCH retired the grants cache and the answer was recomputed. Control leg (the instrument can move): removing Mei's grant through the section's remove path sent DELETE /api/v1/data/sys_user_permission_set/2Qqxo362Ze5rxfGt, which answered 200. Her permissionSets then dropped showcase_contributor and showcase_task edit went from true to false, on the same session. Restore: switching the row back on sent PATCH with active true, which answered 200. Not run: a ledger-side control (a sys_metadata_activation row written through the data door, to show the ledger does switch the set off), because the session's permission classifier refused that write. catalog-activation.ts is unchanged, as the dispatch ordered; it moves onto the ledger door after objectstack#22736 lands.",
"corrected_claims": [
"C1. PR body, 'The active switch is pending the server half': 'The authorization resolver also still reads the row flag (isRowActive), both on main and on stage 1's branch.' This is FALSE at bfc15d27: the resolver reads deactivation from sys_metadata_activation only, and reads neither catalog row's active column. Measured by switch_result. catalog-activation.ts's module doc said the same thing and now says which half moved and which has not. Corrected in 4472538.",
"C2. PR body, server gate 1: 'The resolver's consult point must also move from the row flag to the ledger.' Stage 1 has done this. What remains is the door, objectstack#15204 stage 2c (objectstack#22736).",
"C3. Changeset, active-switch bullet: 'which is today's behaviour on the Setup object pages'. It now reads: the switch writes the flag as the Setup object pages' Activate and Deactivate actions do; on a framework whose resolver reads the activation ledger (objectstack's ADR-0131 stage 1 onward), that flag no longer switches a grant off, so there the switch changes the flag and nothing else until the ledger accepts these types. The app-shell README says the same.",
"C4. PR body, measured table: 'GET /api/v1/meta/capability: 2 items. There are 11 sys_capability rows.' Also gate 2: 'The registry serves 2 of 11.' At bfc15d27 the registry serves 11 items against 11 rows, because objectstack#22669 (964699128, stage 6b-1a) declared the platform capabilities as metadata. Gate 2's server half is met; the capability move stays in part 2. The old reason also sat in the catalog-scope.ts comment, the app-shell README sentence and a catalog-activation.test.ts comment; all three now name the remaining reason, the follow-up.",
"C5. PR body, gate 8: 'Stage 1's branch adds PositionSchema.permissionSets but no field in position.form.ts, so the generic editor has nothing to render it with.' Stage 1 as landed declares permissionSets in position.form.ts as a tags field. Measured: the position editor shows Permission Sets with its value (showcase_auditor) on a declared, locked position, and the New position form shows no Permission Sets field. The console's pinned @objectstack/spec 17.7.0 refuses the key (finding F1). Editing an environment-authored position's binding: NOT MEASURED. Q3 (a registry multi-picker widget) still stands, because the landed widget is free-text tags.",
"C6. PR body, acceptance note: 'Until stage 1 lands, a set's via position holders are empty on main.' Stage 1 has landed, and the holders were measured non-empty (distributed_sets). The changeset now says that the empty case belongs to a framework from before stage 1 (cloud's v17 pin is one).",
"C7. PR body, measured table rows for permission and position (17 items equal to 17 rows; 16 equal to 16; no item carries active, managedBy or _lock): re-read at bfc15d27, unchanged.",
"C8. Gate 3 and the AssignedUsersSection docblock: 'the grant door still requires permission_set_id'. Holds by source at bfc15d27: sys-user-permission-set.object.ts has permission_set_id required: true, and stage 1 did not touch that file. The add path's POST with both keys answered 201. A name-only POST was not re-sent.",
"C9. Gate 4 and the PositionHoldersSection docblock: a registry-only position is refused, because the assignment door looks the name up in sys_position. Holds by source at bfc15d27: position-catalog-refusal.ts has POSITION_CATALOG_OBJECT = sys_position, and stage 1 changed only its test. Not re-measured live.",
"C10. Size: was 1,935 lines in 25 files against 023f00d; now 1,956 lines in 26 files against 5330afd (see size).",
"C11. The coordinator's item on .changeset/11806-login-server-unreachable.md: its README paragraph is still true on the merged head. The sign-up table has the 'config read is still pending' and 'config read failed' rows. The useSignUpOffer recipe answers unreachable for a failed read and pending while the read is loading, before it calls decideSignUpOffer. This PR's README edit is the separate Setup catalog section. No correction was needed; check-changeset-overwrite exits 0."
],
"tests": "All measured at 4472538 (git rev-parse --short HEAD at the run). The tests were NARROWED to 255 files through os-verify-lock, held 9m42s: pnpm exec vitest run --maxWorkers=2 with the file list. Result: Test Files 255 passed (255), Tests 3945 passed (3945), VERDICT command-exit 0. The population is the union of three sets: (a) this PR's 10 pin files plus the write census; (b) 171 test files that import a changed module by name, or metadata-admin's i18n; (c) 86 tree-scanning test files that readdirSync packages or apps. The full suites of app-shell, fields, console and plugin-form are left to CI. The write census alone: 1 failed / 32 passed on the merge, then 33/33 after 47a3631. Type-check (the hyphenated type-check script, each echoing its script name, 0 'error TS'), after building the closures of @object-ui/console and @object-ui/plugin-form: @object-ui/fields, @object-ui/app-shell, @object-ui/plugin-form and @object-ui/console all pass. The lock printed batch-last-exit 0 because the loop body used ';', but the chain was joined with && and exits on the first failure, and its final marker 'TYPECHECK ALL DONE' printed. Lint, narrowed: pnpm exec eslint over the 23 changed ts/tsx files gives exit 0, 0 errors and 171 warnings (counts from --format json). The proof for the narrowing: (1) the population is the root eslint.config.js, the only config, which every package's eslint . reads; (2) 23 files read from the json output; (3) the config sets no parserOptions.project or projectService, so linting is not type-aware and this diff cannot move another file's verdict, except through a custom eslint-rules rule that reads other files, which I did not audit. With --no-inline-config there are 3 react-hooks/static-components errors in ResourceEditPage.tsx, on base-main lines (blame 4babf40) that carry inline disables; objectui's lint honours inline config. Repo-wide pnpm lint is left to CI. Ablation: none this round (no behaviour changed).",
"checks": {
"census_affordanceGrantMap_12082": "exit 1 on the merge (3 unlisted), then exit 0 (33/33) at 47a3631 and at 4472538",
"vitest_narrowed_255_files": "exit 0 (3945 passed)",
"type_check_fields_app_shell_plugin_form_console": "exit 0 for each",
"eslint_23_changed_files": "exit 0 (0 errors)",
"check_new_line_citations": "exit 0 (VERDICT: 0 new citations)",
"check_control_bytes": "exit 0",
"check_test_path_roots": "exit 0",
"check_i18n_keys": "exit 0",
"check_unreferenced_sources": "exit 0",
"check_vi_mock_specifiers": "exit 0",
"check_doc_fences": "exit 0",
"check_changeset_presence": "exit 0 (23 source files of 4 released packages, 1 changeset)",
"check_changeset_no_major": "exit 0",
"check_changeset_overwrite": "exit 0 (no pre-existing changeset modified)",
"check_changeset_fixed": "exit 0",
"check_changeset_claims": "exit 0 (the 11806 flag, answered in C11)",
"own_control_byte_scan_of_edited_files": "grep exit 1 (no match)",
"ci": "in_progress on 4472538; not awaited"
},
"mcp_calls": "0. No MCP GitHub tool was called.",
"api_writes": "1 REST write: POST /repos//issues/12089/comments (this addendum, through post-stamped and the fleet-write relay). There were 2 git pushes of claude/issue-7611-setup-catalog-registry (98d46eb to 47a3631, then 47a3631 to 4472538); a push is not a REST write. There were no label or assignee writes. Reads were REST GETs and git fetches.",
"open_questions": [
{
"question": "F1 below: once this PR lands with objectstack's pin bump (stage 5), the Setup position pages put a red 'does not match the spec' banner on every declared position that names permissionSets, because objectui pins @objectstack/spec 17.7.0. Studio's position editor already shows the banner. Should landing wait for objectui's spec-18 pin?",
"options": [
"A. Land as planned. The banner rides until objectui's spec-18 pin bump, which objectui owes anyway (ce991bd names it pending).",
"B. Hold this PR's landing, or the stage-5 pin bump, until objectui pins a spec that declares PositionSchema.permissionSets."
],
"recommendation": "A. The banner is not this PR's defect: the generic editor shows it in Studio too. The fix is the spec pin, and B would tie C9 to a release that the release-cut condition itself sequences after C9. The seat may still want the spec-18 pin ordered before the stage-5 bump, so the window stays short."
}
],
"out_of_scope_findings": [
"F1 · class: a · reach: browser, this branch's console against objectstack bfc15d27's showcase, on /apps/setup/metadata/position/auditor both with and without ?scope=environment. The page shows a red banner: 'This metadata does not match the spec — 1 validation error(s). (root): Unrecognized key(s) on this position: permissionSets ... capability arrives via runtime bindings (sys_position_permission_set rows, created in Setup ...)'. It appears for every position that declares permissionSets; finance declares none and gets no banner. The server's own _diagnostics reads valid. Cause: objectui resolves @objectstack/spec 17.7.0, whose PositionSchema still refuses the key that stage 1 made legal. The banner calls a valid item invalid, and points the admin at junction rows that grant nothing after stage 1. This PR did not introduce it, but it routes Setup's positions to that editor. carrier: objectui's spec-18 pin bump · dedupe words: position permissionSets unrecognized key banner; spec 17.7.0 pin position validation; does not match the spec permissionSets",
"F2 · class: a · reach: browser, /apps/setup/metadata/permission/showcase_auditor?scope=environment. The matrix editor's provenance badge reads 'Custom' beside a lock banner that says a code package provides the set. The badge reads draft.managedBy, which the registry never serves (at bfc15d27, 0 items carry it), or the packageId prop, which the environment scope does not set. It is the same class as the managedBy list column this PR dropped. carrier: objectui#7611 part 2 (the follow-up PR) · noted, not filed · dedupe words: permission set badge Custom package environment scope; managedBy provenance badge matrix editor",
"F3 · carrier: objectui#7611 part 2, after objectstack#22736 lands · on a stage-1 framework the switch's own label ('Active — click to deactivate') offers a deactivation that switches nothing off. This is the window that release-cut condition (c) accepts · noted, not filed",
"F4 · carrier: objectui#12082's remainder · the merge adds 3 unmapped write sites to its census backlog (PositionHoldersSection add and remove, and the catalog switch), siblings of AssignedUsersSection's two · noted, not filed"
],
"deviations": [
"Boots: each server's startup ran under os-verify-lock, held 16s for the showcase and 5s for the console. The servers then ran detached through the measurement session, so the lock was not held while idle. I stopped both by their own process groups afterwards; both ports were free.",
"The tests are a narrowed set, not the full package suites (see tests). The population is declared; CI runs the farm.",
"The ledger-side control for the switch was not run (the classifier refused the data-door write). The control that was run is the grant removal.",
"objectstack's dispatch-gates is not objectui's, so the gate families were derived by hand from objectui's package.json check scripts and what this diff touches.",
"check-changeset-presence counts the census test under packages/plugin-form/src as published source of a 4th package. The changeset still names app-shell, fields and console, because plugin-form ships nothing new; objectui's fixed group bumps every package together anyway."
]
}


Generated by Claude Code

…ctui#7611 round 2)

Claude-Session: https://claude.ai/code/session_01Rerax7QTjKMPCUZxQUtPFR
Co-authored-by: Claude <noreply@anthropic.com>
…he activation ledger through its door (objectui#7611)

objectstack's ADR-0131 stage 2c landed the activation door for the
security catalog, POST /api/v1/security/_activation/:type/:name with
{ enabled }, which writes one sys_metadata_activation row and no catalog
row. The resolver reads that ledger and never a catalog row's own
`active` column, so the switch now does the same on both sides:

- The state shown is the ledger's, read through the data door's list
  of sys_metadata_activation the way the resolver reads it (false or 0
  is off, no row is active). The record and truncation readers are
  Setup > Packaged automation's, imported. No catalog row is read, and
  there is no fallback onto the row flag (objectstack#22601 B).
- The write goes through the door by item name. Its refusals reach the
  page as the server's own sentence (actionErrorDetail).
- The audience anchors everyone and guest, which the door refuses in
  both directions, render a disabled switch with the reason. The names
  are the spec's AUDIENCE_ANCHOR_POSITIONS, imported lazily as
  clientValidation.ts imports the same module, because the list page is
  in the console's eager closure.
- An item with no catalog row (an environment-authored position) is
  switchable now, so the dash-with-reason state and its i18n key go.
- capability keeps no switch (the door refuses the type with 400).

The write census loses the switch's data-door update site.
PositionHoldersSection's docblock no longer says a registry-only
position is refused: stage 2a judges the assignment by the catalog.

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 290 chunks) 3172.4 KB 3204.6 KB
Main entry chunk (gzip) 74.1 KB 350 KB
Entry file index-BfoX8oYc.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) 20.05KB 7.41KB
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) 18.15KB 5.40KB
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.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
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.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
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) 586.66KB 141.23KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 240.39KB 67.09KB
fields (index.js) 270.05KB 68.44KB
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.26KB 11.71KB
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.31KB 15.86KB
plugin-charts (index.js) 84.72KB 23.27KB
plugin-chatbot (index.js) 201.52KB 47.99KB
plugin-dashboard (index.js) 144.20KB 38.95KB
plugin-designer (index.js) 233.53KB 49.80KB
plugin-detail (index.js) 249.33KB 65.74KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 177.59KB 46.00KB
plugin-gantt (index.js) 179.17KB 45.07KB
plugin-grid (index.js) 249.52KB 69.17KB
plugin-kanban (index.js) 53.23KB 16.71KB
plugin-list (index.js) 122.77KB 31.19KB
plugin-map (index.js) 27.24KB 9.03KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 39.21KB 11.85KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 92.11KB 23.27KB
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.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
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-components.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

…ts state and offers no click (objectui#7611)

Measured as an organization administrator (no manage_metadata) on the
permission-set catalog: the switches are disabled, and the read-only
reason says why, but each one still announced "Active — click to
deactivate". That is the same offer-that-does-nothing shape the switch's
label had on a stage-1 framework (finding F3 of round 1). A disabled
switch for a caller who cannot author now reads "Active" or "Inactive",
the status filter's own strings; an author's switch keeps the click
wording, and an audience anchor keeps its reason.

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 290 chunks) 3172.6 KB 3204.6 KB
Main entry chunk (gzip) 74.1 KB 350 KB
Entry file index-CpDlypdu.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) 20.05KB 7.41KB
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) 18.15KB 5.40KB
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.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
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.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
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) 586.97KB 141.32KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 240.39KB 67.09KB
fields (index.js) 270.05KB 68.44KB
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.26KB 11.71KB
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.31KB 15.86KB
plugin-charts (index.js) 84.72KB 23.27KB
plugin-chatbot (index.js) 201.52KB 47.99KB
plugin-dashboard (index.js) 144.20KB 38.95KB
plugin-designer (index.js) 233.53KB 49.80KB
plugin-detail (index.js) 249.33KB 65.74KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 177.59KB 46.00KB
plugin-gantt (index.js) 179.17KB 45.07KB
plugin-grid (index.js) 249.52KB 69.17KB
plugin-kanban (index.js) 53.23KB 16.71KB
plugin-list (index.js) 122.77KB 31.19KB
plugin-map (index.js) 27.24KB 9.03KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 39.21KB 11.85KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 92.11KB 23.27KB
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.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
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-components.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

os-dev-report
{
"issue": 7611,
"status": "done",
"branch": "claude/issue-7611-setup-catalog-registry",
"pr": "objectui#12089",
"head": "bc7a86bb6fce7a8e70f640591b3bbe6da2f458d6",
"session": "session_01Rerax7QTjKMPCUZxQUtPFR (a subagent of the epic PM, so this is the parent's session)",
"round": "Round 2, the task list of seat note 6104671088. The PR body is unchanged under the one-write rule; its corrections are in corrected_claims below.",
"premise_still_valid": {
"stage_2c_cd3d39112cbb_on_objectstack_main": true,
"stage_2a_2ff0825dabd5_on_objectstack_main": true,
"console_reads_ledger_state_through_a_door_it_already_uses": true,
"registry_only_position_now_assignable": true,
"door_switches_every_catalog_item": false,
"a_walled_fixture_exists": false
},
"premise_evidence": "(1, 2) In the objectstack-c9-main worktree, detached at origin/main 490cb6d9, git merge-base --is-ancestor answers exit 0 for cd3d39112cbb and for 2ff0825dabd5 (an exit 0 proves itself, so no control leg is needed). (3) GET /api/v1/data/sys_metadata_activation?top=1000 is the data-door read that Setup's packaged-actions section already makes (packagedActions.ts). It answers 200 for the platform admin, and 200 for an organization admin holding manage_org_users, setup.access and setup.write but not manage_metadata. A plain member (Mei, and Ada as a member) gets 403 PERMISSION_DENIED. (4) See measurements.c9. (5) The door refuses an environment-authored item: a 503 with 'Package is required'. See finding F5. (6) No example on 490cb6d9 declares @objectstack/organizations (app-crm, app-multi-package, app-showcase, app-todo and embed-objectql were checked). Positive control: the same grep finds @objectstack/spec in each example. Nothing has changed since round 1.",
"summary": "I merged objectui main 206505c as 48cc047; there were no text or semantic conflicts. Then catalog-activation.ts moved onto the activation ledger (4c62713). The switch now shows the ledger's state, read the way the resolver reads it (no row means active), through the data door's list of sys_metadata_activation. It writes through POST /api/v1/security/_activation/:type/:name with { enabled }. No catalog row is read or written, and there is no row-flag fallback. The door's refusals reach the page in the server's own words. The audience anchors render as disabled switches with the reason. capability keeps no switch. PositionHoldersSection's docblock no longer claims that a registry-only position is refused. A bounded in-place fix (bc7a86b) followed: a disabled switch for a caller who cannot author now reads Active or Inactive instead of 'click to deactivate'. Measured in the browser against a showcase booted from objectstack 490cb6d9: switching off takes the set away from its holder, switching on gives it back, and a registry-only position is now assignable. The door refuses environment-authored items with a 503, which is objectstack finding F5.",
"merge": {
"merge_commit": "48cc047cc230f4c5dabe9dbd6e875e3c235b0db3 (parents 4472538 and objectui main 206505c)",
"text_conflicts": "none",
"semantic_conflicts": "None found. The only file both sides touched is the write census (affordanceGrantMap-12082.test.tsx). Main added objectui#12103's recordEdit and listInlineEdit rows there and auto-merged; the census passed in the union run. This round removes one census entry, the switch's data-door update, because that write site left the tree.",
"main_moved_again": "During verification, objectui main advanced from 206505c to 29b9949 (2 commits: objectui#12108 and objectui#12105, 51 files). None of those files is in this PR's 26, and neither commit touches the write census. I did not merge a second time, because the merge queue rebuilds on current main. As a result, 206505c is no longer an ancestor of the latest main for this head."
},
"size": "2,084 changed lines (+1,948 / -136) in 26 files, from git diff --shortstat at merge base 206505c to bc7a86b. Under 3,000. Round 1 measured 1,956 against 5330afd. This round's own delta (48cc047 to bc7a86b) is 10 files, +389 / -271 at 4c62713, plus the 2-file label fix.",
"measurements": {
"setup": "The showcase was booted with objectstack dev --seed-admin --fresh on a private port from the objectstack-c9-main worktree at 490cb6d9, after building the showcase's dependency closure there. The objectui console ran under Vite from this branch's worktree on a private port, proxied to that showcase. The browser was Playwright with chromium from /opt/pw-browsers, signed in through the console's own login form as the seeded platform admin. The first-run timezone prompt was answered 'Keep the default', which writes nothing. Request and response pairs come from the page's own network traffic. Screenshots are in the session scratchpad and are described in words here. Setup row: Mei (usr_showcase_phone_demo) was given a direct grant of showcase_contributor through POST /api/v1/data/sys_user_permission_set with permission_set_id ps_mv3e00q8m9vtqfnk, permission_set showcase_contributor and user_id. That answered 201 with record b1XV9CrMeytpCZwP, and Mei's GET /api/v1/auth/me/permissions then listed permissionSets [showcase_contributor, showcase_member_default, member_default] and showcase_task.allowEdit true.",
"off": "PASS at 4c62713. On /apps/setup/metadata/permission?scope=environment, the showcase_contributor switch read aria-checked true, labelled 'Active — click to deactivate'. Clicking it sent POST /api/v1/security/_activation/permission/showcase_contributor with body {"enabled":false}, which answered 200 with {"success":true,"data":{"type":"permission","name":"showcase_contributor","enabled":false}}. The ledger then held exactly one row (GET /api/v1/data/sys_metadata_activation as admin): id qrOjyAFJHm8-a-bU, metadata_type permission, name showcase_contributor, package_id com.example.showcase, active false. The catalog row sys_permission_set ps_mv3e00q8m9vtqfnk still read active true, so no catalog row was written. After a full reload the switch read aria-checked false, 'Inactive — click to activate'. Screenshot: the permission-set list with showcase_contributor's switch grey and every other switch green. Mei's GET /api/v1/auth/me/permissions, both on her existing session and on a fresh sign-in, listed permissionSets [showcase_member_default, member_default] and showcase_task.allowEdit false. The page made 79 API requests and none named sys_permission_set or sys_position. Its ledger reads were GET /api/v1/data/sys_metadata_activation?top=1000, once before and once after the reload.",
"on": "PASS at 4c62713. Clicking the same switch sent POST /api/v1/security/_activation/permission/showcase_contributor with {"enabled":true}, which answered 200 with enabled true. The same ledger row (qrOjyAFJHm8-a-bU) then read active true. After a reload the switch read aria-checked true. Mei's /auth/me/permissions listed [showcase_contributor, showcase_member_default, member_default] again, with showcase_task.allowEdit true. Again 79 requests, none naming a catalog row object.",
"anchor_everyone": "PASS. On /apps/setup/metadata/position?scope=environment, everyone and guest each render a dimmed, disabled switch in the on position (aria-checked true, disabled true). Each switch's accessible name and its wrapper's title read: 'An audience anchor: it carries the baseline every principal of the deployment holds, so it cannot be switched off. Change what it grants through the permission sets bound to it.' Control: auditor's switch on the same page is enabled. A forced click on everyone's switch sent 0 POST requests and left it on. Screenshot: the position list scrolled to everyone and guest, both with pale disabled switches. The door's own answer to the same target, probed with curl, which writes nothing: POST /api/v1/security/_activation/position/everyone answered 400 VALIDATION_ERROR, 'Position 'everyone' is an audience anchor: it carries the authenticated baseline of every principal in the deployment, so it is not switchable through this door (ADR-0090 D5/D9). Change what it grants through the permission sets bound to it instead.' guest answered the same, with 'anonymous'.",
"capability": "PASS. On /apps/setup/metadata/capability?scope=environment, the list has 11 rows, no Active column header and 0 switches. The page sent no ledger read and no door request (38 API requests). hasCatalogActivation('capability') is false, and writeCatalogActivation refuses the type before any request (unit-pinned). The door's answer, probed with curl: POST /api/v1/security/_activation/capability/manage_metadata answered 400 VALIDATION_ERROR, 'Path must be /security/_activation/:type/:name, with :type one of position, permission'.",
"refusal_reaches_the_user": "PASS, and the source of finding F5. A permission set saved through the metadata door (PUT /api/v1/meta/permission/c9_r2_env_set with name, label and objects {} answered 200, projectionApplied success, state active, and the item is served with no _packageId) is listed with a live switch. Clicking it sent POST /api/v1/security/_activation/permission/c9_r2_env_set with {"enabled":false}, which answered 503 {"success":false,"error":{"code":"VALIDATION_FAILED","message":"Package is required","httpStatus":503}}. The page showed the banner 'The switch was not saved: Package is required', and the switch stayed on, including after a reload. Screenshot: the list with that banner above the table.",
"org_admin_view": "PASS at bc7a86b. The auditor persona (Ada) was promoted to organization admin through POST /api/v1/auth/organization/update-member-role (200; the data door refuses sys_member updates with 405). She then holds manage_org_users, setup.access and setup.write, without manage_metadata. On the permission-set list, her ledger read GET /api/v1/data/sys_metadata_activation?top=1000 answered 200. Her switches show the ledger state, disabled, labelled 'Active', under the read-only reason 'Read-only for your account. Permission Set items are defined by the platform administrator (the manage_metadata capability); your organization assigns them.' At 4c62713 the same disabled switches still read 'Active — click to deactivate', which is what bc7a86b fixes. Her door call, via curl, answered 403 PERMISSION_DENIED, 'Enabling or disabling a permission set requires the manage_metadata capability — switching a shipped artifact off is functionally equivalent to deleting it for as long as it stays off, and the switch is not scoped to the caller's organization.' The ledger was unchanged. Her role was restored to member (200), and sys_member reads owner, member, member.",
"c9": "PASS: a registry-only position is assignable. At boot the registry and the rows agree: GET /api/v1/meta/position has 16 items, /data/sys_position has 16 rows, and both set differences are empty, so no registry-only position existed. I created one through the metadata door: PUT /api/v1/meta/position/c9_r2_registry_only answered 200, state active. The registry then served 17 positions, against 16 sys_position rows with none for it. On /apps/setup/metadata/position/c9_r2_registry_only?scope=environment, Position holders read 0. I chose Add holder, ticked Mei Phone (demo) and confirmed. The page sent POST /api/v1/data/sys_user_position with {"user_id":"usr_showcase_phone_demo","position":"c9_r2_registry_only"}, which answered 201 with record zEH-ZJxeu-wHrkfI. Round 0 measured this as 400 VALIDATION_FAILED. The section, after a reload, read 'Position holders 1: Mei Phone (demo), phone.demo@example.com'. Mei's /auth/me/permissions positions became [org_member, c9_r2_registry_only, everyone]. Control (the refusal still fires): POST /api/v1/data/sys_user_position naming no_such_position_c9 answered 400 VALIDATION_FAILED, field position, code reference_not_found, 'Position: no position is named 'no_such_position_c9'. sys_user_position.position takes the machine name of a position in the security catalog ...'. The same position's switch on the position list read live (aria-checked true, enabled), where round 0 showed a dash. The door refuses it: POST /api/v1/security/_activation/position/c9_r2_registry_only answered 503 'Package is required' in both directions (F5).",
"restored": "Mei's holder was removed through the section (DELETE /api/v1/data/sys_user_position/zEH-ZJxeu-wHrkfI, 200). Both test items were deleted through the metadata door: c9_r2_registry_only (200, 'it no longer exists'), and c9_r2_env_set together with its projected row (rows 17 = registry 17, as at boot). Mei's grant was deleted (DELETE /api/v1/data/sys_user_permission_set/b1XV9CrMeytpCZwP, 200). Her /auth/me/permissions is then equal to her pre-measurement answer (sorted-JSON compare). Ada's role is back to member. One row is not deleted: the ledger row qrOjyAFJHm8-a-bU, which reads active true. The ledger's own contract treats that the same as no row. The door writes rather than deletes, and the data door offers only get and list on that object. The --fresh database was removed when the server stopped (/tmp/objectstack-dev-hsFKty is gone).",
"walled_read": "NOT MEASURED, unchanged. No example on 490cb6d9 declares @objectstack/organizations (see premise_evidence), so a walled boot fails fast (ADR-0093 D5). The walled reason text stays pinned by its unit test only. The walled operator-only rule on the door is server-side and was not exercised.",
"last_admin_403": "NOT MEASURED live. The guard permits a switch-off of admin_full_access while an organization administrator still stands (last-admin-guard.ts, the ledger standing keys). On this showcase the admin is also the organization owner, so the call could answer 200 and strip manage_metadata from the only session that could switch it back on. The surfacing of a 403 refusal is pinned by unit tests with a fixture sentence (catalog-activation.test.ts, and the list page's refusal pin). Live 403s were read on the org-admin door call above."
},
"anchor_choice": "Disabled with the reason, not the door's 400 alone. The task requires that the switch must not look switchable there, and only a disabled switch meets that before a click. A switch that announces 'click to deactivate' and then refuses is the shape F3 named. On the rule against duplicate development: (1) the anchor list is not a client copy. It is the spec's AUDIENCE_ANCHOR_POSITIONS, the constant the door itself imports. It is imported lazily, the way clientValidation.ts imports the same module, because the list page is in the console's eager closure and that module (about 55 KB gzipped before minification) is not. (2) The cell already had a 'not switchable here, and why' state: the row-less dash. The ledger move made that state dead, and the anchor takes its slot. Its i18n key replaces the dead engine.catalog.active.noRow in en and zh, so on net no cell state and no key were added. (3) The door's 400 still reaches the user verbatim through the generic refusal path, if a later spec adds an anchor that objectui's pin does not list.",
"f3": "F3 closes for every packaged item on a stage-2c framework. The switch's offer is now true, as measured by off and on. For a caller who cannot author it closes at bc7a86b, where the disabled switch reads Active or Inactive and offers no click. It does NOT close for an item saved through the metadata door. There the switch offers a deactivation that the door refuses with 503 'Package is required'. The refusal is shown, so this is not a silent no-op, but it is still an offer the server refuses. That remainder rides objectstack finding F5. A client-side disable for package-less items would encode a server defect as a client rule; it would be a transition piece that objectstack#22601 B forbids.",
"corrected_claims": [
"C12. PR body, section 'The active switch is pending the server half' ('They do not.' ... 'it writes the catalog row's active column through the data door'): FALSE at bc7a86b. Stage 2c's door is on main. The switch reads sys_metadata_activation through the data door and writes POST /api/v1/security/_activation/:type/:name; see measurements.off and measurements.on.",
"C13. PR body, server gate 1 ('an activation door that accepts metadata_type permission and position, behind the same refuseUngrantedActivationWrite gate'): MET. Read from source: handleCatalogActivationWrite calls refuseUngrantedActivationAuthoring, then refuseUngrantedActivationWrite. Measured: an org admin gets 403 with the server's sentence. What remains is F5 (package-less items).",
"C14. PR body, 'catalog-activation.ts is the only module on these pages that reads or writes a catalog row': it now reads and writes NO catalog row. The one catalog-row read left on these pages is AssignedUsersSection's resolveGrantRowId on add (gate 3, C8), which is unrelated to the switch.",
"C15. PR body, 'An item with no catalog row shows a dash and the reason. A position authored through the metadata door is one such item.' and, in the browser section, 'On a registry-only position, the active cell shows a dash and the reason': FALSE. Every listed item has a ledger-backed switch (measured on c9_r2_registry_only and c9_r2_env_set), but the door refuses such items (F5).",
"C16. PR body, measured table row 'Assignment by name to a registry-only position: 400 VALIDATION_FAILED ... looks the name up in sys_position rows', gate 4 including 'a position created in Setup can be assigned only after a restart', and round-1 C9: FALSE since stage 2a. Measured 201 (measurements.c9); gate 4 is met. The PositionHoldersSection docblock was corrected in 4c62713. No UI gate was built on the old refusal: the section is mounted for every position in the scope and only shows the server's words.",
"C17. PR body, browser section 'Deactivate and reactivate: the switch wrote the row to active: false, then back to true': superseded. The switch writes the ledger row, and the catalog row's active stays as it is (measured true after the switch-off).",
"C18. PR body, 'Requests the catalog made: ... the row-state seam's /data/sys_permission_set and /data/sys_position with select=id,name,active': superseded. The list now reads GET /api/v1/data/sys_metadata_activation?top=1000 and posts to the door. Measured: 79 requests during each of off and on, none naming either catalog row object.",
"C19. PR body, 'Organization administrator: ... its switches are disabled': holds, re-measured at bc7a86b, and the disabled switch now reads Active or Inactive. The PR body's table row 'Organization admin PATCH sys_permission_set active: 403' describes a path the console no longer takes. The org admin's door call answers 403 PERMISSION_DENIED.",
"C20. Changeset 'The active switch' bullet (round 1's C3 wording), the app-shell README paragraph, catalog-scope.ts's SETUP_CATALOG_TYPES comment, catalog-activation.ts's module doc and its test header all said the switch writes the row flag, or that the flag decides nothing. All are rewritten in 4c62713. The dead i18n key engine.catalog.active.noRow (en, zh) is gone.",
"C21. PR body, measured table rows re-read on 490cb6d9: GET /meta/permission has 17 items against 17 rows (holds); GET /meta/position has 16 items against 16 rows (holds). For 'Platform admin PUT /meta/position/NAME: 200, no row is created' (holds), the PUT answered 200 state active, with no sys_position row. For 'Platform admin PUT /meta/permission/NAME: 200, projected to a row' (holds), the PUT needed objects {} this time (422 INVALID_METADATA without it), and the projected row appeared. The organization admin's GET and PUT rows were not re-measured.",
"C22. Round-1 C8 (the grant door still requires permission_set_id): holds by source at 490cb6d9. sys-user-permission-set.object.ts has permission_set_id required: true, last touched by #22364 before stage 1. The setup grant with both keys answered 201. A name-only POST was not re-sent.",
"C23. PR body Clause-2 line ('widens by one export: ENVIRONMENT_SCOPE_QUERY'): holds. SETUP_CATALOG_TYPES changed shape (to a readonly tuple), but it is module-internal; no package entry reaches it. The changeset stays minor.",
"C24. Size: was 1,956 lines in 26 files against 5330afd (round 1); now 2,084 lines in 26 files against 206505c.",
"C25. Round-1 C11 (.changeset/11806-login-server-unreachable.md): still true. check:changeset-claims names it again because the README was edited. This round's README hunk is inside 'The Setup catalog' section; the sign-up table and the useSignUpOffer recipe it cites are untouched."
],
"tests": "All at bc7a86b (git rev-parse --short HEAD printed by the run). The tests are narrowed to the union of three sets, 248 files in all. (a) 16 test files that import a changed module by name (catalog-activation, catalog-scope, ResourceListPage, PositionHoldersSection, the write census). (b) 91 app-shell tests importing metadata-admin's i18n. (c) 148 test files that read the tree from disk (readdirSync, globSync, fast-glob or git ls-files). They ran as three os-verify-lock batches of pnpm exec vitest run --maxWorkers=2 FILES: 104 files with 1,091 tests passed (5m27s); 72 files with 1,321 passed (1m30s); 72 files with 2,753 passed (1m45s); each VERDICT command-exit 0. The three 'FAILURE here instead' lines in the last log are a gate's fixture text printed by a passing test; that log has 0 failed-test markers. The full suites of app-shell, plugin-form, fields and console are left to CI. Type-check after building @object-ui/app-shell's dependency closure: @object-ui/app-shell (tsc --noEmit and tsconfig.test.json) exit 0, 0 'error TS'; @object-ui/plugin-form exit 0, 0 'error TS'. --listFiles confirms that the test tsconfigs include catalog-activation.test.ts, ResourceListPage.environmentScope-7611.test.tsx and the census. Lint, narrowed: pnpm exec eslint --format json over the PR's 23 changed ts and tsx files gives 23 files, 0 errors and 171 warnings (counts from the JSON). The proof for the narrowing: the population is the root eslint.config.js, which every package's eslint . reads; it sets no parserOptions.project or projectService, so linting is not type-aware. Custom eslint-rules that read other files were not audited, as in round 1. Repo-wide pnpm lint is left to CI. Ablations, committed first, run through objectstack's scripts/ablation-replace.mjs in WRAP mode with the restore proven (blob equals HEAD, git diff HEAD empty). (1) Removing 'anchor ||' from the switch's disabled prop (anchor 1 to 0, blob 884831e3d903 to 90809f6f67be) turned exactly the anchor pin red: 1 failed, 10 passed. (2) Changing the ledger's no-row default from '?? true' to '?? false' (blob 3a87a72ba91a to 6c31d5ed07cd) turned 3 pins red across both files: 3 failed, 15 passed. The subjects resolve from source (same-package relative imports), so no dist rebuild applies.",
"checks": {
"vitest_union_248_files": "exit 0 in each of 3 batches (1,091 + 1,321 + 2,753 passed)",
"type_check_app_shell": "exit 0 (0 error TS)",
"type_check_plugin_form": "exit 0 (0 error TS)",
"eslint_23_changed_files": "exit 0 (0 errors, 171 warnings)",
"check_new_line_citations": "exit 0 (VERDICT: 0 new citations)",
"check_control_bytes": "exit 0",
"check_i18n_keys": "exit 0",
"check_i18n_dead_keys": "exit 0",
"check_i18n_drift": "exit 0",
"check_test_path_roots": "exit 0",
"check_vi_mock_specifiers": "exit 0",
"check_vi_mock_inherit": "exit 0",
"check_vi_mock_override_shape": "exit 0",
"check_unreferenced_sources": "exit 0",
"check_doc_fences": "exit 0",
"check_changeset_claims": "exit 0 (names the 11806 changeset; answered in C25)",
"check_pending_changeset_literals": "exit 0",
"check_phantom_deps": "exit 0",
"check_metadata_write_doors": "exit 0",
"check_side_effects_array": "exit 0",
"check_changeset_presence": "exit 0 (23 source files of 4 released packages, 1 changeset)",
"check_changeset_no_major": "exit 0",
"check_changeset_overwrite": "exit 0",
"check_changeset_fixed": "exit 0",
"own_control_byte_scan_of_round_2_files": "grep exit 1 (no match)",
"eager_closure_budget": "NOT MEASURED locally (it needs a console production build); left to CI. The anchors module is imported lazily so it stays out of the eager closure. The other new imports (packagedFlows.ts, packagedActions.ts, apiBase.ts, @object-ui/core) are modules Setup's eager packaged-automation page already pulls in.",
"ci": "in_progress on bc7a86b; not awaited"
},
"mcp_calls": "0. No MCP GitHub tool was called.",
"api_writes": "1 REST write: POST /repos//issues/12089/comments (this addendum, through post-stamped and the fleet-write relay). There were 2 git pushes of claude/issue-7611-setup-catalog-registry (4472538 to 4c62713, then 4c62713 to bc7a86b); a push is not a REST write. No label, assignee or body writes. Reads were REST GETs and git fetches.",
"open_questions": [
{
"question": "F5: the door refuses every item saved through the metadata door (package-less), with 503 'Package is required'. Should this PR land with that refusal shown on those items' switches, or should the console hide or disable the switch for package-less items until objectstack fixes the door?",
"options": [
"A. Land as is. The switch shows the server's refusal for package-less items until the objectstack fix lands. Packaged items, which are every item a fresh deployment lists, switch correctly.",
"B. Disable the switch client-side for an item with no _packageId, with a reason, until the door accepts package-less items."
],
"recommendation": "A. The door's contract (its docblock, authorization.mdx, ADR-0131 D3) covers every declared catalog item, an environment-authored one included. The defect is the producer's: the ledger's required package_id, against the door's packageId ?? ''. B would be a client rule encoding a server defect and a transition piece for a state the cutover removes (objectstack#22601 B), and it would need its own removal later. The refusal is visible, not silent. If the seat wants the window closed before landing, the fix belongs in objectstack: a sequenced card ahead of this PR's landing, or an item on release-cut condition (c)."
}
],
"out_of_scope_findings": [
"F5 · class: a (the door's declared contract is broken for a declared item) · reach: POST /api/v1/security/_activation/permission/c9_r2_env_set (a set saved by PUT /api/v1/meta/permission, served with no _packageId) answers 503 {code VALIDATION_FAILED, message 'Package is required', httpStatus 503}; the same for position c9_r2_registry_only in both directions; and through the Setup UI, whose banner reads 'The switch was not saved: Package is required'. Measured on objectstack 490cb6d9. Expected: 200 and one ledger row, per the door's docblock and authorization.mdx, which name 404 only for a name with no definition. · Seam: spec:sys_metadata_activation.package_id (Field.text required: true) → runtime:handleCatalogActivationWrite (ObjectStoreMetadataActivationStore.setActive with packageId entry.packageId ?? ''). The door's catch also maps a validation refusal to 503 while keeping code VALIDATION_FAILED, so the ADR-0112 code and status disagree. · Fix lands in objectstack. · dedupe words: activation door Package is required; _activation 503 environment-authored permission set; sys_metadata_activation package_id required package-less",
"F3 · carrier: this PR (bc7a86b) and F5 · closes for packaged items and for non-authors; the remainder for package-less items rides F5 · noted, not filed",
"F4 · carrier: objectui#12082's remainder · this PR now adds 2 unmapped write sites to its census backlog, not 3: the switch's data-door update left the tree with this round, leaving PositionHoldersSection's add and remove, siblings of AssignedUsersSection's two · noted, not filed",
"F2 · carrier: objectui#7611 part 2 · unchanged (the matrix editor's 'Custom' badge) · noted, not filed"
],
"deviations": [
"objectui main moved to 29b9949 after this round's merge (2 commits, 51 files, zero overlap with this PR's files, the census untouched). It was not merged a second time; the merge queue rebuilds on current main. The size is measured against 206505c.",
"A bounded in-place fix beyond the dispatch's list, bc7a86b: a disabled switch for a non-author read 'Active — click to deactivate'. It is the same defect class as F3, a 3-line label change in this round's own cell that reuses the status filter's existing keys, and it is pinned. Measured as the org admin before and after.",
"The last-admin 403 was not exercised live (see measurements.last_admin_403); its surfacing is unit-pinned.",
"One ledger row created by the off/on measurement stays, reading active true, which the ledger's contract treats as no row. Neither the door nor the data door deletes ledger rows, and the --fresh database was removed at shutdown.",
"The refusal measurement reused the off phase's screenshot file names. Those three files were renamed shot-refusal-*, and the off phase's screenshots were overwritten. The off phase's request and response pairs and switch states are in its JSON record, and its after-reload screenshot was viewed and described before it was overwritten.",
"The two dev servers ran detached, outside the verify lock (a dev server is not a lock entry point). Both were stopped by their own process groups, and both ports were free afterwards.",
"objectstack's dispatch-gates is not objectui's, so the gate families were derived by hand from objectui's package.json check scripts and what this diff touches."
]
}


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: bc7a86bb6fce7a8e70f640591b3bbe6da2f458d6
Local-runs: none

Round 2 on PR #12089 (draft by dispatch, Part of #7611, head repo equals base repo, base main, merge base 206505c3): 26 files, +1,948 / −136 (2,084 changed lines, under the 3,000-line budget); no governed-surface path in the file list; 10 commits, two of them merges of main (6e5635d1, 48cc047c), mergeable: clean. This record supersedes 6097834214 (PASS on 98d46ebadb) for this head. Every judgment there carries over except where the delta since 98d46ebadb moves it, named below: ① item 9 (the switch, replaced by the ledger door), ① item 10 (the registry-only assignment, corrected), ① item 8's framing (now the only catalog-row read on these pages), the changeset body under ②, and the landing order under ③. Items 1 to 7 stand unchanged: neither merge of main touched their files, and round 1's own delta (6e5635d1..4472538c, which no record covered) changed census entries, comments, the README and the changeset only.

Check-runs on the head: 43; 40 success, 3 skipped (Test (coverage), its shard matrix, dependabot), 0 failures, 0 pending; the combined commit status is success. Lint, Type Check, Test, Test (dist pins), Test shards 1/8 through 8/8, Build & E2E, Build Docs, Bundle Analysis, Changeset Declaration, Changeset Bump Policy, Changeset Fixed Group Check, Changeset Overwrite Report, Changeset Claim Re-read, README Export Check, Doc Snippet Type Check, Line Citation Gate, Spec Main Shape Gate and Governed Surface Queue Guard are all success; the Console Performance Budget on the head reads PASS, 3,172.6 KB of a 3,204.6 KB eager closure.

Inputs: the card body and its 17 comments (rulings 6094171670, 6094498192, 6095024693; the claim 6095693499 and amendment 6095720536; the reports 6097735087, seat answers 6097763840, ACCEPT 6097840521; seat notes 6104671088 and 6106420299, the last read for its rulings only, the F5 answer and where the fix lands); the PR body, file list and its 9 comments (record 6097834214, dev reports 6104663917 and 6106225321); the net diff against main; the delta 4472538c60..bc7a86bb6f (it carries main's four commits a21ff9a9, 5f75cfad, 16c8810a, 206505c3 through the merge; the round's own change 48cc047c..bc7a86bb is 10 files, +389 / −271, plus the 2-file label fix) and round 1's own delta; on objectstack main: packages/runtime/src/domains/catalog-activation.ts (the door), packages/platform-objects/src/system/sys-metadata-activation.object.ts (the ledger), content/docs/permissions/authorization.mdx, packages/spec/src/identity/ (position.zod.ts declares AUDIENCE_ANCHOR_POSITIONS; index.ts re-exports it); objectui main's manifests (@objectstack/spec ^17.6.0 on app-shell and fields, ^17.0.0 on the root and console; the lockfile resolves @objectstack/spec@17.7.0) and the same spec file at the tag @objectstack/spec@17.7.0 to judge the export at the pin. The helpers the diff imports unchanged from objectui main were read as well: views/setup/packagedActions.ts, packagedFlows.ts, PackagedActionsSection.tsx, utils/apiBase.ts, core/src/actions/actionErrorDetail.ts.

① Derived judgments

Moved by the delta:

  1. The switch's write moved from a data-door PATCH of the catalog row to POST /api/v1/security/_activation/:type/:name with exactly { enabled } (writeCatalogActivation). Every path that used to write active is gone: writeCatalogActive and the adapter update it made are deleted, CatalogRowDoor and CatalogRowState with them; the net diff against main adds no line that writes active on sys_permission_set or sys_position; the census entry for that write site left with it (item 7). The Setup object pages' own Activate and Deactivate row actions remain server-side and reachable by those pages' own URLs until stage 8; this PR routes nothing to them (the console's three system/* routes land on the catalog). The types a path may carry are the door's closed pair: SETUP_CATALOG_TYPES = ['permission', 'position'] equals the door's CATALOG_ACTIVATION_SUBJECTS keys and the two ledger metadata_type values the resolver honours. Right.

  2. The shown state is the ledger's, read through GET /api/v1/data/sys_metadata_activation?top=1000, and nothing reads a catalog row's active. The client's reading (readCatalogActivationRows): rows of the type only (metadata_type === type, so a flow, action or position row with a colliding name is skipped), active === false or 0 is off, anything else on, no row is active (isCatalogItemActive's ?? true). The server's reading: authorization.mdx ("Absent is ACTIVE. Only a ledger row whose active reads false takes a grant away"), the ledger's docblock ("Absence of a row means the packaged default — active"), and the door's docblock (readDisabledCatalogNames reads the ledger "and never the catalog row's active column"). They match. No row-flag fallback (objectstack#22601 B): the module says so, the unit pin asserts that no sys_permission_set or sys_position URL is requested, and the dev's ablation of the ?? true default turned 3 pins red. The one catalog-row read left on these pages is AssignedUsersSection's resolveGrantRowId ($select: ['id'], on ADD only, because the grant door still requires permission_set_id; C22 holds by source), unrelated to the shown state. Right.

    • The read is the same read Setup › Packaged automation makes (PackagedActionsSection.tsx's fetchActivationLedger: /data/sys_metadata_activation?top=LEDGER_PAGE_SIZE, credentials: 'include'), and readDataRecords / ledgerPageTruncated are imported from packagedActions.ts, not copied. A refused read or an explicit hasMore: true rejects; the cell then shows ? ("Activation state could not be read") and the status filter filters nothing — never a guessed "active". The whole ledger is read and filtered client-side, since flows and actions share the table; past 1,000 rows every switch reads ?. Honest and precedent-identical; noted, not a fault.
    • Who can make that read: the ledger declares enable.apiMethods: ['get', 'list'] ("Reads stay open so operability surfaces can answer"); the dev measured 200 for the platform administrator and for an organization administrator holding setup.access, setup.write and manage_org_users without manage_metadata, and 403 for a plain member. The object-level grant that admits the organization administrator lives in plugin-security's default permission sets, outside this record's inputs, so that half rests on the measurement; the client's handling of either answer is right (a disabled switch stating the state for a non-author; ? on a refusal).
  3. The anchors' disabled switch reads the spec constant, not a copy. audienceAnchorsOf('position') lazy-imports @objectstack/spec/identity and returns AUDIENCE_ANCHOR_POSITIONS (everyone, guest), the constant the door imports from the same subpath; for permission it returns an empty set without importing, matching the door's type === 'position' guard. An anchor renders as a disabled switch whose accessible name and wrapper title carry the reason (engine.catalog.active.anchor, en and zh); the pin's forced click sends nothing. Right. The lazy import is sound against the pin: at the tag @objectstack/spec@17.7.0, packages/spec/src/identity/position.zod.ts (blob 989e07cae4) declares AUDIENCE_ANCHOR_POSITIONS and identity/index.ts re-exports position.zod; objectui's lockfile resolves 17.7.0; two test files on this head import it statically (catalog-activation.test.ts and the list pin), and Type Check and the eight test shards are success against the installed 17.7.0. The dynamic import() keeps the identity module out of the console's eager closure (the budget stays PASS). If a later spec adds an anchor the pinned spec does not list, the door's 400 still reaches the user through the generic refusal path (item 5).

  4. capability: no switch and no door call. hasCatalogActivation('capability') is false; readCatalogActivation answers an empty map with no request; writeCatalogActivation rejects before any request (both pinned); the list draws no Active column for the type. Matches the door, which refuses the type with 400 (ADR-0126 §3, code-only). Right.

  5. Every door refusal reaches the user in the server's words. writeCatalogActivation throws actionErrorDetail(json, 'HTTP status') on a non-2xx or a success: false envelope; actionErrorDetail reads error as a string, then error.message, then message, else the fallback — the ADR-0112 envelope deps.error() sends for 400 (shape, anchor, body), 403 (both authority gates, and the last-admin hook, which carries its own status and code), 404 (no definition), 501 (no ledger in this composition) and 503 (no engine, no catalog reader, a reader failure, a store failure). The page renders it as engine.catalog.active.failed ("The switch was not saved: {message}") and leaves the switch as it was; pinned for a 403 with a fixture sentence in both files; read live for the organization administrator's 403, the anchor's 400 (curl) and F5's 503. The two 400 cases are unreachable from the UI by construction (items 3 and 4) and would still render. Right.

  6. PositionHoldersSection: a registry-only position is assignable since objectstack stage 2a. The round's change is the docblock alone: the code never gated on the old refusal — it mounts for every position in the scope, writes { user_id, position: name }, and shows refusalText(err) (body.error, error or message). Measured: 201 on a position authored through the metadata door, 400 reference_not_found on a dangling name. This moves 6097834214's item 10 from "measured: refused by name today" to "assignable; a name the catalog does not resolve is refused"; the judgment itself (a plain data page over sys_user_position, by name, the server's words) stands. Right.

  7. The census entry removed with the write site. affordanceGrantMap-12082.test.tsx loses catalog-activation.ts :: writeCatalogActive :: update, entered as unmapped in round 1 (47a3631), because the write it mapped left the tree; the switch now writes through a fetch POST to the security door, outside the DataSource write population that census counts. The two PositionHoldersSection entries stay; the net diff for the file is +4 / −0. Right (F4 is 2 entries, not 3, in objectui#12082's backlog).

  8. The in-place deviation bc7a86b. In CatalogActiveCell, a switch the caller cannot use (canSwitch false) now carries engine.catalog.status.active / .inactive ("Active" / "Inactive") as its aria-label and wrapper title, instead of "Active — click to deactivate"; disabled is unchanged. Three lines in this round's own cell, reusing the status filter's keys, pinned ("is disabled for a caller who cannot author, and states the state without offering a click"), measured as the organization administrator before and after. In scope of the page this PR builds; no accept set or public surface moves. Right.

  9. The rest of the cell. The title moved from the Switch to a wrapping span (a disabled control surfaces no tooltip); the row-less dash state is dead with the ledger move (an item with no ledger row is a live switch), so engine.catalog.active.noRow leaves en and zh and engine.catalog.active.anchor takes its slot — net zero keys and cell states; check:i18n-dead-keys is inside the green Lint. The ledger effect aborts on unmount through an AbortController, and a refused read after unmount sets nothing. Right.

Carried over from 6097834214, verified against the net diff at this head: item 1 (ENVIRONMENT_SCOPE_QUERY is the one new export; the index.ts hunk is that single line; SETUP_CATALOG_TYPES's change to a readonly tuple and the renamed exports of catalog-activation.ts reach no package entry), item 2 (?scope=environment on list, editor and create, carried by every emitted link and the breadcrumb), item 3 (the caller tier in both editors' write affordance), item 4 (canCreate and the posture-aware reason), item 5 (the managedBy "Source" column dropped), item 6 (RecipientPickerField lists the position registry), item 7 (the three console routes), item 8 (AssignedUsersSection by name; via-position holders from permissionSets, measured non-empty since stage 1).

② Semver level

  • Published source lands in three released packages: apps/console, packages/app-shell, packages/fields; a test file in a fourth (packages/plugin-form/src/affordanceGrantMap-12082.test.tsx, which check-changeset-presence counts and which ships nothing new). The changeset .changeset/7611-setup-catalog-registry.md names the three at minor. Matches.
  • The changeset's switch bullet was rewritten this round and reads true at this head: the ledger's state, no row is active, the door POST /api/v1/security/_activation/:type/:name with { enabled }, no catalog row read or written, refusals in the server's words, the anchors disabled, "the door is objectstack's ADR-0131 stage 2c". On a framework without that door (any objectstack before stage 2c; cloud's v17 line, which the standing .objectui-sha hold keeps off this console) a click fails loudly with that server's own refusal and writes nothing; the bullet names the dependency rather than that failure mode — adequate, noted. The holders bullet still names the pre-stage-1 case and the permission_set_id carry-over, both true (C22).
  • Clause-②: yes, no arm, at the start of line 3 of the PR body. Well-formed; yes takes at least minor: right. The widening is still the one export plus the URL parameter; nothing narrows (the deleted catalog-activation.ts exports were never on the package entry). No breaking changeset, migration text or ADR-0087 marker is owed; no major (objectui's fixed group never declares one); skip-changeset correctly absent.
  • Gates on the head: Changeset Declaration, Bump Policy, Fixed Group Check, Overwrite Report and Claim Re-read all success. The advisory claim re-read names .changeset/11806-login-server-unreachable.md because it cites packages/app-shell/README.md; this round's README hunk is inside the "The Setup catalog" section, and the sign-up table and the useSignUpOffer recipe that claim cites are untouched (C25). Answered, no correction.

③ Boundary flags

open_questions (F5) — the door refuses an environment-authored item with 503 VALIDATION_FAILED "Package is required". The seat's answer (6106420299): A, land as is; the fix is objectstack #15204 stage 2d, in the seat's lane; release-cut condition (a) of objectstack 6102862135 is extended to 2d. Taken as the ruling. My reading of the two server files agrees on the mechanism: handleCatalogActivationWrite writes packageId: entry.packageId ?? '' into sys_metadata_activation.package_id, declared Field.text({ required: true }); the door's catch keeps a thrown code but defaults the status to 503, so a validation refusal leaves as 503 with VALIDATION_FAILED (ADR-0112 code and status disagree). The door's contract (its docblock; authorization.mdx: "The name must resolve in the security catalog, or the route answers 404") covers every declared item, so the defect is the producer's. The client half is right: the refusal is shown in the server's sentence, nothing is written, and no client rule encodes the defect (option B would be a transition piece objectstack#22601 B forbids). Consequence for the card, escalated with its carrier: the #22621 → A parity leg "the platform administrator deactivates and reactivates an environment-authored set from the Setup page" is not met on objectstack main at this head; it is met for every packaged item and rides stage 2d. This record relies on condition (a) holding the release until 2d lands.

Dev flags, round 2 (6106225321)

  • main moved to 29b9949 after the merge (2 commits, 51 files, none of this PR's 26, the census untouched); not re-merged. Answered: the queue rebuilds on current main, and the head is mergeable: clean.
  • The in-place label fix bc7a86b: answered, ① item 8.
  • NOT MEASURED, the last-admin 403 live (on that showcase it could strip the only administrator's manage_metadata). Accepted: the guard is the server's (ADR-0135 D5.2), its surfacing is pinned with a fixture sentence of the same envelope shape, and a live 403 of that shape was read for the organization administrator.
  • NOT MEASURED, a walled posture (no example declares @objectstack/organizations; a walled boot fails fast, ADR-0093 D5). Accepted as in 6097834214 and 6104671088: the door's operator-only rule under a wall is server-side, and the reason text stays unit-pinned. Escalated once more: the first walled fixture that exists owes one read of this page.
  • One ledger row left reading active: true. Answered: the ledger's contract treats it as no row; the data door offers no delete on the object; the --fresh database was removed.
  • Process deviations (dev servers detached outside the verify lock; the off-phase screenshots overwritten; gate families derived by hand): no contract effect, the head's check-runs answer the gates. To the epic PM's round report.
  • Corrected claims C12 to C25: each consistent with my reading of the head, in particular C14 (the switch reads and writes no catalog row; resolveGrantRowId is the one row read left), C16 (gate 4 met since 2a) and C23 (Clause-② holds).
  • The PR body is stale at this head. Its "pending the server half" section, server gate 1, the measured-table rows on the switch and on the registry-only assignment, and the browser section's switch and dash sentences are superseded by C12 to C19 under the seat's one-write rule; this record reads the body with those corrections. Escalated to the epic PM: a squash-merge message is drafted from the body, so before the landing either correct those sentences in the body (one write, read back, the footer kept) or trim them from the squash message, so the landed commit does not describe a switch that writes a catalog row.

Dev flags, round 1 (6104663917), not covered by a record until now

  • The merge 6e5635d1 (no text conflict; i18n.ts auto-merged) and the semantic conflict with objectui#12082's write census: 3 unmapped entries added in 47a3631, 2 after this round. Answered (① item 7; F4 is objectui#12082's backlog).
  • C1 to C11: the switch-pending prose corrected in the changeset, the README and comments in 4472538c, all rewritten again this round. Answered.
  • F1 (the "does not match the spec" banner on positions that declare permissionSets, from objectui's pinned spec 17.7.0): the seat's answer A (6104671088), carrier objectui's spec-18 pin bump, release-cut condition (c). Not this PR's defect; escalated as placed. This PR's anchor import needs nothing from that bump (① item 3).
  • F2 (the matrix editor's "Custom" badge) stays with part 2; F3 closes here for packaged items and for non-authors, its remainder rides F5. Answered as placed.
  • The ledger-side control not run in round 1: superseded by round 2's live door measurement (off takes the set from its holder and from /auth/me/permissions, on returns it).
  • check-changeset-presence counting the census test as a fourth package: answered in ②.

Carried from 6097834214 ③, state at this head: its deviations 1 to 9 stand as answered there (the draft-then-live create note still goes to the maintainer through the round report). Of its landing order, stage 1 (bfc15d27), stage 2a (2ff0825d) and stage 2c (cd3d3911) are on objectstack main. What this record relies on: the PR lands before the table-retiring stage 8, together with objectstack's .objectui-sha bump (stage 5); the objectui part 2 (the capability select, the binding-editor widget, the approver and decision-output readers after the spec card, the clone door call, and resolveGrantRowId off the row) lands before stage 8; cloud's .objectui-sha stays before this PR's merge commit until cloud is on v18; objectui#7205 stays open on objectstack#15206. The round-0 findings (6097735087), placed by 6097763840: the capability registry serving 2 of 11 is closed by objectstack#22669 (C4: 11 of 11); position-catalog-refusal is closed by stage 2a; the activation door is closed by stage 2c with its F5 remainder on 2d; grant by name alone stays stage 8 / C7b (C22 holds); spec bindings, nav entries, object-page special cases and carryOver stay stage 8; the cloud hold goes to the maintainer through the round report.

Implemented-by: claude/issue-7611-setup-catalog-registry
Reviewed-by: session_01Rerax7QTjKMPCUZxQUtPFR

VERDICT: PASS


Generated by Claude Code

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants