Skip to content

Epic: bring this repo's test farm back under the 2026-08-31 ruling — platform-first, os lint --strict first, then retire the local re-implementations by family #1579

Description

@os-zhuang

Epic parent — reserved by pm:epic; the sub-issue tree is the queue for a dedicated epic PM session (/pm-dispatch epic:#1579), summoned by the maintainer. Domain seats do not take these cards. Director seat (objectstack#12708, summon #15) filed the structure on the maintainer's ruling; ⛔ the director seat does not dispatch.

Ruling

Maintainer, 2026-09-05, live director chat, verbatim: 「hotcrm#1579 是不是应该开一个专题卡处理啊」 → 「同意你的建议,你把卡片写好」, accepting the director seat's plan A′ as presented (comment 5552648775 carries the option analysis; the refined plan is below). Standing basis: AGENTS.md § "Scope — a pure metadata application (2026-08-31 ruling)", rule 3 — "Lint, validation, gates and diagnostics belong to the platform, uniformly … Tests in this repo pin this repo's own business facts and nothing else … ⛔ Do not grow a gate farm." Protocol baseline (maintainer 2026-09-05): 「本项目以协议为基准。所以开发应该对其协议,协议有问题应该立卡修改协议」.

Step 5 ruling (2026-09-05, later the same session, verbatim, in order): 「新增平台公开面」 → 「我认为平台的能力应该放在平台,但是平台该怎么设计提供这个能力,你需要完整的重新考虑」 → 「是不是 把执行能力并进 @objectstack/verify / os test 更合理?」 → design B′ (below) → 「同意」 → 「应该还是刚才 hotcrm 专题卡的子卡片吧」. Design B′: the execution capability goes into the already-published @objectstack/verify (ADR-0054 lineage) as an in-process handle on the stack bootStack already boots — real engine, zero re-implemented semantics; no new package; os test (HTTP JSON suites) untouched. Full reasoning: the ruling comment on this card.

Census (comment 5552607309, origin/main b1599507, read-only)

bucket files lines
pure business-fact pin (stay) 86 33,166
pure platform-lint candidate 62 19,614
mixed (one row per class) 35 20,422
delete 1 94
total (test/ 169 + scripts/ 15) 184 73,296

Key finding: ~30 files re-implement rules @objectstack/lint@17.3.0 already ships (≈250 rules: 161 error / 119 warning / 11 advisory) because os lint exits 1 on error only — this repo reads 90 warnings, exit 0. The remedy is relocation to the platform, not deletion; exactly one file is deletable outright. Second finding (step 5 surveys, 2026-09-05T15:4xZ): the shared harnesses are half real (real AutomationEngine, real QuickJS runner, real wrapDeclarativeHook) and half fake (a ctx.api over arrays, hand-sorted hook dispatch, no permission check); the platform's @objectstack/verify already boots the real stack in memory but exposes no in-process way to invoke a hook / flow / action.

Program (sequenced; each step is a sub-issue, Blocked-by: lines are on the cards)

step card repo gate
1 objectstack#15935 — os lint --strict (warnings fail; default unchanged) objectstack platform first
2 #1581 — enable strict in verify, zero the 90 warnings, delete scripts/wow1-live-schema.sh hotcrm Blocked-by step 1 published and pinned (install-surface probe on the card)
3 #1582 F1 flow/predicate/readonly · #1583 F2 reference integrity · #1584 F3 analytics · #1585 F4 i18n + the severity gate · #1586 F5 security/sharing · #1587 F6 write shape hotcrm each Blocked-by #1581; delete only after an ablation proves the platform rule fires under strict; mixed files lose group A only
4 objectstack#15936 — triage decides the live card for the null-guard / totality lint (#4763 closed vs #7219 open) objectstack decides whether the five totality files in F1 are "wait" or "delete"
5a objectstack#15951 — @objectstack/verify in-process handle (hooks.run, flows.run/resume, actions.run, validate, seed/rows, metadata, tenancy option, shared boot) objectstack platform first; Clause-②: yes
5b objectstack#15952 — plugin-spec.mdx stops promising @objectstack/testing; create-objectstack blank template gets a test story objectstack Blocked-by 5a
5c objectstack#15953 — classify hotcrm's platform-semantics pins → derived proof families in verify (D) / platform regression tests (R) / keep (K) objectstack Blocked-by 5a
5 #1595 — replace hook-harness / flow-harness / action-sandbox / metadata-fixtures / tenancy-probe with the handle; delete the stand-ins and their five self-proof suites (~4,069 lines); declare the platform packages tests import hotcrm Blocked-by 5a published and pinned
6 docs↔metadata drift mechanics (19 files / 7,991 lines platform-candidate rows; repo-root / heading-label / docs-anchors helpers) — frozen: no new docs-drift tests; no card; revisit when a platform doc-lint exists
— tenant-scoped unique-index materialisation asserted by case-number-tenant-scope / account-name-tenant-scope group B objectstack folds into 5c's classification (expected (R)); if 5c files it elsewhere, the epic PM adds the Blocked-by on F5

Rules for the epic PM

  • Cards touching packages/spec go to the spec seat's queue with Blocked-by: back-links (none expected here).
  • A platform rule that does not fire under strict on this repo is a platform gap: file upstream, keep the local assertion; never delete on the name of a rule alone.
  • A behaviour the verify handle cannot express is likewise a platform gap (rule 2): file upstream, keep that one helper path, never re-grow a stand-in.
  • Business-fact pins (86 files) are not touched by this epic.
  • Close-out: last sub-issue closed → summary comment here → close this card and remove pm:epic.

Provenance of the census round

Filed by the director seat on the maintainer's instruction 「1543 选 F,普查卡同意开,你现在就派发处理」 (2026-09-05); the read-only census was delivered by an os-dev round (report 5552614013) and ACCEPTed (5552648775). The original census brief is preserved in this card's edit history. Step 5's two read-only surveys (hotcrm helper anatomy; platform test capabilities) were run by the director seat's own explore sub-agents on 2026-09-05T15:3x–15:4xZ; their findings are recorded on objectstack#15951 and #1595.

Refs: #1543 / PR #1580 (the first retirement) · objectstack#15922 (field-consumer diagnostic) · objectstack#13848 (2026-08-31 rulings) · objectstack#15929 (skills finding on the decision frame) · docs/audits/2026-09-hotcrm-handwritten-test-split.md (objectstack).

Activity

  1. self-assigned this
    on Sep 5, 2026
  2. added theissue type on Sep 5, 2026
  3. os-zhuang commented on Sep 5, 2026

    @os-zhuang
    ContributorAuthor

    Claim: PM loop round — director seat summon #15, maintainer direct dispatch (verbatim 「普查卡同意开,你现在就派发处理」, 2026-09-05, director chat); the repo:hotcrm seat #1353 is vacant
    Session: session_01TezFG8ZMrNH6n5VTNpPpdH
    Branch: claude/issue-1579-test-farm-census (probe/report branch only — this round is read-only, no PR expected)
    Worktree: hotcrm-issue-1579
    Domain: repo:hotcrm (single-lane repo; no domain:*)
    File surface: none written — reads test/**, scripts/**, src/**, content/docs/**, node_modules/@objectstack/* (pinned 17.3.0) and the sibling objectstack tree at origin/main; the deliverable is a comment on this card (stop on breach; explain in the report)
    Container & model: M measurement/classification (judgment, not gate-verified), mode:subagent, model: opus — node scripts/pm/dispatch-gates.mjs --tier (objectstack) reads "no path-derived mandate … floor sonnet · default opus · ceiling fable"; default tier because the output is a judgment table
    Clause-②: no
    Serial constraints cleared: read-only round, touches no file; #1543's retirement dev (claude/issue-1543-retire-field-consumer-scanner) runs concurrently in its own worktree — the census must classify scan-field-consumers.ts / field-consumer-scan.test.ts as "platform-lint candidate (already ruled F on #1543, upstream objectstack#15922)" regardless of whether that PR has landed; none otherwise.


    Generated by Claude Code

  4. os-zhuang commented on Sep 5, 2026

    @os-zhuang
    ContributorAuthor

    Dev claim (os-dev subagent) — issue #1579

    Branch: claude/issue-1579-test-farm-census (pushed empty as the write-routing probe; no PR expected this round)
    Worktree: /home/user/hotcrm-issue-1579 from origin/main @ b1599507
    Mode: mode:subagent
    Scope: READ-ONLY census. No file under test/, scripts/, src/, content/ is edited, deleted or moved; no upstream cards filed.
    Sibling platform tree /home/user/objectstack read via git show origin/main:PATH only (never its working tree).
    Deliverable: the full classification table posted as a comment here, then the os-dev-report JSON.


    Generated by Claude Code

  5. claude commented on Sep 5, 2026

    @claude
    Contributor

    Census: 169 test/ + 15 scripts/ files classified against the 2026-08-31 ruling

    Measured on origin/main b15995073eca00f123a7a3e5d109c4d626d067cc (b1599507), read-only from a dedicated worktree. Line counts are wc -l over git show origin/main:PATH blobs, so nothing here depends on my working tree. Nothing under test/, scripts/, src/ or content/ was edited, deleted or moved; no upstream card was filed.

    The card's own figures reproduce exactly: test/ = 169 files / 69,419 lines, scripts/ = 15 files / 3,877 lines. Total classified surface 73,296 lines.

    The three PM mechanism assumptions, measured

    1. Family sizes — confirmed, once the definition is stated. By file-name prefix alone test/docs-* is 26 files / 10,048 lines and test/flow-* is 19 / 8,464. The PM's 27 / 10,334 and 20 / 9,184 are recovered exactly by adding the family's shared helper: test/helpers/docs-anchors.ts (286) and test/helpers/flow-harness.ts (720). Both PM numbers are right; the families include their helper. The tree-walker estimate is the one that moves — see below.
    2. Classify by what the assertion proves — applied, and it is what forces the split rows. 35 files carry both kinds of assertion and get one row per class. test/docs-view-rosters.test.ts is the card's own example and splits exactly as the card predicted: "every view the app ships is named in its page's roster" (platform) against "the zh-Hans roster names views as the zh-CN pack spells them" (business).
    3. Shared harnesses are platform rows — confirmed, and they are heavily shared. test/helpers/hook-harness.ts (618 lines) is imported by 40 test files, test/helpers/repo-root.ts (15) by 52, test/helpers/flow-harness.ts (720) by 20, test/helpers/metadata-fixtures.ts (171) by 18, test/helpers/action-sandbox.ts (407) by 17, test/helpers/tenancy-probe.ts (86) by 7, test/helpers/heading-label.ts (117) by 3, test/helpers/persona-vocabulary.ts (62) by 2, test/helpers/docs-anchors.ts (286) by 1. Eight of the nine are platform rows; persona-vocabulary.ts is not — it holds this app's retired product names.
    4. The three gates — classified under the same three classes. check-source-hygiene.mjs (675) and check-lint-i18n-gate.mjs (158) are platform-lint candidates outright; the i18n gate's own header states its fix is a packages/lint severity change and therefore "out of this repo's reach". check-source-token-ratchet.mjs (567) is the one gate that genuinely splits: the comment-strip-and-count measurement engine is generic, but the committed ceilings and the two headline layers were fixed by a dated maintainer ruling about HotCRM's own positioning claim, and that half is a business-fact pin.

    Totals per class

    Two accountings, because 35 files carry both classes and I did not measure a per-describe line split — reporting one would be a fabricated number.

    A. Whole files, no double counting (the four buckets are disjoint and sum to 184 files / 73,296 lines):

    bucket files lines
    pure business-fact pin 86 33,166
    pure platform-lint candidate 62 19,614
    mixed (one row in each class) 35 20,422
    delete 1 94
    total 184 73,296

    B. Files touching each class (a mixed file counted in both):

    class files rows
    platform-lint candidate 97 97
    business-fact pin 121 121
    delete 1 1
    total rows 219

    The headline: at most 62 files / 19,614 lines are cleanly platform, and at least 86 files / 33,166 lines are cleanly this repo's own business facts. The 20,422 lines in the middle are where the review has to happen — they cannot be moved or kept wholesale.

    Three largest files per class

    class 1 2 3
    platform-lint candidate (pure) test/i18n-references.test.ts 866 test/object-validation-predicates.test.ts 791 test/readonly-write-semantics.test.ts 773
    business-fact pin (pure) test/hooks-runtime-service.test.ts 1503 test/flow-scheduled.test.ts 1502 test/quote-discount-ceiling.test.ts 898
    mixed test/sharing-coverage.test.ts 1309 test/metadata-references.test.ts 1288 test/flow-variable-conditions.test.ts 1272
    delete scripts/wow1-live-schema.sh 94 — —

    Counting mixed files into platform, the three largest platform-touching files are test/sharing-coverage.test.ts (1309), test/metadata-references.test.ts (1288) and test/flow-variable-conditions.test.ts (1272).

    The single delete row, and why there is only one

    scripts/wow1-live-schema.sh (94) is wired to no package.json script and no workflow, asserts nothing, and stages a platform capability claim ("the agent uses a brand-new field seconds after an admin adds it") rather than a HotCRM fact. Everything else in the census still guards something that is still true — the retirement pressure in this repo is relocation to the platform, not deletion, which is a different remedy from the one #1543 applied.

    Tests that re-implement a rule the platform already enforces

    Read from the pinned @objectstack/lint@17.3.0 rule set in node_modules (≈250 rule ids) and the @objectstack/cli@17.3.0 command set (os lint, os validate, os verify, os test, os doctor, os i18n check). flow-inert-node-condition — the known example named on the card — is present, alongside a much larger family than the card assumed.

    local test the platform rule it re-implements
    test/flow-decision-authority.test.ts flow-inert-node-condition (the card's example), flow-decision-unconditional-branch, flow-multiple-default-edges, flow-default-edge-with-condition
    test/view-tab-label-inert.test.ts liveness-dead-property, liveness-live-elsewhere-property
    test/metadata-references.test.ts (group A) 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 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/analytics-integrity.test.ts chart-dataset-unknown, chart-field-unknown, chart-measure-unknown, chart-axis-not-selected, widget-measure-unknown, widget-dataset-unknown, widget-sortby-unselected, dataset-field-unknown, filter-preset-comparand, filter-token-unknown
    test/dataset-granularity.test.ts dataset-include-unknown, dataset-field-not-included, measure-aggregate-incoherent
    test/field-groups-coverage.test.ts field-group-undeclared, field-group-empty, field-group-shadowed
    test/i18n-references.test.ts translation-target-unknown, translation-option-key-unknown, translation-section-name-missing, plus the whole os i18n check command
    scripts/check-lint-i18n-gate.mjs, test/lint-i18n-gate.test.ts the same i18n/missing-* family — this gate exists only to change its severity, which its own header says is a packages/lint change
    test/authorization-coverage.test.ts (group A) security-owd-unset, security-owd-alias, security-private-no-readscope, security-fls-unqualified-key, security-controlled-by-parent-no-relation, security-master-detail-ungranted, rls-predicate-unenforceable, rls-predicate-unparseable, rls-predicate-over-budget
    test/sharing-seeding.test.ts sharing-rule-object-not-shareable, sharing-rule-object-controlled-by-parent, sharing-rule-unlowerable-condition, sharing-rule-runtime-variable-condition
    test/parent-derived-reach.test.ts security-controlled-by-parent-no-relation, security-controlled-by-parent-ambiguous-relation
    test/object-validation-predicates.test.ts, test/view-predicate-dialect.test.ts, test/flow-condition-totality.test.ts, test/flow-variable-conditions.test.ts (group A), test/line-item-conventions.test.ts (group A) visibility-predicate-syntax, visibility-predicate-unknown-function, visibility-bare-identifier, predicate-path-unresolved, predicate-path-unrooted, expression-invalid, validation-rule-regex-uncompilable — the null-guard half is not shipped: objectstack#4763 (closed) proposed it and objectstack#7219 (open) records why the record.* layer cannot get an error-level path gate yet
    test/readonly-write-semantics.test.ts flow-update-readonly-field, flow-update-readonly-when-field, hook-api-update-readonly-field, hook-api-update-readonly-when-field, action-api-update-readonly-when-field
    test/hook-write-shape.test.ts, test/action-sandbox.test.ts (group A) hook-body-write-unknown-field, hook-body-write-unprovisioned-anchor, hook-body-source-unparseable, action-body-write-unknown-field, action-body-source-unparseable, action-record-write-discarded
    test/actions-flows-integrity.test.ts (group A) flow-node-write-unknown-field, flow-template-unknown-field, flow-template-lookup-traversal, flow-runas-unscoped, flow-multi-write-unfiltered
    test/flow-filter-today-token.test.ts (group A) filter-token-unknown, flow-time-relative-descriptor-invalid, flow-time-relative-antipattern, flow-date-equality-filter
    test/skills-integrity.test.ts ai-skill-tool-unresolved, ai-skill-surface-mismatch, capability-reference-unknown
    test/detail-section-dedup.test.ts page-section-group-unknown, form-section-group-unknown
    test/placeholder-picklist-options.test.ts seed-value-outside-state-machine is the nearest shipped neighbour; the placeholder-label smell itself is unshipped
    test/smoke.test.ts os validate — every invariant it asserts (manifest present, no retired workflows[], notify severities legal) is a validate-time structural check
    test/ownership-model.test.ts (group A) security-anchor-high-privilege, org-axis-permission-inheritance
    test/hook-org-inheritance.test.ts org-axis-permission-inheritance, org-axis-cross-org-bu-grant
    test/docs-runnable-samples.test.ts (group A) default-agent-outside-roster, default-agent-legacy-alias
    test/case-number-tenant-scope.test.ts (group B), test/account-name-tenant-scope.test.ts (group B) autonumber-references-unknown-field, autonumber-references-self are adjacent; the NULL-safe per-org unique index materialization they assert is driver-sql behaviour with no lint rule and no upstream card

    Tree-walking files — measured, not recalled

    The card's grep (readdirSync, glob, fast-glob, walk(, fs.readdir) returns 71 files, but the raw term glob is a false positive in every file where it is the only hit — it matches global, globalThis, globally and globex.example.com. No glob or fast-glob dependency is declared in package.json. Removing that term leaves 56, and those split into two genuinely different things:

    • 34 files walk the repository TREE (readdirSync; test/verify-log-decoy-pin.test.ts also shells out via execSync) — 32 under test/, 2 under scripts/.
    • 22 more files walk an in-memory OBJECT GRAPH via a local walk() generator over the already-imported stack (test/helpers/metadata-fixtures.ts exports walk(), a generator over arrays and object values, recursing arrays and object values). These read no directory and are not tree-walkers in the sense the card means.

    The PM's ~40 sits between the two measurements; the tree-walking number is 34.

    file lines class
    scripts/check-source-hygiene.mjs 675 platform-lint candidate
    scripts/check-source-token-ratchet.mjs 567 mixed
    test/actions-flows-integrity.test.ts 468 mixed
    test/collaboration-capabilities.test.ts 175 mixed
    test/deal-threshold-parity.test.ts 398 business-fact pin
    test/docs-analytics-vocabulary.test.ts 428 mixed
    test/docs-app-workflow-paths.test.ts 213 platform-lint candidate
    test/docs-conversion-rate-spelling.test.ts 122 business-fact pin
    test/docs-dashboard-tiles.test.ts 546 mixed
    test/docs-drift.test.ts 437 mixed
    test/docs-locale-callouts.test.ts 136 platform-lint candidate
    test/docs-metadata-counts.test.ts 293 platform-lint candidate
    test/docs-object-term-consistency.test.ts 556 business-fact pin
    test/docs-retired-personas.test.ts 189 business-fact pin
    test/docs-revenue-approvals-navigation.test.ts 622 mixed
    test/docs-role-hierarchy.test.ts 310 business-fact pin
    test/docs-runnable-samples.test.ts 207 mixed
    test/docs-setup-navigation-names.test.ts 743 mixed
    test/docs-src-tree-paths.test.ts 388 platform-lint candidate
    test/docs-view-rosters.test.ts 1095 mixed
    test/docs-zh-hant-justification.test.ts 381 business-fact pin
    test/helpers/docs-anchors.ts 286 platform-lint candidate
    test/hook-input-shape.test.ts 472 platform-lint candidate
    test/hook-query-predicate.test.ts 364 platform-lint candidate
    test/hook-write-shape.test.ts 535 platform-lint candidate
    test/hot-lead-threshold-parity.test.ts 319 business-fact pin
    test/labeler-config.test.ts 152 platform-lint candidate
    test/lead-duplicate-management.test.ts 569 business-fact pin
    test/line-item-conventions.test.ts 256 mixed
    test/runtime-coverage.test.ts 169 platform-lint candidate
    test/script-main-guard.test.ts 343 platform-lint candidate
    test/sharing-posture-declaration.test.ts 264 business-fact pin
    test/verify-log-decoy-pin.test.ts 315 platform-lint candidate
    test/view-predicate-dialect.test.ts 428 platform-lint candidate

    The 22 object-graph walkers (not repository-tree walkers)

    file lines class
    scripts/analytics-reconcile/macros.ts 117 platform-lint candidate
    scripts/lib/source-hygiene-surface.mjs 195 platform-lint candidate
    scripts/scan-field-consumers.ts 573 platform-lint candidate
    test/action-references.test.ts 382 platform-lint candidate
    test/activity-seed-coverage.test.ts 622 business-fact pin
    test/app-navigation-shape.test.ts 155 platform-lint candidate
    test/authorization-coverage.test.ts 750 mixed
    test/case-create-form-narrowing.test.ts 331 business-fact pin
    test/demo-staffing.test.ts 532 business-fact pin
    test/detail-section-dedup.test.ts 150 platform-lint candidate
    test/docs-quick-tour-navigation.test.ts 637 mixed
    test/docs-service-index-analytics.test.ts 261 mixed
    test/flow-decision-authority.test.ts 336 platform-lint candidate
    test/flow-variable-conditions.test.ts 1272 mixed
    test/forecast-seeds.test.ts 471 business-fact pin
    test/helpers/metadata-fixtures.ts 171 platform-lint candidate
    test/i18n-references.test.ts 866 platform-lint candidate
    test/live-work-predicate-parity.test.ts 406 business-fact pin
    test/metadata-references.test.ts 1288 mixed
    test/ownership-model.test.ts 487 mixed
    test/persona-copy.test.ts 173 business-fact pin
    test/view-references.test.ts 720 mixed

    The classification table — every file under test/ and scripts/, one row per class

    219 rows over 184 files: 149 single-class files and 35 mixed files split into a group A row and a group B row, with the assertion groups named in the rationale. upstream card is filled only for platform rows; none found means a targeted search_issues returned no matching objectstack card, not that none should exist.

    file lines class rationale upstream card, if platform
    scripts/analytics-reconcile/macros.ts 117 platform-lint candidate Date-macro resolution table for the reconcile tool — a generic filter-macro vocabulary, not a HotCRM fact. none found
    scripts/analytics-reconcile/reconcile.ts 424 platform-lint candidate Engine-agnostic dual-form analytics parity checker (imports TYPES only); "legacy inline query and dataset binding return identical numbers" is a migration invariant for any app. none found
    scripts/analytics-reconcile/run.ts 121 platform-lint candidate Boots a real kernel and injects the two executors — wiring for the same generic checker. none found
    scripts/backfill-owner-id.ts 236 business-fact pin One-time HotCRM data migration for this app's own retired owner column (#548), run against a live org. —
    scripts/check-lint-i18n-gate.mjs 158 platform-lint candidate Exists only to promote platform i18n/missing-* findings from WARNING to a failing exit code; its own header states the real fix is a packages/lint severity change. objectstack#11617 (open, adjacent); no severity card yet
    scripts/check-source-hygiene.mjs 675 platform-lint candidate console.log / TODO / file-size / control-byte / copyright-header scans — all generic source hygiene, nothing HotCRM-specific. none found (objectstack#5450 is an objectui instance of the control-byte class)
    scripts/check-source-token-ratchet.mjs (group A) 567 platform-lint candidate Measurement engine: TS comment-strip, blank-strip and token count over a directory partition — a generic os measure-shaped capability. none found
    scripts/check-source-token-ratchet.mjs (group B) 567 business-fact pin The committed ceilings and the two headline layers, fixed by a dated maintainer ruling about HotCRM's own positioning claim. —
    scripts/demo-staff.ts 343 business-fact pin Creates HotCRM's demo-org people through a running server; the staffing table is this app's #640 decision. —
    scripts/lib/main-module.d.mts 18 platform-lint candidate Types for the shared isMainModule() helper — a script-kit primitive any repo needs. none found
    scripts/lib/main-module.mjs 87 platform-lint candidate Realpath-aware entry-point test; a generic Node/ESM utility that belongs in a shared kit. none found
    scripts/lib/source-hygiene-surface.mjs 195 platform-lint candidate Declares the scan surface for the hygiene gate — same class as the gate it feeds. none found
    scripts/publish-marketplace.mjs 238 platform-lint candidate Uploads the compiled bundle to the cloud control plane; the pinned CLI already ships os package publish, so this duplicates a platform capability. none found
    scripts/scan-field-consumers.ts 573 platform-lint candidate Field-liveness scan (declared-but-never-read) — already ruled F on #1543. objectstack#15922 (open)
    scripts/sync-docs-screenshots.mjs 31 business-fact pin Copies this app's own screenshot assets into the docs site; the id roster is HotCRM's. Build helper, asserts nothing. —
    scripts/wow1-live-schema.sh 94 delete A demo-pitch timeline script wired to no package script and no workflow; it asserts nothing, and the capability it stages ("the agent sees a new field with no restart") is a platform claim, not a HotCRM fact. —
    test/helpers/action-sandbox.ts 407 platform-lint candidate QuickJS action-body sandbox harness driving the real runtime — a test kit; shared by 17 test files. none found
    test/helpers/docs-anchors.ts 286 platform-lint candidate MDX anchor / heading-slug resolver mirroring fumadocs — generic docs tooling; shared by 1 test file. none found
    test/helpers/flow-harness.ts 720 platform-lint candidate Drives the real AutomationEngine over a fixture store — a flow test kit; shared by 20 test files. none found
    test/helpers/heading-label.ts 117 platform-lint candidate Heading-to-label reader, same class as docs-anchors; shared by 3 test files. none found
    test/helpers/hook-harness.ts 618 platform-lint candidate Builds the engine's ctx wrapper and enforces the kernel's update contract — a hook test kit; shared by 40 test files. none found
    test/helpers/metadata-fixtures.ts 171 platform-lint candidate Walkers and fixtures over the compiled stack — generic metadata traversal; shared by 18 test files. none found
    test/helpers/persona-vocabulary.ts 62 business-fact pin The retired copilot persona spellings are HotCRM's own retired product names; shared by 2 test files. —
    test/helpers/repo-root.ts 15 platform-lint candidate Trivial repo-root resolver; shared by 52 test files. none found
    test/helpers/tenancy-probe.ts 86 platform-lint candidate Boots a real multi-tenant kernel with security and sharing plugins — a tenancy test kit; shared by 7 test files. none found
    test/account-name-normalized-match.test.ts (group A) 594 business-fact pin Acceptance: a case/whitespace variant reuses the same account, and the two normalized match keys are folded by the shipped hooks. —
    test/account-name-normalized-match.test.ts (group B) 594 platform-lint candidate The three premise: groups pin ObjectQL operator semantics ($regex retired, $icontains matches a superstring) and that driver-sql materializes no column for type: formula. none found
    test/account-name-tenant-scope.test.ts (group A) 202 business-fact pin crm_account declares name uniqueness on the FIELD, matching how contact and product spell it. —
    test/account-name-tenant-scope.test.ts (group B) 202 platform-lint candidate The physical per-tenant NULL-safe unique index the driver materializes, verified on real SQLite — a driver property, not a HotCRM fact. none found
    test/account-renewal-model.test.ts 128 business-fact pin #1181 retired the account-level renewal model; renewal stays a contract-level process — a HotCRM product decision. —
    test/action-references.test.ts 382 platform-lint candidate Navigation, dashboard-action route and list-action reference integrity over arbitrary metadata. none found — maps to nav-target-unresolved, dashboard-action-route-unresolved, action-name-undefined
    test/action-sandbox.test.ts (group A) 976 platform-lint candidate The sandbox boundary itself: the engine stub obeys the kernel update contract, capabilities are denied unless declared, bodies get no module scope, every registered hook lowers to a metadata-only body. none found — maps to action-body-source-unparseable, hook-body-source-unparseable
    test/action-sandbox.test.ts (group B) 976 business-fact pin The specific HotCRM action bodies (mass_update_stage, clone_opportunity, create_campaign, send_email, the shared line-item price fill, account_protection territory derivation). —
    test/actions-flows-integrity.test.ts (group A) 468 platform-lint candidate Action/flow writes name real fields, notify recipients resolve, no flow dot-walks a lookup in a template, no action is modal-typed. none found — maps to flow-node-write-unknown-field, action-body-write-unknown-field, flow-template-lookup-traversal, flow-runas-unscoped
    test/actions-flows-integrity.test.ts (group B) 468 business-fact pin lead_conversion dedupe of account and contact, line-item rollup wiring, demo-data readiness, the case escalate/close division. —
    test/activity-recency.test.ts 377 business-fact pin The event/task activity-bubble semantics are HotCRM's own recency model (what counts as contact, where it bubbles to). —
    test/activity-seed-coverage.test.ts 622 business-fact pin Pins this app's seed rows and that every Sales Activity widget returns a number over them. —
    test/analytics-integrity.test.ts 259 platform-lint candidate Report and dashboard binding resolution, no literal trend deltas, no build-time absolute dates in filters. none found — maps to chart-dataset-unknown, widget-measure-unknown, dataset-field-unknown, filter-preset-comparand
    test/app-navigation-shape.test.ts 155 platform-lint candidate One exemplar of every nav-item kind, and no two entries open the same destination — generic navigation shape. none found — maps to nav-target-unresolved, view-key-collision
    test/attendee-type-resolution.test.ts 495 business-fact pin The four-way attendee_type correspondence, its form hints and its seeded rows are HotCRM's own model, driven through a real engine. —
    test/authorization-coverage.test.ts (group A) 750 platform-lint candidate Object CRUD coverage, FLS keys object-qualified and real, RLS predicates pushdownable, inert grants (allowTransfer, readScope on controlled_by_parent) detected. none found — maps to security-owd-unset, security-fls-unqualified-key, rls-predicate-unenforceable, security-private-no-readscope
    test/authorization-coverage.test.ts (group B) 750 business-fact pin The #1096 unowned-case triage rule, the #488 regressions and the allowExport policy are this app's access decisions. —
    test/automation-docs-coverage.test.ts (group A) 520 platform-lint candidate Every shipped flow has exactly one doc row in every locale, and no row names a flow the app does not ship — doc-to-metadata coverage. none found
    test/automation-docs-coverage.test.ts (group B) 520 business-fact pin The trigger-surface wording each row states, and the section counts, are this app's prose. —
    test/bulk-action-dispatch.test.ts 234 platform-lint candidate Every bulk def names a real action and reads the builtin _selectedIds — a dispatch-declaration contract. none found — maps to action-name-undefined
    test/campaign-member-cascade.test.ts 276 business-fact pin Cascade on both party lookups, with the member rule staying satisfiable — HotCRM's campaign model through a real engine. —
    test/campaign-member-lifecycle.test.ts 492 business-fact pin Campaign-member lifecycle, opt-out sync and live metric recompute are HotCRM behaviour. —
    test/cascade-guard-messages.test.ts 314 business-fact pin The exact refusal wording HotCRM ships, including singular/plural agreement. —
    test/case-assignment.test.ts 897 business-fact pin HotCRM's case routing pools, escalation reassignment and the guest-strip ordering. —
    test/case-create-form-narrowing.test.ts 331 business-fact pin Which fields are creator-legitimate on this app's case intake form. —
    test/case-first-response.test.ts 241 business-fact pin What counts as a first response on a HotCRM case, and that the stamp is best-effort. —
    test/case-guest-branch-leftovers.test.ts 309 business-fact pin The guest strip on crm_case — this app's anonymous intake policy. —
    test/case-number-tenant-scope.test.ts (group A) 280 business-fact pin crm_case declares case-number uniqueness on the FIELD, matching account and contact. —
    test/case-number-tenant-scope.test.ts (group B) 280 platform-lint candidate The NULL-safe per-organization unique index, and the reproduction of the old table-composite spelling that constrained nothing untenanted — driver properties. none found
    test/case-sla-matrix.test.ts 290 business-fact pin The priority-by-tier SLA matrix and its fallbacks — the card's own example of a business fact. —
    test/churn-health-score-block.test.ts 332 business-fact pin The churn report's health_score criterion and that it returns rows over the shipped seeds. —
    test/collaboration-capabilities.test.ts (group A) 175 platform-lint candidate A files-enabled object must be editable by a non-guest profile, and no object silently gains an attachment surface — a declaration-coherence sweep. none found
    test/collaboration-capabilities.test.ts (group B) 175 business-fact pin The opt-in roster itself, including the deliberate lead exclusion (#602). —
    test/contact-email-tenant-scope.test.ts 163 business-fact pin contact_integrity's dedupe scoping is a HotCRM hook behaviour, including the system-write and untenanted paths. —
    test/contract-write-depth.test.ts (group A) 428 platform-lint candidate The spec gate accepts own_and_reports only when hierarchy-security is declared, and the open edition fails closed to owner-only — platform capability semantics. none found
    test/contract-write-depth.test.ts (group B) 428 business-fact pin sales_manager holding own_and_reports write depth on crm_contract is this app's grant. —
    test/converted-lead-guard.test.ts 204 business-fact pin The converted-lead lock is one guard in the hook, not a second validation — a HotCRM authoring decision and its wording. —
    test/dashboard-date-range-window.test.ts (group A) 503 platform-lint candidate Re-pins platform datetime-window defects by number (objectstack#3912 $gte/$lte, objectstack#3777 same-day bare-date bound) and the {today}/{yesterday} macro semantics. objectstack#3912, objectstack#3777 (both named in-file)
    test/dashboard-date-range-window.test.ts (group B) 503 business-fact pin That every datetime-windowed HotCRM dashboard answers under its own picker, and narrows as the preset narrows. —
    test/dataset-granularity.test.ts 224 platform-lint candidate Bucket/dimension declaration coherence — only date dimensions declare a bucket, every listed report has one date axis. none found — maps to dataset-field-unknown, measure-aggregate-incoherent
    test/deal-threshold-parity.test.ts 398 business-fact pin One definition of "large deal" and of the director tier, and the #1087 inclusive boundary — HotCRM's own amounts. —
    test/decorative-field-sweep.test.ts 254 business-fact pin #1182's removal verdicts and the account-hierarchy rollup that was kept because it gained a consumer. —
    test/demo-staffing.test.ts 532 business-fact pin The #640 staffing decision, what each staffed person receives, and that the published artifact cannot create these people. —
    test/detail-section-dedup.test.ts 150 platform-lint candidate record:details sections repeat no highlighted field, name no title field and are never empty — generic page shape. none found — maps to page-section-group-unknown
    test/do-not-call-enforcement.test.ts 322 business-fact pin The do_not_call policy boundary (refuse a scheduled call, allow a logged one) is this app's rule. —
    test/docs-analytics-vocabulary.test.ts (group A) 428 platform-lint candidate Links resolve only to pages that exist, and the stated counts equal what the stack registers — doc-to-metadata drift mechanics. none found
    test/docs-analytics-vocabulary.test.ts (group B) 428 business-fact pin The retired cube vocabulary, the "Pipeline by Stage" naming and the refresh-cadence prose are this app's copy. —
    test/docs-anchor-links.test.ts 288 platform-lint candidate MDX anchor resolution against the heading ids fumadocs emits, plus a self-test of the audit — pure docs tooling, no HotCRM fact. none found
    test/docs-app-workflow-paths.test.ts 213 platform-lint candidate The GitHub workflow path filter covers what the build compiles — CI-config drift, generic to any docs app. none found
    test/docs-contact-email-uniqueness.test.ts 122 business-fact pin The docs' uniqueness wording equals what this app enforces, verified against the shipped hook. —
    test/docs-conversion-rate-spelling.test.ts 122 business-fact pin 转化率 over 转换率 on every Chinese page — this app's terminology choice. —
    test/docs-dashboard-tiles.test.ts (group A) 546 platform-lint candidate Every registered dashboard has a section and every bolded tile reference names a real tile — doc-to-metadata reference integrity. none found
    test/docs-dashboard-tiles.test.ts (group B) 546 business-fact pin The per-locale word for "tile" and the two-script Chinese parity are this app's language-pack values. —
    test/docs-declared-versions.test.ts (group A) 653 platform-lint candidate Docs print the version the manifest declares, and one protocol version is stated in three files — generic manifest-to-doc drift. none found
    test/docs-declared-versions.test.ts (group B) 653 business-fact pin docs/STATUS.md's validator transcript figures and runtime-requirements table are this repository's own numbers. —
    test/docs-drift.test.ts (group A) 437 platform-lint candidate Published pages quote the compiled CEL condition verbatim — doc-to-metadata drift mechanics. none found
    test/docs-drift.test.ts (group B) 437 business-fact pin The large-deal threshold the pages must not state exclusively is HotCRM's amount. —
    test/docs-locale-callouts.test.ts 136 platform-lint candidate A translated page carries the same callout count as its English page — generic i18n docs parity. none found
    test/docs-metadata-counts.test.ts 293 platform-lint candidate Every count a doc states equals the count the stack registers, with dead exemptions detected. none found
    test/docs-object-coverage.test.ts 279 platform-lint candidate Every registered business object has a user-facing docs page, and the ledger names no object the stack lacks. none found
    test/docs-object-term-consistency.test.ts 556 business-fact pin The retired zh spellings and the derived object terms are this app's language-pack values — the card's own example of a business pin. —
    test/docs-pipeline-kanban-section.test.ts (group A) 243 platform-lint candidate The page lists exactly the fields the cards are bound to, and links to entries that exist — doc-to-metadata reference integrity. none found
    test/docs-pipeline-kanban-section.test.ts (group B) 243 business-fact pin Open deals only, "stage rules are advisory not enforcement" and the five card-holding stages are HotCRM's board semantics. —
    test/docs-quick-tour-navigation.test.ts (group A) 637 platform-lint candidate Bolded names resolve against the live navigation roster, and retired names stay italic — a citation-integrity mechanism. none found
    test/docs-quick-tour-navigation.test.ts (group B) 637 business-fact pin The six groups, the pinned Home entry, which groups collapse on load and the nine executive tiles are this app's navigation. —
    test/docs-readme-token-figures.test.ts 399 business-fact pin The README headline token figures and the ruled 5% buffer are HotCRM's positioning claim. —
    test/docs-retired-personas.test.ts 189 business-fact pin Sales Copilot / "Service Copilot" are this app's retired product names. —
    test/docs-revenue-approvals-navigation.test.ts (group A) 622 platform-lint candidate The approvals entry resolves to a component ref the installed console registers, and the phantom view names exist nowhere — reference integrity across an installed plugin. none found
    test/docs-revenue-approvals-navigation.test.ts (group B) 622 business-fact pin Keeping all five wrong names on the page with a stated denial is this app's documentation policy. —
    test/docs-role-hierarchy.test.ts 310 business-fact pin No role hierarchy drives visibility (#1019) is this app's security posture stated in prose. —
    test/docs-runnable-samples.test.ts (group A) 207 platform-lint candidate Every documented agent name resolves to a platform agent — reference integrity against @objectstack/spec/ai. none found — maps to default-agent-outside-roster
    test/docs-runnable-samples.test.ts (group B) 207 business-fact pin The action example teaching input._selectedIds and naming the underscore trap is this app's doc copy. —
    test/docs-sales-index-navigation.test.ts (group A) 236 platform-lint candidate Each object entry is spelled with its own navigation label, and every locale bundle gives the entry a real label — doc-to-metadata citation integrity. none found
    test/docs-sales-index-navigation.test.ts (group B) 236 business-fact pin The Sales group holding nine entries in that order, and Products being a Sales entry, are this app's navigation facts. —
    test/docs-search-navigation-views.test.ts 264 business-fact pin The zh-CN labels for the two opportunity views and the approvals plugin's My Pending are this app's pack values as cited in prose. —
    test/docs-service-index-analytics.test.ts (group A) 261 platform-lint candidate Bolded names resolve, and every listed tile exists on the dashboard — doc-to-metadata reference integrity. none found
    test/docs-service-index-analytics.test.ts (group B) 261 business-fact pin An agent ranking cannot be built and the SLA violation-rate vs compliance-percentage distinction are this app's analytics facts. —
    test/docs-setup-navigation-names.test.ts (group A) 743 platform-lint candidate Bold navigation citations are judged against the roster the platform actually ships, in every locale — a generic citation gate. none found
    test/docs-setup-navigation-names.test.ts (group B) 743 business-fact pin The quarantine ledger of retired UI names, and the app-side roster from src/apps plus src/translations. —
    test/docs-src-tree-paths.test.ts 388 platform-lint candidate No doc or agent brief points at a directory that does not exist — generic path-citation drift. none found
    test/docs-view-rosters.test.ts (group A) 1095 platform-lint candidate Every view the app ships is named in its page's roster and the name column names only shipped views — doc-to-metadata coverage, the card's stated platform example. none found
    test/docs-view-rosters.test.ts (group B) 1095 business-fact pin The zh-Hans roster naming views as the zh-CN pack spells them, and the pinned zh-Hant roster — the card's stated business example. —
    test/docs-zh-hant-justification.test.ts 381 business-fact pin The sanctioned reason for this repo's zh-Hant navigation convention, pinned against its AGENTS.md statement. —
    test/escalation-task-subject.test.ts 192 business-fact pin How HotCRM names an escalation task, and that the composed subject fits crm_task.subject's declared 255 cap. —
    test/event-attendee-cascade.test.ts 490 business-fact pin Cascade across the three party lookups with the attendee rule intact — HotCRM's event model through a real engine. —
    test/field-consumer-scan.test.ts 318 platform-lint candidate Tests the field-liveness scanner — already ruled F on #1543. objectstack#15922 (open)
    test/field-groups-coverage.test.ts 180 platform-lint candidate fieldGroups internal consistency: unique keys, every field-level group resolves, no empty or fully hoisted group. none found — maps to field-group-undeclared, field-group-empty, field-group-shadowed
    test/flow-billing-handoff.test.ts 372 business-fact pin The closed-won and contract-activation billing hand-offs, their once-only transition terms and durable delivery — HotCRM outcomes on the real engine. —
    test/flow-campaign-enrollment.test.ts 189 business-fact pin Campaign enrollment eligibility, top-up and the lead/contact branch exclusivity. —
    test/flow-case-actions.test.ts 201 business-fact pin escalate_case, its elevated stamping subflow and close_case — HotCRM screen-action outcomes. —
    test/flow-cold-boot-rebind.test.ts 134 platform-lint candidate Every authored flow registers through the real engine, and a read-decorated flow is rejected until the platform strip runs — a registration contract. none found
    test/flow-condition-totality.test.ts 650 platform-lint candidate Record-change flow conditions guard every field they read, and are TOTAL on the real engine — the generic has()-guard rule. objectstack#4763 (closed); objectstack#7219 (open, record.* path layer). flow-inert-node-condition is the shipped neighbour rule
    test/flow-conversion.test.ts 129 business-fact pin lead_conversion creates or reuses account, contact and opportunity — a HotCRM outcome. —
    test/flow-decision-authority.test.ts 336 platform-lint candidate No decision node carries the inert singular config.condition, every decision decides, a node-authoritative decision with no default sink fails open. none found — direct sibling of the shipped flow-decision-unconditional-branch and flow-multiple-default-edges rules
    test/flow-escalation-ownerless-case.test.ts 205 business-fact pin An ownerless critical case still escalates while the notify is gated, and the skip is a named gate — this app's #1430 ruling. —
    test/flow-filter-today-token.test.ts (group A) 521 platform-lint candidate PREMISE and TEETH groups pin that ObjectQL refuses {TODAY()}, knows {today}, and that an unknown token fails the run — engine semantics. none found — maps to filter-token-unknown
    test/flow-filter-today-token.test.ts (group B) 521 business-fact pin Which shipped HotCRM flow filters carry the token, and that contract_expiration expires exactly the past-due rows. —
    test/flow-followup.test.ts 76 business-fact pin schedule_followup binds the task through both polymorphic halves and stamps next_followup_date. —
    test/flow-harness-declared-columns.test.ts 542 platform-lint candidate The harness row shape is derived from the registry and matches a real driver, NULL is unorderable both ways (#1480), rows are detached (#1490) — a test-kit contract plus driver semantics. none found
    test/flow-quote.test.ts 73 business-fact pin quote_generation prices the quote and advances the stage — a HotCRM outcome. —
    test/flow-record-change.test.ts 578 business-fact pin Start conditions and outcomes for the seven shipped record-change flows, the inclusive #1087 line, and the insert-time twins. —
    test/flow-run-summary.test.ts 186 platform-lint candidate A healthy idempotent skip and a dead gate both trip the run-level predicate, and only the per-node fold tells them apart — an engine observability property. none found
    test/flow-scheduled-org-partition.test.ts (group A) 963 platform-lint candidate Scheduled create_record declares organization_id, and scheduled update_record writes only organization-neutral values, with dead exemptions detected — a generic multi-tenant authoring rule. none found — nearest shipped rule is flow-runas-unscoped
    test/flow-scheduled-org-partition.test.ts (group B) 963 business-fact pin The demo_bootstrap exemption and the #1372 forecast_snapshot cross-org sum are this app's decisions. —
    test/flow-scheduled.test.ts 1502 business-fact pin The nine scheduled sweeps' business outcomes (SLA breach, quote and contract expiry, renewal notice window, forecast snapshot, demo bootstrap) on the real engine. —
    test/flow-sla-ownerless-assignment.test.ts 309 business-fact pin The ownerless-breach assignment path and the empty-pool graceful no-op — this app's routing decision. —
    test/flow-sla-ownerless-case.test.ts 226 business-fact pin case_sla_monitor's #1405 behaviour on ownerless breached cases. —
    test/flow-variable-conditions.test.ts (group A) 1272 platform-lint candidate Flow-variable conditions guard every field read, every variable is bound on every path, and declared defaults are seeded before the start condition — generic authoring and engine rules. objectstack#4763 (closed) for the guard half; flow-bare-dollar-reference / unresolved-variable are the shipped neighbours
    test/flow-variable-conditions.test.ts (group B) 1272 business-fact pin The named HotCRM flows whose defaults have exactly one authority (#1173, #1155) and the two reproduced defects. —
    test/forecast-current-quarter-view.test.ts (group A) 798 platform-lint candidate No view label promises a time scope its filter does not express, with the exemption ledger kept honest — a generic label-vs-filter coherence rule. none found
    test/forecast-current-quarter-view.test.ts (group B) 798 business-fact pin this_quarter_forecasts and closing_this_quarter returning the current quarter on the real engine, and their four-locale empty states. —
    test/forecast-manual-override.test.ts 337 business-fact pin The #1082 manual-override stand-down and its way out — HotCRM forecast policy. —
    test/forecast-period-boundary.test.ts 420 business-fact pin Forecast periods must start on a calendar boundary — this app's rule, enforced through two real drivers. —
    test/forecast-period-end-boundary.test.ts 697 business-fact pin The forecast window rule as an invariant reaching already-stored rows, with the null-guard reverse verification. —
    test/forecast-period-scope.test.ts 364 business-fact pin Every forecast_metrics consumer pins a single period (#614) — this app's analytics rule. —
    test/forecast-seeds.test.ts 471 business-fact pin The forecast seed rows: calendar-true periods, seeder-only identity, cumulative buckets. —
    test/freeze-guard-reference-cleanup.test.ts 810 business-fact pin Which writes the settled-record freeze yields to (a lone link clear) and which it still refuses — this app's guard. —
    test/global-actions.test.ts 661 business-fact pin The activity actions' targets, attendee rows, record_label resolution and console submittability — HotCRM action behaviour. —
    test/guest-submission-sanitisation.test.ts 314 business-fact pin Which internal fields the guest branch strips on crm_case and crm_lead. —
    test/harness-lookup-shape.test.ts 325 platform-lint candidate The harness refuses a junk lookup value the way the engine does, across every reference-valued field — a test-kit fidelity contract. none found
    test/heading-label.test.ts 275 platform-lint candidate headingLabel() reads a heading the way fumadocs renders it, and no two headings on a page resolve to the same label — docs tooling. none found
    test/hook-input-shape.test.ts 472 platform-lint candidate The harness hands a hook the engine's own ctx wrapper (proxy traps, reserved keys, write-back), and no test may pass a plain-object ctx — a test-kit and kernel-contract file. none found
    test/hook-org-inheritance.test.ts 297 platform-lint candidate Hook-created records inherit the triggering organization and nothing escapes the partition — a platform tenancy property. none found — maps to org-axis-permission-inheritance
    test/hook-query-predicate.test.ts 364 platform-lint candidate ctx.api's where/filter alias semantics against the real kernel, with negative controls, plus a scan that no hook queries by filter. none found
    test/hook-write-shape.test.ts 535 platform-lint candidate The kernel's update contract first-hand, and that every hook-side write reaches the engine in that shape — a kernel-contract file. none found — maps to hook-api-update-readonly-field
    test/hooks-runtime-sales.test.ts 824 business-fact pin The sales hooks' business behaviour (opportunity lifecycle, quote workflow, account protection, contact integrity, product catalog). —
    test/hooks-runtime-service.test.ts 1503 business-fact pin The service, marketing and forecast hooks' business behaviour on the real harness. —
    test/hooks-runtime.test.ts 244 business-fact pin Rollups, the stage-age clock, price fill and lead auto-assign — HotCRM hook outcomes. —
    test/hot-lead-threshold-parity.test.ts 319 business-fact pin One definition of "hot" shared by the hot_leads view and the lead_assignment flow — this app's rating cut. —
    test/i18n-references.test.ts 866 platform-lint candidate Locale-pack completeness and key resolution across every authored surface — exactly what os i18n check and the translation-* lint rules cover. objectstack#11617 (open, the flow/screen i18n bucket); translation-target-unknown, translation-option-key-unknown, translation-section-name-missing
    test/i18n-shared-widget-parity.test.ts 212 business-fact pin The shared-widget factory literal reproduced verbatim in the English bundle — this app's dashboard grouping. —
    test/import-mappings.test.ts (group A) 256 platform-lint candidate Mappings target real, writable fields, use no javascript transform, and resolve reference columns through lookup targets. none found — maps to no-field-map, field-unknown
    test/import-mappings.test.ts (group B) 256 business-fact pin The three expected mappings, their templates' 50 example rows and the import guide's column documentation. —
    test/knowledge-article-share-links.test.ts 387 business-fact pin publicSharing shape, redaction set and the two-sided anonymous-visitor acceptance on the real ShareLinkService — this app's knowledge policy. —
    test/knowledge-deflection.test.ts 386 business-fact pin The case-to-article link, the deflection rate and the dashboard widgets bound to it. —
    test/knowledge-feedback.test.ts 317 business-fact pin view_count retired in favour of real feedback rows, and the counters recount from them. —
    test/labeler-config.test.ts 152 platform-lint candidate Every .github/labeler.yml glob matches at least one file and names an existing label — CI-config drift, generic to any repo. none found
    test/lead-disqualification.test.ts 136 business-fact pin Disqualification reason enforced as a rule, and the seeded unqualified leads satisfying it. —
    test/lead-duplicate-link-cleanup.test.ts 630 business-fact pin What happens to a duplicate claim when the record it named is deleted, including the #1164 tombstone. —
    test/lead-duplicate-management.test.ts 569 business-fact pin The duplicate-link data model, its four locale labels and the forms that can satisfy the rule. —
    test/lead-duplicate-visibility.test.ts 573 business-fact pin The suspected-duplicate banner, the conversion-time warning and the #1288 confirmed-duplicate refusal. —
    test/line-item-cascade.test.ts 303 business-fact pin Line items cascade with their parent, and a referenced product still cannot be deleted. —
    test/line-item-conventions.test.ts (group A) 256 platform-lint candidate Every Field.formula uses the F tag and every validation condition the P tag, and predicates are null-guarded — authoring conventions over arbitrary metadata. objectstack#4763 (closed) for the guard half
    test/line-item-conventions.test.ts (group B) 256 business-fact pin The two line-item price-fill hooks sharing literally one handler body, and the quote line tax-on-discounted-amount model. —
    test/lint-i18n-gate.test.ts 194 platform-lint candidate Tests the i18n gate script against synthetic fixtures — same class as the gate. same as check-lint-i18n-gate.mjs
    test/live-work-predicate-parity.test.ts 406 business-fact pin The "no longer live work" status set and its named consumers — HotCRM's case vocabulary. —
    test/metadata-references.test.ts (group A) 1288 platform-lint candidate Page-component, related-list, reference-rail, form, view and app-AI reference integrity over arbitrary metadata — the largest single platform-candidate body in the repo. none found — maps to page-field-unknown, list-view-field-unknown, view-ref-form-target-missing, component-props-unknown-key, default-agent-outside-roster
    test/metadata-references.test.ts (group B) 1288 business-fact pin The #1002 retired-persona check on live UI copy, and the home card pointing at the documented assistant entry point. —
    test/object-validation-predicates.test.ts 791 platform-lint candidate Every authored predicate guards every field it reads, every stdlib argument is null-guarded, and predicates are TOTAL on the real engine. objectstack#4763 (closed); objectstack#7219 (open)
    test/opportunity-creation-date.test.ts 111 business-fact pin crm_opportunity's duplicate created_date is gone while crm_case keeps its own — a HotCRM schema decision. —
    test/ownership-model.test.ts (group A) 487 platform-lint candidate Every owner-scoped object declares owner_id as a sys_user lookup, nothing in the schema fills it, and the transfer gate sees a ctx.api insert but not a beforeInsert mutation — platform middleware semantics. none found — maps to security-anchor-high-privilege
    test/ownership-model.test.ts (group B) 487 business-fact pin Which profiles hold allowTransfer on which objects — this app's grants. —
    test/parent-derived-reach.test.ts 353 platform-lint candidate What a territory-shared account carries into its related lists, and the parent-write gate deriving from the master — controlled_by_parent semantics on the real engine. none found — maps to security-controlled-by-parent-no-relation
    test/persona-copy.test.ts 173 business-fact pin No authored HotCRM string names a retired copilot persona (#1003). —
    test/placeholder-picklist-options.test.ts 136 platform-lint candidate No picklist ships serial placeholder labels ("Competitor A") — a generic authoring smell with a self-test. none found
    test/priority-rank-parity.test.ts 126 business-fact pin The two hand-copied priority rank maps agree, and the unranked sentinel sorts below every real rank. —
    test/quote-accepted-draft-defaults.test.ts 216 business-fact pin The placeholder defaults the drafted contract carries, and the provenance sentence the ruling kept. —
    test/quote-accepted-lookups.test.ts 337 business-fact pin An absent link is an absent key, and a contract that will not draft does not decide whether the deal is won. —
    test/quote-accepted-payment-terms.test.ts 264 business-fact pin Negotiated payment terms carry from quote to contract across the shared vocabulary. —
    test/quote-contact-required-when.test.ts 462 business-fact pin A quote needs a contact from presented onward — this app's gate, enforced through two real drivers. —
    test/quote-discount-ceiling.test.ts 898 business-fact pin The discount ceiling constant, its invariant reach and the line-item half — HotCRM's own number. —
    test/readonly-write-semantics.test.ts 773 platform-lint candidate Which writer survives the readonly strip under which runAs, and that insert is exempt — a platform write-path rule matrix. none found — maps to flow-update-readonly-field, hook-api-update-readonly-field
    test/record-id-not-in-prose.test.ts 387 business-fact pin Task subjects and refusals name the record, not its primary key — this app's copy rule. —
    test/refusal-envelope.test.ts (group A) 350 platform-lint candidate Every refusal uses a member of the platform ErrorCode enum with the declared status, and survives the QuickJS boundary — an ADR-0112 envelope contract. none found
    test/refusal-envelope.test.ts (group B) 350 business-fact pin The REFUSAL_CODES vocabulary and the one deliberately bare throw are this app's declarations. —
    test/runtime-coverage.test.ts 169 platform-lint candidate Every registered hook and flow is named in a runtime test, with the pending list kept honest — a generic coverage ratchet. none found
    test/saas-composition.test.ts 397 business-fact pin The two declared compositions, what each replays, and tenant_admin's org-scoped capability — HotCRM packaging. —
    test/script-main-guard.test.ts 343 platform-lint candidate Every script in scripts/ routes its entry-point test through the shared helper, verified behaviourally through a symlinked path. none found
    test/seed-consistency.test.ts 589 business-fact pin The seed datasets' internal coherence against the hooks that would recompute them. —
    test/seed-validation-warnings.test.ts 264 business-fact pin Seed rows clear the validation rules this app ships them under. —
    test/sharing-coverage.test.ts (group A) 1309 platform-lint candidate The OWD table lists every registered object exactly once and the sharing-rules table lists what the app ships — doc-to-metadata coverage, in every locale. none found
    test/sharing-coverage.test.ts (group B) 1309 business-fact pin Which three rules widen crm_case, the parent-derived reach claims and the Sales Representative block — this app's sharing posture. —
    test/sharing-posture-declaration.test.ts 264 business-fact pin This app's declared sharing posture, pinned against its own source. —
    test/sharing-seeding.test.ts 641 platform-lint candidate Every seeded sharing rule EXECUTES on the configured driver rather than merely compiling, and what a compiled condition does there — engine semantics. none found — maps to sharing-rule-unlowerable-condition, sharing-rule-runtime-variable-condition
    test/skills-integrity.test.ts 233 platform-lint candidate Skill tool references and cross-references resolve. none found — maps to ai-skill-tool-unresolved, ai-skill-surface-mismatch
    test/sla-at-risk-live-work.test.ts 229 business-fact pin The SLA at Risk view still selects on the live-work predicate — this app's view. —
    test/sla-compliance-gauge.test.ts 367 business-fact pin The SLA gauge is bound to a compliance measure and reads 100% on the seeded demo org (#1213). —
    test/smoke.test.ts 83 platform-lint candidate Structural invariants of the compiled bundle (manifest present, no workflows[], notify severities valid) — exactly what os validate proves. none found
    test/source-hygiene-header-position.test.ts 274 platform-lint candidate Tests the copyright-header position check of the hygiene gate — same class as the gate. same as check-source-hygiene.mjs
    test/source-hygiene-scan-surface.test.ts 463 platform-lint candidate Tests the hygiene gate's scan surface, including control bytes in root text files (#838). same as check-source-hygiene.mjs
    test/source-hygiene-size-advisory.test.ts 270 platform-lint candidate Tests the hygiene gate's file-size advisory band. same as check-source-hygiene.mjs
    test/source-token-ratchet.test.ts (group A) 631 platform-lint candidate Tests the measurement basis and the ratchet mechanism — same class as the engine half of the gate. none found
    test/source-token-ratchet.test.ts (group B) 631 business-fact pin this repository, today pins HotCRM's committed ceilings and the derived header table. —
    test/status-state-machines.test.ts 271 business-fact pin Which HotCRM objects have a governed status lifecycle, which stay descriptive, and the admin-docs roster of them. —
    test/territory-seed-coverage.test.ts 291 business-fact pin The demo dataset can exercise this app's territory rules, and every family module is wired into CrmSeedData. —
    test/territory-single-source.test.ts 383 business-fact pin One territory mapping shared by hook, metadata and docs tables — this app's country-to-territory table. —
    test/unassigned-case-triage-reach.test.ts 863 business-fact pin The case self-claim seam and the write half of taking ownership, measured on two drivers — this app's #1096 mechanism. —
    test/undeclared-key-probe.test.ts 345 platform-lint candidate A key a hook writes is refused by every driver in one ADR-0112 envelope — a driver-parity probe on a platform asymmetry. none found
    test/verify-log-decoy-pin.test.ts 315 platform-lint candidate No test may echo a gate failure marker into the verify log (#1302) — a generic test-output hygiene ratchet over gate-fixture suites. none found
    test/view-predicate-dialect.test.ts 428 platform-lint candidate View predicates are record-bound and TOTAL on the real engine, swept across every shipped view. objectstack#7219 (open, record.* path-resolution gate); visibility-predicate-syntax, predicate-path-unresolved
    test/view-references.test.ts (group A) 720 platform-lint candidate View field references, filter tokens, row colors and kanban groups resolve against real fields and option values. none found — maps to list-view-field-unknown, filter-token-unknown, sort-field-unknown
    test/view-references.test.ts (group B) 720 business-fact pin Priority queues sorting by priority_rank and the canonical opportunity stage roster reaching the UI — this app's vocabularies. —
    test/view-tab-label-inert.test.ts 144 platform-lint candidate list.tabs[] is absent rather than merely label-free (#1307) — an inert-declaration check. none found — maps to liveness-dead-property
    test/win-loss-capture.test.ts 857 business-fact pin The win/loss reason conditional write contract, the seeds that carry it, and the measured win rate. —

    Search provenance. Five targeted search_issues calls, one per platform-candidate family, all against repo:objectstack-ai/objectstack. The control hit proving the channel reads: the field-liveness query returned objectstack#15922 (open) — the #1543 platform card named on this issue — as its top result, and the predicate query returned it again alongside objectstack#4763 (closed, "has(x) reads as a null guard and is not one — a publish-time lint should reject un-guarded nullable comparisons in CEL predicates") and objectstack#7219 (open, the record.* path-resolution gate). The i18n query returned objectstack#11617 (open, "hotcrm: the six screen-flow dialogs become visible to os lint once the CLI's flow/screen i18n bucket lands"). A sixth query was rejected 422 for unescaped parentheses in the query string and was re-issued without them. The doc-drift and source-hygiene families returned no matching platform card — the nearest hits were objectstack's own tooling issues (#11434, #5450), which are instances of the class, not cards for shipping it downstream.

    Two measurement notes for the review.

    • @objectstack/verify is not a package in the pinned node_modules (the 51 installed @objectstack/* packages are all 17.3.0 and none is verify). It exists as packages/verify in the objectstack tree and is reachable as the CLI command os verify. The card's phrasing "the platform ships @objectstack/verify" is true of the platform tree, not of anything this repo can import today — which matters for any row whose remedy is "move it to @objectstack/verify".
    • The remedy this census points at is overwhelmingly relocation, not deletion: 1 delete row against 97 platform-touching files. [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's retirement shape (delete the local tool once the upstream card exists) generalises to at most a handful of these without an upstream landing first.

    Generated by Claude Code

  6. claude commented on Sep 5, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 1579,
      "status": "done",
      "branch": "claude/issue-1579-test-farm-census",
      "pr": null,
      "premise_still_valid": true,
      "files_changed": [],
      "gates": "none owed (read-only)",
      "summary": "Read-only census of all 184 files under test/ (169) and scripts/ (15) on origin/main b1599507, classified against the AGENTS.md 2026-08-31 rule 3. The card's own figures reproduce exactly (test/ 69,419 lines; scripts/ 3,877; total 73,296), so the premise holds. 219 rows over 184 files: 149 single-class files plus 35 mixed files split into a group A and a group B row with the assertion groups named. Disjoint whole-file buckets: 86 files / 33,166 lines pure business-fact pin, 62 / 19,614 pure platform-lint candidate, 35 / 20,422 mixed, 1 / 94 delete. Counting a mixed file in both classes, 97 files touch platform-lint candidate and 121 touch business-fact pin. The dominant finding is that the remedy here is relocation to the platform, not deletion: exactly one delete row (scripts/wow1-live-schema.sh, wired to no package script or workflow, asserting nothing) against 97 platform-touching files, so #1543's delete-the-local-tool shape does not generalise without an upstream landing first. Full table, per-class totals, three largest per class, the tree-walker census and the platform-rule duplication table are posted as one comment on the card.",
      "totals_by_class": {
        "accounting_note": "Two accountings are given because 35 files carry both classes and no per-describe line split was measured; reporting one would be fabricated. Bucket A is disjoint and sums to 184 files / 73,296 lines.",
        "bucket_disjoint_whole_files": {
          "pure_business_fact_pin": { "files": 86, "lines": 33166 },
          "pure_platform_lint_candidate": { "files": 62, "lines": 19614 },
          "mixed_both_classes": { "files": 35, "lines": 20422 },
          "delete": { "files": 1, "lines": 94 },
          "total": { "files": 184, "lines": 73296 }
        },
        "files_touching_each_class_mixed_counted_in_both": {
          "platform_lint_candidate": { "files": 97, "rows": 97 },
          "business_fact_pin": { "files": 121, "rows": 121 },
          "delete": { "files": 1, "rows": 1 },
          "total_rows": 219
        },
        "largest_per_class": {
          "platform_lint_candidate_pure": ["test/i18n-references.test.ts 866", "test/object-validation-predicates.test.ts 791", "test/readonly-write-semantics.test.ts 773"],
          "business_fact_pin_pure": ["test/hooks-runtime-service.test.ts 1503", "test/flow-scheduled.test.ts 1502", "test/quote-discount-ceiling.test.ts 898"],
          "mixed": ["test/sharing-coverage.test.ts 1309", "test/metadata-references.test.ts 1288", "test/flow-variable-conditions.test.ts 1272"],
          "delete": ["scripts/wow1-live-schema.sh 94"]
        },
        "tree_walkers": {
          "repository_tree_walkers": 34,
          "of_which_test": 32,
          "of_which_scripts": 2,
          "object_graph_walkers_not_tree": 22,
          "raw_card_grep_hits": 71,
          "note": "The card's grep term `glob` is a false positive in every file where it is the only hit (global / globalThis / globally / globex.example.com); no glob or fast-glob dependency is declared in package.json. Removing it leaves 56, which split into 34 filesystem walkers (readdirSync; verify-log-decoy-pin also uses execSync) and 22 files walking an in-memory object graph via the walk() generator exported by test/helpers/metadata-fixtures.ts. The PM's ~40 sits between the two; the tree-walking number is 34."
        }
      },
      "pm_assumptions_measured": {
        "1_family_sizes": "CONFIRMED once the definition is stated. By file-name prefix alone docs-* is 26 files / 10,048 lines and flow-* is 19 / 8,464. The PM's 27 / 10,334 and 20 / 9,184 are recovered exactly by adding the family's shared helper: test/helpers/docs-anchors.ts (286) and test/helpers/flow-harness.ts (720). Both PM numbers are right; the families include their helper. Only the ~40 tree-walker estimate moves, to 34.",
        "2_classify_by_what_is_proven": "APPLIED, and it is what forces the 35 split rows. docs-view-rosters.test.ts splits exactly as the card predicted.",
        "3_shared_harnesses_are_platform_rows": "CONFIRMED and heavily shared: hook-harness 40 sharers, repo-root 52, flow-harness 20, metadata-fixtures 18, action-sandbox 17, tenancy-probe 7, heading-label 3, persona-vocabulary 2, docs-anchors 1. Eight of the nine are platform rows; persona-vocabulary.ts is not, it holds this app's retired product names.",
        "4_three_gates": "check-source-hygiene.mjs (675) and check-lint-i18n-gate.mjs (158) are platform-lint candidates outright; the i18n gate's own header states its fix is a packages/lint severity change, out of this repo's reach. check-source-token-ratchet.mjs (567) is the one gate that genuinely splits: generic measurement engine (platform) against ceilings and headline layers fixed by a dated maintainer ruling about HotCRM's positioning claim (business)."
      },
      "tests": "None owed and none run: this round edits no file and ships no PR, so there is no diff for any gate to read. Measurement evidence instead: line counts from `wc -l` over `git show origin/main:PATH` blobs for all 184 files, reconciling to the card's own figures exactly (test/ 69,419; scripts/ 3,877; sum of the four disjoint buckets 20,422+94+33,166+19,614 = 73,296 = 69,419+3,877). File coverage verified mechanically with comm against `git ls-tree -r --name-only origin/main -- test scripts`: 184 files listed, 184 classified, zero missing and zero extra. Platform capabilities read from the pinned node_modules (51 @objectstack/* packages, all 17.3.0) rather than from memory: ~250 lint rule ids extracted from @objectstack/lint/dist/index.js and the oclif command set from @objectstack/cli/dist/commands. No ablation applies (nothing was implemented).",
      "mcp_calls": "9 — 2 issue_read (get, get_comments), 1 add_issue_comment (claim), 6 search_issues attempts of which 5 returned (one rejected 422 for unescaped parentheses and was re-issued). The census comment and this report comment were posted over REST, not MCP.",
      "open_questions": [],
      "out_of_scope_findings": [
        "NOT FILED, per this round's explicit no-upstream-cards rule; recorded here for the maintainer's review instead. (a) `@objectstack/verify` is not a package in the pinned node_modules — none of the 51 installed @objectstack/* packages is `verify`. It exists as packages/verify in the objectstack tree and is reachable as the CLI command `os verify`. The card's phrasing 'the platform ships @objectstack/verify' is true of the platform tree, not of anything this repo can import today, which matters for any row whose stated remedy is 'move it to @objectstack/verify'. (b) The null-guard / predicate-totality family (5 local files, the single largest coherent platform-candidate cluster after reference integrity) has no shipped lint rule: objectstack#4763 proposed exactly it and is CLOSED, and objectstack#7219 is OPEN recording why the record.* layer cannot get an error-level path gate yet. Any plan to retire those five locally depends on which of those two is the live card. (c) scripts/publish-marketplace.mjs duplicates the pinned CLI's `os package publish` command."
      ]
    }

    Generated by Claude Code

  7. os-zhuang commented on Sep 5, 2026

    @os-zhuang
    ContributorAuthor

    Review — ACCEPT (read-only round), and the card moves to the decision box (director seat, summon #15, session_01TezFG8ZMrNH6n5VTNpPpdH, 2026-09-05T15:0xZ)

    Verified on GitHub, not on the report: the census comment 5552607309 is on the card (64,331 chars), measured on origin/main b1599507; its four disjoint buckets sum to 184 files / 73,296 lines = the card's own test/ 69,419 + scripts/ 3,877; no file under test/, scripts/, src/, content/ changed (branch claude/issue-1579-test-farm-census carries zero commits beyond base); no upstream card filed. The PM's "~40 tree-walkers" is corrected to 34 with the method stated (the glob grep term was a false positive). Deliverable complete.

    决策卡(程序级取舍,归维护者)

    一句话问题:hotcrm 的 184 个测试/脚本文件里,97 个含「任何元数据应用都成立」的平台级断言(62 个纯平台、35 个混合),只有 1 个可直接删;按 2026-08-31 裁决它们该在平台,但今天平台还没有对应的 lint/verify 能力可接 —— 这是一个多周程序,不是一批删除。

    实测(普查评论 5552607309):纯业务钉 86 文件 / 33,166 行;纯平台候选 62 / 19,614;混合 35 / 20,422;可删 1(scripts/wow1-live-schema.sh)。九个共享 helper 中八个是平台级 test-kit 形状(hook-harness 40 个引用、repo-root 52、flow-harness 20、metadata-fixtures 18、action-sandbox 17)。平台侧:@objectstack/verify 不是 pin 里的可安装包(只作 os verify 命令存在);空值守卫/谓词完备性这一族(5 文件)对应的平台 lint 规则从未落地(objectstack#4763 已关、#7219 仍开)。

    选项 × 真实代价

    • A 平台先行、按族迁移:每族先在 objectstack 立平台卡(lint 规则 / verify 能力 / 发布 test-kit),平台能力可安装后 hotcrm 同族测试退役。代价:数周;期间 hotcrm 测试原样保留(不再新增)。业务含义:样板停止自己造门,但不牺牲今天的覆盖。
    • B 直接删平台候选:62 纯平台文件现在删。代价:约 19,600 行覆盖消失而平台无替代,退化只能在真实部署上发现。业务含义:为整洁付覆盖。
    • C 只冻结:不新增门,现存全部保留,不迁。代价:2026-08-31 裁决停留在纸面。

    推荐 A,并把顺序定为:① 共享 harness 升格为平台 test-kit(一张 objectstack 卡,解锁 40+ 测试的迁移);② 引用完整性族(i18n/metadata/action references,最大且最机械);③ 空值守卫/谓词族(先裁 #4763/#7219 谁是活卡);④ 混合 35 文件逐个拆行,业务半留、平台半随族走。回退 C。

    os-decision-facets
    ① 项目长远合理性:A 缩小特例 —— 一套门在平台,N 个应用共用;B/C 都把「样板自己造门」留在原地。
    ② 实际业务拉动:今天没有用户撞它;拉动是 2026-08-31 裁决与 #1543 的先例,以及每个照抄 hotcrm 的 AI 都在学「自己写门」。
    ③ 防 AI 犯错:A 让平台 lint 对所有应用响亮一致;B 让退化静默到部署才被发现。
    ④ 创业阶段不扩散:A 不新增声明面,只搬;但它是多周程序,需要 appetite —— 这正是进决策箱的原因。
    推荐:A(顺序 ①②③④)。选项:A / B / C。
    本分析看不见什么:平台侧各族的实现成本(lint 规则数 vs test-kit 包的发布形态);hotcrm 席何时有人坐(现空);#4763 / #7219 之间哪张是活的。

    State transition, same stroke: pm:dispatched → needs-user-decision; assignee released. ⛔ Not dispatchable until ruled; the census table is the working document.


    Generated by Claude Code

  8. added
    needs-user-decisionNeeds the maintainer's call before work proceeds
    and removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Sep 5, 2026
  9. removed their assignment
    on Sep 5, 2026
  10. 39 remaining items

  11. os-steve commented on Sep 8, 2026

    @os-steve
    Collaborator

    ⭐ I was wrong that this epic had no dispatchable work. Two cards filed and dispatched, neither blocked on anything.

    Epic PM session_01DuzfS5chho38Yx1jxx9DEj, 06:5xZ. Base origin/main e5cedc7.

    The correction

    On 2026-09-06 03:2xZ I wrote here: "Once #1637 reports, this epic has no dispatchable work until a release cuts. I will hold rather than manufacture any." I then reported that same conclusion every hour for most of a day, until the maintainer asked 「你在等什么」.

    ⭐ The lesson was already written on #1581, by the maintainer, on 2026-09-05 — 「必须要等发版本吗?」 → 「ok」 → 「不要等发版,hotcrm能做的开发可以先做」 — and I had recorded it myself: a Blocked-by sits on a CARD, but blockage is a property of the WORK. I then spent a day treating card-level blockage as work-level blockage anyway, because my own hourly status line had become my evidence instead of the repo.

    Two pieces survive that test. Both are filed, claimed and dispatched.

    #1769 — declare the 9 @objectstack/* packages test/ imports and package.json does not

    Split out of #1595 item 4. #1595 is Blocked-by: objectstack#15951 and keeps that block for its harness port; this half never needed the handle, the release, or --strict.

    Re-measured on e5cedc7 rather than carried: test/ + scripts/ import 19 @objectstack/* packages, package.json declares 12, the gap is 9. ⚠️ #1595's body lists 7 — it was written 09-05 and service-storage and trigger-record-change have joined since. The card says re-derive, ⛔ do not transcribe.

    ⭐ The change is resolution-neutral, and that is measured. All nine are published at exactly 17.3.0 and pnpm-lock.yaml already resolves all nine at 17.3.0, so declaring them at exact 17.3.0 (this repo's convention — no ^ on any @objectstack/*) moves nothing. The packages: section must come back byte-identical, and the card makes the dev prove that with hashes rather than assert it.

    ⚠️ Why "declare, do not regenerate" is load-bearing this week. @objectstack/plugin-auth is one of the nine, and it is the package at the centre of objectstack#16186: its published 17.3.0 declares the better-auth family at ^1.7.2, and a patch release of @better-auth/core removed a public ./db export. A caret cannot protect against that. The exact pin landed in objectstack main (PR #16634, merged 09-07T23:08Z, with scripts/check-vendor-export-contract.mjs as a new gate) but is not published. This repo is safe only because its lock already pins @better-auth/core@1.7.2 — incidental protection that a pnpm install --force would throw away. So the card's second acceptance line is that @better-auth/core still resolves to 1.7.2 after install.

    #1770 — vacuity sweep of the test farm

    ⭐ The part of the farm this epic has never looked at. #1613 / #1621 / #1637 measured 97 rows, every one an assertion credited against a platform lint rule. The census's 86 pure business-fact files (33,166 lines) were declared "stay" and never audited for whether they assert anything at all — gate 1 only ever ran where a platform rule was already in the frame.

    ⛔ Not hypothetical. Every time anyone has looked it has been there:

    where what
    PR #1611 two walkers green while inspecting nothing; fixing the walk moved the case count 61 → 47
    #1637 §1b analytics-integrity's "every url-type widget and header action resolves" inspects an empty set — 0 header actions, 0 widget actionUrl
    #1637 §5 7 of 22 rows credited against a structurally empty population

    Starting proxy on e5cedc7: of 180 files, 141 walk a collected set and 62 of those carry no non-empty guard anywhere. ⚠️ 62 is neither a floor nor a ceiling and the card forbids reporting it as either — a file can guard one walker and not a second, and a walker can be non-vacuous without saying so. ⭐ The unit is the walker, not the file, and reporting the grep as the answer would be the eighth instance of this epic's signature failure. The method is to instrument each walk to report the size it actually inspects at run time, then plant a defect in one of the cases it did inspect and prove the file reds.

    ⭐ It is also the exact mirror of step 3's own surprise. That found 21 rows where the platform covers something no local test asserts; this looks for assertions that cover nothing. Both are coverage the repo believes it has and does not.

    ⛔ Read-only: no deletions, no test changes, no PR, no surviving branch, and ⛔ no upstream cards — nothing here is a platform question. Every vacuous walker found is a follow-up, because fixing one is a business-fact judgement, not a sweep.

    Unchanged this round, verified rather than carried

    Tree

    step card state
    1 · 2a · 2c objectstack#15935 · #1596 · #1604 landed 2da2901e · 8223d0a · 9924ee1a
    2b #1581 blocked — CLI still 17.3.0 (06:50Z)
    3 surveys #1613 · #1621 · #1637 closed; 97 of 97 rows measured
    3 retirement #1625 / PR #1629 landed f598320c — 11 ids, 169 lines
    3 remainder #1582–#1587 blocked on #1581 / the fourteen upstream cards
    4 objectstack#15936 triage; ping unspent by design
    5a objectstack#15951 ⛔ HOLDING on Fable capacity
    5 (deps half) #1769 dispatched — needs no release
    5 (port half) #1595 blocked on 5a published and pinned
    — #1770 dispatched — read-only, needs no release
    6 docs↔metadata drift frozen

    Generated by Claude Code

  12. os-steve commented on Sep 8, 2026

    @os-steve
    Collaborator

    Step 5's dependency half LANDED. ⭐ The #16186 guard is provably intact on main, not just on the PR.

    Epic PM session_01DuzfS5chho38Yx1jxx9DEj, 07:5xZ.

    PR #1771 merged 07:26:34Z → 5b2ecdd on main. Verified from main's own log against five distinct sibling commits (#1762, #1761, #1760, #1757, #1756), ⛔ not from the API's merged: true. pm:dispatched stripped from #1769, pm:epic kept.

    Re-checked on main after the merge, ⛔ not carried from the PR

    check on origin/main
    the nine declared 9 / 9, all exact 17.3.0, none missing
    the card's comm -23 gap check empty — every @objectstack/* that test/ or scripts/ imports is now declared
    packages: section hash 95bfe9b47789dda033387104270392f014c6fb47 — identical to the pre-merge base e5cedc7

    ⭐ That third row is the one worth stating. A squash merge is a re-application of the diff, so "the PR did not move the lock's resolution" and "main did not move the lock's resolution" are different claims and only the second one protects anything. The pin that keeps @better-auth/core at 1.7.2 — the whole objectstack#16186 guard — lives in that section, and it survived the merge byte-for-byte. ⛔ Neither the merge nor any later declaration-only change may be assumed to preserve it; re-derive the hash.

    Where the epic actually stands

    Two pieces this seat had been reporting as blocked were not blocked. One is now on main; the other (#1770, the vacuity sweep) is in flight. Nothing else in the epic is dispatchable, and this time the claim is itemised rather than asserted:

    what what it genuinely waits on
    #1581 → the deletion half of #1582–#1587 a published @objectstack/cli > 17.3.0 carrying --strict. Re-probed 07:52Z: still 17.3.0, modified 2026-09-04. Control @object-ui/components = 17.6.0 from the same query shape, so the registry was genuinely read.
    the fourteen upstream cards' consequences each card filed → developed → merged → published. ⛔ Merged unblocks nothing here; this repo consumes published pins.
    objectstack#15951 (5a) and #15952 / #15953 / #1595's port half behind it contract-review capacity. The one item genuinely waiting on the maintainer.
    step 4 (objectstack#15936) triage. Ping still deliberately unspent — #1582 is gated on #1581 first, so it is not on the critical path.
    step 6 frozen by ruling.

    ⛔ I am not manufacturing work to fill that. But the itemisation above is now the standard this epic holds itself to before the word "blocked" is used at all — ⭐ name the artifact each piece is waiting for, and check whether every item on the card actually needs it. That test is what produced #1769 and #1770 out of a day I had reported as fully release-gated.


    Generated by Claude Code

  13. os-steve commented on Sep 8, 2026

    @os-steve
    Collaborator

    ⭐ The sweep found a security-shaped guard that has never once run. #1770 closed, #1772 dispatched.

    Epic PM session_01DuzfS5chho38Yx1jxx9DEj, 08:0xZ. #1770 accepted and closed (report 5581335470, acceptance 5581402301).

    The finding

    test/authorization-coverage.test.ts:327 — "a masked (unreadable) field is never filtered or sorted on by a view" — reads node.filters. That key is on 0 of 103 walked nodes. Paired legs, same test, same run: masked field in sort → RED; the same field in filter, the exact case the assertion names → GREEN, 33 passed.

    ⚠️ Verified on origin/main before banking it, and it is worse than the report framed it. filters appears 0 times in src/views/ + src/pages/ (filter 39, sort 42), and @objectstack/spec's view and page node schemas have no filters key at all. Where filters does appear in the spec is in the alias tables — chart.zod.ts:436 filters: 'filter', app.zod.ts:1066, time-relative-trigger.zod.ts:102, and analytics.zod.ts:384's "filters is not an AnalyticsQuery field."

    ⇒ ⭐ The author wrote the exact wrong-but-natural spelling the protocol anticipates and normalises away — and because the test reads the authored object rather than the normalised one, the alias never rescues it. Not a stale key: never right, on any conformant app, for as long as the assertion existed. The assertion's own comment says a masked field in a filter throws field_predicate_denied and "breaks the whole list."

    ⭐ This is the argument for why the sweep was worth running. Not a lint duplicate, not a platform gap, not release-gated — a guard in this repo's own farm reporting success without looking, in the file whose subject is authorization.

    The method, which is the other half of the result

    ⛔ Not a grep. A vite transform parsed every file under test/ and scripts/ with @babel/parser and wrapped every for…of / .forEach / .every / .some in a counting wrapper: 1197 walker sites, 1140 executed, 11 inspecting 0 on every execution, 1129 live.

    ⭐ And the instrument proved itself first: the instrumented suite reproduced the clean baseline exactly (164 files / 3438 passed / 1 skipped, chunk by chunk). This epic has been misled six times by an instrument that reported the absence of something it had itself removed. That control is what makes every "size 0" above mean anything.

    ⭐ My own 62-file proxy did not reproduce — 48 of 150 under the dev's stated definition at the same SHA — and the dev reported the delta rather than forcing a match. Correct: the proxy was mine, coarse by construction, and the card's own point was that the grep is not the measurement. ⛔ Neither number is a result.

    #1772 — dispatched, needs no release

    Three assertions made to inspect what they claim: item 1 above; flow-record-change.test.ts:259, whose anti-vacuity guard expect(notifications.length + crm_task.length).toBeGreaterThan(0) is ⭐ disjunctive and so is satisfied by the notification while the task walk it appears to cover inspects nothing — the failure an author reaches for while already thinking about vacuity; and an it.each row in docs-setup-navigation-names.test.ts making zero expect() calls.

    ⛔ Adds no test and no assertion. ⚠️ And the card is explicit that if item 1 goes red on real metadata, that is the finding — ⛔ never weaken the assertion to get back to green, since the defect being repaired is exactly an assertion that was green for the wrong reason.

    ⛔ Left alone deliberately: analytics-integrity.test.ts:120 and action-references.test.ts:268 (both proved to red when given a case ⇒ guards on an empty population, not defects) · flow-decision-authority.test.ts:180 (self-declared and compensated in-file).

    ⚠️ Close-out notes, ⛔ not cards

    • crm_contact.label is pinned by nothing. 'Contact' → 'Kontakt' left all 164 files green, despite crm_contact being sampled by 75 walker sites across 30 files. ⭐ The dev correctly recorded this as UNMEASURED with respect to those walkers rather than as vacuity — the injection is out of their class, and collapsing the two would have manufactured a finding. ⛔ No card: writing a test to pin a label grows the farm from the other end, and the label is translated, so the i18n gate owns that question if anyone does.
    • All 44 walker sites under scripts/ are unmeasured — they run under hygiene / hygiene:tokens / lint:i18n-gate and tsx entry points, none instrumented. ⛔ No rate extrapolated onto them; the boundary stays exactly where Vacuity sweep of the test farm — which walking assertions actually inspect a non-empty set? (epic #1579, read-only, needs no release) #1770 left it.

    Tree

    step card state
    1 · 2a · 2c objectstack#15935 · #1596 · #1604 landed
    2b #1581 blocked — CLI still 17.3.0 (07:52Z, control fired)
    3 surveys #1613 · #1621 · #1637 closed; 97/97 rows measured
    3 retirement #1625 / PR #1629 landed f598320c
    3 remainder #1582–#1587 blocked on #1581 / the fourteen upstream cards
    5 (deps) #1769 / PR #1771 landed 5b2ecdd
    5a objectstack#15951 holding on contract-review capacity
    — #1770 closed — 1197 sites, 11 vacuous
    — #1772 dispatched — needs no release
    6 docs↔metadata drift frozen

    Generated by Claude Code

  14. os-steve commented on Sep 8, 2026

    @os-steve
    Collaborator

    #1772 landed. ⭐ And the "is anything actually unblocked" test came back no this round — with the checks that say so.

    Epic PM session_01DuzfS5chho38Yx1jxx9DEj, 08:5xZ.

    PR #1774 merged 08:22:42Z → a02d4c1 on main, verified from main's own log against five distinct siblings (#1771, #1762, #1761, #1760, #1757). pm:dispatched stripped from #1772, pm:epic kept.

    Re-verified ON main, ⛔ not on the PR

    check on origin/main
    the FLS guard reads the singular key line 340: for (const f of node.filter ?? [])
    the disjunctive guard is gone 2 × expect(h.notifications.length, …); 0 non-comment uses of crm_task.length inside an expect
    the silencing continue is gone 0 non-comment continue; in the it.each block; .toBe(exempt) present
    nothing under src/ moved git diff --stat 5b2ecdd origin/main -- src/ empty
    #1769's lock invariant survived a second merge packages: hash still 95bfe9b47789dda033387104270392f014c6fb47

    ⚠️ Two greps came back 1 and I checked rather than reported them. Both are the old code quoted inside the new docblocks (flow-record-change.test.ts:242, docs-setup-navigation-names.test.ts:836) — the comments that explain what was wrong. ⭐ A count is not a verdict until you have looked at the line; that is the same reflex this whole round has been about.

    ⭐ The "is anything actually unblocked" test — run properly, answer no

    I have now told this epic three times to itemise before saying "blocked", so here is the itemisation with the two candidates actually chased down rather than waved past:

    ⚠️ But the check did turn up a stale card, and I fixed it

    #1581's body still said 59 warnings. Measured on 5b2ecdd: errors:0 warnings:1 suggestions:12, exit 0.

    family 2026-09-05 2026-09-08 why
    flow-loop-body-uncontained 42 0 #1604 / PR #1611
    component-props-invalid 16 0 #1653 / PR #1656
    component-props-unknown-key 1 1 #1216

    ⇒ ⭐ After the CLI release, #1216 is the only remaining internal blocker on #1581. Worth knowing before the release lands rather than after — but ⛔ #1216 is another card and this epic does not dispatch it. #1581's body now carries the corrected table, the ⛔ never-re-quote-59 marker, and an instruction to re-derive the count on the flipper's own base SHA rather than trust that table either. ⭐ Same lesson that bit me on #1769 twelve hours after I recorded it: a number on a card is stale from the moment it is written.

    Genuinely waiting, itemised

    piece artifact it needs
    #1581 → the deletion half of #1582–#1587 published @objectstack/cli > 17.3.0 carrying --strict. 08:53Z: still 17.3.0, modified 2026-09-04; control @object-ui/components 17.6.0 same query shape ⇒ the registry was read
    the fourteen upstream cards' consequences each filed → developed → merged → published
    objectstack#15951 (5a) → #15952 / #15953 / #1595's port half contract-review capacity
    step 4 (objectstack#15936) triage; ping unspent by design
    step 6 frozen by ruling

    Tree

    step card state
    1 · 2a · 2c objectstack#15935 · #1596 · #1604 landed
    2b #1581 blocked on the release; ⭐ body corrected, 59 → 1
    3 surveys #1613 · #1621 · #1637 closed; 97/97 measured
    3 retirement #1625 / PR #1629 landed f598320c
    3 remainder #1582–#1587 blocked on #1581 / the fourteen upstream cards
    5 (deps) #1769 / PR #1771 landed 5b2ecdd
    5a objectstack#15951 holding on contract-review capacity
    — #1770 closed — 1197 walker sites, 11 vacuous
    — #1772 / PR #1774 landed a02d4c1
    6 docs↔metadata drift frozen

    ⛔ Not this epic's, filed for their owners and not chased: #1773 · #1775 · objectstack#16813.


    Generated by Claude Code

  15. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    Contributor

    repo:hotcrm seat, session_01X8U3asekbiC7yWoEPWR4Dg · lane observation (comment only; this subtree is the epic PM's) · 2026-09-25T02:21Z

    ⚠️ This epic reads as idle — the maintainer has been asked whether to re-summon its PM or shrink the subtree

    Pending the maintainer's call: re-summon /pm-dispatch epic:#1579, or shrink/park the remaining children. The lane seat does not act inside this subtree.


    Generated by Claude Code

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

    @objectstack-fleet
    Contributor

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

    repo:hotcrm seat, session_012zh91QzFgePbkmuHnugLN3. The maintainer answered 「同意」 in this session's chat to the batch presented there with the recommendations 1A 2A 3A 4B. It also answers this seat's question of 2026-09-25 (5825624825).

    The decision: B. Shrink: dissolve this epic and finish its two real cards as ordinary repo:hotcrm lane cards. The fallback was A, re-summon /pm-dispatch epic:#1579; C was to park or close everything.

    Readings this ruling stands on (hotcrm origin/main c967803, @objectstack/cli 17.7.0)

    Where every child goes

    The epic PM session session_01DuzfS5chho38Yx1jxx9DEj has produced nothing on this subtree since 2026-09-09. Its territory is released with this close. Closed completed; pm:epic is removed from this card and from every child in the same round.


    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

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions