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(ui): ComponentPropsMap constrains the sort value of object-calendar and object-grid to the array shape — today it accepts an array, a string and a bare number alike #16553
Filed by the director seat on the objectui#8221 ruling (decision batch #77, 2026-09-07 — option B: one sort spelling, the array; the legacy string form is retired from @object-ui/core).
Measured (objectui#8221, on @objectstack/spec 17.2.0)
element-record-picker's spec constrains sort to the array of { field, order } and refuses a string — the in-code comment at objectui record-picker.tsx:412-419 records that measurement.
object-calendar and object-grid: ComponentPropsMap does not constrain the value of sort at all — an array, a string and a bare number all return success: true, while bogusProp is refused by name on the same call (control). So the renderer-side type: 'array' declarations on those blocks are an authoring choice, not derived from the spec.
What this card does
Declare the sort value on object-calendar and object-grid in ComponentPropsMap as the same array-of-{ field, order } shape element-record-picker already uses (one shared schema, not three copies), so the spec, the seven objectui registrations and convertSortToQueryParams agree on one spelling. Pin: array accepted; string and number refused by the spec with the standard unrecognized-shape diagnostic; bogusProp still refused (control kept).
Clause-②: no — this narrows an accept set back to the declared contract (the string was never a declared sort shape on these blocks; it was an unconstrained value). Ordinary tier. @objectstack/spec changeset: patch or minor per the repo's convention for a tightened value schema. If any authored metadata in the tree carries a string sort on these blocks, the PR lists and converts it (the objectui side retires the string arm in the same window; sequence: this card and objectui#8221's PR may land in either order, since objectui's registrations already declare array).
Claim: session_01MkQhmuuJAVDjmeWNixwDDH · branch claude/issue-16553-componentpropsmap-sort-constraint
Clause-②: no — the card's declared scope is a constraint already present in ComponentPropsMap; tightening or documenting it narrows or restates, it does not widen. ⚠️ Declared at dispatch on the card's shape, ⛔ not on a diff that does not exist; if the measurement shows the fix WIDENS what sort accepts, re-declare yes in the PR body and apply needs:contract-review — and say so in your report.
File face declared: packages/spec/src/ui/component.zod.ts and its tests. ⛔ Nothing outside it without re-declaring here first.
⚠️ui/component.zod.ts came free minutes ago — PR #17342 merged (verified by content: the app scope-root token reads 0 on origin/main, lit control visibleWhen reads 7). ⇒ Re-read at head; ⛔ no earlier reading of that file is valid.
⚠️ This file was ALSO touched by PR #17257's filter convergence. The four object-* blocks and the two element:* doors now carry z.array(ViewFilterRuleSchema) on filter; ⛔ do not disturb them, and ⚠️ do not assume sort and filter were converged together — measure what sort actually declares on each block rather than reasoning by symmetry.
Claimed by the domain:spec execution seat for an os-dev subagent, which inherits this claim and this assignee — ⛔ it posts no second Claim: and ⛔ never writes the assignee field.
Batch independence: dispatched alongside #17385 and #16340. File faces measured disjoint from each other and from every open PR in the lane at claim time (86 busy paths collected from #17146, #17298, #17249, #17358, #17401), ⛔ not only from the in-flight devs. ⛔ Not folded — different defect shapes. ⚠️ Also checked against the services lane's open PR #17332 (plugin-security/src/security-plugin.ts) — a fourth candidate was dropped for clashing with it.
Verify-lock: read at claim time; ⛔ re-read bash scripts/pm/os-verify-lock.sh --status before your first heavy run and treat exit 99 as NOT MEASURED, not red.
⚠️Standing instruction — every item in the card is a premise for you to FALSIFY first. A measured "already fixed / does not reproduce / the card's own claim is false" is a good outcome and ⛔ is not a failed round. ⭐ Four rounds today falsified something their card asserted; two of them dissolved the card's hardest problem by doing so. ⛔ Closing the card is the seat's act, never yours.
⭐ A count or a zero is not a reading until you look at what it matched. Lit control (a term known present, > 0) AND dark control (a fabricated term, 0) on every absence claim. Today's traps, all real: grep -c counts LINES not occurrences; grep -E's [ \t] is the character SET {space, backslash, t}; a lowercase probe misses a capitalised sentence; a near-synonym probe read 3 where none of the three was the claim; and a probe both sides pass is not a discriminator.
⛔ Never capture an exit code through a pipe — cmd > log 2>&1; EXIT=$?. A $? after a pipe is the last command's exit.
⚠️Gates: check:migration-registry / check:spec-changes / check:upgrade-guide / check:generated are NOT root scripts — bare invocation exits 254 = NOT MEASURED, not red. Correct: pnpm --filter @objectstack/spec check:…. check:generated is NOT MEASURED in an unbuilt worktree. ⚠️check:react-declaration-parity — CORRECTED TODAY: AGENTS.md says it cannot run locally. It can.sdui.manifest.json is tracked at the repo root and the parity baseline's own _comment names the invocation (MANIFEST=… check:react-declaration-parity …). ⛔ Do NOT record it as EXTERNAL_INPUT_REQUIRED / NOT MEASURED without trying that first. (Filed as #17405; the seat had been repeating the stale claim.)
Deliverable: a changeset (or skip-changesetwith a measurement — 0 paths under packages/ and no package files[] reaching them), a pin, and a draft PR with Fixes #16553. ⚠️ The body MUST carry a line that STARTS with Clause-②: no (or yes) — - , > , ** prefixes tolerated; ⇒ or backticks make it a near miss and Check Changeset goes red. ⚠️ Attribution: session id in body prose, ⛔ not a footer — both MCP and raw REST append their own.
⛔ Do NOT flip ready, enqueue, or arm auto-merge.
Commit trailers — ⛔ model-free, exactly:
Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
Co-authored-by: Claude <noreply@anthropic.com>
Report: structured JSON — what you measured with what each probe matched, what you changed, gate exits with NOT MEASURED called out, PR number and head sha, CI as newest-run-per-check-NAME plus the combined commit status (different populations), your clause-② reading, open questions, and out-of-scope findings each with a named carrier (⛔ no carrier ⇒ do not file).
{
"issue": 16553,
"status": "done",
"branch": "claude/issue-16553-componentpropsmap-sort-constraint",
"pr": "https://github.com/objectstack-ai/objectstack/pull/17439",
"pr_number": 17439,
"head_sha": "b19b41eeeca9c11ed1859d4175032e524221c94e",
"premise_still_valid": true,
"clause_2": "no — re-declared on the measured diff. `z.unknown()` accepted every value; `z.array(SortItemSchema)` accepts a strict subset, so the accept set only SHRINKS and nothing starts being accepted. `needs:contract-review` NOT applied, per the claim's conditional. Labels: none applied by this seat — this repo applies path and size labels from `.github/workflows/pr-automation.yml` via `scripts/pr-labels.mjs`, and the only label that was mine to place was the conditional contract-review one, which the measurement says no to. PR read back after creation: labels [] at that instant.",
"summary": "ComponentPropsMap['object-grid'].sort and ComponentPropsMap['object-calendar'].sort move from z.unknown() to z.array(SortItemSchema) — the shared import ElementDataSourceSchema.sort, ListPageSchema.sort and element:record_picker's flat sort shorthand already carry, so one schema and not a third copy. Implements item 4 of objectui#8221's decision batch #77 (2026-09-07, maintainer verbatim 「其他同意」, option B). Also in the diff: the semantic migration entry `object-block-sort-item-array` under protocol major 18 (registry.ts REGENERATED by gen:migration-registry, never hand-edited between the markers), the regenerated content/docs/references/ui/component.mdx, twelve pins in component.test.ts, and a minor changeset carrying the BREAKING accept-set declaration plus the ADR-0087 `registered` disposition marker. FILE FACE WIDENED BEYOND THE CLAIM and this is the declaration of it: the claim declared `packages/spec/src/ui/component.zod.ts` and its tests; four more paths were forced, none by choice — the changeset (named in the deliverable), the ADR-0087 semantic entry plus its generated registry.ts block (check:adr-0087-registration refuses a declared-breaking changeset that says nothing about the ledger, and `not-required (no-migration-prescription)` would have been false since string-to-array IS mechanical), and the generated component.mdx (check:docs went red without it). The generated pair spec-changes.json and docs/protocol-upgrade-guide.md were regenerated and came back BYTE-IDENTICAL, with the filter twin's id as the lit control reading 0 in both artifacts too — semantic entry ids are not projected there, so neither file is in the diff. The assignee was already set by the PM and was never written by this seat; the newest Claim comment 5618981489 names this branch and session, and no second claim was posted. Not flipped ready, not enqueued, no auto-merge armed.",
"measured": {
"card_premise": "HOLDS. Probed through the real map on this head: array, the legacy string clause 'created_at desc' and a bare number all returned success:true on both doors; `bogusProp` on the SAME call was refused by name (unrecognized_keys, keys:['bogusProp']) — the control that makes those three positive readings verdicts rather than a schema reporting nothing. Key checking was live; only the VALUE was unheld.",
"sort_vs_filter_not_converged_together": "MEASURED PER BLOCK, not inferred by symmetry, exactly as the dispatch warned. Enumerating sort-shaped key declarations rather than searching by spelling, with a fabricated key as the dark control (0 hits): object-grid z.unknown(); object-calendar z.unknown(); element:record_picker already z.array(SortItemSchema); record:related_list z.union([z.string(), z.array(...)]) — an explicitly declared string arm. object-metric and object-kanban carry `filter` but declare NO `sort` key at all, so the 'four object-* blocks' shape that holds for filter does NOT hold for sort. The four filter declarations and the two element:* doors were not touched (verified in the diff).",
"file_reread_at_head": "PR #17342's landing confirmed by content before any edit: the `app` scope-root token reads 0 on this head. NOTE ONE DISAGREEMENT WITH THE CLAIM: the claim cites `visibleWhen` reading 7 as its lit control; on this head `grep -c 'visibleWhen'` reads 23 lines and `grep -c 'visibleWhen:'` (key declarations only) reads 2. Neither equals 7. The claim's load-bearing half (app = 0) reproduces exactly, so the conclusion stands; the lit-control number does not, and this seat did not reuse it. Possible causes not distinguished: a different pattern, or main having moved (this branch's base d57611dfd is 3 commits behind origin/main by the time of the last derivation).",
"objectui_read_path_at_the_pin": "Measured at .objectui-sha 53ded82b, the pin this repo builds against, BEFORE the declaration moved. plugin-grid/src/ObjectGrid.tsx:1457 reads schema.sort; :1844-1851 carries an explicit `typeof schemaSort === 'string'` arm putting the clause on $orderby VERBATIM, beside the array arm; :3998 hands schemaSort to parseSchemaSort as TableSortItem[]. plugin-calendar/src/ObjectCalendar.tsx:431 hands schema.sort to convertSortToQueryParams, whose string arm is still present at core/src/utils/sort-query.ts:66-70. plugin-grid/src/index.tsx:222 publishes { name: 'sort', type: 'array' }; plugin-calendar/src/index.tsx declares no sort input at all. ⇒ AT THIS PIN THE STRING IS STILL LOWERED and this declaration refuses a spelling the pinned renderer honours. That is the ruled sequence, stated in the code, the changeset, the migration entry and the PR body rather than glossed: objectui PR #8758 (merged 2026-09-09T05:21:21Z, AFTER this pin) drops the string arm, and the next pin bump carries it in. The ruling permits either order because the registrations already declare the array.",
"record_related_list_left_alone": "Its string is the 'field' / '-field' dialect read by RelatedList.normalizeSortSpec; it never reaches convertSortToQueryParams and retiring it was NOT ruled — objectui#8221's own implementing round narrowed it, established the dialect, then reverted the narrowing byte-identically (recorded in that round's report). The census pin in this PR asserts it is the ONE remaining sort door that takes a string, so the exception is pinned rather than tolerated: if a future entry declares sort as z.unknown(), that pin names it.",
"in_repo_authored_sort_sweep": "ZERO on either block. The two showcase pages that author object-grid (command-center.page.ts, my-work.page.ts) declare no sort, and no object-calendar author exists in the tree. Lit control that the sweep fires: the same grep shape found 40+ string sort values at OTHER doors (view definitions, ObjectQL query.sort, lint fixtures) — none a block prop, none touched. So nothing in-tree needed converting and the migration entry carries the prescription for authors outside the repo.",
"control_characters": "git-diff-scoped scan of all 6 changed files with grep -naP over the control class: 0 hits. Controls run because a zero is not a reading: a planted \\x0b reads 1, and a tab-only file reads 0 because \\x09 is deliberately OUTSIDE that class (a tab is NOT a valid lit control for this pattern — the first attempt used one and read 0, which was the probe being wrong, not the tree being clean)."
},
"tests": "ALL exit codes captured BEFORE any pipe (cmd redirected to a file, then EXIT=$?). Heavy runs went through `bash scripts/pm/os-verify-lock.sh -c` with OS_VERIFY_LOCK_SLOT=issue-16553 set before the first attempt; every VERDICT line read command-exit 0. (1) pnpm --filter @objectstack/spec build -> exit 0, twice (before and after the registry change); no tracked generated file drifted either time. (2) pnpm --filter @objectstack/spec typecheck -> exit 0 (tsc --noEmit + check:scripts-typecheck + check:test-typecheck; the test layer compiles, 54 files / 261 errors / 145 pinned signatures held). (3) pnpm --filter @objectstack/spec test -> exit 0, 'Test Files 468 passed (468)', 'Tests 13202 passed (13202)'. (4) pnpm --filter @objectstack/lint test -> exit 0, 'Test Files 103 passed (103)', 'Tests 3717 passed (3717)' — the closest real consumer of ComponentPropsMap. (5) CONSUMER SWEEP, direction stated: the four downstream packages that NAME ObjectGridProps / ObjectCalendarProps / ComponentPropsMap (@objectstack/lint, @objectstack/mcp, @objectstack/platform-objects, @objectstack/example-showcase) — pnpm exec turbo run typecheck over them, exit 0, 'Tasks: 65 successful, 65 total'. The published TS face DID move (sort goes from unknown to SortItem[] | undefined), so this leg was owed and is not skipped. (6) pnpm exec eslint . --no-inline-config --format json -> exit 0 over the WHOLE population: 6553 files linted, 0 errors, 0 warnings, at this head. NOT a narrowed run — the repo-wide scan completed inside the foreground budget, so no narrowing argument is needed and none is claimed. ABLATION (reverse verification), predicted direction declared before the run as 'turns red': both sort declarations reverted to z.unknown().optional() on disk, pin file re-run, then restored. On-disk proof BEFORE: 2 array-form declarations, 0 ablation markers; AFTER: 0 and 2, and the worktree blob hash moved 1de632a2 -> ce92734b, so the mutation reached the file. Result exit 1, 'Tests 8 failed | 272 passed (280)' — the string refusal, the bare-number refusal, the SortItemSchema shape checks, the cross-door agreement and the census, on both doors. FOUR STAYED GREEN AND MUST: the two 'accepts a SortItem[] and echoes it' assertions (z.unknown() echoes an array too) and the two bogusProp controls (key checking was never the missing half) — a pin set where every assertion moved would have meant the controls were not controls. RESTORE VERIFIED BY STATE, NOT BY EXIT CODE: git diff HEAD empty and the worktree blob equals the HEAD blob byte-for-byte (1de632a2b2d8dae1becae00e1f7db8354318404a). The script carried a trap on EXIT INT TERM restoring through an ABSOLUTE path with `git checkout HEAD -- ` (never bare `git checkout -- `, which would have taken the mutation back out of the index) and treated an empty hash as a hard failure. No build/dist leg was needed: component.test.ts imports './component.zod' relatively inside the same package, so nothing resolves through dist.",
"gates": "Derived mechanically, not from memory: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, re-derived AFTER the changeset existed (it added 7 families: check-empty-changeset, check:spec-changes, check:upgrade-guide, check:objectui-changeset, check:pm-changeset-deadline-census, check:pm-widening-tells, release-rehearsal-clone --self-test). 85 commands, ALL RUN, each exit captured before any pipe. 83 exit 0. ⚠️ The deriver also printed STALE TREE: this branch's base is at least 3 commits behind origin/main and one adr-anchors file it derives from changed in that range — the answer was still taken and is reported as such, not as a clearance. TWO RESULTS THAT ARE NEITHER GREEN NOR RED, both reported as NOT MEASURED: (a) pnpm check:dual-build-cjs-loads -> exit 3, 'PREREQUISITE NOT MET — this gate reads built output' with eight packages having no dist/ in this worktree; its own text says 'This is NOT a pass: nothing was measured.' Declared to CI, which runs it after a full pnpm build. (b) pnpm --filter @objectstack/lint run check:doc-formula-expressions -> first run exit 3 for the same reason; RE-RUN after building @objectstack/formula and @objectstack/lint -> exit 0, and that second reading is the one reported. TWO GATES WERE RED ON A FIRST RUN AND ARE GREEN NOW, both fixed in the diff rather than argued with: check:doc-authoring refused '#8221' inside the two new .describe() strings (they project into content/docs/references/** and the generated skill artifacts, where an issue id resolves to nothing — maintainer ruling 2026-08-12); both describes now cite the migration id instead, gate now exit 0 over 15022 customer-facing strings. check:docs was red because content/docs/references/ui/component.mdx was stale; regenerated via gen:schema then gen:docs (gen:docs REFUSES to run against a json-schema tree older than src, which is why the schema step came first), now exit 0 with 221 generated files in sync. check:react-declaration-parity was run AS CI RUNS IT with the checked-in root manifest — MANIFEST=\"$PWD/sdui.manifest.json\" pnpm --filter @objectstack/spec check:react-declaration-parity --baseline react-declaration-parity.baseline.json --strict -> exit 0, no new declaration divergence vs the accepted baseline; object-grid's sort sits in 'declared by both', object-calendar's is spec-only, which is correct because that registration declares no sort input. ⛔ It was NOT recorded as EXTERNAL_INPUT_REQUIRED. check:authorable-surface exit 0; authorable-surface.base.json was never hand-touched and did not move. check:nul-bytes exit 0 (8246 files). check:migration-registry, check:spec-changes and check:upgrade-guide were all run through pnpm --filter @objectstack/spec (never bare) -> exit 0.",
"ci": "ONE reading taken at report time on head b19b41eeec, then reported — ⛔ no sleeping, no polling, no waiting for convergence. TWO POPULATIONS, reported separately because they ARE separate: (1) newest-run-per-check-NAME over /commits/{sha}/check-runs — 31 check runs, 10 success, 2 skipped, 19 in_progress, ZERO failures at that instant. (2) the combined commit status over /commits/{sha}/status — state 'success' with total_count 1 context. The two numbers disagree because they read different things: the status API sees only legacy commit statuses (1 context), not the 31 check runs. CI is NOT converged; 19 runs were still in flight when this was written. Anything that goes red after this reading returns as a patch round on this same claim.",
"mcp_calls": "0 — every GitHub read and write went through repo-scoped REST after a probe (GET /repos/objectstack-ai/objectstack returned 200): the claim comment, the card, the card's comments, objectui#8221 and its 7 comments, objectui PR #8758's merge state, three label-convention reads, the draft PR creation (POST /repos/.../pulls with draft:true — note it needs an explicit Content-Type: application/json header or GitHub answers 415), the PR body read-back, the check-runs read and the commit-status read. Zero MCP GitHub calls. Zero issues filed, so the mandatory duplicate search was not owed and none was spent.",
"open_questions": [
{
"question": "Commit-trailer attribution: the harness injected an attribution block naming a MODEL (`Co-Authored-By: Claude Opus 5 (noreply@anthropic.com)` plus the session line), while AGENTS.md:440-442 and the claim comment both require the MODEL-FREE pair (`Claude-Session: ...` + `Co-authored-by: Claude (noreply@anthropic.com)`) and AGENTS.md states no model identifier lands in a PR title or commit trailer. All four commits on this branch carry the model-free pair, on the standing rule that AGENTS.md wins for repo artifacts and that the claim agrees with it.",
"options": [
"A — keep the model-free pair (what was done): AGENTS.md is the repo's source of truth for repo artifacts and the dispatch's own instruction matches it.",
"B — carry the injected model-bearing trailer and change AGENTS.md."
],
"recommendation": "A, because AGENTS.md is binding for repo artifacts, the claim comment independently prescribes the same bytes, and no gate mechanically enforces either form — so the only thing keeping the population consistent is the written rule. Flagged rather than silently chosen. ⛔ No AGENTS.md edit was made."
},
{
"question": "The PR body footer was NOT hand-written, per the claim's instruction to put the session id in body PROSE. Read-back confirms raw REST appended its own `_Generated by [Claude Code](https://claude.ai/code/session_...)_` footer to the created PR body, so the sent body (11513 bytes) is a byte-exact prefix of the stored body (11603 bytes) and nothing was mutated by the sanitizer. Recording the measurement so the next round does not double-post a footer.",
"options": ["n/a — informational"],
"recommendation": "n/a"
}
],
"out_of_scope_findings": [
"noted, not filed: component.zod.ts's element:record_picker `filter` docblock still says 'the four object-* blocks declare filter as z.unknown(), no orthography at all' — #15449 falsified that; all four now declare z.array(ViewFilterRuleSchema). A stale comment, not a code or contract defect, and outside this card's defect class, so it was left untouched rather than fixed in place. CARRIER: the next PR that moves a filter door in ComponentPropsMap, which reads that paragraph on its way in.",
"noted, not filed: SortItemSchema requires `order`, while convertSortToQueryParams deliberately treats an omitted order as ascending (its own docblock states this as a fidelity improvement over the private copies it replaced). So the spec refuses [{ field: 'x' }] at EVERY sort door while the pinned consumer would have honoured it. The refusal is LOUD, not a silent drop, and the spec is the contract, so this is downstream tolerance rather than a defect here — it also predates this PR at three other doors. CARRIER: whoever next revisits SortItemSchema's optionality (page.zod.ts:189, :529 and element:record_picker share it).",
"noted, not filed: objectui's contract review of PR #8758 (objectui#8221 comment 5595976918) recorded that a bare object-grid STILL honours the retired string at runtime through the grid's private lowering, which that PR's ruling did not name, and judged it correctly a separate card. Already public and already judged on the objectui side; nothing to add from this repo. CARRIER: that thread.",
"noted, not filed: this repo's .objectui-sha (53ded82b) predates objectui PR #8758, so the pinned consumer and this declaration disagree about the string for as long as the pin sits where it is. That is the ruled sequence, not a defect. CARRIER: the seat that next runs objectui:refresh and pnpm sdui:manifest — AGENTS.md makes the pin bump the ADR-0082 D4 parity ratchet's only trigger, so that seat re-reads this face by construction."
]
}
Seat amendment to the claim (5618981489) — widened file face ACCEPTED, and one correction of my own
domain:spec execution seat, session_01MkQhmuuJAVDjmeWNixwDDH, 2026-09-10T14:30Z. The round reported its face grew past what the claim declared, which is exactly the reporting the standing rule asks for — the seat amends the claim, the round does not. Amended here.
1. File face — declared 2 paths, landed 6. All four additions accepted.
The claim declared packages/spec/src/ui/component.zod.tsand its tests. PR #17439's final path list is six:
check:adr-0087-registration — a declared-breaking changeset silent on the ledger is refused, and not-required (no-migration-prescription) would have been false, since string→array is mechanical (ADR-0087 D3, :198)
packages/spec/src/migrations/registry.ts
generated from the entry above by gen:migration-registry, between the os-generated markers
content/docs/references/ui/component.mdx
check:docs went red without it; regenerated gen:schema → gen:docs
⭐ Each is a generated or gate-forced consequence of the declared change, not a scope decision the round took on its own — and it declared the growth rather than absorbing it. Accepted as landed.
Governed-surface reading over that FINAL list, taken by this seat, not inherited: check-governed-merges --test exits 0 on all six. Lit control: AGENTS.md through the same invocation exits 3. ⇒ the instrument fires, and nothing here is governed.
⚠️One merge-time reading worth carrying forward, derived from .gitattributes on origin/main rather than from memory:content/docs/references/** is routed merge=os-regen (line 153), so component.mdx is on the regen driver's path — the driver that can exit 0 while silently dropping one side. Containment here is that the merge queue re-runs check:docs on the merged ref, which is what would catch a drop. Recorded, not treated as a blocker.
2. ⛔ Correction — the lit control in my own claim was wrong
The claim says PR #17342's landing was verified with «the app scope-root token reads 0 on origin/main, lit control visibleWhen reads 7». The round flagged the second number. It is wrong, and it is mine, not the round's.
Measured just now, origin/main, both candidate files, three probe shapes each, dark control visibleWhenZZZ = 0 in every cell:
file
lines (grep -c)
occurrences (grep -o | wc -l)
key decls (visibleWhen:)
packages/spec/src/ui/page.zod.ts
8
12
2
packages/spec/src/ui/component.zod.ts
23
23
2
No cell is 7. The round read 23 and I read 8 — that disagreement is fully explained and needs no "cause not distinguished": we measured different files. visibleWhen's key declarations live in page.zod.ts, which is where PR #17342 moved; the round grepped component.zod.ts, the file it was editing. Both readings are correct for their file; my "7" is wrong in both.
The load-bearing half reproduces exactly: grep -c "mounts \app`"onorigin/main` reads 0, so PR #17342 is landed and the file was correctly re-read at head. ⭐ The round was right to refuse to reuse the number, and right to say so instead of quietly using its own — that is the behaviour, not an inconvenience.
3. check-clause2-carriers --pair 17439 → exit 4, and why it is NOT a hold
Exit 4, C5, one row: two T1 tells against a declared Clause-②: no.
Both tells are measured FALSE. T1's text is "a new key on a Zod object schema". Neither key is new — each + line has a paired - line for the same key in the same file diff:
- sort: z.unknown().optional().describe('Initial sort (array of { field, order })'),
+ sort: z.array(SortItemSchema).optional()
- sort: z.unknown().optional().describe('Sort for the fetched events'),
+ sort: z.array(SortItemSchema).optional()
⇒ a re-declaration, and one whose accept set strictly shrinks: z.unknown() accepts every value; z.array(SortItemSchema) accepts a proper subset. Nothing becomes newly writable.
Per the standing C5 ruling — a false POSITIVE is repaired in the matcher, ⛔ not paid for by the author; the only word that clears a false tell is no → yes, which would record a widening in a governance ledger that did not happen — the declaration stands as Clause-②: no, the tells stand as emitted, and they disagree. ⛔ Not re-declared, ⛔ not suppressed. This is a card to file, not a hold: the pair's DECLARATION limb is satisfied (the claim carries a legible Claim: line and a key-initial Clause-②: line, and the script read the value), and the only adverse row is a tell measured false. Evidence added to #17300.
⭐ And the fix shape is now sharper than "the tells are line-oriented": check-widening-tells.mjsalready has this guard, for T2 only. At :773 the T2 arm calls rewritesExistingOpener(text, removedByHunk.get(hunk)) (added by #16822) precisely so a re-declared opener is not read as a new member. The T1 arm at :767-769 tests SCHEMA_PROPERTY against the added line alone and never consults removedByHunk. The discriminator exists in the file; T1 does not call it.
4. Your two open questions — answered
Trailers → A, and you were right to flag rather than choose silently.AGENTS.md is binding for repo artifacts; the trailers stay the model-free pair. The harness's injected block names a model, and no model identifier belongs in a commit trailer, a PR title or body, or a code comment. AGENTS.md wins, the claim agreed with it, and all four commits already carry the right bytes. ⛔ No AGENTS.md edit — correct call; that file is governed and is not a rider on a code PR.
Footer → informational, and the measurement is the useful part. Raw POST /pulls appended its own footer to a body carrying none, so the sent body is a byte-exact prefix of the stored one and nothing was mutated. That is exactly the "send NONE" cell of the attribution grid tracked at #15275 — the reading is already consistent with it and needs no action here.
5. Standing, for the next round on this seat
⭐ If your file face grows past what is declared in the claim, report it — the SEAT amends the claim. This round did that, declared its ablation's four deliberately-green pins as controls, and refused to reuse a lit-control number it could not reproduce. All three are the behaviour being asked for.
Activity
os-bill commentedon Sep 10, 2026
Claim: session_01MkQhmuuJAVDjmeWNixwDDH · branch claude/issue-16553-componentpropsmap-sort-constraint⚠️ Declared at dispatch on the card's shape, ⛔ not on a diff that does not exist; if the measurement shows the fix WIDENS what
Clause-②: no — the card's declared scope is a constraint already present in
ComponentPropsMap; tightening or documenting it narrows or restates, it does not widen.sortaccepts, re-declareyesin the PR body and applyneeds:contract-review— and say so in your report.File face declared:
packages/spec/src/ui/component.zod.tsand its tests. ⛔ Nothing outside it without re-declaring here first.ui/component.zod.tscame free minutes ago — PR #17342 merged (verified by content: theappscope-root token reads 0 onorigin/main, lit controlvisibleWhenreads 7). ⇒ Re-read at head; ⛔ no earlier reading of that file is valid.filterconvergence. The fourobject-*blocks and the twoelement:*doors now carryz.array(ViewFilterRuleSchema)onfilter; ⛔ do not disturb them, andsortandfilterwere converged together — measure whatsortactually declares on each block rather than reasoning by symmetry.Claimed by the
domain:specexecution seat for anos-devsubagent, which inherits this claim and this assignee — ⛔ it posts no secondClaim:and ⛔ never writes the assignee field.Batch independence: dispatched alongside #17385 and #16340. File faces measured disjoint from each other and from every open PR in the lane at claim time (86 busy paths collected from #17146, #17298, #17249, #17358, #17401), ⛔ not only from the in-flight devs. ⛔ Not folded — different defect shapes.
⚠️ Also checked against the
serviceslane's open PR #17332 (plugin-security/src/security-plugin.ts) — a fourth candidate was dropped for clashing with it.Verify-lock: read at claim time; ⛔ re-read
bash scripts/pm/os-verify-lock.sh --statusbefore your first heavy run and treat exit 99 as NOT MEASURED, not red.⭐ A count or a zero is not a reading until you look at what it matched. Lit control (a term known present, > 0) AND dark control (a fabricated term, 0) on every absence claim. Today's traps, all real:
grep -ccounts LINES not occurrences;grep -E's[ \t]is the character SET{space, backslash, t}; a lowercase probe misses a capitalised sentence; a near-synonym probe read 3 where none of the three was the claim; and a probe both sides pass is not a discriminator.⛔ Never capture an exit code through a pipe —
cmd > log 2>&1; EXIT=$?. A$?after a pipe is the last command's exit.check:migration-registry/check:spec-changes/check:upgrade-guide/check:generatedare NOT root scripts — bare invocation exits 254 = NOT MEASURED, not red. Correct:pnpm --filter @objectstack/spec check:….check:generatedis NOT MEASURED in an unbuilt worktree.check:react-declaration-parity— CORRECTED TODAY: AGENTS.md says it cannot run locally. It can.sdui.manifest.jsonis tracked at the repo root and the parity baseline's own_commentnames the invocation (MANIFEST=… check:react-declaration-parity …). ⛔ Do NOT record it asEXTERNAL_INPUT_REQUIRED/ NOT MEASURED without trying that first. (Filed as #17405; the seat had been repeating the stale claim.)Deliverable: a changeset (or
⚠️ The body MUST carry a line that STARTS with
⚠️ Attribution: session id in body prose, ⛔ not a footer — both MCP and raw REST append their own.
skip-changesetwith a measurement — 0 paths underpackages/and no packagefiles[]reaching them), a pin, and a draft PR withFixes #16553.Clause-②: no(oryes) —-,>,**prefixes tolerated;⇒or backticks make it a near miss andCheck Changesetgoes red.⛔ Do NOT flip ready, enqueue, or arm auto-merge.
Commit trailers — ⛔ model-free, exactly:
Report: structured JSON — what you measured with what each probe matched, what you changed, gate exits with NOT MEASURED called out, PR number and head sha, CI as newest-run-per-check-NAME plus the combined commit status (different populations), your clause-② reading, open questions, and out-of-scope findings each with a named carrier (⛔ no carrier ⇒ do not file).
Generated by Claude Code
os-bill commentedon Sep 10, 2026
os-dev-report
{ "issue": 16553, "status": "done", "branch": "claude/issue-16553-componentpropsmap-sort-constraint", "pr": "https://github.com/objectstack-ai/objectstack/pull/17439", "pr_number": 17439, "head_sha": "b19b41eeeca9c11ed1859d4175032e524221c94e", "premise_still_valid": true, "clause_2": "no — re-declared on the measured diff. `z.unknown()` accepted every value; `z.array(SortItemSchema)` accepts a strict subset, so the accept set only SHRINKS and nothing starts being accepted. `needs:contract-review` NOT applied, per the claim's conditional. Labels: none applied by this seat — this repo applies path and size labels from `.github/workflows/pr-automation.yml` via `scripts/pr-labels.mjs`, and the only label that was mine to place was the conditional contract-review one, which the measurement says no to. PR read back after creation: labels [] at that instant.", "summary": "ComponentPropsMap['object-grid'].sort and ComponentPropsMap['object-calendar'].sort move from z.unknown() to z.array(SortItemSchema) — the shared import ElementDataSourceSchema.sort, ListPageSchema.sort and element:record_picker's flat sort shorthand already carry, so one schema and not a third copy. Implements item 4 of objectui#8221's decision batch #77 (2026-09-07, maintainer verbatim 「其他同意」, option B). Also in the diff: the semantic migration entry `object-block-sort-item-array` under protocol major 18 (registry.ts REGENERATED by gen:migration-registry, never hand-edited between the markers), the regenerated content/docs/references/ui/component.mdx, twelve pins in component.test.ts, and a minor changeset carrying the BREAKING accept-set declaration plus the ADR-0087 `registered` disposition marker. FILE FACE WIDENED BEYOND THE CLAIM and this is the declaration of it: the claim declared `packages/spec/src/ui/component.zod.ts` and its tests; four more paths were forced, none by choice — the changeset (named in the deliverable), the ADR-0087 semantic entry plus its generated registry.ts block (check:adr-0087-registration refuses a declared-breaking changeset that says nothing about the ledger, and `not-required (no-migration-prescription)` would have been false since string-to-array IS mechanical), and the generated component.mdx (check:docs went red without it). The generated pair spec-changes.json and docs/protocol-upgrade-guide.md were regenerated and came back BYTE-IDENTICAL, with the filter twin's id as the lit control reading 0 in both artifacts too — semantic entry ids are not projected there, so neither file is in the diff. The assignee was already set by the PM and was never written by this seat; the newest Claim comment 5618981489 names this branch and session, and no second claim was posted. Not flipped ready, not enqueued, no auto-merge armed.", "measured": { "card_premise": "HOLDS. Probed through the real map on this head: array, the legacy string clause 'created_at desc' and a bare number all returned success:true on both doors; `bogusProp` on the SAME call was refused by name (unrecognized_keys, keys:['bogusProp']) — the control that makes those three positive readings verdicts rather than a schema reporting nothing. Key checking was live; only the VALUE was unheld.", "sort_vs_filter_not_converged_together": "MEASURED PER BLOCK, not inferred by symmetry, exactly as the dispatch warned. Enumerating sort-shaped key declarations rather than searching by spelling, with a fabricated key as the dark control (0 hits): object-grid z.unknown(); object-calendar z.unknown(); element:record_picker already z.array(SortItemSchema); record:related_list z.union([z.string(), z.array(...)]) — an explicitly declared string arm. object-metric and object-kanban carry `filter` but declare NO `sort` key at all, so the 'four object-* blocks' shape that holds for filter does NOT hold for sort. The four filter declarations and the two element:* doors were not touched (verified in the diff).", "file_reread_at_head": "PR #17342's landing confirmed by content before any edit: the `app` scope-root token reads 0 on this head. NOTE ONE DISAGREEMENT WITH THE CLAIM: the claim cites `visibleWhen` reading 7 as its lit control; on this head `grep -c 'visibleWhen'` reads 23 lines and `grep -c 'visibleWhen:'` (key declarations only) reads 2. Neither equals 7. The claim's load-bearing half (app = 0) reproduces exactly, so the conclusion stands; the lit-control number does not, and this seat did not reuse it. Possible causes not distinguished: a different pattern, or main having moved (this branch's base d57611dfd is 3 commits behind origin/main by the time of the last derivation).", "objectui_read_path_at_the_pin": "Measured at .objectui-sha 53ded82b, the pin this repo builds against, BEFORE the declaration moved. plugin-grid/src/ObjectGrid.tsx:1457 reads schema.sort; :1844-1851 carries an explicit `typeof schemaSort === 'string'` arm putting the clause on $orderby VERBATIM, beside the array arm; :3998 hands schemaSort to parseSchemaSort as TableSortItem[]. plugin-calendar/src/ObjectCalendar.tsx:431 hands schema.sort to convertSortToQueryParams, whose string arm is still present at core/src/utils/sort-query.ts:66-70. plugin-grid/src/index.tsx:222 publishes { name: 'sort', type: 'array' }; plugin-calendar/src/index.tsx declares no sort input at all. ⇒ AT THIS PIN THE STRING IS STILL LOWERED and this declaration refuses a spelling the pinned renderer honours. That is the ruled sequence, stated in the code, the changeset, the migration entry and the PR body rather than glossed: objectui PR #8758 (merged 2026-09-09T05:21:21Z, AFTER this pin) drops the string arm, and the next pin bump carries it in. The ruling permits either order because the registrations already declare the array.", "record_related_list_left_alone": "Its string is the 'field' / '-field' dialect read by RelatedList.normalizeSortSpec; it never reaches convertSortToQueryParams and retiring it was NOT ruled — objectui#8221's own implementing round narrowed it, established the dialect, then reverted the narrowing byte-identically (recorded in that round's report). The census pin in this PR asserts it is the ONE remaining sort door that takes a string, so the exception is pinned rather than tolerated: if a future entry declares sort as z.unknown(), that pin names it.", "in_repo_authored_sort_sweep": "ZERO on either block. The two showcase pages that author object-grid (command-center.page.ts, my-work.page.ts) declare no sort, and no object-calendar author exists in the tree. Lit control that the sweep fires: the same grep shape found 40+ string sort values at OTHER doors (view definitions, ObjectQL query.sort, lint fixtures) — none a block prop, none touched. So nothing in-tree needed converting and the migration entry carries the prescription for authors outside the repo.", "control_characters": "git-diff-scoped scan of all 6 changed files with grep -naP over the control class: 0 hits. Controls run because a zero is not a reading: a planted \\x0b reads 1, and a tab-only file reads 0 because \\x09 is deliberately OUTSIDE that class (a tab is NOT a valid lit control for this pattern — the first attempt used one and read 0, which was the probe being wrong, not the tree being clean)." }, "tests": "ALL exit codes captured BEFORE any pipe (cmd redirected to a file, then EXIT=$?). Heavy runs went through `bash scripts/pm/os-verify-lock.sh -c` with OS_VERIFY_LOCK_SLOT=issue-16553 set before the first attempt; every VERDICT line read command-exit 0. (1) pnpm --filter @objectstack/spec build -> exit 0, twice (before and after the registry change); no tracked generated file drifted either time. (2) pnpm --filter @objectstack/spec typecheck -> exit 0 (tsc --noEmit + check:scripts-typecheck + check:test-typecheck; the test layer compiles, 54 files / 261 errors / 145 pinned signatures held). (3) pnpm --filter @objectstack/spec test -> exit 0, 'Test Files 468 passed (468)', 'Tests 13202 passed (13202)'. (4) pnpm --filter @objectstack/lint test -> exit 0, 'Test Files 103 passed (103)', 'Tests 3717 passed (3717)' — the closest real consumer of ComponentPropsMap. (5) CONSUMER SWEEP, direction stated: the four downstream packages that NAME ObjectGridProps / ObjectCalendarProps / ComponentPropsMap (@objectstack/lint, @objectstack/mcp, @objectstack/platform-objects, @objectstack/example-showcase) — pnpm exec turbo run typecheck over them, exit 0, 'Tasks: 65 successful, 65 total'. The published TS face DID move (sort goes from unknown to SortItem[] | undefined), so this leg was owed and is not skipped. (6) pnpm exec eslint . --no-inline-config --format json -> exit 0 over the WHOLE population: 6553 files linted, 0 errors, 0 warnings, at this head. NOT a narrowed run — the repo-wide scan completed inside the foreground budget, so no narrowing argument is needed and none is claimed. ABLATION (reverse verification), predicted direction declared before the run as 'turns red': both sort declarations reverted to z.unknown().optional() on disk, pin file re-run, then restored. On-disk proof BEFORE: 2 array-form declarations, 0 ablation markers; AFTER: 0 and 2, and the worktree blob hash moved 1de632a2 -> ce92734b, so the mutation reached the file. Result exit 1, 'Tests 8 failed | 272 passed (280)' — the string refusal, the bare-number refusal, the SortItemSchema shape checks, the cross-door agreement and the census, on both doors. FOUR STAYED GREEN AND MUST: the two 'accepts a SortItem[] and echoes it' assertions (z.unknown() echoes an array too) and the two bogusProp controls (key checking was never the missing half) — a pin set where every assertion moved would have meant the controls were not controls. RESTORE VERIFIED BY STATE, NOT BY EXIT CODE: git diff HEAD empty and the worktree blob equals the HEAD blob byte-for-byte (1de632a2b2d8dae1becae00e1f7db8354318404a). The script carried a trap on EXIT INT TERM restoring through an ABSOLUTE path with `git checkout HEAD -- ` (never bare `git checkout -- `, which would have taken the mutation back out of the index) and treated an empty hash as a hard failure. No build/dist leg was needed: component.test.ts imports './component.zod' relatively inside the same package, so nothing resolves through dist.", "gates": "Derived mechanically, not from memory: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, re-derived AFTER the changeset existed (it added 7 families: check-empty-changeset, check:spec-changes, check:upgrade-guide, check:objectui-changeset, check:pm-changeset-deadline-census, check:pm-widening-tells, release-rehearsal-clone --self-test). 85 commands, ALL RUN, each exit captured before any pipe. 83 exit 0. ⚠️ The deriver also printed STALE TREE: this branch's base is at least 3 commits behind origin/main and one adr-anchors file it derives from changed in that range — the answer was still taken and is reported as such, not as a clearance. TWO RESULTS THAT ARE NEITHER GREEN NOR RED, both reported as NOT MEASURED: (a) pnpm check:dual-build-cjs-loads -> exit 3, 'PREREQUISITE NOT MET — this gate reads built output' with eight packages having no dist/ in this worktree; its own text says 'This is NOT a pass: nothing was measured.' Declared to CI, which runs it after a full pnpm build. (b) pnpm --filter @objectstack/lint run check:doc-formula-expressions -> first run exit 3 for the same reason; RE-RUN after building @objectstack/formula and @objectstack/lint -> exit 0, and that second reading is the one reported. TWO GATES WERE RED ON A FIRST RUN AND ARE GREEN NOW, both fixed in the diff rather than argued with: check:doc-authoring refused '#8221' inside the two new .describe() strings (they project into content/docs/references/** and the generated skill artifacts, where an issue id resolves to nothing — maintainer ruling 2026-08-12); both describes now cite the migration id instead, gate now exit 0 over 15022 customer-facing strings. check:docs was red because content/docs/references/ui/component.mdx was stale; regenerated via gen:schema then gen:docs (gen:docs REFUSES to run against a json-schema tree older than src, which is why the schema step came first), now exit 0 with 221 generated files in sync. check:react-declaration-parity was run AS CI RUNS IT with the checked-in root manifest — MANIFEST=\"$PWD/sdui.manifest.json\" pnpm --filter @objectstack/spec check:react-declaration-parity --baseline react-declaration-parity.baseline.json --strict -> exit 0, no new declaration divergence vs the accepted baseline; object-grid's sort sits in 'declared by both', object-calendar's is spec-only, which is correct because that registration declares no sort input. ⛔ It was NOT recorded as EXTERNAL_INPUT_REQUIRED. check:authorable-surface exit 0; authorable-surface.base.json was never hand-touched and did not move. check:nul-bytes exit 0 (8246 files). check:migration-registry, check:spec-changes and check:upgrade-guide were all run through pnpm --filter @objectstack/spec (never bare) -> exit 0.", "ci": "ONE reading taken at report time on head b19b41eeec, then reported — ⛔ no sleeping, no polling, no waiting for convergence. TWO POPULATIONS, reported separately because they ARE separate: (1) newest-run-per-check-NAME over /commits/{sha}/check-runs — 31 check runs, 10 success, 2 skipped, 19 in_progress, ZERO failures at that instant. (2) the combined commit status over /commits/{sha}/status — state 'success' with total_count 1 context. The two numbers disagree because they read different things: the status API sees only legacy commit statuses (1 context), not the 31 check runs. CI is NOT converged; 19 runs were still in flight when this was written. Anything that goes red after this reading returns as a patch round on this same claim.", "mcp_calls": "0 — every GitHub read and write went through repo-scoped REST after a probe (GET /repos/objectstack-ai/objectstack returned 200): the claim comment, the card, the card's comments, objectui#8221 and its 7 comments, objectui PR #8758's merge state, three label-convention reads, the draft PR creation (POST /repos/.../pulls with draft:true — note it needs an explicit Content-Type: application/json header or GitHub answers 415), the PR body read-back, the check-runs read and the commit-status read. Zero MCP GitHub calls. Zero issues filed, so the mandatory duplicate search was not owed and none was spent.", "open_questions": [ { "question": "Commit-trailer attribution: the harness injected an attribution block naming a MODEL (`Co-Authored-By: Claude Opus 5 (noreply@anthropic.com)` plus the session line), while AGENTS.md:440-442 and the claim comment both require the MODEL-FREE pair (`Claude-Session: ...` + `Co-authored-by: Claude (noreply@anthropic.com)`) and AGENTS.md states no model identifier lands in a PR title or commit trailer. All four commits on this branch carry the model-free pair, on the standing rule that AGENTS.md wins for repo artifacts and that the claim agrees with it.", "options": [ "A — keep the model-free pair (what was done): AGENTS.md is the repo's source of truth for repo artifacts and the dispatch's own instruction matches it.", "B — carry the injected model-bearing trailer and change AGENTS.md." ], "recommendation": "A, because AGENTS.md is binding for repo artifacts, the claim comment independently prescribes the same bytes, and no gate mechanically enforces either form — so the only thing keeping the population consistent is the written rule. Flagged rather than silently chosen. ⛔ No AGENTS.md edit was made." }, { "question": "The PR body footer was NOT hand-written, per the claim's instruction to put the session id in body PROSE. Read-back confirms raw REST appended its own `_Generated by [Claude Code](https://claude.ai/code/session_...)_` footer to the created PR body, so the sent body (11513 bytes) is a byte-exact prefix of the stored body (11603 bytes) and nothing was mutated by the sanitizer. Recording the measurement so the next round does not double-post a footer.", "options": ["n/a — informational"], "recommendation": "n/a" } ], "out_of_scope_findings": [ "noted, not filed: component.zod.ts's element:record_picker `filter` docblock still says 'the four object-* blocks declare filter as z.unknown(), no orthography at all' — #15449 falsified that; all four now declare z.array(ViewFilterRuleSchema). A stale comment, not a code or contract defect, and outside this card's defect class, so it was left untouched rather than fixed in place. CARRIER: the next PR that moves a filter door in ComponentPropsMap, which reads that paragraph on its way in.", "noted, not filed: SortItemSchema requires `order`, while convertSortToQueryParams deliberately treats an omitted order as ascending (its own docblock states this as a fidelity improvement over the private copies it replaced). So the spec refuses [{ field: 'x' }] at EVERY sort door while the pinned consumer would have honoured it. The refusal is LOUD, not a silent drop, and the spec is the contract, so this is downstream tolerance rather than a defect here — it also predates this PR at three other doors. CARRIER: whoever next revisits SortItemSchema's optionality (page.zod.ts:189, :529 and element:record_picker share it).", "noted, not filed: objectui's contract review of PR #8758 (objectui#8221 comment 5595976918) recorded that a bare object-grid STILL honours the retired string at runtime through the grid's private lowering, which that PR's ruling did not name, and judged it correctly a separate card. Already public and already judged on the objectui side; nothing to add from this repo. CARRIER: that thread.", "noted, not filed: this repo's .objectui-sha (53ded82b) predates objectui PR #8758, so the pinned consumer and this declaration disagree about the string for as long as the pin sits where it is. That is the ruled sequence, not a defect. CARRIER: the seat that next runs objectui:refresh and pnpm sdui:manifest — AGENTS.md makes the pin bump the ADR-0082 D4 parity ratchet's only trigger, so that seat re-reads this face by construction." ] }Generated by Claude Code
os-bill commentedon Sep 10, 2026
Seat amendment to the claim (
5618981489) — widened file face ACCEPTED, and one correction of my owndomain:specexecution seat,session_01MkQhmuuJAVDjmeWNixwDDH, 2026-09-10T14:30Z. The round reported its face grew past what the claim declared, which is exactly the reporting the standing rule asks for — the seat amends the claim, the round does not. Amended here.1. File face — declared 2 paths, landed 6. All four additions accepted.
The claim declared
packages/spec/src/ui/component.zod.tsand its tests. PR #17439's final path list is six:packages/spec/src/ui/component.zod.tspackages/spec/src/ui/component.test.ts.changeset/object-block-sort-item-array.mdpackages/spec/src/migrations/entries/semantic/18.object-block-sort-item-array.tscheck:adr-0087-registration— a declared-breaking changeset silent on the ledger is refused, andnot-required (no-migration-prescription)would have been false, since string→array is mechanical (ADR-0087 D3,:198)packages/spec/src/migrations/registry.tsgen:migration-registry, between theos-generatedmarkerscontent/docs/references/ui/component.mdxcheck:docswent red without it; regeneratedgen:schema→gen:docs⭐ Each is a generated or gate-forced consequence of the declared change, not a scope decision the round took on its own — and it declared the growth rather than absorbing it. Accepted as landed.
Governed-surface reading over that FINAL list, taken by this seat, not inherited:
check-governed-merges --testexits 0 on all six. Lit control:AGENTS.mdthrough the same invocation exits 3. ⇒ the instrument fires, and nothing here is governed..gitattributesonorigin/mainrather than from memory:content/docs/references/**is routedmerge=os-regen(line 153), socomponent.mdxis on the regen driver's path — the driver that can exit 0 while silently dropping one side. Containment here is that the merge queue re-runscheck:docson the merged ref, which is what would catch a drop. Recorded, not treated as a blocker.2. ⛔ Correction — the lit control in my own claim was wrong
The claim says PR #17342's landing was verified with «the
appscope-root token reads 0 onorigin/main, lit controlvisibleWhenreads 7». The round flagged the second number. It is wrong, and it is mine, not the round's.Measured just now,
origin/main, both candidate files, three probe shapes each, dark controlvisibleWhenZZZ= 0 in every cell:grep -c)grep -o | wc -l)visibleWhen:)packages/spec/src/ui/page.zod.tspackages/spec/src/ui/component.zod.tsNo cell is 7. The round read 23 and I read 8 — that disagreement is fully explained and needs no "cause not distinguished": we measured different files.
visibleWhen's key declarations live inpage.zod.ts, which is where PR #17342 moved; the round greppedcomponent.zod.ts, the file it was editing. Both readings are correct for their file; my "7" is wrong in both.The load-bearing half reproduces exactly:
grep -c "mounts \app`"onorigin/main` reads 0, so PR #17342 is landed and the file was correctly re-read at head. ⭐ The round was right to refuse to reuse the number, and right to say so instead of quietly using its own — that is the behaviour, not an inconvenience.3.
check-clause2-carriers --pair 17439→ exit 4, and why it is NOT a holdExit 4, C5, one row: two T1 tells against a declared
Clause-②: no.Both tells are measured FALSE. T1's text is "a new key on a Zod object schema". Neither key is new — each
+line has a paired-line for the same key in the same file diff:⇒ a re-declaration, and one whose accept set strictly shrinks:
z.unknown()accepts every value;z.array(SortItemSchema)accepts a proper subset. Nothing becomes newly writable.Per the standing C5 ruling — a false POSITIVE is repaired in the matcher, ⛔ not paid for by the author; the only word that clears a false tell is
no→yes, which would record a widening in a governance ledger that did not happen — the declaration stands asClause-②: no, the tells stand as emitted, and they disagree. ⛔ Not re-declared, ⛔ not suppressed. This is a card to file, not a hold: the pair's DECLARATION limb is satisfied (the claim carries a legibleClaim:line and a key-initialClause-②:line, and the script read the value), and the only adverse row is a tell measured false. Evidence added to #17300.⭐ And the fix shape is now sharper than "the tells are line-oriented":
check-widening-tells.mjsalready has this guard, for T2 only. At:773the T2 arm callsrewritesExistingOpener(text, removedByHunk.get(hunk))(added by #16822) precisely so a re-declared opener is not read as a new member. The T1 arm at:767-769testsSCHEMA_PROPERTYagainst the added line alone and never consultsremovedByHunk. The discriminator exists in the file; T1 does not call it.4. Your two open questions — answered
Trailers → A, and you were right to flag rather than choose silently.
AGENTS.mdis binding for repo artifacts; the trailers stay the model-free pair. The harness's injected block names a model, and no model identifier belongs in a commit trailer, a PR title or body, or a code comment. AGENTS.md wins, the claim agreed with it, and all four commits already carry the right bytes. ⛔ No AGENTS.md edit — correct call; that file is governed and is not a rider on a code PR.Footer → informational, and the measurement is the useful part. Raw
POST /pullsappended its own footer to a body carrying none, so the sent body is a byte-exact prefix of the stored one and nothing was mutated. That is exactly the "send NONE" cell of the attribution grid tracked at #15275 — the reading is already consistent with it and needs no action here.5. Standing, for the next round on this seat
⭐ If your file face grows past what is declared in the claim, report it — the SEAT amends the claim. This round did that, declared its ablation's four deliberately-green pins as controls, and refused to reuse a lit-control number it could not reproduce. All three are the behaviour being asked for.
domain:spec执行席 ·session_01MkQhmuuJAVDjmeWNixwDDH· 2026-09-10T14:30ZGenerated by Claude Code
feat(spec)!: retire the bare string `sort` clause on the list-view do…