Skip to content

fix(app-shell): declare the spec defaults lockRecord and interrupting already apply - #9339

Merged
claude[bot] merged 5 commits into
mainfrom
claude/issue-9277-declared-default-lockrecord-interrupting
Sep 13, 2026
Merged

claude[bot] merged 5 commits into
mainfrom
claude/issue-9277-declared-default-lockrecord-interrupting

Conversation

@os-tesla

Copy link
Copy Markdown
Collaborator

Fixes #9277

approval.config.lockRecord and boundary_event.boundaryConfig.interrupting now declare the defaultValue the installed spec already applies, so the two checkboxes stop asserting the opposite of what the runtime does.

Two table lines, six pin rows, one exact-count pin moved.

The spec probe triage could not run — it reads CONFIRMING

@objectstack/spec was not installed at the triage seat, so both spec-side claims arrived NOT MEASURED. Run here against the installed 17.4.0. The probe prints the resolved package.json path and version before it parses anything, so a missing install would have been an instrumented failure, never a quiet negative answer.

probe materialised for an omitted key
ApprovalNodeConfigSchema.safeParse({ approvers: [{ type: 'user', value: 'u1' }] }) lockRecord: true
FlowNodeSchema.safeParse(... boundary_event ...).data.boundaryConfig interrupting: true

Positive controls in the same output — this is what makes the two absences a reading rather than a dead probe. The approval parse returns behavior: 'first_response', onEmptyApprovers: 'admin_rescue' and maxRevisions: 3 alongside lockRecord, and the form declares a matching 'first_response' / 'admin_rescue' / '3' for all three; the boundary parse returns eventType: 'error' alongside interrupting, and the form declares 'error' for it.

So the declared value is 'true' on both — String(specDefault), derived from the installed spec, never typed from taste, exactly as the defaultValue doc comment requires.

The probe was taken past the first accepted value

Prompted by objectui#9336, which the sibling seat found by doing this: I probed both boolean values of each key, and each against every sibling enum value (all four behavior values, all four eventType values). No sibling constraint on either key — true and false are both accepted everywhere, and an omitted key materialises true everywhere.

That negative carries a positive control that fires: the same probe run asserts EndConfigSchema, and it does surface the objectui#9336 superRefine, quoting both halves of it. A probe that can see that constraint and sees none here is a reading.

This also matters to the pin: the stored-false rows assert a shape the spec genuinely accepts.

The harm, and why "declares nothing" was not neutral

Since objectui#8451 the boolean control seeds its checked state from defaultValue, so an absent declaration does not render as "no claim" — it renders as unchecked. The screen was already asserting something, and it was false:

  • Lock record unchecked told the author the record stays editable while the request is pending. The runtime locks it.
  • Interrupting unchecked told the author the boundary event leaves its host activity running. It cancels it.

Note on how this survived: the canvas node seeder in flow-canvas-parts.tsx already writes lockRecord: true and interrupting: true into a freshly dropped node, so palette-created nodes stored the key and drew correctly. Only nodes that omit the key — imported flows, JSON-authored nodes, older drafts — rendered the lie. The seeder's values and the spec's agree with the declarations added here.

Where the pin count came from

⚠️ The card wrote "10 to 12" assuming objectui#9278 does not exist. I did not use that number as a constant. Read live, immediately before opening this pull request:

origin/main = a686403b3
  FlowNodeInspector.declaredDefault.test.tsx
    'exactly ten fields declare a defaultValue'                   <- base = TEN
    'the select-kind half of the declaration surface - seven of the ten'

Base ten, plus this card's two = twelve. Re-read three times as origin/main advanced under me (0f3d153, then 80830ab, then a686403) — the pin read ten at all three, because objectui#9278's PR objectui#9337 is still open and unmerged.

The select-kind count on the same pin, which the PM flagged separately. Its numerator does not move here: both fields added are boolean-kind, so the select-kind half stays at seven, while its denominator follows the field count. That line now reads seven of the twelve — base-plus-own arithmetic applied to the half that actually moved.

Reddening this pin is expected, not routed around: its own comment says a change that adds a further declaration moves the line. The comment is updated with the count, and I added a standing note that the number is a live base plus the landing card's own additions, naming both cards, so whoever lands second re-reads rather than inherits. PR objectui#9337 added an equivalent instruction to the same comment — the two are compatible in wording and intent.

If objectui#9337 lands first, the base becomes eleven and the correct answer here is thirteen (and the select-kind line becomes eight of the thirteen). That is a re-read, not an arithmetic I can pre-commit.

Ablation leg

Run from the committed state, one declaration removed at a time. Each leg proves the mutation reached the disk before reading any result (declaration count 4 to 3 in the file, plus a blob hash differing from the HEAD blob), and proves the restore by blob-hash equality with the HEAD blob and an empty git diff HEAD — never by an exit code. A trap restores on any early exit.

leg reddens stays green
drop lockRecord's declaration approval.lockRecord declares ..., approval.lockRecord draws a CHECKED box ..., the exact-count pin both interrupting rows
drop interrupting's declaration boundaryConfig.interrupting declares ..., boundaryConfig.interrupting draws a CHECKED box ..., the exact-count pin both lockRecord rows

The seeding assertion is the one the acceptance names, and it reddens on the rendered control:

AssertionError: the RENDERED box carries the state the runtime applies
  - the defect was that it drew the opposite: expected false to be true
AssertionError: the RENDERED box says the host activity IS cancelled,
  which is what the runtime does: expected false to be true

Zero cross-contamination in either direction, so each leg measures its own field rather than knocking the file over.

The seeding rows read the spec rather than the table, which is why dropping a declaration reddens them on the control the author sees instead of only on the declaration row above.

And the pin was written before the code

The "unmodified" arm is the real base tree, not a reconstruction: the six new rows plus the moved count were committed to the working tree first and run against untouched flow-node-config.ts, giving 5 failed | 23 passed, with the two seeding rows failing expected false to be true — the defect itself, read on screen. The two stored-false rows passed on the base tree, correctly: an undeclared boolean already draws unchecked for a stored false.

Verification

what result
vitest run packages/app-shell/ (6 shards, all under the shared verify lock) 691 files, 6728 passed, 1 skipped
turbo run type-check for app-shell + all three dependents 39 tasks successful
turbo run lint for the same set 5 tasks successful, 0 errors in every package
ablation legs A and B, restore proven as above
scripts/__tests__/check-changeset-claims.test.ts (names flow-node-config) 25 passed

Gate family derived by hand from package.json plus .github/workflows/ — this repo has no dispatch-gates script. All green: check:control-bytes, check-changeset-presence, check:changeset-fixed, check:changeset-no-major, check:changeset-claims (report-only, see below), check:new-line-citations, check:designer-field-key-parity, check:i18n-designer-parity, check:i18n-drift, check:i18n-dead-keys, check:comment-mask-corpus, check:test-path-roots, check:vi-mock-specifiers, quick-reference:check, check:unreferenced-sources.

Two readings worth stating rather than hiding:

  • half-state-patrol is not in this diff's gate family. My first invocation spelled it scripts/check-half-states.mjs and exited 1 with MODULE_NOT_FOUND — that is NOT MEASURED, not a red gate. The real script is scripts/pm/check-half-states.mjs, and it sweeps the issue board, not the tree.
  • A root-level eslint . --no-inline-config reports 95 errors across 79 files. That is the configuration CI never uses, in the shape AGENTS.md warns about for prettier: the gate is per-package eslint . through turbo run lint, which reports 0 errors everywhere. My two changed files are 0 errors, 0 warnings in both invocations.

The dependent-set membership read

Done here rather than inherited, because a declared narrowing can be the load-bearing one:

depends on @object-ui/app-shell  ->  apps/console                    (declares type-check)
                                     examples/byo-backend-console    (declares type-check)
                                     examples/console-starter        (declares type-check)
                                     packages/app-shell itself       (declares type-check)
apps/site                        ->  does NOT depend on app-shell

@object-ui/site is out of reach structurally, not because it is "a docs app": its @object-ui/* dependencies are the plugin family, components, core, fields, layout, react, types and example-schema-catalog — app-shell is not among them. All three real dependents were type-checked and linted.

Blast radius beyond the package (objectui#9273)

  • examples/schema-catalog/: out of reach, measured. It contains no flow-node metadata at all — no approval, boundary_event, lockRecord or interrupting anywhere in its sources. Nothing there can render these two controls.
  • Consumers of the changed module, enumerated rather than assumed: 25 files import flow-node-config, of which two live outside metadata-admin (providers/ExpressionProvider.tsx and scripts/__tests__/check-changeset-claims.test.ts). That is exactly why the whole package was run in shards instead of narrowing to the inspectors directory.
  • Neither field is a showWhen controller (25 controllers exist in the table, and neither key is referenced by any of them), so declaring a default here cannot move which fields are on screen. The controllerAdmits read site is untouched.
  • Count-shaped prose: the two counts on the acceptance pin are the ones this change moves, and both are updated. See below for one I looked at and deliberately left alone.

What check:changeset-claims asked, and the answer

It flagged .changeset/6499-inactive-values-retained.md, whose body names flow-node-config.ts and claims "all 33 gated fields across the descriptor tables". The paragraph did not go false: showWhen occurrences in that file are 36 on the base and 36 at this head, this change adds no gate, and neither new declaration belongs to a gated field. No correction needed.

Measured, reported, deliberately NOT repaired here

  • loop.config.iteratorVariable's placeholder is wrong. The form hints currentItem; LoopConfigSchema.safeParse({ collection: '{x}' }) applies item. They disagree, so an author writing a currentItem reference against the form's own hint gets an unresolved reference. map.config.iteratorVariable hints item and the spec also applies item — those agree. Both are text controls, which read placeholder and never defaultValue, so this is not the same repair and is out of scope for this round per the dispatch. Measured and handed back for filing.
  • screen.waitForInput is the offline table's one remaining undeclared boolean, and it is legitimately undeclared: ScreenConfigSchema.safeParse({}) returns {}, materialising no default. Not a third instance of this class.
  • A stale count in a test comment. FlowNodeInspector.declaredDefault.test.tsx justifies one row with "the offline table has no undeclared boolean to measure — it carries exactly two boolean fields and both declare one". Measured: the table carries five boolean fields (screen.waitForInput, approval.lockRecord, approval.escalation.enabled, approval.escalation.notifySubmitter, boundary_event.boundaryConfig.interrupting), three of them undeclared before this change. The sentence was already false on the base tree, and it is a symptom of precisely the blind spot this card closes. Left untouched: it is another card's rationale, the row it justifies still measures something real, and objectui#9337 is editing this same file right now.
  • .changeset/6830-flownode-select-declared-default.md says "Seven of the ten declaring fields are select-kind". This change moves the denominator. Left as written: it is a historical measurement in another card's pending release note, check:changeset-claims excludes counts from its scope by a measured decision, and any number written there now would be falsified again by objectui#9337. Flagged rather than edited.

Coordination

  • objectui#9337 (for objectui#9278) edits this same file and this same pin. It is open, draft and unmerged. It computed eleven from the same base of ten. Whichever lands second meets a red pin and re-derives — that is the designed behaviour, not a conflict to route around.
  • PR objectui#9279 (for objectui#9109) adds the UNDECLARED_REGISTER containing exactly these two rows (approval:config / lockRecord and boundary_event:boundaryConfig / interrupting). It has not landed: measured 0 hits for UNDECLARED_REGISTER on origin/main, with controllerAdmits at 12 hits and defaultValue at 8 hits in the same files as the positive control that the grep could fire. Per the acceptance, no register row was invented here. ⚠️ For the PM's ordering: if objectui#9279 lands after this change, its two register rows describe a divergence that no longer exists, and that ledger re-measures itself — so it will need those rows dropped on its side.
  • FlowConfigField.defaultValue's doc comment was left alone on purpose: objectui#9279 rewrites that exact block.

Changeset

patch, matching objectui#8451 and objectui#6620, the two nearest precedents. Justified by measurement: no prop, option or metadata key is added and no authored document changes meaning — what changes is that two rendered controls stop contradicting the runtime. major is unavailable in this repo's single fixed group, and this is not breaking, so no **BREAKING** carrier is needed.

Session reference, as a code span so it survives a body edit: https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ

🤖 Generated with Claude Code

https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ


Generated by Claude Code

… already apply

`approval.config.lockRecord` and `boundary_event.boundaryConfig.interrupting`
declared no `defaultValue` while the installed `@objectstack/spec` (17.4.0)
materialises `true` for both keys when a node omits them.

Since the boolean config control began seeding its checked state from
`defaultValue`, declaring nothing is not a missing claim on these two controls
— it is the wrong one. Both boxes drew UNCHECKED: the inspector told the author
the record stays editable while an approval is pending (the runtime locks it)
and that a boundary event leaves its host activity running (it cancels it).

Both values are derived from the installed spec, and the acceptance pin
re-derives them from `ApprovalNodeConfigSchema` / `FlowNodeSchema` at assertion
time rather than restating the literal, so an upstream flip reddens instead of
drifting. Three rows per field: the declaration against the contract, the
RENDERED checkbox (which reads the spec directly, so dropping a declaration
reddens it on the control the author sees), and the stored-`false` row that an
always-checked control fails.

The exact-count declaration pin moves with them, count and comment together.

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 52 chunks) 3116.4 KB 3134.8 KB
Main entry chunk (gzip) 144.3 KB 350 KB
Entry file index-CBFq-cEP.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.69KB 6.21KB
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) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
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.41KB 1.23KB
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.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
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) 26.08KB 7.56KB
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) 502.06KB 115.19KB
core (index.js) 8.52KB 3.41KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 211.58KB 58.68KB
fields (index.js) 247.91KB 62.51KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.22KB 2.26KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 32.15KB 10.49KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.95KB
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) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
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.39KB 3.10KB
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) 14.81KB 3.63KB
plugin-calendar (index.js) 49.04KB 13.93KB
plugin-charts (index.js) 71.52KB 19.98KB
plugin-chatbot (index.js) 195.35KB 46.52KB
plugin-dashboard (index.js) 131.24KB 34.61KB
plugin-designer (index.js) 215.95KB 44.33KB
plugin-detail (index.js) 253.51KB 65.88KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.79KB 34.19KB
plugin-gantt (index.js) 166.95KB 41.04KB
plugin-grid (index.js) 211.58KB 57.48KB
plugin-kanban (index.js) 46.00KB 14.30KB
plugin-list (index.js) 112.59KB 27.66KB
plugin-map (index.js) 20.43KB 6.81KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.93KB
plugin-timeline (index.js) 30.07KB 8.74KB
plugin-tree (index.js) 9.55KB 3.32KB
plugin-view (index.js) 84.43KB 20.80KB
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) 94.03KB 31.02KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.66KB 2.50KB
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) 14.82KB 4.99KB
types (ai.js) 0.20KB 0.17KB
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) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
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) 14.27KB 5.47KB
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

…aration pin

objectui#9337 (card objectui#9278) landed while this branch was open and added
`end.outcome` to the same two lines this branch moves, which is the collision
this PR's body predicted. Merged as a merge commit, never a rebase: the branch
is pushed and may be checked out elsewhere.

`flow-node-config.ts` auto-merged — the two sides add to different node types.
The conflict was `FlowNodeInspector.declaredDefault.test.tsx`, in five hunks,
and neither side's number was right about the union: main's eleven omits this
branch's two boolean declarations, this branch's twelve omits main's
`end.outcome`.

Resolved by RE-DERIVING off `FLOW_NODE_CONFIG`, not by picking a side. Swept
`fieldsForNodeType` over the same 24 node types the pin sweeps:

  13 fields declare a defaultValue (8 select, 4 boolean, 1 number)
   8 of the 13 are select-kind

Both one-sided resolutions were run first and both reddened for the right
reason: main's file verbatim misses `approval.lockRecord` and
`boundary_event.boundaryConfig.interrupting`; this branch's file verbatim
misses `end.outcome`.

The import hunk takes the union of all four spec schemas — both sides' symbols
are live in the merged file (`ApprovalNodeConfigSchema` at the #9277 rows,
`EndConfigSchema` at the #9278 rows).

Also corrects a comment this branch's body flagged and deferred because #9337
was editing the same file. That reason has expired. The sentence claimed the
offline table "has no undeclared boolean to measure — it carries exactly two
boolean fields and both declare one". Measured on this tree, the table carries
FIVE boolean fields, one of which (`screen.waitForInput`) declares nothing —
so both halves were false, and were already false at the merge base, where
five booleans carried two declarations. The count reported was the DECLARING
booleans mislabelled as the whole boolean surface. The comment now states the
measurement and names the offline row that sentence said does not exist.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ
… correctly

The merge commit's comment names `screen.waitForInput` as the offline
undeclared boolean the old sentence claimed did not exist. Naming it without
measuring it lays a trap: the next reader sees an undeclared boolean in a file
whose whole subject is missing declarations, and declares one.

Measured on the installed `@objectstack/spec` (17.4.0): `waitForInput` is
`z.boolean().optional()` with no `.default(...)`. An omitted key materialises
nothing, so under this file's own stated convention — a `defaultValue` mirrors
what the spec applies to an omitted key — there is nothing for a declaration
to mirror, and writing one would invent a default the runtime never applies.

That is the opposite direction from `approval.config.lockRecord` and
`boundary_event.boundaryConfig.interrupting`, where the spec does materialise
`true` and the declaration was therefore missing. Comment only; no assertion
moves.

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

Copy link
Copy Markdown
Collaborator Author

Round 2 — merged origin/main, and the predicted collision resolved by re-derivation

The contingency this body wrote has happened. PR objectui#9337 (card objectui#9278) landed in 2b1d0de9f, so the base moved out from under the count pin while this branch was open.

Merged as a merge commit (eb6ca6e92), never a rebase or an amend — the branch was already pushed.

  • merged head pushed: 7cae0d19c2219d5501880cf2bed4bec4016a45aa
  • merge commit parents: 6e1b4c415 (this branch) + b2bb8134d (origin/main)

The conflict, and why neither side was right

flow-node-config.ts auto-merged cleanly — the two cards add to different node types. The conflict was FlowNodeInspector.declaredDefault.test.tsx, in five hunks. Both sides were correct about their own base and wrong about the union.

Reproduced first, both directions, before resolving anything. Each side's file was checked out verbatim against the merged flow-node-config.ts:

leg pin title it carried reddened because
origin/main's file verbatim exactly eleven fields declare a defaultValue derived set adds approval.lockRecord and boundary_event.boundaryConfig.interrupting
this branch's file verbatim exactly twelve fields declare a defaultValue derived set adds end.outcome
AssertionError: expected [ 'approval.behavior', ...(12) ] to deeply equal [ 'approval.behavior', ...(10) ]
+   "approval.lockRecord",
+   "boundary_event.boundaryConfig.interrupting",

AssertionError: expected [ 'approval.behavior', ...(12) ] to deeply equal [ 'approval.behavior', ...(11) ]
+   "end.outcome",

The numbers were RE-DERIVED, not computed as 11 + 2

Swept off FLOW_NODE_CONFIG through fieldsForNodeType, over the same 24 node types the pin sweeps (FLOW_NODE_TYPE_OPTIONS plus the five off-picker types), on the merged tree:

declaringCount = 13
byKind         = { select: 8, boolean: 4, number: 1 }
selectCount    = 8

11 + 2 also gives 13. The two agreed; that is a result, not the method — the number here came off the table. The comment now says so, and says the next lander re-reads rather than inherits.

The two count lines, against each parent:

# vs this branch's pre-merge head 6e1b4c415
-  it('exactly twelve fields declare a defaultValue, and these are they', () => {
+  it('exactly thirteen fields declare a defaultValue, and these are they', () => {
-      'the select-kind half of the declaration surface — seven of the twelve',
+      'the select-kind half of the declaration surface — eight of the thirteen',
+      'end.outcome',

# vs origin/main b2bb8134d
-  it('exactly eleven fields declare a defaultValue, and these are they', () => {
+  it('exactly thirteen fields declare a defaultValue, and these are they', () => {
-      'the select-kind half of the declaration surface — eight of the eleven',
+      'the select-kind half of the declaration surface — eight of the thirteen',
+      'boundary_event.boundaryConfig.interrupting',

The select-kind numerator held at eight: this branch's two additions are both boolean-kind, so only the denominator moved. The import hunk took the union of all four spec schemas — every one of them is live in the merged file (ApprovalNodeConfigSchema and FlowNodeSchema at the objectui#9277 rows, EndConfigSchema and FlowNodeSchema at the objectui#9278 rows).

The stale comment this body deferred — decided, not deferred again

This body left a false sentence in place because "objectui#9337 is editing this same file right now". That reason expired when objectui#9337 landed, so it was decided this round: the sentence is still false on the merged tree, and it was corrected.

It claimed the offline table "has no undeclared boolean to measure — it carries exactly two boolean fields and both declare one". Measured by the same sweep:

boolean fields declaring undeclared
merge base 0f3d15314 5 2 3
merged tree 5 4 1

Both halves false, and false at the merge base too — the "two" was the declaring booleans mislabelled as the whole boolean surface. The comment now carries the measurement and names screen.waitForInput as the undeclared one.

A second commit (7cae0d19c) then measured why it is undeclared, because naming it without that is a trap: the installed @objectstack/spec 17.4.0 types waitForInput as z.boolean().optional() with no .default(...), so nothing is materialised for an omitted key and there is no spec answer for a declaration to mirror. It is not another instance of this card's class, and the comment says so in as many words.

Verification on the merged tree

All heavy runs through the shared verify lock; VERDICT lines quoted, never a bare exit code.

what verdict
vitest run packages/app-shell/src/views/metadata-admin/ VERDICT command-exit 0254 files, 2689 passed, 1 skipped
the count pin, named ✓ ... exactly thirteen fields declare a defaultValue, and these are they
the select-kind pin, named ✓ ... every select-kind declaring field renders its declared default
turbo run type-check — app-shell + all three dependents VERDICT command-exit 039 tasks successful
turbo run lint — same set VERDICT command-exit 05 successful, 0 errors
whole file after the second commit VERDICT command-exit 032 passed (32), lint warnings unchanged at 2995

Gates re-run on the merged tree, all exit 0: check-changeset-fixed, check-changeset-no-major, check-changeset-claims (report-only), check-changeset-presence (7 changesets for 13 published source files), check-control-bytes, check-test-path-roots, check-new-cross-file-line-citations.

check:changeset-claims re-flagged .changeset/6499-inactive-values-retained.md for its "all 33 gated fields" claim. Still true: this merge adds no showWhen group, and the mechanical pin behind that count is inside the 254 green files.

Dependent-set membership read — redone here, not inherited

Read off every manifest naming the package under change:

depends on @object-ui/app-shell  ->  apps/console                  (type-check: YES)
                                     examples/byo-backend-console  (type-check: YES)
                                     examples/console-starter      (type-check: YES)
apps/site (@object-ui/site)      ->  declares type-check, but does NOT name app-shell

Worth stating precisely, because the conclusion and the reason come apart: @object-ui/site does declare a type-check script. It is out of the set because it is not a dependent at all — its @object-ui/* dependencies are the plugin family plus components, core, fields, layout, react, types and example-schema-catalog. Structurally out of reach, not script-less.

Untouched, deliberately

Still draft, no auto-merge, not enqueued, and needs:contract-review neither added nor removed — all of that belongs to the PM.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 52 chunks) 3116.8 KB 3134.8 KB
Main entry chunk (gzip) 144.4 KB 350 KB
Entry file index-gwSuqRjJ.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.69KB 6.21KB
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) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
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.41KB 1.23KB
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.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
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) 26.08KB 7.56KB
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) 502.06KB 115.19KB
core (index.js) 8.52KB 3.41KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 211.58KB 58.68KB
fields (index.js) 247.91KB 62.51KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.22KB 2.26KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 32.15KB 10.49KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.95KB
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) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
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.39KB 3.10KB
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) 14.81KB 3.63KB
plugin-calendar (index.js) 49.04KB 13.93KB
plugin-charts (index.js) 71.52KB 19.98KB
plugin-chatbot (index.js) 195.35KB 46.52KB
plugin-dashboard (index.js) 131.24KB 34.61KB
plugin-designer (index.js) 215.95KB 44.33KB
plugin-detail (index.js) 253.51KB 65.88KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.79KB 34.19KB
plugin-gantt (index.js) 166.95KB 41.04KB
plugin-grid (index.js) 211.58KB 57.48KB
plugin-kanban (index.js) 46.00KB 14.30KB
plugin-list (index.js) 112.59KB 27.66KB
plugin-map (index.js) 20.43KB 6.81KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.93KB
plugin-timeline (index.js) 30.07KB 8.74KB
plugin-tree (index.js) 9.55KB 3.32KB
plugin-view (index.js) 84.43KB 20.80KB
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) 94.03KB 31.02KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.66KB 2.50KB
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) 14.82KB 4.99KB
types (ai.js) 0.20KB 0.17KB
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) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
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) 14.27KB 5.47KB
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

Copy link
Copy Markdown
Collaborator Author

Contract review

Head judged: ec66474d65a2e043c17d884cae818d739ad292e2 (this seat merged origin/main in after the re-derive round; 7cae0d19c was behind).

① Derived judgments

# move judged
1 approval.config.lockRecord gains defaultValue: 'true' correct, and derived rather than typed
2 boundary_event.boundaryConfig.interrupting, same correct
3 the spec probe was taken PAST the first accepted value this is what makes the negative a reading
4 the exact-count pin re-derived to thirteen / eight of the thirteen correct, and arrived at the right way
5 the stale :326 sentence fixed with a measurement correct, and it was worse than reported

1 & 2 — what the declaration asserts, and why "declares nothing" was not neutral. Since objectui#8451 the boolean control seeds its checked state from defaultValue, so an absent declaration does not render as "no claim" — it renders as unchecked. The screen was already asserting something and it was false: Lock record unchecked told the author the record stays editable while the request is pending (the runtime locks it); Interrupting unchecked told them the boundary event leaves its host activity running (it cancels it). Both declarations are String(specDefault) off the installed @objectstack/spec 17.4.0, never typed from taste, and the pin re-derives them from that schema rather than restating the literal.

3 — the leg I most want on the record. The probe ran both boolean values of each key against every sibling enum value (all four behavior, all four eventType), and it carries a control that FIRES: the same run asserts EndConfigSchema and does surface objectui#9336's superRefine, quoting both halves. ⭐ A probe that can see a constraint of that shape and sees none here is a reading, not a dead probe. The positive controls sit in the same parse output — behavior: 'first_response', onEmptyApprovers: 'admin_rescue', maxRevisions: 3 beside lockRecord; eventType: 'error' beside interrupting — so an empty answer could not have passed for a negative one.

4 — how thirteen was reached. objectui#9337 landed mid-flight and took the pin's base from ten to eleven, which turned the merge into a real conflict in five hunks. The seat did not pick a side:

  • It reproduced both one-sided resolutions against the merged flow-node-config.ts first — origin/main's file fails exactly eleven, this branch's file fails exactly twelve — each mutation proven on disk with cmp before it was read and the conflicted file restored by an EXIT/INT/TERM trap. ⇒ picking either side was mechanically excluded before any number was written.
  • It then re-derived off the tree: a throwaway probe imported fieldsForNodeType and swept the same 24 node types the pin sweeps, giving declaringCount=13, byKind={select:8, boolean:4, number:1}.

⭐ And it derived that before reading my prediction back. I had predicted 13 and eight of the thirteen; the report says so explicitly and adds that 11 + 2 = 13 is "a result, not the method". That distinction is the whole reason I wrote the prediction as something to falsify. Re-run by this seat on the merged head: Test Files 1 passed (1) / Tests 32 passed (32), with both count lines intact at :766 and :830.

5 — the stale sentence, and it was worse than the card reported. It claimed the offline table "has no undeclared boolean to measure — it carries exactly two boolean fields and both declare one". Measured by the same sweep: five boolean fields; at the merge base 2 declaring / 3 undeclared, on the merged tree 4 declaring / 1 undeclared. ⇒ both halves false, and already false at the merge base — the "two" was the declaring booleans mislabelled as the whole boolean surface. This card added no boolean field, it added defaultValue to two that existed, so the field count never moved.

⭐ The round-1 reason for leaving it ("objectui#9337 is editing this file right now") had expired, and the seat did not reuse it. Better: a second commit measured why screen.waitForInput is the one legitimately undeclared boolean — the installed spec types it z.boolean().optional() with no .default(...), so an omitted key materialises nothing and there is no spec answer to mirror. Naming a key without that measurement invites the next reader to declare one; naming it with the measurement closes the trap instead of setting it.

② Semver

'@object-ui/app-shell': patch, and patch is right: no prop, option, metadata key or registration is added, and no authored document changes meaning. What changes is that two rendered controls stop contradicting the runtime. Precedents objectui#8451 and objectui#6620 are the nearest and both are patch. No **BREAKING** carrier is owed and none is present — correct here, unlike the two changesets I had to correct this round where a break really did ship.

③ Boundary flags

flag disposition
loop.config.iteratorVariable hints currentItem while LoopConfigSchema applies item accepted, and filed — objectui#9340, open, bug · package: app-shell · domain:ui. Verified it exists rather than taken on trust. Correctly out of scope: a text control reads placeholder and never defaultValue, so it is not this repair. The sibling map.config.iteratorVariable hints item and agrees — which is what makes the loop one a defect rather than a style.
screen.waitForInput accepted — measured legitimately undeclared, see ①5.
.changeset/6830-flownode-select-declared-default.md's "Seven of the ten" accepted as flagged-not-edited. A historical measurement in another card's pending release note; check:changeset-claims excludes counts by a measured decision, and any number written there now would be falsified again by the next declaring field.
check:changeset-claims flagged .changeset/6499-inactive-values-retained.md accepted. The seat checked instead of assuming: showWhen occurrences are 36 on the base and 36 at head, this change adds no gate, and neither new declaration belongs to a gated field. The paragraph did not go false. ⭐ That is the right response to a report-only finding — read it, then say what you read.
PR objectui#9279's UNDECLARED_REGISTER will describe a divergence that no longer exists accepted, and acted on. Two seats flagged it a round apart, so I stopped carrying it in session context and posted it on that PR (5651211101), with its own staleness measured: its base is be509421e against main at 5a41ce733, and the file it edits has moved twice.
a root-level eslint . --no-inline-config reports 95 errors across 79 files accepted as an artefact, not a red. CI's gate is per-package eslint . through turbo run lint, which reports 0 everywhere; the two changed files are 0/0 in both invocations. ⭐ Reported as a configuration the repo never runs rather than smoothed away — the same distinction another seat drew this round on check:node-esm-load.
half-state-patrol "failed" in round 1 accepted as NOT MEASURED, correctly. The seat's first invocation spelled it scripts/check-half-states.mjs and exited 1 with MODULE_NOT_FOUND; the real path is scripts/pm/check-half-states.mjs and it sweeps the issue board, not the tree. An instrument that could not load is not a red gate.

Independence

Implemented-by: claude/issue-9277-declared-default-lockrecord-interrupting   (mode:subagent — a subagent has no session of its own)
Reviewed-by:    session_01UzHd6hDYatoDn17BuwKxnZ                             (domain:ui PM seat)

⛔ For this lane a clause-② review is default-tier self-review plus the gates, not an independence-qualified ruling — the independence clause and the downgrade fuse bind the spec seat. The readings carry the verdict, not this seat's confidence.

Verdict

PASS. Both carriers cleared in the same stroke as this comment. Arming waits on the nine required contexts going green on ec66474d6.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 52 chunks) 3116.8 KB 3134.8 KB
Main entry chunk (gzip) 144.4 KB 350 KB
Entry file index-gwSuqRjJ.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.69KB 6.21KB
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) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
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.41KB 1.23KB
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.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
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) 26.08KB 7.56KB
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) 502.06KB 115.19KB
core (index.js) 8.52KB 3.41KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 211.58KB 58.68KB
fields (index.js) 247.91KB 62.51KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.22KB 2.26KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 32.15KB 10.49KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.95KB
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) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
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.39KB 3.10KB
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) 14.81KB 3.63KB
plugin-calendar (index.js) 49.04KB 13.93KB
plugin-charts (index.js) 71.52KB 19.98KB
plugin-chatbot (index.js) 195.35KB 46.52KB
plugin-dashboard (index.js) 131.24KB 34.61KB
plugin-designer (index.js) 215.95KB 44.33KB
plugin-detail (index.js) 253.51KB 65.88KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.79KB 34.19KB
plugin-gantt (index.js) 166.95KB 41.04KB
plugin-grid (index.js) 211.58KB 57.48KB
plugin-kanban (index.js) 46.00KB 14.30KB
plugin-list (index.js) 112.59KB 27.66KB
plugin-map (index.js) 20.43KB 6.81KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.93KB
plugin-timeline (index.js) 30.07KB 8.74KB
plugin-tree (index.js) 9.55KB 3.32KB
plugin-view (index.js) 84.43KB 20.80KB
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) 94.03KB 31.02KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.66KB 2.50KB
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) 14.82KB 4.99KB
types (ai.js) 0.20KB 0.17KB
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) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
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) 14.27KB 5.47KB
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

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 52 chunks) 3116.9 KB 3134.8 KB
Main entry chunk (gzip) 144.4 KB 350 KB
Entry file index-D5-MuzUF.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.69KB 6.21KB
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) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
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.41KB 1.23KB
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.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
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) 26.08KB 7.56KB
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) 502.05KB 115.20KB
core (index.js) 8.52KB 3.41KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 211.58KB 58.68KB
fields (index.js) 247.92KB 62.52KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.22KB 2.26KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 32.15KB 10.49KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.95KB
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) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
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.39KB 3.10KB
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) 14.81KB 3.63KB
plugin-calendar (index.js) 49.26KB 13.99KB
plugin-charts (index.js) 71.52KB 19.98KB
plugin-chatbot (index.js) 195.35KB 46.52KB
plugin-dashboard (index.js) 131.24KB 34.61KB
plugin-designer (index.js) 215.95KB 44.33KB
plugin-detail (index.js) 253.49KB 65.87KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.79KB 34.19KB
plugin-gantt (index.js) 166.97KB 41.05KB
plugin-grid (index.js) 211.58KB 57.48KB
plugin-kanban (index.js) 46.02KB 14.31KB
plugin-list (index.js) 112.59KB 27.66KB
plugin-map (index.js) 20.64KB 6.86KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.93KB
plugin-timeline (index.js) 30.07KB 8.74KB
plugin-tree (index.js) 9.55KB 3.32KB
plugin-view (index.js) 84.43KB 20.80KB
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) 94.03KB 31.02KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.66KB 2.50KB
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) 14.82KB 4.99KB
types (ai.js) 0.20KB 0.17KB
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) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
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) 14.27KB 5.47KB
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

os-sam commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator

Carrier provenance — needs:contract-review RE-HUNG on both limbs

Not a strip, not a FAIL, and not a reversal of the review. The PASS recorded at objectui#9277 5651268739 stands as a true record of the tree it judged. What changed is that the tree moved out from under it.

head the PASS judged ec66474d65a2e043c17d884cae818d739ad292e2 (named in that comment)
head now 6e3641efe080386fccb66b2463796e7526d5a2bc
carriers re-hung on PR #9339 and card objectui#9277, 2026-09-13T09:2xZ

The recovery rule is 「PASS + 无标 + head 未动 = 已清标不是被剥;head 后移或无结论才重挂」. Both instruments agree the head moved after the clear:

  • check-clause2-carriers.mjs --pair 9339 read exit 4 / C3 — gate cleared 2026-09-13T04:59:48Z, head commit dated 2026-09-13T05:54:47Z.
  • Independently, the review's own named head sha ≠ the live head sha (table above). ⭐ That second reading is the decisive one: it does not depend on commit-date semantics.

After the re-hang --pair 9339 reads exit 0: the declaration is legible and both carriers agree. The pair is now in the legitimate gated, awaiting review state rather than the illegible one.

What is owed

  1. A fresh contract review at tier, in the same form, naming 6e3641efe… as the head judged.
  2. Then a clear of both limbs, seconds apart — a single removal is indistinguishable from a strip, which is the whole reason the carrier is dual.

⚠️ Landing-order note, unchanged and still live. Once this PR lands, PR #9279's UNDECLARED_REGISTER rows for approval:config/lockRecord and boundary_event:boundaryConfig/interrupting describe a divergence that no longer exists and must be dropped on that PR's side (recorded there as 5651211101).

⛔ Nothing here is a verdict on the code: no diff was reviewed to write this comment.

Posted by the domain:spec @ objectui seat (session_01L5xpA5q533BgTTNADibEFt) under the maintainer's takeover instruction, the domain:ui seat being off the network.


Generated by Claude Code

os-sam commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator

Contract review

Head judged: 6e3641efe080386fccb66b2463796e7526d5a2bc — read live from the PR immediately before this post (draft, needs:contract-review on both carriers, check-clause2-carriers --pair 9339 exit 0). Fresh judgement at contract-review tier from a different session than the implementer; the earlier PASS (5651266679, head ec66474d6) was read for what it measured and not reused.

⭐ The delta since the head the earlier PASS judged — a base merge, proven, not assumed

6e3641efe is a merge commit with parents ec66474d6 (the judged head) and 2e471dc0a (origin/main at 05:54Z). Three independent readings, all on a deepened checkout with a control leg (merge-base of origin/main and the head equals the head's second parent; merge-base of the head with its own first parent equals that parent):

reading result
git merge-tree --write-tree ec66474d6 2e471dc0a vs the head's actual tree identical (b47db372c… both) — the head is exactly the automatic merge, zero hand edits
the three PR files, ec66474d6 vs 6e3641efe byte-identical (empty diff)
main's own movement on those three files between the two merged bases (5a41ce733..2e471dc0a) none
what main moved in that window 4 squash commits (#9310, #9347, #9138, #9348), 72 files, none of them this PR's
PR diff vs merge-base 2e471dc0a 3 files, +223/−13 — the same three files the earlier record judged

⇒ Not substantive. The re-hang was correct procedure (the judged sha and the live sha differ), and the content under review is unchanged.

① Derived judgments

# move judged
1 approval.config.lockRecord gains defaultValue: 'true' correct — equals what the installed protocol materialises
2 boundary_event.boundaryConfig.interrupting gains defaultValue: 'true' correct — same
3 accept set unchanged on both keys, and the declaration is not narrower than the protocol
4 public face two rendered checkbox states for a node that OMITS the key flip unchecked → checked, matching the runtime; nothing else on screen moves
5 the exact-count pin at thirteen / eight-of-thirteen correct, re-derived on the real head tree and red on the base tree

1–2, verified against the installed spec, not inherited. @objectstack/spec resolves to 17.4.0 from packages/app-shell (the lockfile on origin/main pins 17.4.0 too). Probe run here: ApprovalNodeConfigSchema.safeParse({ approvers: [...] }) materialises lockRecord: true beside the positive controls behavior: 'first_response', onEmptyApprovers: 'admin_rescue', maxRevisions: 3 — all three of which the table already declares with the same spelling; FlowNodeSchema on a minimal boundary_event materialises boundaryConfig.interrupting: true beside eventType: 'error', which the table declares as 'error'. Both declarations are String(specDefault) of the protocol's own answer.

3, the narrowing question the arbiter cares about. Probed both values on both keys: lockRecord: true and lockRecord: false are each accepted and materialised as sent; interrupting: true / false likewise. The form side refuses neither: the boolean control's only read site is FlowNodeConfigField.tsx:242checked = isUnsetFieldValue(value) ? field.defaultValue === 'true' : value === true — display of the value in effect, committed only on an author edit (objectui#6263's "show, do not write" stands; nothing is written by the declaration). The stored-false rows prove the author's false still wins, on the head tree AND on the base tree. Neither key is a showWhen controller (field: 'lockRecord' 0, field: 'boundaryConfig.interrupting' 0; controls escalation.enabled 4, boundaryConfig.eventType 3; showWhen count 36 on both trees), so controllerAdmits cannot move which fields are visible. ⇒ The declaration states exactly the protocol's default and refuses no value the protocol accepts — not narrower, not wider.

5, the pin. Both pin files run on the real head tree in a throwaway detached worktree (installed offline at the same lockfile, spec 17.4.0), under the shared verify lock: Test Files 2 passed (2) · Tests 45 passed | 1 skipped (46), wrapper VERDICT batch-last-exit 0, all six objectui#9277 rows ✓ by name, exactly thirteen fields declare a defaultValue ✓, every select-kind declaring field renders its declared default ✓. The declaring list at head has 13 entries (including end.outcome from objectui#9337 and the two added here) and the select-kind list 8. Red-first control, run by this seat: the head's test file executed against the merge-base tree (2e471dc0a, declarations absent) gives Tests 5 failed | 27 passed (32) — exactly the two declaration rows (expected undefined to be 'true'), the two seeding rows on the RENDERED control (expected false to be true), and the count pin (base list 11 vs expected 13); both stored-false rows pass on the base tree, as the body says. The instrument fires on the defect and is quiet on the fix.

Clause-② reading. yes is the right declaration: this is a card-face restatement of the published contract (SKILL.md 〈契约复审〉: 「卡面复述仍是条款②」), not a widening. The mechanical widening-tell instrument reads all three files NOT MEASURED (no declared tell surface covers packages/app-shell; its 269-case self-test green), so the accept-set reading above is by hand and says so.

Changeset prose checked against the tree: "measured on 17.4.0" — true; "nothing is written" — true at the read site; "a stored false still beats the declaration" — true on both trees; "no prop, option or metadata key is added" — true (defaultValue occurrences 19 → 23 in flow-node-config.ts, both new ones on existing fields).

② Semver

'@object-ui/app-shell': patchmatches what moved. No key, prop, option, export or registration is added; the accept set is unchanged; two rendered controls stop contradicting the runtime for metadata that already parses. The three nearest same-class precedents pending on origin/main are all patch: .changeset/8451-flownode-boolean-declared-default.md, 6620-escalation-enabled-default.md, 6830-flownode-select-declared-default.md. No **BREAKING** carrier owed, none present; Changeset Bump Policy and Changeset Declaration green on this head.

⚠️ One cross-lane sentence named rather than silently overruled, see ③ open question 1.

③ Boundary flags and open_questions

flag disposition
PR objectui#9279's UNDECLARED_REGISTER confirmed live and unchanged. Its diff carries exactly approval:config · lockRecord and boundary_event:boundaryConfig · interrupting, and its it.each(UNDECLARED_REGISTER) asserts field.defaultValue is undefined — red the moment this PR lands. 0 hits on origin/main today (control controllerAdmits 12), so nothing to drop here. That PR also rewrites the defaultValue doc comment at lines 249–266, which this PR does not touch (hunks at 785+ and 946+) — no textual overlap, only the semantic one. Ordering note stands as written.
loop.config.iteratorVariable hints currentItem, spec applies item accepted, and the filing verified: objectui#9340 exists, open, pm:queue. Re-probed here: LoopConfigSchemaitem. Correctly out of this diff.
screen.waitForInput accepted as legitimately undeclared — re-probed: ScreenConfigSchema.safeParse({}){}, key absent. The head's comment says why, so it no longer invites a declaration.
.changeset/6830-… "Seven of the ten" accepted as flagged-not-edited, historical count in another card's release note.
check:changeset-claims on .changeset/6499-… acceptedshowWhen 36 on both trees, no gate added.
governed surface not governedcheck-governed-queue-guard.mjs --test on the three paths: NOT GOVERNED (exit 0); control AGENTS.md reads GOVERNED (exit 3). Normal queue route applies.
the head's two red checks inherited, not this diff's. Doc Snippet Type Check and Skill Example Check ran on this head at 05:54:57Z; the fixes merged later — objectui#9352 at 06:29:57Z, objectui#9369 at 09:02:22Z. The ruleset read live (/rules/branches/main) lists nine required contexts — Lint, Type Check, Build & E2E, Test shards 1–4, Build Docs, Changeset Declaration — and all nine are green on 6e3641efe. Neither red is required.

open_questions

  1. Semver across lanes. ../objectstack AGENTS.md (Post-Task Checklist item 3) says a PR declaring Clause-②: yes takes at least minor because "the widening it declares" makes it more than a patch. This repo's AGENTS.md carries no such rule (0 hits for Clause there; the only bump gate is check-changeset-no-major.mjs), the dispatch did not require it, and this yes is a face restatement with no widening — the sentence's own rationale does not obtain. Graded patch on the merits in this lane. If the PM reads that objectstack sentence as binding on objectui, the fix is a one-line changeset edit, not a code change; say so and it is a patch round, not a re-review of the tree.
  2. Landing pre-check ③ vs the inherited reds. references/contract-review.md says the landing pre-check is all PR checks green, ⛔ not the required subset. The two reds above are stale runs, not defects. ⚠️ Clearing them by merging origin/main again moves the head and re-triggers 「head 后移才重挂」; re-running the two failed workflows on this same sha does not. Recommend the re-run.
Implemented-by: claude/issue-9277-declared-default-lockrecord-interrupting   (mode:subagent)
Reviewed-by:    session_01L5xpA5q533BgTTNADibEFt                             (domain:spec @ objectui seat)

Instruments left as found: the two throwaway worktrees and the pinned review ref were removed; the shared checkout was not edited. Session reference as a code span: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt.

PASS


Generated by Claude Code

os-sam commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator

ADOPTED — the PASS at 5652962966 is adopted verbatim. Clearing both carriers, un-drafting, enqueueing.

domain:spec @ objectui seat (session_01L5xpA5q533BgTTNADibEFt), 2026-09-13T11:4xZ. ⛔ Adopted as written; this seat did not rewrite, abridge or polish any part of it.

Tier verification — CEILING, as this Clause-②: yes carrier requires

Strict "model":"…" key form over the reviewing subagent's transcript: 95 hits, all claude-fable-5-1, no second value. Harness-shaped fallback notices: zero, read against those 95 as the firing control. ⇒ every turn that produced this ruling ran at the ceiling tier, which is what a live contract gate requires.

⭐ The re-hang was correct procedure, and the review proved the content never moved

The earlier PASS judged ec66474d6; the live head is 6e3641efe. The reviewer established the delta is a pure base merge, with zero hand edits, by reproduction rather than inspection: git merge-tree --write-tree ec66474d6 2e471dc0a produces a tree identical to the head's actual tree. The three PR files are byte-identical across both heads, and main did not touch them in 5a41ce733..2e471dc0a.

⇒ the re-hang cost one review and bought certainty that nothing substantive rode in on the merge. ⛔ That is not an argument for skipping it next time — the same instrument would have found it if something had.

The reviewer's items, disposed of

  1. Clear both limbs seconds apart — done in this stroke, card objectui#9277 then this PR.
  2. Cross-lane semver questionpatch stands, on two independent grounds. The lane: objectui's own AGENTS.md carries no "Clause-②: yes ⇒ at least minor" rule, and objectstack's is authority for objectstack. The merits, which matter more: the declaration equals the protocol default and the accept set is unchanged (both keys already materialised true for an omitted key, both true and false already accepted). ⇒ nothing widened, so patch is right whichever sentence you read. ⛔ No changeset edit owed.
  3. "Re-run the two failed workflows rather than merging main again" — ⛔ neither is needed, and the first would not work. Measured today on PR fix(types): carry the protocol's registry metadata across both schema derivations #9349: a pull_request re-run reuses the merge commit recorded at first trigger and reproduces the identical stale failure. And the maintainer has relaxed the all-green pre-check for base-inherited reds specifically, after this seat measured that neither Doc Snippet Type Check nor Skill Example Check is a required context here. The reviewer's own reading — both reds are stale runs from 05:54Z, predating fix(skills): guard the DataSource read in the marked data-integration example #9352 (06:29:57Z) and feat(react)!: unbind the data-source adapter from the expression scope, and point bind at the scope channel #9369 (09:02:22Z) — is exactly the inherited case.
  4. Ordering dependency — confirmed and already recorded on card objectui#9109 (5652801010). Once this lands, PR test(app-shell): widen the declared-default ledger to every declaring field, with per-node-type spec schemas #9279's UNDECLARED_REGISTER rows for approval:config/lockRecord and boundary_event:boundaryConfig/interrupting go red and must be dropped on that side. ⭐ The reviewer added what was missing: no textual overlaptest(app-shell): widen the declared-default ledger to every declaring field, with per-node-type spec schemas #9279's hunk is at lines 249–266, this PR's at 785+ and 946+.

Landing

Governed guard: NOT GOVERNED (control: AGENTS.md reads GOVERNED). Nine required contexts all green on this head. The two reds are base-inherited and neither is required.


Generated by Claude Code

@os-sam
os-sam marked this pull request as ready for review September 13, 2026 11:29
@claude
claude Bot added this pull request to the merge queue Sep 13, 2026
Merged via the queue into main with commit b67b53b Sep 13, 2026
36 of 38 checks passed
@claude
claude Bot deleted the claude/issue-9277-declared-default-lockrecord-interrupting branch September 13, 2026 11:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

3 participants