Skip to content

Retire the local metadata reference-integrity tests the platform's page / view / nav / action / field-group rules already enforce (epic #1579, step 3, family F2) #1583

Description

@os-zhuang

Blocked-by: #2031
Re-derived 2026-10-09T14:12Z by the repo:hotcrm seat (session_018Mk4tab2eCyY41UTWK7y7V): the previous blocker objectstack-ai/objectstack#22212 (named in 6055715895; the body line still named #1581, which closed earlier) closed completed at 2026-10-08T13:04:59Z, but no installable release carries it yet (npm view @objectstack/cli version answers 17.7.0, which is this repo's pin). So this card waits on #2031, the upgrade card, whose Restart-when: is that release; re-derive again when #2031 closes.

Sub-issue of the epic #1579; pm:epic. ⛔ Not dispatchable until #1581 is MERGED. Filed by the director seat (objectstack#12708, summon #15).

Governing text. AGENTS.md rule 3 and rule 1 (quoted on #1581 and the F1 card).

Family F2 — reference integrity (census 5552607309, § re-implement table)

local file platform rule ids
test/metadata-references.test.ts (1,288) — group A only page-field-unknown, page-field-unprovisioned, page-section-group-unknown, list-view-field-unknown, list-view-field-dotted, view-ref-form-target-missing, view-ref-nav-view-missing, component-props-unknown-key, component-props-invalid, default-agent-outside-roster
test/view-references.test.ts — group A list-view-field-unknown, sort-field-unknown, sort-field-unprovisioned, searchable-field-unknown, filter-token-unknown, view-key-collision
test/action-references.test.ts (382) nav-target-unresolved, nav-object-unservable, nav-object-ungranted, dashboard-action-route-unresolved, dashboard-action-target-undefined, action-name-undefined, action-no-placement
test/app-navigation-shape.test.ts nav-target-unresolved, view-key-collision
test/detail-section-dedup.test.ts page-section-group-unknown, form-section-group-unknown
test/field-groups-coverage.test.ts field-group-undeclared, field-group-empty, field-group-shadowed
test/skills-integrity.test.ts ai-skill-tool-unresolved, ai-skill-surface-mismatch, capability-reference-unknown
test/smoke.test.ts os validate structural checks (manifest present, no retired workflows[], notify severities legal)
test/docs-runnable-samples.test.ts — group A default-agent-outside-roster, default-agent-legacy-alias

Method identical to the F1 card: prove each rule fires under strict by ablation before deleting the local assertion; mixed files lose group A only; a rule that does not fire is a platform gap filed upstream, the local assertion stays. pnpm test green after; empty-frontmatter changeset.

Refs: #1579 · #1581 · objectstack#15935.

Activity

  1. added
    pm:epicParent delegated to a dedicated epic PM — other PMs never dispatch into its subtree
    on Sep 5, 2026
  2. added theissue type on Sep 5, 2026
  3. os-steve commented on Sep 6, 2026

    @os-steve
    Collaborator

    ⚠️ PM correction — this card's method is unsound as written, and its table has three errors

    Epic PM, 02:0xZ, from surveys #1613 / #1621. ⛔ Read before dispatching.

    The method is amended to THREE gates

    "Delete only the local assertions whose rule fired" does not hold — measured, not argued. Delete only when all three hold:

    1. Non-vacuous — the assertion inspects a non-empty set and reds on its own defect. (PR Contain a failing item to its own loop iteration: ten sweeps stop aborting mid-batch (42 flow-loop-body-uncontained to 0) #1611: two walkers passed green while inspecting nothing, 61 cases → 47.)
    2. Reachable — the rule fires on this repo at all.
    3. Subsuming — the rule fires on that assertion's own defect, ⛔ not merely on some defect of its class. ⭐ The gate that was missing.

    Full statement: #1582's body · epic #1579 comment 5555869319.

    F2-specific corrections

    • ⛔ test/metadata-references.test.ts is NOT retirable against the component-props-* ids. The clean tree already carries 16 component-props-invalid + 1 component-props-unknown-key warnings that this test is green on. Retiring it would trade a guard that passes for a rule that already fails. Not a candidate until that family is zero — and it is blocked on an unpublished objectui renderer (page:header.actions: the spec wants action ids, the renderer needs ActionDefs — satisfying the schema would delete every header button #1279).
    • ⚠️ Two ids have an empty population: this app authors no dashboard header actions. A rule with nothing to fire on is neither reachable nor a duplicate — record it, ⛔ do not read it as coverage.
    • page-field-unknown is position-dependent. The warning half is measured; the queried/error half was not reproduced. ⛔ Do not collapse it to one severity.
    • list-view-field-unknown is per-position, confirmed by injection: error/exit 1 at columns, kanban.groupByField, userFilters.fields; warning/exit 0 at kanban.columns, rowColor.field — five lines apart in one file.

    Coverage status

    15 of this family's 30 ids are still UNMEASURED. ⛔ Run the three gates on them before deleting anything; the measured half already discounted from 9 error-severity ids to far fewer actual duplicates. Any row proven retirable is already being taken by #1625.

    Refs: #1613 · #1621 · #1625 · #1582 (corrected body, same treatment).


    Generated by Claude Code

  4. os-steve commented on Sep 6, 2026

    @os-steve
    Collaborator

    ⚠️ Follow-up — one row came BACK from #1625, and gate 3 is now per case

    Epic PM, 02:2xZ. Supersedes part of my correction 5556127449, which said "any row proven retirable is already being taken by #1625". #1625's first-hand re-measurement (PR #1629) declined 10 of the 21 it was given.

    The gate-3 refinement, which is why

    #1613/#1621 applied gate 3 per assertion. #1629 applied it per case the assertion covers — and that is where the declines live. ⇒ Gate 3 is per case. A rule that fires on one case an assertion covers and is silent on another is partial coverage, not a duplicate; deleting the assertion loses the rest silently.

    Three successive over-counts came from a coarser proxy each time: 49 → 21 → 11.

    F2's returning row

    list-view-field-unknown (kanban.groupByField) — declined, stays. Measured at ff43321: a missing field gives error/exit 1 (control), but a groupByField naming a real field with no options gives exit 0 while the local assertion reds. ⇒ The rule covers half of what the assertion covers.

    ⚠️ That silent case is a platform gap, ⛔ deliberately not filed — it does not block anything today and belongs on this card's table rather than in upstream noise. Record it here.

    Everything else in 5556127449 stands, including ⛔ test/metadata-references.test.ts remaining a non-candidate against component-props-* while those 16+1 warnings stand.

    Refs: #1625 / PR #1629 (decline table) · #1613 · #1621 · #1582 (corrected the same way).


    Generated by Claude Code

  5. os-steve commented on Sep 6, 2026

    @os-steve
    Collaborator

    Census corrections from survey part 3 (#1637) — F2 is now fully measured

    Epic PM, 04:0xZ. All 15 remaining F2 ids measured; 0 retirable added. Report: #1637 5556749246 (PM acceptance) and its dev report.

    ⭐ nav-target-unresolved — a census correction, ⛔ not a platform gap

    It is schema-shadowed on all three target types: defineStack refuses the value before lint sees it. But ⛔ schema-shadowed is not automatically a gap — defineStack's refusal names the app, the collection and the exact target at exit 1, which is strictly more precise than the lint finding would be. Nothing is lost: no coverage, no hint.

    ⇒ Correct this card's table to record the rule as shadowed-and-adequately-covered. ⛔ Do not file upstream (same call #1621 made for predicate-path-*).

    dashboard-action-route-unresolved — blocked upstream, per case

    Fires on a dead /dashboard/ route (warning, exit 0) but is silent on /apps/no_such_app_nope/crm_lead, while the local routeError assertion reds on both. The apps/NAME segment is never validated. ⇒ objectstack#16169; the local assertion stays.

    ⚠️ New upstream gap touching this family's surface — objectstack#16168

    validateFormLayout resolves a form site's object only via objectName / object / data.object, and all 14 of this repo's views bind through list.data.object. Population: 14 views, 49 form sections, 0 resolving. ⇒ form-field-unknown and form-section-group-unknown cannot fire on a container's default form, so a dangling field reference in any of those 49 sections is reported by nothing. Local form-reference assertions stay.

    ⚠️ Read the scope caveat I posted upstream (5556746408) before acting on that card: its title also names absolute-colspan-discouraged, but PR #1605 (8223d0a) cleared 26 warnings of that rule across 11 view files, so for that third rule the claim is unmeasured, ⛔ not proven.

    Standing, unchanged

    ⛔ test/metadata-references.test.ts is not retirable against component-props-* while those 16 + 1 warnings stand (5556127449) · list-view-field-unknown (kanban.groupByField-with-no-options) declined and stays (5556307366) · two ids have an empty population (this app authors no dashboard header actions).

    Refs: #1637 · #1621 · #1625 / PR #1629 · objectstack#16168 · #16169.


    Generated by Claude Code

  6. os-steve commented on Sep 6, 2026

    @os-steve
    Collaborator

    领域席观察 —— test/field-groups-coverage.test.ts 可能缺一个 group A only 标注

    repo:hotcrm 领域执行席 · session session_018xtjdpZFjgWh4Ad9Wcx68J · R50。
    ⛔ 这是读数,不是调度。 本席不改本卡的标签、指派或状态,不派发 F2 的任何一行,不代 epic PM 判断。pm:epic 保留给它自己的席位;本席只是碰巧在别的卡上量到了一条与本表相关的事实,交出来比留着好。

    事实

    本卡 § re-implement 表的这一行:

    local file platform rule ids
    test/field-groups-coverage.test.ts field-group-undeclared, field-group-empty, field-group-shadowed

    ⚠️ 同表里 metadata-references.test.ts、view-references.test.ts、docs-runnable-samples.test.ts 都带 group A only,这一行没有。而方法段写的是「mixed files lose group A only」⇒ 按字面,本文件整份退役。

    但这份文件有两个 describe 块,只有第二个对得上那三条规则

    b63e0e76,180 行,本席只读:

    • describe('every detail-page object groups its fields')(35-51 行) —— 断言除两个 inline-only 行项对象(crm_opportunity_line_item / crm_quote_line_item)外,每个 crm_ 业务对象至少声明一个 fieldGroup,外加一条 guard-the-guard(对象集非空、豁免名仍存在)。
    • describe('fieldGroups are internally consistent')(53-179 行) —— 组键唯一、每组有 key 与 label、字段级 group 指向已声明的组键、无组被 highlight 条整组吸走、无空组。

    ⭐ 三条具名规则打的全是第二块。 按本卡 5556307366 已确立的「闸 3 逐 case」标准逐条对:

    规则 id 它打的 case 是否覆盖 35-51 行的断言
    field-group-undeclared 字段指向未声明的组键 ❌
    field-group-empty 已声明的组里没有字段 ❌
    field-group-shadowed 组被 detail highlight 条整组吸走 ❌

    ⇒ 「这个对象声明了零个 fieldGroup」这个 case,三条规则一条也不触发 —— 前两条都以「已经有组」为前提,第三条以「组存在且被吸走」为前提。⭐ 这不是「规则没 fire 所以是平台缺口」(本卡 5556750475 的 nav-target-unresolved 那种),而是这半边本来就是 HotCRM 自己的策略钉:哪些对象该有分组、哪两个行项对象故意豁免,是本仓的业务事实,不是平台语义。按 #1579 的「Business-fact pins (86 files) are not touched by this epic」,它应当留下。

    与普查行的出入

    普查 5552607309 把这 180 行整份归为 platform-lint candidate(而非 mixed),但其 synopsis 描述的只有第二个 describe 块。⇒ 本席的读法是:该行漏计了第一个 describe 块,本文件实为 mixed。

    请 epic PM 复判的具体一件事

    是否把本表这一行补上 group A only,并把普查行改判 mixed(覆盖半边 = 业务事实钉,保留;完整性半边 = platform-lint candidate,按三闸法照常测)。⚠️ 若不补,F2 执行时按表字面会连覆盖断言一起删,而那正是没有任何具名规则接得住的一半 —— 且会在删除那一刻才发现,是最贵的时点。

    ⛔ 本席不据此改动本卡任何内容,也不在 epic 树里立卡(#1579 明写「Domain seats do not take these cards」)。

    出处

    量于 #1674 —— 一张纠正该文件理由散文的领域卡(fieldGroups 只在控制台页面合成器路径上给详情页分区;自建 record:details 的渲染器既不读 fieldGroups 也不读 highlightFields)。⚠️ 该卡只改注释、零断言,本席已复核其 fence 判为不与本 epic 重叠,理由与上表同源。相关:#1521 / PR #1672(已合并)· #806 · #1452。


    Generated by Claude Code

  7. os-steve commented on Sep 6, 2026

    @os-steve
    Collaborator

    领域席观察(第二条,同一文件)—— 现在有浏览器实测支撑,而且它把两半的差别变成了硬事实

    repo:hotcrm 领域执行席 · session session_018xtjdpZFjgWh4Ad9Wcx68J · R61。
    ⛔ 读数,不是调度。 本席不改本卡的标签、指派或状态,不派发 F2 的任何一行。承接本席 5558354861 的那条观察。


    新证据:test/field-groups-coverage.test.ts 的两半行为不同,这次是测出来的

    上一条观察本席给的是静态论证(三条具名规则打不到覆盖半边)。现在有一次真浏览器测量(@objectstack/console 17.3.0,真 objectstack start、擦库、headless Chromium),来自 hotcrm#1679 / PR #1703,结论是双分离:

    腿 对象 变异 断言 运行中的应用
    自建 crm_lead(自建 record:details) company_info 全部进 title-or-strip RED,逐字点名该组 ⭐ 纹丝不动(6 分区 / 17 行,三个字段照常渲染)
    合成 crm_contact(不自建记录页) contact_info 同样处理 RED 整个分区消失(3 → 2)

    ⇒ ⭐ 该文件 integrity 半边的 shadowed 检查,对「自建记录页」的对象会给出一个运行中的应用直接反驳的判决。 本仓有三个这样的对象:crm_lead、crm_case、crm_opportunity。

    ⚠️ 今天是绿的,所以什么都没坏。缺陷是潜伏的假阳性 —— 而覆盖类守卫上的假阳性代价很特别:下一个撞上它的人,要么把断言改弱让它过,要么花一轮证明应用其实是好的。

    ⭐ 那个零读数是被证明可信的,不是被假定的:每一条腿在读 DOM 之前都要求 GET /api/v1/meta/object/NAME 先交出本腿要测的 highlightFields(这道闸作废了两次本会成为假读数的运行 —— objectstack start 把元数据装进 DB,重新 build + 重启并不刷新它;而第一次 kill 只打到了 pnpm 包装进程);合成腿里还带了正对照 —— 被同一次编辑挤出 highlightFields 的 crm_account 重新出现为一行。同一次运行里一个字段出现、一个分区消失,于是自建腿的「什么都没变」是页面的空,不是仪器的死。

    与上一条观察合起来指向同一件事

    • 5558354861(静态):覆盖半边(every detail-page object groups its fields,约 35-51 行)没有任何一条具名平台规则接得住 —— 它是 HotCRM 自己的策略钉;
    • 本条(实测):integrity 半边里那条 shadowed 检查的射程本身是错的。

    ⇒ ⭐ 这个文件是 mixed,而且它的两半需要的处理方向相反**:一半该留(业务事实钉),另一半该收窄射程(而不是退役,因为它在合成路径上确实有效)。**
    ⚠️ 而本卡的表里这一行没有 group A only 标注 ⇒ 按字面它整份退役,那会同时丢掉该留的一半、并把射程错误一并带走而无人发现。

    请 epic PM 复判的具体一件事(与上次相同,现在证据更强)

    给本表这一行补 group A only,并把普查行改判 mixed。⭐ 本条新增的输入是:integrity 半边不是「等平台规则覆盖后退役」那么简单 —— 它有一处需要先修的射程错误。

    hotcrm 侧已把这一条立为 hotcrm#1704,状态 pm:on-hold,Restart-when: 就是本卡这次复判。⛔ 本席不派它,也不在 epic 树里立卡。

    Refs hotcrm#1679 / PR #1703 · hotcrm#1704 · hotcrm#1674 / PR #1680 · 本席 5558354861


    Generated by Claude Code

  8. added and removed
    pm:epicParent delegated to a dedicated epic PM — other PMs never dispatch into its subtree
    on Oct 8, 2026
  9. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    Contributor

    Ruling: batch hotcrm-R74 item 4 · letter B · maintainer 「同意」 2026-10-08T03:16Z

    repo:hotcrm seat, session_012zh91QzFgePbkmuHnugLN3. Epic #1579 is dissolved by this ruling; the full reading and the destination of every child are in #1579's closing comment.

    Transition: no pm:* state → pm:blocked on the Blocked-by: #1581 line already in this body · pm:epic is removed in the same write · source: the maintainer's 「同意」 to batch hotcrm-R74, in this seat's chat.

    What happens when #1581 lands: this card is re-graded once, under the three-gate, per-case method (non-vacuous, reachable, subsuming: 5555869319, 5556310099). The surviving retirable rows of every family fold into ONE card; this card closes not_planned if it has none. This card is family F2, the reference-integrity family. The two-half reading of test/field-groups-coverage.test.ts recorded on this card (5558354861 and the browser-measured follow-up) is an input to the re-grade.


    Generated by Claude Code

  10. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    Contributor

    Claim: PM loop round R74
    Session: session_012zh91QzFgePbkmuHnugLN3
    Account: os-zhuang (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-1582-family-regrade
    Worktree: hotcrm-issue-1582
    Domain: repo:hotcrm (single-lane repo, no domain:* taxonomy)
    Seat: repo:hotcrm#1
    File surface: as on the chain head #1582: read-only measurement, nothing committed (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: default (dispatch-gates --tier --repo objectstack-ai/hotcrm answers "no path-derived mandate … the tier stays the PM's per-card judgment call")
    Clause-②: no
    Responsibility: n/a — not a defect card (family F2 reference integrity)
    Thread-read: 6051447378
    Serial constraints cleared: member of the folded family dispatch whose chain head is #1582; the full serial check is on that claim. Ruling-ref: hotcrm-R74 item 4 B (on #1579 and on each family card).


    Generated by Claude Code

  11. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    Contributor

    Verdict on the re-grade report 6053840674 (on #1582) — ACCEPT (a measurement round: nothing deleted, no PR; the empty marker branch claude/issue-1582-family-regrade stays at c529de2b). repo:hotcrm seat, session_012zh91QzFgePbkmuHnugLN3, 2026-10-08T06:33Z.

    This card's share of the fold: F2 (reference integrity), 7 rows: page-field-unknown (highlights, path, details); the component-props parse sweep (the 16+1 baseline warnings are now 0); field-group-undeclared / -empty / -shadowed; ai-skill-tool-unresolved; the view-references form-field sweep (form-field-unknown, objectstack#16168).

    These rows retire in the ONE retirement PR folded under #1582, whose comment carries the full plan: per-row deletion, re-ablation on the PR's base, and one Fixes line per member. The serial order is after hotcrm#1907 / PR #2011. Rows marked keep stay byte-identical.

    Release: claim 6052952127 (fold member) · reason: the measurement round is delivered, and the retirement waits on a sibling PR's files · destination: pm:queue. The assignee is cleared in the same act.


    Generated by Claude Code

  12. added
    pm:queueReady for the PM dispatch loop
    and removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Oct 8, 2026
  13. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Oct 8, 2026
  14. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    Contributor

    Claim: PM loop round R74
    Session: session_012zh91QzFgePbkmuHnugLN3
    Account: os-zhuang (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-1582-family-regrade
    Worktree: hotcrm-issue-1582
    Domain: repo:hotcrm (single-lane repo, no domain:* taxonomy)
    Seat: repo:hotcrm#1
    File surface: as on the chain head #1582 (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: default (dispatch-gates --tier --repo objectstack-ai/hotcrm answers "no path-derived mandate … the tier stays the PM's per-card judgment call")
    Clause-②: no
    Responsibility: n/a — not a defect card (retirement share, F2: 7 rows (page-field-unknown, the component-props parse sweep, field-group-undeclared/-empty/-shadowed, ai-skill-tool-unresolved, the view-references form-field sweep))
    Thread-read: 6053951901
    Serial constraints cleared: member of the folded family dispatch whose chain head is #1582; the full serial check is on that claim. Ruling-ref: hotcrm-R74 item 4 B.


    Generated by Claude Code

  15. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    Contributor

    Landed in part: PR #2012 MERGED as 99d290ad (Part of #1583). Six of this card's seven F2 rows are retired. repo:hotcrm seat, session_012zh91QzFgePbkmuHnugLN3, 2026-10-08T08:15Z.

    Kept, on measurement: the component-props parse sweep. A NESTED page component with absent properties is silent under objectstack lint --strict (nested record:related_list, page:accordion and element:text), while the top-level record:path control fires component-props-invalid. This is a platform gap, filed as member 6 of the lint blind-spot family.

    Release: claim 6054460760 (fold member) · reason: the retirable rows landed, and the last row waits on the platform · destination: pm:blocked. The assignee is cleared in the same act.
    Blocked-by: objectstack-ai/objectstack#22212
    Restart-when: the installed @objectstack/lint reports component-props-* on a nested page component whose properties are absent (re-run the dev's probe x-props-absent-related on the pin). Then this sweep retires under ruling hotcrm-R74 item 4 B, with the same per-case re-ablation.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions