Repository navigation
Give the six declared-but-unnamed fields the consumer they should already have — clear 6 of the 12 field-no-consumers warnings blocking #1581 (epic #1579) #1826
Description
Filed by the epic PM (session_01DuzfS5chho38Yx1jxx9DEj) on the maintainer's instruction, 2026-09-09, verbatim: 「继续处理所有相关任务」. pm:epic reserves it for the epic PM.
#1581 is gated on this. Its acceptance needs errors: 0 and warnings: 0. Errors are 0 since the 17.4.0 migration (#1807 / PR #1814, main 965933b). 13 warnings stand — and until they reach 0, os lint --strict cannot flip, which in turn blocks six family cards (#1582–#1587). This card takes 6 of the 12.
Where the 12 came from
field-no-consumers is a new rule in @objectstack/lint@17.4.0. It fires on a declared field that no metadata names — its own wording: "no view column, form section, page binding, flow node, dataset, widget, formula, validation, hook or action names it."
⭐ The reading that governs all twelve, and it is not the obvious one. Every one of the 12 carries a group, every group is a declared fieldGroup, and each object renders through the synthesized detail/form layout rather than an authored *.page.ts. A synthesized layout names nothing. ⇒ "no consumer" here means "not named in metadata", ⛔ not "unreachable by a user" — all twelve are on screen and editable in the product today.
⛔ That is why none of these is a deletion. The per-field analysis is written up in PR #1814's body ("The 12 field-no-consumers warnings — per-field disposition"); ⛔ do not re-derive it, read it.
The six in this card
| # | field | carriers | the consumer that is missing |
|---|---|---|---|
| 1 | crm_account.logo |
4 (locale labels only) | Uploaded brand image in the branding group beside brand_color. No list column shows it; account_detail.page.ts does not bind it. Candidate: the account detail header. |
| 2 | crm_campaign.description |
11 | Markdown body on the basic group; seven seed campaigns fill it. On no view column, and no campaign detail page exists. |
| 3 | crm_campaign_member.added_date |
57 (2 flow nodes + 4 labels + 51 seed values) | ⭐ The sharpest — and it is in direct tension with the migration that just landed. #1807 requires this stamp to keep being written, and PR #1814 built two elevated sub-flows so it can be. The rule reports nobody reads it — because writers are carriers, not consumers. ⛔ Removing it would contradict a landed card. Candidate: a view column or detail binding on crm_campaign_member — makes the audit stamp legible and settles the rule in one move. |
| 4 | crm_contract.description |
9 | Same shape as #2. crm_contract also declares special_terms, which is consumed — so which of the two markdown bodies a reader is meant to use is a real question, not an accident. Answer it or say you could not. |
| 5 | crm_quote_line_item.line_number |
23 (19 seed rows carry it) | readonly ordinal in the basic group. Nothing orders or displays by it — the line-item related list uses it as neither sort key nor column, which is probably the actual defect. |
| 6 | crm_article_feedback.comment |
4 (locale labels only) | ⭐ Its own description states a consumer that does not exist: "Optional note explaining the verdict — read by the article's author." Nothing surfaces it to the author. ⇒ Either build that surface, or the column collects text nobody reads and the description is a false promise. This one may legitimately come back as "retire the column" — say which, with the reason. |
⛔ How to do it, and how NOT to
- ⛔ Never suppress, whitelist, locally re-severity, or delete a field to shrink the count. That is the gate farm this epic exists to remove, and it would make Turn on
os lint --strictin this repo's verify chain and prove the gate reds (epic #1579, step 2b — the half that needs a release) #1581's flip meaningless. - ⭐ Add the consumer the field should already have — a view column, a detail/form binding, a sort key. The candidates above are the PM's reading of the dev's analysis, ⛔ not a specification: if the obvious consumer is wrong for a field, say so and propose the right one.
⚠️ Where the right consumer is a genuine product decision you cannot settle from the repo, STOP and report it rather than inventing UI to silence a linter. A field left warning with a stated reason is a better outcome than a fabricated surface. ⭐ "I could not settle 2 of 6" is an acceptable result; a guess dressed as a decision is not.⚠️ Every change here is user-visible product surface. Follow this repo's authoring conventions and i18n discipline (a new column or binding needs its locale labels in every shipped locale).
Acceptance
- Measure
pnpm lint --jsonbefore and after on your own base SHA and report both triples. ⭐ The claim "adding a view column clearsfield-no-consumers" is the PM's reading of the rule's wording — ⛔ verify it empirically on the first field you fix and say whether it held. If it does not clear the rule, STOP and report: the remedy is then wrong for all six. pnpm verifygreen end to end.- Every one of the six is either cleared (with the consumer named) or reported standing (with the reason and what decision it needs).
- Changeset: user-visible metadata changes here, so ⛔ not empty frontmatter — write it for the release-notes reader.
⛔ Not in this card
- The five
crm_contact.mailing_*fields — a separate product question (they are import-mapping targets the CSV importer fills; see the companion card). crm_forecast.seed_key— correctly unconsumed by design (hidden: true, seeder-only upsert identity); raised upstream as objectstack#17135.component-props-unknown-key×1 — The Sales Home AI card's paragraph never reaches the screen:descriptionis not a proppage:carddeclares, and #1002's guard pins the copy there #1216's, ⛔ never fixed here.- The
--strictflip itself — Turn onos lint --strictin this repo's verify chain and prove the gate reds (epic #1579, step 2b — the half that needs a release) #1581. - Any governed surface.
Refs: #1579 (epic) · #1581 (the gate this unblocks) · #1807 / PR #1814 (where the 12 were measured and dispositioned) · #1667 (added_date ruled readonly) · objectstack#17135.
Activity
os lint --strictfirst, then retire the local re-implementations by family #1579os-steve commentedon Sep 9, 2026
Claim:session_01DuzfS5chho38Yx1jxx9DEj→ branchclaude/issue-1826-field-consumers-sixDispatched by the epic PM for #1579. The dev inherits this claim — ⛔ do not post a second
Claim:, ⛔ do not touch the assignee field.Base off⚠️
mainat965933bor later.965933bis where PR #1814 (the 17.4.0 migration) landed; anything older still carries the pre-migrationrefreshIntervalcarrier and the old readonly semantics, and the 13 warnings will not measure the same. ⛔ Do not branch fromclaude/issue-1581-lint-strict-flip— it is polluted by #1806's superseded bump commits.Model: Opus, on the maintainer's ruling 「没有 fable 派 opus」.
Deliverable: a draft PR. 「等总监复审」 — ⛔ do not mark ready for review, ⛔ do not enable auto-merge. The PR stops at draft and waits for the director.
Generated by Claude Code
claude commentedon Sep 9, 2026
os-dev-report
{ "issue": 1826, "status": "needs_decision", "branch": "claude/issue-1826-field-consumers-six", "pr": "https://github.com/objectstack-ai/hotcrm/pull/1831", "premise_still_valid": true, "summary": "The card's gating premise HELD and is verified: a metadata binding does clear field-no-consumers. 4 of the 6 fields are cleared with the consumer named; 2 are reported standing because each needs a product decision, which the card named as an acceptable result. pnpm verify is green end to end and the PR is a draft awaiting the director. Base 965933b (= origin/main tip, where PR #1814 landed). One per-field candidate in the card body was falsified: crm_quote_line_item.line_number cannot take a view column or sort key, because the field has NO writer at all — a column would render blank on every line a user creates and carry a number only on the 19 demo seed rows. crm_article_feedback.comment has neither a writer nor a reader: the two feedback actions are one-click header buttons that never ask for a note, so its description ('read by the article's author') promises a surface that does not exist on either side. Nothing was suppressed, whitelisted, re-severitied or deleted. The #1216 component-props-unknown-key warning and the mailing_*/seed_key groups were not touched.", "tests": "pnpm verify GREEN end to end under the container's shared verify lock (os-verify-lock VERDICT command-exit 0, held 161s). pnpm test: 165 test files passed, 3454 passed, 1 skipped, 0 failed — unchanged from base; no test added, per AGENTS.md scope rule 3 (this repo does not grow a gate farm for a platform rule). pnpm lint --json triples, parsed with raw_decode from the first '{': BASE 965933b = errors 0 / warnings 13 / suggestions 12. AFTER-ONE-FIELD probe (crm_campaign.description added to the basic form section) = errors 0 / warnings 12 / suggestions 12, and objects[2].fields.description left the issue list — the empirical check the card gated on, and it held. FINAL = errors 0 / warnings 9 / suggestions 12, passed true; field-no-consumers 12 -> 8, component-props-unknown-key 1 -> 1. pnpm lint:i18n-gate: 0 i18n/missing-* issues — no new locale key was needed (all four fields already carry labels in en/zh-CN/es-ES/ja-JP, every form section reused is an existing _sections key, relatedListColumns adds no translatable surface). pnpm hygiene + hygiene:tokens clean, no ceiling moved (authored total ~137,818 of ~140,000). No ablation applicable: this card adds metadata bindings, it builds no gate.", "mcp_calls": "4 — two targeted search_issues (duplicate check before filing) and two issue creates. CHANNEL SWITCH DECLARED: the REST /search/issues endpoint is blocked in this container ('sessions are bound to their configured repositories'), including for the control probe, so the duplicate check fell back to MCP search_issues as the rules prescribe. All reads (issue body, comments, PR #1814 body, PR read-back) went over repo-scoped REST at 0 MCP cost; the PR create and this report comment went over repo-scoped REST too.", "open_questions": [ { "question": "crm_quote_line_item.line_number — the field has no writer in the app at all. readonly:true, no hook, no flow node, no action writes it; quote_generation creates the quote and no line items, so quote lines are only ever created by hand; and since objectql 17.4.0 a static readonly field is stripped from a non-system caller's INSERT too, so a rep cannot seed it either. Only the 19 seed rows carry values (test/seed-consistency.test.ts pins those 1..n per parent — a fixture pin, not a runtime writer). What should happen to the ordinal?", "options": [ "A — Give it a writer: a per-parent ordinal assigner on insert, in the shape src/objects/_line-item-price-fill.ts already establishes for this pair of objects. Because the field is readonly the writer must be system-context, which puts it under AGENTS.md house rule 9 (elevate as little as possible) — the same question #1807 settled for crm_campaign_member.added_date. Cost: a new hook plus the elevation review; then a related-list column becomes truthful and clears the warning.", "B — Retire the ordinal and its 23 carrier sites (4 locale labels + 19 seed values), and drop the key from billing-handoff's LINE_ITEM_FIELDS and the three billing-handoff doc pages. Cost: the billing payload loses a line ordinal it never really carried.", "C — Add the view column anyway. REJECTED here: it renders blank on every line a user creates and shows a number only on demo data, which is the fabricated surface the card ranks below an honest standing warning." ], "recommendation": "A, because the twin object makes it load-bearing rather than cosmetic: crm_opportunity_line_item.line_number has the identical gap AND billing-handoff.flow.ts ships it in the billing payload on every won deal, so the app currently hands the billing system a field that is null on every non-seed row. Filed as #1828 with that measurement. Whichever answer is chosen should cover both line-item objects, since they deliberately mirror each other." }, { "question": "crm_article_feedback.comment — the column has neither a writer nor a reader. The only creators of feedback rows are mark_article_helpful / mark_article_not_helpful, one-click header buttons whose bodies write crm_knowledge_article, verdict and owner_id and nothing else, so a reader is never asked for a note; and its own description promises 'Optional note explaining the verdict — read by the article's author' while no surface routes an author to it. Build the promise, or retire it?", "options": [ "A — Build what the description promises: a capture step plus an author-facing surface. Note the capture cannot be a type:'script' action — those bodies collect no input — so the one-click thumb would have to become a screen flow, and the author surface needs somewhere on the knowledge article to read the notes.", "B — Retire the column and its 4 carrier sites, and delete the false promise in the description with it." ], "recommendation": "No recommendation — this is a genuine product decision and I decline to dress a guess as one, per the card. The trade-off to weigh: this object exists to make helpful_count / not_helpful_count trustworthy (its docblock is explicit that a two-valued verdict was chosen deliberately, and that 'a vocabulary nothing reads would recreate the very defect it is fixing'). Interposing a note prompt between the reader and the thumb suppresses votes, which is a direct cost to the counters the object was built for. If the article author is not actually asking for prose, B is the honest answer and matches the object's own stated design philosophy. Not retired here, per the card's explicit instruction." } ], "out_of_scope_findings": [ "filed as #1828: crm_opportunity_line_item.line_number has no writer either, yet billing-handoff.flow.ts ships it in LINE_ITEM_FIELDS — the billing payload carries a field that is null on every line item created in the product, non-null only on demo seed rows, and content/docs/revenue/billing-handoff.mdx documents it with a worked example value.", "filed as #1829: content/docs/marketing/campaign-members.mdx and its two Chinese siblings still document First Opened / First Clicked fields and Opened / Clicked / Bounced statuses that #597 removed from crm_campaign_member — and then derive Open rate and Click rate from those phantom statuses. #961 corrected a different paragraph on the same page without reaching these.", "not filed, noted only: src/views/account.view.ts's file docblock lists 'gallery — branded account cards with brand_color highlights', which described the cards before this PR gave them a logo cover; corrected in place as part of this change, not a separate card." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 8, 2026
Ruling: batch hotcrm-R74 item 1 · letter A · maintainer 「同意」 2026-10-08T03:06Z
repo:hotcrmseat,session_012zh91QzFgePbkmuHnugLN3. The maintainer answered 「同意」 in this session's chat to the batch presented there with the recommendations 1A 2A 3A 4B. Item 1 is this card's last open question:crm_article_feedback.comment.The decision as presented: keep the field and close this card (A). The fallback was B, retire the field; C was to build a guided capture step after the vote.
The premise was refreshed before presenting. The 2026-09-09 report said the field had "neither a writer nor a reader". On
origin/mainc967803that no longer holds:commentin the article's feedback panel throughrelatedListColumns: ['verdict', 'owner_id', 'comment']. So the field's description, "read by the article's author", is now true.allowEditoncrm_article_feedback. The two vote buttons still write the verdict only, and the one-click vote is kept on purpose.Disposition of all six fields, so the card closes complete:
crm_quote_line_item.line_numbergot its writer undercrm_opportunity_line_item.line_numberhas no writer, yet the billing hand-off payload ships it on every won deal #1828.crm_article_feedback.commentstands as it is, under this ruling.Release: the epic-PM claim
5603053957(sessionsession_01DuzfS5chho38Yx1jxx9DEj, assigneeos-steve) is released with this close. Source: the maintainer's 「同意」 above, in this session's chat; epic #1579 is dissolved by item 4 of the same batch. Destination: closedcompleted;pm:epicand the assignee are removed in the same act.Generated by Claude Code