From 6ca7cc94ecca8b255076490d09fb0f736f134c63 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Wed, 29 Jul 2026 22:23:45 -0700 Subject: [PATCH] =?UTF-8?q?triage=20census=20=E2=80=94=20core/types/archiv?= =?UTF-8?q?e-planning.ts=201=E2=86=920,=20plus=20the=20self-healing.ts=20a?= =?UTF-8?q?udit=20(10=20guards,=202=20traps=20a=20mechanical=20conversion?= =?UTF-8?q?=20would=20miss)=20(#2622)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Batched: one conversion plus the audit for the largest remaining file, so the CAPACITY worker inherits the analysis instead of redoing it. ## Per-file counts | File | Before | After | |---|---:|---:| | `packages/core/src/types/archive-planning.ts` | 1 | **0** | Verified with the raw pattern (`column [!=]== "triage"`), which is what the coordinator greps — including checking that my own explanatory comment did not reintroduce the literal. It did, on the first attempt; caught and removed before pushing. ## The conversion: a doc that manufactures dead guards There is **no executable guard** in this file — raw 1, code 0. I fixed it anyway, because the documentation was wrong in the way that propagates: it told consumers to derive running agents via a hardcoded intake-column comparison. Post-merge the default lineage declares one Planning column and no `triage`, so anyone implementing from that sentence writes a comparison that matches nothing — **a dead guard authored on purpose, from an instruction we left lying around.** A doc handing out a dead predicate is worse than a dead guard, because it manufactures more of them. Now describes roles. Also disambiguates the neighbouring line where "triage agent" is a lane/role id, not a column — the same conflation that accounts for 23 of the original broad 48. ## Audit: `self-healing.ts` (10 guards, unclaimed at time of writing) 45% of the remaining bar, and every one sits in a recovery sweep. **6 of the 10 are sole-`triage` and already dead for default-workflow cards.** | Line | Guard | Fires for default cards? | What silently stops | |---|---|---|---| | 2964 / 2984 / 3019 | advanced-triage recovery: filter, live re-check, `moveTaskIf` CAS | **No** | stranded specification work never recovered | | 12173 / 12494 | orphaned-approved + orphaned-planning sweeps | **No** | orphaned planning sessions never reaped | | 12321 | `task_refine` candidates | **No** | refinement tasks never recovered | | 9218, 10703, 11282, 12218 | paired with `todo` | Yes, via the `todo` arm | — (legacy-compat arms) | **Two traps a mechanical conversion walks straight into:** 1. **`listTasks({ column: "triage" })` at 12172 and 12493 is a dead QUERY, not just a dead filter.** Convert only the `t.column === "triage"` predicate and both sweeps scan an empty result set — the file counts as converted while the sweeps stay exactly as dead. This is the coordinator's rule #2 in its most literal form: a guard surviving in another branch of the same function. 2. **Lines 2964 / 2984 / 3019 are one transaction** — filter, live re-verify, and a `moveTaskIf` compare-and-set. Convert them independently and you get a filter matching the resolved intake column against a CAS still demanding the literal, so **every move refuses**. Silently: `moveTaskIf` returning false is indistinguishable from a lost race. Both need the resolved-vs-guessed distinction from **#2618** — a `builtin:legacy-coding` card in `triage` must still be recovered when the store cannot name its workflow. ## Related live finding, not fixed here `resolvePlannerLanesForTask` (merged in #2610; used by `executor.ts:11978`, `scheduler.ts:1843`/`:2414`, `mission-autopilot.ts:973`) cannot tell a resolved workflow from a guessed one. Probe on main against a `{ getTask }`-only store: ``` PROBE lanes: ["todo"] dedicated: [] ``` So a `builtin:legacy-coding` card in `triage` is not recognised as a planner lane — mission-feature rollback stops firing and the spec-staleness planner skip never fires. Neither errors. #2618 is the fix; ~3 lines per resolver in `planner-lane-resolution.ts`. ## Verification `tsc --noEmit` on `@fusion/core` clean; `pnpm lint` clean. Comment-only change, no behaviour change, no changeset. 🤖 Generated with [Claude Code](https://claude.com/claude-code) ## Summary by CodeRabbit * **Documentation** * Clarified terminology for archived task planning overrides. * Updated project health documentation to better explain how active agent counts are calculated across workflow stages. --------- Co-authored-by: Claude Opus 5 (1M context) --- packages/core/src/types/archive-planning.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/core/src/types/archive-planning.ts b/packages/core/src/types/archive-planning.ts index 1efa891b75..de8e044092 100644 --- a/packages/core/src/types/archive-planning.ts +++ b/packages/core/src/types/archive-planning.ts @@ -110,7 +110,16 @@ export interface ArchivedTaskEntry { modelId?: string; validatorModelProvider?: string; validatorModelId?: string; - /** Optional: planning model override for triage agent */ + /** + * Optional provider/model override for the planning session — the same provider/model pair + * shape as the sibling `*ModelProvider` / `*ModelId` fields, resolved by the model-selection + * hierarchy. + * + * Named for the "triage" LANE (the agent role that runs specification), which is a role id and + * not a column id. The distinction is worth stating here because `triage` is also a legacy + * column id, and conflating the two is what produced dead column guards elsewhere in the + * codebase — this field has never had anything to do with a column. + */ planningModelProvider?: string; planningModelId?: string; mergerModelProvider?: string; @@ -760,7 +769,13 @@ export interface ProjectHealth { /** * FNXC:Concurrency 2026-06-26-23:46: * Persisted project-health bookkeeping refreshed only by health polling / slot accounting paths; it is not a live read-layer running-agent count. - * Consumers that need current running agents must derive from the shared top-level slot predicate: in-progress executors, active triage planners (`column === "triage" && status === "planning" && !paused`), and active in-review reviewer/merger/fix agents including PR/fix merge substates, leaving this stored value untouched. + * Consumers that need current running agents must derive from the shared top-level slot predicate: implementation-column executors, active planners (a card in its workflow's INTAKE column with `status === "planning" && !paused`), and active review/merge-column reviewer/merger/fix agents including PR/fix merge substates, leaving this stored value untouched. + * + * FNXC:WorkflowLifecycleColumns 2026-07-30-15:10 (triage-guard census): + * This prescribed a hardcoded intake-column id verbatim. No executable guard lives in this file, but a + * doc that hands out a dead predicate is worse than a dead guard: whoever implements from it + * writes a comparison that matches nothing on the default lineage, which post-merge declares one + * Planning column and no `triage`. Roles, not ids — resolve intake/wip/review by trait. */ inFlightAgentCount: number; /** ISO-8601 timestamp of last activity */