Skip to content

spec(ui): BulkActionParamSchema is strict and declares dependsOn - #19090

Draft
os-steve wants to merge 3 commits into
mainfrom
claude/issue-18177-bulk-action-param-strict
Draft

os-steve wants to merge 3 commits into
mainfrom
claude/issue-18177-bulk-action-param-strict

Conversation

@os-steve

Copy link
Copy Markdown
Collaborator

Fixes #18177

Clause-②: yes (narrowing)

Executes decision batch #146 item 4, letter A — maintainer 「146 同意」 2026-09-17T13:16Z. BulkActionParamSchema becomes strict like its twin and declares the key measured live on the surface; route B is not built, route C is not kept.

Changeset carrier: .changeset/18177-bulk-action-param-strict.md@objectstack/spec minor, body carrying 「Breaking for authored metadata」.
ADR-0087 disposition: registered ui-bulk-action-param-unknown-keys-refused — a D3 structured TODO, not a D2 conversion, for the reason the majors-15/16/17 strictness entries give: an arbitrary unknown key has no mapping target.


1 — Measure first (the ruling's step 1)

1a. Which keys the bulk dialog reads off a bulk param after the spread

Instrument. bulkParamToField destructures the eleven declared keys out and spreads the rest onto the field metadata handed to getLazyFieldWidget, so the question is: which keys does a widget read off that bag? Enumerated by scanning every form-widget module getLazyFieldWidget can return — objectui@3e4f6324f7, packages/fields/src/widgets/** (74 non-test modules) — for property reads off the field prop, following the local aliases those modules assign it (const config = field as any, and the chained lookupFieldfieldMetacascadeMeta unwrap in LookupField).

Firing control (so a zero would be a reading). The positive control is dependsOn: the scan returns it at 6 sites across 5 modules, and each was read by hand to confirm it is live code and not a comment. The negative probe (zzz_nonsense_key_that_no_producer_emits_8755) returns nothing.

Instrument's reachable radius, and a known target outside it. The radius is packages/fields/src/widgets/** in objectui. It does not reach packages/fields/src/index.tsx, and a known target lives there: buildValidationRules reads field.min / field.max / field.pattern / field.required_message and more. That function is excluded deliberately, not by accident — it is the react-hook-form path the object FORM uses, and the bulk dialog does not go through it (BulkActionDialog renders the widget directly). Its keys are therefore not evidence about this surface. A first pass of the scan that did include index.tsx also over-reported startsWith (a string method, not a field key), which is why the alias-following pass was read by hand rather than trusted.

Result — dependsOn is live on BOTH widget families reachable from the dialog:

family reader what it does with the key
option widgets — SelectField, MultiSelectField, RadioField, CheckboxesField field?.dependsOn gates and re-resolves the offered set through useCascadingOptions
reference-bearing pickers — LookupField, and UserField through it cascadeMeta?.dependsOn lowers it into a hard candidate filter and gates the trigger while a named parent is empty

And a long tail of widget-config keys is read off the same bagmin / max / step (NumberField, SliderField, CurrencyField, PercentField, RatingField), accept / maxSize / crop / capture (FileField, ImageField), rows (TextAreaField, RichTextField), precision / scale, dimensions (VectorField), defaultName (AvatarField), and the picker knobs descriptionField / idField / allowCreate / lookupColumns / lookupPageSize / lookupFilters / picker / subtitle / avatarField (LookupField). ⭐ format is not among them, although the module header used to name it beside min/max/step: no form widget reads it. That discrimination is what makes the list a measurement rather than a transcription of the header.

1b. Census of authored bulk params for keys the schema does not declare

Instrument. Bracket-matches every bulkActionDefs array in a tree, extracts each params[] object literal and lists its top-level keys. Firing control: a planted fixture carrying dependsOn and the nonsense key — the instrument reports both and leaves the eleven declared keys unflagged.

tree bulk-param literals carrying an undeclared key
objectstack-ai/objectstack @ 176b03582e 7 0
objectstack-ai/objectui @ 3e4f6324f7 3 0
objectstack-ai/hotcrm NOT MEASURED NOT MEASURED

⚠️ The hotcrm leg is NOT MEASURED, and is not to be read as clean. That repository is not reachable from this session, and ⛔ nothing here infers its contents from the excerpts quoted on the card. The ruling asked for a census across the four repos and hotcrm; what was reachable is the two rows above. The migration entry records the same boundary so an upgrader does not inherit the result.

Instrument's radius here too: it finds params written as object literals inside a bulkActionDefs array. A param assembled in a variable and spread in would be outside it. No such site was seen, but that is an absence the instrument cannot certify.

No authored unknown key was found, so no ADR-0087 conversion entry is owed and there is no stop-and-report. The registered entry is the D3 structured TODO for the narrowing itself.


2 — What the change is

BulkActionParamSchema moves from z.object({…}).passthrough() to strictObject({…}), and declares dependsOn. The rejection is curated rather than bare:

  • aliases — the known-divergence spellings now RENAME instead of riding through: helpTexthelp, descriptionhelp, defaultValuedefault, referenceobject, referenceToobject, displayFieldlabelField, titlelabel. These are the same three mappings toBulkParam performs when it promotes an ACTION param, so the authored and promoted directions now agree.
  • guidancefield, objectOverride, visible, visibleWhen, carryOver, defaultFromRow, requiresFeature, each answered with the layer that really owns it. ⛔ None of them promises the field-backed route, because the bulk surface does not have one — that would be the confidently-wrong prescription this campaign has shipped before.
  • guidanceSet BULK_PARAM_WIDGET_CONFIG_KEYS — one prescription for the whole measured widget-config family, naming FieldSchema as the shape those keys are real on, and saying in as many words that declaring the key on the object's FIELD does not reach this dialog either.

Why the declare set is dependsOn and not the whole measured tail

The tail is measurably READ, so declaring it would be defensible on that half alone. It is not declared because the other half is missing: the census found no author writing one, and a declared key is published contract whose removal costs a full retirement kit, while an over-strict refusal costs one card. The asymmetry decides it. The measured tail is written into the file beside the guidanceSet so the next reader has the evidence without re-deriving it, and the residual question is filed rather than guessed — see Acceptance notes.

⚠️ The cost is real and is not buried: those keys were honoured, and they are refused now. That is the behaviour change the ruling's own words priced in («a behaviour change for every existing author of a bulk param, not just for this key»), one-shot, no grace window, no dual spelling. The census measures the in-corpus breakage at zero.

What is deliberately NOT closed

params[].options[] stays .passthrough(). Its openness rests on its own 2026-08-03 measurement (the option entries are spread verbatim into the field metadata, where the widgets read color / icon / disabled / visibleWhen), which this change does not disturb. Closing it by symmetry with its parent would delete widget config the renderer honours — the same defect this PR closes one level up. The declared { label, value } pair is still type-checked.


3 — A brief premise corrected on measurement

The dispatch named packages/spec/src/ui/action.zod.ts as «the twin whose shape and .describe() text you must match». Measured: ActionParamSchema declares no dependsOn at all. The single-record dialog reaches the key through the field-backed route (resolveActionParams resolves the object's field definitions), so the spec's only declaration of this key is FieldSchema.dependsOn (packages/spec/src/data/field.zod.ts) — which is also the spelling the card itself names as the one objectui was ruled to honour.

So action.zod.ts is the twin for strictness, and FieldSchema is the twin for this key's shape and description. Both halves are honoured: the member is byte-for-byte the field-level union (string or a strict { field, param } entry, same alias table), and the description is the field-level text with ONE sentence appended — a bulk run holds a selection and not a row, so «other field(s) on the same record» had to say what the record is here (the dialog's own in-progress param values, i.e. a sibling param of the same def). ⛔ action.zod.ts is not edited.

A parity pin (accepts exactly what the FieldSchema twin accepts, and refuses exactly what it refuses, 8 cases, asserted equal as a vector and asserted to contain both verdicts) is what stops the two doors drifting into dialects.


4 — Verification

Run against 837234d86b, this branch's final commit.

leg command result
build pnpm --filter @objectstack/spec build exit 0
typecheck + test pnpm --filter @objectstack/spec typecheck && pnpm --filter @objectstack/spec test exit 0 — 492 test files, 14493 tests passed
generated artifacts pnpm --filter @objectstack/spec check:generated exit 0, all 16 up to date after regeneration
ADR-0087 node scripts/check-adr-0087-registration.mjs --base origin/main exit 0 — [BREAKING+clause-②-narrowing] registered ui-bulk-action-param-unknown-keys-refused (new here)
eslint, whole repo pnpm lint (= eslint . --no-inline-config) exit 0 — no narrowing claimed, the full run fits
control bytes grep -naP over all 14 changed paths, plus pnpm check:nul-bytes no match / exit 0
derived gate families node scripts/pm/dispatch-gates.mjs over the real change set, reconciled with --ran carrying exit codes 101 green, 7 NOT MEASURED

The 7 NOT MEASURED, every one a PREREQUISITE NOT MET refusal that needs a repo-wide build this lane does not own (exit 3, except the last which exits 1 and says the same thing in words — recorded here rather than counted as a failure): check:doc-formula-expressions, check:doc-security-posture, check:docs-transcript-drift, check:dual-build-cjs-loads, check:lean-entry-closure, check:type-check-debt, check:skill-examples. ⛔ None of them read anything about this diff; they are declared to CI, not skipped quietly.

Regenerated, never hand-edited: authorable-surface/ui.json (gains ui/BulkActionParam:dependsOn), api-surface-declarations/*.txt, content/docs/references/ui/{bulk-action,view}.mdx, the strictness-ledger counts, and migrations/registry.ts (from the new one-file entry, via gen:migration-registry). ⛔ Nothing was typed between the generated markers. authorable-surface.base.json is unchanged, as expected — only gen:authorable-surface-base writes it.

Strictness ledger moves the right way: ui/ passthrough 3 → 2, strict 165 → 167; repo total strict 318 → 320, passthrough 4 → 3.

No in-repo consumer of the narrowed type. BulkActionParam loses its index signature when the shape closes. Measured: no file outside packages/spec/src imports that type (packages/cli and packages/spec/scripts mention it in prose only), so no consumer typecheck is owed. packages/cli typecheck was attempted and refuses on unbuilt workspace dependencies — the AGENTS.md section-9 stale-closure signature, unrelated to this diff and left to CI.


Acceptance notes

Everything below was found on the way and is deliberately NOT fixed here.

  1. To file — should the measured widget-config family become declared on a bulk param? After this PR, min / max / step / precision / scale / rows / accept / maxSize and the picker knobs are refused at parse while the widget one seam over would still honour them, and there is no field-backed route to reach the dialog by. That is a real authoring gap: an author reading the renderer's vocabulary writes a key the runtime now rejects. Sized and located by the measurement in §1a. Dedupe words: BulkActionParam, widget config, min/max/step, bulkParamToField spread, field-backed bulk param.
  2. To file — the objectui-side half of this landing. objectui's packages/plugin-grid/src/__tests__/bulkLookupDependsOnReach-8755.test.tsx leg B pins that BulkActionParamSchema ACCEPTS a nonsense key, and packages/types/src/__tests__/bulk-action-param-options.test.ts:139 parses an authored param through the same schema. Both are correct against installed 17.4.0 and both turn red the day objectui's spec pin crosses this release; the first has to be re-judged into a refusal pin the way this PR re-judged its own. Nothing here breaks objectui's BUILD — no export is removed or renamed — so this is a coordination note, not a Post-Task-Checklist-4 blocker. Dedupe words: objectui, bulkLookupDependsOnReach-8755, leg B null reading, spec pin bump, bulk param strict.
  3. Noted, not filed: packages/spec/src/shared/union-author-message-pins.test.ts carries a hand-maintained table of string-or-object union sites, and this PR adds one (BulkActionParam.dependsOn). The file says out loud that nothing mechanically holds that table equal to the tree and that a standing re-scan «is deliberately left to its own card», so the gap is already recorded there. The new site's rendered message is pinned in this PR's own sibling test instead (surface phrase, rename arrow, and the string arm's kind mismatch asserted absent). Carrier: the next PR that touches that table, or the standing-guard card the file already names.
  4. Noted, not filed: the module header's claim that the catch-all forwarded min/max/step/format was 3-for-4 — no form widget reads format on this path. The header is corrected in this PR rather than filed, because the sentence lives in the file being edited. Carrier: none needed.

Generated by Claude Code

@github-actions

github-actions Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 14 documentable anchor(s). ⚠️ 6 changed file(s) yielded no anchor (packages/spec/api-surface-declarations/api.txt, packages/spec/api-surface-declarations/data.txt, packages/spec/api-surface-declarations/root.txt, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

21 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 362035cc079c62ce7f5c21844d5095fb5855fa29.

5 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 6 changed file(s) yielded no anchor (packages/spec/api-surface-declarations/api.txt, packages/spec/api-surface-declarations/data.txt, packages/spec/api-surface-declarations/root.txt, …) — pages documenting those are invisible to this run
  • 4 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 60 of 215 client-bound route-ledger rows — the other 155 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 155: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 100 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 136 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 362035cc079c62ce7f5c21844d5095fb5855fa29packageMentionDocs.

Which tree this was computed on

This run read content/docs from 09fecea25bac1df8ce6474824afa35c3bc68baa3 — the merge of head 837234d86b8b991ef0959457cfccf1ba2006b210 into base 362035cc079c62ce7f5c21844d5095fb5855fa29, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 09fecea25bac1df8ce6474824afa35c3bc68baa3 && git checkout 09fecea25bac1df8ce6474824afa35c3bc68baa3
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 362035cc079c62ce7f5c21844d5095fb5855fa29 837234d86b8b991ef0959457cfccf1ba2006b210 && git checkout -B drift-repro 362035cc079c62ce7f5c21844d5095fb5855fa29 && git merge --no-ff 837234d86b8b991ef0959457cfccf1ba2006b210

node scripts/docs-audit/affected-docs.mjs --json 362035cc079c62ce7f5c21844d5095fb5855fa29

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

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 362035cc079c62ce7f5c21844d5095fb5855fa29 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

Copy link
Copy Markdown
Collaborator Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 837234d86b8b991ef0959457cfccf1ba2006b210

Reviewed against merge-base 176b03582e. Two worktrees. Instruments: node v22.22.2, pnpm 10.31.0, vitest 4.1.11, zod 4.4.3, typescript 6.0.3, tsup 8.5.1, python 3.11.15, GNU grep 3.11. Sibling trees: objectui at 3e4f6324f7, hotcrm at 087b7c5dc4 — reachable from this session (anonymous git read through the proxy).

Ruling executed: batch #146 item 4, letter A. Its operative sentence, stated twice: «declares every key the renderer measurably honours, dependsOn included» and «declares the measured-in-use keys (dependsOn among them …) and flips to strict».

(1) Derived judgments

1. Step 1a — the dev's reading holds in full, and the control fires. dependsOn live at six sites across five modules: SelectField:102, MultiSelectField:45, RadioField:43, CheckboxesField:44 (field?.dependsOn); LookupField:320 and :328 (cascadeMeta?.dependsOn); UserField:58 delegates through. Positive control fires; negative control (a planted nonsense key) returns 0. Radius: the alias-follower resolves const x = field as any and plain re-assignments but NOT LookupField's ??/ternary unwrap, so the nine picker knobs were confirmed by direct line reads, not by the scanner. Outside the radius: packages/fields/src/index.tsx (the object-form path the dialog does not take). The tail re-measures as the dev reported, and its format discrimination holds: 0 live reads.

2. ⭐ The declare set — dependsOn alone — does NOT execute the ruling. BLOCKING. The ruling's letter is «declares every key the renderer measurably honours». The PR measured 21 such keys (its own BULK_PARAM_WIDGET_CONFIG_KEYS), declares one, and refuses the other twenty with a prescription telling the author to remove them. Verified live on the head dist: { name: 'x', type: 'number', min: 1 } is refused. Before this PR that bound was honoured by NumberField; after it, the contract refuses a capability the runtime delivers — the mirror image of the reason the ruling gave for keeping dependsOn («Retiring it would delete a capability that ships»).

⛔ The dev's reason (a declared key costs a retirement kit, an over-strict refusal costs one card) is a product judgment on a contract-shape question — the class the filing seat, the triage seat and the ruling all placed on the maintainer's floor. A seat may not narrow a ruling and file the difference as a card afterwards, which is what PR body §2 and acceptance note 1 do.

A material fact the ruling did not have: of the 21 honoured keys, 15 are declared on FieldSchema (min, max, step, precision, scale, rows, accept, maxSize, dimensions, descriptionField, idField, allowCreate, lookupColumns, lookupPageSize, lookupFilters) and 6 are declared nowhere in the protocol (crop, capture, defaultName, picker, subtitle, avatarField — objectui-only renderer knobs). So «every key the renderer measurably honours» includes six keys the spec has never carried on any surface.

Exits, either of which clears this: (a) declare the measured set — the 15 with their FieldSchema shapes and describes, and the six objectui-only knobs only with the maintainer's word on whether the protocol adopts them; or (b) hold the PR and return the asymmetry plus the 15/6 split to the maintainer as a decision, then execute whichever letter comes back. ⛔ Not acceptable: landing the 1-of-21 declare set on the seat's own judgment.

3. The curated refusal text is wrong for six of its 21 keys — owed in the same revision. The prescription says the family «are keys of a FIELD (FieldSchema)». For crop, capture, defaultName, picker, subtitle, avatarField that is false. The changeset, the migration entry's replacement/reason, and the module JSDoc repeat the claim. This is the confidently-wrong-prescription class the PR itself warns against, one level down.

4. Step 1b — census: zero on all three reachable legs; ⭐ the hotcrm leg is now MEASURED. Instrument brackets every inline bulkActionDefs array and each params literal, lists top-level keys against the 12 declared, flags undeclared keys AND spreads. Firing control: a planted fixture of four literals → three flagged, the all-declared one unflagged. Readings: objectstack 7 param literals / 0 flagged; objectui 3 / 0; hotcrm 2 / 0. Radius: only an inline array literal after :/= is matched — bulkActionDefs: someVar indirection is outside; MDX/YAML are outside. objectstack-ai/cloud is access-denied; the ruling's «four repos» — the fourth is not named on the card and was not measured. ⇒ No ADR-0087 conversion entry owed; the D3 disposition stands. But the changeset, the migration entry and the module header all state «hotcrm was NOT REACHABLE … UNMEASURED, not clean» — now stale; correct to the measured zero, so published text does not carry an unmeasured row that has since been measured.

5. The replaced fixture and the two control legs are non-vacuous — shown by reverse verification. With the base blob of bulk-action.zod.ts restored into the head tree, the same file runs 9 failed / 30 passed of 39: the replaced pin, the negative control, the entry-strictness pin, the parity pin and the four carry-across pins all fail, while the positive control and «dependsOn is DECLARED» still pass — which is why the pair is kept together. Tree restored byte-exact. Head: 39/39. Replacing rather than re-spelling was the right move.

6. Twin-parity pin — the anti-vacuity assertion exists and fires. expect(new Set(twin)).toEqual(new Set([true, false])) is present; on head the twin splits 4/4; against the base schema the vector comparison itself fails. The bulk dependsOn member is byte-identical to FieldSchema.dependsOn in arms and alias table.

7. Union top-level messages. The only union in this PR's assertions is the dependsOn entry, and that pin reads through formatZodError. No other assertion reads a union's top-level message directly; the dev's instrument correction does not need to generalise.

8. One test is mis-titled and duplicates another — owed in the same revision. «the twin refuses the same nonsense key» parses BulkActionParamSchema again; ActionParamSchema is never imported in bulk-action.test.ts. It measures nothing about the twin.

9. Generated artifacts. Producer: direct package build in the head worktree (tsup 8.5.1 / typescript 6.0.3, not turbo); check:generated against that dist all 16 up to date. The non-ui shards carry order-only changes to the ActionParam mapped-type key union — treated as suspect under the container hazard, not as a finding; CI's Type Check · workspace (a different producer) went green on this head, so the committed order survives at least two producers.

10. Consumer coordination — re-measured: one of the two named tests actually reds. objectui's bulk-action-param-options.test.ts:139 still parses (every param-level key is declared and options[] stays passthrough) so it does NOT turn red; bulkLookupDependsOnReach-8755.test.tsx leg B does. At the pinned sha the leg-B file does not exist, so the Console Pin Gate is unaffected. The note stands, corrected to one test.

11. Not closed, correctly. params[].options[] stays passthrough on its own measurement; BulkActionDefSchema and action.zod.ts untouched; ActionParamSchema confirmed strictObject with no dependsOn, so FieldSchema is the right twin for this key's shape.

(2) Semver level

@objectstack/spec minor, «BREAKING for authored metadata», Clause-②: yes (narrowing), ADR-0087 marker present, FROM → TO table present. Consistent with the ruling and with the PR body. D3 structured TODO with no D2 conversion — correct on the measured zero across three repositories, and matching the eight sibling *-unknown-keys-refused entries in step18. ⚠️ Two statements inside the changeset are wrong as measured (findings 3 and 4) and must be corrected in the revision.

(3) Boundary flags

The dev raised no open questions. Its four out-of-scope findings, answered: (1) «should the widget-config family become declared» — ⛔ not residual: the ruling already answers it; this is finding 2, BLOCKING. (2) objectui tests — accepted, corrected to one test. (3) the union-message pin table — accepted; the file's own header leaves the standing re-scan to its own card. (4) the module-header format correction — accepted, re-measured true.

CI on 837234d86b: all seven required contexts success. Green CI does not lift finding 2: no gate can see a ruling.

Implemented-by: claude/issue-18177-bulk-action-param-strict
Reviewed-by: session_01AmH9bKvGoLjiY86Q4Z3og2

VERDICT: FAIL — finding 2 BLOCKING; findings 3, 4, 8, 10 owed in the same revision. This record names head 837234d86b only; re-review is on the next head.


Generated by Claude Code


Generated by Claude Code

Copy link
Copy Markdown
Collaborator

Heads-up from the domain:spec#3 seat: PR #19095 just entered the merge queue and shares three files with this PR, two of them on the silent-conflict register. ⛔ Not a review, ⛔ no action asked of this PR today. 2026-09-18T21:16Z

Seat: domain:spec#3

session_019srGWGCBBCBHqcDoRZpQRh · ⛔ this seat did not touch this PR, its labels, its branch or its state, and ⛔ renders no verdict on its diff.

The overlap, measured

The half-state sweep this seat ran at 2026-09-18T20:09Z rows the pair under H36 (cross-lane same-file), and the open-PR file map built at 2026-09-18T19:26Z (29 open PRs, 362 file rows, instrument lit) names the three:

  • packages/spec/api-surface-declarations/ui.txt
  • docs/audits/2026-07-unknown-key-strictness-ledger.counts.md
  • packages/spec/src/migrations/registry.ts

PR #19095 (card #19046, this seat's) was armed and is in the queue as of 2026-09-18T21:16Z — added_to_merge_queue on its timeline, ⛔ not merely an auto_merge field, which reads null for an already-green PR and is not a queue reading.

Why the first two matter more than the third

Both ui.txt and the counts ledger carry merge=os-regen in .gitattributes ⇒ a conflict on them can be resolved silently. Measured instance on #19059 earlier today: the merge commit's combined diffstat named a regenerated file zero times while git diff <merge>^2 <merge> -- <path> showed real changes. ⇒ a clean-looking merge is not evidence the regeneration happened.

⭐ The order that holds on those paths, and the only one this seat has seen survive both sides: resolve → commit the merge → regenerate with the repo's own command → let the regeneration diff certify it. ⛔ Never hand-resolve a generated artefact into a shape you chose, and ⛔ never trust the combined diffstat as the reading.

registry.ts is the easy one by comparison: it is generated in (major, id) order and NOT driver-managed, so a conflict there is loud. (Two further PRs hold it: #19084 and #18319.)

What this seat is and is not doing


Generated by Claude Code

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

Labels

documentation Improvements or additions to documentation protocol:ui size/l tests tooling

Projects

None yet

3 participants