Repository navigation
chore(objectui): bump the console pin to f8a9d0fb0596 (carries objectui#10221 and the objectui#9910 re-cut) with re-measured pin citations and the lockstep re-record - #20036
Conversation
…ui#10221 0651e7ab4 and the objectui#9910 re-cut 5ea623ea) Claude-Session: https://claude.ai/code/session_01BMUTTbeXStLRWxU9zEWDJq Co-authored-by: Claude <noreply@anthropic.com>
…migration-entry pin citations at objectui f8a9d0fb0 Claude-Session: https://claude.ai/code/session_01BMUTTbeXStLRWxU9zEWDJq Co-authored-by: Claude <noreply@anthropic.com>
…n pin citations at objectui f8a9d0fb0 Claude-Session: https://claude.ai/code/session_01BMUTTbeXStLRWxU9zEWDJq Co-authored-by: Claude <noreply@anthropic.com>
…ns at objectui f8a9d0fb0 Claude-Session: https://claude.ai/code/session_01BMUTTbeXStLRWxU9zEWDJq Co-authored-by: Claude <noreply@anthropic.com>
…easure-format pin citations at objectui f8a9d0fb0 Claude-Session: https://claude.ai/code/session_01BMUTTbeXStLRWxU9zEWDJq Co-authored-by: Claude <noreply@anthropic.com>
…and FormField.span docblock at objectui f8a9d0fb0 Claude-Session: https://claude.ai/code/session_01BMUTTbeXStLRWxU9zEWDJq Co-authored-by: Claude <noreply@anthropic.com>
…parser lockstep at f8a9d0fb0596 Claude-Session: https://claude.ai/code/session_01BMUTTbeXStLRWxU9zEWDJq Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 4 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 137 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 6688d84f1c770438c344d574e5548dde427483e1 && git checkout 6688d84f1c770438c344d574e5548dde427483e1
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 246314dffef9af072b6172d0f1f9678fb7363dec b4d386f23c514356f155589a8f7ae20cee640c45 && git checkout -B drift-repro 246314dffef9af072b6172d0f1f9678fb7363dec && git merge --no-ff b4d386f23c514356f155589a8f7ae20cee640c45
node scripts/docs-audit/affected-docs.mjs --json 246314dffef9af072b6172d0f1f9678fb7363dec
|
…i f8a9d0fb0596 and regenerate view.mdx Claude-Session: https://claude.ai/code/session_01BMUTTbeXStLRWxU9zEWDJq Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: ① Derived judgments
② Semver level
Both changesets follow precedent in shape:
The manifest and lockstep files are tracked artefacts, not published packages, so no further changeset is owed. ③ Boundary flags
Implemented-by: VERDICT: PASS Land through the queue once it is out of draft; no rework is owed. Conditions:
Isolated at-tier reviewer, adopted by the Generated by Claude Code |
|
Regen-provenance: os-regen-merge.sh · 90f9a30 → b4d386f Landing step per the seat's comment What the hop is: one merge commit Step 4, the regeneration: Gates on
Files that changed in the hop (
This PR's own delta against Generated by Claude Code |
… paths (objectstack-ai#20056) Fixes objectstack-ai#20034 Clause-②: no ## Patch round 1 (head `10b1176328`) Added on top of the reviewed head `4c216561c6` (at-tier review PASS, comment 5824559177). It carries the implementer's own two out-of-scope findings and the reviewer's Clause ② reading: - **ADR-0087 D3 entry `automation-runs-cursor-retired`**: `packages/spec/src/migrations/entries/semantic/18.automation-runs-cursor-retired.ts` `:11`, `:43` and `:72` now name `GET /api/v1/automation/:name/runs`, the path this PR's contract publishes and the dispatcher mounts. `packages/spec/src/migrations/registry.ts` was regenerated with `pnpm --filter @objectstack/spec gen:migration-registry` (not hand-edited; the diff is the same three lines at `:5943`, `:5975` and `:6004`). `gen:upgrade-guide` and `gen:spec-changes` were re-run and changed no bytes, because the entry is in step 18, beyond `PROTOCOL_MAJOR` 17. The text still ships today as data in `MIGRATIONS_BY_MAJOR[18]`, which is why it is corrected now. Open PR objectstack-ai#20031 regenerates a different region of `registry.ts`; whichever of the two lands second regenerates. - **Two comments**: `packages/runtime/src/query-param.ts:179` and `packages/services/service-automation/src/run-list-truncation.test.ts:6` now quote the `/api/v1` path. Both are comments only. - **Clause ②**: `.changeset/20034-automation-contract-api-v1-paths.md:9` and this body's line 2 now read `Clause-②: no`, with no arm. This diff adds no key, widens no accepted input and adds no export (`scripts/pm/clause2-line.mjs:70`). The level stays `minor`. - No pending changeset quotes a sentence of the D3 entry. `.changeset/19365-automation-runs-cursor-hasmore.md:117` carries only the registration marker naming the entry's id, and the id is unchanged. So this round needs no further deliberate correction. ## What this changes `AutomationApiContracts` (`@objectstack/spec/api`) declared its nine flow endpoints under `/api/automation`. The dispatcher mounts the automation door at `config.prefix || '/api/v1'` plus `/automation`, and `objectstack serve` passes no prefix, so every declared path answered `404 ENDPOINT_NOT_FOUND` on the default composition (measured on a composed runtime by the objectstack-ai#19966 dev). This PR takes remedy 1: the contract moves to the served paths. **The runtime and dispatcher are unchanged.** - `packages/spec/src/api/automation-api.zod.ts`: the nine `path` values, the module's `Base path` line and endpoint list, and every other in-file path quote (section headers, `@example`s, the resume docblock, and the `cursor` tombstone text `ListRunsRequestSchema` raises) move from `/api/automation…` to `/api/v1/automation…`. After the edit the file holds 0 occurrences of `/api/automation` (28 moved, plus the 10 docblock lines rewritten). - `packages/spec/src/api/automation-api.zod.test.ts`: the nine path pins move with the values. - `content/docs/references/api/automation-api.mdx`: regenerated with `pnpm --filter @objectstack/spec gen:docs` (not hand-edited). - `packages/runtime/src/automation-api-contract-mounts.test.ts` (new): the drift pin, below. - `.changeset/20034-automation-contract-api-v1-paths.md` (new): `@objectstack/spec` `minor`. - `.changeset/19365-automation-runs-cursor-hasmore.md`: a deliberate correction of a pending note, below. - Patch round 1: `migrations/entries/semantic/18.automation-runs-cursor-retired.ts` and the regenerated `migrations/registry.ts`, plus comments in `packages/runtime/src/query-param.ts` and `packages/services/service-automation/src/run-list-truncation.test.ts`. ## Reproduction, at base `adbbc5d01e` - Spec: `automation-api.zod.ts:14` `Base path: /api/automation`; `:658`–`:706` nine `path` values under `/api/automation`; the test pinned all nine to themselves (`automation-api.zod.test.ts:803`–`:811`). - Runtime: `dispatcher-plugin.ts:909` `const prefix = config.prefix || '/api/v1';`; `registerAutomationRoutes(base)` mounts `${base}/automation…` (`:1465` onwards), called with `prefix` at `:1746`, and with `${prefix}/environments/:environmentId` at `:1742` / `:1750` when project scoping is on. - Route ledger: `route-ledger.ts:429` `POST /automation` (client `automation.create`) and siblings; the header (`:17`) says to prepend `/api/v1` for the wire path. - CLI: `packages/cli/src/commands/serve.ts:4412` calls `createDispatcherPlugin({ scoping, enforceProjectMembership, observability, rateLimit })`, no `prefix`; scoping defaults to off (`:4340`). ## Consumer search: nothing depends on the unversioned form | candidate | reads the contract's `path`? | verdict | | --- | --- | --- | | `packages/adapters/hono/src/hono.test.ts:453` (`GET /api/automation delegates to dispatch()`) | no | Not a consumer. It drives `createHonoApp` with the adapter's own default `prefix` (`options.prefix \|\| '/api'`, `hono/src/index.ts:303`) against a mocked dispatcher and asserts the dispatcher-internal `/automation`. It never imports the contract. | | `packages/client` | no | Builds automation URLs from discovery or its `/api/v1/automation` convention (`getRoute('automation')`); it never names `AutomationApiContracts`. Two comments name the spec test file `automation-api.zod.test.ts`, not the constant. | | everything else in this repo | no | `AutomationApiContracts` occurs only in its declaring file, its spec test, `api-surface/api.json` (name only) and `export-origins/api.json`. No generator reads the path values. | | objectui at the pinned `.objectui-sha` `62597c588` | no | `git grep -F` at that commit: `AutomationApiContracts` 0 files, `/api/automation` 0 files; positive control `/api/v1/automation` 33 files. | | `objectstack-ai/cloud` and npm consumers | not measured | not checked out here | One served surface does use the unversioned form: a host built with `createHonoApp({ kernel })` and no `prefix` serves the whole dispatcher, automation included, under `/api`. That is a documented adapter default, and it applies to every contract family: under that host every other `*ApiContracts` row (`/api/v1/…`) is off by the same segment. The old automation paths matched it by coincidence, not by design, and no code reads the contract under that host, so remedy 2 does not apply. The changeset says how such a host maps the contract paths. ## Deliberate correction of a pending release note `.changeset/19365-automation-runs-cursor-hasmore.md` (pending, not yet released) quotes the `cursor` tombstone text in its FROM/TO block. That text is one of the path quotes this PR moves, so the note became false. Its line 32 changes from -> throws: '`cursor` was removed from GET /api/automation/:name/runs in to -> throws: '`cursor` was removed from GET /api/v1/automation/:name/runs in Nothing else in that note changes. This is the DELIBERATE CORRECTION class that `check-empty-changeset.mjs` names. `skip-changeset` is not applied, and `Check Changeset` stays red **by design**. The same-head at-tier review (comment 5824559177) names the note and judges the changed sentence. No other pending changeset quotes an unversioned automation path. At the base, `git grep -n "/api/automation" -- '.changeset/*.md'` showed only that line. Patch round 1 corrected no further note: no pending changeset quotes the D3 entry's sentences. ## Changeset level `minor`, not declared breaking. The `path` type stays `string`, no accepted input narrows, no method changes, and the old values named paths that no route served on the default composition, so a caller that read the constant gets a working URL now without changing code. This follows the precedent of the `PackageApiContracts.installPackage.path` rebind (`.changeset/18058-install-door-contract-rebind.md`, `minor`, not breaking). The declaration is `Clause-②: no` with no arm, in both this body and the changeset. It answers the reader's question "does this widen an accepted input or grow the public surface?" and the answer here is no: no key added, no accepted input widened, no export grown. It is not `(narrowing)` either, because nothing an author writes is removed. `minor` is valid under `no`: a published constant's value moves, and `patch` is a floor, not a ceiling. The first head declared `yes`, copied from the claim, and the reviewer judged that over-declared. ## The drift pin, and proof that it can fail `packages/runtime/src/automation-api-contract-mounts.test.ts` has two legs: 1. **mount**: it starts `createDispatcherPlugin` with **no** `prefix` (the composition `objectstack serve` builds) on a server that records registrations, and requires every contract `METHOD path` to be one of them. The prefix comes from the plugin's own default, not from a constant in the test. 2. **ledger**: every contract route must be a `route-ledger.ts` row under the documented `/api/v1` wire prefix. The live-mount parity gate probes those rows through the real router. The runtime vitest config aliases `@objectstack/spec/*` to spec **source**, so the spec side of the pin reads `src/`, not a build. The ablation was run on the committed tree (`4c216561c6`) with `scripts/ablation-replace.mjs`, one leg at a time, with restores anchored on `HEAD`: | leg | mutation (landed on disk: anchor 1 → 0, blob moved) | pin result | | --- | --- | --- | | contract | `getRun.path` back to `/api/automation/:name/runs/:runId` | mount red, ledger red (`getRun` named), 1 passed | | dispatcher | default prefix `'/api/v1'` → `'/api/v2'` | mount red (all nine named), 2 passed | | restored | none (blobs equal `HEAD`, `git diff HEAD` empty for both paths) | 3 passed | ## Environment-scoped mount The contract does not carry `/api/v1/environments/:environmentId/automation…`, and this PR does not add it. No `*ApiContracts` map declares the scoped variants. Scoping is one mount-time transformation the dispatcher applies to automation, actions, AI and packages alike, and the client derives scoped URLs from discovery. If the variants are ever declared, that belongs once in a contract shared by all the families, not copied into each map. Note that under `projectResolution: 'required'` none of the nine unscoped paths is mounted (`dispatcher-plugin.required-scoping-mounts.integration.test.ts` pins that). ## Verification (head `10b1176328`, patch round 1) - Tests, each package's full local project, under the verify lock at this head. The lock's verdict is `batch-last-exit 0`: the last part of the batch requires all three suites to exit 0, and the batch printed `SUITES spec=0 service-automation=0 runtime=0`. - `@objectstack/spec`: 532 files, 15648 passed, 2 todo. - `@objectstack/service-automation`: 144 files, 1725 passed. - `@objectstack/runtime` (`--project local`): 277 files, 3896 passed, 1 skipped, including the drift pin's 3 cases. - Build: `turbo run build --filter='./packages/*' --filter='./packages/*/*'` at this head: 72 of 72 tasks succeeded, including the tsup and declaration builds of spec, runtime and service-automation. - Spec generated artifacts: `check:generated` reports "All 15 generated artifacts are up to date". `check:migration-registry` reports "src/migrations/registry.ts is current (242 semantic, 210 retired-key, 183 retired-def)". `check:spec-changes` and `check:upgrade-guide` both report up to date. `check:api-surface` reports "public API surface + factory signatures unchanged", and `check:docs` reports "225 generated files in sync". - Derived gate set, taken after `git fetch origin main` (`dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`, merge base `adbbc5d01`, 10 paths): 113 families, 6 more than round 0 (`check:migration-registry`, `check:spec-changes`, `check:upgrade-guide`, `check:future-spec-major`, and `check-tenant-audit-census` with its self-test). All 113 ran, and `--ran` reports "113 run, 0 NOT-MEASURED" with 0 UNRUN. 112 exited 0. `node scripts/check-empty-changeset.mjs --base origin/main` exits 1 on the deliberate correction, as designed. No family answered PREREQUISITE NOT MET this round. - The branch is behind `origin/main` (15 commits at the seat's re-read). Three of those commits regenerated one of this PR's 10 paths, `packages/spec/src/migrations/registry.ts`: objectstack-ai#20036 (`0bf85eaae6`), objectstack-ai#19909 (`5b9402d89b`) and objectstack-ai#19818 (`66960564d9`). `git merge-tree --write-tree` of `origin/main` and this head is clean, and the at-tier re-review measured that objectstack-ai#20036's and objectstack-ai#19909's hunks do not touch this PR's region (`:5940`–`:6004`). The merge queue's rebuilt generation regenerates the file. The derivation's one changed family input across that range is `scripts/sdui-manifest.record.json`, from objectstack-ai#20036. (Corrected by the seat after the at-tier review `5825376693` found the earlier sentence, "None of this PR's 10 paths is touched by those commits", false.) - Clause ② and ADR-0087, run offline with this body as the `pull_request` event: - `check-changeset-no-major --event` prints "✓ This diff introduces no `major` bump." and "✓ LEVEL AXIS: this PR declares clause-② `no`, so no package here is declared to have grown a published surface." (declaration line `Clause-②: no`, no arm). - `check-adr-0087-registration` prints "✓ … this PR adds no declared-breaking changeset (2 non-breaking changeset(s) seen)". This gate reads the Clause ② arm from the changeset body (`readClause2Line(parsed.body)`), not from the PR event. Its verdict is the same with and without `--event`. - Lint, narrowed and proven: all 7 touched TS files are in the eslint population (`--print-config` resolves each). `--no-inline-config --format json` reports 7 files, 0 errors, 0 warnings. `eslint.config.mjs` enables no type-aware linting: all seven `parserOptions` blocks are `{ ecmaVersion, sourceType }` only, with no `project` or `projectService`. So this diff cannot change the verdict on any untouched file. The repo-wide `pnpm lint` is left to CI. - Round-0 evidence still stands, and its sources are unchanged in round 1: the ablation above on `4c216561c6`, and the spec and runtime `typecheck` runs, both exit 0. Round 1 changes only string literals (the D3 entry and its registry mirror), comments and one changeset line. The type-check-debt gate re-measured at this head: "4 ledger entr(ies) … none above its recorded number". ## Acceptance notes (observations, not filed) - `RouterConfigSchema` (`spec/src/api/router.zod.ts`) defaults `basePath` to `/api` with `mounts.automation: '/automation'`. That spec-only declaration has no runtime reader in this repo. - The contract lists nine of the 17 routes `registerAutomationRoutes` mounts. The ones not listed are resume, cancel, restore-suspension, screen, actions, connectors, `_status` and the legacy trigger form. - **Every remaining `/api/automation` path at head `10b1176328`, and why it stays** (`git grep -n "/api/automation\b"`, with the `automation-api` file-name hits filtered out): - `.changeset/20034-automation-contract-api-v1-paths.md` `:5` and `:15`-`:20`: the FROM column of this PR's own FROM/TO table. - `packages/adapters/hono/src/hono.test.ts:453`-`:454`: the Hono adapter's own default `prefix` (`/api`), driven against a mocked dispatcher. It does not read the contract. - `packages/runtime/src/automation-api-contract-mounts.test.ts:9`: the pin's docblock, describing the drift it guards. - `packages/spec/scripts/file-description.test.ts:878`-`:937`: synthetic fixtures for the docblock-description extractor. They do not quote the contract. - `packages/spec/src/api/router.test.ts:343`: a custom-mounts fixture for `RouterConfigSchema`. - Released `CHANGELOG.md` entries: `packages/client/CHANGELOG.md:3230`, `packages/runtime/CHANGELOG.md:11589`, `packages/services/service-automation/CHANGELOG.md:5802` and `packages/spec/CHANGELOG.md:32723` quote `GET /api/automation/:name/runs` in the released ExecutionStatus-filter entry. These are release-owned and never edited in a code PR; an amendment would be a dedicated docs-only PR. `packages/spec/CHANGELOG.md:14982` names the file `automation-api.mdx`, not a path. Implemented in session `session_019c3Hi6ZMU1p6m6aA6Bz45d` (claim 5823821835; patch round 1 dispatched by the `domain:spec` seat 4). --------- Co-authored-by: Claude <noreply@anthropic.com>
…isContainer; lockstep pins the predicate (objectstack-ai#20065) Fixes objectstack-ai#19969 Clause-②: no ## What this does The save gate's `validateTree` in `packages/sdui-parser` now decides `not-a-container` by the component's declared `children` input. It no longer reads `isContainer`, and there is no fallback to the flag. This is the byte-equal port of objectui `5ea623ea` (objectui#9910 Q1-A, re-cut as objectui PR objectstack-ai#10270), which is already inside the console pin `f8a9d0fb0596` (PR objectstack-ai#20036). The objectui side it mirrors is `packages/sdui-parser/src/validate.ts` at the pin: `CHILD_LIST_KEY` at `:33`, `acceptsChildren` at `:57-59` and the containment branch at `:220`. - **Predicate semantics mirrored exactly.** The test is `comp.inputs.some((input) => input.name === CHILD_LIST_KEY)`. It matches on the **name** `children` alone, whatever the input's coarse type. The dispatch brief said "name `children`, type `slot`". objectui deliberately matches on the name only, because the five page kinds publish `children` typed `array`, and objectui pins that case. This port follows objectui, not the brief. - **A component the manifest has no entry for** draws `unknown-component` and gets no containment verdict. This is unchanged and is the same on both sides. - **`isContainer`** stays on `ManifestComponent`, re-described as layout containment in the same words objectui uses. `index.ts` is unchanged: no new public export. objectui exported `acceptsChildren` and `CHILD_LIST_KEY` from its entry for its own census tests, and nothing here needs that. ## The lockstep pin (maintainer-named in the ruling) `scripts/check-sdui-lockstep.mjs` now also compares the **containment predicate**. The gate extracts the condition of the one `if` in `validate.ts` whose body stamps `not-a-container`. It adds the module-level declarations that condition reaches, transitively (`acceptsChildren`, then `CHILD_LIST_KEY`), and reduces the result to its token sequence. Comments and wrapping do not change the reading. A changed token does. The gate compares this reading against a new `containmentPredicate` field in the vendored objectui record. - The record was regenerated with `pnpm gen:sdui-lockstep` against an objectui checkout parked on the pin `f8a9d0fb0596`. Every other field of the record is byte-unchanged; the only diff is the new `containmentPredicate` block. - If `validate.ts` has zero or two containment branches, the gate refuses (`[containment-unreachable]`). It never measures only one of two. - A new self-test battery with 10 cases is added, and the battery floor goes from 6 to 7. The first case asserts that the flag predicate and the slot predicate read differently. ## The five served components (tracked `sdui.manifest.json` at `0bf85eaa`, 5 of 59 disagree) | component | `isContainer` | declares `children` | before (flag) | after (declared input) | |---|---|---|---|---| | `badge` | false | yes (`slot`) | warns `not-a-container` (false) | no warning | | `alert` | false | yes (`slot`) | warns (false) | no warning | | `button` | false | yes (`slot`) | warns (false) | no warning | | `page:tabs` | true | no | silent | warns `not-a-container` | | `page:accordion` | true | no | silent | warns `not-a-container` | The new `containment-declared-slot.test.ts` uses these five entries, copied verbatim from the manifest. Each gets its own case, plus a control asserting that the two predicates really disagree on every one. The file also carries objectui's 2×2 (flag × slot) reading. ## Ablation (a trap restores the file, and the restore is proven byte-identical to HEAD by blob hash and an empty `git diff HEAD`) - **Leg 1: revert the port.** The branch was changed back to `!comp.isContainer` through `scripts/ablation-replace.mjs`: anchor 1 to 0, blob `819b7b6a83ac` to `670ce98bdb15`. `node scripts/check-sdui-lockstep.mjs` exited **1** with `[containment-drift]`, printing both texts. The file was then restored to blob `819b7b6a83ac` = HEAD. - **Leg 2: sneak the fallback into the helper.** The mutation appended an `isContainer` fallback to the `acceptsChildren` return. The gate exited **1** with `[containment-drift]`. Restored to `819b7b6a83ac`. - **Test-suite leg (same mutation as leg 1).** The new test file went **8 failed / 5 passed** (exit 1). Restored. - **Restore leg.** `pnpm check:sdui-lockstep` on HEAD `f3ff1e6e9` exits **0**: the self-test passes, and the gate reports agreement on all 26 codes and on the `not-a-container` predicate (blob `76c18fb95d1f`). ## Verification (HEAD `f3ff1e6e9`; re-run on `bfb082b0b` in the section below) - `pnpm --filter @objectstack/sdui-parser exec vitest run`: 11 files, 189 tests passed (VERDICT command-exit 0). - `pnpm --filter @objectstack/sdui-parser typecheck`: exit 0. `--listFiles` includes the new test. - `pnpm check:sdui-lockstep`: exit 0. - `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` derived 81 commands. `--ran` reconciles: 77 exit 0, 4 NOT MEASURED, 0 unrun. - NOT MEASURED, prerequisite unmet: `check:dual-build-cjs-loads`, `check:lean-entry-closure` and `check:type-check-debt` each exited 3 because they need a full workspace build. - NOT MEASURED, over the time cap: `check:pm-dispatch-gates`. Its self-test ran past the foreground cap, was killed twice, and is declared to CI. - `check-plugin-teardown-shape --self-test` first exited 3 because this is a shallow checkout. It exited 0 after fetching its pinned fixture commit. - `eslint --no-inline-config` over the 6 touched source files: 0 errors, 0 warnings. This is a targeted run, not a proven narrowing, so the full `pnpm lint` is declared to CI. ## packages/lint fixture: resolved by PM ruling `5825043664` The PM ruling on objectstack-ai#19969 (comment `5825043664`, Q2 = A) widened this card file surface by exactly one test fixture. Commit `bfb082b0b` gives the hand-made `flex` entry at `packages/lint/src/validate-jsx-pages.test.ts:39` the `children` input, the same triage objectui made on its own fixtures: ```ts flex: { type: flex, namespace: ui, isContainer: true, inputs: [{ name: children, type: slot }] }, ``` No other `packages/lint` file changes; `authoring-rules.ts` and the lint caller are untouched. Q1 = A, provisionally: the changeset stays `patch` with `Clause-②: no`, and the contract review judges it on the final head. On head `bfb082b0b`, after rebuilding the `@objectstack/lint` dependency closure: - `pnpm --filter @objectstack/lint exec vitest run`: 108 of 108 files passed; 4133 tests passed, 5 skipped, 0 failed (VERDICT command-exit 0). Before this commit it was 1 failed / 4132 passed. - `pnpm --filter @objectstack/lint typecheck`: exit 0. - sdui-parser suite: 189 of 189 passed. `pnpm --filter @objectstack/sdui-parser typecheck`: exit 0. `pnpm check:sdui-lockstep`: exit 0. - `dispatch-gates --commands` on the 9-path set derived 82 commands (one new: `check:docs-transcript-drift`, exit 0 after building `@objectstack/lint`). `--ran` reconciles: 78 exit 0, 4 NOT MEASURED, 0 unrun. The four are `check:dual-build-cjs-loads`, `check:lean-entry-closure` and `check:type-check-debt` (each exit 3, prerequisite: a full workspace build) and `check:pm-dispatch-gates` (over the foreground cap). All four are declared to CI. ## Acceptance notes - `codegen.ts`'s `generateBlockList` still labels its column "container" from `isContainer`. Under Q2-A that column now means layout container. It is unchanged here, and objectui leaves it as-is too. --- _Generated by [Claude Code](https://claude.ai/code/session_01VDtqoecgES7ScQYGbFVDRv)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
…s / related_list with one true describe shared with record:quick_actions (objectstack-ai#19913) Fixes objectstack-ai#18159 Clause-②: yes <sub>Rewritten short by the `domain:spec#5` seat (2026-09-23T19:59Z). The dev reports are on objectstack-ai#18159 (`5798750532`, `5800008407`, `5800978880`, `5801656924`); the earlier long body is in the edit history.</sub> `requiredPermissions` is now declared on `record:details`, `record:highlights` and `record:related_list`, with the shape `record:quick_actions` already has (`z.array(z.string()).optional()`). Ruling: batch objectstack-ai#197 item 2, letter A (`5749268463`), after objectui#10058 became installable at `.objectui-sha` `62597c588072`. ## One describe, four blocks (seat ruling `5798783314`) The ruling asks for the same describe as `record:quick_actions`. That published describe said "…every named permission **on this object**", which is false for the renderer at the pin: the check is `hasCapabilities`, and the renderer's own comment says the capability "is not object-scoped". So all four blocks now share ONE describe, from one constant (`RECORD_BLOCK_REQUIRED_PERMISSIONS_DESCRIPTION`). **The published `record:quick_actions` describe changes; its shape does not.** The describe states: - the names are ADR-0066 capabilities, not object actions; - the user must hold all of them; - when one is missing, the block shows an insufficient-permissions notice instead of its content; - it is presentation only, and the data API still serves the data; - a client that cannot resolve the user's capabilities renders the block (fails open). The renderer lines behind each clause are in the dev report `5800008407`. ## Other changes - The texts that said the key is "deliberately NOT declared" are rewritten: the `RecordDetailsProps` family header, the related-list and highlights pointers, and the test header. - The absence pins are now accept pins: a parse probe and `.shape` enumeration per block, with `aria` and `fields` as lit controls, plus a pin that the four JSON Schemas of the key are identical. Removing the three declarations turns 11 of 25 pins red. - `authorable-surface/ui.json` and `component.mdx` are regenerated. ## `Check Changeset` is red on purpose The pending note `.changeset/18159-record-block-field-security-pair.md` (from PR objectstack-ai#19185) said the third key "is deliberately NOT declared". This PR makes that false, so the PR corrects it. That is a DELIBERATE CORRECTION under `check-empty-changeset.mjs`: the check stays red, and the correction awaits the maintainer's written confirmation on this PR. The line to confirm is quoted verbatim in `5801679846`. `Check Changeset` is not a required context. ## Fix round 2026-09-25: `origin/main` merged, objectui pin re-measured Fix round for `domain:spec#4` (session `session_019c3Hi6ZMU1p6m6aA6Bz45d`), new head `b19ac16c77`. The merge queue dequeued the PR. `Type Check · source gates` failed at `check:objectui-pin-citations`: objectstack-ai#20036 (`0bf85eaae6`) moved `.objectui-sha` from `62597c588072` to `f8a9d0fb0596`, and the shared `requiredPermissions` docblock in `component.zod.ts` still asserted the old pin. This round adds three commits and changes nothing else: - `62decd77dd` merges `origin/main` at `66960564d9` with `scripts/pm/os-regen-merge.sh` (merge commit; no rebase or force-push). It had no text conflict. - `a3075b6ace` regenerates `packages/spec/authorable-surface/ui.json` on the merged tree. In that file the merge driver kept this branch's side and dropped main's 13 `DocNavItem` keys. The regenerated file has main's keys plus this PR's three `requiredPermissions` keys, and nothing else. - `b19ac16c77` re-measures the docblock at the new pin. It updates the sha and the anchors together (10 lines replaced, 0 added). ### What was measured objectui at `f8a9d0fb0596f4521076628e2bbfe27e6ce67d52` was read from a scratch fetch of both pins. The seven cited files match the codeload tarball of the same commit byte for byte. All 21 anchors in the docblock were re-read. **At the new pin, each cited range holds the same text it held at `62597c588072`.** No read point changed meaning or disappeared. - `record-details.tsx` shifted +1 (one new import line). `record-related-list.tsx` shifted +34 (an import block and the props-type docblock). Nine anchors get new numbers. - `record-highlights.tsx`, `record-quick-actions.tsx`, `MePermissionsProvider.tsx`, `PermissionProvider.tsx` and `usePermissions.ts` did not change between the two pins, so their twelve anchors keep their numbers. | Anchor, old → new | First line of the range at `f8a9d0fb0596` | |---|---| | `record-details.tsx:234` → `:235` | `if (required.length > 0 && !perms.hasCapabilities(required)) {` | | `record-details.tsx:223` → `:224` | `* capability is not object-scoped, and the old && objectName conjunct was` | | `record-details.tsx:234-242` → `:235-243` | `if (required.length > 0 && !perms.hasCapabilities(required)) {` | | `record-details.tsx:186` → `:187` | `if (!ctx) {` | | `record-related-list.tsx:242` → `:276` | `if (required.length > 0 && !perms.hasCapabilities(required)) {` | | `record-related-list.tsx:229` → `:263` | `* capability is not object-scoped. This site never carried the` | | `record-related-list.tsx:242-250` → `:276-284` | `if (required.length > 0 && !perms.hasCapabilities(required)) {` | | `record-related-list.tsx:184` → `:218` | `if (!objectName) {` | | `record-related-list.tsx:202` → `:236` | `if (perms.isLoaded && !perms.can(objectName, 'read')) {` | | `record-highlights.tsx:93` (unchanged) | `const highlightsAllowed = required.length === 0 \|\| perms.hasCapabilities(required);` | | `record-highlights.tsx:77` (unchanged) | `* capability is not object-scoped, and the old && objectName conjunct was` | | `record-highlights.tsx:151-164` (unchanged) | `if (!highlightsAllowed) {` | | `record-highlights.tsx:146-149` (unchanged) | `useRegisterHighlightFields(` | | `record-quick-actions.tsx:263` (unchanged) | `if (required.length > 0 && !perms.hasCapabilities(required)) {` | | `record-quick-actions.tsx:252` (unchanged) | `* capability is not object-scoped, and the old && objectName guard was a` | | `record-quick-actions.tsx:263-271` (unchanged) | `if (required.length > 0 && !perms.hasCapabilities(required)) {` | | `MePermissionsProvider.tsx:416`, and the later `:416` (unchanged) | `return required.every((p) => held.has(p));` | | `MePermissionsProvider.tsx:414` (unchanged) | `if (!Array.isArray(perms)) return true;` | | `PermissionProvider.tsx:77` (unchanged) | `const ALL_CAPABILITIES: PermissionContextValue['hasCapabilities'] = () => true;` | | `usePermissions.ts:45` (unchanged) | `hasCapabilities: () => true,` | (The inner backticks around `&& objectName` in the three docblock lines are left out of the table.) One observation from the re-read, which changes no anchor. The props-type docblock that `record-related-list.tsx` gained (`:89-92`) says no block the contract maps onto this tag declares `requiredPermissions`, and says not to reopen the type to admit it. This PR makes that sentence stale. That is the objectui half ruled **A** on objectstack-ai/objectui#10281, and it moves to the `domain:ui` seat. The renderer's read of the key (`:240-242`, through a cast) and its gate (`:276`) are unchanged. ### Gates, on head `b19ac16c77` - `check:objectui-pin-citations` exits 0 on `--self-test`, on the ordinary run and on `--verify-anchors`, with objectui at the new pin supplied through `OBJECTUI_ROOT`. The ordinary run prints: "48 asserting objectui pin citation(s) match .objectui-sha (f8a9d0fb0), 40 historical citation(s) recorded and not checked, across 1555 spec source(s). 7 anchor content assertion(s) verified against objectui at f8a9d0fb0". CI has no objectui checkout, so it verifies the sha label only. - `pnpm --filter @objectstack/spec build`, then `check:generated`: all 15 generated artifacts are up to date. `typecheck` exits 0. The full spec suite: 535 files, 15727 passed, 2 todo. - `node scripts/pm/dispatch-gates.mjs --commands` derived 107 commands for this change set. All 107 ran, and `--ran` reconciles 107 of 107 with recorded exit codes and 0 NOT MEASURED. Seven of them first exited 3 (no build to read). They were re-run green after building the lint closure, the client-react closure and then every package. 106 exit 0. The one exit 1 is `check-empty-changeset.mjs`. That is the deliberate correction described above, which the maintainer confirmed at head `aeb6a57456` (`5823706830`). - The changesets are byte-identical to that head: `git diff --exit-code aeb6a57 b19ac16 -- .changeset/18159-record-block-field-security-pair.md .changeset/18159-record-block-required-permissions.md` exits 0. The blobs are `d502a9a04c6a` and `80546d851168` on both heads. The test file and `component.mdx` are byte-identical to that head as well. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #20029
Clause-②: no
Moves the console pin to objectui
mainf8a9d0fb0596(the tip at 2026-09-24T20:41Z). That pin carries objectui#10221 (0651e7ab4, a currency amount's decimals come from its currency, never fromscale), which ruling 乙 on #19910 item 2 requires before PR #19909 lands. It also carries the objectui#9910 re-cut (5ea623ea, containment is the declaredchildrenslot), which #19969 waits on. The PR folds in the #19503 / PR #19832 precedent's two steps: the sdui-parser lockstep re-record, and a re-MEASURE of thepackages/spec/src/**citations thatcheck:objectui-pin-citationsnames. Every spec edit is to comment or migration-rationale prose only, plus ONE.describe()citation re-point (theFormField.spanpin, under PM ruling5822830122, option A). No schema, key, default or export changes. Clause-②: no, because every authored document is accepted and refused exactly as before.✅ All 47 pin citations are now green. The one
.describe()this PR first stopped on (view.zod.ts:3392,FormField.span) was re-pointed under PM ruling5822830122(option A) on #20029. See "The describe, resolved" below.Ancestry reading (fix → new pin: AHEAD)
compare: NOT MEASURED. This session's proxy refuses GitHub API reads onobjectstack-ai/objectui.GET /repos/objectstack-ai/objectui/compare/0651e7ab4...f8a9d0fb0answered "GitHub access to this repository is not enabled for this session".objectstack-ai/objectui, wheregit rev-parse --is-shallow-repositoryreturnsfalse. An exit 0 is self-proving, and an exit 1 there is not a shallow-window artefact.git merge-base --is-ancestor 0651e7ab4 f8a9d0fb0596git rev-list --count 0651e7ab4..f8a9d0fb0596ahead)git merge-base --is-ancestor 5ea623ea f8a9d0fb0596git merge-base --is-ancestor 5ea623ea 0651e7ab4git merge-base --is-ancestor 62597c588072 0651e7ab4git merge-base --is-ancestor 5ea623ea 62597c588072git rev-list --count 62597c588072..f8a9d0fb0596The bump's own changeset lists both:
(objectui 0651e7ab4)and(objectui 5ea623eab).What moved, and how each artefact was produced
.objectui-sha+.changeset/console-f8a9d0fb0596.mdbash scripts/bump-objectui.sh --no-commit f8a9d0fb0596…exit 0. It reads 86 releasing changesets and 9 breaking (all 9 annotated, none declared major);@objectstack/console: minor. The generator'sadr-0087: TODOis answerednot-required (no-migration-prescription), in the wording of the last bump that carried breaking entries.OBJECTUI_ROOT=../objectui bash scripts/build-console.sh, through the verify lock, VERDICT command-exit 0 (691 s). Theimport/jobsbundle canary is present.pnpm check:console-shaexit 0: "Console dist matches the objectui pin (objectui@f8a9d0fb0596)".sdui.manifest.json+scripts/sdui-manifest.record.jsonnode scripts/gen-sdui-manifest-node.mjsexit 0. It wrote 59 components, sha25612dd4f8fbf45. The diff is +39: thechildrenslot input objectui#9910 declared onalert,badge,box,button,card,container,flex,gridandstack.node scripts/check-sdui-manifest.mjsexit 0.packages/sdui-parser/objectui-lockstep.jsonOBJECTUI_ROOT=../objectui pnpm gen:sdui-lockstepexit 0. The diff is 3+/3-, onlyrev,revDateandrecordedAgainstPin. Grammar blob0131f27cf86dis unchanged (214 lines), and all 26 diagnostic codes agree.pnpm check:sdui-lockstepexit 0: "byte-identical to objectui@f8a9d0fb0596". No parser port is owed by the lockstep gate.packages/spec/src/migrations/registry.tspnpm --filter @objectstack/spec gen:migration-registry, from the six edited entriesThe #19969 save/render window (measured, not fixed here)
In the NEW
sdui.manifest.json, 5 of 59 components disagree betweenisContainer(whatpackages/sdui-parser/src/validate.tsstill tests) and "declares a{ name: 'children', type: 'slot' }input" (what the pinned renderer now decides containment by):isContainerchildrenslotbadgenot-a-containeron children the renderer rendersalertbuttonisContainer: objectui#6804 ruled that flag means LAYOUT containment" (button.tsx:97-104)page:tabsitems[].children)page:accordionNon-zero, so #19969 can be sequenced right behind this PR. The predicate is ⛔ not ported here.
Citation re-measure: method
git diff --quiet 62597c588072 f8a9d0fb0596 -- FILE.registry.ts).git grep -o -F TOKEN SHA | wc -l. The method was calibrated first: at62597c588it reproduces every recorded count exactly (8303 files,objectstack13125,@objectstack/spec5043,timeout1086,RuntimeConfig240,resourceLimits2,useState2389,window3526,period170,interval179,metrics324,TTL156,tenant987,Span486,SpanSchema53).f8a9d0fb0, all 100 dark tokens still count 0: every export of the four cited zod files plus each key name.Spanhits arecolSpanand the word "Spanish".check:future-spec-major's measurement exemption to the sentence "controls objectstack 12966 and @objectstack/spec 4997 on the same corpus" is kept verbatim. The new figures follow it as "13347 and 5123 … at this pin", and the gate is exit 0.Every
packages/spec/src/**edit, with its before → after anchorf8a9d0fb0api-methods-batch-conformance.test.tssys_api_keyObjectGrid.tsx(changed +18/-5: objectui#10083rowActionsDeclared, objectui#9909 currency locale),useBulkExecutor.ts(identical)4024-4051→4032-4059, still hashc88443302d40.298-303is identical (hash01083348330f)functional-completeness.tscalendar / gantt / timeline / map rowsListView.tsx(changed: objectui#10275$select; objectui#10037 / objectui#10250 self-query filter and search on gantt / tree / chart),ObjectGantt.tsx(changed:$search),ObjectCalendar.tsx/ObjectTimeline.tsx/ObjectMap.tsx(identical)registry.tscopiescomponent.zod.ts/component.test.tstabs + accordioniconcontainers.tsx(changed,ba0b61a60: an import line +page:header)853-859,912,1069-1075,1116did NOT move, text byte-identicalcomponent.zod.ts/component.test.tsbuttoniconbutton.tsx(changed,5ea623eab),resolve-icon.ts/lazy-icon.tsx(identical)85-97→85-105(+childrenslot input),defaultProps98-102→106-110; still noiconinput.:43/:72/:74unmovedcomponent.zod.tsmetriciconObjectMetricWidget.tsx(changed,0651e7ab4),index.tsx/MetricWidget.tsx(identical)174→181, forward483→528, text byte-identicalcomponent.zod.ts/component.test.tskanbanlimit×3,quickAdd×2,navigationObjectKanban.tsx(changed, objectui#10068$orderby), plugin-kanbanindex.tsx(changed),objectql.ts,ElementDataSourceGate.tsx,ListView.tsx, plugin-viewObjectView.tsx(all changed);types.ts,element-data-source.ts,KanbanImpl.tsx,plugin-kanban.mdx(identical)$orderby(674-679→680-690), mapping gainedsort: true(447-450→507-511); moved byte-identical::84→:85,:553→:554,:559→:565,:676→:687,:1210→:1225,:1218-1219→:1233-1234,:1468→:1483,:1484-1497→:1499-1512,:1563→:1578,:1587→:1602,index.tsx:313→:363-364,objectql.ts:3735→:3832, Gate316-331→373-388,192-194→200-202, ListView2979→3067,2952→3040, ObjectView1638→1666,1579→1607.onQuickAdd/quickAddstill 0,onCardClickstill 11component.zod.tscalendarnavigationObjectCalendar.tsx(identical)component.zod.tsobject-mapheader,filter,sortObjectMap.tsx, plugin-mapindex.tsx,record-source.ts(identical)component.zod.tsobject-ganttheaderObjectGantt.tsx(changed +29/-2, objectui#10250):501-503,:613unmoved; the rest moved 21 / 29 lines byte-identical (e.g.844→865,1615→1644,2181→2210,2144→2173).search/searchableFields(:776-779,:880-885) are recorded, host-generated byListView's toolbarcomponent.zod.tsflatTreeConfigguidance +object-treeheader + registry-shell countObjectTree.tsx(changed +69/-3: objectui#9549, objectui#9136),ListView.tsx(changed);index.tsx,record-source.ts(identical)filter.treestash read is DELETED (getTreeConfig235-248→246-259,tree:236→:247), andfiltergains a second read on the inlinevalueprovider (:837);ObjectTreeSchemanow declaresfilter(objectui#9549). Moved byte-identical:582→593,871/922/985→937/988/1051,813-814→879-880,742→753,908→974,891→957,775→786,741-753→752-764,751→762,750→761,197-218→198-219, ListView3261-3275→3366-3385,3270→3380.ElementDataSourceGatecount re-taken: tree 0, map / gantt / grid / calendar 3 eachcomponent.zod.tsobject-timelineheader +datareadrenderer.tsx(changed,0b6b295a1),ObjectTimeline.tsx/index.tsx(identical)1215→1240,1505→1531,1442-1457→1468-1483, text byte-identicalcomponent.zod.tsComponentPropsMapcomments (map/gantt/tree; timeline)dataset.zod.tsmeasureformatdataset-format.ts,date-display.ts(both changed, objectui#10026)formatDate271-306→355-390and225→309byte-identical;formatMeasureDate229-264→229-263(comment-only change), call370→369, datetime arm260-262→259-261; the style handling is unchangedview.zod.tsview-facelimitreach (#19228)RelatedList.selectFls-10186.test.tsx:314, executable ⇒ 9 executable, was 8). Instrument 2: the same four flattening spreads and the same exclusions. Every anchor re-pointed byte-identical (e.g.ListView.tsx:3084→3172,4702→4815,ObjectView.tsx:1725→1753,2319→2347). Both verdicts standview.zod.tsListMapConfigSchemacontent-asserted anchorsObjectView.tsx,objectql.zod.ts(changed)case 'map':1764→1792(17-line block identical),ObjectMapConfigSchema1574→1589(47-line declaration identical),LIST_VIEW_LOCAL_OVERRIDES734→741(list identical, still nomap).--verify-anchorspasses all content assertionsview.zod.tsFormField.spanDOCBLOCKform.tsx(changed,5ea623eab: registration inputs only),autoLayout.ts(identical)spanLadderFor:204-231is byte-identical, so it still emits the ladderThe describe, resolved (PM ruling
5822830122, option A)The ruling widened the file surface by exactly three items, all landed in one new commit (
90f9a30):packages/spec/src/ui/view.zod.tsFormField.span.describe()citation,.objectui-sha=62597c588→.objectui-sha=f8a9d0fb0596; nothing else in the sentence changes. The claim was re-read true at the new pin:plugin-form'sautoLayout.tsis byte-identical, andform.tsx'sspanLadderFor:204-231is byte-identicalcontent/docs/references/ui/view.mdxpnpm --filter @objectstack/spec gen:schema && pnpm --filter @objectstack/spec gen:docs(VERDICT command-exit 0): the twospanrows only (+2/-2). No other generated file moved.changeset/20029-pin-bump-describe-correction.md'@objectstack/spec': patch, stating the describe correction, in the shape of17429-/19503-pin-bump-describe-corrections.mdGates
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandson head90f9a30derives 124 families (theview.mdxedit adds the docs families).--ranreads: "124 derived famil(ies) accounted for — 121 run, 3 NOT-MEASURED".pnpm --filter @objectstack/spec run check:objectui-pin-citations("47 … match .objectui-sha"),pnpm check:sdui-lockstep,pnpm check:console-sha,node scripts/check-sdui-manifest.mjs,pnpm --filter @objectstack/spec run check:generated,check:docs,check:objectui-changeset,check:future-spec-majorandcheck:migration-registry.@objectstack/lint/formula/client-react). Afterturbo run buildof those closures they read exit 0:check:doc-formula-expressions,check:doc-security-posture,check:skill-examples,check:docs-transcript-drift,check:lean-entry-closure.check-plugin-teardown-shape --self-test(shallow clone);check:dual-build-cjs-loadsandcheck:type-check-debt(both need a full workspace build). This diff changes no code in any package those read.@objectstack/specvitest oncomponent.test.ts,api-methods-batch-conformance.test.ts,src/migrations,functional-completeness,src/ui/view*andsrc/ui/dataset*: 26 files, 1666 / 1666 pass (head530fc4d); re-run onsrc/ui/view*at head90f9a30: 19 files, 1085 / 1085 pass.Changed lines: 884 (+600 / -284, 20 files), under the 5000 threshold.
Acceptance notes
component.test.ts's metricicontest body still citesindex.tsx:204,ObjectMetricWidget.tsx:142/:474andMetricWidget.tsx:312-321. It carries no pin citation, so it is outside the gate's population. Those anchors were already stale before this hop: the docblock beside them reads:237,:181/:528and:351-360. Left as they are, because the gate does not name them.object-kanbanreadssortnow (objectui#10068, viaElementDataSourceGatefrom the binding'sdataSource.sort). The node schema declares nosort. The key reaches the board from the binding, not from the authored node, so this is not an authoring gap. Recorded, not acted on.claude/issue-20029-objectui-pin-bump, as the claim names. The console changeset is the script's ownconsole-f8a9d0fb0596.md(the precedent's spelling), not a hand-named20029-*.md.维护者速读(草稿)
62597c588推进到f8a9d0fb0(objectui main 当时的最新提交),其中包含货币小数位修复 objectui#10221(0651e7ab4)和容器判定改用children插槽的 objectui#9910(5ea623ea)。同时按先例重新生成 SDUI 清单、重录 sdui-parser 对齐记录,并在新版本上逐条重新核对 spec 里引用 objectui 代码行号的注释(47 处中 46 处已改)。scalefrom the currency field type — refused at parse, no longer enforced on writes #19909 才能落地;同一次推进也是 sdui-parser (save gate): containment reads the declaredchildrenslot input, notisContainer— port of objectui#9910 ruling A, same release and pin bump #19969 在等的前提。5822830122(选项 A)把FormField.spandescribe 里引用的钉住版本改为新版本(附 spec patch changeset,重新生成view.mdx);不改任何 schema、键、默认值或导出,已写的元数据接受/拒绝结果完全不变。控制台行为随 objectui 这 92 个提交变化(其中 9 条标注 breaking,都是 objectui 自身的类型面)。清单里有 5 个组件的isContainer与children插槽不一致(badge、alert、button、page:tabs、page:accordion),这是 sdui-parser (save gate): containment reads the declaredchildrenslot input, notisContainer— port of objectui#9910 ruling A, same release and pin bump #19969 要补的保存/渲染窗口。回滚:还原本 PR 即可。Dev session:
session_01BMUTTbeXStLRWxU9zEWDJq(PM seatdomain:devx#1,session_01VDtqoecgES7ScQYGbFVDRv).🤖 Generated with Claude Code
https://claude.ai/code/session_01BMUTTbeXStLRWxU9zEWDJq
Generated by Claude Code