Raised by the repo:hotcrm PM seat. #1198 was dispatched to execute its 2026-08-17 deletion ruling and came back blocked with the tree byte-identical — no PR, no commits, nothing edited. The refusal is correct and this card is why.
⛔ This card does not ask whether crm_product.tax_rate should be deleted. That is ruled and the seat is not reopening it. The dev's own reasoning is endorsed: a regression fixture is not a business consumer, and letting a test fixture veto a maintainer ruling inverts the hierarchy. The deletion stands. What is undecided is what happens to the guard on the other side of it.
The measured collision
test/field-consumer-scan.test.ts — #1193's object-aware-resolution guard — uses crm_product.tax_rate as its canonical fixture. Not incidentally: 19 occurrences, and the file's own prose calls it "#1193's headline row" and "the control".
Verified independently by the seat, not taken from the report:
test/field-consumer-scan.test.ts:62 expect(fieldsByObject.get('crm_product')?.has('tax_rate')).toBe(true)
test/field-consumer-scan.test.ts:65 expect(verdictOf('crm_product','tax_rate')).toBe('inert')
test/field-consumer-scan.test.ts:262 expect(output).toContain('crm_product.tax_rate — 4 site(s)')
test/field-consumer-scan.test.ts:304 "'tax_rate' is declared on crm_product, crm_quote_line_item"
scripts/scan-field-consumers.ts:123 CARRIER_ROOTS = new Set(['translations','data','mappings'])
scripts/scan-field-consumers.ts:144 DISPLAY_ROOTS = new Set(['views','pages','apps'])
The ruling's zero-consumers basis was sound and structurally blind at the same time. scan-field-consumers.ts walks the registered metadata stack only — those six roots, never test/. A test fixture therefore cannot appear in the ledger the ruling was read from. The ruling was not careless; the instrument could not see this.
The dev measured the breakage by ablation rather than prediction: removing the field and its four locale rows turns 4 of 19 cases red (Tests 4 failed | 15 passed), with the mutation proved on disk (five blob hashes moved and were restored to their HEAD values, git diff HEAD empty afterwards) and a positive control that pnpm validate really re-read the mutated tree (334 Fields → 333 Fields).
⚠️ And the dependency was deepened after the ruling. Dated through the REST commits endpoint because the local clone is shallow: 5d46177f created the scan and the fixture at 2026-08-17T05:46:24Z, 6h14m before the ruling comment at 12:00:39Z. Then ac02bf15 (#1266) at 2026-08-24T02:45:30Z added the --sites control that pins crm_product.tax_rate by name with exit 0 and a literal output string — seven days after the ruling.
Why re-pointing the fixture does not solve it
The guard needs a field name declared on two objects with different verdicts. Measured from pnpm scan:fields --json, exactly three exist: tax_rate, line_number, description.
#1199 proposes removing line_number and description. So:
|
inert rows on the tree |
under adjudication |
| #1198 |
1 (crm_product.tax_rate) |
yes, ruled |
| #1199 |
14 |
yes, queued |
| remaining if both land as proposed |
0 |
— |
⇒ the live-vs-inert pair class is extinguished, not merely reduced. Re-pointing the fixture at line_number today means re-pointing it again when #1199 lands. This is why the dev stopped rather than picking one.
The options
A — Extend #1198's surface to test/field-consumer-scan.test.ts; repair the fixture; land the deletion in one PR. Fastest. ⚠️ Buys a second re-point when #1199 lands, and does authorship inside another card's guard.
B — Re-point to a live-vs-display-only pair. Seven shared names keep that divergence through both cards (billing_address, description, do_not_call, lead_source, notes, title, website). ⚠️ Changes what the guard asserts, and the --sites control — which by its own comment needs "a DECLARED but genuinely inert field" returning exit 0 — has no inert field left to pin and must be re-specified.
C — Sequence #1198 behind #1199 so the fixture is re-pointed once, after the whole inert ledger is adjudicated. The dev's recommendation (C then A).
D — ⭐ Retire the live-vs-inert half of the guard, rather than re-point it. ⛔ Neither the dev nor the ruling considered this, and the seat thinks it is the reading that fits the repo's own rule: a guard retires because its subject retired, never because it went quiet. If zero inert fields is the intended steady state — and #1198 + #1199 together say it is — then a guard proving the resolver can distinguish live from inert is guarding a category the repo has decided not to have. Option B keeps the guard alive by quietly redefining what it proves; D says so out loud and deletes it. ⚠️ D is only correct if "zero inert fields" is intended rather than incidental. That is the question underneath all four options, and it is the one the seat cannot answer.
What the seat has done meanwhile
Three scope items in the 2026-08-17 ruling are stale — measured, not inferred
Recorded so the ruling is not re-executed verbatim once this unblocks:
⚠️ Also for whoever executes: src/objects/quote_line_item.object.ts:60 says "crm_product keeps tax_rate there too" — one comment clause that goes false the moment the deletion lands. Fix it in the same PR; it is not worth its own card.
Raised by the
repo:hotcrmPM seat. #1198 was dispatched to execute its 2026-08-17 deletion ruling and came back blocked with the tree byte-identical — no PR, no commits, nothing edited. The refusal is correct and this card is why.⛔ This card does not ask whether
crm_product.tax_rateshould be deleted. That is ruled and the seat is not reopening it. The dev's own reasoning is endorsed: a regression fixture is not a business consumer, and letting a test fixture veto a maintainer ruling inverts the hierarchy. The deletion stands. What is undecided is what happens to the guard on the other side of it.The measured collision
test/field-consumer-scan.test.ts— #1193's object-aware-resolution guard — usescrm_product.tax_rateas its canonical fixture. Not incidentally: 19 occurrences, and the file's own prose calls it "#1193's headline row" and "the control".Verified independently by the seat, not taken from the report:
The ruling's zero-consumers basis was sound and structurally blind at the same time.
scan-field-consumers.tswalks the registered metadata stack only — those six roots, nevertest/. A test fixture therefore cannot appear in the ledger the ruling was read from. The ruling was not careless; the instrument could not see this.The dev measured the breakage by ablation rather than prediction: removing the field and its four locale rows turns 4 of 19 cases red (
Tests 4 failed | 15 passed), with the mutation proved on disk (five blob hashes moved and were restored to their HEAD values,git diff HEADempty afterwards) and a positive control thatpnpm validatereally re-read the mutated tree (334 Fields→333 Fields).5d46177fcreated the scan and the fixture at 2026-08-17T05:46:24Z, 6h14m before the ruling comment at 12:00:39Z. Thenac02bf15(#1266) at 2026-08-24T02:45:30Z added the--sitescontrol that pinscrm_product.tax_rateby name with exit 0 and a literal output string — seven days after the ruling.Why re-pointing the fixture does not solve it
The guard needs a field name declared on two objects with different verdicts. Measured from
pnpm scan:fields --json, exactly three exist:tax_rate,line_number,description.#1199 proposes removing
line_numberanddescription. So:crm_product.tax_rate)⇒ the live-vs-inert pair class is extinguished, not merely reduced. Re-pointing the fixture at
line_numbertoday means re-pointing it again when #1199 lands. This is why the dev stopped rather than picking one.The options
A — Extend #1198's surface to⚠️ Buys a second re-point when #1199 lands, and does authorship inside another card's guard.
test/field-consumer-scan.test.ts; repair the fixture; land the deletion in one PR. Fastest.B — Re-point to a live-vs-display-only pair. Seven shared names keep that divergence through both cards (⚠️ Changes what the guard asserts, and the
billing_address,description,do_not_call,lead_source,notes,title,website).--sitescontrol — which by its own comment needs "a DECLARED but genuinely inert field" returning exit 0 — has no inert field left to pin and must be re-specified.C — Sequence #1198 behind #1199 so the fixture is re-pointed once, after the whole inert ledger is adjudicated. The dev's recommendation (C then A).
D — ⭐ Retire the live-vs-inert half of the guard, rather than re-point it. ⛔ Neither the dev nor the ruling considered this, and the seat thinks it is the reading that fits the repo's own rule: a guard retires because its subject retired, never because it went quiet. If zero inert fields is the intended steady state — and #1198 + #1199 together say it is — then a guard proving the resolver can distinguish live from inert is guarding a category the repo has decided not to have. Option B keeps the guard alive by quietly redefining what it proves; D says so out loud and deletes it.⚠️ D is only correct if "zero inert fields" is intended rather than incidental. That is the question underneath all four options, and it is the one the seat cannot answer.
What the seat has done meanwhile
crm_product.tax_rate: enforcing it is measurably wrong, so the remaining option is removal — which needs a ruling #1198 →pm:blocked, claim released, tree untouched, branch pushed empty atf0a05613.pm:blockedon this same card. ⛔ It is not independently dispatchable: it removes the two replacement fixtures, so dispatching it first hits this identical wall from the other side.src/translations/**serial fence [Decision]crm_product.tax_rate: enforcing it is measurably wrong, so the remaining option is removal — which needs a ruling #1198 was holding is released; Orphaned locale keys are invisible to every i18n check — the gate only looks surface→translation #1262 and All four locale files translate acrm_caseform sectionsla_overviewthat was deleted with the SLA/Resolution sections — 4objectstack validatewarnings, heading stays in the source locale #1511 are free again.Three scope items in the 2026-08-17 ruling are stale — measured, not inferred
Recorded so the ruling is not re-executed verbatim once this unblocks:
docs/feature-inventory.mdrow — does not exist. Nothing to delete.docs/developers/api_reference.mdfield list — already removed by docs/developers/api_reference.md is the fourth copy of the drifted crm_* roster — 15 names against 18 on disk #1488; the file now says field lists are "deliberately not restated here". ⇒ the docs/README.md still orders maintainers to hand-copy object fields into docs/developers/api_reference.md #1494 fence has nothing to protect on this card.src/data/revenue.seed.ts— seeds no product tax rate. Its onetax_rateis a comment at:223aboutcrm_quote_line_item. ⛔ Must not be touched.products.mdx/.zh-Hans/.zh-Hant.src/objects/quote_line_item.object.ts:60says "crm_product keeps tax_rate there too" — one comment clause that goes false the moment the deletion lands. Fix it in the same PR; it is not worth its own card.