From 79b08a2e99a136d18f26a0da3d89acb8670aad15 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Fri, 31 Jul 2026 17:12:10 -0700 Subject: [PATCH] gate(census): say WHY role and status are not backlog, where the numbers print (#3275) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## The hazard The column backlog is **0**. The two largest numbers the census prints are now `ROLE (12)` and `STATUS (185)`, sitting directly beneath it, labelled only `(not guards)`. That is a verdict with no reason. For a worker under a directive to drive a census down — finding the backlog line already at zero and two bigger numbers underneath — "(not guards)" is thin protection. This PR puts the reason where the numbers are. ## Why they are genuinely not backlog Both classify by **receiver**, not by the literal (`ROLE_RECEIVER_TOKENS` = `role, agentType, agent, lane, capability, sessionPurpose, surface, purpose, agentRole`; status matches `/status/i`). A legacy column id next to one of those is a different domain that happens to share vocabulary with the old board. Sampled from the current tree, not reasoned: | site | receiver | what it actually is | | --- | --- | --- | | `packages/cli/src/commands/task.ts:529` | `outcome === "archived"` | a task **outcome** | | `.../routes/register-chat-routes.ts:894` | `type === "done"` | a chat **message type** | | `.../cli-agent/telemetry-hub.ts:304` | `kind === "done"` | a telemetry **kind** | | `packages/cli/src/commands/goals.ts:178` | `status === "archived"` | a **goal's** status | | `packages/cli/src/commands/mission.ts:145` | `status === "in-progress"` | a **mission's** status | None is a task column, so none has a workflow lane to resolve against. Converting a goal's `status === "archived"` to a column trait would not remove a legacy id — **it asks the wrong object for a lane it does not have**, and the resulting bug would be invisible on the default board for precisely the reason every inert conversion is. That is 185 opportunities to inject a real defect while a number goes down. ## Output-only, verified Counts, JSON, baseline comparison and exit codes are untouched: ``` --strict exit=0 json totals: {"column": 0, "role": 12, "status": 185, "deliberate": 150} # byte-identical ``` 60 census tests pass (`lifecycle-column-census.test.ts`, `census-reclassification-message.test.ts`). `lifecycle-columns`, `move-target-literals`, `inert-sync-lanes`, `quarantine-ledger` all exit 0. ## Provenance I raised "role/status have no inertness proof behind them" several times as a reason not to touch them, which was too weak — it implied the work might be valid pending proof. Rather than leave that hanging I went and looked. They are not unproven conversions; they are **not conversions at all**. Correcting my own earlier framing, and putting the finding where the next person will hit it instead of in a report they will not read. No changeset — internal tooling. --- scripts/lifecycle-column-census.mjs | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/scripts/lifecycle-column-census.mjs b/scripts/lifecycle-column-census.mjs index 9d2de6b184..2bc55544c3 100644 --- a/scripts/lifecycle-column-census.mjs +++ b/scripts/lifecycle-column-census.mjs @@ -277,8 +277,31 @@ if (json) { } else { console.log(`lifecycle-column-census: scanned ${files.length} source files\n`); console.log(` COLUMN guards (the backlog): ${summary.totals.column}`); - console.log(` ROLE comparisons (not guards): ${summary.totals.role}`); - console.log(` STATUS comparisons (not guards): ${summary.totals.status}`); + /* + FNXC:LifecycleColumnCensus 2026-07-31-23:57 (these two are NOT a second backlog — do not convert them): + Both classify by RECEIVER, not by the literal. A legacy column id appearing next to a role- or + status-named receiver is a DIFFERENT DOMAIN that happens to share vocabulary with the old board. + Measured samples from the current tree: + + outcome === "archived" packages/cli/src/commands/task.ts a task OUTCOME + type === "done" .../routes/register-chat-routes.ts a chat MESSAGE TYPE + kind === "done" .../cli-agent/telemetry-hub.ts a telemetry KIND + status === "archived" packages/cli/src/commands/goals.ts a GOAL's status + status === "in-progress" packages/cli/src/commands/mission.ts a MISSION's status + + None is a task column, so none has a workflow lane to resolve against. "Converting" a goal's + `status === "archived"` to a column trait does not remove a legacy id — it asks the wrong object for + a lane it does not have, and the resulting bug is invisible on the default board for the same reason + every inert conversion is. + + Why this needs saying where the numbers PRINT rather than only in the classifier: the two counts sit + directly under the backlog with larger values (12 and 185 against a backlog of 0). A worker told to + drive a census down, finding the column line at zero, has two bigger numbers in front of them and + "(not guards)" alone does not explain why they are off-limits. The label states the verdict; this + states the reason. + */ + console.log(` ROLE comparisons (NOT backlog — agent/chat/telemetry receivers, not columns): ${summary.totals.role}`); + console.log(` STATUS comparisons (NOT backlog — goal/mission/step status, not columns): ${summary.totals.status}`); console.log(` DELIBERATE-LITERAL (reviewed): ${summary.totals.deliberate}`); /* FNXC:LifecycleColumnCensus 2026-08-01-01:40: