Skip to content

feat(app-shell): the locked permission matrix offers Clone to customize through the published clone_permission_set action (objectui#5987) - #10754

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-5987-permset-clone-to-customize
Sep 27, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-5987-permset-clone-to-customize

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #5987
Clause-②: no

Placeholders in this body are written as capitalised words (NAME, PR_NUMBER) rather than in angle brackets, because the body sanitizer strips angle-bracket shapes at rest.

What changed

packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx, i18n.ts (new rows only, plus one rewritten hint), a small helper permission-set-clone-dispatch.ts (resolves the published action off the object definitions and shapes the runner dispatch), one new pin file, one changeset (@object-ui/app-shell: minor; the dispatch order said patch, the contract review at 48aaf0f068 flagged the conflict with .changeset/README.md's "minor (new feature)" and the feat( headline as the seat's call, and the seat then ruled minor, correcting its own order; sibling objectui#10449 is minor). The lock predicate isArtifactBackedLayer is byte-identical to main; no second predicate.

Why the helper (second commit): permission-slice.authoredKeys.test.ts scans PermissionMatrixEditor.tsx for { ...spread, key } literals inside setDraft( regions and reads their keys as permission-set facets. Its setDraft( regex also matches the prose funnels through setDraft (matrix checkboxes, … in a // comment, and the ) that would close that region sits on the next comment line, which the walker skips wholesale — so on main too that region runs to end of file and every leading-spread literal below it is counted. My dispatch literal ({ ...localized, objectName, params }, the action call's argument shape, not a facet) landed inside it and CI read objectName / params as dropped facets. Moving the dispatch construction into the helper, which the scan does not read, is the honest fix: the keys were never facets, and the slice's carried-key list is untouched. The scanner defect itself is reported in the dev report, not changed here.

  • The locked editor (artifact tier is the deciding gate: lockedByArtifactTier) renders Clone to customize in the header slot Save would occupy, and a visible guidance line (role="note") that names cloning first. The badge hint perm.readOnly.artifact.hint is rewritten so cloning comes first and the former remedies (edit the source artifact and redeploy, a new runtime set, OS_METADATA_WRITABLE) follow as secondary routes; the server's reason and code stay in it.
  • Clicking it runs the server-published clone_permission_set record action on sys_permission_set through the console's shared action runner (useAction()), resolved by name off the object definition the console metadata store holds (useMetadata().ensureType('object') then findCloneAction), with the set's sys_permission_set row (found by name, the same lookup AssignedUsersSection uses) stashed under params._rowRecord and the declared params surfaced as actionParams (buildCloneDispatch) — the dispatch shape of DeclaredActionsBar. No clone is assembled from create/update calls: an object that publishes no such action, or a set with no row, is a named refusal (perm.clone.actionMissing, perm.clone.rowMissing).
  • On success the routed metadata admin navigates to ../NAME (relative: 'path', the spelling ResourceEditPage uses after create), where the clone loads with no code layer and is writable. An embedded host has no route and gets a role="status" notice naming the clone.
  • Item 3: a save that still reaches the environment door and is refused 403 with code NOT_OVERRIDABLE (matched with errorCodeIs, never on prose) renders perm.save.locked with the same Clone button and keeps the server's sentence underneath, instead of the transport's message alone. Env door only (!packageId).

H1 — the agreement measurement

Booted showcase: NOT MEASURED. Blocker, measured in this container: /home/user/objectstack has node_modules with 0 entries and no dist under packages/spec, packages/plugins/plugin-security or packages/cli; objectui's node_modules/@objectstack holds only spec (17.4.0), no runtime plugin. A showcase boot needs a full objectstack install plus a workspace build under the shared verify lock, which is outside this card's objectui-only file surface and the foreground cap. Not guessed.

Source-level reading instead (objectstack origin/main at 2bbebf5321; sys-permission-set.object.ts is byte-identical at tag @objectstack/plugin-security@17.4.0, git diff empty), on the same two inputs:

  • Server isArtifactBacked(type, name) in metadata-protocol/src/protocol.ts is true when lookupArtifactItem(type, name) answers — artifact items only, the sys_metadata rehydration sentinel excluded, plain-key shadows ignored — or when isNestedArtifactField does (declared field-only, irrelevant to permission).
  • The layered read's code layer is the MetadataService copy when one exists, else lookupArtifactItem(...) ?? registry.getItem(...); a sys_metadata row reaches that getItem fallback as hydrateOverlayIntoRegistry registered it, _provenance: 'org' stamped by stateTenantAuthorship and _packageId absent (stripDerivedProvenance strips it at write) or, on the listing path, re-stamped by getMetaItems from the row's package_id column, where the sys_metadata sentinel appears only on a legacy package-less row. Its provenance is the item's own _provenance stamp (resolveLockState → extractProtection, @objectstack/spec kernel metadata-protection.zod.ts).
  • Client isArtifactBackedLayer(lay) is code non-null AND code._packageId !== 'sys_metadata' AND provenance !== 'org'.
  • Case A, a code-package set: server true → saveMetaItem refuses the env-door PUT with 403 NOT_OVERRIDABLE (allowOrgOverride: false, when environmentId is defined); client code carries the package id → locked. Agree.
  • Case B, a sys_metadata-backed package set (managed_by: 'package', definition only in sys_metadata): server lookupArtifactItem (getArtifactItem, artifact-only) misses a DB-hydrated row → false → the allowRuntimeCreate tier accepts; client code is the hydrated item, registered with no _packageId and stamped _provenance: 'org' (hydrateOverlayIntoRegistry then stateTenantAuthorship), so provenance !== 'org' is false and the editor does not lock. Agree.
  • The data door's classifyPackagedPermissionSet (packaged-permission-set-lock.ts) decides from the same artifact rule (declaredPackageIdOf excludes _envProjection echoes and the sentinel), so all three spellings key on one fact.
  • Known residue, unchanged (objectui#4526): on a single-kernel host (environmentId undefined) the server disengages its artifact tier and accepts; the client still locks. Conservative, previously accepted.

H2 — how the console invokes a published record action

clone_permission_set is type: 'api', method: 'POST', target: '/api/v1/data/sys_permission_set', bodyExtra: { active: true }, params: label and name (required text), description and the five facets with defaultFromRow / carryOver. It is not a script action, so serverActionHandler is not its path: the console dispatches type: 'api' through useConsoleActionRuntime.apiHandler, registered on the ActionProvider that GlobalActionRuntimeProvider (ConsoleShell) wraps every route in, with onParamCollection seeding defaultFromRow params from params._rowRecord. The editor uses that same provider via useAction().

What the action returns: NOT MEASURED on a live server. By source, the data door answers the spec's CreateDataResponse — { object, id, record } — with status 201 (runtime/src/domains/data.ts, CreateDataResponseSchema in the installed @objectstack/spec@17.4.0), and apiHandler unwraps the { success, data } envelope to result.data. The editor reads result.data.record.name; when that is absent it says so (perm.clone.noName) rather than guessing.

H3 — strings

All through metadata-admin/i18n.ts, en-US and zh-CN rows. Rewritten: perm.readOnly.artifact.hint. New: perm.clone.action, perm.clone.hint, perm.clone.guidance, perm.clone.actionMissing, perm.clone.rowMissing, perm.clone.noName, perm.clone.done, perm.save.locked. check:i18n-designer-parity and check:i18n-drift (designer population) both green.

H4 — item 3

Measured with the 403 mocked at the data-source boundary (client.save rejecting with status: 403, code: 'NOT_OVERRIDABLE'). Before: the red strip rendered the transport's message. For the metadata door that message (saveMetaItem's artifact-tier refusal) says to edit the source artifact and redeploy or set OS_METADATA_WRITABLE — the pre-ruling remedies, never the clone — so it was the generic form. Changed as described above; the pin and its controls are below.

Pins and ablations

New file PermissionMatrixEditor.cloneToCustomize.test.tsx, 17 tests, real ActionProvider with the runtime's three seams faked (api handler, param dialog, toast); adapter and metadata store faked through @object-ui/react with importOriginal spread.

Red on the base editor plus base i18n (a2f361b8c1, the test file kept): Tests 11 failed | 6 passed (17) — the 6 that pass on base are the controls (org-owned / sentinel / type-locked / host-locked show no clone prompt; other refusals stay generic), which hold on base by construction. Head: 17 passed.

Per-hunk ablations, each landed on disk (anchor 1 → 0, replacement present), run, and restored with git checkout HEAD -- ABSOLUTE_PATH, proven by git diff HEAD empty and blob hash equal to HEAD's:

  • A header Clone button removed → 8 failed (primary action, zh, dispatch, opens writable, embedded notice, cancel, both no-fallback refusals).
  • B item-3 branch removed → 2 failed (NOT_OVERRIDABLE guidance, lowercase-code spelling).
  • C en hint with the secondary routes moved before cloning → 1 failed (ordering pin).
  • D visible guidance strip removed → 2 failed (primary-action pin, zh).
  • E zh perm.clone.action row deleted → 1 failed (zh), and check-i18n-designer-table-parity exit 1 naming perm.clone.action.
  • F declared params not surfaced to the runner → 2 failed (dispatch pin, cancel pin).
  • G clone not opened after success → 1 failed (opens-writable pin).

Gates (local; the farm is CI's)

All under the shared verify lock unless noted; verdict lines quoted from the tools.

  • pnpm exec vitest run --maxWorkers=2 over PermissionMatrixEditor*, the metadata-admin i18n pins and PackageOwdOverviewPanel.readDecorationStrip: Test Files 22 passed (22) · Tests 119 passed (119) after the one selector fix (first run 118/119; the zh pin matched two elements).
  • Whole packages/app-shell/src/views/metadata-admin/ directory, every test file, at 48aaf0f068: Test Files 314 passed (314) · Tests 3629 passed | 1 skipped (3630) (VERDICT command-exit 0).
  • pnpm exec turbo run build --filter=@object-ui/app-shell^... --concurrency=2: Tasks: 28 successful, 28 total.
  • pnpm --filter @object-ui/app-shell type-check (script name echoed): VERDICT command-exit 0, no error TS.
  • eslint on the editor and the helper (lock-free, --format json, re-measured at 2f47d40422): 0 errors; editor 21 warnings, 20 pre-existing on unchanged lines plus one no-explicit-any on the new clone handler's catch (err: any), the spelling doSave already uses; helper 0. Population = eslint . from packages/app-shell under the root flat config, which sets no parserOptions.project / projectService, so type-aware linting is off and this diff cannot move any untouched file's verdict. pnpm lint (repo scan) is CI's.
  • Lock-free gates, each exit 0: check-i18n-designer-table-parity, check-i18n-en-drift, check-i18n-call-site-keys, check-changeset-presence (4 source file(s) of 1 released package(s) changed ... declares 1 changeset(s)), check-changeset-no-major, check-changeset-fixed, check-changeset-overwrite, check-changeset-claims, check-pending-changeset-literals, check-control-bytes, check-new-cross-file-line-citations (0 new citation(s)), check-vi-mock-specifiers, check-vi-mock-inherit, check-vi-mock-override-shape, check-test-path-roots, check-unreferenced-sources, check-handler-key-read-sites, check-object-metadata-write-doors.
  • Not run locally, CI's: pnpm lint, the four/eight test shards, pnpm check (needs the CLI build).

Serial

origin/main fetched into a private ref before each push. At the final push (2f47d40422) origin/main is 704e05b09d: 6 commits since the base a2f361b8c1 by git rev-list --count (no merges; the review record counted 7 — the six are listed in the dev report), none touching the changed paths; git merge-tree --write-tree against HEAD exits 0. No merge commit needed.

Round 2 (published text only)

Head 2f47d40422, commit docs(changeset): grade Clone to customize minor and name the door whose refusal lacks the clone remedy (objectui#5987), one file: .changeset/5987-permset-clone-to-customize.md (md5 796364a5879f561ed0748f29b4eee335 before, 31bb4262ee708eab72e78455936b16a9 after). No source change, so the pins, ablations, type-check and directory run above hold as measured at 48aaf0f068.

  • Frontmatter '@object-ui/app-shell': patch → minor (the seat's ruling after the review; see "What changed").
  • The CHANGELOG-bound sentence now reads: "was never on the screen, and the metadata-door refusal a Studio save receives names only the pre-ruling remedies; the data door's refusal is the one that names the Clone action." It used to say the server's 403 refusal names the clone, which is true of the data door only.
  • Gates at this head, each exit 0: check-changeset-fixed, check-changeset-no-major (together changeset:check), check-changeset-presence (4 source file(s) of 1 released package(s) ... declares 1 changeset(s)), check-changeset-overwrite (No pre-existing changeset was modified or deleted — this changeset is new on this branch), check-changeset-claims, check-control-bytes, check-new-cross-file-line-citations (0 new citation(s)); eslint and Serial as re-measured above.
  • The H1 Case B sentence's server half was aligned to the record's reading (getArtifactItem misses a DB-hydrated row) alongside the verbatim client-half replacement.

Acceptance notes

  • Observation (objectstack side, not in this PR): the metadata door's artifact-tier refusal text in saveMetaItem still names edit-and-redeploy and OS_METADATA_WRITABLE, while the data door's PackagedPermissionSetLockedError names the Clone action. The Studio editor hits the metadata door, which is why item 3 had to key on the code. Reported in the dev report for the seat, not filed here.
  • The 6 control pins pass on base by construction (they assert an absence); the 11 affordance pins are the red-then-green evidence.
  • useAction() falls back to a bare ActionRunner when no ActionProvider is mounted (a host that embeds the editor outside ConsoleShell); that fallback is the platform's existing shape for every useAction() consumer and is not changed here.
  • If the typed clone name collides with a package-declared set, the data door's insert leg refuses it; that refusal is the published action's own and reaches the user through the runner's error toast, unchanged.
  • The single-kernel residue of objectui#4526 is unchanged.

Generated by Claude Code

…ize" through the published clone_permission_set action (objectui#5987)

A permission set a code package ships is locked by the artifact tier
(`isArtifactBackedLayer`, unchanged). Its guidance offered only the
pre-ruling remedies; the ruled path — lock the base, clone to customize —
was never on the screen, although the server's 403 refusal names it.

- The locked editor renders "Clone to customize" in the slot Save would
  occupy and names cloning first in its guidance (en-US and zh-CN rows).
- The button runs the `clone_permission_set` record action published on
  `sys_permission_set`: resolved by name off the object definition the
  console holds, the set's row stashed under `params._rowRecord`, the
  declared params surfaced to the runner's dialog, dispatched through the
  shared console action runner. No clone is assembled from create/update
  calls; a missing action or row is a named refusal.
- The returned clone is opened (`../name`) on the routed metadata admin,
  where it loads with no code layer and is writable; an embedded host is
  told the clone's name.
- A save that still reaches the environment door and is refused
  403 NOT_OVERRIDABLE shows the same guidance and action, keyed on the
  code, beside the server's sentence.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
@github-actions

github-actions Bot commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 3 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/6310-designer-formula-key-retired.md

  • names packages/app-shell/src/views/metadata-admin/i18n.ts → packages/app-shell/src/views/metadata-admin/i18n.ts — edited by this change

    Not touched: designer.field.formula ('Formula (CEL)') in packages/app-shell/src/views/metadata-admin/i18n.ts, a different and live key belonging to metadata-admin's ObjectFieldInspector — the surface that still authors formula expressions.

.changeset/7125-dashboard-empty-state-keys-retired.md

  • names packages/app-shell/src/views/metadata-admin/i18n.ts → packages/app-shell/src/views/metadata-admin/i18n.ts — edited by this change

    Not touched: table.noRows ('No rows to display') and engine.form.noRows (packages/app-shell/src/views/metadata-admin/i18n.ts, read at widgets.tsx) — two different, same-named keys in different namespaces. Nor the comments in WidgetEmptyState.tsx, DatasetWidget.tsx, ObjectDataTable.tsx and PivotTable.tsx that record WHY three widgets with three strings became one shared empty state; the packs' own comment keeps that rationale and now names the retirement instead of a row that is gone.

.changeset/8632-malformed-picklist-option-loud.md

  • names views/metadata-admin/i18n.ts → packages/app-shell/src/views/metadata-admin/i18n.ts — edited by this change

    Two new strings land in the designer's own en / zh tables — the metadata-admin console owns its strings in views/metadata-admin/i18n.ts and is deliberately outside the ten locale packs (packages/i18n/README.md, "Scope — the engine.* carve-out").

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 704e05b09 (merge-base with origin/main): 4 file(s) changed outside .changeset/, read against 1574 pending declaration(s) that publish a body (2162 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3067.0 KB 3104.5 KB
Main entry chunk (gzip) 148.3 KB 350 KB
Entry file index-BmrvQ__R.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) 16.57KB 6.15KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
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.17KB 10.58KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
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) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.52KB 3.45KB
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.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 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) 554.92KB 132.66KB
core (index.js) 9.52KB 3.79KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 223.91KB 62.28KB
fields (index.js) 259.29KB 65.64KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
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.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.28KB 11.09KB
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) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
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.01KB 3.93KB
plugin-calendar (index.js) 51.65KB 14.69KB
plugin-charts (index.js) 82.37KB 22.57KB
plugin-chatbot (index.js) 198.40KB 47.22KB
plugin-dashboard (index.js) 134.05KB 35.61KB
plugin-designer (index.js) 216.25KB 44.39KB
plugin-detail (index.js) 233.23KB 61.74KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 155.59KB 39.75KB
plugin-gantt (index.js) 169.75KB 41.96KB
plugin-grid (index.js) 218.12KB 59.72KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 114.70KB 28.42KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.91KB 9.05KB
plugin-tree (index.js) 10.58KB 3.72KB
plugin-view (index.js) 87.83KB 22.01KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
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) 116.21KB 38.14KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.78KB 2.56KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.20KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
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) 0.20KB 0.18KB
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) 4.74KB 2.25KB
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 (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) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
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

…itor surface (objectui#5987)

CI Test (shard 1/8) failed `permission-slice.authoredKeys.test.ts` on the
first commit: `expected [ 'objectName', 'params' ] to deeply equal []`.

The scanner reads `{ ...spread, key }` literals inside `setDraft(` regions of
`PermissionMatrixEditor.tsx` as facets written into the permission-set draft.
Its `setDraft(` regex also matches the prose `funnels through setDraft (matrix
checkboxes, …` in a `//` comment, and the `)` that would close that region is
on the next comment line, which the walker skips wholesale — so that region
runs to end of file (on main too) and every leading-spread literal below it is
counted. The clone dispatch literal (`{ ...localized, objectName, params }`)
is the action runner's call shape, not a permission-set facet, and it landed
inside that region.

The construction now lives in `permission-set-clone-dispatch.ts`
(`findCloneAction`, `buildCloneDispatch`), which the scan does not read. No
behaviour change; the slice's carried-key list is untouched. The scanner's
comment-matching is reported separately, not changed here.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3067.4 KB 3104.5 KB
Main entry chunk (gzip) 148.3 KB 350 KB
Entry file index-8U9AF8xf.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) 16.57KB 6.15KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
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.17KB 10.58KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
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) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.52KB 3.45KB
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.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 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) 554.92KB 132.66KB
core (index.js) 9.52KB 3.79KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 223.93KB 62.31KB
fields (index.js) 259.29KB 65.64KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
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.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.28KB 11.09KB
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) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
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.01KB 3.93KB
plugin-calendar (index.js) 51.65KB 14.69KB
plugin-charts (index.js) 82.37KB 22.57KB
plugin-chatbot (index.js) 198.40KB 47.22KB
plugin-dashboard (index.js) 134.11KB 35.62KB
plugin-designer (index.js) 216.25KB 44.39KB
plugin-detail (index.js) 233.23KB 61.74KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 157.08KB 40.13KB
plugin-gantt (index.js) 169.75KB 41.96KB
plugin-grid (index.js) 218.12KB 59.72KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 114.70KB 28.42KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.91KB 9.05KB
plugin-tree (index.js) 10.58KB 3.72KB
plugin-view (index.js) 87.83KB 22.01KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
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) 116.21KB 38.14KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.78KB 2.56KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.20KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
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) 0.20KB 0.18KB
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) 4.74KB 2.25KB
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 (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) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
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: 48aaf0f06807bada2fdad685171dfda4f2b66969

Head moved mid-review from 6986574dab. Carried over (the delta touches neither): every objectstack read, the i18n rows and gates, the base run, ablations A/B/F (F's anchor now lives in the helper; logic byte-equal), the item-3 trace, open-PR scan shape. Re-measured at 48aaf0f068: predicate hash, delta diff, scanner replica, authoredKeys, the 17 pins, the metadata-admin set, the whole directory, changeset gates, eslint, governed check, merge-tree, CI, open PRs, PR state.

① Derived judgments

  • Predicate unchanged. isArtifactBackedLayer sha1 78a839a9… identical on main 704e05b09d, on 6986574dab and on 48aaf0f068; no second predicate; lockedByArtifactTier pre-exists on main. Server side (objectstack origin/main 369bcbeda7; sys-permission-set.object.ts, protocol.ts, packaged-permission-set-lock.ts byte-identical to 2bbebf5321 and to tag @objectstack/plugin-security@17.4.0): isArtifactBacked = lookupArtifactItem (artifact-only getArtifactItem, sentinel excluded) OR isNestedArtifactField (field-only). Case A (code-package set): server true, env door throws 403 NOT_OVERRIDABLE at protocol.ts about line 15810 when environmentId defined and !overlayAllowed; client code._packageId = real id, provenance not org, so locked. Agree. Case B (sys_metadata-backed): server getArtifactItem misses a DB-hydrated row, so accepts; client: hydrateOverlayIntoRegistry (about 14705) calls registerItem WITHOUT a package id and stamps _provenance: 'org' via stateTenantAuthorship (1495), so the hydrated code has NO _packageId and the non-lock comes from the provenance !== 'org' clause (the PermissionMatrixEditor artifact tier over-locks on single-kernel hosts (environmentId undefined) — accepted residue of #4518 #4526 fix), not from the sentinel as the body says; the sentinel applies only to a just-saved row before reboot. Verdict "agree" holds; the body's mechanism sentence is wrong (see ②). Single-kernel residue (PermissionMatrixEditor artifact tier over-locks on single-kernel hosts (environmentId undefined) — accepted residue of #4518 #4526) correctly disclosed. Boot NOT MEASURED is stated honestly and I could not boot either (/home/user/objectstack/node_modules empty).
  • Action wiring. useAction() under GlobalActionRuntimeProvider (ConsoleShell.tsx:286, wraps every route) then ActionRunner.execute: actionParams array goes to paramCollectionHandler (console dialog, seeds defaultFromRow from params._rowRecord, resolves field params against objectName = sys_permission_set), merges collected values, preserves _rowRecord, then handlers.get('api') = useConsoleActionRuntime.apiHandler. Declaration matches: type: 'api', method: 'POST', target: '/api/v1/data/sys_permission_set', bodyExtra: { active: true }, params label,name required plus description and five carryOver facets; no recordIdParam, no {id} token, so _rowRecord is stripped and the POST body is collected params plus active: true (source trace of apiHandler 361–492). Measured at the runner-to-handler seam by the pin (name/type/method/target/objectName/bodyExtra/params/actionParams); the fetch itself is source-traced, not fetch-measured. Not a generic create/update. findCloneAction reads ensureType('object') items, the same raw definitions (extractItems, meta.getItems) that DeclaredActionsBar reads objectDef.actions from; missing object/action gives perm.clone.actionMissing, missing row gives perm.clone.rowMissing, both pinned with apiHandler never called.
  • Return and open. Data door: callData('create') then protocol.createData returns { object, id, record }, status 201; installed spec 17.4.0 CreateDataResponseSchema agrees; apiHandler unwraps json.data. Editor reads result.data.record.name, hops ../NAME with relative: 'path'; pin proves the clone renders Save, no clone prompt, no note, layered called with the clone name. Real-world writability of the clone rests on it having no artifact registration (an org insert), consistent with Case B above.
  • Controls. Org-owned, sentinel, type-locked, host-locked (readOnly with packageId): no clone prompt; 6 controls green on base by construction. Note: lockedByArtifactTier && cloneButton does not consult readOnly; unreachable today because the only embedding host (StudioDesignSurface.tsx:4691) passes packageId alongside readOnly, which zeroes artifactTierApplies. Consequence worth the seat's eye: under the package door (Studio Access pillar) no clone affordance exists at all; consistent with the narrowed scope (artifact tier), not with the card title's broader wording.
  • Item 3. Dispatcher envelope error.code = thrown.code (dispatcher-plugin.ts about 790); MetadataClient.parseError sets err.code from details.code ?? error.code ?? body.code; errorCodeIs compares case-insensitively on err.code. Env-door only (!packageId). Probes pinned: PERMISSION_DENIED stays generic, package-door NOT_OVERRIDABLE stays generic, lowercase code matches. Correct.
  • i18n. en-US and zh-CN rows present; parity, en-drift and call-site gates exit 0 in my worktree. zh wording sound (「克隆后自定义」, remedies re-ordered clone-first).
  • Pins and ablations (my worktree, under the verify lock). Base (a2f361b8c1 editor + i18n, test kept): 11 failed | 6 passed (17). Head 6986574dab: 17 passed. Ablation A (button line removed): 8 failed; B (item-3 branch): 2 failed; F (actionParams not surfaced): 2 failed — each mutation proven anchor 1 to 0 and restore proven by empty git diff HEAD and blob equal to HEAD. At 48aaf0f068: authoredKeys 3/3, the 17 pins green, 21 files / 118 tests green, whole metadata-admin/: Test Files 314 passed (314) · Tests 3629 passed | 1 skipped (3630).
  • The red on 6986574dab and the delta. Reproduced locally: authoredKeys "carries every facet" failed with ['objectName','params']. Scanner replica: the CALL regex matches the prose funnels through setDraft ( in a // comment; balanced() skips the next comment line whole, so the region runs to EOF on main (617 to 1603), on 6986574dab (663 to 1813) and on 48aaf0f068 (651 to 1784); key sets on main and new head identical (objects, fields, name, label, systemPermissions). Defect is real and pre-existing. The delta 6986574dab..48aaf0f068 is a pure move: findCloneAction and buildCloneDispatch reproduce the lookup, spread precedence (localized declaration first, then objectName, then params: { _rowRecord }) and the conditional actionParams; objectName/params never reach setDraft or mergePermissionSlice; EDITOR_AUTHORED_KEYS untouched; no allow-list. Honest fix; the scanner stays defective on main (reported out of scope by the dev; it will trip the next leading-spread literal in the editor).
  • Not verified live: the fetch body, the 201 response, the clone's writability on a real kernel — all stated NOT MEASURED in the body, correctly.

② Semver level

'@object-ui/app-shell': patch, Clause-②: no. Nothing on packages/app-shell/src/index.ts moves (neither PermissionMatrixEditPage nor the helper is exported), so patch matches the dispatch grade and the #4518 declared-surface test; it contradicts .changeset/README.md's "minor (new feature)" and the changeset's own feat( headline (sibling #10449 used minor). Seat's call; not a stop.

Changeset body, sentence by sentence: lock predicate, clone-through-published-action, refusals-not-fallbacks, item 3 and "no second predicate" are all true. One sentence is wrong for the door this editor hits: "The ruled path … was never on the screen, although the server's own 403 not_overridable refusal names it." Only the DATA door's PackagedPermissionSetLockedError names Clone; the metadata door's saveMetaItem refusal (the one a Studio save receives) names edit-and-redeploy and OS_METADATA_WRITABLE, as the body's own Acceptance note says. Replacement: "…was never on the screen, and the metadata-door refusal a Studio save receives names only the pre-ruling remedies; the data door's refusal is the one that names the Clone action."

Replacement body pr-body-10754-r2.md (identical to the addendum's embedded body): title accurate; "What changed" and "Why the helper" accurate per my replica; H2, H3, H4, Pins and ablations accurate. Stale or wrong on the head that lands:

  1. H1 Case B: "client code is the hydrated shadow with _packageId: 'sys_metadata' (or null)". Replacement: "client code is the hydrated item, registered with no _packageId and stamped _provenance: 'org' (hydrateOverlayIntoRegistry then stateTenantAuthorship), so provenance !== 'org' is false and the editor does not lock; a just-saved row carries the sys_metadata sentinel until reboot."
  2. Gates: "eslint, narrowed to the three changed files … counts 3 files … 32 warnings". Replacement: "eslint on the editor and the helper: 0 errors (editor 21 pre-existing warnings, helper 0)."
  3. Gates: "check-changeset-presence (2 source file(s) of 1 released package(s) changed …)". Replacement: "4 source file(s) of 1 released package(s) changed … declares 1 changeset(s)."
  4. Serial: "01700dcd7d … 1 commit since the base … merge-tree against HEAD exits 0". Replacement: "origin/main at 704e05b09d: 7 commits since the base a2f361b8c1, none touching the changed paths; git merge-tree --write-tree exits 0."
  5. Gates should also carry the addendum's whole-directory line: "metadata-admin/: Test Files 314 passed (314) · Tests 3629 passed | 1 skipped (3630)."
    Out-of-scope finding (metadata-door prose lacks the clone remedy): confirmed at protocol.ts about line 15810.

③ Boundary flags

  • CI on 48aaf0f068: 43 check runs, 40 success, 3 skipped, 0 failure, 0 in progress; all eight Test shards and Type Check finished green; Inert vi.mock Specifier Check green (main-red before fix(console): mock react-map-gl/maplibre by its bare specifier in the filter-token sweep (objectui#10731) #10749 merged 05:18Z; red on 6986574dab along with Test (shard 1/8), the authoredKeys failure).
  • git merge-tree --write-tree refs/review/main refs/review/pr-10754 against 704e05b09d: exit 0, tree 9038c80c8c. PR reads mergeable_state: clean, base sha 01700dcd7d.
  • Open PRs (12): none touches the five paths. fix(app-shell): inspector literals with an existing catalogue row read it (objectui#10696) #10741 merged 04:28Z before the branch; it edited i18n readers under inspectors/, not i18n.ts; no conflict.
  • Draft: draft: true — the seat must mark ready.
  • Governed surface: check-governed-queue-guard.mjs --test on the five real paths: NOT GOVERNED.
  • Bare useAction() fallback (no ActionProvider): buildApiRequestBody would POST { _rowRecord, active: true } with no dialog — unreachable inside ConsoleShell; a useHasActionProvider() guard is a reasonable follow-up, not a stop.

Implemented-by: claude/issue-5987-permset-clone-to-customize
Reviewed-by: session_014mXUNuFomfj24w7s1pZzhN

VERDICT: FAIL

Code and pins pass at 48aaf0f068; the stop is published text only. Before landing: correct the changeset sentence in ② (CHANGELOG-bound), and apply replacements 1–5 to the r2 body so the body describes the head that lands. Then this record reads PASS with no further measurement.

…se refusal lacks the clone remedy (objectui#5987)

Contract review at 48aaf0f (published text only):
- `'@object-ui/app-shell': minor` — a new user-facing affordance is a
  feature under .changeset/README.md, matching the `feat(` headline; the
  dispatch order's `patch` was the seat's own correction.
- The CHANGELOG-bound sentence no longer says the server's 403 refusal
  names the clone: only the data door's PackagedPermissionSetLockedError
  does; the metadata-door refusal a Studio save receives names the
  pre-ruling remedies.

Prose and frontmatter only; no source change.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3067.4 KB 3104.5 KB
Main entry chunk (gzip) 148.3 KB 350 KB
Entry file index-8U9AF8xf.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) 16.57KB 6.15KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
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.17KB 10.58KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
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) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.52KB 3.45KB
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.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 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) 554.92KB 132.66KB
core (index.js) 9.52KB 3.79KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 223.93KB 62.31KB
fields (index.js) 259.29KB 65.64KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
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.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.28KB 11.09KB
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) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
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.01KB 3.93KB
plugin-calendar (index.js) 51.65KB 14.69KB
plugin-charts (index.js) 82.37KB 22.57KB
plugin-chatbot (index.js) 198.40KB 47.22KB
plugin-dashboard (index.js) 134.11KB 35.62KB
plugin-designer (index.js) 216.25KB 44.39KB
plugin-detail (index.js) 233.23KB 61.74KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 157.08KB 40.13KB
plugin-gantt (index.js) 169.75KB 41.96KB
plugin-grid (index.js) 218.12KB 59.72KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 114.70KB 28.42KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.91KB 9.05KB
plugin-tree (index.js) 10.58KB 3.72KB
plugin-view (index.js) 87.83KB 22.01KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
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) 116.21KB 38.14KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.78KB 2.56KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.20KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
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) 0.20KB 0.18KB
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) 4.74KB 2.25KB
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 (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) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
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: 2f47d4042245efd838847738bcb9cc5560032cbc

Round 2, narrow: the delta, the changeset, the r3 body (pr-body-10754-r3.md, diffed against pr-body-10754-r2.md), the live title, and the boundary. Code, pins and ablations carried from round 1 (48aaf0f068); no source moved, so nothing there was re-run.

① Derived judgments

  • Delta is exactly the one changeset file. git diff --name-status 48aaf0f068 refs/review/pr-10754: M .changeset/5987-permset-clone-to-customize.md; numstat 4 2; one commit 2f47d40422 (docs(changeset): grade Clone to customize minor and name the door whose refusal lacks the clone remedy (objectui#5987)). PR files vs merge-base a2f361b8c1 are still the five paths (A changeset, A test, M editor, M i18n.ts, A helper). No source moved. md5 before 796364a5879f561ed0748f29b4eee335, after 31bb4262ee708eab72e78455936b16a9, both as the body's Round 2 section states.
  • Frontmatter is byte-exact '@object-ui/app-shell': minor (od -c: '@object-ui/app-shell': minor\n).
  • Sentence is round 1's replacement verbatim, word for word, wrapped at 80 columns: "was never on the screen, and the metadata-door refusal a Studio save receives names only the pre-ruling remedies; the data door's refusal is the one that names the Clone action."
  • Changeset gates. NOT run locally: /home/user/objectui/node_modules does not exist, so check-changeset-*.mjs cannot load. changeset:check is check-changeset-fixed.mjs && check-changeset-no-major.mjs (root package.json); @object-ui/app-shell sits in the single fixed group (.changeset/config.json), and minor is not major. CI on this head: Changeset Fixed Group Check, Changeset Bump Policy, Changeset Declaration, Changeset Overwrite Report, Changeset Claim Re-read all success. The bot's re-read comment counts 4 file(s) changed outside .changeset/, matching the body's "4 source file(s) of 1 released package(s)".
  • Pending changesets 6310 / 7125 / 8632: blobs 905322ab4c, a71b407ce7, 7cd08db8df identical at 48aaf0f068, at 2f47d40422 and on main. Unchanged.
  • Case B server half (new wording) is true on objectstack main. origin/main is now 455dcc060d; protocol.ts, sys-permission-set.object.ts, packaged-permission-set-lock.ts byte-identical to 369bcbeda7 (round 1's read). isArtifactBacked (protocol.ts 13785) is lookupArtifactItem (13879) OR isNestedArtifactField; lookupArtifactItem calls registry.getArtifactItem (objectql registry.ts 3919), whose composite scan needs isCodeArtifactBody and whose bare-key fallback needs _packageId truthy, not the sentinel, and !isTenantAuthored (metadata-core code-artifact-provenance.ts 43: _provenance === 'org'). A row hydrateOverlayIntoRegistry (14705) registers goes through stateTenantAuthorship (1495, _provenance: 'org'), so the fallback rejects it: a DB-hydrated row misses. permission declares supportsOverlay: true, allowOrgOverride: false, allowRuntimeCreate: true (metadata-plugin.zod.ts 1105), so neither the code-only gate (15715) nor the env-door refusal (15740, artifactBacked && !overlayAllowed) fires. Agree.
  • Case B client half (round 1's text, verbatim) holds. Boot hydration loadMetaFromDb (21947, not gated by environmentId) hands the stored body to the same hydrator; stripDerivedProvenance (1462) removed _packageId at write and no boot-path re-stamp exists (the _packageId re-stamps are 7643, 7789, 8171, 8239, all read paths), so the registered body has no _packageId and _provenance: 'org'; resolveLockState(code ?? overlay) (8805) serves that as provenance. One clause I could not locate on main: "a just-saved row carries the sys_metadata sentinel until reboot". The save stores package_id = targetPackageId (requested package or null, sys-metadata-repository.ts 651 to 653), the write-through hydrates with 'org', and the only sentinel writers left are classifyObjectContribution's contributor key for object (14535, 14540) and legacy column values (19866). The client docblock (editor 131 to 136) calls it "the save-path sentinel"; that is the client's own reading. Verdict-neutral; the seat may drop the clause.
  • The Serial count is 6, not 7. git rev-list --count a2f361b8c1..704e05b09d = 6 (same with --no-merges and --first-parent): 704e05b09d, 1422a920ed, 7b395d8c56, 9327397045, e2b8f99c22, 01700dcd7d. Round 1's 7 was wrong. 2f47d40422 was committed 06:12:17Z and its checks started 06:12:26Z; feat(core,sdui-parser,components): the published manifest declares the html tier's intrinsic elements, marked tier html — div stays out (objectui#10735) #10753 merged 06:16:46Z, so "at the final push origin/main is 704e05b09d" is accurate. The body should state 6, and does. Current main baac95a261 is 7 since base; none of the 20 files it moved is one of the five.
  • Round 2 section vs head: commit subject, file name, md5s, "no source change" all match; changeset:check composition matches package.json; check-changeset-overwrite's quoted line is the script's own (check-changeset-overwrite.mjs 334) and the changeset is absent at the merge-base, so "new on this branch" is right. "Why the helper (second commit)" is still the second of three.

② Semver level

'@object-ui/app-shell': minor, Clause-②: no. Consistent with .changeset/README.md ("minor (new feature)"), the feat( headline, the fixed group (one group, minor bumps it together, no-major unaffected) and sibling objectui#10449, whose .changeset/4526-matrix-artifact-by-provenance.md is '@object-ui/app-shell': minor (confirmed via its /files). Correct level.

Replacements 1 to 5 from round 1: all applied, verified by diff -u of r2 against r3 (Case B client half verbatim; eslint line; presence count 4; Serial rewritten with 6; whole-directory line added). What is still wrong in the r3 body, each with replacement text:

R1, required. H1 code-layer bullet still states the superseded mechanism: "a DB-hydrated runtime shadow carries _packageId: 'sys_metadata'". On main the hydrated body carries _provenance: 'org' and no _packageId at boot, or on the listing path the row's package_id column value (getMetaItems 7643), which is the sentinel only where a legacy package-less row holds it. It contradicts the corrected Case B bullet two lines below. Replacement: "The layered read's code layer is the MetadataService copy when one exists, else lookupArtifactItem(...) ?? registry.getItem(...); a sys_metadata row reaches that getItem fallback as hydrateOverlayIntoRegistry registered it, _provenance: 'org' stamped by stateTenantAuthorship and _packageId absent (stripDerivedProvenance strips it at write) or, on the listing path, re-stamped by getMetaItems from the row's package_id column, where the sys_metadata sentinel appears only on a legacy package-less row. Its provenance is the item's own _provenance stamp (resolveLockState → extractProtection, @objectstack/spec kernel metadata-protection.zod.ts)."

R2, required. Gates: "editor 21 pre-existing warnings on unchanged lines" is false in its qualifier. The head editor adds catch (err: any) on a new line (diff hunk +170), @typescript-eslint/no-explicit-any is warn for **/*.{ts,tsx} (eslint.config.js 101), the file carries no disable directive, and the editor's any token count goes 16 to 17 with none removed, so one warning is on a changed line; the dev's own r2 text said exactly this ("plus one catch (err: any) in the new handler"), and round 1's "21 pre-existing" carried the slip. I could not run eslint (no node_modules); CI Lint is green. Replacement: "eslint on the editor and the helper (lock-free, --format json, re-measured at 2f47d40422): 0 errors; editor 21 warnings, 20 pre-existing on unchanged lines plus one no-explicit-any on the new clone handler's catch (err: any), the spelling doSave already uses; helper 0. Population = eslint . from packages/app-shell under the root flat config, which sets no parserOptions.project / projectService, so type-aware linting is off and this diff cannot move any untouched file's verdict. pnpm lint (repo scan) is CI's."

R3, required. "What changed": "the seat corrected its own order in the contract review at 48aaf0f068" misplaces the ruling. The review (comment 5853251945) flagged the conflict and wrote "Seat's call; not a stop"; the seat ruled minor afterwards. Replacement: "one changeset (@object-ui/app-shell: minor; the dispatch order said patch, the contract review at 48aaf0f068 flagged the conflict with .changeset/README.md's "minor (new feature)" and the feat( headline as the seat's call, and the seat then ruled minor, correcting its own order; sibling objectui#10449 is minor)." Round 2 bullet, same fix: "Frontmatter '@object-ui/app-shell': patch → minor (the seat's ruling after the review; see "What changed")."

Optional, not a stop: the Serial paragraph may add "main has since moved to baac95a261 (7 since base); merge-tree still exits 0". The commit message of 2f47d40422 says "the dispatch order's patch was the seat's own correction", which inverts the correction; not CHANGELOG-bound, noted only.

③ Boundary flags

  • CI on 2f47d4042245efd838847738bcb9cc5560032cbc: 43 check runs, 40 success, 3 skipped (Test (coverage), Test (coverage shard), dependabot), 0 failure, 0 in progress at close (9 were in progress at first poll and finished during review). All eight Test (shard n/8), Type Check, Lint, Test (dist pins), Build & E2E, Inert vi.mock Specifier Check, Governed Surface Queue Guard, Spec Main Shape Gate and the five Changeset checks green.
  • git merge-tree --write-tree refs/review/main refs/review/pr-10754 against baac95a261 (feat(core,sdui-parser,components): the published manifest declares the html tier's intrinsic elements, marked tier html — div stays out (objectui#10735) #10753 merged): exit 0, tree 3be95c6ae1. PR reads mergeable: true, mergeable_state: behind, base sha recorded 01700dcd7d.
  • Open PRs (11: 10763, 10762, 10761, 10760, 10759, 10754, 10751, 10714, 10278, 8941, 5400): none but 10754 touches the five paths; 10763 and 10762 touch metadata-admin/previews/ and inspectors/ only.
  • Governed predicate: node /home/user/objectstack/scripts/pm/check-governed-merges.mjs --pr objectstack-ai/objectui#10754: "0 of 5 path(s) hit the register (6 surfaces)", NOT governed; 920 changed lines under the 5000 human-merge threshold; exit 0.
  • Draft true (the seat must mark ready); labels tests, package: app-shell; assignee os-elon-musk; 3 commits, +916 / −4, 5 files. Live title unchanged and accurate.

Implemented-by: claude/issue-5987-permset-clone-to-customize
Reviewed-by: session_014mXUNuFomfj24w7s1pZzhN

VERDICT: FAIL

Text only, again: the delta, the changeset (level and sentence), CI, merge-tree, governed check and the pending changesets all pass. The stop is the r3 body: apply R1 (the code-layer bullet still states the sentinel mechanism and contradicts Case B), R2 (one editor warning is on a new line, not "pre-existing on unchanged lines") and R3 (the minor ruling is the seat's, made after the review, not in it). With those three edits the body describes the head that lands and this record reads PASS with no further measurement.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 2f47d4042245efd838847738bcb9cc5560032cbc

Round 3, body-only: pr-body-10754-land.md diffed against pr-body-10754-r3.md, the five edits against round 2's text and against source, the live PR, and the boundary re-run against the moved main. Code, pins and ablations carried from rounds 1 and 2; no commit since.

① Derived judgments

  • Head unchanged. REST head.sha = 2f47d4042245efd838847738bcb9cc5560032cbc on claude/issue-5987-permset-clone-to-customize; refs/review/pr-10754 fetched to the same. Still 3 commits (6986574dab, 48aaf0f068, 2f47d40422), +916 / −4, the five paths vs merge-base a2f361b8c1 (A changeset, A test, M editor, M i18n.ts, A helper). Changeset md5 31bb4262ee708eab72e78455936b16a9, frontmatter '@object-ui/app-shell': minor.
  • The diff is exactly the five spots. diff -u r3 to land: five changed lines, nothing else. Line 8 (R3, "What changed"), line 24 (R1, the code-layer bullet), line 27 (Case B, the clause "; a just-saved row carries the sys_metadata sentinel until reboot" dropped, rest untouched), line 69 (R2, eslint), line 81 (R3, the Round 2 bullet). Land is 15565 bytes, r3 15145.
  • Verbatim. Each of the four replacement strings, extracted from round 2's record between its quote marks, occurs exactly once in land and zero times in r3; the four superseded strings ("a DB-hydrated runtime shadow carries _packageId: 'sys_metadata'", "21 pre-existing warnings on unchanged lines", "the seat corrected its own order in the contract review", "the seat's own correction") occur zero times in land. Trailing periods kept.
  • R1 is true on objectstack origin/main, now 3875ae677325 (24 commits past round 2's 455dcc060d). Among the seven files R1 rests on, only protocol.ts moved: +22 / −1 inside assertAggregationFieldsExist (#20148), nothing R1 names; registry.ts, code-artifact-provenance.ts, sys-metadata-repository.ts, packaged-permission-set-lock.ts, sys-permission-set.object.ts, metadata-protection.zod.ts diff empty. Anchors re-read: DERIVED_PROVENANCE_KEYS (1442) holds _packageId, stripDerivedProvenance (1461) deletes them; stateTenantAuthorship (1495) returns { ...data, _provenance: 'org' }; hydrateOverlayIntoRegistry (14726) registers mergeArtifactProtection(stateTenantAuthorship(data), artifact); getMetaItems (7379) re-stamps _packageId = recPkg only when undefined (7643, 7789), the listing path; the code layer is this.lookupArtifactItem(...) ?? this.engine.registry.getItem(...) (8668 to 8669); isArtifactBacked 13806, lookupArtifactItem 13900; env-door refusal 15762; loadMetaFromDb 21968. The bullet no longer contradicts Case B two lines below it.
  • R2 is true on the head. catch (err: any) is an added line (editor diff hunk +170); editor any tokens 16 at base, 17 at head, none removed; helper 0; no eslint-disable in the editor; doSave spells catch (err: any) (574, 898). The "21 / 20" count is the dev's measurement: eslint is not runnable here (no node_modules), CI Lint is success.
  • R3 is true in order. Comment 5853251945 (06:10:10Z, round 1 at 48aaf0f068) reads: "it contradicts .changeset/README.md's "minor (new feature)" and the changeset's own feat( headline (sibling fix(app-shell): decide the permission matrix's artifact tier by ADR-0010 provenance (objectui#4526) #10449 used minor). Seat's call; not a stop." The minor commit 2f47d40422 is 06:12:17Z, after it. Both spots (lines 8, 81) now say review flagged, seat ruled.
  • Nothing else contradicts. Remaining sys_metadata mentions (23 server exclusion, 25 client predicate, 27 Case B's label, 28 declaredPackageIdOf, 49 the test control) are consistent with the corrected Case B; pre-existing occurs only in line 69; corrected / correction zero; the ruling order appears only at lines 8 and 81; line 84 remains true. No angle brackets in the body (count 0). Title unchanged and accurate.
  • Serial (line 75) stays true as a statement about the final push: 704e05b09d, 6 since base (round 2's rev-list --count). Main is now 25c7d584e4 (feat(types,runner)!: retire the app node's actions array, AppAction and AppActionSchema (objectui#7469) #10761, 07:29:38Z), 10 commits since a2f361b8c1; none of the 94 files moved is one of the five. The optional note stays optional; if the seat adds it, it must read 25c7d584e4 (10 since base), not round 2's baac95a261 (7), which is stale.
  • The commit message of 2f47d40422 still inverts the correction; not CHANGELOG-bound, noted only, as in round 2.

② Semver level

'@object-ui/app-shell': minor, Clause-②: no, unchanged from round 2 and correct (README "minor (new feature)", feat( headline, single fixed group, sibling objectui#10449 minor). The body now states the level and who ruled it accurately in both places.

③ Boundary flags

  • CI on 2f47d4042245efd838847738bcb9cc5560032cbc: 43 check runs, 40 success, 3 skipped (Test (coverage), Test (coverage shard), dependabot), 0 failure, 0 in progress. Unchanged since round 2.
  • git merge-tree --write-tree refs/review/main refs/review/pr-10754 with main at 25c7d584e4: exit 0, tree c00fbe8e9d. PR reads mergeable: true, mergeable_state: clean (two earlier polls null / unknown while GitHub recomputed); base recorded 01700dcd7d. Semantic overlap with the 10 landed commits: feat(types,runner)!: retire the app node's actions array, AppAction and AppActionSchema (objectui#7469) #10761 retires the APP node's actions / AppAction in @object-ui/types; the helper's setObject?.actions is the OBJECT definition's record-action array, typed structurally ({ actions?: unknown }); ActionDef (@object-ui/core actions/ActionRunner.ts 113, exported through actions/index.js) and errorCodeIs (@object-ui/types error-code.ts 22) are present on main; packages/react, consoleActionDispatch, useConsoleActionRuntime, DeclaredActionsBar did not move since base. A merged-tree type-check is not runnable here.
  • Open PRs, 10 (10766, 10763, 10762, 10760, 10759, 10754, 10714, 10278, 8941, 5400; 10761 and 10751 merged since round 2): each /files read, none but 10754 touches the five paths; 10763 (7 files under metadata-admin/previews/) and 10762 (2 under metadata-admin/inspectors/) share the directory only.
  • Draft true (the seat must mark ready); assignee os-elon-musk; labels tests, package: app-shell; 0 formal reviews; 6 issue comments, the last two the round 1 and round 2 records.
  • The live PR body is still the round-1 text, 11677 bytes: @object-ui/app-shell: patch, "eslint, narrowed to the three changed files … 32 warnings", 2 source file(s), Serial at 01700dcd7d with 1 commit, no helper, no "Why the helper", no Round 2 section, the pre-round-2 Case B. PR updated_at 06:35:23Z is comment 5853438249's timestamp (round 2's record), not a body edit. The seat must write pr-body-10754-land.md to the PR, verbatim, before marking ready; as it stands the PR describes a patch changeset the head does not carry.

Implemented-by: claude/issue-5987-permset-clone-to-customize
Reviewed-by: session_014mXUNuFomfj24w7s1pZzhN

VERDICT: PASS

The landing body describes the head that lands: the five edits are round 2's text verbatim and true at source, nothing else in the body contradicts Case B, the eslint reading or the ruling's order, and CI, merge-tree against 25c7d584e4, the open-PR sweep and the changeset all hold. One action remains before ready: the live body on the PR is still the round-1 text and must be replaced with pr-body-10754-land.md as it is.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 27, 2026 10:59
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 27, 2026
Merged via the queue into main with commit 35eb2f0 Sep 27, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-5987-permset-clone-to-customize branch September 27, 2026 11:14
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.

Studio: package-declared permission sets render locked, with Clone-to-customize as the only edit path (UI half of objectstack#11513)

2 participants