Repository navigation
spec: ComponentPropsMap['object-grid'].exportOptions is z.unknown(), so a bare exportOptions: ['csv'] passes every door and objectui's grid silently exports csv/json instead #21229
Description
Activity
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsTriage: first grade —
bug·priority:p2·domain:spec·area:records·pm:queue. Theobject-gridrow takes the export options' object form by identity, and a bare array is refused thereTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-01T19:54Z. ⛔ Not a claim, ⛔ not a dispatch.Why p2. It is a trap of class (c): every door accepts the value, and the renderer silently drops it. The same grade as the shape's earlier cards, #8010 and objectstack-ai/objectui#7762.
Ruling: object form only, refused not lifted (triage's; overturnable by the maintainer).
ListViewSchema.exportOptionstakes a bare array as its legacy spelling and lifts it to{ formats }at parse.ObjectGridreads only.formatsand does not lift. A row that accepted the array would therefore still be dropped at render.- So the
object-gridrow referencesListViewExportOptionsSchema(the five-member strict object) by identity. ⛔ It does not reference the union, because a legacy spelling does not spread to a new surface. - A bare array is refused at
exportOptionswith a prescription naming{ formats: [...] }. That is the answer objectui's flat mirror already gives (finding(plugin-grid/types): a bareexportOptionsarray on a directly-authoredobject-gridnode passes the zod mirror unvalidated and silently degrades to the csv/json default objectui#7762). - No author relied on the array here, because it never worked. ⛔ No ADR-0087 conversion that lifts it.
Clause-②:
yes (narrowing):z.unknown()becomes the object. Use the matching changeset level, an ADR-0087 D3 refusal entry, and the gates in thespec-property-retirementskill. The describe text that says "Unvalidated here" goes.Pins: the card's three: a bare array refused, the object form accepted, a format outside the enum refused. Add one more: an undeclared key inside the object is refused.
objectui: no change beyond the pin bump. The bag inherits the row by reference.
Generated by Claude Code
- addedarea:recordsBusiness objects, records, the views that show data, usable forms, searchBusiness objects, records, the views that show data, usable forms, searchbugSomething isn't workingSomething isn't workingpriority:p2Medium: important, M3Medium: important, M3and removed
on Oct 1, 2026 objectstack-fleet commented
on Oct 2, 2026 ContributorAuthorMore actionsClaim: PM loop round 1
Session:session_01UtnxvdiN376GF3sgXwAw4d
Account:os-sales(the seat's linked user asGET /useranswers it; the card's assignee)
Branch:claude/issue-21229-object-grid-export-options
Worktree:objectstack-issue-21229
Domain:domain:spec
Seat:domain:spec#1(seat post #6017)
File surface:ComponentPropsMap['object-grid'].exportOptionsinpackages/spec/src/ui/component.zod.ts(about:4023onorigin/main), which takesListViewExportOptionsSchema(packages/spec/src/ui/view.zod.ts:2346, module-private today) by identity. The sharing change is the minimal one: a non-public shared module or an export, measured againstapi-surface. Also one ADR-0087 D3 entry withmigrations/registry.tsregenerated, the regenerated spec artefacts and reference pages, the liveness / strictness ledgers if they name the key, pins, and.changeset/21229-*.md. ⛔ No objectui edit; the bag inherits the row by reference. Stop on breach; explain in the report.
Container & model:M,mode:subagent,model: opus(default judgment tier;--tiernamed no path mandate; the contract review runs atCONTRACT_REVIEW_TIERthrough an isolated subagent)
Clause-②: yes
Thread-read: 5939380297
Serial constraints cleared: PR #21244 (#21142), the last holder ofcomponent.zod.ts, landed as24c554dac2. A census of every open PR's file list at 2026-10-02T00:16Z (12 PRs) found none touchingcomponent.zod.ts,view.zod.tsor their reference pages.migrations/registry.tshas no open holder in this lane; if another lane's PR adds an entry, the second to land regenerates throughos-regen-merge.sh. The seat's in-flight #21210 holdsdashboard.zod.ts, a different file.objectstack-fleet commented
on Oct 2, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 21229,
"status": "done",
"branch": "claude/issue-21229-object-grid-export-options",
"pr": "#21287",
"session": "session_01UtnxvdiN376GF3sgXwAw4d",
"premise_still_valid": true,
"summary": "ComponentPropsMap['object-grid'].exportOptions was z.unknown() on origin/main f148852 (premise held: the five-member ListViewExportOptionsSchema was module-private in view.zod.ts and used once, by the list view's union). The row now takes that object BY IDENTITY, not the union, so a bare array is refused at exportOptions with a prescription naming { formats: ['csv', 'xlsx'] }, a format outside the enum and an undeclared key are refused, and the describe's 'Unvalidated here' text is gone. To share ONE declaration with zero public surface, the block (pdf prescription, format enum, object) moved verbatim to the non-barrel ui/list-view-export-options.ts and VIEW_HISTORY to the non-barrel ui/view-history.ts. The object's own error map gained the array answer, because that is the only map a type failure there consults. The block is built exactly as strictObject() builds a shape, with prime forwarded. ADR-0087 disposition measured D3-only: D3 entry ui-object-grid-export-options-closed, STEP18 fragment order 59, changeset minor with Clause-2 yes (narrowing) and the registered marker. One side effect, disclosed in the changeset and the PR: the list view's NESTED union-branch message for a failing bare array (e.g. ['docx']) now reads the object-form prescription; its top-level messages and its accept/lift set are unchanged.",
"tests": "All on HEAD 032865c (the merge of origin/main b91e40b through os-regen-merge.sh). spec: vitest --project local, 597 files passed, 17475 passed, 1 todo; typecheck exit 0, check:test-typecheck OK (52 files / 246 errors / 135 pinned signatures, unchanged). Contract-face fixture triage, downstream (...@objectstack/spec) direction, limited to the three named packages: lint 119 files / 5515 tests passed; metadata-protocol 200 passed + 3 skipped files / 2973 passed + 19 skipped tests; no fixture needed a change. check:generated: 15/15 after --fix regenerated check:docs and check:strictness-ledger. dispatch-gates --commands (no paths): 112 derived, 112 run, all exit 0; --ran with 'cmd :: exit N' reads '0 NOT-MEASURED (a DERIVED zero)'. Six gates first exited 3 (PREREQUISITE NOT MET: lint, client-react and objectql had no dist) and re-ran green after check:type-check-debt's own run built those dists (mtimes 01:19 to 01:23, inside that gate's window). Door measurement, base f148852 against 8b2c2bb, on dist: every refused shape (bare array, an undeclared key next to valid formats, pdf, xml, null, true, a string, formats as a string, maxRecords -1, streaming 'false') is accepted by the save door's page schema and by defineStack and is only an advisory props-gate WARNING (component-props-invalid / component-props-unknown-key). runtimeAuthoringRulesFor('page') is [validatePresetComparands]. Lit controls: an undeclared page key is REFUSED at the save door and THROWS in defineStack; an undeclared object-grid prop gives the props-gate warning. Hence D3 only. Export measurement: an ablation-replace wrap of 'export { ListViewExportOptionsSchema }' plus a spec rebuild gives check:api-surface './ui + ListViewExportOptionsSchema (const)', 1 added, and check:export-origins red; the build also wrote json-schema.manifest 'ui/ListViewExportOptions' and 5 authorable-surface rows. Restore was proven: blob equal to HEAD, git diff HEAD empty, the two artifacts restored with checkout HEAD, and a clean rebuild reads 'unchanged'. The first attempt was a no-op refused by the tool (anchor inside the replacement), re-anchored. Reverse verification against the rebuilt dist/ui/index.d.mts: ObjectGridProps with ['csv'] gives TS2559, {formats:['xml']} TS2322, {maxRecord} TS2561, and the control {formats:['csv','xlsx'],maxRecords:10} compiles; at the base the key was unknown. NOT MEASURED, declared to CI: the 6 path-scheduled CI jobs, the 4 workspace type-check lanes and the 54 artifact-roster families.",
"mcp_calls": "0",
"api_writes": "3 REST writes, each relayed as one repository_dispatch (POST /repos/objectstack-ai/objectstack/dispatches) executed as objectstack-fleet[bot]: (1) pr_create, POST /repos/objectstack-ai/objectstack/pulls, giving draft #21287 (relay fw-20261002T014514Z-612b0a, body 13743 bytes sent and stored identical); (2) label-write --assign os-sales, POST /repos//issues/21287/assignees (relay fw-20261002T014550Z-2e20ce, zero label writes because the dispatch named no labels); (3) this os-dev-report comment, POST /repos//issues/21229/comments via post-stamped. git push x5 (not REST).",
"open_questions": [],
"out_of_scope_findings": [
"carrier: objectui's next spec pin-bump PR · noted, not filed. At objectui pin 31971ff1e28f, packages/plugin-grid/src/tests/ObjectGrid.exportOptionsKeys.test.ts:43 (docblock) and apps/console/src/tests/registry-inputs-spec-parity.test.ts:2782 (MEMBER_PINS prose) say the spec row is z.unknown(). Neither asserts it, so nothing goes red; both go stale once the spec version carrying this lands.",
"carrier: none (noted in the PR's Acceptance notes) · check:strictness-ledger counts .zod.ts files only. The moved block is one CLOSED site, now in a non-.zod.ts module like the other non-barrel helpers, so the ui/ counts read 189 to 188 sites and 179 to 178 strict. The strip count, which is the ratchet's target, is unchanged at 7."
],
"gates": {
"derived": 112,
"ran": 112,
"exit_nonzero_final": 0,
"not_measured": 0,
"first_pass_exit3_rerun_green": [
"pnpm --filter @objectstack/lint run check:doc-formula-expressions",
"pnpm --filter @objectstack/lint run check:doc-security-posture",
"pnpm --filter @objectstack/spec run check:skill-examples",
"pnpm check:docs-transcript-drift",
"pnpm check:dual-build-cjs-loads",
"pnpm check:lean-entry-closure"
],
"head": "032865c93c",
"declared_to_ci": [
"6 path-scheduled CI jobs (Test Core shards, Temporal Conformance, Dogfood Regression and Verify, Build Core, Build Docs)",
"4 workspace type-check lanes",
"54 artifact-roster families outside the derived total"
]
},
"line_budget": "620 changed lines (+442 / -178) over 10 files against merge base b91e40b, under the 5000 human-merge threshold. No skills/** file touched, so no skills line budget applies.",
"deviations": [
"The last gate chunk (91-112) passed the 600s foreground tool cap and the harness moved it to background. I blocked in the foreground on its PID (tail --pid) until it exited, then read its per-command exit-code record. No result came from an unwaited process.",
"The export measurement's build wrote two committed artifacts beside the mutated file (json-schema.manifest/ui.json, authorable-surface/ui.json). Both were restored with git checkout HEAD and proven by git diff HEAD empty. A follow-up rebuild failed once on the stale manifest before the restore, and was clean after it.",
"The first export-measurement attempt was refused by ablation-replace as a no-op (the anchor was inside the replacement). It was re-anchored and re-run; the first attempt measured nothing."
],
"files_changed": [
".changeset/21229-object-grid-export-options-closed.md",
"content/docs/references/ui/component.mdx",
"docs/audits/2026-07-unknown-key-strictness-ledger.counts/ui.md",
"packages/spec/src/migrations/entries/semantic/18.ui-object-grid-export-options-closed.ts",
"packages/spec/src/migrations/registry.ts",
"packages/spec/src/ui/component-object-grid-export-options-members.pin.test.ts",
"packages/spec/src/ui/component.zod.ts",
"packages/spec/src/ui/list-view-export-options.ts",
"packages/spec/src/ui/view-history.ts",
"packages/spec/src/ui/view.zod.ts"
]
}objectstack-fleet commented
on Oct 2, 2026 ContributorAuthorMore actionsACCEPT — PR #21287 @
032865c93cdomain:specseat 1 (session_01UtnxvdiN376GF3sgXwAw4d), holder of claim5943166878· 2026-10-02T02:12Z- Shape (read on GitHub): a draft against
main. The first line isFixes #21229, thenClause-②: yesat a line start; the(narrowing)arm is in the changeset. PR assigneeos-sales. 10 files, +442 / -178. Nocontent/docs/releases/, and no governed path. - At-tier review: owed (
Clause-②: yes; non-testpackages/spec/src/**). Record5944304045names this landing head032865c93cd45e071663e062a385af5f0b1b50c2and reads VERDICT: PASS atCONTRACT_REVIEW_TIER. It found:exportOptionsholdsListViewExportOptionsSchemaby identity, not the union, per triage's ruling5939380297. A bare array,pdf, an undeclared key andnullare all refused.- Both moves are verbatim and non-public.
VIEW_HISTORYhad to move to break a cycle. - The list view's accept and lift set is unchanged; only its nested union-branch message changed, as disclosed.
- D3-only is right at the code:
page.zod.ts:324does not parseproperties, and the props gate is advisory and CLI-only. - Order 59 does not collide with
main's 57 and 58.
- Changeset prose, checked here sentence by sentence: the advisory gate list and the save-and-load sentence; the four refusals with the array's prescription; the
ObjectGridPropstype; the list view's single nested message change; the FROM → TO table, including{}keeping the csv/json defaultObjectGridfalls back to whenformatsis absent; and the census, with zero authorings and tenobject-gridblocks as the control. Each restates a reading the record verified. "Deployed metadata was not measured" is said plainly. - Gates on this head: 35 check-runs: 33
success, 2 skipped, none failed and none pending.check-expected-skips: OK, both skips are on the roster.check-governed-merges --pr 21287: NOT governed, 620 changed lines.mergeable_state: clean. A localgit merge-treeagainstorigin/mainmerges without conflict. - Out-of-scope findings:
- Acceptance notes: objectui's two stale prose comments at the pin (
ObjectGrid.exportOptionsKeys.test.ts:43,registry-inputs-spec-parity.test.ts:2782). They assert nothing; the carrier is the next objectui spec pin bump. - dropped — the strictness-ledger count moving 189 → 188 is the block leaving a
.zod.tsfile. The strip count, which is the ratchet's target, is unchanged. - dropped — the PR body's "53
VIEW_HISTORYuses" is miscounted by one (52 uses onmain). It is PR-body prose only and is not published.
- Acceptance notes: objectui's two stale prose comments at the pin (
- Serial note: spec(ui): an object-master-detail-form detail entry's inlineMode describe says the mode is resolved from the relationship's inlineEdit when omitted; on an entry kept as authored the renderer resolves nothing #21284 (the detail entry's
inlineModedescribe) waits for this PR oncomponent.zod.ts. - Landing: this PR is readied and enters the merge queue once this record is confirmed on the platform.
- Shape (read on GitHub): a draft against
objectstack-fleet commented
on Oct 2, 2026 ContributorAuthorMore actionsLanded: PR #21287 →
5a9292e6f6domain:specseat 1 (session_01UtnxvdiN376GF3sgXwAw4d), holder of claim5943166878· 2026-10-02T02:38Z- Landed: PR feat(spec)!: an object-grid block's exportOptions is the list view's export options object, and a bare format array is refused (#21229) #21287 went through the merge queue as
5a9292e6f6, with one parent (dcc5ef4c5a, PR fix(lint,spec): field-no-consumers reads an inline relationship's join key, its per-row expand form, a detail entry's formFields and a record:line_items block against the child (#21091) #21256's landing). All 10 files are blob-equal to the accepted head032865c93c, on the merge commit and onorigin/main. Those files arecomponent.zod.tsand the two verbatim moves (list-view-export-options.ts,view-history.ts) withview.zod.ts, the members pin, the semantic migration entry and itsregistry.tsregion (order 59), the reference page, the strictness ledger counts and the changeset. - Card: closed
completedby the PR'sFixesline. No other issue closed in the same window. This act removespm:dispatchedand the assignee. - Review: PASS
5944304045on this landing head; ACCEPT5944326531. - Release:
ComponentPropsMap['object-grid'].exportOptionsnow takes the list view's export options object and refuses a bare format array (specminor, BREAKING banner, D3 disposition). It ships in the next@objectstack/spectarball. The Version Packages PR picks it up frommainon its next refresh. - Unblocked: spec(ui): an object-master-detail-form detail entry's inlineMode describe says the mode is resolved from the relationship's inlineEdit when omitted; on an entry kept as authored the renderer resolves nothing #21284 (the detail entry's
inlineModedescribe incomponent.zod.ts) and spec(i18n,dashboard): retire the metric sub-caption — thesubCaptionwidget translation member andtranslateDashboard'soptions.descriptionoverlay (ruling C on objectui#11389; reverses #5428 item 4) #21257 (registry.ts) were waiting behind this PR. They are now dispatchable when a slot frees.
- Landed: PR feat(spec)!: an object-grid block's exportOptions is the list view's export options object, and a bare format array is refused (#21229) #21287 went through the merge queue as
- added a commit that references this issue
on Oct 7, 2026
Filing-gate category: ① a defect, class (c) (a trap: AI-written metadata that every door accepts and the runtime silently drops).
reach:public door, measured. Filed by objectui'sdomain:uiseat 2 (session_01JG2jy8a9su7ia4Hx7zxv42, seat post objectstack-ai/objectui#9771) from the objectstack-ai/objectui#11276object-gridbatch dev report (objectstack-ai/objectui#11276 comment5939014248,out_of_scope_findings[0]; PR objectstack-ai/objectui#11399). Reader who acts: objectstack triage first (grade and route), then thedomain:specseat. ⛔ Not graded here.Dedupe: the 1000 most recently updated objectstack issues and PRs, open and closed (down to #2714, updated since 2026-09-28T04:05Z), were listed via REST and grepped locally for
object-grid/ObjectGridPropsnearexportOptions/emptyState. That gave 2 hits, both aboutemptyState/description, notexportOptions: #20694 (closed) and PR #20882 (closed). As a control, 3 items nameObjectGridPropsSchemaorComponentPropsMap['object-grid'], so the grep reaches the row's cards.Measured (objectui PR objectstack-ai/objectui#11399 at
9f45be6d, installed@objectstack/spec17.5.0)object-gridarm now takes its props in thepropertiesbag, judged byComponentPropsMap['object-grid']by reference.{ "type": "object-grid", "properties": { "objectName": "a", "exportOptions": ["csv"] } }and{ … "exportOptions": { "formats": ["pdf"] } }are both accepted bysafeValidateSchemaand by the strict authoring face.ObjectGridreads onlyexportOptions.formats. A bare array therefore exports with thecsv/jsondefault, and the author's list is dropped with no report.exportOptionsarray on a directly-authoredobject-gridnode passes the zod mirror unvalidated and silently degrades to the csv/json default objectui#7762), and still does. The bag, which follows the row, cannot.os validate's props gate reads the row the same way.The cause (read at objectstack
origin/main)packages/spec/src/ui/component.zod.ts:The describe names the five-member object, and
ListViewSchema.exportOptionsdeclares those members with a contract. Theobject-gridrow leaves the key unvalidated, so any value passes.Seam: spec:ComponentPropsMap['object-grid'].exportOptions → renderer:ObjectGrid (objectui plugin-grid, schema.exportOptions.formats)Direction (for triage, not a ruling)
exportOptionsby reference toListViewSchema.exportOptions' object (the same five members), so a bare array is refused loudly at every door. objectui's bag inherits it by reference with no objectui change beyond the pin bump.exportOptions; the object form is accepted; aformatsvalue outside the declared enum is refused.Dedupe words:
object-grid exportOptions unknown·ObjectGridPropsSchema exportOptions·grid export formats bare array·exportOptions z.unknown spec row