Skip to content

fix(spec)!: defineStack judges a record:line_items column that declares no type as the type it renders (#24190) - #24347

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-24190-line-items-hydrated-check
Oct 18, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-24190-line-items-hydrated-check

Conversation

@objectstack-fleet

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

Copy link
Copy Markdown
Contributor

Fixes #24190

Clause-②: yes (narrowing)

defineStack's resolved-type check, collectHydratedInlineColumnErrors in packages/spec/src/stack.zod.ts, now walks the fourth carrier of the inline grid column: a record:line_items page block's columns. A column that declares no type is judged as the type it renders, against the field of the child object the panel lists, with the column schema's own refusal (STACK_CROSS_REFERENCE_INVALID, 422). Today that refuses scale on an identity-only column over a currency child field. The same column now gets the same answer under record:line_items and under object-master-detail-form.

What changed

  • packages/spec/src/stack.zod.ts. The page walk (the shared forEachPageComponent traversal) judges a record:line_items node's columns with the same per-column judge the other three carriers use. The location reads Page 'NAME' (PATH) record:line_items columns[K].KEY. A new helper, lineItemsChildObject, resolves the child object the way the renderer does (below). The function's docblock lists four carriers instead of "All three carriers" (rider i from 6236510990). Its line-items bullet names the positions the walk visits (a region's components, a container's component children, a slot) and says a block under a page:tabs item's children is not yet visited (patch round 1, contract review 6251625691 item ① 9). The forEachPageComponent docblock and the call-site comment name the new user.
  • packages/spec/src/inline-grid-column-carriers.test.ts. The line-items section's "pinned on purpose" note is gone (rider ii). The CONTROL test "defineStack does not judge an identity-only line-items column" is flipped into a PARITY test: for the same columns, the line-items block and the master-detail block both refuse with STACK_CROSS_REFERENCE_INVALID / 422, one finding each, and the finding text past its location is byte-identical. New cases: refusal on a region, inside a container, under a slot; the object dataSource binding wins over childObject (both directions); an API binding falls back to childObject; a column invalid on its own is left to the props half; controls; an unresolved child object (undeclared, or none) is not judged; the remedy builds. The refusal cases assert the envelope (code, status), the located subject, the column name, the child field's type (field 'crm_invoice_line.amount' is \currency``) and the ruled first sentence.
  • packages/spec/src/ui/component.zod.ts. TSDoc only, on RecordLineItemsProps. Its paragraph said the check "walks the three master-detail carriers and not this block", which this change makes false. It also said the panel draws untyped columns as text "until the .objectui-sha pin carries" objectui#12854. The pin carries it now (see G2). No schema, key or .describe() changes. Measured: this TSDoc block is not emitted into packages/spec/dist/*.d.ts (zero hits for the old or the new text after the build), so it ships no bytes.
  • packages/spec/src/migrations/entries/semantic/18.ui-record-line-items-props-closed.ts. Text only, no id, conversion or matcher change. Its reason said "defineStack's identity-only column check does not reach this block", which this change makes false. Its replacement said "this block draws a column exactly as declared and hydrates nothing from the child object's field", which contradicts the refusal this change adds. The entry now states the identity-only reach in its surface, replacement, reason and acceptance criteria, in the words the object-master-detail-form entry uses for the same reach, plus this change's population census. Patch round 1 (item ① 9): its reason names the three positions the walk visits and says a block under a page:tabs item's children is not yet judged, instead of "wherever a page carries it" (no tracker number in that author-facing text). The census notes that the showcase project detail block sits under a page:tabs item.
  • packages/spec/src/migrations/registry.ts.template (patch round 1, contract review 6251625691 item ① 8). Text only. id and order are unchanged, and so is the list's shape (step18-rationale-merge.test.ts). The STEP18_RATIONALE fragment ui-record-line-items-props-closed (order 57) said the panel draws its columns "with no hydration from the child object's field, so defineStack's identity-only check does not reach it". The upgrade guide prints that fragment just above this entry's reason, and objectstack migrate meta prints it too. It now states the reach the way the master-detail fragment (order 56) states its own: the panel hydrates an untyped column from the child field, so the identity-only check reaches the block, against the child object the panel lists, in a page region's components, a container's component children and a slot. It also says a block under a page:tabs item's children is not yet judged. gen:migration-registry regenerates the gitignored registry.ts; nothing else to commit.
  • .changeset/24190-line-items-hydrated-check.md. @objectstack/spec minor, the ! title shape, Clause-②: yes (narrowing), a FROM → TO table and the one-line fix. Its defineStack bullet names the three positions the walk reaches and the page:tabs exclusion (patch round 1, item ① 9). Its ADR-0087 disposition is not-required (already-registered ui-record-line-items-props-closed): the entry pre-dates the merge base, and this diff makes its text cover this change.

Measured before editing (origin/main ec749fbb44)

  • G1. collectHydratedInlineColumnErrors was at stack.zod.ts:3576 and the "All three carriers" sentence at :3560. The "pinned on purpose" note was at inline-grid-column-carriers.test.ts:615–:620 and the CONTROL test at :740. All matched.
  • G2, the precondition. .objectui-sha is 0707b4ae37120d45d6401170666a0b76cb7a3546. In the objectui clone, git merge-base --is-ancestor 54e79f989 0707b4ae3712 exits 0. Exit 0 proves ancestry in any checkout, so no control leg is owed (control leg run anyway: the pin's 60th ancestor, e43a16207, also exits 0). At the pin, packages/plugin-form/src/LineItemsPanel.tsx hands hydrateColumns(schema.columns, childSchema, …) to the grid. That is the same hydrateColumns from deriveMasterDetail.ts the master-detail carriers call. childSchema is dataSource.getObjectSchema(schema.childObject).
  • G3, how the panel names its child object. There is no relationship-field lookup. The panel reads schema.childObject, and its registration (packages/plugin-form/src/index.tsx, RECORD_LINE_ITEMS_DATA_SOURCE = { object: 'childObject', … }) wraps it in ElementDataSourceGate. When the node carries an object dataSource binding (a plain object with a non-empty string object, isElementDataSourceConfig), the gate writes the binding's object onto childObject over the authored one (next[objectKey] = composed.object, "binding wins" in element-data-source.ts). An API binding (provider: 'api') is not such a config, so the panel reads properties.childObject as authored. lineItemsChildObject follows the same order. This differs from the master-detail block, which has only details[].childObject. The spec declares both channels: RecordLineItemsProps.childObject is optional "because the component-level dataSource binding can supply the object instead", and its docblock says the block's mapping writes the binding's object onto childObject. So this is the declared precedence, not a consumer-side fallback.
  • G4, who authors these columns. Two blocks in examples/app-showcase: project-detail.page.ts (five typed columns) and invoice-document.page.ts (four { name, label } columns, two of them over currency fields). One in skills/objectstack-ui/rules/pages.md (four { name } columns). None carries scale, and scale is the only type-conditional column key today. So no shipped example, skill or fixture is refused by the new check.

Shapes left unjudged

Each of these follows the function's own rule, "an unresolved column is not a wrong one":

Tests

All runs are on the final head ced64a4af (patch round 1 on top of af540b850, which is this branch with origin/main 68a090fb9 merged), through scripts/pm/os-verify-lock.sh. Patch round 1 also ran pnpm --filter @objectstack/spec gen:migration-registry ("wrote src/migrations/registry.ts (475 semantic, 269 retired-key, 224 retired-def)") and pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 --project repo scripts/step18-rationale-merge.test.ts (Tests 9 passed (9)). It ran pnpm check:doc-authoring (exit 0, "19013 customer-facing string(s) across 1429 spec sources clean"), pnpm --filter @objectstack/spec check:upgrade-guide (exit 0) and pnpm --filter @objectstack/spec check:spec-changes (exit 0). The built packages/spec/dist/migrations/index.js carries the new fragment, and the old "does not reach it" sentence is gone. Each reading in the list below was re-taken on ced64a4af. The ablation after the list is the round-1 reading on c07bb2cc8; this round changed no code line it mutated (only a docblock in stack.zod.ts):

  • pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/inline-grid-column-carriers.test.ts: Tests 52 passed (52), VERDICT command-exit 0.
  • pnpm --filter @objectstack/spec typecheck: exit 0 (tsc --noEmit, check:scripts-typecheck, and check:test-typecheck: OK).
  • pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2 (the whole local project): Test Files 694 passed (694), Tests 21050 passed | 1 todo (21051), VERDICT command-exit 0.
  • pnpm --filter @objectstack/spec check:generated: "All 14 generated artifacts are up to date" after pnpm --filter @objectstack/spec build. Nothing was regenerated and the tree is clean.
  • test:repo (the vitest repo project) was not run locally. It belongs to CI's Test Core.

Ablation, one behaviour at a time, with scripts/ablation-replace.mjs (anchor must hit, blob hash moves, restore proven by blob == HEAD and an empty git diff HEAD). The test imports ./stack.zod relatively, so it reads source and no dist/ leg applies.

  1. The anchor if (node.type === 'record:line_items') { was replaced with a type no node carries. Expected and observed direction: RED. Tests 5 failed | 47 passed (52). The PARITY test, the region, container and slot refusals, the binding test and the API-binding test failed, and every control stayed green. Restored: blob 9f842c19fe4c == HEAD.
  2. const binding = node.dataSource; was replaced with undefined. RED, 1 failed | 51 passed: only the binding-precedence test failed. Restored: blob == HEAD.

Gates

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths) was run at ced64a4af and derives 92 commands, the same 92 as at af540b850. All 92 were run locally, each exit code captured before any pipe. --ran reconciliation: "92 derived famil(ies) accounted for — 91 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)".

  • NOT MEASURED: pnpm check:dual-build-cjs-loads. Reason: PREREQUISITE NOT MET, because it reads every package's dist/ (92 packages unbuilt in this worktree). That is a full-monorepo build, which belongs to CI.
  • pnpm --filter @objectstack/lint run check:doc-formula-expressions and pnpm check:lean-entry-closure read built formula, lint and objectql. Those were built before the run (turbo run build --filter=@objectstack/formula --filter=@objectstack/lint --filter=@objectstack/objectql --concurrency=2), and both gates exited 0 inside it.
  • node scripts/check-adr-0087-registration.mjs --base origin/main: exit 0, ".changeset/24190-line-items-hydrated-check.md [BREAKING+bang+clause-②-narrowing] not-required (already-registered)".
  • node scripts/check-changeset-no-major.mjs --base origin/main: exit 0. Its level axis was NOT APPLICABLE locally ("no pull_request payload"), so CI's run on this PR reads the Clause-② line above.
  • pnpm --filter @objectstack/spec run check:api-surface: "public API surface + factory signatures unchanged". pnpm check:nul-bytes: OK.

Acceptance notes

  • Contract-tier review before the queue. This narrows what defineStack / objectstack validate publish, so it owes one review at CONTRACT_REVIEW_TIER, per triage's grade 6238078348.
  • Stale comments outside this card's file surface, not changed. examples/app-showcase/src/ui/pages/invoice-document.page.ts (the comment above its record:line_items columns) says the block "does not fill a column from the child field, so an identity-only { name } column prints its raw field name". examples/app-showcase/test/invoice-document-line-items.test.ts (its header) says the block "draws a column exactly as declared". Both stopped being true when the console pin took objectui#12854, not because of this change. Comment-only, owned by nobody: no card.
  • The page:tabs traversal blind spot (above) is spec(stack): forEachPageComponent never visits a page:tabs item's children, so defineStack's page cross-reference checks (inline action targets, inline grid columns) do not judge anything placed inside tabs #24349.
  • The three text edits outside the claim's listed file surface (the RecordLineItemsProps TSDoc, the step-18 migration entry, and, in patch round 1, its STEP18_RATIONALE fragment in registry.ts.template) are fixes of sentences this change makes false. None changes a schema, a key, an id, an order or a conversion.

Generated by Claude Code

…s against the child field's type

collectHydratedInlineColumnErrors walks the fourth carrier of the inline
grid column, a record:line_items page block's columns, resolving the child
object the way the renderer does (an object dataSource binding's object wins
over childObject). The CONTROL test flips into a parity test against
object-master-detail-form.

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

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

4 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ⚠️ 2 changed file(s) yielded no anchor (packages/spec/src/migrations/registry.ts.template, packages/spec/src/ui/component.zod.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/spec/src/migrations/registry.ts.template, packages/spec/src/ui/component.zod.ts) — pages documenting those are invisible to this run
  • 3 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 143 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json a21fd6c531e50a53deb26a7d04886fbb073164e8 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 9d8e4375a742f41bafad8ded4d01802b4e168c3b — the merge of head ced64a4aff570ff45217b1b865f11001054d6a81 into base a21fd6c531e50a53deb26a7d04886fbb073164e8, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 9d8e4375a742f41bafad8ded4d01802b4e168c3b && git checkout 9d8e4375a742f41bafad8ded4d01802b4e168c3b
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a21fd6c531e50a53deb26a7d04886fbb073164e8 ced64a4aff570ff45217b1b865f11001054d6a81 && git checkout -B drift-repro a21fd6c531e50a53deb26a7d04886fbb073164e8 && git merge --no-ff ced64a4aff570ff45217b1b865f11001054d6a81

node scripts/docs-audit/affected-docs.mjs --json a21fd6c531e50a53deb26a7d04886fbb073164e8

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 (seat check) · PR #24347 · domain:spec seat 2 · zhuangjianguo · session session_019oFY6Hi35J7atvgVFnmoaK · 2026-10-18T14:53Z

ACCEPT at head af540b850df47dfd207cfc42c81cff807d827b3f (claim 6250629938, the dev's report on #24190). A contract review at CONTRACT_REVIEW_TIER is owed: this narrows what defineStack publishes (Clause-②: yes (narrowing)), in non-test packages/spec/src/**. It runs on this head, isolated from the seat; needs:contract-review is on the PR until its record is adopted. The PR lands after a PASS there and a fully green head.

item reading
Shape Draft, base main, assignee zhuangjianguo. The body's line 1 is Fixes #24190, line 3 is Clause-②: yes (narrowing); no other closing keyword. 5 files, +275/-65. The changeset is minor with the ! title, Clause-②: yes (narrowing), one ADR-0087 marker (not-required (already-registered ui-record-line-items-props-closed)), and a FROM → TO table.
The card's three bullets and both riders (1) collectHydratedInlineColumnErrors walks record:line_items columns with the same judge as the other carriers (stack.zod.ts, the page walk). (2) The CONTROL test is now a PARITY test against object-master-detail-form for the same column. (3) Rider (i): the docblock lists four carriers. Rider (ii): the "pinned on purpose" note is gone.
The child object, read against the renderer at the pin 0707b4ae3712 objectui packages/plugin-form/src/index.tsx:666 maps the binding's object onto childObject (RECORD_LINE_ITEMS_DATA_SOURCE), and LineItemsPanel renders inside ElementDataSourceGate. So a binding that names an object wins over an authored childObject, and an API binding names none. lineItemsChildObject follows that order. Right.
Accepted deviation: two text edits outside the listed surface The RecordLineItemsProps TSDoc (component.zod.ts) and the step-18 entry ui-record-line-items-props-closed both said the check does not reach this block. This PR makes that false, so the os-dev rule (fix a published sentence the change falsifies) applies. Both are text only: no id, conversion or matcher changes. The contract review judges them with the rest.
Who is affected (dev's census on ec749fbb44) 2 example blocks and 1 skill block, none carrying scale. No shipped example changes verdict.
Local runs (dev, on af540b850) the carriers file 52/52; spec 694 files and 21050 tests passed; typecheck exit 0; check:generated 14/14. Ablation: removing the line-items branch turned 5 tests red, and removing the binding read turned 1 red; both restores were proven by blob hash. Gates: 92 derived, 91 run with exit 0, 1 NOT-MEASURED (check:dual-build-cjs-loads, exit 3).
Out of scope forEachPageComponent does not visit a page:tabs item's children. Its docblock says it "reaches every container spelling", so inline-action targets and this PR's column check are not judged under tabs. The showcase project detail page's own line-items block sits there. The seat re-read the walk on main and confirms it. The seat files a domain:spec card in this round and links it here.
Landing After the contract review PASS and a fully green head: pr_ready then automerge_enable. The merge closes #24190.

Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: af540b850df47dfd207cfc42c81cff807d827b3f
Local-runs: none
Scope: packages/spec/src/stack.zod.ts,packages/spec/src/ui/component.zod.ts,packages/spec/src/migrations/entries/semantic/18.ui-record-line-items-props-closed.ts,packages/spec/src/inline-grid-column-carriers.test.ts,.changeset/24190-line-items-hydrated-check.md + packages/spec/src/migrations/registry.ts.template,packages/spec/src/migrations/types.ts,packages/spec/scripts/build-upgrade-guide.ts,packages/cli/src/commands/migrate/meta.ts,packages/cli/src/commands/validate.ts,packages/spec/src/ui/page.zod.ts,packages/spec/src/migrations/entries/semantic/18.ui-object-master-detail-form-details-closed.ts,packages/spec/src/migrations/entries/semantic/18.inline-grid-column-identity-only-currency-scale-refused.ts,examples/app-showcase/src/ui/pages/project-detail.page.ts,examples/app-showcase/src/ui/pages/invoice-document.page.ts,skills/objectstack-ui/rules/pages.md,scripts/check-adr-0087-registration.mjs,scripts/check-changeset-no-major.mjs,docs/adr/0087-metadata-protocol-upgrade-contract.md, and at the console pin 0707b4ae3712 objectui packages/plugin-form/src/LineItemsPanel.tsx,packages/plugin-form/src/deriveMasterDetail.ts,packages/plugin-form/src/index.tsx,packages/core/src/data-scope/element-data-source.ts,packages/react/src/hooks/useElementDataSource.ts,packages/react/src/element-data-source/ElementDataSourceGate.tsx

Rendered: 2026-10-18T15:22Z

Inputs, and nothing else: card #24190 (body and all seven comments: 6236510990, 6238078348, 6247443653, 6250222523, 6250629938, 6251308833, 6251342740); PR #24347 (body, the five-file list, and the net diff against the merge base 68a090fb97fe49c685d303d13001b680c50f2f2a, +275/−65); the head's check-runs, read twice (readings in ③). The seat's ACCEPT (6251330275) was read as a seat record and not weighed. Read-only throughout: the shared checkout was read with git show against a ref fetched for this review, the pin with git show in the objectui clone; nothing built, run or re-run.

① Derived judgments

  1. The narrowing itself — RIGHT. defineStack now refuses, with STACK_CROSS_REFERENCE_INVALID 422, a record:line_items column that declares no type, names a currency field of the child object the panel lists, and carries scale. Read against the renderer at the pin: LineItemsPanel.tsx:768 hands hydrateColumns(schema.columns, childSchema ?? undefined, …) to the grid; hydrateColumns (deriveMasterDetail.ts) resolves a type only for a column that declares none, only when the child field exists (if (!d) return col, left as text), through fieldTypeToColumnType, where currency is the only field type that yields currency; and a currency column's scale is unread (reportCurrencyColumnScale, a console.warn). The spec's judge mirrors every branch: a declared type is skipped; an unknown child field is skipped; HYDRATED_INLINE_COLUMN_TYPE is { currency }, equal to the renderer's one currency arm; a column the column schema refuses on its own is left to the component-props gate (no second rule); the verdict is InlineGridColumnSchema.safeParse({ ...column, type: resolved }), the schema's own message. So it refuses exactly the column the pin draws as a currency cell with an ignored scale, and nothing that renders correctly today. The census holds: invoice-document.page.ts (four { name, label } columns, two over currency fields), project-detail.page.ts (five typed columns), skills/objectstack-ui/rules/pages.md (four { name }) — none carries scale.
  2. lineItemsChildObject — RIGHT, the renderer's order exactly. At the pin, useElementDataSource.ts takes schema.dataSource only when isElementDataSourceConfig holds (a plain object with a non-empty string object and no find); it composes that binding, and ElementDataSourceGate.tsx:398 writes next[objectKey] = composed.object with objectKey = 'childObject' from RECORD_LINE_ITEMS_DATA_SOURCE (index.tsx:666): the binding wins over an authored childObject. With no such config — absent, or an API binding, which carries no object (the spec's ElementApiDataSourceSchema is strict and refuses object by guidance) — the schema passes through and the panel reads childObject as authored. The helper reads the node's dataSource as a record whose object is a non-empty string, else props.childObject when that is a string: the same two steps, the same non-empty test, the same fallback. The tests pin both directions of the precedence and the API-binding fallback.
  3. Door reach — RIGHT, and the same as the other three carriers. collectHydratedInlineColumnErrors is one function serving all four carriers; its only caller is validateCrossReferences, whose only caller is buildDefinedStack under defineStack (stack.zod.ts; git grep on main finds no caller of either outside packages/spec). So the new refusal reaches exactly the doors the other carriers' hydrated refusals reach: defineStack, and everything that loads a stack through it — os validate / os build (validate.ts:244: the STACK_* cross-field refusals run inside defineStack only) and the runtime load. composeStacks runs only collectArtifactCrossReferenceErrors and the default-record-page check on the composed set; a hydrated column is judged in each input's own defineStack, as for every carrier. The metadata save door runs the component-props gate (@objectstack/lint's validateComponentProps) and not this check — for all four carriers, before and after this change. No door is missed relative to the carriers; none is gained.
  4. Public surface — RIGHT. No export added (lineItemsChildObject is module-private); check:api-surface reports the surface unchanged; no .describe() moved, so check:docs has nothing to regenerate; TypeScript Type Check, the job that runs every spec gate, is green on this head. The TSDoc edit on RecordLineItemsProps is true after the change: the pin carries objectui#12854 (54e79f989 is an ancestor of 0707b4ae3712, read by triage in 6250222523), and the check now walks this block.
  5. The refusal text — RIGHT. The location reads Page 'NAME' (PATH) record:line_items columns[K].KEY; past the location the finding is byte-identical to the master-detail block's for the same column, and the PARITY test holds that.
  6. The test file — RIGHT. The CONTROL is now a PARITY test (both blocks: STACK_CROSS_REFERENCE_INVALID / 422 / one finding each / identical tail), with refusals at region, nested and slot positions, the binding precedence both ways, the API-binding fallback, the props-half control, the controls, the unresolved child object, and the remedy; the pre-existing pin holding HYDRATED_INLINE_COLUMN_TYPE to the column schema's type-conditional rules stays. No other test on the head pins the old line-items answer (the showcase invoice-document-line-items.test.ts asserts labels and a parse, nothing about hydration; only its header comment is stale, noted by the dev).
  7. The step-18 semantic entry's text — RIGHT. surface, replacement, reason and acceptanceCriteria of ui-record-line-items-props-closed now state the identity-only reach in the words the master-detail entry uses; the replacement no longer says the block "hydrates nothing from the child object's field" (false since docs(spec): record:line_items columns hydrate from the child field, like the master-detail carriers #24188 landed). id, conversion and matcher are unchanged, so the chain's replay is unchanged.
  8. WRONG — the step-18 rationale fragment for this entry still states the opposite of this change. packages/spec/src/migrations/registry.ts.template, STEP18_RATIONALE, fragment id: 'ui-record-line-items-props-closed' (order 57), ends: "The panel draws its columns as authored, with no hydration from the child object's field, so defineStack's identity-only check does not reach it." This diff makes the second clause false (the first has been false since docs(spec): record:line_items columns hydrate from the child field, like the master-detail carriers #24188). That text is load-bearing prose by declaration (MigrationStep.rationale, types.ts:162: "the one place prose is load-bearing") and it is published: build-upgrade-guide.ts:114 prints step.rationale into the Protocol 17 → 18 guide two lines above the semantic list that prints this same entry's corrected reason (:135–:137), so after this diff one page says the check does not reach the block and, a few lines down, that it does; objectstack migrate meta prints the rationale to the author (meta.ts:933) and carries it in its report (:1233). The master-detail fragment (order 56) carries its own reach ("defineStack's identity-only check reaches the block wherever a page carries it, with the reach inline-grid-column-identity-only-currency-scale-refused records"); the parallel fragment for this block owes the same sentence. The dev's own rule for the two text edits — a published sentence this change makes false is fixed in the same change — reaches this one. Remedy: edit the fragment's text only (id and order unchanged; step18-rationale-merge.test.ts holds the list's shape; gen:migration-registry regenerates the gitignored registry.ts; nothing else to commit).
  9. WRONG — the shipped claim "wherever a page carries it" is wider than the walk's reach. The changeset bullet ("now reaches a record:line_items block wherever a page carries it (a region, a container's children, a slot)"), the entry's reason ("the check reaches the block wherever a page carries it", printed by the upgrade guide and migrate meta) and the stack.zod.ts docblock bullet ("wherever the block sits on a page") all claim every position. forEachPageComponent recurses only into arrays under properties whose entries are component-shaped (isComponentNode: an object with a string type); a page:tabs item is { value?, label, children } (PageTabsProps.items, component.zod.ts, children: componentSlot(z.array(z.unknown()))), not component-shaped, so items[].children is never visited. To an author, a tab item's children is "a container's children". The showcase's own project-detail.page.ts block sits exactly there (page:tabs at :38, the block at :74) and is not judged. The gap is in the shared walk, reaches the master-detail and inline-action checks alike, and is spec(stack): forEachPageComponent never visits a page:tabs item's children, so defineStack's page cross-reference checks (inline action targets, inline grid columns) do not judge anything placed inside tabs #24349's subject — ⛔ not to be widened here (it also moves the inline-action check). What is this PR's: the sentence it ships. Keep the claim as narrow as the enforcement (Prime Directive chore: version packages #10): say the three positions the walk reaches and name the exclusion in words (a block under a page:tabs item's children is not yet visited), in the changeset, the entry's reason (no tracker number in prose an author is shown — check:doc-authoring) and the docblock; or drop "wherever". The sibling master-detail text on main carries the same overclaim and is spec(stack): forEachPageComponent never visits a page:tabs item's children, so defineStack's page cross-reference checks (inline action targets, inline grid columns) do not judge anything placed inside tabs #24349's to true up with the walk.
  10. Unchanged and noted, not this PR's claim. The forEachPageComponent docblock's "reaches every container spelling" predates this PR; the diff only adds this check to its user list. The invoice-document.page.ts comment and its test header ("draws a column exactly as declared") stopped being true at the pin bump (chore(objectui): move the console pin from 5e15928b4631 to a green objectui main commit at or after 32b492b67 — it carries the PDF chain's three print fixes (#8346) and the line-items column hydration (#24190) #24283 / PR chore(objectui): move the console pin from 5e15928b4631 to 0707b4ae3712, the newest green objectui main #24311), not here: comment-only, assertion-free, no card owed under Prime Directive chore: version packages #10; a rider on spec(stack): forEachPageComponent never visits a page:tabs item's children, so defineStack's page cross-reference checks (inline action targets, inline grid columns) do not judge anything placed inside tabs #24349 or the next edit to those files is enough.

② Semver level

  • Clause-②: yes (narrowing) on the PR body (line 3) and in the changeset: well-formed, one arm from the closed pair, and what the diff publishes — an accept-set narrowing of defineStack / objectstack validate on the published package @objectstack/spec. (narrowing) is BREAKING; the **BREAKING** banner is present, and the ! title shape is on the PR title and the changeset's first line.
  • minor — RIGHT, the repo's convention. check-changeset-no-major.mjs (header: "During the launch window we ship breaking changes as minor", end condition at GA) and the Check Changeset step's prose (pr-automation.yml:759–:761: breaking-ness "is carried by the BREAKING banner plus the ADR-0087 disposition, not by the level"). Precedents on main, same sentence, same package: .changeset/24162-record-json-retired.md, 24230-marketplace-installed-list-retired.md, 24062-authored-install-scope-project.md; and the direct precedent for this exact reach, 20928-master-detail-details-closed.md as landed in a29a0ea55 ('@objectstack/spec': minor, Clause-②: yes (narrowing), BREAKING). One reading to record: .changeset/pre.json is mode: pre (tag next), so the major guard currently stands aside (its exempt verdict) — minor is the convention's choice, not a forced one, and the convention holds. Check Changeset is green on this head, twice (14:51Z, 14:55Z): the level axis reads the Clause-②: yes line and finds minor on the package whose source moved.
  • ADR-0087 disposition not-required (already-registered ui-record-line-items-props-closed) — RIGHT in kind, incomplete in fact until ① item 8 lands. The gate verifies the id resolves at HEAD and pre-dates the merge base (it does: packages/spec/CHANGELOG.md:9046 registered it; the fragment and entry exist on main); ADR-0087 (:849) says the gate never verifies the entry COVERS the change — "the only thing closing this door is the author's honesty". The honest use is exactly what the diff does to the entry: making its four prose fields cover the identity-only reach, as finding(spec): ObjectMasterDetailFormPropsSchema.details is z.unknown(), a third unjudged carrier of the inline grid column: a bogus key or a typed currency column with scale publishes green #20928's entry does for the master-detail block. The alternatives are worse: registered would add a D3 entry for a change that retires no key, shape or spelling (nothing for migrate meta to rewrite); no-migration-prescription is refused by the FROM → TO table. Editing an already-registered entry's text — not its id, conversion or matcher — is sound under ADR-0087: the chain's "forever artifact" guarantee is the replayability of conversions and steps, the prose fields are declared load-bearing and current (reason: "the one load-bearing prose field"; rationale: "the one place prose is load-bearing"), and the repo amends them in place (70cfb1277, spec(migrations): two step-18 upgrade rationale fragments print a tracker id to the author, "(#11509, ruling A-narrow)", in os migrate meta and the upgrade guide #24179: a text-only fix to the step-18 rationale with its own changeset). But "already covers this change" is the entry AND the step's rationale for it, which the same guide section prints together; with the fragment left as it is, the disposition names an entry whose published rationale denies the change. Fix ① item 8 and the marker stands as written.

③ Boundary flags

  • Dev deviation (1), two text edits outside the claim's listed surface (RecordLineItemsProps TSDoc; the step-18 entry): answered in ① items 4 and 7 — both true after the change, both text only, no schema, key, describe, id, conversion or matcher change; check:api-surface unchanged; the claim's "no change to RecordLineItemsProps" holds for the schema. The rule the dev applied (a published sentence this change makes false is fixed in the same change) is the right rule, and it reaches a third carrier of the same sentence the diff left standing: ① item 8.
  • Dev finding, the page:tabs traversal gap (spec(stack): forEachPageComponent never visits a page:tabs item's children, so defineStack's page cross-reference checks (inline action targets, inline grid columns) do not judge anything placed inside tabs #24349): confirmed on the code (① item 9: isComponentNode plus PageTabsProps.items). Not this PR's to widen. This PR's own shipped text claims the reach it does not have: ① item 9.
  • Dev acceptance note, stale comments in invoice-document.page.ts and its test header: agreed, ① item 10.
  • open_questions: none in the report; none owed.
  • Claim and head: the newest Claim: on the card (6250629938) names claude/issue-24190-line-items-hydrated-check; the dev's report (6251308833) posts no second claim; the PR head is the sha the brief names and the sha the seat's ACCEPT names. The PR object's base.sha snapshot (33f700b8) is not the merge base; the diff above is against the merge base 68a090fb9, the origin/main the dev merged before opening.
  • Governed surfaces: none of the five paths is a register row; Governed Surface Queue Guard is green. This record is owed by the narrowing face alone.
  • Check-runs on the head — the gate verdicts as read. At 2026-10-18T15:03:18Z: 44 runs; Lint & Repo Gates success, TypeScript Type Check success, Build Core success, Governed Surface Queue Guard success, Check Changeset success (two runs), Spec property liveness success, every Lint · and Type Check · lane success; Test Core (1/6) to (6/6) all in_progress; the full-battery jobs skipped on a PR run (Console Pin Gate, Temporal Conformance, the dogfood shards, Build Docs) and the opt-in tarball smoke skipped; no failure. At 2026-10-18T15:17:28Z: Test Core 1, 2, 4, 5 and 6 success, Test Core (3/6) still in_progress; no failure. One required context was still running when this record was rendered; the landing seat reads it to completion. Not a FAIL input.
  • The dev's local runs (52/52 on the carriers file; the spec local project 694 files / 21050 tests; typecheck; check:generated 14/14; the two ablations with blob-hash restores; 92 derived gate families, 91 run, check:dual-build-cjs-loads not measured for want of a monorepo build) are the dev's readings, consistent with the diff and with the green gates; none was re-run here.

Two FAIL items, both prose on shipped surfaces, both one edit each: ① item 8 (the step-18 rationale fragment in registry.ts.template must state the reach, not deny it) and ① item 9 (narrow "wherever a page carries it" to the walk's reach and name the page:tabs exclusion, or drop the word). Everything else in the diff is right as read. A FAIL is per head: the next head carries the fixes and is reviewed again.

Implemented-by: claude/issue-24190-line-items-hydrated-check
Reviewed-by: session_019oFY6Hi35J7atvgVFnmoaK

VERDICT: FAIL


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 (seat check) · PR #24347 · domain:spec seat 2 · zhuangjianguo · session session_019oFY6Hi35J7atvgVFnmoaK · 2026-10-18T15:23Z

REWORK (patch round 1, same claim 6250629938, same dev) at head af540b850df47dfd207cfc42c81cff807d827b3f. The seat adopts contract review 6251625691 (FAIL, two items, both prose on shipped surfaces). needs:contract-review stays on the PR: the fixed head is reviewed again.


Generated by Claude Code

… reach, and every shipped reach sentence names the positions the page walk visits

The rationale fragment for ui-record-line-items-props-closed said the
panel draws columns with no hydration and that defineStack's identity-only
check does not reach it. The changeset, the entry's reason and the
stack.zod.ts docblock said the check reaches the block wherever a page
carries it; the walk visits a region's components, a container's component
children and a slot, and not a page:tabs item's children.

Claude-Session: https://claude.ai/code/session_019oFY6Hi35J7atvgVFnmoaK
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 (seat check) · PR #24347 · patch round 1 · domain:spec seat 2 · zhuangjianguo · session session_019oFY6Hi35J7atvgVFnmoaK · 2026-10-18T16:02Z

Head ced64a4aff570ff45217b1b865f11001054d6a81: one text-only commit over af540b850 (5 files, +25/-15; dev report 6251995185 on #24190). The seat read the diff.

  • ① 8: the STEP18_RATIONALE fragment ui-record-line-items-props-closed (order 57) now states the reach. It names the three positions the walk visits and says a block under a page:tabs item's children is not yet judged. id and order are unchanged.
  • ① 9: the changeset bullet, the entry's reason, the stack.zod.ts docblock bullet and one test title no longer say "wherever"; each names the positions and the page:tabs exclusion. The entry's reason carries no tracker number. The sibling master-detail text keeps its own "wherever" for spec(stack): forEachPageComponent never visits a page:tabs item's children, so defineStack's page cross-reference checks (inline action targets, inline grid columns) do not judge anything placed inside tabs #24349.
  • PR body: the seat applied the dev's 10 old_str/new_str pairs. Each matched the live body exactly once, and the result equals the dev's copy (14852 bytes, read back identical). Fixes #24190 is still the only closing declaration.
  • Next: the contract review runs again on this head. needs:contract-review stays until that record is adopted.

Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: ced64a4aff570ff45217b1b865f11001054d6a81
Local-runs: none
Scope: packages/spec/src/stack.zod.ts,packages/spec/src/ui/component.zod.ts,packages/spec/src/migrations/entries/semantic/18.ui-record-line-items-props-closed.ts,packages/spec/src/migrations/registry.ts.template,packages/spec/src/inline-grid-column-carriers.test.ts,.changeset/24190-line-items-hydrated-check.md + packages/spec/scripts/build-upgrade-guide.ts,packages/spec/src/migrations/types.ts,packages/spec/scripts/step18-rationale-merge.test.ts,packages/spec/src/migrations/entries/semantic/18.inline-grid-column-identity-only-currency-scale-refused.ts,packages/cli/src/commands/migrate/meta.ts,examples/app-showcase/src/ui/pages/project-detail.page.ts,scripts/check-doc-authoring.mjs, and at the console pin 0707b4ae3712 objectui packages/plugin-form/src/LineItemsPanel.tsx,packages/plugin-form/src/deriveMasterDetail.ts,packages/plugin-form/src/index.tsx,packages/react/src/element-data-source/ElementDataSourceGate.tsx

Rendered: 2026-10-18T16:10Z. Round 2: the previous record on this PR, 6251625691 on head af540b850, was a FAIL on two prose items, ① 8 and ① 9. A FAIL is per head, so this head is reviewed in full; each of the two items is named fixed or not below.

Inputs, and nothing else: card #24190 (body and all nine comments: 6236510990, 6238078348, 6247443653, 6250222523, 6250629938, 6251308833, 6251342740, 6251644029, 6251995185); PR #24347 (its refreshed body, the six-file list, the net diff against the merge base 68a090fb97fe49c685d303d13001b680c50f2f2a, +287/−67, and the round-1 record 6251625691); the head's check-runs (readings in ③). The seat's ACCEPT 6251330275 and REWORK 6251633996 were read as seat records and not weighed. Read-only throughout: the shared checkout was read with git show against refs/review/pr-24347-r2, fetched for this review; the pin with git show in the objectui clone; nothing built, run or re-run.

What this head adds over the one reviewed before. One commit, ced64a4af on af540b850: five files, +25/−15. Every changed line is inside a string literal (the changeset, the entry's reason, the template fragment), a docblock, or one test title; filtering those shapes out of the patch leaves nothing. No code token moved, the merge base is unchanged, and the diff against it is the round-0 diff plus that text. So the code judgments of 6251625691 (① items 1 to 7 and 10) hold on this head by identity of the code; they are re-stated in one item below with the pin reads re-taken.

① Derived judgments

  1. ① 8 — FIXED. packages/spec/src/migrations/registry.ts.template, STEP18_RATIONALE, fragment id: 'ui-record-line-items-props-closed', order: 57 (:3210–:3227 on the head). The sentence "The panel draws its columns as authored, with no hydration from the child object's field, so defineStack's identity-only check does not reach it" is gone. It now reads: the panel hydrates a column that declares no type from the child object's field, as the other carriers do, so the identity-only check reaches the block, with the reach inline-grid-column-identity-only-currency-scale-refused records for the first two carriers, against the child object the panel lists (the object dataSource binding's object when the node carries one, else childObject); it judges the block in a page region's components, in a container's component children and in a slot; a block under a page:tabs item's children is not yet judged. Each clause is true on the head: the cited entry's surface is object.fields.NAME.inlineColumns[].scale and view.form.subforms[].columns[].scale, the first two carriers; the child-object order is lineItemsChildObject (stack.zod.ts:3685–:3694); the three positions and the exclusion are forEachPageComponent's reach (item 2). It is the shape fragment 56 (ui-object-master-detail-form-details-closed, :3079–:3095) uses for its own reach — "defineStack's identity-only check reaches the block …, with the reach inline-grid-column-identity-only-currency-scale-refused records for the other two carriers" — narrowed to what the walk visits. id and order unchanged: the whole-file diff base→head touches only text lines (2 removed, 7 added), every id:/order: pair is byte-identical in order, and the list holds 126 fragments before and after; step18-rationale-merge.test.ts's element parser (six-space text lines ending },) matches the new lines. The printed pair is consistent now. joinRationale (:1181–:1186) sorts by order, so fragment 56 and 57 print adjacent; build-upgrade-guide.ts:114 prints step.rationale and :136 prints this entry's reason under the same hop section; migrate meta prints hop.rationale (meta.ts:933) and carries it in the report (:1233). Rationale and reason now say the same thing — reaches, three positions, page:tabs excluded — where the previous head had one deny the other.
  2. ① 9 — FIXED. Added lines of the net diff carry no "wherever" (a grep over every + line: zero hits). The claim is now enumerated in five places and each enumeration equals the walk: the changeset bullet, the entry reason ("the check reaches the block in a page region's components, in a container's component children and in a slot … reach: a block under a page:tabs item's children is not yet judged"), the fragment (item 1), the stack.zod.ts docblock bullet (:3571–:3576, "in the positions forEachPageComponent visits — a region's components, a container's component children, a slot; a block under a page:tabs item's children is not yet visited, so not yet judged"), and the test title (:817, "the other positions the page walk visits"). forEachPageComponent (stack.zod.ts:2406–:2443): roots are regions[].components[] and slots.NAME (one component or an array); recursion is into any array under properties whose entries are component-shaped (isComponentNode, an object with a string type); nothing else. A page:tabs item (component.zod.ts:874–:998, { value?, label, count?, children, … }) has no type, so items[].children is never visited — the exclusion named. No tracker number in author-facing text: the only # numbers the diff adds are in code comments and docblocks, in the test file, and in the changeset's title line (#24190), which is the changeset-title convention on main (23983-previous-null-on-insert.md, 24120-…, 24130-…, 24140-line-items-columns-hydrate.md carry the same tail); the entry's strings, the fragment and the changeset body carry none; check:doc-authoring ran green inside Lint & Repo Gates.
  3. One observation on item 2, not a FAIL — rider to spec(stack): forEachPageComponent never visits a page:tabs item's children, so defineStack's page cross-reference checks (inline action targets, inline grid columns) do not judge anything placed inside tabs #24349. page:accordion declares the same item shape: items: z.array(strictObject({ …, children: componentSlot(…) })) (component.zod.ts:2667–:2727), so a block under an accordion item's children is unvisited for the same reason a tabs item's is. The shipped sentences' positive claim ("a container's component children") is the walk's own rule and does not reach an accordion item, which is not a component node; the exclusion sentence names page:tabs alone, the position the census measured and the one the round-1 record prescribed, and says nothing of "only". The claim is therefore no wider than the enforcement; the exclusion list is one member of a two-member class. spec(stack): forEachPageComponent never visits a page:tabs item's children, so defineStack's page cross-reference checks (inline action targets, inline grid columns) do not judge anything placed inside tabs #24349 owns the walk, and its fix re-trues every sentence that names the walk's reach (these, and the master-detail siblings that keep "wherever": fragment 56, the master-detail entry, the docblock bullet at :3568–:3570); naming the accordion item there, or in these sentences when they are next touched, closes the gap. Not a change this PR owes.
  4. Everything round 1 judged RIGHT still holds — confirmed from the diff, the pin reads re-taken. The narrowing (a record:line_items column with no type, over a currency field of the child object the panel lists, carrying scale, refused STACK_CROSS_REFERENCE_INVALID 422 with the column schema's own message), lineItemsChildObject's order, the door reach (one function under validateCrossReferences under defineStack, the same doors as the other three carriers), the public surface (no export added, check:api-surface unchanged, no .describe() moved), the refusal text (location then a tail byte-identical to the master-detail block's, held by the PARITY test), the test file, and the step-18 entry's four prose fields are the same bytes as on af540b850 except the text in items 1 and 2. At the pin: LineItemsPanel.tsx:36 imports hydrateColumns from deriveMasterDetail, :350 loads ds.getObjectSchema(schema.childObject), :768 hands hydrateColumns(schema.columns, childSchema ?? undefined, …) to the grid; deriveMasterDetail.ts:64–:65 is the one currency arm; index.tsx:666–:667 RECORD_LINE_ITEMS_DATA_SOURCE = { object: 'childObject', … }; ElementDataSourceGate.tsx:398 next[objectKey] = composed.object. The census holds (project-detail.page.ts: page:tabs at :38, the block at :74, five typed columns; invoice-document.page.ts four { name, label }; skills/objectstack-ui/rules/pages.md four { name }; none carries scale), and the changeset and entry now say the showcase project-detail block sits under a page:tabs item, so is not yet judged — true.
  5. RecordLineItemsProps TSDoc (component.zod.ts) — RIGHT, unchanged this round. "walks this block as it walks the three master-detail carriers … judged against the field of the child object the panel lists — the object dataSource binding's object when the node carries one, else childObject" is true after the change; no schema, key or .describe() moved.
  6. Unchanged and noted, not this PR's claim. forEachPageComponent's own docblock ("reaches every container spelling", :2403–:2404) predates this PR and is spec(stack): forEachPageComponent never visits a page:tabs item's children, so defineStack's page cross-reference checks (inline action targets, inline grid columns) do not judge anything placed inside tabs #24349's; the invoice-document.page.ts comment and its test header stopped being true at the pin bump, comment-only, no card owed.

② Semver level

  • Clause-②: yes (narrowing) on the PR body (line 3) and in the changeset: well-formed, one arm from the closed pair, and what the diff publishes — an accept-set narrowing of defineStack / objectstack validate on @objectstack/spec. BREAKING banner present; ! title shape on the PR title and the changeset's first line.
  • minor — RIGHT, the repo's launch-window convention for accept-set narrowings (check-changeset-no-major.mjs; precedent 20928-master-detail-details-closed.md for this exact reach). Check Changeset is green on this head twice (15:58:16Z and 16:03:29Z).
  • ADR-0087 disposition not-required (already-registered ui-record-line-items-props-closed) — RIGHT, and now complete in fact. Round 1 accepted the kind and held it incomplete while the entry's rationale fragment denied the change. With item 1, the registered entry (surface, replacement, reason, acceptance criteria) and the step's rationale for it both cover the identity-only reach, as finding(spec): ObjectMasterDetailFormPropsSchema.details is z.unknown(), a third unjudged carrier of the inline grid column: a bogus key or a typed currency column with scale publishes green #20928's pair does for the master-detail block. check:adr-0087-registration runs inside Lint & Repo Gates, green.

③ Boundary flags

  • Dev deviations, round 1 report (6251995185): (1) registry.ts.template is outside the claim's listed surface — text only, the fix round 1 prescribed; answered in ① 1. (2) The test-title edit beyond the three named sites — the same overclaim in a sentence this PR added; right to fix. (3) Prerequisite builds before the gate battery — a reporting note. Round 0 deviations (the TSDoc and the entry text outside the surface; the -- relaunch; the main merge; the re-run exit codes) stand answered as in 6251625691.
  • open_questions: none in either report; none owed.
  • out_of_scope_findings: round 0's page:tabs traversal gap is spec(stack): forEachPageComponent never visits a page:tabs item's children, so defineStack's page cross-reference checks (inline action targets, inline grid columns) do not judge anything placed inside tabs #24349, not widened here (right: it also moves the inline-action check). Round 1 reports none. This record adds the page:accordion sibling as a rider to spec(stack): forEachPageComponent never visits a page:tabs item's children, so defineStack's page cross-reference checks (inline action targets, inline grid columns) do not judge anything placed inside tabs #24349 (① 3).
  • Claim and head: the newest Claim: on the card (6250629938) names claude/issue-24190-line-items-hydrated-check; neither dev report posts a second claim; the head is the sha the brief names and the PR's head.sha; the merge base is 68a090fb9 (the PR object's base.sha snapshot 33f700b8 is not it). The PR body was refreshed for this round and lists six files; its claims about the fragment, the three positions and the census match what was read.
  • Governed surfaces: none of the six paths is a register row; Governed Surface Queue Guard green. This record is owed by the narrowing face alone.
  • Check-runs on the head — the gate verdicts as read. At 2026-10-18T16:02:44Z: 46 runs; Lint & Repo Gates success, Governed Surface Queue Guard success, Check Changeset success, Check PR Size success, Spec property liveness success, every Lint · lane success, Type Check · source gates success; Build Core, Test Core (1/6) to (6/6), Type Check · consumer gates, · debt ledger, · workspace in_progress; the full-battery jobs skipped on a PR run; no failure. At 2026-10-18T16:06:49Z: Build Core success, Type Check · debt ledger success, the second Check Changeset success; Test Core (1/6) to (6/6), Type Check · consumer gates and · workspace still in_progress; the TypeScript Type Check aggregate not yet listed (it appears when its lanes complete, as Lint & Repo Gates did at 16:01:24Z); no failure. At 2026-10-18T16:09:08Z, the last reading before posting: Type Check · consumer gates success (16:07:59Z); Type Check · workspace and Test Core (1/6) to (6/6) still in_progress; the TypeScript Type Check aggregate not yet listed; no failure among the 46 runs. Two required contexts were still running when this record was rendered; the landing seat reads them to completion — a green head is the landing's condition, not this verdict's input.
  • The dev's local runs (52/52 on the carriers file; step18-rationale-merge.test.ts 9/9; the spec local project 694 files / 21050 tests; typecheck; check:generated 14/14; gen:migration-registry; check:doc-authoring, check:upgrade-guide, check:spec-changes exit 0; 92 derived gate families, 91 run, check:dual-build-cjs-loads not measured for want of a monorepo build) are the dev's readings, consistent with the diff and the green gates; none was re-run here.

Both round-1 FAIL items are fixed on this head: ① 8 (the fragment states the reach, id and order unchanged, the upgrade guide prints a consistent rationale / reason pair) and ① 9 (every shipped sentence names the three positions the walk visits and the page:tabs exclusion, no tracker number in author-facing text). Everything else in the diff is right as read. One rider for #24349 (① 3), nothing owed here.

Implemented-by: claude/issue-24190-line-items-hydrated-check
Reviewed-by: session_019oFY6Hi35J7atvgVFnmoaK

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review adopted · domain:spec seat 2 · session session_019oFY6Hi35J7atvgVFnmoaK · 2026-10-18T16:11Z


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 18, 2026 16:31
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 18, 2026 16:31
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 18, 2026
Merged via the queue into main with commit 8215ff7 Oct 18, 2026
52 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-24190-line-items-hydrated-check branch October 18, 2026 17:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:ui size/m tests tooling

Projects

None yet

2 participants