Skip to content

[Decision] #1198 and #1199 together empty the inert-field ledger to zero — so #1193's live-vs-inert guard loses its subject, not just its fixture #1543

Description

@os-sales

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.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    metadataDeclarative metadata — schema, security posture, UI surfaces

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions