Skip to content

feat(types): NamedListView mirrors spec 18's list-view requiredPermissions — no client gate - #12360

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-12341-list-view-required-permissions
Oct 14, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-12341-list-view-required-permissions

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #12341
Clause-②: yes

The objectui half of objectstack#22611 ruling A: @object-ui/types mirrors the list view's audience key requiredPermissions from @objectstack/spec 18. ⛔ No client-side gate is added. The server enforces the key (objectstack PR #22731) and the console surfaces its refusal (objectui#12112).

What changed

  • NamedListView declares requiredPermissions (packages/types/src/objectql.ts), typed by reference as ListViewSchema['requiredPermissions']. That member is the spec slot, taken by specFieldsExcept in the zod mirror, so the type is string[] | undefined, the same as the spec's ListView['requiredPermissions'].
  • Where the mirror was missing (measured on origin/main 065a1cf): three faces of this package carry a list view. Two of them already had the key by reference. The list-view node (ListViewSchema) takes every spec key it does not override, and list-view-spec-parity.test.ts asserts that. ObjectViewSchema.listViews has been the spec's ObjectListViewSchema record since objectui#7928. Only the hand-written NamedListView lacked the key. objectui#12030 had pinned it there as PENDING_SERVER_ENFORCED_DECLARATION.
  • That pin is flipped, not duplicated (object-view-unmirrored-keys-7779.test.ts). The "narrower than the protocol" set is now []. A declared member that no renderer reads would otherwise land in bucket ①, whose emptiness means "implementation owed". A server-enforced gate owes the renderer nothing, so the census gets a fifth bucket, BUCKET_SERVER_ENFORCED_UNREAD = ['requiredPermissions']. The partition is now 0 + 8 + 11 + 2 + 1 = 22 unread. Declared members go from 64 to 65, read members stay at 43, and the loose-regex count goes from 73 to 74 (the gap stays at 9). Bucket ① asserts that it holds exactly the server-enforced bucket and nothing else.
  • New pins (list-view-required-permissions-12341.test.ts), accept and refuse on every face. Type-level: the member on all three faces equals the spec's string[] | undefined. On NamedListView, a bare string and a number list are each a @ts-expect-error. Runtime: the list-view mirror accepts and keeps a capability list, and refuses a string, a number list, an object and a boolean at the key, after a base-only parse control. ObjectViewSchema.listViews accepts the key and refuses a bare string at listViews.legal_desk.requiredPermissions.
  • Changeset .changeset/12341-list-view-required-permissions.md: @object-ui/types minor. The published accept set widens.

Widening, measured on the built declaration closure

pnpm --filter @object-ui/types build, then read packages/types/dist/objectql.d.ts: export interface NamedListView gains exactly one member, requiredPermissions?: ListViewSchema['requiredPermissions']; (dist line 291 of the interface block). A probe compiled against dist/index with tsc --strict (exit 0) checks three things: the member equals string[] | undefined, { label, requiredPermissions: ['a.b'] } is accepted, and requiredPermissions: 'a.b' is refused. Nothing else in the published surface moved. ListViewSchema and ObjectViewSchema['listViews'] already carried the key.

Reverse verification (a one-time proof, no permanent file)

The fix was committed first (4f01c04). Then ablation-replace.mjs --delete removed the declaration line (anchor 1 to 0, blob 76eef18c1220 to 0270a098714c), with this result:

  • tsc -p tsconfig.test.json went red with 3 errors in the new test file: Property 'requiredPermissions' does not exist on type 'NamedListView'.
  • vitest on the two files went red, 7 failed / 230 passed. The failures were the census count, the declared-set equality, the instrument agreement, the partition, the per-member UNREAD row, the narrower-than-protocol zero and the bucket ① reading.
  • Restore: blob back to 76eef18c1220 (equal to HEAD), and git diff HEAD is empty.
  • The 12341 runtime tests stayed green under the ablation, as expected: the zod faces already carried the key.

The one measurement the card asks for: the D4 viewer

Answer: yes, the console offers such a view. No, opening it does not answer 403, so objectui#12112's surface is never reached.

  • Server: /meta/object serves an object WHOLE to a caller ADR-0106 D4 exempts. See objectstack packages/rest/src/meta-item-read-gate.ts:1657 on origin/main b5b1bf72: if (!sysPerms || isObjectSchemaMaskExempt(ctx)) return serve(document);. Its stated reason is that a pruned load saved back would delete the withheld list views. Every other caller gets filterObjectListViewsForUser (lines 768-772).
  • Console listing: packages/app-shell/src/views/ObjectView.tsx:2335-2336 builds the switcher tabs from objectDef.listViews (buildViewTabs({ definedViews: objectDef.listViews … })). No audience check is applied there, and none should be.
  • Console opening: the by-name probe runs only when the requested view MISSED the tab list (ObjectView.tsx:2441-2455, missedViewName then useMetaItemRefusal('view', …)). A D4 viewer's gated view is in the list, so it resolves, renders and never asks. The refusal face (ObjectView.tsx:3844-3848, view-access-denied) is not reached. In any case the server has no by-name /meta/view item for an object's own list view that it could refuse.
  • Already pinned: packages/app-shell/src/views/ObjectView.viewRefusal-12112.test.tsx:221-230, the case "D4 (measured): an object's own list view served to its editor is listed and opens — no client rule hides or refuses it". In that case the probe is not called and no view-access-denied face appears.
  • Reading: this is not a data leak. The records come from the caller's own object read permissions, and a D4 caller may edit the schema. It is an audience mismatch: an object's editor is offered, at runtime, views authored for another audience. Any fix belongs on the SERVER, and ⛔ not in a client gate. A proposed shape is for the plain runtime read to prune for D4 callers too, and to keep the whole document for the authoring doors (draft, layered or diff reads), which is the split the app: 'author-exempt' policy already makes. That is an objectstack decision, raised in the report's open_questions. Nothing in this PR touches it.

Gates (worktree HEAD 9e6cb65)

  • pnpm --filter @object-ui/types build: exit 0. pnpm --filter @object-ui/types type-check (tsc + examples + tsconfig.test.json; --listFiles counts both touched test files in): exit 0.
  • pnpm exec vitest run packages/types/: 378 files / 10262 tests passed.
  • pnpm turbo run build --concurrency=2 --filter='@object-ui/plugin-view^...' (15/15) and then pnpm --filter @object-ui/plugin-view type-check: exit 0. plugin-view is the one package whose tests import NamedListView as a type. Other mentions (app-shell, data-objectstack, console) are in comments only.
  • pnpm exec eslint --no-inline-config on the 3 touched .ts files: 0 errors. There are 27 warnings, all pre-existing no-explicit-any lines outside the diff. The config is root eslint.config.js and is not type-aware (no parserOptions.project or projectService), so the diff cannot move any verdict on an untouched file. The repo-wide pnpm lint is left to CI.
  • check:phantom-deps, self-import, unreferenced-sources, doc-example-readers, handler-key-reads, side-effects-array, element-data-source-declaration, spec-symbols, action-forward-parity, designer-field-key-parity, icon-record-names, i18n-keys, i18n-drift, i18n-designer-parity: all exit 0. check-changeset-fixed.mjs and check-changeset-presence.mjs: exit 0.
  • check:spec-floors: exit 1, the known failure on main ("The scan collapsed: 0 package(s) declare @objectstack/spec …"). objectui#12334 carries it, and it is not touched here. check:doc-types was not run because no docs changed. objectui has no check:nul-bytes script, so a control-byte grep over the 4 touched files was run instead: 0 hits.

Acceptance notes

  • The NamedListView docblock still says its fate "follows objectui#7924", and this PR does not change that.
  • The spec's shared description still says the gate "is not enforced yet". Per triage, that sentence is the spec owner's to fix.
  • The census's failure message still points at .changeset/object-view-unmirrored-keys-7779.md, which no longer exists (it was consumed at release). Only its member count was corrected here.

The session for this work is https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB.


Generated by Claude Code

…ssions

The audience key `@objectstack/spec` 18 declares on the list-view shape
(objectstack#22611 ruling A) and the server enforces (objectstack PR
#22731). Typed by reference through ListViewSchema['requiredPermissions'].
No renderer reads it; no client-side gate is added.

The objectui#12030 PENDING pin in the objectui#7779 census is flipped: the
narrower-than-protocol set is empty again, and the member sits in a fifth,
server-enforced bucket rather than bucket one ("implementation owed").

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 72 chunks) 3054.5 KB 3119.4 KB
Main entry chunk (gzip) 73.8 KB 350 KB
Entry file index-C_YYR5yh.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.69KB 7.62KB
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.26KB 8.03KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (commentAuthorship.js) 1.39KB 0.78KB
collaboration (index.js) 1.93KB 0.85KB
collaboration (useCollaborationTranslation.js) 6.50KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 592.14KB 142.83KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 248.41KB 69.48KB
fields (index.js) 283.00KB 72.12KB
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) 55.05KB 16.09KB
plugin-charts (index.js) 84.72KB 23.27KB
plugin-chatbot (index.js) 201.72KB 48.07KB
plugin-dashboard (index.js) 143.63KB 38.79KB
plugin-designer (index.js) 233.77KB 50.02KB
plugin-detail (index.js) 259.78KB 68.65KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 178.88KB 46.47KB
plugin-gantt (index.js) 178.63KB 45.19KB
plugin-grid (index.js) 258.97KB 72.23KB
plugin-kanban (index.js) 53.23KB 16.71KB
plugin-list (index.js) 123.64KB 31.63KB
plugin-map (index.js) 27.24KB 9.03KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 46.66KB 13.13KB
plugin-timeline (index.js) 39.21KB 11.85KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 91.99KB 23.23KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 12.07KB 3.68KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.07KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.81KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 23.87KB 7.83KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 1.13KB 0.65KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 5.78KB 2.70KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (node-slots.js) 7.18KB 2.34KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-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: 9e6cb65c00f6dc8b7487df7f95699b2c2d88b6a2
Local-runs: none

Inputs read: card #12341 (body, triage 6162030734, claim 6163589922, os-dev-report 6164268813), PR #12360 (body, 4-file list, net diff against main), the 33 check-runs on this head and the failing one's annotations, and at this head packages/types/package.json, packages/types/src/index.ts, packages/types/src/objectql.ts.

① Derived judgments

Accept-set and public-surface changes the diff implies, each judged:

  1. NamedListView gains requiredPermissions?: ListViewSchema['requiredPermissions'] (packages/types/src/objectql.ts +3223) — right. NamedListView is exported from the . entry barrel (src/index.ts, the from './objectql.js' block), so this is a widening of the published accept set: an authored named view may now carry a string[] at the key; nothing previously accepted is refused. Typed by reference, not restated, which the card's "by reference to the spec where the mirror allows" asks for. The chain holds at this head: ListViewSchema = ListViewAuthored & ListViewRuntimeProps, ListViewAuthored is a mapped type over ListViewInferred (imported from ./zod/objectql.zod.js) minus the ListViewRuntimeProps keys, and ListViewRuntimeProps declares no requiredPermissions, so the member is the spec slot and nothing local. The new pin _NamedListViewMirrorsSpec asserts Equal<NamedListView['requiredPermissions'], SpecListView['requiredPermissions']> and _IsOptionalStringList asserts that is string[] | undefined; Type Check is green on this head, so the equality is a gate verdict, not a claim.
  2. The dev's claim that the other two list-view faces already carried the key by reference — verified. ListViewSchema (the list-view node): derived from the zod mirror as above, and the new pin _ListViewNodeMirrorsSpec plus the runtime case (ListViewSchema.safeParse accepts ['legal.review', 'setup.access'], refuses a string, a number list, an object and a boolean at the key after a base-only control) pass on this head. ObjectViewSchema.listViews: at this head it is Record<string, z.input<typeof SpecObjectListViewSchema>> (objectql.ts:2550, the objectui#7928 reference), and the pin _ObjectListViewEntryMirrorsSpec plus the runtime refusal at listViews.legal_desk.requiredPermissions pass. Neither face is touched by the diff, and the changeset says so. So the landing face is NamedListView only, as the dev states.
  3. No second client-side gate — verified none was added. The file list is objectql.ts (a type member and its docblock), two test files under packages/types/src/__tests__/, and a changeset. No renderer, app-shell, plugin-list, plugin-view or permissions file is in the diff, and the only non-test runtime change is a declaration. The census test moves the member into a new BUCKET_SERVER_ENFORCED_UNREAD, asserts it is read off no named view, and asserts the "narrower than the protocol" set is now [] while bucket ① holds exactly that bucket and nothing else. That is the right reading: the member is declared and deliberately unread, not "implementation owed".
  4. The flipped pin, not duplicated (object-view-unmirrored-keys-7779.test.ts): PENDING_SERVER_ENFORCED_DECLARATION becomes BUCKET_SERVER_ENFORCED_UNREAD with the same one member; counts move 64 to 65 declared, 21 to 22 unread, loose regex 73 to 74 (gap stays 9), partition 0 + 8 + 11 + 2 + 1. Each figure is asserted by a derived instrument in the same file, and the Test shards are green, so the figures are read, not remembered. Right.
  5. requiredPermissions?: string[] at objectql.ts:715 is the bulk-action def's action-level key (ADR-0066, objectui#3492), pre-existing and not a list-view face. Not a duplicate of this change.
  6. Published entry points (packages/types/package.json exports): unchanged, still ., ./base … ./zod, ./internal/retired-field-keys. Nothing is added to or removed from the export map; the surface moves only inside .'s NamedListView.

② Semver level

.changeset/12341-list-view-required-permissions.md: '@object-ui/types': minor. Matches the diff. The published interface NamedListView gains one optional member and the accept set widens; nothing is removed or narrowed, no entry point moves. Minor is the right level; patch would understate a widened accept set, major is not owed. The Changeset Declaration, Changeset Bump Policy, Changeset Fixed Group Check, Changeset Claim Re-read and Changeset Overwrite Report checks are all green on this head.

Clause-②: yes

The PR body and the claim both say Clause-②: yes, and the diff agrees: a published accept set widened, which is why this record exists.

③ Boundary flags

  • The one failing check, Spec Main Shape Gate (run 115757942224) — not this PR's. Every failure annotation names packages/react/src/hooks/useViewData.ts:124-125 (TS2367) or packages/core/src/adapters/resolveDataSource.ts:59-62 (TS2678/TS2339), compiled against @objectstack/spec built from objectstack 04a314873cdc; the intersection of annotation paths with this PR's four files is empty. The break is the spec-main api data-source arm, which the claim already names as objectui#12325's surface. Accepted as pre-existing; nothing for this PR to carry. All other 32 check-runs are success or skipped (dependabot, coverage).
  • Dev deviation "landing face is NamedListView only" — answered in ①.2: verified, correct.
  • Dev deviation "zero label writes" — objectui has no label gate and the dispatch named none. Nothing owed.
  • Dev flag: NamedListView docblock still says its fate "follows objectui#7924" — out of this card's scope; the card asks for the mirror only. Accepted as-is.
  • Dev flag: the spec's "is not enforced yet" sentence — the triage ruling (6162030734) already assigns it to the spec owner. Not objectui's; accepted.
  • Dev out-of-scope finding: the census failure message points at a consumed changeset — a stale pointer in test prose, no verdict depends on it. Accepted as carried by the next editor, per the report; no block.
  • Open question (Section 3, the D4 viewer): the dev measured that a D4-exempt caller (studio.access, manage_metadata, setup.access, isSystem) receives listViews whole from /meta/object, the console lists and opens such a view from objectDef.listViews, and no 403 occurs because the by-name probe runs only on a miss. Options A (leave), B (server prunes the plain runtime read for D4, whole document only on authoring doors), C (client gate, refused by the card). Ruling here: A for this PR, and the question is escalated to objectstack, not objectui. The card forbids C outright. B is a server change in packages/rest/src/meta-item-read-gate.ts with a save-back hazard the exemption exists for, so it is objectstack's decision and not a condition of this mirror. No objectui change is owed either way, and this PR correctly implements nothing for it. The dev's recommendation (A now, B on a measured complaint) is accepted; triage on the objectstack side decides whether to file B.
  • Local-runs: none. The dev's build, type-check, vitest, ablation and dist-probe figures in the PR body are read as claims and cross-checked only against the green Test, Test (dist pins), Type Check, Lint, Repo Gates and Build & E2E checks on this head; nothing was re-run.

Implemented-by: claude/issue-12341-list-view-required-permissions
Reviewed-by: session_01XVp19E1MYUTFM3dnkwz8gB

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

2 participants