You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[spec] Field.inlineColumns is z.array(z.any()) — a mis-keyed inline-grid column publishes clean and renders blank cells #9227
Found while implementing objectui#3951 (grid columns converge on the declared name key). Out of that card's scope — filed, not fixed.
What
packages/spec/src/data/field.zod.ts:553 declares the inline master-detail grid's explicit column list with no shape at all:
inlineColumns: z.array(z.any()).optional().describe('Explicit columns for the inline grid (derived from the child object when omitted)'),
z.any() means every column object validates: right keys, wrong keys, misspelled keys, empty objects. Publish-time validation has nothing to check against, so the only place a mis-keyed column is ever noticed is the browser, as blank cells.
Why it matters now
objectui#3951 was exactly this failure with the divergence in the renderer instead of the metadata: GridColumnDefinition declared the column key as name while the widget read field, so metadata authored from the published type rendered a grid with the right row COUNT and every cell empty, plus a React missing-key warning. That renderer is now aligned to name (objectui PR for #3951).
What that fix cannot reach is the authoring surface. After it lands, a relationship field authored as
still parses green, still deploys, and still renders a grid of blank cells — because z.any() accepts the retired spelling and the renderer no longer reads it. The same holds for any other typo (lable, colmns, a bare string where an object belongs).
Census taken during objectui#3951: zeroinlineColumns usages across examples/app-crm, examples/app-showcase, examples/app-todo — every inline grid in the example apps takes the auto-derived columns path. So nothing in-repo is broken today; the exposure is for downstream apps and for AI-authored metadata, which is the repo's stated second axis: a lenient producer schema is where generated metadata errors hide.
Suggested direction (for triage, not a decision)
Give inlineColumns a real element schema mirroring ObjectUI's GridColumnDefinition — name (required), label?, type?, width?, required?, plus the column extras the grid reads (options, reference, readonlyWhen, requiredWhen, expression, scale) — and make it strict, so an unknown key is a named rejection at publish time rather than a blank cell at render time. relatedListColumns (field.zod.ts:588) is z.array(z.any()) for the same reason and is worth checking in the same pass; note lookupColumns (:611) already has a typed element schema, so the pattern exists in this file.
Sizing the strictness is a spec decision (which extras are authorable, whether the renderer's column extras belong in the spec at all), which is why this is filed rather than fixed.
Reproduce
Author any object with a master_detail child whose relationship field sets inlineEdit: 'grid' and inlineColumns: [{ field: 'x' }], then FieldSchema.safeParse it — it succeeds. Swap field for a nonsense key such as zzz and it still succeeds.
Triage: decision card — needs-user-decision + domain:spec (semantic face: a typed element schema narrows the acceptance set, i.e. accept/reject behavior changes; clause-② contract-review tier applies whenever this is dispatched), type Task. Rationale: the tightening direction is standing doctrine (strict producer schema over consumer tolerance), but the element shape is a real contract decision — which column extras (options, reference, readonlyWhen, requiredWhen, expression, scale) become authorable spec surface, and whether relatedListColumns (the same z.array(z.any()) shape at field.zod.ts:588) rides in the same pass. lookupColumns (:611) already has a typed element schema, so the in-file pattern exists. Dedup: inlineColumns matches only this card.
Four-prong analysis:
Platform long-term coherence — three sibling column lists in one file under two disciplines (typed vs z.any()); converging on the lookupColumns pattern shrinks the special case rather than growing one.
Measured business pull — zero in-repo (0 inlineColumns usages across the three example apps); the exposure is downstream apps and AI-authored metadata — and objectui#3951 just measured this exact failure (blank cells with the right row count) one seam over.
AI-agent error-resistance — textbook case: z.any() on an authoring surface is where generated-metadata errors hide; a strict element schema turns a mis-keyed column into a named publish-time rejection instead of blank cells at render time.
Startup scope discipline — smallest honest scope is name required plus the keys the renderer actually reads today, strict; declining to admit renderer extras into the spec is a legitimate outcome and should be decided, not defaulted.
Recommendation: approve tightening with a minimal strict element schema mirroring ObjectUI's GridColumnDefinition (name required), rule relatedListColumns in the same pass, and rule explicitly on whether the renderer's column extras enter the spec surface. Blast-radius note: example apps are clean, so the narrowing breaks no in-repo metadata.
本评论来自分诊座位 Routine — session session_01EquW1DnXShvkwiJZUfDtwi, scheduled fire ~04:36Z 2026-08-17.
Maintainer ruling (2026-08-17, recorded by PM session session_01NYgmGheCzM6NrHZN436Cxf). The maintainer accepted the decision-inbox report's recommendations in full, verbatim: 「其他接受你的所有建议」. For this card:
Ruled: approved — inlineColumns gets a real, strict element schema.
Shape mirrors ObjectUI's GridColumnDefinition: name (required), label?, type?, width?, required?, plus the column extras the grid actually reads (options, reference, readonlyWhen, requiredWhen, expression, scale) — the exact authorable-extras set is settled by the implementing dev against the renderer's measured reads, reported in the PR.
relatedListColumns (field.zod.ts:588) checked in the same pass — same z.array(z.any()) disease; lookupColumns (:611) is the in-file precedent to follow.
Migration cost measured at zero in-repo (no inlineColumns usage in any example app); accept-set narrowing on a published authoring surface ⇒ claude-fable-5 mandatory, spec lane.
Label flipped needs-user-decision → pm:queue in the same stroke.
Claim: PM loop round 5
Session: session_01Fs18A2DdXLVN2h8PaaFBcP
Branch: claude/issue-9227-inline-columns-strict
Worktree: objectstack-issue-9227
Domain: domain:spec
File surface: packages/spec/src/data/field.zod.ts (regions: inlineColumns at current :835 and relatedListColumns at :870 — card-body line numbers have drifted), field tests, generated baselines/forms the strictness change moves, .changeset/; objectui is read-only measurement input for the renderer's actual column reads (stop on breach; explain in the report)
Container & model: M, mode:subagent, model: fable — mandatory per the maintainer's 2026-08-17 ruling on this card (accept-set narrowing on a published authoring surface); also the ⚠️#8656 pre-dispatch check (Field builder-object edits ⇒ fable rider) is subsumed by the mandate
Serial constraints cleared: no open PR touches field.zod.ts (checked this round); #9176 / #9310 claimed this same round (this seat) touch metadata-protocol/protocol.ts+protocol.zod.ts and liveness/doc.json respectively — file-disjoint; generated-artifact family overlap possible at landing ⇒ serial relay applies there, not at development. No in-flight sibling's pins assert inlineColumns/relatedListColumns behaviour.
Census re-verification on current main (the ruling's precondition) found that authored inlineColumns usage HAS appeared since the issue's census: invoice.object.ts lines ~213-221 author seven column entries in the retired field spelling ({ field: 'product' } ...), landed 2026-08-16 alongside #9123. The narrowing this card orders refuses exactly that spelling, so the fixture is in this PR's fixture-triage radius (respell to the ruling's prescribed name key). Sequencing consequence vs the objectui pin will be reported in the dev report as an open question. No open PR touches this file (checked 2026-08-17).
{
"issue": 9227,
"status": "needs_decision",
"branch": "claude/issue-9227-inline-columns-strict",
"pr": "https://github.com/objectstack-ai/objectstack/pull/9355",
"premise_still_valid": true,
"summary": "Implemented in full per the ruling; needs_decision is scoped to LANDING SEQUENCING, not the implementation. inlineColumns now parses as strict name-keyed InlineGridColumnSchema admitting exactly the objectui grid renderer's measured live-read set (20 keys; delta vs the ruling's shortlist recorded in the PR — adds prefix/step/displayField/idField/multiple/accept/defaultHidden/computed/autofill, and 'expression' lands as the measured pair computed+expr with the CEL-envelope form refused because evalArith reads a bare string); the retired field spelling is refused with the prescription naming name. relatedListColumns' measured read set differs, so its schema differs (as the ruling delegates): child field-name STRINGS only — object entries split between two objectui vocabularies (columnIdentity canonical field vs data-table accessor accessorKey-or-name) so the spec-canonical object spelling renders blank cells today; strings are the only fully-hydrated form and match all 3 in-repo usages plus the strings-only record:related_list block. ADR-0087 conversion field-column-lists-canonicalized registered (step-18 chain); liveness ledger drilled for all 20 column child keys. CENSUS FORK (the ruling's stop-and-report clause fired): authored inlineColumns usage appeared since the census — examples/app-showcase invoice.object.ts (landed 2026-08-16, #9123) authors 7 entries in the retired field spelling; this PR migrates them to name per the ruling's own prescription. Sequencing consequence in open_questions.",
"tests": "At head fde7df9a7 (post-merge of origin/main): pnpm --filter @objectstack/spec exec vitest run — 409 files / 10889 tests passed (incl. new inline-related-columns.test.ts, 15 pins); reverse verification from committed state (schema reverted to origin/main): 13 red / 2 green — every refusal pin red, only pure-accept pins survive z.any(); restored 15/15. spec typecheck OK; check:generated all 13 artifacts current; check:liveness, check:spec-changes, check:upgrade-guide, check:adr-0087-registration (registered field-column-lists-canonicalized), check:spec-parsed-alias, check:merge-driver, check:objectui-changeset, check:quick-reference-counts, check:role-word, check:cross-package-test-inputs, check:nul-bytes, check:dev-prereqs, check:variant-docs, check:strictness-ledger all OK. Ratchet family re-run at fde7df9a7: check:where-matcher / check:query-options-erasure (baseline vs 3397d7e) / check:engine-double-contract / check:type-check-debt ('none above its recorded number') / check:type-check-coverage OK. Consumer sweep (downstream direction): @objectstack/example-showcase validate + typecheck + test OK (334 tests). Gate list re-derived from actual diff via dispatch-gates.mjs; gates it added beyond the dispatch's named set and run green: check:spec-parsed-alias (forced InlineGridColumnParsed alias), check:adr-0087-registration (forced the registered conversion), check:liveness (forced drilling the 20 child keys).",
"open_questions": [
{
"question": "Census fork + landing sequencing: the ruling's zero-migration premise changed (showcase invoice authors 7 inlineColumns entries in the retired field spelling, landed 2026-08-16), and the .objectui-sha pin (665661ab) predates objectui#3951 — the PINNED console reads field end-to-end, while objectui main reads name. The PR migrates the fixture to name (required for green CI and correct against objectui main/the next pin), but the currently published console will render the showcase invoice inline grid with blank cells until the pin crosses objectui#3951. When should this land?",
"options": [
"A: Land after (or together with) the next objectui pin bump that crosses objectui#3951 — no window where the shipped console renders the showcase fixture blank.",
"B: Land now and accept the transient showcase-only cosmetic regression in the published console — the spec contract itself is pin-independent (publish-time refusals), and dev-server (:5180) rendering is already correct.",
"C: Land now but drop the fixture's explicit inlineColumns (revert to auto-derived columns) — avoids the blank window but regresses the fixture author's deliberate curation opt-out (7th column demotes the receipt file column)."
],
"recommendation": "A, because it is the only option with no user-visible broken window (real business need: the showcase app is the demo surface); it matches long-term direction (the name-keyed contract is where both repos converge, no workaround code in any option); and it costs nothing in error-resistance — the spec refusals are identical in all three options. B is acceptable if a pin bump is imminent anyway; C sacrifices a deliberate fixture decision and is not recommended."
}
],
"out_of_scope_findings": [
"filed as objectstack-ai/objectui#5022: RelatedList object columns — the spec-canonical field spelling passes every RelatedList read (columnIdentity) then renders header + blank cells at the data-table accessor (accessorKey || name only); the two column vocabularies never meet."
]
}
ACCEPT — PR #9355 (reviewer of record: spec seat, session session_01Fs18A2DdXLVN2h8PaaFBcP, round 6), parked in draft under a named release condition (below).
Verified on GitHub, not from the report's self-description:
The ruling is implemented in full: inlineColumns is a strict name-keyed element schema admitting exactly the measured 20-key live-read set (per-key read counts in the PR body — the ruling's own delegation "settled by the implementing dev against the renderer's measured reads"); the retired field spelling refuses with the prescription naming name; relatedListColumns was checked in the same pass and its measurement legitimately produced a DIFFERENT schema (strings only — the object spellings split between two objectui column vocabularies and the spec-canonical one renders blank cells today; the objectui-side gap filed as objectui#5022). ADR-0087 conversion field-column-lists-canonicalized registered on the step-18 chain; liveness ledger drilled for all 20 child keys.
The census fork fired exactly as the ruling ordered: authored usage appeared post-census (showcase invoice.object.ts, 7 entries in the retired spelling, landed 2026-08-16) — the dev stopped, reported, and migrated the fixture per the ruling's own prescription, declared as a claim amendment.
Scope: 15 files, all within the claimed surface + the amended fixture. Reverse verification: schema reverted ⇒ 13/15 pins red (only pure-accept survives z.any()), restored green.
Clause-② enqueue gate: dispatch tier claude-fable-5 = CONTRACT_REVIEW_TIER — at the floor, may enqueue when released.
Sequencing ruling (PM discretion — task ordering/dependency is a named non-escalation class): option A. The .objectui-sha pin (665661ab, verified this round) predates objectui#3951, so landing now would render the migrated showcase fixture as blank inline-grid cells in the published console — a visible demo-surface regression window that option A alone avoids, at zero cost to the contract (refusals are identical in all options).
Release condition (named trigger, mechanically checkable): .objectui-sha on origin/main advances to a commit at or past the objectui#3951 merge ⇒ this seat re-reads PR #9355's mergeable_state (one more os-regen merge round if main moved on its generated surfaces), verifies gate-job conclusions, and flips ready + auto-merge. The bump itself is queued as #9356 (devx lane, filed this round; any earlier bump crossing objectui#3951 discharges it).
{
"issue": 9227,
"status": "done",
"branch": "claude/issue-9227-inline-columns-strict",
"pr": "https://github.com/objectstack-ai/objectstack/pull/9355",
"premise_still_valid": false,
"summary": "The dispatch premise (PR #9355 conflicted against main, needs a human-shaped merge) was true when written at 23:39Z but died before this agent could act on it: the domain:spec owning seat (session_01Fs18A2DdXLVN2h8PaaFBcP) had already pushed its own os-regen resolution at ~23:41Z, moving the head fde7df9a75 -> 347b6cf97f, and posted a stand-down request at 23:45Z. This agent had already resolved the conflict independently in its own worktree before seeing that push (local head 0833ac4f75, merged origin/main through e0ff5b742a, four merges further than the owning seat's round). Both resolutions merge with current origin/main (51a46a4408) to the byte-identical tree d7ac34422f, and the PR's own 15 files are byte-identical on the two tips. Nothing was pushed: pushing would have required merging the owning seat's commits into this history to avoid a force-push, adding two commits and restarting CI for a merged tree already proven identical. No PR state was touched (draft flag, labels, assignees, auto-merge). Findings posted as PR comment 5321872943; local branch deleted, worktree removed.",
"tests": "Conflict-resolution correctness was verified with a driver-free probe, because the ordinary local check is unsound here: git merge-tree honours merge=os-regen from .gitattributes and runs this repo's LOCAL merge driver, while GitHub's server-side merge has none — so a local merge-tree can return exit 0 on exactly the generated paths GitHub calls conflicted. Probe run in a --shared clone with merge.os-regen.driver absent (plain built-in 3-way = GitHub semantics). CONTROL, proving the probe can see the failure: merge(origin/main, fde7df9a75) -> 'CONFLICT (content)' on content/docs/references/index.mdx, docs/audits/2026-07-unknown-key-strictness-ledger.counts.md, packages/spec/liveness/state-counts.md — matching GitHub's mergeable_state: dirty. Then merge(origin/main, 347b6cf97) -> clean, tree d7ac34422f; merge(origin/main, 0833ac4f75) -> clean, tree d7ac34422f (same hash). Durability: no merge=os-regen path on main changed at all between 83fe945d6 (the seat's merge point) and 51a46a4408 (current main), so main's five later advances cannot collide with this branch's surface. Full gate union run at this agent's final commit 0833ac4f75, which carries the identical PR diff (15 files, +744/-40 — file set diffed against the pre-merge baseline, identical): pnpm --filter @objectstack/spec check:generated -> 'All 13 generated artifacts are up to date.'; pnpm --filter @objectstack/spec exec vitest run -> 'Test Files 409 passed (409) / Tests 10915 passed (10915)' (10889 at fde7df9a7, +26 arriving with main); pnpm --filter @objectstack/spec typecheck -> green (tsc --noEmit, check:scripts-typecheck, check:test-typecheck 'OK — 55 file(s) / 263 error(s) held'); ratchet family green — check:type-check-debt 'OK — 33 ledger entr(ies) re-measured in 249.1s, 1926 raw tsc error(s) total, none above its recorded number. surplus: none', check:type-check-coverage, check:query-options-erasure, check:engine-double-contract, check:where-matcher; gate family re-derived from the actual changed paths via node scripts/pm/dispatch-gates.mjs and the additions run: check:liveness, check:strictness-ledger, check:empty-state, check:variant-docs, check:adr-0087-registration (+ --self-test), check:merge-driver, check:spec-parsed-alias, check:cross-package-test-inputs (+ scripts/ form), check:doc-authoring, check:doc-formula-expressions, check:docs-audit-scope, check:docs-redirects, check:quick-reference-counts, check:role-word, check:type-source-resolution, check:objectui-changeset, check:changeset-gate-self-tests, check:changeset-no-major, check:empty-changeset, check:dev-prereqs, check:nul-bytes, docs-audit/check-affected-docs — all PASS. check:doc-formula-expressions and check:dev-prereqs were red on first pass solely because the fresh worktree was unbuilt (unresolvable @objectstack/formula/dist, stale dists); green after turbo run build --filter=./packages/* --filter=./packages/*/*. Sibling-survival assertion per os-regen-merge step 4: packages/spec/src/migrations/registry.ts and src/conversions/registry.ts each hold main's full entry set plus exactly one addition ('field-column-lists-canonicalized'); every generated artifact is additive-only against main (api-surface +3/-0, authorable-surface +20/-0, export-origins +3/-0, json-schema.manifest +1/-0). No ablation in this task, so no rebuild claim is made. CI at 347b6cf97 is the owning seat's to read; per the dispatch contract this report is not held for it.",
"open_questions": [],
"out_of_scope_findings": []
}
Activity
os-zhuang commentedon Aug 17, 2026
Triage: decision card —
needs-user-decision+domain:spec(semantic face: a typed element schema narrows the acceptance set, i.e. accept/reject behavior changes; clause-② contract-review tier applies whenever this is dispatched), type Task. Rationale: the tightening direction is standing doctrine (strict producer schema over consumer tolerance), but the element shape is a real contract decision — which column extras (options,reference,readonlyWhen,requiredWhen,expression,scale) become authorable spec surface, and whetherrelatedListColumns(the samez.array(z.any())shape atfield.zod.ts:588) rides in the same pass.lookupColumns(:611) already has a typed element schema, so the in-file pattern exists. Dedup:inlineColumnsmatches only this card.Four-prong analysis:
z.any()); converging on thelookupColumnspattern shrinks the special case rather than growing one.inlineColumnsusages across the three example apps); the exposure is downstream apps and AI-authored metadata — and objectui#3951 just measured this exact failure (blank cells with the right row count) one seam over.z.any()on an authoring surface is where generated-metadata errors hide; a strict element schema turns a mis-keyed column into a named publish-time rejection instead of blank cells at render time.namerequired plus the keys the renderer actually reads today, strict; declining to admit renderer extras into the spec is a legitimate outcome and should be decided, not defaulted.Recommendation: approve tightening with a minimal strict element schema mirroring ObjectUI's
GridColumnDefinition(namerequired), rulerelatedListColumnsin the same pass, and rule explicitly on whether the renderer's column extras enter the spec surface. Blast-radius note: example apps are clean, so the narrowing breaks no in-repo metadata.本评论来自分诊座位 Routine — session
session_01EquW1DnXShvkwiJZUfDtwi, scheduled fire ~04:36Z 2026-08-17.Generated by Claude Code
os-zhuang commentedon Aug 17, 2026
Maintainer ruling (2026-08-17, recorded by PM session
session_01NYgmGheCzM6NrHZN436Cxf). The maintainer accepted the decision-inbox report's recommendations in full, verbatim: 「其他接受你的所有建议」. For this card:Ruled: approved —
inlineColumnsgets a real, strict element schema.GridColumnDefinition:name(required),label?,type?,width?,required?, plus the column extras the grid actually reads (options,reference,readonlyWhen,requiredWhen,expression,scale) — the exact authorable-extras set is settled by the implementing dev against the renderer's measured reads, reported in the PR.namewhenfieldis used), never a blank cell at render time.relatedListColumns(field.zod.ts:588) checked in the same pass — samez.array(z.any())disease;lookupColumns(:611) is the in-file precedent to follow.inlineColumnsusage in any example app); accept-set narrowing on a published authoring surface ⇒claude-fable-5mandatory, spec lane.Label flipped
needs-user-decision→pm:queuein the same stroke.Generated by Claude Code
os-steve commentedon Aug 17, 2026
Claim: PM loop round 5⚠️ #8656 pre-dispatch check (
Session:
session_01Fs18A2DdXLVN2h8PaaFBcPBranch:
claude/issue-9227-inline-columns-strictWorktree:
objectstack-issue-9227Domain:
domain:specFile surface:
packages/spec/src/data/field.zod.ts(regions:inlineColumnsat current:835andrelatedListColumnsat:870— card-body line numbers have drifted), field tests, generated baselines/forms the strictness change moves,.changeset/; objectui is read-only measurement input for the renderer's actual column reads (stop on breach; explain in the report)Container & model:
M,mode:subagent,model: fable— mandatory per the maintainer's 2026-08-17 ruling on this card (accept-set narrowing on a published authoring surface); also theFieldbuilder-object edits ⇒ fable rider) is subsumed by the mandateSerial constraints cleared: no open PR touches
field.zod.ts(checked this round); #9176 / #9310 claimed this same round (this seat) touchmetadata-protocol/protocol.ts+protocol.zod.tsandliveness/doc.jsonrespectively — file-disjoint; generated-artifact family overlap possible at landing ⇒ serial relay applies there, not at development. No in-flight sibling's pins assertinlineColumns/relatedListColumnsbehaviour.Generated by Claude Code
os-steve commentedon Aug 17, 2026
Claim amendment (same session
session_01Fs18A2DdXLVN2h8PaaFBcP, branchclaude/issue-9227-inline-columns-strict): file surface extended withexamples/app-showcase/src/data/objects/invoice.object.ts.Census re-verification on current main (the ruling's precondition) found that authored
inlineColumnsusage HAS appeared since the issue's census:invoice.object.tslines ~213-221 author seven column entries in the retiredfieldspelling ({ field: 'product' }...), landed 2026-08-16 alongside #9123. The narrowing this card orders refuses exactly that spelling, so the fixture is in this PR's fixture-triage radius (respell to the ruling's prescribednamekey). Sequencing consequence vs the objectui pin will be reported in the dev report as an open question. No open PR touches this file (checked 2026-08-17).Generated by Claude Code
Generated by Claude Code
feat(spec): strict element schemas for Field.inlineColumns / relatedL…
feat(spec): register ADR-0087 conversion field-column-lists-canonical…
os-steve commentedon Aug 17, 2026
{ "issue": 9227, "status": "needs_decision", "branch": "claude/issue-9227-inline-columns-strict", "pr": "https://github.com/objectstack-ai/objectstack/pull/9355", "premise_still_valid": true, "summary": "Implemented in full per the ruling; needs_decision is scoped to LANDING SEQUENCING, not the implementation. inlineColumns now parses as strict name-keyed InlineGridColumnSchema admitting exactly the objectui grid renderer's measured live-read set (20 keys; delta vs the ruling's shortlist recorded in the PR — adds prefix/step/displayField/idField/multiple/accept/defaultHidden/computed/autofill, and 'expression' lands as the measured pair computed+expr with the CEL-envelope form refused because evalArith reads a bare string); the retired field spelling is refused with the prescription naming name. relatedListColumns' measured read set differs, so its schema differs (as the ruling delegates): child field-name STRINGS only — object entries split between two objectui vocabularies (columnIdentity canonical field vs data-table accessor accessorKey-or-name) so the spec-canonical object spelling renders blank cells today; strings are the only fully-hydrated form and match all 3 in-repo usages plus the strings-only record:related_list block. ADR-0087 conversion field-column-lists-canonicalized registered (step-18 chain); liveness ledger drilled for all 20 column child keys. CENSUS FORK (the ruling's stop-and-report clause fired): authored inlineColumns usage appeared since the census — examples/app-showcase invoice.object.ts (landed 2026-08-16, #9123) authors 7 entries in the retired field spelling; this PR migrates them to name per the ruling's own prescription. Sequencing consequence in open_questions.", "tests": "At head fde7df9a7 (post-merge of origin/main): pnpm --filter @objectstack/spec exec vitest run — 409 files / 10889 tests passed (incl. new inline-related-columns.test.ts, 15 pins); reverse verification from committed state (schema reverted to origin/main): 13 red / 2 green — every refusal pin red, only pure-accept pins survive z.any(); restored 15/15. spec typecheck OK; check:generated all 13 artifacts current; check:liveness, check:spec-changes, check:upgrade-guide, check:adr-0087-registration (registered field-column-lists-canonicalized), check:spec-parsed-alias, check:merge-driver, check:objectui-changeset, check:quick-reference-counts, check:role-word, check:cross-package-test-inputs, check:nul-bytes, check:dev-prereqs, check:variant-docs, check:strictness-ledger all OK. Ratchet family re-run at fde7df9a7: check:where-matcher / check:query-options-erasure (baseline vs 3397d7e) / check:engine-double-contract / check:type-check-debt ('none above its recorded number') / check:type-check-coverage OK. Consumer sweep (downstream direction): @objectstack/example-showcase validate + typecheck + test OK (334 tests). Gate list re-derived from actual diff via dispatch-gates.mjs; gates it added beyond the dispatch's named set and run green: check:spec-parsed-alias (forced InlineGridColumnParsed alias), check:adr-0087-registration (forced the registered conversion), check:liveness (forced drilling the 20 child keys).", "open_questions": [ { "question": "Census fork + landing sequencing: the ruling's zero-migration premise changed (showcase invoice authors 7 inlineColumns entries in the retired field spelling, landed 2026-08-16), and the .objectui-sha pin (665661ab) predates objectui#3951 — the PINNED console reads field end-to-end, while objectui main reads name. The PR migrates the fixture to name (required for green CI and correct against objectui main/the next pin), but the currently published console will render the showcase invoice inline grid with blank cells until the pin crosses objectui#3951. When should this land?", "options": [ "A: Land after (or together with) the next objectui pin bump that crosses objectui#3951 — no window where the shipped console renders the showcase fixture blank.", "B: Land now and accept the transient showcase-only cosmetic regression in the published console — the spec contract itself is pin-independent (publish-time refusals), and dev-server (:5180) rendering is already correct.", "C: Land now but drop the fixture's explicit inlineColumns (revert to auto-derived columns) — avoids the blank window but regresses the fixture author's deliberate curation opt-out (7th column demotes the receipt file column)." ], "recommendation": "A, because it is the only option with no user-visible broken window (real business need: the showcase app is the demo surface); it matches long-term direction (the name-keyed contract is where both repos converge, no workaround code in any option); and it costs nothing in error-resistance — the spec refusals are identical in all three options. B is acceptable if a pin bump is imminent anyway; C sacrifices a deliberate fixture decision and is not recommended." } ], "out_of_scope_findings": [ "filed as objectstack-ai/objectui#5022: RelatedList object columns — the spec-canonical field spelling passes every RelatedList read (columnIdentity) then renders header + blank cells at the data-table accessor (accessorKey || name only); the two column vocabularies never meet." ] }Generated by Claude Code
Generated by Claude Code
os-steve commentedon Aug 17, 2026
ACCEPT — PR #9355 (reviewer of record: spec seat, session
session_01Fs18A2DdXLVN2h8PaaFBcP, round 6), parked in draft under a named release condition (below).Verified on GitHub, not from the report's self-description:
inlineColumnsis a strictname-keyed element schema admitting exactly the measured 20-key live-read set (per-key read counts in the PR body — the ruling's own delegation "settled by the implementing dev against the renderer's measured reads"); the retiredfieldspelling refuses with the prescription namingname;relatedListColumnswas checked in the same pass and its measurement legitimately produced a DIFFERENT schema (strings only — the object spellings split between two objectui column vocabularies and the spec-canonical one renders blank cells today; the objectui-side gap filed as objectui#5022). ADR-0087 conversionfield-column-lists-canonicalizedregistered on the step-18 chain; liveness ledger drilled for all 20 child keys.invoice.object.ts, 7 entries in the retired spelling, landed 2026-08-16) — the dev stopped, reported, and migrated the fixture per the ruling's own prescription, declared as a claim amendment.z.any()), restored green.claude-fable-5=CONTRACT_REVIEW_TIER— at the floor, may enqueue when released.Sequencing ruling (PM discretion — task ordering/dependency is a named non-escalation class): option A. The
.objectui-shapin (665661ab, verified this round) predates objectui#3951, so landing now would render the migrated showcase fixture as blank inline-grid cells in the published console — a visible demo-surface regression window that option A alone avoids, at zero cost to the contract (refusals are identical in all options).Release condition (named trigger, mechanically checkable):
.objectui-shaonorigin/mainadvances to a commit at or past the objectui#3951 merge ⇒ this seat re-reads PR #9355'smergeable_state(one more os-regen merge round if main moved on its generated surfaces), verifies gate-job conclusions, and flips ready + auto-merge. The bump itself is queued as #9356 (devx lane, filed this round; any earlier bump crossing objectui#3951 discharges it).Generated by Claude Code
os-zhuang commentedon Aug 18, 2026
{ "issue": 9227, "status": "done", "branch": "claude/issue-9227-inline-columns-strict", "pr": "https://github.com/objectstack-ai/objectstack/pull/9355", "premise_still_valid": false, "summary": "The dispatch premise (PR #9355 conflicted against main, needs a human-shaped merge) was true when written at 23:39Z but died before this agent could act on it: the domain:spec owning seat (session_01Fs18A2DdXLVN2h8PaaFBcP) had already pushed its own os-regen resolution at ~23:41Z, moving the head fde7df9a75 -> 347b6cf97f, and posted a stand-down request at 23:45Z. This agent had already resolved the conflict independently in its own worktree before seeing that push (local head 0833ac4f75, merged origin/main through e0ff5b742a, four merges further than the owning seat's round). Both resolutions merge with current origin/main (51a46a4408) to the byte-identical tree d7ac34422f, and the PR's own 15 files are byte-identical on the two tips. Nothing was pushed: pushing would have required merging the owning seat's commits into this history to avoid a force-push, adding two commits and restarting CI for a merged tree already proven identical. No PR state was touched (draft flag, labels, assignees, auto-merge). Findings posted as PR comment 5321872943; local branch deleted, worktree removed.", "tests": "Conflict-resolution correctness was verified with a driver-free probe, because the ordinary local check is unsound here: git merge-tree honours merge=os-regen from .gitattributes and runs this repo's LOCAL merge driver, while GitHub's server-side merge has none — so a local merge-tree can return exit 0 on exactly the generated paths GitHub calls conflicted. Probe run in a --shared clone with merge.os-regen.driver absent (plain built-in 3-way = GitHub semantics). CONTROL, proving the probe can see the failure: merge(origin/main, fde7df9a75) -> 'CONFLICT (content)' on content/docs/references/index.mdx, docs/audits/2026-07-unknown-key-strictness-ledger.counts.md, packages/spec/liveness/state-counts.md — matching GitHub's mergeable_state: dirty. Then merge(origin/main, 347b6cf97) -> clean, tree d7ac34422f; merge(origin/main, 0833ac4f75) -> clean, tree d7ac34422f (same hash). Durability: no merge=os-regen path on main changed at all between 83fe945d6 (the seat's merge point) and 51a46a4408 (current main), so main's five later advances cannot collide with this branch's surface. Full gate union run at this agent's final commit 0833ac4f75, which carries the identical PR diff (15 files, +744/-40 — file set diffed against the pre-merge baseline, identical): pnpm --filter @objectstack/spec check:generated -> 'All 13 generated artifacts are up to date.'; pnpm --filter @objectstack/spec exec vitest run -> 'Test Files 409 passed (409) / Tests 10915 passed (10915)' (10889 at fde7df9a7, +26 arriving with main); pnpm --filter @objectstack/spec typecheck -> green (tsc --noEmit, check:scripts-typecheck, check:test-typecheck 'OK — 55 file(s) / 263 error(s) held'); ratchet family green — check:type-check-debt 'OK — 33 ledger entr(ies) re-measured in 249.1s, 1926 raw tsc error(s) total, none above its recorded number. surplus: none', check:type-check-coverage, check:query-options-erasure, check:engine-double-contract, check:where-matcher; gate family re-derived from the actual changed paths via node scripts/pm/dispatch-gates.mjs and the additions run: check:liveness, check:strictness-ledger, check:empty-state, check:variant-docs, check:adr-0087-registration (+ --self-test), check:merge-driver, check:spec-parsed-alias, check:cross-package-test-inputs (+ scripts/ form), check:doc-authoring, check:doc-formula-expressions, check:docs-audit-scope, check:docs-redirects, check:quick-reference-counts, check:role-word, check:type-source-resolution, check:objectui-changeset, check:changeset-gate-self-tests, check:changeset-no-major, check:empty-changeset, check:dev-prereqs, check:nul-bytes, docs-audit/check-affected-docs — all PASS. check:doc-formula-expressions and check:dev-prereqs were red on first pass solely because the fresh worktree was unbuilt (unresolvable @objectstack/formula/dist, stale dists); green after turbo run build --filter=./packages/* --filter=./packages/*/*. Sibling-survival assertion per os-regen-merge step 4: packages/spec/src/migrations/registry.ts and src/conversions/registry.ts each hold main's full entry set plus exactly one addition ('field-column-lists-canonicalized'); every generated artifact is additive-only against main (api-surface +3/-0, authorable-surface +20/-0, export-origins +3/-0, json-schema.manifest +1/-0). No ablation in this task, so no rebuild claim is made. CI at 347b6cf97 is the owning seat's to read; per the dispatch contract this report is not held for it.", "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
Generated by Claude Code
feat(spec): strict element schemas for Field.inlineColumns and Field.…
fix(spec): the protocol 16 → 17 conversion summaries and three descri…
fix(spec): the protocol 17 → 18 conversion summaries state each decis…