Repository navigation
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
Activity
- addedpm:dispatchedDispatched to a dev agent by /pm-dispatchDispatched to a dev agent by /pm-dispatch
on Sep 5, 2026 Claim: PM loop round — director seat summon #15, maintainer direct dispatch (verbatim 「普查卡同意开,你现在就派发处理」, 2026-09-05, director chat); the
repo:hotcrmseat #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; nodomain:*)
File surface: none written — readstest/**,scripts/**,src/**,content/docs/**,node_modules/@objectstack/*(pinned 17.3.0) and the sibling objectstack tree atorigin/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 classifyscan-field-consumers.ts/field-consumer-scan.test.tsas "platform-lint candidate (already ruled F on #1543, upstream objectstack#15922)" regardless of whether that PR has landed; none otherwise.
Generated by Claude Code
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-1579fromorigin/main@b1599507
Mode:mode:subagent
Scope: READ-ONLY census. No file undertest/,scripts/,src/,content/is edited, deleted or moved; no upstream cards filed.
Sibling platform tree/home/user/objectstackread viagit show origin/main:PATHonly (never its working tree).
Deliverable: the full classification table posted as a comment here, then theos-dev-reportJSON.
Generated by Claude Code
Census: 169
test/+ 15scripts/files classified against the 2026-08-31 rulingMeasured on
origin/mainb15995073eca00f123a7a3e5d109c4d626d067cc(b1599507), read-only from a dedicated worktree. Line counts arewc -lovergit show origin/main:PATHblobs, so nothing here depends on my working tree. Nothing undertest/,scripts/,src/orcontent/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
- Family sizes — confirmed, once the definition is stated. By file-name prefix alone
test/docs-*is 26 files / 10,048 lines andtest/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) andtest/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. - 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.tsis 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). - 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.tsis not — it holds this app's retired product names. - The three gates — classified under the same three classes.
check-source-hygiene.mjs(675) andcheck-lint-i18n-gate.mjs(158) are platform-lint candidates outright; the i18n gate's own header states its fix is apackages/lintseverity 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-
describeline 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.ts866test/object-validation-predicates.test.ts791test/readonly-write-semantics.test.ts773business-fact pin (pure) test/hooks-runtime-service.test.ts1503test/flow-scheduled.test.ts1502test/quote-discount-ceiling.test.ts898mixed test/sharing-coverage.test.ts1309test/metadata-references.test.ts1288test/flow-variable-conditions.test.ts1272delete scripts/wow1-live-schema.sh94— — Counting mixed files into platform, the three largest platform-touching files are
test/sharing-coverage.test.ts(1309),test/metadata-references.test.ts(1288) andtest/flow-variable-conditions.test.ts(1272).The single
deleterow, and why there is only onescripts/wow1-live-schema.sh(94) is wired to nopackage.jsonscript 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.0rule set innode_modules(≈250 rule ids) and the@objectstack/cli@17.3.0command 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.tsflow-inert-node-condition(the card's example),flow-decision-unconditional-branch,flow-multiple-default-edges,flow-default-edge-with-conditiontest/view-tab-label-inert.test.tsliveness-dead-property,liveness-live-elsewhere-propertytest/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-rostertest/view-references.test.ts(group A)list-view-field-unknown,sort-field-unknown,sort-field-unprovisioned,searchable-field-unknown,filter-token-unknown,view-key-collisiontest/action-references.test.tsnav-target-unresolved,nav-object-unservable,nav-object-ungranted,dashboard-action-route-unresolved,dashboard-action-target-undefined,action-name-undefined,action-no-placementtest/app-navigation-shape.test.tsnav-target-unresolved,view-key-collisiontest/analytics-integrity.test.tschart-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-unknowntest/dataset-granularity.test.tsdataset-include-unknown,dataset-field-not-included,measure-aggregate-incoherenttest/field-groups-coverage.test.tsfield-group-undeclared,field-group-empty,field-group-shadowedtest/i18n-references.test.tstranslation-target-unknown,translation-option-key-unknown,translation-section-name-missing, plus the wholeos i18n checkcommandscripts/check-lint-i18n-gate.mjs,test/lint-i18n-gate.test.tsthe same i18n/missing-*family — this gate exists only to change its severity, which its own header says is apackages/lintchangetest/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-budgettest/sharing-seeding.test.tssharing-rule-object-not-shareable,sharing-rule-object-controlled-by-parent,sharing-rule-unlowerable-condition,sharing-rule-runtime-variable-conditiontest/parent-derived-reach.test.tssecurity-controlled-by-parent-no-relation,security-controlled-by-parent-ambiguous-relationtest/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 therecord.*layer cannot get an error-level path gate yettest/readonly-write-semantics.test.tsflow-update-readonly-field,flow-update-readonly-when-field,hook-api-update-readonly-field,hook-api-update-readonly-when-field,action-api-update-readonly-when-fieldtest/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-discardedtest/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-unfilteredtest/flow-filter-today-token.test.ts(group A)filter-token-unknown,flow-time-relative-descriptor-invalid,flow-time-relative-antipattern,flow-date-equality-filtertest/skills-integrity.test.tsai-skill-tool-unresolved,ai-skill-surface-mismatch,capability-reference-unknowntest/detail-section-dedup.test.tspage-section-group-unknown,form-section-group-unknowntest/placeholder-picklist-options.test.tsseed-value-outside-state-machineis the nearest shipped neighbour; the placeholder-label smell itself is unshippedtest/smoke.test.tsos validate— every invariant it asserts (manifest present, no retiredworkflows[], notify severities legal) is a validate-time structural checktest/ownership-model.test.ts(group A)security-anchor-high-privilege,org-axis-permission-inheritancetest/hook-org-inheritance.test.tsorg-axis-permission-inheritance,org-axis-cross-org-bu-granttest/docs-runnable-samples.test.ts(group A)default-agent-outside-roster,default-agent-legacy-aliastest/case-number-tenant-scope.test.ts(group B),test/account-name-tenant-scope.test.ts(group B)autonumber-references-unknown-field,autonumber-references-selfare adjacent; the NULL-safe per-org unique index materialization they assert isdriver-sqlbehaviour with no lint rule and no upstream cardTree-walking files — measured, not recalled
The card's grep (
readdirSync,glob,fast-glob,walk(,fs.readdir) returns 71 files, but the raw termglobis a false positive in every file where it is the only hit — it matchesglobal,globalThis,globallyandglobex.example.com. Nogloborfast-globdependency is declared inpackage.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.tsalso shells out viaexecSync) — 32 undertest/, 2 underscripts/. - 22 more files walk an in-memory OBJECT GRAPH via a local
walk()generator over the already-imported stack (test/helpers/metadata-fixtures.tsexportswalk(), 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
~40sits between the two measurements; the tree-walking number is 34.file lines class scripts/check-source-hygiene.mjs675 platform-lint candidate scripts/check-source-token-ratchet.mjs567 mixed test/actions-flows-integrity.test.ts468 mixed test/collaboration-capabilities.test.ts175 mixed test/deal-threshold-parity.test.ts398 business-fact pin test/docs-analytics-vocabulary.test.ts428 mixed test/docs-app-workflow-paths.test.ts213 platform-lint candidate test/docs-conversion-rate-spelling.test.ts122 business-fact pin test/docs-dashboard-tiles.test.ts546 mixed test/docs-drift.test.ts437 mixed test/docs-locale-callouts.test.ts136 platform-lint candidate test/docs-metadata-counts.test.ts293 platform-lint candidate test/docs-object-term-consistency.test.ts556 business-fact pin test/docs-retired-personas.test.ts189 business-fact pin test/docs-revenue-approvals-navigation.test.ts622 mixed test/docs-role-hierarchy.test.ts310 business-fact pin test/docs-runnable-samples.test.ts207 mixed test/docs-setup-navigation-names.test.ts743 mixed test/docs-src-tree-paths.test.ts388 platform-lint candidate test/docs-view-rosters.test.ts1095 mixed test/docs-zh-hant-justification.test.ts381 business-fact pin test/helpers/docs-anchors.ts286 platform-lint candidate test/hook-input-shape.test.ts472 platform-lint candidate test/hook-query-predicate.test.ts364 platform-lint candidate test/hook-write-shape.test.ts535 platform-lint candidate test/hot-lead-threshold-parity.test.ts319 business-fact pin test/labeler-config.test.ts152 platform-lint candidate test/lead-duplicate-management.test.ts569 business-fact pin test/line-item-conventions.test.ts256 mixed test/runtime-coverage.test.ts169 platform-lint candidate test/script-main-guard.test.ts343 platform-lint candidate test/sharing-posture-declaration.test.ts264 business-fact pin test/verify-log-decoy-pin.test.ts315 platform-lint candidate test/view-predicate-dialect.test.ts428 platform-lint candidate The 22 object-graph walkers (not repository-tree walkers)
file lines class scripts/analytics-reconcile/macros.ts117 platform-lint candidate scripts/lib/source-hygiene-surface.mjs195 platform-lint candidate scripts/scan-field-consumers.ts573 platform-lint candidate test/action-references.test.ts382 platform-lint candidate test/activity-seed-coverage.test.ts622 business-fact pin test/app-navigation-shape.test.ts155 platform-lint candidate test/authorization-coverage.test.ts750 mixed test/case-create-form-narrowing.test.ts331 business-fact pin test/demo-staffing.test.ts532 business-fact pin test/detail-section-dedup.test.ts150 platform-lint candidate test/docs-quick-tour-navigation.test.ts637 mixed test/docs-service-index-analytics.test.ts261 mixed test/flow-decision-authority.test.ts336 platform-lint candidate test/flow-variable-conditions.test.ts1272 mixed test/forecast-seeds.test.ts471 business-fact pin test/helpers/metadata-fixtures.ts171 platform-lint candidate test/i18n-references.test.ts866 platform-lint candidate test/live-work-predicate-parity.test.ts406 business-fact pin test/metadata-references.test.ts1288 mixed test/ownership-model.test.ts487 mixed test/persona-copy.test.ts173 business-fact pin test/view-references.test.ts720 mixed The classification table — every file under
test/andscripts/, one row per class219 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 cardis filled only for platform rows;none foundmeans a targetedsearch_issuesreturned no matching objectstack card, not that none should exist.file lines class rationale upstream card, if platform scripts/analytics-reconcile/macros.ts117 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.ts424 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.ts121 platform-lint candidate Boots a real kernel and injects the two executors — wiring for the same generic checker. none found scripts/backfill-owner-id.ts236 business-fact pin One-time HotCRM data migration for this app's own retired ownercolumn (#548), run against a live org.— scripts/check-lint-i18n-gate.mjs158 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 apackages/lintseverity change.objectstack#11617 (open, adjacent); no severity card yet scripts/check-source-hygiene.mjs675 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.ts343 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.mts18 platform-lint candidate Types for the shared isMainModule()helper — a script-kit primitive any repo needs.none found scripts/lib/main-module.mjs87 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.mjs195 platform-lint candidate Declares the scan surface for the hygiene gate — same class as the gate it feeds. none found scripts/publish-marketplace.mjs238 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.ts573 platform-lint candidate Field-liveness scan (declared-but-never-read) — already ruled F on #1543. objectstack#15922 (open) scripts/sync-docs-screenshots.mjs31 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.sh94 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.ts407 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.ts286 platform-lint candidate MDX anchor / heading-slug resolver mirroring fumadocs — generic docs tooling; shared by 1 test file. none found test/helpers/flow-harness.ts720 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.ts117 platform-lint candidate Heading-to-label reader, same class as docs-anchors; shared by 3 test files. none found test/helpers/hook-harness.ts618 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.ts171 platform-lint candidate Walkers and fixtures over the compiled stack — generic metadata traversal; shared by 18 test files. none found test/helpers/persona-vocabulary.ts62 business-fact pin The retired copilot persona spellings are HotCRM's own retired product names; shared by 2 test files. — test/helpers/repo-root.ts15 platform-lint candidate Trivial repo-root resolver; shared by 52 test files. none found test/helpers/tenancy-probe.ts86 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 fortype: 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.ts128 business-fact pin #1181 retired the account-level renewal model; renewal stays a contract-level process — a HotCRM product decision. — test/action-references.test.ts382 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-undefinedtest/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-unparseabletest/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-unscopedtest/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.ts377 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.ts622 business-fact pin Pins this app's seed rows and that every Sales Activity widget returns a number over them. — test/analytics-integrity.test.ts259 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-comparandtest/app-navigation-shape.test.ts155 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-collisiontest/attendee-type-resolution.test.ts495 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-readscopetest/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.ts234 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-undefinedtest/campaign-member-cascade.test.ts276 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.ts492 business-fact pin Campaign-member lifecycle, opt-out sync and live metric recompute are HotCRM behaviour. — test/cascade-guard-messages.test.ts314 business-fact pin The exact refusal wording HotCRM ships, including singular/plural agreement. — test/case-assignment.test.ts897 business-fact pin HotCRM's case routing pools, escalation reassignment and the guest-strip ordering. — test/case-create-form-narrowing.test.ts331 business-fact pin Which fields are creator-legitimate on this app's case intake form. — test/case-first-response.test.ts241 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.ts309 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.ts290 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.ts332 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.ts163 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_reportsonly 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.ts204 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.ts224 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-incoherenttest/deal-threshold-parity.test.ts398 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.ts254 business-fact pin #1182's removal verdicts and the account-hierarchy rollup that was kept because it gained a consumer. — test/demo-staffing.test.ts532 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.ts150 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-unknowntest/do-not-call-enforcement.test.ts322 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.ts288 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.ts213 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.ts122 business-fact pin The docs' uniqueness wording equals what this app enforces, verified against the shipped hook. — test/docs-conversion-rate-spelling.test.ts122 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.ts136 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.ts293 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.ts279 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.ts556 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.ts399 business-fact pin The README headline token figures and the ruled 5% buffer are HotCRM's positioning claim. — test/docs-retired-personas.test.ts189 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.ts310 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-rostertest/docs-runnable-samples.test.ts(group B)207 business-fact pin The action example teaching input._selectedIdsand 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.ts264 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.ts388 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.ts381 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.ts192 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.ts490 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.ts318 platform-lint candidate Tests the field-liveness scanner — already ruled F on #1543. objectstack#15922 (open) test/field-groups-coverage.test.ts180 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-shadowedtest/flow-billing-handoff.test.ts372 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.ts189 business-fact pin Campaign enrollment eligibility, top-up and the lead/contact branch exclusivity. — test/flow-case-actions.test.ts201 business-fact pin escalate_case, its elevated stamping subflow and close_case — HotCRM screen-action outcomes. — test/flow-cold-boot-rebind.test.ts134 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.ts650 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-conditionis the shipped neighbour ruletest/flow-conversion.test.ts129 business-fact pin lead_conversion creates or reuses account, contact and opportunity — a HotCRM outcome. — test/flow-decision-authority.test.ts336 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-branchandflow-multiple-default-edgesrulestest/flow-escalation-ownerless-case.test.ts205 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-unknowntest/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.ts76 business-fact pin schedule_followup binds the task through both polymorphic halves and stamps next_followup_date. — test/flow-harness-declared-columns.test.ts542 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.ts73 business-fact pin quote_generation prices the quote and advances the stage — a HotCRM outcome. — test/flow-record-change.test.ts578 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.ts186 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-unscopedtest/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.ts1502 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.ts309 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.ts226 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-variableare the shipped neighbourstest/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.ts337 business-fact pin The #1082 manual-override stand-down and its way out — HotCRM forecast policy. — test/forecast-period-boundary.test.ts420 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.ts697 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.ts364 business-fact pin Every forecast_metrics consumer pins a single period (#614) — this app's analytics rule. — test/forecast-seeds.test.ts471 business-fact pin The forecast seed rows: calendar-true periods, seeder-only identity, cumulative buckets. — test/freeze-guard-reference-cleanup.test.ts810 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.ts661 business-fact pin The activity actions' targets, attendee rows, record_label resolution and console submittability — HotCRM action behaviour. — test/guest-submission-sanitisation.test.ts314 business-fact pin Which internal fields the guest branch strips on crm_case and crm_lead. — test/harness-lookup-shape.test.ts325 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.ts275 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.ts472 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.ts297 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-inheritancetest/hook-query-predicate.test.ts364 platform-lint candidate ctx.api's where/filteralias semantics against the real kernel, with negative controls, plus a scan that no hook queries byfilter.none found test/hook-write-shape.test.ts535 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-fieldtest/hooks-runtime-sales.test.ts824 business-fact pin The sales hooks' business behaviour (opportunity lifecycle, quote workflow, account protection, contact integrity, product catalog). — test/hooks-runtime-service.test.ts1503 business-fact pin The service, marketing and forecast hooks' business behaviour on the real harness. — test/hooks-runtime.test.ts244 business-fact pin Rollups, the stage-age clock, price fill and lead auto-assign — HotCRM hook outcomes. — test/hot-lead-threshold-parity.test.ts319 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.ts866 platform-lint candidate Locale-pack completeness and key resolution across every authored surface — exactly what os i18n checkand thetranslation-*lint rules cover.objectstack#11617 (open, the flow/screen i18n bucket); translation-target-unknown,translation-option-key-unknown,translation-section-name-missingtest/i18n-shared-widget-parity.test.ts212 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-unknowntest/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.ts387 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.ts386 business-fact pin The case-to-article link, the deflection rate and the dashboard widgets bound to it. — test/knowledge-feedback.test.ts317 business-fact pin view_count retired in favour of real feedback rows, and the counters recount from them. — test/labeler-config.test.ts152 platform-lint candidate Every .github/labeler.ymlglob matches at least one file and names an existing label — CI-config drift, generic to any repo.none found test/lead-disqualification.test.ts136 business-fact pin Disqualification reason enforced as a rule, and the seeded unqualified leads satisfying it. — test/lead-duplicate-link-cleanup.test.ts630 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.ts569 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.ts573 business-fact pin The suspected-duplicate banner, the conversion-time warning and the #1288 confirmed-duplicate refusal. — test/line-item-cascade.test.ts303 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.ts194 platform-lint candidate Tests the i18n gate script against synthetic fixtures — same class as the gate. same as check-lint-i18n-gate.mjstest/live-work-predicate-parity.test.ts406 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-rostertest/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.ts791 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.ts111 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-privilegetest/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.ts353 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-relationtest/persona-copy.test.ts173 business-fact pin No authored HotCRM string names a retired copilot persona (#1003). — test/placeholder-picklist-options.test.ts136 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.ts126 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.ts216 business-fact pin The placeholder defaults the drafted contract carries, and the provenance sentence the ruling kept. — test/quote-accepted-lookups.test.ts337 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.ts264 business-fact pin Negotiated payment terms carry from quote to contract across the shared vocabulary. — test/quote-contact-required-when.test.ts462 business-fact pin A quote needs a contact from presentedonward — this app's gate, enforced through two real drivers.— test/quote-discount-ceiling.test.ts898 business-fact pin The discount ceiling constant, its invariant reach and the line-item half — HotCRM's own number. — test/readonly-write-semantics.test.ts773 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-fieldtest/record-id-not-in-prose.test.ts387 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.ts169 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.ts397 business-fact pin The two declared compositions, what each replays, and tenant_admin's org-scoped capability — HotCRM packaging. — test/script-main-guard.test.ts343 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.ts589 business-fact pin The seed datasets' internal coherence against the hooks that would recompute them. — test/seed-validation-warnings.test.ts264 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.ts264 business-fact pin This app's declared sharing posture, pinned against its own source. — test/sharing-seeding.test.ts641 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-conditiontest/skills-integrity.test.ts233 platform-lint candidate Skill tool references and cross-references resolve. none found — maps to ai-skill-tool-unresolved,ai-skill-surface-mismatchtest/sla-at-risk-live-work.test.ts229 business-fact pin The SLA at Risk view still selects on the live-work predicate — this app's view. — test/sla-compliance-gauge.test.ts367 business-fact pin The SLA gauge is bound to a compliance measure and reads 100% on the seeded demo org (#1213). — test/smoke.test.ts83 platform-lint candidate Structural invariants of the compiled bundle (manifest present, no workflows[], notify severities valid) — exactly whatos validateproves.none found test/source-hygiene-header-position.test.ts274 platform-lint candidate Tests the copyright-header position check of the hygiene gate — same class as the gate. same as check-source-hygiene.mjstest/source-hygiene-scan-surface.test.ts463 platform-lint candidate Tests the hygiene gate's scan surface, including control bytes in root text files (#838). same as check-source-hygiene.mjstest/source-hygiene-size-advisory.test.ts270 platform-lint candidate Tests the hygiene gate's file-size advisory band. same as check-source-hygiene.mjstest/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.ts271 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.ts291 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.ts383 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.ts863 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.ts345 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.ts315 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.ts428 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-unresolvedtest/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-unknowntest/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.ts144 platform-lint candidate list.tabs[]is absent rather than merely label-free (#1307) — an inert-declaration check.none found — maps to liveness-dead-propertytest/win-loss-capture.test.ts857 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_issuescalls, one per platform-candidate family, all againstrepo: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, therecord.*path-resolution gate). The i18n query returned objectstack#11617 (open, "hotcrm: the six screen-flow dialogs become visible toos lintonce 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/verifyis not a package in the pinnednode_modules(the 51 installed@objectstack/*packages are all 17.3.0 and none isverify). It exists aspackages/verifyin the objectstack tree and is reachable as the CLI commandos 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
- Family sizes — confirmed, once the definition is stated. By file-name prefix alone
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
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/mainb1599507; its four disjoint buckets sum to 184 files / 73,296 lines = the card's owntest/69,419 +scripts/3,877; no file undertest/,scripts/,src/,content/changed (branchclaude/issue-1579-test-farm-censuscarries zero commits beyond base); no upstream card filed. The PM's "~40 tree-walkers" is corrected to 34 with the method stated (theglobgrep 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-harness40 个引用、repo-root52、flow-harness20、metadata-fixtures18、action-sandbox17)。平台侧:@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
- addedneeds-user-decisionNeeds the maintainer's call before work proceedsNeeds the maintainer's call before work proceedsand removedpm:dispatchedDispatched to a dev agent by /pm-dispatchDispatched to a dev agent by /pm-dispatch
on Sep 5, 2026 - added sub-issues
on Sep 5, 2026 39 remaining items
⭐ 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. Baseorigin/maine5cedc7.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-bysits 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/*packagestest/imports andpackage.jsondoes notSplit out of #1595 item 4. #1595 is
Blocked-by: objectstack#15951and keeps that block for its harness port; this half never needed the handle, the release, or--strict.Re-measured on
e5cedc7rather than carried:test/+scripts/import 19@objectstack/*packages,package.jsondeclares 12, the gap is 9.⚠️ #1595's body lists 7 — it was written 09-05 andservice-storageandtrigger-record-changehave 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.0andpnpm-lock.yamlalready resolves all nine at17.3.0, so declaring them at exact17.3.0(this repo's convention — no^on any@objectstack/*) moves nothing. Thepackages: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-authis one of the nine, and it is the package at the centre of objectstack#16186: its published17.3.0declares thebetter-authfamily at^1.7.2, and a patch release of@better-auth/coreremoved a public./dbexport. A caret cannot protect against that. The exact pin landed in objectstackmain(PR #16634, merged 09-07T23:08Z, withscripts/check-vendor-export-contract.mjsas 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 apnpm install --forcewould throw away. So the card's second acceptance line is that@better-auth/corestill resolves to1.7.2after 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 widgetactionUrl#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
- npm:
@objectstack/cli·lint·verify·specall still 17.3.0,modified 2026-09-04. Turn onos lint --strictin this repo's verify chain and prove the gate reds (epic #1579, step 2b — the half that needs a release) #1581 and Retire the local tests that re-implement the platform's flow / predicate / readonly-write lint rules, onceos lint --strictguards them (epic #1579, step 3, family F1) #1582–Retire the local hook / action / flow write-shape tests the platform's *-body-write-* and flow-node-write rules already enforce (epic #1579, step 3, family F6) #1587 stay blocked; the release predicate for--strictis unmoved. - objectstack#15951 (step 5a):
pm:epiconly, no dispatch. ⛔ Holding on Fable/subagent capacity after threeHTTP 429s —Clause-②binds it to the contract-review tier and ⛔ the tier is not negotiable to get it moving. This is the one item where the block is real and is on the work, not the card. - objectstack#15936 (step 4): triage, one ping still deliberately unspent — it is not on the critical path.
Tree
step card state 1 · 2a · 2c objectstack#15935 · #1596 · #1604 landed 2da2901e·8223d0a·9924ee1a2b #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 lines3 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
- npm:
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 →
5b2ecddonmain. Verified frommain's own log against five distinct sibling commits (#1762, #1761, #1760, #1757, #1756), ⛔ not from the API'smerged: true.pm:dispatchedstripped from #1769,pm:epickept.Re-checked on
mainafter the merge, ⛔ not carried from the PRcheck on origin/mainthe nine declared 9 / 9, all exact 17.3.0, none missingthe card's comm -23gap checkempty — every @objectstack/*thattest/orscripts/imports is now declaredpackages:section hash95bfe9b47789dda033387104270392f014c6fb47— identical to the pre-merge basee5cedc7⭐ 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 "
maindid not move the lock's resolution" are different claims and only the second one protects anything. The pin that keeps@better-auth/coreat1.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
⭐ 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 (report5581335470, acceptance5581402301).The finding
test/authorization-coverage.test.ts:327— "a masked (unreadable) field is never filtered or sorted on by a view" — readsnode.filters. That key is on 0 of 103 walked nodes. Paired legs, same test, same run: masked field insort→ RED; the same field infilter, the exact case the assertion names → GREEN, 33 passed.⚠️ Verified onorigin/mainbefore banking it, and it is worse than the report framed it.filtersappears 0 times insrc/views/+src/pages/(filter39,sort42), and@objectstack/spec's view and page node schemas have nofilterskey at all. Wherefiltersdoes appear in the spec is in the alias tables —chart.zod.ts:436filters: 'filter',app.zod.ts:1066,time-relative-trigger.zod.ts:102, andanalytics.zod.ts:384's "filtersis 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_deniedand "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/andscripts/with@babel/parserand wrapped everyfor…of/.forEach/.every/.somein 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 guardexpect(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 anit.eachrow indocs-setup-navigation-names.test.tsmaking zeroexpect()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:120andaction-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 cardscrm_contact.labelis pinned by nothing.'Contact'→'Kontakt'left all 164 files green, despitecrm_contactbeing 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 underhygiene/hygiene:tokens/lint:i18n-gateandtsxentry 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 f598320c3 remainder #1582–#1587 blocked on #1581 / the fourteen upstream cards 5 (deps) #1769 / PR #1771 landed 5b2ecdd5a 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
#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 →
a02d4c1onmain, verified frommain's own log against five distinct siblings (#1771, #1762, #1761, #1760, #1757).pm:dispatchedstripped from #1772,pm:epickept.Re-verified ON
main, ⛔ not on the PRcheck on origin/mainthe 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 ofcrm_task.lengthinside anexpectthe silencing continueis gone0 non-comment continue;in theit.eachblock;.toBe(exempt)presentnothing under src/movedgit diff --stat 5b2ecdd origin/main -- src/empty#1769's lock invariant survived a second merge packages:hash still95bfe9b47789dda033387104270392f014c6fb47⚠️ Two greps came back1and 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:
scripts/wow1-live-schema.sh— the census's one deletable file, and the obvious candidate for release-free work. ⛔ Already refused on Turn onos lint --strictin this repo's verify chain and prove the gate reds (epic #1579, step 2b — the half that needs a release) #1581, and the refusal holds.git grep wow1finds six published documentation pages across three locales instructing readers to run it. "Wired to nothing" and "referenced by nothing" are different claims. Re-confirmed onmaintoday: the file is still there and this stays a docs decision with its own card, ⛔ never a rider. The check paid off by producing no card.- Retire the local tests that re-implement the platform's flow / predicate / readonly-write lint rules, once
os lint --strictguards them (epic #1579, step 3, family F1) #1582–Retire the local hook / action / flow write-shape tests the platform's *-body-write-* and flow-node-write rules already enforce (epic #1579, step 3, family F6) #1587's gate-1 halves — ⛔ nothing left: Survey part 3 — the 22 rows #1613/#1621 left unmeasured, so any remaining platform gap catches the same release train (epic #1579) #1637 measured all 97 rows through gates 1/2/3, and Vacuity sweep of the test farm — which walking assertions actually inspect a non-empty set? (epic #1579, read-only, needs no release) #1770 swept the remaining farm at the walker level. - Replace the hand-built hook / flow / action harnesses with
@objectstack/verify's in-process handle; delete the stand-ins and the suites that only prove the stand-ins; declare the platform packages tests import (epic #1579, step 5) #1595's port half — genuinely needs objectstack#15951 published and pinned. Its dependency-declaration item was the separable piece, and it landed as Declare the 9@objectstack/*packagestest/imports butpackage.jsondoes not (epic #1579, step 5 item 4 — needs no release) #1769. - Step 4 / step 6 — triage (not on the critical path, ping still unspent) and frozen by ruling.
⚠️ 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-uncontained42 0 #1604 / PR #1611 component-props-invalid16 0 #1653 / PR #1656 component-props-unknown-key1 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/components17.6.0 same query shape ⇒ the registry was readthe 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 f598320c3 remainder #1582–#1587 blocked on #1581 / the fourteen upstream cards 5 (deps) #1769 / PR #1771 landed 5b2ecdd5a objectstack#15951 holding on contract-review capacity — #1770 closed — 1197 walker sites, 11 vacuous — #1772 / PR #1774 landed a02d4c16 docs↔metadata drift frozen ⛔ Not this epic's, filed for their owners and not chased: #1773 · #1775 · objectstack#16813.
Generated by Claude Code
- added a commit that references this issue
on Sep 8, 2026 - added sub-issues
on Sep 9, 2026 objectstack-fleet commented
on Sep 25, 2026 ContributorMore actionsrepo:hotcrmseat,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- No claim, branch or PR in the subtree since 2026-09-09 (~14 days). Turn on
os lint --strictin this repo's verify chain and prove the gate reds (epic #1579, step 2b — the half that needs a release) #1581's assignee isos-steve, but its PR chore(deps): bump the @objectstack/* line to 17.4.0 — and whylint --strictmust NOT flip yet (#1581) #1806 closed unmerged. - The release gate is gone:
os lint --strictships in the installed@objectstack/cli@17.4.0. - On
087b7c5(per the batch-3 draft):linthas 0 errors and 7 warnings, solint --strictexits 1. Three of the seven arefield-no-consumersoncrm_account, added 2026-09-16 by feat(account): registration identifier, a capability gate, business profile and approval (REQ-0003) #1948 (epic Epic: split HotCRM into ADR-0130 packages — sales is the app, service / revenue / marketing are modules, a directory is a package #1904 Track A). The two epics currently pull against each other. - Children's upstream tables are stale: lint rules #16093/#16095/#16096/#16105/#16118/#16168/#16169 all ship in lint 17.4.0.
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
- No claim, branch or PR in the subtree since 2026-09-09 (~14 days). Turn on
- removedpm:epicParent delegated to a dedicated epic PM — other PMs never dispatch into its subtreeParent delegated to a dedicated epic PM — other PMs never dispatch into its subtree
on Oct 8, 2026 objectstack-fleet commented
on Oct 8, 2026 ContributorMore actionsRuling: batch hotcrm-R74 item 4 · letter B · maintainer 「同意」 2026-10-08T03:12Z
repo:hotcrmseat,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:hotcrmlane 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/mainc967803,@objectstack/cli17.7.0)os lint: 0 errors · 1 warning · 18 suggestions. The one warning iscomponent-props-unknown-keyonsrc/sales/pages/home.page.ts, where the "Ask the AI Assistant"page:cardcarries adescriptionthe component drops. The code comment there says "Filed as The Sales Home AI card's paragraph never reaches the screen:descriptionis not a proppage:carddeclares, and #1002's guard pins the copy there #1216", and The Sales Home AI card's paragraph never reaches the screen:descriptionis not a proppage:carddeclares, and #1002's guard pins the copy there #1216 now answers 404. So turning--stricton (Turn onos lint --strictin this repo's verify chain and prove the gate reds (epic #1579, step 2b — the half that needs a release) #1581) is one fix away.- The release gates are gone:
--stricthas shipped since cli 17.4.0.@objectstack/verify17.7.0 exports the in-process handle (hooks.run,actions.runin its.d.ts; a CHANGELOG entry); objectstack#15951 closed on 2026-09-10.- objectstack#15936 closed on 2026-10-06.
- The hand-built stand-ins are still on
main:test/helpers/{hook-harness,flow-harness,action-sandbox,metadata-fixtures,tenancy-probe}.ts= 641 + 720 + 427 + 171 + 86 = 2,045 lines, before their self-proof suites.
Where every child goes
- Turn on
os lint --strictin this repo's verify chain and prove the gate reds (epic #1579, step 2b — the half that needs a release) #1581 →pm:queue,repo:hotcrmlane, dispatched next. First the AI card's copy has to really reach the screen; that copy is held by the Home page still ships a card titled "Ask the Sales Copilot" — a retired persona in live UI metadata, outside every docs guard's scan surface #1002 persona guard, whose ruling is re-read first. Then strict goes intoverify. Theos-steveclaim is released on the card. - Replace the hand-built hook / flow / action harnesses with
@objectstack/verify's in-process handle; delete the stand-ins and the suites that only prove the stand-ins; declare the platform packages tests import (epic #1579, step 5) #1595 →pm:queue. ItsBlocked-by: objectstack-ai/objectstack#15951is closed and shipped in the pin. It goes serial after feat(picklists): the shared option lists becomepicklistmetadata —*.picklist.tsandField.select({ picklist })replace_picklists.ts(acceptance of objectstack#18164) #2000, because both touch tests. - Retire the local tests that re-implement the platform's flow / predicate / readonly-write lint rules, once
os lint --strictguards them (epic #1579, step 3, family F1) #1582 – Retire the local hook / action / flow write-shape tests the platform's *-body-write-* and flow-node-write rules already enforce (epic #1579, step 3, family F6) #1587 →pm:blockedonBlocked-by: #1581(already in each body). When Turn onos lint --strictin this repo's verify chain and prove the gate reds (epic #1579, step 2b — the half that needs a release) #1581 lands, they are re-graded once under the three-gate, per-case method (5555869319,5556310099). Rows that are still retirable fold into ONE card; the rest closenot_planned. - Give the six declared-but-unnamed fields the consumer they should already have — clear 6 of the 12
field-no-consumerswarnings blocking #1581 (epic #1579) #1826 closedcompletedunder item 1 of the same batch. - Not this lane's: objectstack#15952 and #15953 (steps 5b, 5c) stay on objectstack.
- Step 6 (docs↔metadata drift) stays frozen, with no card.
The epic PM session
session_01DuzfS5chho38Yx1jxx9DEjhas produced nothing on this subtree since 2026-09-09. Its territory is released with this close. Closedcompleted;pm:epicis removed from this card and from every child in the same round.
Generated by Claude Code
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 stackbootStackalready 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/mainb1599507, read-only)test/169 +scripts/15)Key finding: ~30 files re-implement rules
@objectstack/lint@17.3.0already ships (≈250 rules: 161 error / 119 warning / 11 advisory) becauseos lintexits 1 onerroronly — 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 (realAutomationEngine, real QuickJS runner, realwrapDeclarativeHook) and half fake (actx.apiover arrays, hand-sorted hook dispatch, no permission check); the platform's@objectstack/verifyalready 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)os lint --strict(warnings fail; default unchanged)verify, zero the 90 warnings, deletescripts/wow1-live-schema.shBlocked-bystep 1 published and pinned (install-surface probe on the card)Blocked-by#1581; delete only after an ablation proves the platform rule fires under strict; mixed files lose group A only@objectstack/verifyin-process handle (hooks.run,flows.run/resume,actions.run,validate,seed/rows,metadata,tenancyoption, shared boot)Clause-②: yesplugin-spec.mdxstops promising@objectstack/testing;create-objectstackblank template gets a test storyBlocked-by5averify(D) / platform regression tests (R) / keep (K)Blocked-by5ahook-harness/flow-harness/action-sandbox/metadata-fixtures/tenancy-probewith the handle; delete the stand-ins and their five self-proof suites (~4,069 lines); declare the platform packages tests importBlocked-by5a published and pinnedrepo-root/heading-label/docs-anchorshelpers)case-number-tenant-scope/account-name-tenant-scopegroup BBlocked-byon F5Rules for the epic PM
packages/specgo to the spec seat's queue withBlocked-by:back-links (none expected here).verifyhandle cannot express is likewise a platform gap (rule 2): file upstream, keep that one helper path, never re-grow a stand-in.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-devround (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).