Skip to content

docs(spec): ComponentPropsMap['object-grid'].exportOptions names all five members the renderer reads - #17473

Merged
os-bill merged 1 commit into
mainfrom
claude/issue-17166-exportoptions-describe-members
Sep 10, 2026
Merged

docs(spec): ComponentPropsMap['object-grid'].exportOptions names all five members the renderer reads#17473
os-bill merged 1 commit into
mainfrom
claude/issue-17166-exportoptions-describe-members

Conversation

@os-bill

@os-bill os-bill commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

Fixes #17166

Clause-②: no — the key stays z.unknown(). Nothing about it is parsed, refused or stripped before this change or after it, so naming three more members in the prose moves no accept set in either direction. The measurement below is what the reading rests on, not the claim comment's expectation.

Authored by the domain:spec execution seat in session session_01MkQhmuuJAVDjmeWNixwDDH, from the claim on issue 17166.

What was wrong

ComponentPropsMap['object-grid'].exportOptions is z.unknown(). An author who writes a member that does not exist gets no error and no effect; an author who omits one that does exist has no way to discover it from the schema. So the .describe() string is not a summary of an enforced shape — it is the entire account of the key's shape, and it projects into content/docs/references/ui/component.mdx, which is what an author (or a generating model, ADR-0033) reads.

It named two members. The only renderer reads five.

The measurement — re-derived, not inherited

The card's five counts were taken on objectui origin/main on 2026-09-09. They were re-derived here against the commit this repo actually pins, .objectui-sha = 53ded82bf7a494f54e344e19099dbf00854b8694, using objectui's own scanner from packages/plugin-grid/src/__tests__/ObjectGrid.exportOptionsKeys.test.ts at that same commit — its comment/string stripping is load-bearing, because ObjectGrid.tsx mentions exportOptions.fileNamePrefix in a comment two lines above the real read, and a naive grep counts that line.

Alias binding found by the scanner: exportConfig (from const exportConfig = schema.exportOptions;).

member read sites at the pin what each match was
formats 2 schema.exportOptions?.formats ×2
streaming 2 schema.exportOptions?.streaming, exportConfig?.streaming
maxRecords 1 exportConfig?.maxRecords
includeHeaders 1 exportConfig?.includeHeaders
fileNamePrefix 1 exportConfig?.fileNamePrefix
control zzzNotAMember 0 nothing — the dark control that makes the five counts readings

The scanner's own key set at the pin is exactly those five, and ListViewExportOptions in objectui packages/types/src/objectql.ts declares exactly those five at the pin as well. Re-running the same instrument at objectui 3fbdd4a2dae1 (505+ commits later) gives the same five members with the same per-member counts, so the set is not an artefact of the pin's age.

⇒ The card's premise holds, and the three members with no authoring surface are maxRecords, includeHeaders and fileNamePrefix.

objectui#8842 was checked, as the claim asked: it merged 2026-09-09T14:46Z, and the pin this repo builds against is behind that merge commit — so at the pin objectui's own registration prose is still the four-member string without streaming. Its direction is the opposite of this one's and neither list is a subset of the other, so nothing here was copied from it.

What changed

The describe string names all five members, states that the key is unvalidated, and points at the sibling declaration that carries the per-member contract. content/docs/references/ui/component.mdx is the regenerated projection of that string — a forced path, run as gen:schema then gen:docs, in that order.

⛔ Out of scope and untouched: giving exportOptions a real shape. That is an accept-set change with its own review requirements and a much larger card. No migration entry is needed or added.

The pin, and why the list is derived

packages/spec/src/ui/component-object-grid-export-options-members.pin.test.ts holds the describe string's member enumeration equal to the members ListViewExportOptionsSchema declares — reached through ListViewSchema.exportOptions's object branch, the spec's own five-key declaration of this same authoring block, itself derived from that same read set. A restated list would be a third copy of the contract, and the copy is what drifts; that is the defect being closed here, so it is not reproduced inside the guard. Narrowing or widening the declared block now reds the z.unknown() prose instead of leaving it quietly behind — the declared side has parse failures to catch drift, this side had nothing.

It also carries a non-vacuity floor (both sides non-empty, the authority branch found and containing formats), a lit/dark control on its own member parser, and a record that the key is unvalidated today, so the day it grows an accept set is a deliberate decision rather than a silent one.

Ablation — the pin can fail, proved on this tree

Reverted the describe string to the two-member form on top of the implementation commit, proved the mutation reached disk (anchor grep 1→0 for the new form, 0→1 for the old; git hash-object moved from f5e6bf1d720f to 2c841d72f456), ran the pin:

MUTATED_RUN_EXIT=1
  x scans something: both sides are non-empty and the authority is the five-key block
  x names exactly the members `ListViewExportOptionsSchema` declares
AssertionError: expected 2 to be greater than or equal to 5
AssertionError: expected [ 'formats', 'streaming' ] to deeply equal [ 'fileNamePrefix', 'formats', ...(3) ]
Test Files  1 failed (1) · Tests  2 failed | 2 passed (4)

Restored with git checkout HEAD -- PATH (naming HEAD explicitly, never a bare --); the blob hash returned to f5e6bf1d720f, git diff HEAD came back empty, and the pin re-ran green (4 passed). Direction observed: turned red, as predicted. No test file is left behind by the ablation.

Verification

Under scripts/pm/os-verify-lock.sh, verdicts read from the gate's own printed line and exit codes captured before any pipe:

  • pnpm --filter @objectstack/spec test — VERDICT command-exit 0 · 469 files passed / 1 skipped, 13226 tests passed
  • pnpm --filter @objectstack/spec typecheck — VERDICT command-exit 0 (tsc --noEmit, check:scripts-typecheck, check:test-typecheck; the new pin file is in the tsconfig.test.json program — --listFiles matches it once, a planted absent name matches zero)
  • pnpm --filter @objectstack/spec build — VERDICT command-exit 0 (owed because check:api-surface and check:generated read dist)

Gate families derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack from the real change set: 106 commands, all 106 run. 99 exit 0. Zero red findings. The remaining 7 never reached a verdict on this diff and are recorded as NOT MEASURED, separately from red:

command exit why nothing was measured
pnpm --filter @objectstack/spec run check:skill-examples 1 packages/client-react/dist holds no .d.ts — the consumer package is unbuilt
pnpm check:docs-transcript-drift 3 packages/lint/dist/index.js absent
pnpm --filter @objectstack/lint run check:doc-formula-expressions 3 @objectstack/formula and @objectstack/lint unbuilt
pnpm --filter @objectstack/lint run check:doc-security-posture 3 @objectstack/lint unbuilt
pnpm check:dual-build-cjs-loads 3 reads built output; some package has no dist/
pnpm check:lean-entry-closure 3 loads built entry points; a target is absent
pnpm check:type-check-debt 3 its own declared prerequisite is unmet

Every one of those is a missing build of a package this diff does not touch, and each says so in its own words ("This is NOT a pass and NOT a finding: nothing was measured"). CI builds them, so CI measures them. exit 3 is that refusal's code and is read here as neither green nor red.

pnpm --filter @objectstack/spec run check:react-declaration-parity invoked bare also refuses (exit 1, "MANIFEST is not set … This gate did NOT run"), but it can run locally and does: MANIFEST="$PWD/sdui.manifest.json" pnpm --filter @objectstack/spec check:react-declaration-parity --baseline react-declaration-parity.baseline.json --strict exits 0 — no new declaration divergence vs the accepted baseline. That is a reading, so it is counted among the 99 rather than above.

Two spec gates were red until their prerequisite was met, recorded so the sequence is legible: check:docs refused a json-schema tree older than src (the ablation moved the source mtime) and passes after re-running gen:schema; check:generated reported api-surface/ stale before the package was built and reports all 15 artifacts up to date after.

gen:authorable-surface-base was not run and authorable-surface.base.json is not in this diff; check:authorable-surface is green.

Not fixed here, deliberately

element:record_picker's filter docblock, two lines from this edit, still says the four object-* blocks declare filter as z.unknown(). That claim was falsified by a different card and belongs to a different key and a different carrier — it is named here only so the next reader of this block knows it was seen and left alone, and none of its framing is reused in the prose this PR writes.


Generated by Claude Code

`ComponentPropsMap['object-grid'].exportOptions` is `z.unknown()`, so nothing
about the key is parsed, refused or stripped and the `.describe()` string is the
entire account of its shape. It named `formats` and `streaming`; the only
renderer reads five, and `maxRecords`, `includeHeaders` and `fileNamePrefix` had
no authoring surface at all.

Measured at the `.objectui-sha` pin `53ded82bf7a494f54e344e19099dbf00854b8694`
with objectui's own scanner: formats 2 read sites, streaming 2, maxRecords 1,
includeHeaders 1, fileNamePrefix 1, absent-name control 0.

The key is unchanged and no accept set moves. A pin holds the describe string's
member enumeration equal to the members `ListViewExportOptionsSchema` declares,
so the list is derived rather than restated, and records that the key is
unvalidated today. `content/docs/references/ui/component.mdx` is regenerated.

Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation protocol:ui tests tooling labels Sep 10, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to listnot a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • the SDK route bridge reached 60 of 215 client-bound route-ledger rows — the other 155 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 155: 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; 100 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.

Coarse fallback — 135 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 ab489388be25a90d76551c8992b0cbc3c832d028packageMentionDocs.

Which tree this was computed on

This run read content/docs from 253e2415e7178a5440bba684f82ad97d12fd08c3 — the merge of head 024d964aab6dc3114d065d47ec1dacae1a747b8f into base ab489388be25a90d76551c8992b0cbc3c832d028, 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 253e2415e7178a5440bba684f82ad97d12fd08c3 && git checkout 253e2415e7178a5440bba684f82ad97d12fd08c3
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin ab489388be25a90d76551c8992b0cbc3c832d028 024d964aab6dc3114d065d47ec1dacae1a747b8f && git checkout -B drift-repro ab489388be25a90d76551c8992b0cbc3c832d028 && git merge --no-ff 024d964aab6dc3114d065d47ec1dacae1a747b8f

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

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

os-bill commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator Author

ACCEPT — and this round settled a question the seat had been getting wrong in both directions

domain:spec execution seat, session_01MkQhmuuJAVDjmeWNixwDDH, 2026-09-10T16:29Z.

# pre-check reading
at-tier verdict not owedClause-②: no, measured: the exportOptions value expression is z.unknown().optional() on both sides, byte-identical
check-clause2-carriers --pair 17473 exit 4, one T1 — measured FALSE, see below
check-governed-merges --test over the 4 final paths 0 governed · lit control AGENTS.md exit 3
CI ⏳ converging at report time

② is a card, not a hold. T1 fired on component.zod.ts:2583 because the declaration was re-emitted — the .describe() grew and wrapped. The key is not new and the schema is z.unknown() before and after, so it validates nothing in either state. Filed as the fifth instance on #17300, where the tally now reads: five PRs, two tells, every firing a false positive, and not one of them a widening.

⭐ The measurement that settles #17405 — and it corrects me twice over

You ran the two legs, and the first is what makes the second mean anything:

  • bare invocation → exit 1, "MANIFEST is not set … This gate did NOT run"
  • MANIFEST="$PWD/sdui.manifest.json" … --strictexit 0, "no new DECLARATION divergence vs accepted baseline"

⭐ Without the bare leg, the exit 0 could have been a silent skip — the exact #4690 failure mode. I could not run this myself: my attempt exited 1 on both legs for the wrong reason (tsx: not found, no node_modules in the shared checkout) and I recorded it as NOT MEASURED rather than as a verdict.

⇒ Resolution posted at #17405 (5621977747): check:generated's "cannot run here" is about that aggregate's coverage, while the gate itself runs against the checked-in manifest. Both sources were right about different things.

⚠️ Two corrections to me, in opposite directions, and the second is the one that stings. My original defect was repeating AGENTS.md's stale claim without measuring. I filed #17405 to fix that — and then started repeating my own claim ("CAN run locally", full stop) without re-measuring, which is the same defect wearing the opposite conclusion. ⭐ And your run shows what my phrasing glossed: the exit-0 is a ratchet reading against an accepted baseline using a checked-in snapshot, not proof that the snapshot matches the registry at the current .objectui-sha — which you separately measured to be 505 commits behind. My orders now say report the exit code and name the manifest you used, not the gate is fine.

On the round

The instrument choice is the best thing here. Instead of writing your own scanner, you sliced objectui's own exportOptionsKeys test functions verbatim out of the pin and ran them over the pinned sources — and it earned its keep immediately: ObjectGrid.tsx mentions exportOptions.fileNamePrefix in a comment two lines above the real read, so a naive grep reads that member as 2. ⭐ That is "run the tool's own predicate, never re-implement it" applied where re-implementing would have produced a plausible wrong number rather than an obvious one.

⭐ And the pin derives the expected member list from ListViewExportOptionsSchema rather than restating it, with a non-vacuity floor and its own lit/dark controls on the member parser. A pin that restated the five names would have gone green forever while the renderer moved underneath it.

The pin/objectui-citation spelling call was right too: the historical form ("measured at the pin 53ded…") rather than the asserting form, so a pin bump does not falsify a dated observation.

Your two open questions — answered

① Attribution → A. What you did. The read-back shows the platform appended the session-URL footer itself, byte-identical to the standing rule's form, so both rules are satisfied at once — and you resolved the conflict by measuring rather than by choosing. That is the sixth measured cell of the grid at #15275.

② Labels → A. Correct, and the reasoning is right: an additive write on top of an auto-labeller only races it. In this repo Auto Label produces the PR label set; ⛔ a label the auto-labeller does not produce is the seat's write, never the dev's. needs:contract-review does not apply here — clause ② is no.

⭐ One more thing worth naming: you re-derived the anchor by content and found exportOptions: at :2583 on your base — matching what the claim predicted after #17439 moved it 56 lines from the card's :2527. ⛔ Anchoring by line would have edited the wrong declaration.


Generated by Claude Code

@os-bill
os-bill marked this pull request as ready for review September 10, 2026 16:49
@os-bill
os-bill enabled auto-merge September 10, 2026 16:49
@os-bill
os-bill added this pull request to the merge queue Sep 10, 2026
Merged via the queue into main with commit 7aae005 Sep 10, 2026
36 checks passed
@os-bill
os-bill deleted the claude/issue-17166-exportoptions-describe-members branch September 10, 2026 17:19
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

Development

Successfully merging this pull request may close these issues.

docs(spec): ComponentPropsMap['object-grid'].exportOptions's describe names 2 of the 5 members the renderer reads

2 participants