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
Epic parent — reserved by pm:epic; the sub-issue tree is the queue for a dedicated epic PM session (/pm-dispatch epic:#1579), summoned by the maintainer. Domain seats do not take these cards. Director seat (objectstack#12708, summon #15) filed the structure on the maintainer's ruling; ⛔ the director seat does not dispatch.
Ruling
Maintainer, 2026-09-05, live director chat, verbatim: 「hotcrm#1579 是不是应该开一个专题卡处理啊」 → 「同意你的建议,你把卡片写好」, accepting the director seat's plan A′ as presented (comment 5552648775 carries the option analysis; the refined plan is below). Standing basis: AGENTS.md § "Scope — a pure metadata application (2026-08-31 ruling)", rule 3 — "Lint, validation, gates and diagnostics belong to the platform, uniformly … Tests in this repo pin this repo's own business facts and nothing else … ⛔ Do not grow a gate farm." Protocol baseline (maintainer 2026-09-05): 「本项目以协议为基准。所以开发应该对其协议,协议有问题应该立卡修改协议」.
Step 5 ruling (2026-09-05, later the same session, verbatim, in order): 「新增平台公开面」 → 「我认为平台的能力应该放在平台,但是平台该怎么设计提供这个能力,你需要完整的重新考虑」 → 「是不是 把执行能力并进 @objectstack/verify / os test 更合理?」 → design B′ (below) → 「同意」 → 「应该还是刚才 hotcrm 专题卡的子卡片吧」. Design B′: the execution capability goes into the already-published @objectstack/verify (ADR-0054 lineage) as an in-process handle on the stack bootStack already boots — real engine, zero re-implemented semantics; no new package; os test (HTTP JSON suites) untouched. Full reasoning: the ruling comment on this card.
Census (comment 5552607309, origin/main b1599507, read-only)
| bucket | files | lines |
|---|---|---|
| pure business-fact pin (stay) | 86 | 33,166 |
| pure platform-lint candidate | 62 | 19,614 |
| mixed (one row per class) | 35 | 20,422 |
| delete | 1 | 94 |
total (test/ 169 + scripts/ 15) |
184 | 73,296 |
Key finding: ~30 files re-implement rules @objectstack/lint@17.3.0 already ships (≈250 rules: 161 error / 119 warning / 11 advisory) because os lint exits 1 on error only — this repo reads 90 warnings, exit 0. The remedy is relocation to the platform, not deletion; exactly one file is deletable outright. Second finding (step 5 surveys, 2026-09-05T15:4xZ): the shared harnesses are half real (real AutomationEngine, real QuickJS runner, real wrapDeclarativeHook) and half fake (a ctx.api over arrays, hand-sorted hook dispatch, no permission check); the platform's @objectstack/verify already boots the real stack in memory but exposes no in-process way to invoke a hook / flow / action.
Program (sequenced; each step is a sub-issue, Blocked-by: lines are on the cards)
| step | card | repo | gate |
|---|---|---|---|
| 1 | objectstack#15935 — os lint --strict (warnings fail; default unchanged) |
objectstack | platform first |
| 2 | #1581 — enable strict in verify, zero the 90 warnings, delete scripts/wow1-live-schema.sh |
hotcrm | Blocked-by step 1 published and pinned (install-surface probe on the card) |
| 3 | #1582 F1 flow/predicate/readonly · #1583 F2 reference integrity · #1584 F3 analytics · #1585 F4 i18n + the severity gate · #1586 F5 security/sharing · #1587 F6 write shape | hotcrm | each Blocked-by #1581; delete only after an ablation proves the platform rule fires under strict; mixed files lose group A only |
| 4 | objectstack#15936 — triage decides the live card for the null-guard / totality lint (#4763 closed vs #7219 open) | objectstack | decides whether the five totality files in F1 are "wait" or "delete" |
| 5a | objectstack#15951 — @objectstack/verify in-process handle (hooks.run, flows.run/resume, actions.run, validate, seed/rows, metadata, tenancy option, shared boot) |
objectstack | platform first; Clause-②: yes |
| 5b | objectstack#15952 — plugin-spec.mdx stops promising @objectstack/testing; create-objectstack blank template gets a test story |
objectstack | Blocked-by 5a |
| 5c | objectstack#15953 — classify hotcrm's platform-semantics pins → derived proof families in verify (D) / platform regression tests (R) / keep (K) |
objectstack | Blocked-by 5a |
| 5 | #1595 — replace hook-harness / flow-harness / action-sandbox / metadata-fixtures / tenancy-probe with the handle; delete the stand-ins and their five self-proof suites (~4,069 lines); declare the platform packages tests import |
hotcrm | Blocked-by 5a published and pinned |
| 6 | docs↔metadata drift mechanics (19 files / 7,991 lines platform-candidate rows; repo-root / heading-label / docs-anchors helpers) |
— | frozen: no new docs-drift tests; no card; revisit when a platform doc-lint exists |
| — | tenant-scoped unique-index materialisation asserted by case-number-tenant-scope / account-name-tenant-scope group B |
objectstack | folds into 5c's classification (expected (R)); if 5c files it elsewhere, the epic PM adds the Blocked-by on F5 |
Rules for the epic PM
- Cards touching
packages/specgo to the spec seat's queue withBlocked-by:back-links (none expected here). - A platform rule that does not fire under strict on this repo is a platform gap: file upstream, keep the local assertion; never delete on the name of a rule alone.
- A behaviour the
verifyhandle cannot express is likewise a platform gap (rule 2): file upstream, keep that one helper path, never re-grow a stand-in. - Business-fact pins (86 files) are not touched by this epic.
- Close-out: last sub-issue closed → summary comment here → close this card and remove
pm:epic.
Provenance of the census round
Filed by the director seat on the maintainer's instruction 「1543 选 F,普查卡同意开,你现在就派发处理」 (2026-09-05); the read-only census was delivered by an os-dev round (report 5552614013) and ACCEPTed (5552648775). The original census brief is preserved in this card's edit history. Step 5's two read-only surveys (hotcrm helper anatomy; platform test capabilities) were run by the director seat's own explore sub-agents on 2026-09-05T15:3x–15:4xZ; their findings are recorded on objectstack#15951 and #1595.
Refs: #1543 / PR #1580 (the first retirement) · objectstack#15922 (field-consumer diagnostic) · objectstack#13848 (2026-08-31 rulings) · objectstack#15929 (skills finding on the decision frame) · docs/audits/2026-09-hotcrm-handwritten-test-split.md (objectstack).
Activity
os-zhuang commentedon 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 vacantSession:
session_01TezFG8ZMrNH6n5VTNpPpdHBranch:
claude/issue-1579-test-farm-census(probe/report branch only — this round is read-only, no PR expected)Worktree:
hotcrm-issue-1579Domain:
repo:hotcrm(single-lane repo; nodomain:*)File surface: none written — reads
test/**,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 tableClause-②: 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
os-zhuang commentedon Sep 5, 2026
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@b1599507Mode:
mode:subagentScope: READ-ONLY census. No file under
test/,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 the
os-dev-reportJSON.Generated by Claude Code
claude commentedon Sep 5, 2026
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
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.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).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.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):
B. Files touching each class (a mixed file counted in both):
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
test/i18n-references.test.ts866test/object-validation-predicates.test.ts791test/readonly-write-semantics.test.ts773test/hooks-runtime-service.test.ts1503test/flow-scheduled.test.ts1502test/quote-discount-ceiling.test.ts898test/sharing-coverage.test.ts1309test/metadata-references.test.ts1288test/flow-variable-conditions.test.ts1272scripts/wow1-live-schema.sh94Counting 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.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.tsi18n/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:readdirSync;test/verify-log-decoy-pin.test.tsalso shells out viaexecSync) — 32 undertest/, 2 underscripts/.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.scripts/check-source-hygiene.mjsscripts/check-source-token-ratchet.mjstest/actions-flows-integrity.test.tstest/collaboration-capabilities.test.tstest/deal-threshold-parity.test.tstest/docs-analytics-vocabulary.test.tstest/docs-app-workflow-paths.test.tstest/docs-conversion-rate-spelling.test.tstest/docs-dashboard-tiles.test.tstest/docs-drift.test.tstest/docs-locale-callouts.test.tstest/docs-metadata-counts.test.tstest/docs-object-term-consistency.test.tstest/docs-retired-personas.test.tstest/docs-revenue-approvals-navigation.test.tstest/docs-role-hierarchy.test.tstest/docs-runnable-samples.test.tstest/docs-setup-navigation-names.test.tstest/docs-src-tree-paths.test.tstest/docs-view-rosters.test.tstest/docs-zh-hant-justification.test.tstest/helpers/docs-anchors.tstest/hook-input-shape.test.tstest/hook-query-predicate.test.tstest/hook-write-shape.test.tstest/hot-lead-threshold-parity.test.tstest/labeler-config.test.tstest/lead-duplicate-management.test.tstest/line-item-conventions.test.tstest/runtime-coverage.test.tstest/script-main-guard.test.tstest/sharing-posture-declaration.test.tstest/verify-log-decoy-pin.test.tstest/view-predicate-dialect.test.tsThe 22 object-graph walkers (not repository-tree walkers)
scripts/analytics-reconcile/macros.tsscripts/lib/source-hygiene-surface.mjsscripts/scan-field-consumers.tstest/action-references.test.tstest/activity-seed-coverage.test.tstest/app-navigation-shape.test.tstest/authorization-coverage.test.tstest/case-create-form-narrowing.test.tstest/demo-staffing.test.tstest/detail-section-dedup.test.tstest/docs-quick-tour-navigation.test.tstest/docs-service-index-analytics.test.tstest/flow-decision-authority.test.tstest/flow-variable-conditions.test.tstest/forecast-seeds.test.tstest/helpers/metadata-fixtures.tstest/i18n-references.test.tstest/live-work-predicate-parity.test.tstest/metadata-references.test.tstest/ownership-model.test.tstest/persona-copy.test.tstest/view-references.test.tsThe 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.scripts/analytics-reconcile/macros.tsscripts/analytics-reconcile/reconcile.tsscripts/analytics-reconcile/run.tsscripts/backfill-owner-id.tsownercolumn (#548), run against a live org.scripts/check-lint-i18n-gate.mjsi18n/missing-*findings from WARNING to a failing exit code; its own header states the real fix is apackages/lintseverity change.scripts/check-source-hygiene.mjsscripts/check-source-token-ratchet.mjs(group A)os measure-shaped capability.scripts/check-source-token-ratchet.mjs(group B)scripts/demo-staff.tsscripts/lib/main-module.d.mtsisMainModule()helper — a script-kit primitive any repo needs.scripts/lib/main-module.mjsscripts/lib/source-hygiene-surface.mjsscripts/publish-marketplace.mjsos package publish, so this duplicates a platform capability.scripts/scan-field-consumers.tsscripts/sync-docs-screenshots.mjsscripts/wow1-live-schema.shtest/helpers/action-sandbox.tstest/helpers/docs-anchors.tstest/helpers/flow-harness.tstest/helpers/heading-label.tstest/helpers/hook-harness.tstest/helpers/metadata-fixtures.tstest/helpers/persona-vocabulary.tstest/helpers/repo-root.tstest/helpers/tenancy-probe.tstest/account-name-normalized-match.test.ts(group A)test/account-name-normalized-match.test.ts(group B)premise:groups pin ObjectQL operator semantics ($regex retired, $icontains matches a superstring) and that driver-sql materializes no column fortype: formula.test/account-name-tenant-scope.test.ts(group A)test/account-name-tenant-scope.test.ts(group B)test/account-renewal-model.test.tstest/action-references.test.tsnav-target-unresolved,dashboard-action-route-unresolved,action-name-undefinedtest/action-sandbox.test.ts(group A)action-body-source-unparseable,hook-body-source-unparseabletest/action-sandbox.test.ts(group B)test/actions-flows-integrity.test.ts(group A)flow-node-write-unknown-field,action-body-write-unknown-field,flow-template-lookup-traversal,flow-runas-unscopedtest/actions-flows-integrity.test.ts(group B)test/activity-recency.test.tstest/activity-seed-coverage.test.tstest/analytics-integrity.test.tschart-dataset-unknown,widget-measure-unknown,dataset-field-unknown,filter-preset-comparandtest/app-navigation-shape.test.tsnav-target-unresolved,view-key-collisiontest/attendee-type-resolution.test.tstest/authorization-coverage.test.ts(group A)security-owd-unset,security-fls-unqualified-key,rls-predicate-unenforceable,security-private-no-readscopetest/authorization-coverage.test.ts(group B)test/automation-docs-coverage.test.ts(group A)test/automation-docs-coverage.test.ts(group B)test/bulk-action-dispatch.test.ts_selectedIds— a dispatch-declaration contract.action-name-undefinedtest/campaign-member-cascade.test.tstest/campaign-member-lifecycle.test.tstest/cascade-guard-messages.test.tstest/case-assignment.test.tstest/case-create-form-narrowing.test.tstest/case-first-response.test.tstest/case-guest-branch-leftovers.test.tstest/case-number-tenant-scope.test.ts(group A)test/case-number-tenant-scope.test.ts(group B)test/case-sla-matrix.test.tstest/churn-health-score-block.test.tstest/collaboration-capabilities.test.ts(group A)test/collaboration-capabilities.test.ts(group B)test/contact-email-tenant-scope.test.tstest/contract-write-depth.test.ts(group A)own_and_reportsonly when hierarchy-security is declared, and the open edition fails closed to owner-only — platform capability semantics.test/contract-write-depth.test.ts(group B)test/converted-lead-guard.test.tstest/dashboard-date-range-window.test.ts(group A){today}/{yesterday}macro semantics.test/dashboard-date-range-window.test.ts(group B)test/dataset-granularity.test.tsdataset-field-unknown,measure-aggregate-incoherenttest/deal-threshold-parity.test.tstest/decorative-field-sweep.test.tstest/demo-staffing.test.tstest/detail-section-dedup.test.tspage-section-group-unknowntest/do-not-call-enforcement.test.tstest/docs-analytics-vocabulary.test.ts(group A)test/docs-analytics-vocabulary.test.ts(group B)test/docs-anchor-links.test.tstest/docs-app-workflow-paths.test.tstest/docs-contact-email-uniqueness.test.tstest/docs-conversion-rate-spelling.test.tstest/docs-dashboard-tiles.test.ts(group A)test/docs-dashboard-tiles.test.ts(group B)test/docs-declared-versions.test.ts(group A)test/docs-declared-versions.test.ts(group B)test/docs-drift.test.ts(group A)test/docs-drift.test.ts(group B)test/docs-locale-callouts.test.tstest/docs-metadata-counts.test.tstest/docs-object-coverage.test.tstest/docs-object-term-consistency.test.tstest/docs-pipeline-kanban-section.test.ts(group A)test/docs-pipeline-kanban-section.test.ts(group B)test/docs-quick-tour-navigation.test.ts(group A)test/docs-quick-tour-navigation.test.ts(group B)test/docs-readme-token-figures.test.tstest/docs-retired-personas.test.tstest/docs-revenue-approvals-navigation.test.ts(group A)test/docs-revenue-approvals-navigation.test.ts(group B)test/docs-role-hierarchy.test.tstest/docs-runnable-samples.test.ts(group A)@objectstack/spec/ai.default-agent-outside-rostertest/docs-runnable-samples.test.ts(group B)input._selectedIdsand naming the underscore trap is this app's doc copy.test/docs-sales-index-navigation.test.ts(group A)test/docs-sales-index-navigation.test.ts(group B)test/docs-search-navigation-views.test.tstest/docs-service-index-analytics.test.ts(group A)test/docs-service-index-analytics.test.ts(group B)test/docs-setup-navigation-names.test.ts(group A)test/docs-setup-navigation-names.test.ts(group B)test/docs-src-tree-paths.test.tstest/docs-view-rosters.test.ts(group A)test/docs-view-rosters.test.ts(group B)test/docs-zh-hant-justification.test.tstest/escalation-task-subject.test.tstest/event-attendee-cascade.test.tstest/field-consumer-scan.test.tstest/field-groups-coverage.test.tsfield-group-undeclared,field-group-empty,field-group-shadowedtest/flow-billing-handoff.test.tstest/flow-campaign-enrollment.test.tstest/flow-case-actions.test.tstest/flow-cold-boot-rebind.test.tstest/flow-condition-totality.test.tsflow-inert-node-conditionis the shipped neighbour ruletest/flow-conversion.test.tstest/flow-decision-authority.test.tsconfig.condition, every decision decides, a node-authoritative decision with no default sink fails open.flow-decision-unconditional-branchandflow-multiple-default-edgesrulestest/flow-escalation-ownerless-case.test.tstest/flow-filter-today-token.test.ts(group A){TODAY()}, knows{today}, and that an unknown token fails the run — engine semantics.filter-token-unknowntest/flow-filter-today-token.test.ts(group B)test/flow-followup.test.tstest/flow-harness-declared-columns.test.tstest/flow-quote.test.tstest/flow-record-change.test.tstest/flow-run-summary.test.tstest/flow-scheduled-org-partition.test.ts(group A)flow-runas-unscopedtest/flow-scheduled-org-partition.test.ts(group B)test/flow-scheduled.test.tstest/flow-sla-ownerless-assignment.test.tstest/flow-sla-ownerless-case.test.tstest/flow-variable-conditions.test.ts(group A)flow-bare-dollar-reference/unresolved-variableare the shipped neighbourstest/flow-variable-conditions.test.ts(group B)test/forecast-current-quarter-view.test.ts(group A)test/forecast-current-quarter-view.test.ts(group B)test/forecast-manual-override.test.tstest/forecast-period-boundary.test.tstest/forecast-period-end-boundary.test.tstest/forecast-period-scope.test.tstest/forecast-seeds.test.tstest/freeze-guard-reference-cleanup.test.tstest/global-actions.test.tstest/guest-submission-sanitisation.test.tstest/harness-lookup-shape.test.tstest/heading-label.test.tstest/hook-input-shape.test.tstest/hook-org-inheritance.test.tsorg-axis-permission-inheritancetest/hook-query-predicate.test.tswhere/filteralias semantics against the real kernel, with negative controls, plus a scan that no hook queries byfilter.test/hook-write-shape.test.tshook-api-update-readonly-fieldtest/hooks-runtime-sales.test.tstest/hooks-runtime-service.test.tstest/hooks-runtime.test.tstest/hot-lead-threshold-parity.test.tstest/i18n-references.test.tsos i18n checkand thetranslation-*lint rules cover.translation-target-unknown,translation-option-key-unknown,translation-section-name-missingtest/i18n-shared-widget-parity.test.tstest/import-mappings.test.ts(group A)no-field-map,field-unknowntest/import-mappings.test.ts(group B)test/knowledge-article-share-links.test.tstest/knowledge-deflection.test.tstest/knowledge-feedback.test.tstest/labeler-config.test.ts.github/labeler.ymlglob matches at least one file and names an existing label — CI-config drift, generic to any repo.test/lead-disqualification.test.tstest/lead-duplicate-link-cleanup.test.tstest/lead-duplicate-management.test.tstest/lead-duplicate-visibility.test.tstest/line-item-cascade.test.tstest/line-item-conventions.test.ts(group A)test/line-item-conventions.test.ts(group B)test/lint-i18n-gate.test.tscheck-lint-i18n-gate.mjstest/live-work-predicate-parity.test.tstest/metadata-references.test.ts(group A)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)test/object-validation-predicates.test.tstest/opportunity-creation-date.test.tstest/ownership-model.test.ts(group A)security-anchor-high-privilegetest/ownership-model.test.ts(group B)test/parent-derived-reach.test.tssecurity-controlled-by-parent-no-relationtest/persona-copy.test.tstest/placeholder-picklist-options.test.tstest/priority-rank-parity.test.tstest/quote-accepted-draft-defaults.test.tstest/quote-accepted-lookups.test.tstest/quote-accepted-payment-terms.test.tstest/quote-contact-required-when.test.tspresentedonward — this app's gate, enforced through two real drivers.test/quote-discount-ceiling.test.tstest/readonly-write-semantics.test.tsflow-update-readonly-field,hook-api-update-readonly-fieldtest/record-id-not-in-prose.test.tstest/refusal-envelope.test.ts(group A)test/refusal-envelope.test.ts(group B)test/runtime-coverage.test.tstest/saas-composition.test.tstest/script-main-guard.test.tstest/seed-consistency.test.tstest/seed-validation-warnings.test.tstest/sharing-coverage.test.ts(group A)test/sharing-coverage.test.ts(group B)test/sharing-posture-declaration.test.tstest/sharing-seeding.test.tssharing-rule-unlowerable-condition,sharing-rule-runtime-variable-conditiontest/skills-integrity.test.tsai-skill-tool-unresolved,ai-skill-surface-mismatchtest/sla-at-risk-live-work.test.tstest/sla-compliance-gauge.test.tstest/smoke.test.tsworkflows[], notify severities valid) — exactly whatos validateproves.test/source-hygiene-header-position.test.tscheck-source-hygiene.mjstest/source-hygiene-scan-surface.test.tscheck-source-hygiene.mjstest/source-hygiene-size-advisory.test.tscheck-source-hygiene.mjstest/source-token-ratchet.test.ts(group A)test/source-token-ratchet.test.ts(group B)test/status-state-machines.test.tstest/territory-seed-coverage.test.tstest/territory-single-source.test.tstest/unassigned-case-triage-reach.test.tstest/undeclared-key-probe.test.tstest/verify-log-decoy-pin.test.tstest/view-predicate-dialect.test.tsvisibility-predicate-syntax,predicate-path-unresolvedtest/view-references.test.ts(group A)list-view-field-unknown,filter-token-unknown,sort-field-unknowntest/view-references.test.ts(group B)test/view-tab-label-inert.test.tslist.tabs[]is absent rather than merely label-free (#1307) — an inert-declaration check.liveness-dead-propertytest/win-loss-capture.test.tsSearch 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".Generated by Claude Code
claude commentedon Sep 5, 2026
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
os-zhuang commentedon Sep 5, 2026
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,并把顺序定为:① 共享 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
os lint --strict— a flag that makes warning-severity findings fail the run, so an app can rely on the 119 warning-level rules instead of re-implementing them locally (epic hotcrm#1579, step 1) objectstack#15935os lint --strictin this repo's verify chain and prove the gate reds (epic #1579, step 2b — the half that needs a release) #1581os lint --strictguards them (epic #1579, step 3, family F1) #158239 remaining items
os-steve commentedon Sep 8, 2026
⭐ 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⚠️ #1595's body lists 7 — it was written 09-05 and
e5cedc7rather than carried:test/+scripts/import 19@objectstack/*packages,package.jsondeclares 12, the gap is 9.service-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.@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:
analytics-integrity's "every url-type widget and header action resolves" inspects an empty set — 0 header actions, 0 widgetactionUrlStarting proxy on⚠️ 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.
e5cedc7: of 180 files, 141 walk a collected set and 62 of those carry no non-empty guard anywhere.⭐ 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
@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.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.Tree
2da2901e·8223d0a·9924ee1af598320c— 11 ids, 169 linesGenerated by Claude Code
os-steve commentedon Sep 8, 2026
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 PRorigin/main17.3.0, none missingcomm -23gap check@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:@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.⛔ 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
os-steve commentedon Sep 8, 2026
⭐ 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.origin/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).crm_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.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
f598320c5b2ecddGenerated by Claude Code
os-steve commentedon Sep 8, 2026
#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 PRorigin/mainfor (const f of node.filter ?? [])expect(h.notifications.length, …); 0 non-comment uses ofcrm_task.lengthinside anexpectcontinueis gonecontinue;in theit.eachblock;.toBe(exempt)presentsrc/movedgit diff --stat 5b2ecdd origin/main -- src/emptypackages:hash still95bfe9b47789dda033387104270392f014c6fb471and 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.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.@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.#1581's body still said 59 warnings. Measured on
5b2ecdd:errors:0 warnings:1 suggestions:12, exit 0.flow-loop-body-uncontainedcomponent-props-invalidcomponent-props-unknown-key⇒ ⭐ 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
@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 readTree
f598320c5b2ecdda02d4c1⛔ Not this epic's, filed for their owners and not chased: #1773 · #1775 · objectstack#16813.
Generated by Claude Code
revert(test): drop demo-ownership-routing.test.ts — Zone 1 said add n…
field-no-consumerswarnings blocking #1581 (epic #1579) #1826crm_contact.mailing_*fields: the CSV importer writes them and the authored contact form may show none of them — clear the last 5field-no-consumerswarnings blocking #1581 (epic #1579) #1827objectstack-fleet commentedon Sep 25, 2026
repo:hotcrmseat,session_01X8U3asekbiC7yWoEPWR4Dg· lane observation (comment only; this subtree is the epic PM's) · 2026-09-25T02:21Zos 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.os lint --strictships in the installed@objectstack/cli@17.4.0.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.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
objectstack-fleet commentedon Oct 8, 2026
Ruling: 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.--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.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
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.@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.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.field-no-consumerswarnings blocking #1581 (epic #1579) #1826 closedcompletedunder item 1 of the same batch.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