Ruled: 5933225770 · letter A · 2026-10-01T14:11Z
Blocked-by: objectstack-ai/objectui#11383
Produced by pnpm scan:fields, the object-aware ledger added in the PR for #1193. Filed as one card because a ledger is the unit that gets adjudicated in one sitting — #1182 handled ten rows that way — not as 14 separate issues.
crm_product.tax_rate, the fifteenth row, has its own card: #1198.
The ledger, on 62560210
inert here means the scan found no site that reads the field and no site that draws it — only carriers (locale bundles, seed values, import columns, prose). "masked" means the name is also declared on another object, so the token grep that produced #1182's row set read it as consumed and it could never have reached that card.
| Field |
Masked by |
What the scan found |
crm_campaign.description |
12 other objects |
4 locale rows only. Not on the campaign form — product, task, event, account, lead, case, opportunity all put description on a form; campaign does not. |
crm_contract.description |
12 other objects |
same shape |
crm_quote_line_item.description |
12 other objects |
same shape. Note billing-handoff.flow.ts sends a line description in its payload, but from crm_opportunity_line_item — a different object, which is the point. |
crm_quote_line_item.line_number |
crm_opportunity_line_item |
the opportunity twin IS read (billing hand-off payload); the quote one is not |
crm_quote_line_item.subtotal |
crm_quote |
a formula field. quote_total_rollup deliberately recomputes from raw quantity/unit_price/discount rather than reading it, and crm_quote_line_item has no view file — highlightFields is crm_product, quantity, unit_price, total_price. So this formula is evaluated on every read and displayed nowhere. |
crm_account.logo |
— |
image field, on no form and no view |
crm_article_feedback.comment |
— |
highlightFields is crm_knowledge_article, verdict, owner_id; the comment a reader types is stored and never shown |
crm_campaign_member.added_date |
— |
written by campaign-enrollment.flow (two nodes stamp {NOW()}) and seeded, read by nothing and drawn nowhere — the is_taxable shape exactly |
crm_contact.mailing_street |
— |
declared, plus an import-mapping column |
crm_contact.mailing_city |
— |
same |
crm_contact.mailing_state |
— |
same |
crm_contact.mailing_postal_code |
— |
same |
crm_contact.mailing_country |
— |
same |
crm_forecast.seed_key |
— |
the seed externalId, by deliberate design (#613) — listed for completeness, not as a defect |
Two clusters worth a verdict rather than a row-by-row pass
The five crm_contact.mailing_* fields are one decision, and the interesting one. They are on no form and in no view, yet assets/import-templates/contacts.csv and contact_import.mapping ask a customer to fill five columns whose values then become invisible in the product. That is worse than an inert field: it is inert and it asks the customer for work. Either the contact form grows a Mailing Address section or the five fields and their import columns go together. Note crm_account models its address as a single Field.address (billing_address), which renders as a unit — the same shape would collapse five contact fields into one.
The three description fields are one decision. Nine objects put description on a form; three declare it and never show it. Either those three forms gain the field or the three declarations go.
Not a gate, on purpose
pnpm scan:fields is a report, not part of pnpm verify — the argument is in the script header and in the PR for #1193. Declaring a field one PR before its consumer is ordinary, and the 2026-08-17 ruling asked for a verdict per field rather than a blanket rule. What is guarded is the scan's ability to see: test/field-consumer-scan.test.ts fails if the resolver stops distinguishing two objects that share a field name.
Reproduce: pnpm scan:fields, or pnpm scan:fields --sites crm_contact.mailing_city for one row's evidence.
Filed unassigned; no work started.
Ruled: 5933225770 · letter A · 2026-10-01T14:11Z
Blocked-by: objectstack-ai/objectui#11383
Produced by
pnpm scan:fields, the object-aware ledger added in the PR for #1193. Filed as one card because a ledger is the unit that gets adjudicated in one sitting — #1182 handled ten rows that way — not as 14 separate issues.crm_product.tax_rate, the fifteenth row, has its own card: #1198.The ledger, on
62560210inert here means the scan found no site that reads the field and no site that draws it — only carriers (locale bundles, seed values, import columns, prose). "masked" means the name is also declared on another object, so the token grep that produced #1182's row set read it as consumed and it could never have reached that card.
crm_campaign.descriptionproduct,task,event,account,lead,case,opportunityall putdescriptionon a form; campaign does not.crm_contract.descriptioncrm_quote_line_item.descriptionbilling-handoff.flow.tssends a linedescriptionin its payload, but fromcrm_opportunity_line_item— a different object, which is the point.crm_quote_line_item.line_numbercrm_opportunity_line_itemcrm_quote_line_item.subtotalcrm_quotequote_total_rollupdeliberately recomputes from rawquantity/unit_price/discountrather than reading it, andcrm_quote_line_itemhas no view file —highlightFieldsiscrm_product, quantity, unit_price, total_price. So this formula is evaluated on every read and displayed nowhere.crm_account.logocrm_article_feedback.commenthighlightFieldsiscrm_knowledge_article, verdict, owner_id; the comment a reader types is stored and never showncrm_campaign_member.added_datecampaign-enrollment.flow(two nodes stamp{NOW()}) and seeded, read by nothing and drawn nowhere — theis_taxableshape exactlycrm_contact.mailing_streetcrm_contact.mailing_citycrm_contact.mailing_statecrm_contact.mailing_postal_codecrm_contact.mailing_countrycrm_forecast.seed_keyexternalId, by deliberate design (#613) — listed for completeness, not as a defectTwo clusters worth a verdict rather than a row-by-row pass
The five
crm_contact.mailing_*fields are one decision, and the interesting one. They are on no form and in no view, yetassets/import-templates/contacts.csvandcontact_import.mappingask a customer to fill five columns whose values then become invisible in the product. That is worse than an inert field: it is inert and it asks the customer for work. Either the contact form grows a Mailing Address section or the five fields and their import columns go together. Notecrm_accountmodels its address as a singleField.address(billing_address), which renders as a unit — the same shape would collapse five contact fields into one.The three
descriptionfields are one decision. Nine objects putdescriptionon a form; three declare it and never show it. Either those three forms gain the field or the three declarations go.Not a gate, on purpose
pnpm scan:fieldsis a report, not part ofpnpm verify— the argument is in the script header and in the PR for #1193. Declaring a field one PR before its consumer is ordinary, and the 2026-08-17 ruling asked for a verdict per field rather than a blanket rule. What is guarded is the scan's ability to see:test/field-consumer-scan.test.tsfails if the resolver stops distinguishing two objects that share a field name.Reproduce:
pnpm scan:fields, orpnpm scan:fields --sites crm_contact.mailing_cityfor one row's evidence.Filed unassigned; no work started.