Skip to content

spec: GroupingFieldSchema.field accepts a padded field name that three objectui readers bucket wrong — refuse non-trimmed names at the producer (ruling C on objectui#7347) #17360

Description

@os-litant

Filed by the director seat (summon #21, session_01QVMnxyWBx8cAQMsV6akDV9) as the protocol card for ruling C on objectui#7347 — maintainer verbatim 「其他同意」 on decision batch #110 item 5 (director chat, 2026-09-10T08:4xZ; analysis and options at objectui#7347 comment 5615378321). Routed domain:spec by the anchoring rule (every change under packages/spec goes to the spec seat) under the maintainer's ruling; pm:queue. ⛔ Not claimed.

The defect this narrows away

GroupingFieldSchema.field (packages/spec, the grouping.fields[] block of list-view metadata) is a bare z.string(): ' business_unit ' is valid authored metadata. Measured on objectui (comment 5607077319, M1–M11 with live controls): the projection harvester collectGroupingFieldRefs trims the name for $select, while three renderers bucket rows by the raw name — plugin-grid usableGroupingFields, plugin-list ObjectGallery.groupedItems, plugin-kanban effectiveSwimlaneField. The server answers under business_unit, the readers look up ' business_unit ', every row reads undefined, and the view shows one (empty) group (grid, gallery) or one Uncategorized lane (kanban) holding every record — a silent wrong answer that reads as a true statement about the data. The sibling groupByField axis (kanban / gantt / timeline) is symmetric and is ⛔ not this card.

The ruling

C — refuse at the producer. GroupingFieldSchema.field gets a non-padded pattern (no leading/trailing whitespace; the same shape the field-name vocabulary already uses elsewhere in packages/spec if one exists — reuse it, do not invent a second), with a refusal message naming the field and the offending spelling. ⛔ Not .trim(): a trimming schema makes ' a ' and 'a' silently equivalent, which is the consumer-tolerance direction AGENTS.md #0.1 refuses. The harvester's trim on the objectui side stays as defence-in-depth; nothing is removed there.

Governing text: AGENTS.md #0.1 (fix the producer, do not teach the renderer to tolerate); 2026-08-27 maintainer ruling 「短期不考虑渐进」 — the narrowing ships at once, no deprecation window.

Acceptance

  • A refusal pin: a padded grouping field name is refused by name at grouping.fields[N].field; control: the clean name still parses, and every existing grouping fixture in the repo still parses (enumerate them — a narrowing that reddens an in-tree fixture is a finding, report it, do not widen the pattern to fit).
  • Clause-②: yes by the conservative routing precedent for spec narrowings (objectui#8285 ruling: a spec narrowing declares yes; minor + BREAKING note in the changeset, ADR-0087 disposition: stored metadata with a padded name now fails validation and must be re-authored — say so in the changeset).
  • Changeset names objectui#7347 as the consumer that unblocks on the installable release, not on merge.

Cross-repo landing

objectui#7347 is pm:blocked on this card (Blocked-by: in its body). On release the objectui side is a pin bump plus a regression test that a padded name is refused before it reaches any renderer; the gallery and kanban sites are covered by this one producer fix and get no cards of their own (dev's reasoning at objectui#7347 5607077319, endorsed).


Generated by Claude Code

Activity

  1. os-bill commented on Sep 10, 2026

    @os-bill
    Collaborator

    Serial hold — queued BEHIND PR #17447. ⚠️ And the vocabulary this card says to reuse has been RETIRED

    domain:spec execution seat, session_01MkQhmuuJAVDjmeWNixwDDH, 2026-09-10T15:01Z. ⛔ Not a claim — this card stays pm:queue and unassigned, ⛔ labels untouched. The ruling is not touched or reinterpreted; this is scheduling plus one measured premise.

    fold-or-serial, answered: serial

    GroupingFieldSchema is declared at packages/spec/src/ui/view.zod.ts:853, and its fixtures live in packages/spec/src/ui/view.test.ts. PR #17447 (card #16577, opened 14:53Z) is open on both of those exact files. The folding gate fails at its first door — different defect shape (a padded-name accept vs an undocumented guard scope), different fix, different review requirement (this card is Clause-②: yes and needs an at-tier verdict; #17447 is a measured no). ⛔ Not folded, ⛔ not run in parallel.

    ⚠️ Whoever picks this up: re-derive :853 on the merged tree. #17447 adds ~130 lines to view.test.ts and a docblock to view.zod.ts, so line numbers in that file will have moved. Anchor by content (export const GroupingFieldSchema), never by line.

    ⚠️ The premise worth knowing before you start — measured, not inherited

    The ruling says:

    GroupingFieldSchema.field gets a non-padded pattern (the same shape the field-name vocabulary already uses elsewhere in packages/spec if one exists — reuse it, do not invent a second)

    ⭐ Measured on origin/main: it does not exist any more. FieldNameSchema was retired under protocol 18 — there is a retired-def entry (packages/spec/src/migrations/entries/retired-defs/18.shared__FieldName.ts) and a semantic entry (18.branded-identifier-schemas-retired) covering it, and the retired-def's own text records why:

    No schema in either repo ever composed FieldNameSchema…

    ⇒ The card's conditional resolves to its second branch, and the dev will have to define the pattern rather than reuse one. ⚠️ ⛔ That is not licence to invent a general field-name vocabulary as a rider — the ruling asks for a non-padded pattern on one key, and reviving a retired branded-identifier family would be re-opening a protocol-18 retirement. ⭐ Whoever takes this should state, in the PR, which of those two things they did and why. If the honest answer turns out to be "this needs the retired vocabulary back", that is a 回翻 — stop and return the card, ⛔ do not decide it in the round.

    Potholes recorded at the moment of deferral, per the serial discipline

    • Clause-②: yes is fixed by the ruling, not by the diff's measurement — the card cites the objectui#8285 precedent that a spec narrowing declares yes. ⇒ needs:contract-review on both carriers, an at-tier verdict on the head that lands, minor + a **BREAKING** note, and an ADR-0087 disposition. ⛔ Do not re-declare no on the ground that the diff looks small.
    • ⚠️ The ADR-0087 disposition means a migration entry, which means packages/spec/src/migrations/registry.ts — a hot file with two open writers today (spec(ui): constrain the object-grid / object-calendar sort value to the SortItem array #17439, queued; fix(triggers,spec,service-automation)!: a time-triggered flow declares its acting organization and the run executes as it #17334, in flight). Expect merge-queue arbitration; ⛔ never hand-edit between the os-generated markers, and ⛔ never reorder the queue manually.
    • The fixture enumeration is an acceptance criterion, not a formality. The card says: a narrowing that reddens an in-tree grouping fixture is a finding to report, ⛔ not a reason to widen the pattern to fit. Enumerate them with a lit control (a fixture known to carry grouping.fields[]) and a dark control.
    • ⛔ Not .trim() — the ruling is explicit that a trimming schema makes ' a ' and 'a' silently equivalent, which is the consumer-tolerance direction AGENTS.md #0.1 refuses. The objectui-side trim stays as defence-in-depth; nothing is removed there.
    • The changeset must name objectui#7347 as unblocking on the installable release, ⛔ not on merge.
    • ⚠️ The sibling groupByField axis (kanban / gantt / timeline) is symmetric and is explicitly ⛔ not this card. Do not widen into it.

    Refs: PR #17447 (the holder) · card #16577 · objectui#7347 (blocked on this) · decision batch #110 item 5 · 18.branded-identifier-schemas-retired

    domain:spec 执行席 · session_01MkQhmuuJAVDjmeWNixwDDH · 2026-09-10T15:01Z


    Generated by Claude Code

  2. os-bill commented on Sep 10, 2026

    @os-bill
    Collaborator

    Serial hold RELEASED — PR #17447 landed. ⚠️ Two things changed under this card while it waited

    domain:spec execution seat, session_01MkQhmuuJAVDjmeWNixwDDH, 2026-09-10T16:13Z. ⛔ Still not a claim — this card stays pm:queue and unassigned, ⛔ labels untouched. It is next in this lane's queue and will be claimed when a slot frees (cap 3, all three in flight).

    The blocker is gone. PR #17447 merged; verified by content on origin/main: the #16577 scope note reads 2 in packages/spec/src/ui/view.zod.ts, lit control 13817 reads 2 in the same file, dark control 16577999 reads 0. view.zod.ts and view.test.ts are free.

    ⚠️ Re-derive the anchor — it has moved, and by more than the hold predicted

    The card names GroupingFieldSchema at packages/spec/src/ui/view.zod.ts:853. PR #17447 added ~130 lines to view.test.ts and a docblock block to view.zod.ts above that point. ⛔ Anchor by content (export const GroupingFieldSchema), never by line. This is not hypothetical caution: the sibling card #17166 was held the same way behind PR #17439, and when it was released its anchor had moved 56 lines (:2527 → :2583).

    ⚠️ And the premise measured during the hold still stands — read it before you start

    The ruling says the non-padded pattern should reuse "the same shape the field-name vocabulary already uses elsewhere in packages/spec if one exists — reuse it, do not invent a second".

    ⭐ Measured: it does not exist any more. FieldNameSchema was retired under protocol 18 — packages/spec/src/migrations/entries/retired-defs/18.shared__FieldName.ts plus the semantic entry 18.branded-identifier-schemas-retired, whose own text records "No schema in either repo ever composed FieldNameSchema". ⇒ the ruling's conditional resolves to its second branch and the pattern has to be defined.

    ⛔ That is not licence to revive the retired branded-identifier family as a rider — doing so would re-open a protocol-18 retirement, which is a 回翻: stop and return the card rather than deciding it in the round. Whoever takes this states in the PR which of the two things they did and why.

    The rest of the potholes, unchanged (full text at 5620776042)

    • Clause-②: **yes** is fixed by the ruling, not by the diff's size (objectui#8285 precedent: a spec narrowing declares yes) ⇒ needs:contract-review on both carriers, an at-tier verdict on the head that lands, minor + a **BREAKING** note, and an ADR-0087 disposition.
    • ⚠️ That disposition means a migration entry in packages/spec/src/migrations/registry.ts. ⭐ Good news from the wait: both PRs that held that file (spec(ui): constrain the object-grid / object-calendar sort value to the SortItem array #17439, fix(triggers,spec,service-automation)!: a time-triggered flow declares its acting organization and the run executes as it #17334) have now landed, so it is no longer contended — but re-check at write time rather than trusting this line.
    • The in-tree grouping-fixture enumeration is an acceptance criterion: a narrowing that reddens a fixture is a finding to report, ⛔ not a reason to widen the pattern to fit. Enumerate with a lit and a dark control.
    • ⛔ Not .trim() — a trimming schema makes ' a ' and 'a' silently equivalent, the consumer-tolerance direction AGENTS.md #0.1 refuses. objectui's defence-in-depth trim stays; nothing is removed there.
    • The changeset names objectui#7347 as unblocking on the installable release, ⛔ not on merge.
    • ⛔ The sibling groupByField axis (kanban / gantt / timeline) is explicitly not this card.

    Refs: PR #17447 (the released holder) · card #16577 · objectui#7347 · decision batch #110 item 5 · 18.branded-identifier-schemas-retired · 5620776042 (the full hold note)

    domain:spec 执行席 · session_01MkQhmuuJAVDjmeWNixwDDH · 2026-09-10T16:13Z


    Generated by Claude Code

  3. self-assigned this
    on Sep 10, 2026
  4. os-bill commented on Sep 10, 2026

    @os-bill
    Collaborator

    Claim: session_01MkQhmuuJAVDjmeWNixwDDH · branch claude/issue-17360-grouping-field-non-padded
    Clause-②: yes — fixed by the ruling, ⛔ not by the diff's size. The card cites the objectui#8285 precedent that a spec narrowing declares yes, so this carries needs:contract-review on both carriers (hung on the card with this claim), needs an at-tier verdict on the head that lands, a minor bump with a **BREAKING** note, and an ADR-0087 disposition. ⛔ Do not re-declare no on the ground that the diff looks small.

    File face declared — packages/spec/src/ui/view.zod.ts, view.test.ts, the ADR-0087 semantic entry plus its regenerated packages/spec/src/migrations/registry.ts block, and a changeset. ⭐ If your face grows past this list, report it — the SEAT amends this comment, ⛔ never widen it yourself.

    The ruling — maintainer-decided, ⛔ not yours to revisit

    Ruling C on objectui#7347, maintainer verbatim 「其他同意」, decision batch #110 item 5: refuse at the producer. GroupingFieldSchema.field gets a non-padded pattern with a refusal naming the field and the offending spelling.

    ⛔ Not .trim() — the ruling is explicit: a trimming schema makes ' a ' and 'a' silently equivalent, which is the consumer-tolerance direction AGENTS.md #0.1 refuses. objectui's harvester trim stays as defence-in-depth; nothing is removed there.
    ⚠️ The sibling groupByField axis (kanban / gantt / timeline) is symmetric and explicitly ⛔ not this card. Do not widen into it.

    ⭐ The premise the seat measured during the hold — read this before you start

    The ruling says reuse "the same shape the field-name vocabulary already uses elsewhere in packages/spec if one exists".

    ⭐ Measured: it does not. FieldNameSchema was retired under protocol 18 — packages/spec/src/migrations/entries/retired-defs/18.shared__FieldName.ts plus the semantic entry 18.branded-identifier-schemas-retired, whose own text records "No schema in either repo ever composed FieldNameSchema". ⇒ the conditional resolves to its second branch and you must define the pattern.

    ⛔ That is not licence to revive the retired branded-identifier family as a rider — that would re-open a protocol-18 retirement, which is a 回翻: stop and return the card rather than deciding it in the round. ⚠️ Whichever you do, say which in the PR and why. ⭐ Re-verify this yourself against origin/main before relying on it; it is my reading, and a premise you inherit unmeasured is a premise you own.

    The anchor — re-derived just now, and the result is the opposite of the sibling case

    export const GroupingFieldSchema is at view.zod.ts:853 on the merged tree — the same line the card names. PR #17447 landed in that file but added its content below this declaration, so nothing moved. ⚠️ That is luck, not a licence: the sibling card #17166 was held the same way and its anchor moved 56 lines. ⛔ Still anchor by content (export const GroupingFieldSchema), never by line.

    The target key, read at that anchor:

    field: z.string().describe('Field name to group by — one `groupBy` column of the group header query; …'),

    The defect, and the acceptance the ruling fixes

    field is a bare z.string(), so ' business_unit ' is valid authored metadata. objectui measured (M1–M11 with live controls) that the projection harvester trims the name for $select while three renderers bucket rows by the raw name — plugin-grid usableGroupingFields, plugin-list ObjectGallery.groupedItems, plugin-kanban effectiveSwimlaneField. The server answers under business_unit, the readers look up the padded spelling, every row reads undefined, and the view shows one (empty) group or one Uncategorized lane holding every record — ⭐ a silent wrong answer that reads as a true statement about the data.

    Acceptance, from the card:

    • a refusal pin: a padded name is refused by name at grouping.fields[N].field;
    • control: the clean name still parses;
    • ⭐ and every existing grouping fixture in the repo still parses — enumerate them. A narrowing that reddens an in-tree fixture is a finding to report, ⛔ not a reason to widen the pattern to fit. Enumerate with a lit control (a fixture known to carry grouping.fields[]) and a dark control.
    • the changeset names objectui#7347 as unblocking on the installable release, ⛔ not on merge.

    Fences and the tree

    ⭐ packages/spec/src/migrations/registry.ts is free again — both PRs that held it (#17439, #17334) have landed. ⚠️ Re-check at write time rather than trusting this line. ⛔ Never hand-edit between the os-generated markers; regenerate with gen:migration-registry.
    ⛔ Fenced out, held elsewhere: packages/spec/src/data/field.zod.ts (#16867, in flight) · packages/spec/src/ui/component.zod.ts (#17473, enqueued) · packages/spec/src/ui/dashboard.zod.ts (#17474, enqueued) · packages/spec/src/data/driver/** (#17457) · packages/spec/scripts/** · docs/adr/** and skills/** (governed — read only).


    ⭐ A count or a zero is not a reading until you look at what it matched. Lit control (> 0) AND dark control (0) on every absence claim. Traps confirmed in this lane today: grep -c counts LINES not occurrences; grep -E's [ \t] is the character SET {space, backslash, t} — use grep -P; a near-synonym read 3 where none was the claim; a probe both sides pass is not a discriminator; ⚠️ the shared checkout /home/user/objectstack sits on another agent's branch — a round today read a pre-merge state from it and nearly declared a live premise dead, so verify against origin/main via git show origin/main:PATH, ⛔ never the working tree.

    ⛔ Never capture an exit code through a pipe — cmd > log 2>&1; EXIT=$?.
    ⛔ Run a tool's own predicate; never re-implement it. ⭐ Today's best round sliced objectui's own scanner out of the pin rather than writing a grep — and it mattered, because a comment two lines above a real read would have inflated a naive count.
    ⚠️ Exit 3 = a gate's own PREREQUISITE NOT MET ⇒ NOT MEASURED, not red.
    ⚠️ check:migration-registry / check:spec-changes / check:upgrade-guide / check:generated are NOT root scripts — bare invocation exits 254 = NOT MEASURED. Use pnpm --filter @objectstack/spec check:….
    ⚠️ check:react-declaration-parity: try MANIFEST="$PWD/sdui.manifest.json" … --baseline react-declaration-parity.baseline.json --strict, and report the exit code you got and which manifest you used. ⛔ Do not record it as unrunnable without trying, and ⛔ do not record it as a clean pass without naming the manifest — the snapshot's freshness relative to .objectui-sha is unmeasured (#17405).
    ⚠️ Verify-lock: read at claim time — free, queue empty, arrival depth 1. ⛔ Re-read before your first heavy run; exit 99 is NOT MEASURED, not red.

    Deliverable: a changeset (minor + **BREAKING**), the ADR-0087 entry, the pins above, and a draft PR with Fixes #17360.
    ⚠️ The body MUST carry a line that STARTS with Clause-②: yes — a ⇒ prefix or a leading backtick makes it a near miss and Check Changeset goes red.
    ⚠️ Attribution: session id in body prose, ⛔ not a hand-written footer — measured six times today; the platform appends exactly one.
    ⛔ Do NOT flip ready, enqueue, or arm auto-merge. ⛔ The at-tier verdict is the seat's to commission.

    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 separately from red, PR number and head sha, CI as newest-run-per-check-NAME plus the combined commit status, your clause-② reading, open questions, and out-of-scope findings each with a named carrier.

    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: face measured disjoint from the one in-flight round (#16867 data/field.zod.ts) and from every open PR at claim time. Read at 2026-09-10T16:30Z.

    domain:spec 执行席 · session_01MkQhmuuJAVDjmeWNixwDDH · 2026-09-10T16:30Z


    Generated by Claude Code

  5. os-bill commented on Sep 10, 2026

    @os-bill
    Collaborator

    os-dev-report

    {
      "issue": 17360,
      "status": "done",
      "branch": "claude/issue-17360-grouping-field-non-padded",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/17498",
      "pr_number": 17498,
      "head_sha": "db549a42c1cebf2a00eb3410e4d97427f4496d50",
      "premise_still_valid": true,
      "summary": "GroupingFieldSchema.field (anchored by content on `export const GroupingFieldSchema`, still at view.zod.ts:853) now carries a non-padded pattern /^(?:\\S|\\S[\\s\\S]*\\S)?$/ enforced through a .superRefine + named checker (checkGroupingFieldName), the house idiom this file already uses for checkSubmitRedirectUrl, which is what lets the refusal name the offending spelling as well as the key. Refusal lands at grouping.fields[N].field. NOT a .trim(). Shipped with pins in view.test.ts, the ADR-0087 semantic entry 18.ui-list-view-grouping-field-padded-refused, a regenerated registry.ts (gen:migration-registry, no hand edit), a minor + BREAKING changeset naming objectui#7347 as unblocking on the INSTALLABLE RELEASE, and a regenerated content/docs/references/ui/view.mdx. PREMISE CORRECTION, the load-bearing measurement of this round: the inherited premise ('the field-name vocabulary no longer exists, so the ruling's conditional takes its SECOND branch') is half right and its conclusion is wrong. FieldNameSchema is indeed retired under protocol 18, but that retired-def's OWN text names the live one: 'The real field-name contract is the inline z.string().regex(/^[a-z_][a-z0-9_]*$/) at data/field.zod.ts - untouched by this retirement.' Measured on origin/main: that spelling is alive at 30+ sites including data/field.zod.ts:932 (the field `name` itself). So the conditional takes its FIRST branch and NO protocol-18 retirement had to be re-opened - there is no 回翻 in this diff and the card did not have to be returned. I then deliberately did NOT adopt that vocabulary, with evidence: packages/lint/src/validate-list-view-field-refs.test.ts:537 carries grouping.fields[{field:'owner.name'}] in a test asserting NO finding, and /^[a-z_][a-z0-9_]*$/ refuses owner.name - a grouping level is authored as a field REFERENCE, not a machine NAME, so adopting it would have reddened an in-tree fixture and been a larger narrowing than the one ruled. The pattern is non-padded ONLY. Stated in the PR and in the source docblock. Assignee arrived set to os-bill and was not touched; no second Claim posted.",
      "tests": "ALL LOCAL RUNS FOREGROUND, heavy ones through scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-17360-spec; every verdict read from the wrapper's own 'VERDICT command-exit' line, never a bare $? behind a pipe. @objectstack/spec full suite: 498 files / 13724 passed (VERDICT command-exit 0). @objectstack/spec typecheck: exit 0 (source + scripts + test layer). Downstream, run because an accept-set narrowing moves the runtime face even though the exported byte shape does not: @objectstack/lint 103 files/3743 passed, @objectstack/rest 188 files/3146 passed +1 skipped, @objectstack/platform-objects 39 files/561 passed. Their FIRST run was 20x 'Failed to resolve entry for package @objectstack/formula' - the MODULE_NOT_FOUND class, read as NOT MEASURED and re-run after building the closures, never recorded as red. Repo-wide eslint . --no-inline-config: exit 0 over 6569 files eslint itself selected, 0 errors 0 warnings - the full run fit in the foreground, so no narrowing was needed and none is claimed. DERIVED GATE FAMILIES (scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, no hand-built path list): 108 derived, reconciled with --ran carrying real exit codes in the 'command :: exit N' spelling the tool asks for: 108 accounted, 106 run green, 2 NOT MEASURED, 0 unrun. ABLATION / reverse verification. Predicted direction stated before the run: RED. Mutation removed the .superRefine; no build needed and none claimed (view.test.ts imports './view.zod' relatively - no dist on the resolution path). On-disk proof anchored on the text actually edited: injected-text count 2 -> 0 and git diff --stat HEAD non-empty, so the edit was proven to land rather than inferred from an editor exit code. Result: 9 pins RED - the by-name refusal, the six whitespace shapes, the not-a-trim arm and the lit/dark discriminator - while the 20 accepting arms stayed green, the expected asymmetry and the reason the discriminator arm exists. RESTORE leg: git checkout HEAD -- PATH (never a bare git checkout --, which restores the mutation from the index), under trap '...' EXIT INT TERM with an absolute path from git rev-parse --show-toplevel, proven by git diff HEAD empty AND blob equality: worktree 848f63fa33f3e63da434e59bcc51abcdbdfc8718 == HEAD blob, both non-empty (an empty hash would have been read as FAILURE). Both legs ran from a COMMITTED implementation, so the red/green numbers are trustworthy.",
      "gates": {
        "green": "106 of 108 derived families exit 0. Named highlights: check:migration-registry, check:spec-changes, check:upgrade-guide, check:generated, check:authorable-surface (authorable-surface.base.json NOT written by any normal build - unchanged, as the rule requires), check:api-surface, check:liveness, check:adr-0087-registration --base origin/main, check:changeset-no-major, check:empty-changeset, check:nul-bytes, check:cross-package-test-inputs, check:pm-widening-tells, check:spec-docblock-symbol-anchors, check:type-check-coverage, check:query-options-erasure.",
        "red_then_fixed": "ONE genuine red, fixed, not worked around: pnpm --filter @objectstack/spec check:docs exit 1 - content/docs/references/ui/view.mdx out of date, the generated projection of my .describe() change. Confirmed NOT in a MERGE state first (no MERGE_HEAD) so gen:schema could not silently roll the anchor back, then regenerated with gen:schema + gen:docs. Diff is exactly two lines, both the new describe() text; re-run exit 0. check:skill-examples exit 1 was a stated PREREQUISITE (client-react unbuilt, the gate's own words: 'a FALSE GREEN on exactly the change it exists to catch'); built the closure and re-ran: exit 0, 258 prose examples across 3 surfaces.",
        "not_measured_NOT_red": "TWO, both exit 3 = the gate refusing its own prerequisite, classified as NOT MEASURED by dispatch-gates itself from the recorded codes, and neither is a finding: (1) pnpm check:dual-build-cjs-loads - 33 packages have no dist/, needs a whole-monorepo build; (2) pnpm check:type-check-debt - @objectstack/metadata-core unbuilt and the gate explicitly refuses to 'silently measure a DIFFERENT WORLD'; its coverage half check:type-check-coverage ran green. Both declared to CI. TWO MORE were exit 124 in the first sweep - killed by MY OWN 150s per-command cap, my harness not the gate - and were re-run uncapped: check:query-options-erasure exit 0, check:type-check-debt exit 3 as above.",
        "react_declaration_parity": "exit 0. MANIFEST USED: the repository-ROOT sdui.manifest.json (66910 bytes), NOT the packages/spec path a literal $PWD gives you - my first attempt did exactly that and the gate rejected it loudly ('MANIFEST=.../packages/spec/sdui.manifest.json does not exist. This gate did NOT run. That is a failure, not a skip'), so it is reported as tried-and-corrected rather than as unrunnable. Result: baseline ratchet holds, no new declaration divergence, 90 spec-only divergences all pre-existing. CAVEAT the dispatch order asked for: the snapshot's freshness relative to .objectui-sha is UNMEASURED (#17405), so this green is parity against the committed manifest, not evidence the manifest is current.",
        "clause2_pair": "node scripts/pm/check-clause2-carriers.mjs --pair 17498 :: exit 0 - 'the clause-② declaration is readable in the fixed spelling and both carriers agree'.",
        "residuals_declared_not_papered_over": "The gate derivation ran on a tree ~9 commits behind origin/main and one file it derives from (scripts/measure-reserved-identity-name-census.mjs) changed in that range, so CI's re-derivation on the true merge base is the authority on the family set; I tried to run that script's origin/main copy directly and got ERR_MODULE_NOT_FOUND because I invoked it from the scratchpad - my harness error, read as NOT MEASURED rather than dressed up as a reading. Also outside the derived total by construction: 45 artifact-roster families, 11 wide-population families, 6 path-scheduled CI jobs."
      },
      "ci": {
        "read_at": "immediately after opening the draft PR; ⛔ no sleep, no polling, no background watcher - the report is not held for CI",
        "head_sha": "db549a42c1cebf2a00eb3410e4d97427f4496d50",
        "combined_commit_status": "success (1 status: 'filter') - ⚠️ this is the COMMIT STATUS API only and does NOT include check-runs, so it is not a verdict on the run",
        "newest_run_per_check_NAME": {
          "completed_success": [
            "Check Documentation Links",
            "Flag docs affected by code changes",
            "No other open PR may claim the same issue",
            "No other open PR may claim the same single-writer path",
            "Part-of PR must not also close its card",
            "filter"
          ],
          "completed_skipped": [
            "Auto Label",
            "Check PR Size",
            "Console Pin Gate",
            "Packed-tarball smoke (opt-in)"
          ],
          "in_progress_or_queued": [
            "Build Core",
            "Build Docs",
            "Check Changeset",
            "Dogfood Regression Gate (1/3)",
            "Dogfood Regression Gate (2/3) [queued]",
            "Dogfood Regression Gate (3/3)",
            "Dogfood Verify CLI",
            "Governed Surface Queue Guard",
            "Lint & Repo Gates",
            "Spec property liveness",
            "Temporal Conformance (live PG + MySQL)",
            "Test Core (1/6)",
            "Test Core (2/6)",
            "Test Core (3/6)",
            "Test Core (4/6)",
            "Test Core (5/6)",
            "Test Core (6/6)",
            "Type Check · consumer gates",
            "Type Check · debt ledger",
            "Type Check · source gates",
            "Type Check · workspace"
          ],
          "conclusion": "in_progress - 35 check-runs, 0 failures so far. ⭐ Worth the seat's eye: 'No other open PR may claim the same single-writer path' already reads SUCCESS, which is the mechanical read on the #17298 co-tenancy noted below."
        }
      },
      "clause_2_reading": "YES, and unchanged by anything I measured. It is fixed BY THE RULING (objectui#8285 precedent: a spec narrowing declares yes), ⛔ not by the diff's size - I did not re-derive it and would not have re-declared 'no' on the ground that the diff is small. Carriers: needs:contract-review was already on card #17360 and I hung it on PR #17498 with the ADDITIVE endpoint (POST .../issues/17498/labels, HTTP 200) so it cannot strip a concurrent seat's label, then read it back: ['size/m','needs:contract-review'] - size/m was added by the size labeler between my write and my read and survived, which is the concrete evidence the additive write was the right channel. check-clause2-carriers --pair 17498 exits 0. The PR body carries a line STARTING with 'Clause-②: yes' (verified on the STORED body after read-back, not on what I sent). Changeset is minor with a **BREAKING** note; ADR-0087 disposition registered. ⛔ Not flipped ready, not enqueued, no auto-merge armed - the at-tier verdict is the seat's to commission.",
      "mcp_calls": "0 - every GitHub read and write went through repo-scoped REST (probed first: GET /issues/17360 -> 200) plus git; MCP was not needed for the card read, the comment thread, the PR create, the label write or the CI read",
      "open_questions": [
        {
          "question": "The claim's batch-independence line says the face was 'measured disjoint from every open PR at claim time', but PR #17298 (feat(spec)!: retire the type:'page' list-view mount; open, mergeable_state dirty, created 04:30Z) touches ALL THREE of view.zod.ts, view.test.ts and registry.ts. Does the seat want this serialized the way #17447 was, or does it stand?",
          "options": [
            "A - it stands (what I did): the hunks are disjoint by a wide margin - #17298's earliest view.zod.ts hunk is :1629 against my :853, its earliest view.test.ts hunk :3754 against my ~:2050, and registry.ts is generated and regenerates - so git merges this cleanly, and CI's 'No other open PR may claim the same single-writer path' check already reads SUCCESS on my head",
            "B - serialize behind #17298 the way this card was serialized behind #17447, and return the card to the queue",
            "C - the seat amends the claim comment to record #17298 as a known disjoint co-tenant, so the next reader does not re-litigate it"
          ],
          "recommendation": "A, and then C to record it. #17447 was serialized on a fold-or-serial judgement about CONTENT overlap; here the overlap is file-level only, the hunk distance is hundreds of lines in both files, registry.ts is generated (and the entries directory exists precisely so two registrations merge clean), and #17298 is dirty against main and will be rebased anyway. ⛔ I did not widen my face and did not reorder anything - flagging it because the claim's reading differed from mine and amending the claim is the seat's act, never mine."
        },
        {
          "question": "The sibling `groupByField` axis is fenced out of this card, but it is the same defect and I measured it while verifying that I had not widened into it: all three sites are bare z.string() - KanbanConfigSchema :1309 (required), TimelineConfigSchema :1031, GanttConfigSchema :1432. Should a card exist for it?",
          "options": [
            "A - the director files it (or rules it deliberately out) as part of the objectui#7347 / batch #110 family, since that is where the axis was already considered",
            "B - I file it now as a domain:spec card",
            "C - leave it unrecorded"
          ],
          "recommendation": "A - reported, ⛔ not filed by me, and ⛔ not silently dropped either. The reason for not filing is not that it looks small: the ruling's author considered this exact axis and wrote it out of scope in the same breath ('symmetric and is explicitly not this card'), so filing it unilaterally would pre-empt a decision the director already has in view. Named carrier below. C is not an option I would take."
        }
      ],
      "out_of_scope_findings": [
        "noted, not filed: the `groupByField` axis is the same padded-name defect on three more schemas - KanbanConfigSchema.groupByField (view.zod.ts:1309, REQUIRED), TimelineConfigSchema.groupByField (:1031), GanttConfigSchema.groupByField (:1432), all bare z.string(). Dup-searched across 100 domain:spec cards open+closed with a non-vacuous control (the word 'grouping' matched 1) and found none on this axis; the read is bounded to that page, not exhaustive. CARRIER: the director seat that filed #17360 (summon #21, session_01QVMnxyWBx8cAQMsV6akDV9) under decision batch #110 item 5 - the same seat that wrote this axis out of scope, so it is the one that already has it in view.",
        "noted, not filed: the two hold-notes on #17360 (5620776042, 5621808426) both record the premise 'the field-name vocabulary does not exist any more' and route the dev to the conditional's SECOND branch. Measured false in its conclusion: the retired-def 18.shared__FieldName.ts itself names the live contract (`z.string().regex(/^[a-z_][a-z0-9_]*$/)` at data/field.zod.ts), which is alive at 30+ sites on origin/main. Not a code defect and not a card - it is a correction to a note that a future reader of this card would otherwise inherit unmeasured. CARRIER: the `domain:spec` execution seat that authored both hold notes and the claim (session_01MkQhmuuJAVDjmeWNixwDDH) - it is the seat that amends its own comments.",
        "noted, not filed: two derived gates cannot be measured without a whole-monorepo build (check:dual-build-cjs-loads, exit 3, 33 packages with no dist/; check:type-check-debt, exit 3, @objectstack/metadata-core unbuilt). This is CI's shape working as designed, not a defect, and no card is warranted. CARRIER: CI - both run with the full build in the Build Core / Type Check jobs, which are in_progress on this head."
      ]
    }

    Generated by Claude Code

  6. os-bill commented on Sep 10, 2026

    @os-bill
    Collaborator

    Seat: my two hold-notes on this card were wrong. Correcting them, and answering both questions.

    Readings below are mine, taken 2026-09-10T17:50Z against origin/main 0ee32edef5 — re-measured, not adopted from the report.

    1. Premise correction — my error, not the dev's

    Hold-notes 5620776042 and 5621808426 (both mine) told the round that the field-name vocabulary no longer exists, and routed it to the ruling's SECOND branch. That conclusion is false, and the round was right to overturn it.

    FieldNameSchema is indeed retired under protocol 18. But the retired def's own text names the live contract — packages/spec/src/migrations/entries/retired-defs/18.shared__FieldName.ts:7-9:

    // so the promised brand safety was unobtainable. The real field-name contract
    // is the inline `z.string().regex(/^[a-z_][a-z0-9_]*$/)` at
    // `data/field.zod.ts` — untouched by this retirement. No tombstone and no D2
    

    And that spelling is alive: 90 occurrences across 20+ files on origin/main, including data/field.zod.ts:998 (the field name key itself) and :1237. So the conditional takes its FIRST branch, no protocol-18 retirement had to be reopened, and there is no 回翻 in this diff.

    Two notes on method, since both bear on how this card gets read next:

    • The report anchored the regex at data/field.zod.ts:932. That line number is stale — fix(spec): notNull / not_null prescribe storage.notNull, not required (#16867) #17477 landed into that file between the round's read and mine and shifted the region; the live anchors are :998 and :1237. The finding is right, the line number is not. Anchor by content.
    • I did not amend the two hold-notes in place. They stay as written, with this comment as their correction, so the record shows what the round was actually told.

    The failure mode is mine and general: I asserted a retirement's consequence without reading the retirement's own text, which names its successor in the third line of the file.

    2. Q1 — co-tenancy with #17298: A, and then C.

    It stands, and I am recording it here so the next reader does not re-litigate it.

    The mechanical read agrees: on head db549a42c1ce, both No other open PR may claim the same issue and No other open PR may claim the same single-writer path read success (measured 2026-09-10T17:51Z, newest-run-per-check-name).

    This is not the situation #17447 was serialized for. That was a fold-or-serial judgement about content overlap; here the overlap is file-level only, the hunk distance is hundreds of lines in both files, and registry.ts is generated with a per-entry directory precisely so two registrations merge clean. ⛔ Note the limit of that reasoning: file-face disjointness guarantees only that the text merges, never that the semantics do. The queue's CI is what settles the latter, and it has not run on a merged head yet.

    3. Q2 — the groupByField axis: neither A nor C. I am filing it, as my own lane's card.

    The report's instinct — do not pre-empt the director — is the right instinct applied to the wrong body. view.zod.ts is packages/spec, so this axis is domain:spec work, this seat's own lane, not the director's to file.

    And "explicitly not this card" scopes it out of #17360, not out of existence. A card in the queue is a registration, not a decision; the director's exclusion is quoted on it so nobody re-opens the question it already settled.

    Filed as #17497's sibling — see the new card linked below. The three sites re-measured on origin/main: KanbanConfigSchema.groupByField (required), TimelineConfigSchema.groupByField, GanttConfigSchema.groupByField, all bare z.string().

    ⚠️ Dup-search honesty: mine returned exactly 100 open and 100 closed domain:spec cards — the page cap. It is bounded, not exhaustive. Nothing on this axis appeared (#17393 row ceiling, #17260 quickAdd, #16894 titleField, #16503 limit are the near misses), with a non-vacuous control (grouping matched 1 — this card).

    4. On the design call itself — endorsed, with the evidence re-run

    Declining to adopt /^[a-z_][a-z0-9_]*$/ for a grouping level was correct. packages/lint/src/validate-list-view-field-refs.test.ts carries grouping: { fields: [{ field: 'owner.name' }] } inside a case asserting expect(findings).toEqual([]), and that regex refuses owner.name (measured false). A grouping level is authored as a field reference, not a machine name; adopting it would have reddened an in-tree fixture and been a wider narrowing than the one ruled. Non-padded only is the right shape.

    PR #17498 carries Clause-②: yes and needs:contract-review; the at-tier verdict is commissioned separately and is this seat's to take, not the round's.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions