fe910fcce7c875c596ed56a44f8a27c7c6eba866
200 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
fe910fcce7 |
FN-9122: align merge gate timing and policy baselines
Re-establish a trustworthy W33 merge-gate timing baseline without weakening blocking coverage. - Document the controlled W33 re-measurement and future regression protocol. - Align static-validator test ledgers with all 15 canonical gate checks. - Correct gate composition, engine-core inventory, and bundle metrics in testing guidance. Files changed: .../merge-gate-w33-walltime-regression.md | 82 ++++++++++++++++++++++ docs/testing.md | 12 ++-- .../__tests__/engine-vitest-gate-policy.test.mjs | 12 ++++ scripts/__tests__/run-static-gate-checks.test.mjs | 2 + scripts/__tests__/verify-fast.test.mjs | 8 +++ 5 files changed, 111 insertions(+), 5 deletions(-) Fusion-Task-Id: FN-9122 Fusion-Task-Lineage: 47ace0d7-902d-4ea5-848d-3d2386867c42 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
111c6c96cc |
FN-9117: Preserve Planning Mode answers across session hydration
Keep visible Planning Mode controls bound to the current turn during asynchronous refreshes. - preserve active question and plan-review workspaces during same-session hydration - submit from live session state and retain dirty answers across response identity changes - add desktop and mobile regression coverage for deferred hydration and stale Stop polling - document the ownership race and add a patch changeset Files changed: .changeset/fn-9117-planning-turn-ownership.md | 7 + .../suite-only-flakes-observed-register.md | 24 ++++ .../dashboard/app/components/PlanningModeModal.tsx | 141 ++++++++++++--------- .../PlanningModeModal.planning-flow.test.tsx | 132 +++++++++++++++++++ .../PlanningModeModal.ui-interactions.test.tsx | 20 +++ 5 files changed, 265 insertions(+), 59 deletions(-) Fusion-Task-Id: FN-9117 Fusion-Task-Lineage: 18d63f63-b532-49eb-a6d1-241e31c5b2c7 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
3272affbb3 |
FN-9120: Fence Create Room agent roster loads
Keep Create Room picker state accurate across overlapping agent roster requests. - Track explicit idle, loading, loaded, and failed picker phases. - Ignore stale close, reopen, project-change, and unmount request completions. - Reconcile selected members and cover ordering, failure, empty, duplicate, desktop, and mobile states. - Document the loaded-lane flake investigation and add a patch changeset. Files changed: .changeset/fn-9120-create-room-picker.md | 7 ++ .../suite-only-flakes-observed-register.md | 17 +++ .../dashboard/app/components/CreateRoomModal.tsx | 50 ++++++-- .../components/__tests__/CreateRoomModal.test.tsx | 126 ++++++++++++++++----- 4 files changed, 165 insertions(+), 35 deletions(-) Fusion-Task-Id: FN-9120 Fusion-Task-Lineage: 5c9ff011-7653-40ab-a7c1-e3464ca3eaf5 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
7527d2651f |
FN-9116: fence planning reconciliation by turn ownership
Prevent stale Planning Mode snapshots and recovery callbacks from replacing a newer interview turn. - track session-load and turn epochs across response reconciliation, polling, streaming, and automatic retry - fence loading-poll fetches at launch so accepted SSE questions and newer responses retain ownership - add desktop and mobile race-ordering coverage and document the resolved suite-only flake - publish a patch changeset for the operator-visible recovery fix Files changed: .changeset/fn-9116-planning-reconciliation.md | 7 + .../suite-only-flakes-observed-register.md | 19 + .../dashboard/app/components/PlanningModeModal.tsx | 109 ++++- .../PlanningModeModal.planning-flow.test.tsx | 502 ++++++++++++++++++++- 4 files changed, 614 insertions(+), 23 deletions(-) Fusion-Task-Id: FN-9116 Fusion-Task-Lineage: b861176e-9104-4caf-8b6c-e645972aa5f6 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
c59afeb15f |
docs: record PlanningModeModal duplicate-response flake sighting (register entry 8)
One clean solo-lane sighting of the parametrized duplicate-response reconciliation test (mobile row; the desktop row failed once earlier under a contaminated concurrent run, recorded as context). Third distinct flaky test in this suite plus one prior FN-8936 stabilization — the AGENTS.md repeated-quarantine rule calls that a subsystem product-race smell, so the product-path audit is filed as FN-9116 rather than stabilizing a fourth time. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
920bf8b022 |
FN-9105: document boot-smoke anomaly remeasurement
Confirm the W33 spike as cold-start variance and preserve a repeatable diagnosis protocol. - Record five sequential phase-timed samples and the 20.5-second median threshold. - Link the controlled remeasurement protocol from the testing guide. - Explain the timing snapshot handoff and why incomplete CI artifacts were not published. - Preserve the no-appeasement requirement beside boot-smoke phase timing. Files changed: .../boot-smoke-w33-walltime-anomaly.md | 100 +++++++++++++++++++++ docs/testing.md | 2 +- scripts/boot-smoke.mjs | 7 ++ 3 files changed, 108 insertions(+), 1 deletion(-) Fusion-Task-Id: FN-9105 Fusion-Task-Lineage: 6956b037-db0f-4560-bb04-136080a975b5 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
6a2de64381 |
fix: normalize Anthropic storage ids at the ModelRuntime.login choke point
Third incident of the same class (#1857/FN-7391, FN-9101, GitHub #3462): Fusion's Anthropic auth-card/storage ids (anthropic-subscription, anthropic-api-key) leaking into pi, which only registers the execution provider 'anthropic'. FusionAuthStorage.login is the single seam that hands a provider id to ModelRuntime.login; it now normalizes via toExecutionModelProviderId so a future caller bug degrades to a correct upstream anthropic login instead of a hard 'Unknown provider' failure, with a regression test pinning the seam. The invariant, incident history, and guard inventory are captured in docs/solutions/integration-issues/anthropic-storage-ids-are-never-pi-provider-ids.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
284feeaf11 |
FN-9092: prevent model menus from dismissing host dialogs
Keep portaled model-menu interaction from propagating to host dialog dismissal. - Mark model-menu portal surfaces and stop their pointer events from reaching dialog backdrops. - Apply the dismissal guard across agent, chat, floating-window, and model-selection dialogs. - Close enabled overlays on valid touch taps while suppressing duplicate compatibility mouse closes. - Add desktop and mobile regression coverage for portal-host dismissal behavior. Files changed: .../fn-9092-model-filter-dialog-dismissal.md | 7 + .../ui-bugs/portaled-model-menu-host-dismissal.md | 19 +++ packages/dashboard/app/components/AgentsView.tsx | 6 + .../app/components/ChatThinkingLevelControl.tsx | 17 ++- packages/dashboard/app/components/ChatView.tsx | 38 +++-- .../app/components/CustomModelDropdown.tsx | 13 +- .../dashboard/app/components/FloatingWindow.tsx | 17 +-- .../app/components/ModelSelectionModal.tsx | 17 +-- .../dashboard/app/components/NewAgentDialog.tsx | 12 +- .../dashboard/app/components/QuickEntryBox.tsx | 5 +- .../ChatThinkingLevelControl.portal.test.tsx | 9 ++ .../__tests__/CustomModelDropdown.test.tsx | 24 ++- .../__tests__/ModelSelectionModal.test.tsx | 3 +- .../NewAgentDialog.portal-dismissal.test.tsx | 77 ++++++++++ .../components/__tests__/NewAgentDialog.test.tsx | 11 ++ .../model-menu-filter-host-dismissal.test.tsx | 163 +++++++++++++++++++++ .../app/hooks/__tests__/useOverlayDismiss.test.tsx | 7 +- packages/dashboard/app/hooks/useOverlayDismiss.ts | 26 +++- .../app/utils/__tests__/portalSurfaces.test.ts | 31 ++++ packages/dashboard/app/utils/portalSurfaces.ts | 31 ++++ 20 files changed, 469 insertions(+), 64 deletions(-) Fusion-Task-Id: FN-9092 Fusion-Task-Lineage: a0d0b84d-2efe-4a67-9485-77c08f7cc57e Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
19dffe36f6 |
fix(engine): make the planning->plan-review handoff atomic so planned cards stop stranding in Todo
Triage announced specification completion before its finally block marked the plan work item terminal, so the Plan Review seeder saw its own still-running predecessor as an "active continuation", bailed, and the discarded result silently stranded the card until FN-8592 self-healing re-seeded it ~10 minutes later (529 occurrences in 18 days). - seedStrandedPlanReviewContinuation gains retirePredecessorId: idle check excludes the named predecessor, then retires it and installs the successor in ONE transaction under the task lock; a bailed seed mutates nothing. - triage threads planningWorkItemId through PlanningHandoffReport; the runtime reaction passes it as retirePredecessorId. - reactToSpecificationComplete consumes the seed result: typed quiet parks (incl. new "no-pre-release-plan-review"), bounded retries with a fresh task/IR snapshot per attempt (mid-retry pause/needs-replan honored), loud warning naming self-healing on exhaustion. - Tests: PG both-orderings/no-mutation-on-bail/cross-task cases, direct engine seeder handoff cases, reaction retry/park/pause/replan cases. - docs/solutions: new planning-handoff-race writeup; graph-entry-contract doc reclassifies the FN-8592 sweep as backstop-only. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
0e4c0c713e |
FN-8995: reconcile durable-write inventory drift
Keep the durable-write inventory synchronized while preserving reviewed lifecycle verdicts. - Add fail-closed manifest regeneration and regression coverage. - Reconcile current call-site structure while retaining equivalent verdicts through module moves. - Clarify durable wedge-notification writer classification and regeneration guidance. Files changed: .../merge-orphan-body-durable-write-fences.md | 18 +- .../__tests__/_merge-durable-write-callsites.ts | 84 +- .../merge-orphan-durable-write-inventory.json | 15414 +++++++++---------- ...ge-orphan-durable-write-inventory-drift.test.ts | 88 +- 4 files changed, 7836 insertions(+), 7768 deletions(-) Fusion-Task-Id: FN-8995 Fusion-Task-Lineage: df3d27ca-6614-418d-b09f-e1dd9f49f910 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
5134a55b77 |
FN-8958: fence orphaned merge-body writes
Prevent cancelled merge generations from writing stale task state. - Add a signal-aware merge write fence with orphan audit reporting. - Fence merge finalization, post-push metadata, and recovery-branch task logs. - Cover durable write callsites and cancellation behavior with tests and guidance. Files changed: .changeset/fn-8958-orphan-merge-write-fence.md | 7 + AGENTS.md | 1 + .../reliability/orphan-merge-body-write-fence.md | 68 ++ .../__tests__/_merge-durable-write-callsites.ts | 4 + .../merge-orphan-durable-write-inventory.json | 982 +++++++++++---------- .../merge-orphan-body-durable-writes.test.ts | 38 +- .../engine/src/__tests__/merge-write-fence.test.ts | 39 + .../engine/src/merge/auto-merge-finalization.ts | 9 + packages/engine/src/merge/merge-write-fence.ts | 92 ++ packages/engine/src/merge/merger-ai.ts | 173 ++-- 10 files changed, 875 insertions(+), 538 deletions(-) Fusion-Task-Id: FN-8958 Fusion-Task-Lineage: 5f398c44-4320-4f0c-be15-707184f66aa8 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
a6ce7f89ef |
FN-8987: expose release-gate verdicts for Promote visibility
Expose server-evaluated release-gate state so Promote controls accurately reflect approval readiness. - Attach transient release-gate verdicts to task API responses. - Preserve only fresh REST verdicts across task snapshots and render them in task cards. - Align hold-release gating and document the Promote-state contract. Files changed: .changeset/fn-8987-release-gate-verdict.md | 7 + docs/dashboard-guide.md | 4 + docs/solutions/workflow-learnings/project-union-versus-per-task-lanes.md | 1 + docs/workflow-steps.md | 4 + packages/core/src/index.gate.ts | 2 +- packages/core/src/index.ts | 2 +- packages/core/src/types.ts | 2 + packages/core/src/types/task/task-core.ts | 20 +++ packages/dashboard/app/components/TaskCard.tsx | 3 +- packages/dashboard/app/components/__tests__/TaskCard.test.tsx | 31 ++++ packages/dashboard/app/hooks/__tests__/useTasks.test.ts | 26 ++++ packages/dashboard/app/hooks/useTasks.ts | 135 +++++++++++++++-- packages/dashboard/app/utils/__tests__/releaseGate.contract.test.ts | 29 ++++ packages/dashboard/app/utils/__tests__/releaseGate.test.ts | 49 ++++++ packages/dashboard/app/utils/releaseGate.ts | 26 ++++ packages/dashboard/app/utils/reviewBudgetApproval.ts | 9 ++ packages/dashboard/src/routes/register-task-workflow-routes.ts | 20 ++- packages/engine/src/__tests__/release-gate-verdict.test.ts | 40 +++++ packages/engine/src/execution/hold-release.ts | 166 +++++++++------------ packages/engine/src/index.ts | 3 + 20 files changed, 469 insertions(+), 110 deletions(-) Fusion-Task-Id: FN-8987 Fusion-Task-Lineage: 6f1742bc-2b2b-4a32-9be5-92160335d90d Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
7ddcf7e2a6 |
FN-8979: remove legacy mission resume blockers
Retire the deprecated v0 mission-resume blocker payload in favor of canonical descriptors. - Remove legacy blocker types, conversions, exports, and HTTP payload mirrors. - Deduplicate canonical blocker descriptors and update clients, tests, and documentation. - Add a major changeset for the breaking API removal. Files changed: .changeset/fn-8979-removal.md | 7 +++++ docs/missions.md | 4 +-- .../suite-only-flakes-observed-register.md | 14 +++++++++ .../src/__tests__/mission-blocked-clear.test.ts | 23 ++++++++++++++- .../core/src/__tests__/mission-blockers.test.ts | 26 +++++++++++------ .../__tests__/postgres/mission-store.pg.test.ts | 5 +--- .../core/src/async-stores/async-mission-store.ts | 7 +---- packages/core/src/index.gate.ts | 3 +- packages/core/src/index.ts | 3 +- packages/core/src/missions/mission-blockers.ts | 34 +++++++++++++--------- packages/core/src/missions/mission-types.ts | 9 ++---- packages/core/src/types.ts | 2 +- packages/dashboard/app/api/missions/missions.ts | 25 +++++----------- .../MissionManager.blocked-repair.test.tsx | 17 +++++++---- .../MissionManager.resume-conflict.test.tsx | 9 ++++-- .../src/__tests__/mission-resume-conflict.test.ts | 11 ++++--- packages/dashboard/src/mission-routes.ts | 10 +++---- 17 files changed, 127 insertions(+), 82 deletions(-) Fusion-Task-Id: FN-8979 Fusion-Task-Lineage: 2a0dd0b6-43ca-44ad-b992-79f4ef8eda67 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
d749a11a5b |
FN-8923: document orphan merge durable-write fences
Record the bounded cancellation frontier for orphaned AI merge bodies. - Add AST-derived durable-writer inventory and drift guard for the pinned merge closure. - Characterize orphan and successor merge-lane writes with production-path fixtures. - Document unresolved follow-ups and link the fence from the architecture guide. Files changed: docs/architecture.md | 2 +- .../merge-orphan-body-durable-write-fences.md | 92 + .../__tests__/_merge-durable-write-callsites.ts | 938 ++ .../merge-orphan-durable-write-inventory.json | 9965 ++++++++++++++++++++ .../merge-orphan-body-durable-writes.test.ts | 348 + ...ge-orphan-durable-write-inventory-drift.test.ts | 56 + packages/engine/src/merge/merger-ai.ts | 6 + packages/engine/src/project-engine.ts | 6 + 8 files changed, 11412 insertions(+), 1 deletion(-) Fusion-Task-Id: FN-8923 Fusion-Task-Lineage: 5b2d278c-1626-439a-affd-077f2de2aef1 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
00e369711c |
FN-8949: add dead mock specifier guard
Prevent silent dead engine test mocks and restore renamed-lane coverage. - Add a ratcheting test that detects unresolved relative vi.mock specifiers. - Update self-healing test seams for moved modules and queue transitions. - Document mock-specifier and store-fake failure patterns. Files changed: .../dead-vi-mock-specifiers-fail-silently.md | 57 ++++++ ...e-defects-that-masquerade-as-production-bugs.md | 12 ++ docs/testing.md | 5 + .../self-healing-query-filter-blindness.test.ts | 42 ++++- .../__tests__/vi-mock-specifiers-resolve.test.ts | 199 +++++++++++++++++++++ 5 files changed, 306 insertions(+), 9 deletions(-) Fusion-Task-Id: FN-8949 Fusion-Task-Lineage: a1079d1c-a2a9-4ac2-8245-1db434468405 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
6cf95433bf |
FN-8928: evict flaky workflow IR PG gate canary
Remove the flaky sync-workflow-IR PostgreSQL canary from the blocking merge gate while preserving non-blocking coverage. - Remove the default workflow-IR PostgreSQL test from the gate canary script. - Update gate-policy coverage expectations and flake-eviction documentation. - Record the observed setup-hook timeout and retained regression coverage. Files changed: .../suite-only-flakes-observed-register.md | 25 +++++++++++++-- docs/testing.md | 6 ++-- packages/core/package.json | 2 +- .../sync-workflow-ir-is-always-default.pg.test.ts | 6 ++++ .../__tests__/engine-vitest-gate-policy.test.mjs | 37 +++++++++++----------- 5 files changed, 51 insertions(+), 25 deletions(-) Fusion-Task-Id: FN-8928 Fusion-Task-Lineage: b725ba1a-fb33-4d49-89b4-277a64246cdd Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
e7a873c505 |
FN-8936: stabilize Planning Mode handoff tests
Stabilize live Proceed-action handoffs and re-admit the Planning Mode flow suite. - Settle hydration and re-query the Proceed action before direct-create test clicks. - Remove the Planning Mode test quarantine and record its rescue in the testing ledger. Files changed: .../suite-only-flakes-observed-register.md | 4 ++++ docs/testing.md | 3 +++ .../PlanningModeModal.planning-flow.test.tsx | 20 ++++++++++++++++---- packages/dashboard/vitest.config.ts | 5 ----- scripts/lib/test-quarantine.json | 5 ----- 5 files changed, 23 insertions(+), 14 deletions(-) Fusion-Task-Id: FN-8936 Fusion-Task-Lineage: ed869b67-9394-458b-879c-54da0d7d327e Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
b9c5df04af |
FN-8791: prevent NUL-containing chat checkpoints from crashing
Prevent malformed tool output from breaking chat checkpoint persistence. - Sanitize NUL characters at chat JSONB persistence boundaries without mutating clean values. - Observe rejected best-effort checkpoint writes so streaming sessions do not emit unhandled rejections. - Add PostgreSQL, dashboard, and sanitizer regression coverage with operator-facing release notes. Files changed: .changeset/fn-8791-chat-nul-checkpoint.md | 7 ++ .../reliability/chat-jsonb-nul-sanitization.md | 44 +++++++++++ packages/core/src/__tests__/nul-sanitize.test.ts | 21 ++++- .../chat-store-content-search-edit.pg.test.ts | 91 +++++++++++++++++++++- packages/core/src/async-stores/async-chat-store.ts | 61 ++++++++++----- packages/core/src/postgres/nul-sanitize.ts | 17 ++-- .../dashboard/src/__tests__/chat-manager.test.ts | 90 +++++++++++++++++++++ packages/dashboard/src/chat.ts | 21 ++++- 8 files changed, 322 insertions(+), 30 deletions(-) Fusion-Task-Id: FN-8791 Fusion-Task-Lineage: 51e13634-04cc-4827-b72f-f33e40760940 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
78543233aa |
fix(planning): allow repeated task creation
Advance the durable creation epoch for each explicit Planning Mode action while preserving idempotency for transport retries. Recover soft-deleted task links with a fresh claim key and cover live, deleted, and retry paths. |
||
|
|
b269bff4d4 |
FN-8757: add mobile planning review shortcut
Add a reversible Plan preview shortcut after five answered mobile interview questions. - Count only populated question-and-response history entries toward the threshold. - Preserve unsent answers while switching between mobile question and plan tabs. - Add coverage, operator documentation, and a patch changeset. Files changed: .changeset/fn-8757-mobile-planning-review.md | 7 ++ docs/dashboard-guide.md | 3 + .../suite-only-flakes-observed-register.md | 14 +++ .../dashboard/app/components/PlanningModeModal.tsx | 32 +++++- .../PlanningModeModal.planning-flow.test.tsx | 113 +++++++++++++++++++++ 5 files changed, 167 insertions(+), 2 deletions(-) Fusion-Task-Id: FN-8757 Fusion-Task-Lineage: ac92a785-aafd-4c92-a1af-b089c21dbb99 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
79d2a73a10 |
FN-8690: document Grok CLI provenance blocker
Document the unavailable Grok CLI source provenance and preserve the existing usage gate. - Record provenance investigation results and the BLOCKED-NO-SOURCE hand-off - Validate FN-8690 evidence sections and canonical verdicts - Explain why the API-supplied percentage gate remains unchanged Files changed: docs/solutions/integration-issues/grok-cli-usage-data-source.md | 64 ++++++++++++++++++++++ packages/dashboard/src/__tests__/grok-usage-finding-doc.test.ts | 21 +++++++ packages/dashboard/src/usage.ts | 3 + 3 files changed, 88 insertions(+) Fusion-Task-Id: FN-8690 Fusion-Task-Lineage: 4521063d-2a9a-4710-9609-35b7582a2f2d Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
006cc40454 |
FN-8685: add durable cross-process task deletion consumers
Deliver durable, replay-safe cross-process task deletion observation. - Add PostgreSQL lifecycle consumer cursors, leases, acknowledgements, retention, and recovery. - Start named consumers in dashboard, serve, and engine runtime paths. - Preserve delete integration metadata while suppressing replayed GitHub and GitLab side effects. - Cover outbox identity, observed delivery, fencing, and reconciliation behavior. Files changed: ...fn-8685-cross-process-task-deleted-observers.md | 7 + .../fn-8685-task-deleted-outbox-consumers.md | 7 + docs/architecture.md | 8 +- ...tgres-cross-process-task-deleted-observation.md | 8 +- docs/storage.md | 10 +- packages/cli/src/commands/dashboard.ts | 9 +- packages/cli/src/commands/serve.ts | 9 +- packages/cli/src/project-context.ts | 9 +- .../task-deleted-outbox-consumer.pg.test.ts | 157 ++++++++ ...-deleted-observed-dispatch-side-effects.test.ts | 36 ++ .../task-lifecycle-consumer-identity.test.ts | 22 ++ packages/core/src/index.ts | 11 + .../0041_fn_8685_task_lifecycle_consumers.sql | 88 +++++ packages/core/src/postgres/schema-applier.ts | 16 +- packages/core/src/postgres/schema/project.ts | 45 +++ packages/core/src/postgres/startup-factory.ts | 4 + packages/core/src/store.ts | 54 ++- .../__tests__/lifecycle-outbox-writer.test.ts | 4 +- .../core/src/task-store/archive-lifecycle-2.ts | 1 + packages/core/src/task-store/lifecycle-ops.ts | 13 +- packages/core/src/task-store/lifecycle-outbox.ts | 2 + packages/core/src/task-store/project-store-ops.ts | 4 +- .../src/task-store/task-deleted-outbox-consumer.ts | 333 +++++++++++++++++ .../task-store/task-lifecycle-consumer-identity.ts | 32 ++ .../task-store/task-lifecycle-consumer-registry.ts | 396 +++++++++++++++++++++ .../task-store/task-lifecycle-event-retention.ts | 104 ++++++ packages/core/src/task-store/task-mutation-ops.ts | 1 + packages/dashboard/src/github-tracking-state.ts | 12 +- packages/dashboard/src/gitlab-delete-close.ts | 3 + packages/dashboard/src/gitlab-split-close.ts | 7 +- packages/dashboard/src/project-store-resolver.ts | 9 +- packages/engine/src/project-manager.ts | 4 +- packages/engine/src/project-runtime.ts | 2 +- packages/engine/src/runtimes/in-process-runtime.ts | 17 +- packages/engine/src/self-healing.ts | 27 ++ 35 files changed, 1439 insertions(+), 32 deletions(-) Fusion-Task-Id: FN-8685 Fusion-Task-Lineage: 63eca9ac-d2af-44b0-ba79-388a950148d3 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
4b306f10bd |
FN-8689: document Grok CLI source provenance gap
Record the unrecoverable Grok CLI provenance chain and preserve the unmeterable usage state. - Document installed asset identity, attempted provenance retrievals, and the static-blocked verdict. - Clarify that the legacy billing request is not verified CLI /usage behavior. - Add a regression test for the provenance finding and credential-safe documentation. Files changed: docs/solutions/integration-issues/grok-cli-usage-data-source.md | 172 +++++++++++---------- packages/dashboard/src/__tests__/grok-usage-finding-doc.test.ts | 45 ++++++ packages/dashboard/src/usage.ts | 8 +- 3 files changed, 143 insertions(+), 82 deletions(-) Fusion-Task-Id: FN-8689 Fusion-Task-Lineage: 473fc008-2e08-47f6-8f53-152da5b2c31a Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
a7591853eb |
FN-8688: document unverified Grok CLI usage source
Document the provenance gap that prevents deriving Grok CLI usage data safely. - Record version-skewed source and sanitized billing-replay evidence - Keep absent Grok usage fields authenticated but unmeterable pending source-backed confirmation - Link the usage-provider rationale to the investigation record Files changed: .../grok-cli-usage-data-source.md | 93 ++++++++++++++++++++++ packages/dashboard/src/usage.ts | 4 +- 2 files changed, 95 insertions(+), 2 deletions(-) Fusion-Task-Id: FN-8688 Fusion-Task-Lineage: 5aafa505-968f-4f63-83db-35e623f84052 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
4009eb34cb |
FN-8683: remove unreachable SQLite task polling replica
Document PostgreSQL task-deletion observation and remove the obsolete SQLite polling path. - Remove polling state, replica emissions, and activity-log suppression from TaskStore. - Retain backend-aware cache warming while documenting the transactional-outbox follow-up. - Add tombstone and soft-delete abort coverage across core and engine lanes. Files changed: docs/architecture.md | 3 +- ...tgres-cross-process-task-deleted-observation.md | 128 ++++++++++++++++ docs/storage.md | 3 +- .../task-delete-nonblocking-cleanup.test.ts | 54 +++++++ .../task-deleted-polling-replica-tombstone.test.ts | 57 +++++++ .../task-updated-lanes-emit-surfaces.test.ts | 26 ---- packages/core/src/store.ts | 13 +- packages/core/src/task-store/lifecycle-ops.ts | 168 ++------------------- packages/core/src/task-store/task-artifacts-ops.ts | 4 - .../__tests__/executor-soft-delete-abort.test.ts | 15 ++ .../src/__tests__/triage-soft-delete-abort.test.ts | 14 ++ 11 files changed, 284 insertions(+), 201 deletions(-) Fusion-Task-Id: FN-8683 Fusion-Task-Lineage: a052db0c-b6fc-4b05-b6b2-f8217b56ded0 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
ebe514c3e4 |
FN-8677: propagate task update lanes before conversion
Propagate cache-warmed workflow lanes through task updates so synchronous engine consumers support renamed boards. - Add task lane cache and attach resolved lanes to task:updated metadata. - Update scheduler, triage, and notification consumers to use carried lanes with bridge-safe fallbacks. - Cover lane propagation and renamed-lane event behavior with core and engine tests. Files changed: .changeset/fn-8677-manual-merge-hold-lanes.md | 7 ++ .changeset/task-updated-carries-lanes.md | 7 ++ ...orkflow-ir-readers-always-return-the-default.md | 22 +++++ .../sync-workflow-ir-second-blocker.test.ts | 43 +++----- .../core/src/__tests__/task-lane-cache.test.ts | 30 ++++++ .../task-updated-lanes-emit-surfaces.test.ts | 92 ++++++++++++++++++ .../__tests__/task-updated-lanes-payload.test.ts | 42 ++++++++ packages/core/src/index.ts | 1 + packages/core/src/store.ts | 36 ++++++- packages/core/src/task-lane-cache.ts | 63 ++++++++++++ .../core/src/task-store/archive-lifecycle-2.ts | 3 + packages/core/src/task-store/moves.ts | 1 + packages/core/src/task-store/task-artifacts-ops.ts | 1 + packages/core/src/task-store/task-update.ts | 1 + packages/core/src/task-store/update-task-deps.ts | 4 +- .../core/src/task-store/workflow-definitions.ts | 71 +++++--------- .../__tests__/scheduler-task-updated-lanes.test.ts | 108 +++++++++++++++++++++ .../task-updated-lanes-bridge-compat.test.ts | 94 ++++++++++++++++++ ...task-updated-lanes-engine-emit-surfaces.test.ts | 101 +++++++++++++++++++ .../src/__tests__/triage-pause-abort.test.ts | 22 +++++ .../src/__tests__/triage-planning-wake.test.ts | 25 +++++ .../notification-renamed-lifecycle-columns.test.ts | 84 +++++++++++++++- .../__tests__/task-wedge-notification.test.ts | 19 ++++ .../src/notification/notification-service.ts | 56 ++++------- packages/engine/src/scheduler.ts | 62 +++--------- packages/engine/src/triage.ts | 105 ++++++-------------- scripts/lib/inert-sync-lane-baseline.json | 5 +- 27 files changed, 858 insertions(+), 247 deletions(-) Fusion-Task-Id: FN-8677 Fusion-Task-Lineage: d8fef9db-0f88-4dfd-9813-be25e10e3588 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
cced31208e |
FN-8672: document observed suite-only flakes
Record first-sighting evidence for three suite-only flakes while preserving their substantial test coverage. - Define the narrow first-sighting observed-register exception and second-sighting quarantine escalation. - Add reproduction data for the core and engine PostgreSQL-adjacent flakes. - Validate register metadata, paths, hierarchy segments, and escalation guidance. Files changed: AGENTS.md | 4 ++ .../suite-only-flakes-observed-register.md | 74 ++++++++++++++++++++++ docs/testing.md | 4 ++ scripts/__tests__/observed-flake-register.test.mjs | 61 ++++++++++++++++++ 4 files changed, 143 insertions(+) Fusion-Task-Id: FN-8672 Fusion-Task-Lineage: b52c74fb-aa7b-49e3-9f1d-a2c8c577f9c7 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
26fdb67505 |
docs(solutions): the audit table had one axis — add the one that missed three defects (#3293)
## What My blind-spot table in #3251 audited **one axis**. Adds the one that missed three defects. Docs only. That table records what each of the five lifecycle ratchets can and cannot **see**. I probed that carefully — several spellings per tool — and then wrote *"nothing found; sound"* for two of them. Within a day, three of those same tools turned out to share a completely different defect: **they wrote to the tree they were checking**, auto-tightening their own baseline during a plain check run. | gate | wrote during a check | fixed by | |---|---|---| | `check-fnxc-future-dates` | yes | #3287 | | `lifecycle-column-census` | yes, under `--strict` | #3289 | | `check-sql-column-literals` | yes | #3292 | **No number of detection probes could have surfaced that.** The table asserted one property carefully and said nothing about the other *while reading as comprehensive* — which is precisely the failure it documents in the tools it audits. ## The rule it adds 1. **What can it see?** — probe each spelling of the thing it claims to catch. 2. **Can it fail at all?** — invoke it as `package.json` does; a report-only run exits 0 forever (#3255). 3. **Does it write?** — `git status --porcelain` before and after, on a clean tree. With the trap on the third spelled out: these gates write only when a tightening is **available**, so a clean tree after a run proves the *trigger* is absent, not that the tool is read-only. Inflate a baseline entry first, then run it. I hit exactly this while reviewing #3292 — ran all three gates on main, saw a clean tree, and had to stop myself concluding the SQL gate was fine. ## Why the pattern, not the people Three tools converged on write-during-check independently. That argues the design is **attractive**, not that three authors were careless: the tightening is correct, the write saves a step, and the message even tells you to commit it. It only becomes a defect at the moment a second person runs the same gate — which is invisible from inside any one of them. What it cost, measured: #3283 and #3285 are the same `+0/-1`, five minutes apart, by two authors, **neither of whom wrote that line**. ``` lint clean; fnxc-future-dates clean ``` |
||
|
|
5efe97c0ae |
docs(solutions): your environment is a variable in every measurement you report (#3291)
Extends the doc from #3255/#3273 with the failure that cost the most in a single session: **one stale install produced five wrong reports on one issue** (#3264). ## What happened A `node_modules` that had drifted from the lockfile — `jsdom@29.0.1` installed, `29.1.1` pinned — generated failures that existed on no CI machine and no other checkout. They were not subtle: deterministic, reproducible on demand, with plausible stack traces and real-looking assertion diffs. Each round of triage got **more precise about the wrong data**: | round | claim | why it was wrong | | --- | --- | --- | | 1 | "4 deterministic failures" | measured in a 4-file batch, called it isolation | | 2 | "3 deterministic, 2 order-dependent" | isolated correctly, but a race is not deterministic | | 3 | "TaskCard is broken" | stale jsdom; the CSS assertion was correct | | 4 | "no contamination" | true of four app files; published unqualified | | 5 | "quarantine these two" | never read the failure text — both were timeouts | The through-line is not carelessness about the code. **The environment was never treated as part of the claim**, so no amount of care about the analysis could recover it. ## The checks, in the order they cost the most ```bash pnpm install --frozen-lockfile # node_modules is not evidence until it matches the lockfile <run the file ALONE, 3+ times> # isolation and repetition answer different questions <read the failure TEXT> # a timeout and an assertion failure need opposite responses uptime # a loaded box manufactures timeouts that mean nothing ``` ## Why the load check earned its place Two tests "failing" in a full-suite run were `Test timed out in 15000ms` on a box at **load average 9.7 with 84 users**. Under AGENTS.md's quarantine-on-sight rule that reads as a flake to quarantine — and the ledger's **14-day deletion ratchet would have made the lost coverage permanent**. The rule presumes the failure is a property of the test, not of the machine. A wall-clock budget crossed under local contention is evidence about the hardware. I was one comment away from deleting healthy coverage on that basis. ## The tell A finding is environment-derived when it is **local, recent, and unshared**: nobody else has reported it, CI is green, and it appeared without a commit that could explain it. Any two of those should stop a report before it is written. All three applied here, and the report went out anyway — five times. ## Verification Docs only; no code paths change. `fnxc-future-dates`, `lifecycle-columns`, `quarantine-ledger` exit 0. No changeset — internal docs are excluded. Context: the one finding in #3264 that survived all five rounds is #3286 (merged), and it survived because it was verified by **reverting the product change** rather than by trusting a red — 3/3/2 failures without the fix, 27/27 across four runs with it. |
||
|
|
29eb512d57 |
docs(solutions): the general shape — a green that answers a different question (#3273)
Extends the doc merged in #3255 with two more instances of the same pattern, both found this session, **neither involving a ratchet**. Four instances now, from four unrelated directions: | what was read as "pass" | what the green actually meant | | --- | --- | | `node scripts/check-*.mjs` exits 0 | report-only mode — the failure path needs `--strict` | | a census reports 0 for a new file | the file is untracked, so it was never scanned | | a backgrounded `cmd > log; grep …` reports exit 0 | that is `grep`'s status; the suite inside had 8 failures | | a rebased branch's tests pass | the rebase never started, so it ran on the **old** base | The two new ones are worth writing down because they are not about tooling anyone built here — they are about how results are read. **Exit codes belong to the last command in the pipeline.** A backgrounded `run_tests > log 2>&1; echo done; grep X log` exits with `grep`'s status, so the harness reported "completed, exit code 0" for a dashboard suite that had 8 failures. I nearly recorded that suite as green. Read the summary out of the log; never infer a suite's result from a wrapper's exit code. **A failed rebase leaves you on the old base, and the tests still pass there.** `git rebase` refused with `cannot rebase: You have unstaged changes`, so the branch never moved. `git diff origin/main` then listed 20+ files including other workers' commits — which reads exactly like my branch had reverted their work — and a full test run on that tree came back green. Both signals were true about a tree nobody cared about. ``` git merge-base --is-ancestor origin/main HEAD ``` said STALE while the tests said pass. That is the only check that separates the two, and it belongs before any claim of "verified on current main". The shared tell, stated once: **a result too clean, or too alarming, for what changed.** Every probe shape passing including ones that obviously should not; a two-file branch appearing to revert twenty. When the answer does not fit the size of the question, find out what was actually measured before believing it. ## Verification Docs only; no code paths change. `lifecycle-columns`, `move-target-literals`, `inert-sync-lanes`, `quarantine-ledger` all exit 0. No changeset — AGENTS.md excludes internal docs. **Pre-existing red, not from this branch:** `check:fnxc-future-dates` currently fails on main from a `2026-08-01-00:50` stamp in `packages/core/src/task-store/lifecycle-ops.ts` (commit `e52da740a5`) — a timezone-ahead clock writing tomorrow's date, at 23:45 UTC. Already claimed by **#3269 and #3270**, so I have not touched it; flagging only so this branch's CI result is not misattributed. It is the same recurring class this doc's sibling rule addresses: take the stamp from `date -u`, not the local clock. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Added guidance for identifying misleadingly successful CI and test results. * Documented checks for report-only runs, untracked files, masked failures, and tests running on an outdated code base. * Included recommendations for reviewing logs and verifying branch ancestry. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
d14294b6cb |
docs(solutions): add the count-based probe, which the report-only trap cannot fool (#3257)
## What Adds one technique to #3255. Docs only. #3255 records that probing a ratchet **by exit code** can read green because the tool is report-only without `--strict` — a real trap that nearly got a healthy gate reported as dead. There is a second technique that sidesteps it entirely and is strictly more informative: **parse the tool's own per-file count.** ```bash node scripts/check-move-target-literals.mjs 2>&1 | grep -a "my-probe-tmp" \ | grep -aoE "^ +[0-9]+" | tr -d ' ' ``` **Immune to the report-only trap** — a report-only run still *prints* the count, so the number moves 0 → 1 whether or not `--strict` was passed. **It measures which shapes, not just whether something fired.** An exit code is one bit for the whole run. Auditing a detector means asking *"of these five spellings, which are seen?"*, and five separate binary runs cannot distinguish **partial** detection from a probe file that failed to compile. The move-target audit read `direct 1 / backtick 1 / ternary 0 / const 0` in a single run, which named the gap immediately. ## Both belong | question | technique | |---|---| | **can this ratchet fail at all?** | `pnpm check:*` — ask this first (#3255 §1) | | **what can it see?** | per-file counts — an exit code is too coarse | I also added a caveat that applies to both: confirm the probe is actually being scanned by watching the tool's **scanned-file total** move. A probe that never compiled and a probe the tool never discovered both report zero hits, and neither is a finding — that one cost me a wasted measurement before I noticed the total had stayed at 1961. ## Why this is worth a follow-up rather than a comment #3255's rule as written — *"use `pnpm check:*`, not a bare `node scripts/...`"* — would have made the shape-coverage audits impossible, since `--strict` collapses five distinct per-form answers into one bit. The rule is right for its question and wrong for the other one, and the distinction is easy to lose once only the rule survives in someone's memory. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Added guidance for evaluating ratchets using per-file output counts. * Documented report-only and shape-coverage limitations, count-based versus failure-based checks, and verifying that probe files were scanned. * Included a command example for probing ratchet behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
9690f46439 |
docs(solutions): probe the instrument the way CI runs it (#3255)
Records two instrument-level defects found this session. Both were in the tools the program uses as ground truth, and both looked exactly like a pass. ## 1. A ratchet that could not fail from the command I typed `check-move-target-literals` is report-only unless given `--strict`, which `package.json` supplies. Probed bare, it returned **exit 0 for every probe** — including a blatant `moveTask(id, "in-review")` pasted into `scheduler.ts`. That is the exact signature of a dead ratchet, and I nearly reported another worker's guard as inert on the strength of it. The guard was fine; my invocation could not fail. What makes it dangerous is the output: a report-only run prints its normal summary line and exits 0, so the terminal is indistinguishable from a genuine pass. ## 2. A ratchet that could not see the file I had just written `lifecycle-column-census` and `check-move-target-literals` discovered files with `git ls-files` — **tracked only** — while the other five walk the filesystem. | new file with a plain legacy guard | result | | --- | --- | | same guard in an already-tracked file | caught | | new file, untracked | **missed, exit 0** | | identical file, `git add`ed | caught, exit 1 | The detectors are fine. The blindness is discovery, and it lands at the one moment the number is consulted: add a helper, check your own work, read zero, commit — and it surfaces later in someone else's CI run, attributed to a push instead of to the edit. The tool was answering about the last commit while being asked about the working tree. ## 3. Why it is worth a doc rather than two one-line fixes Individually these are cheap. Together they cost a day. Because `check-inert-sync-lane-conversions` walks the filesystem and the census did not, the **same probe file** was caught by one and missed by the other. I read that differential as a claim about expression walking and investigated it as one — the real cause was that two instruments in the same program disagreed about which files exist. When the measuring tools disagree about their own domain, every differential between them is unreadable until someone notices. That is the transferable lesson, and it is not visible from either fix alone. ## Status of the fixes - Census discovery scope: **#3254** (open). - Type-assertion blind spot in the sync-lane ratchet: **#3252** (open). - `check-move-target-literals` discovery scope: reported to **#3253**, whose author is already in that file — not touching it. ## Verification Docs only; no code paths change. All eight ratchets exit 0. No changeset — AGENTS.md excludes internal docs. |
||
|
|
59dfc4678b |
docs(solutions): record what each lifecycle ratchet cannot see, measured (#3251)
## What This note already prescribes: *"Before trusting a ratchet: mutate the shape it claims to catch and confirm it exits non-zero."* This is that checklist item **executed against all five lifecycle gates** on one tree, one staged probe file per form. Docs only. **Two of the five were wrong.** | gate | catches | does NOT catch | |---|---|---| | `lifecycle-column-census` | `===` / `!==` | ~~membership, switch~~ **fixed (#3247)** | | `check-move-target-literals` | direct + backtick destinations | ~~ternary~~ **fixed (#3250)**; still misses a destination bound to a local | | `check-sql-column-literals` | `"column"` comparisons — **including plain template literals**, not only drizzle `sql` tags | nothing; the one miss probed was an identifier the schema never uses | | `check-inert-sync-lane-conversions` | lane reads via the `resolvePlannerLanes` helper | a **direct** `store.resolveTaskWorkflowIrSync(...)` read feeding `resolveLifecycleColumns` — inert by the same mechanism, untracked | | `check-fnxc-future-dates` | future stamps | nothing — it caught this table's author, twice | ## The two lessons the table encodes **A ratchet's blind spot is invisible in exactly the way its subject is.** Both fixed gaps sat next to a printed zero *and a sentence promising nothing could land silently*. The count was true. The sentence was true only for the forms the parser happened to visit. That is the same shape as the conversions this program spent weeks finding — code that looks converted because the instrument cannot see the difference. **Probe correctness is its own trap.** The first census probe measured nothing: the scanner enumerates git-tracked files, the probe was untracked, and the scanned-file count staying flat reads *exactly* like "no gap". A `DELIBERATE-LITERAL` probe likewise read as a broken escape hatch until the marker moved to its own line — mid-expression it attaches to the wrong node, which is the documented gotcha, and it still caught the person who had just written it down. ## Reported, not fixed The inert-sync gap is left open deliberately: it is one narrow shape, the only in-tree instance (`replan-target.ts:95`) is documented, new conversions would use the tracked helper, and that gate has uncommitted work from another worker. Recording it beats editing a file someone else is mid-change on. ``` lint clean; fnxc-future-dates: none added; all five gates --strict green on this tree ``` |
||
|
|
478b15d7ec |
docs(solutions): add the CI failure-rate method, and a fourth instance (#3244)
## What Follow-up to #3243. That note said *"take a second measurement of a different kind"* — true, and useless at 2am without the technique. This adds the one that actually settled every case, plus a fourth instance that occurred after #3243 was written. Docs only. ## The technique Enumerate recent failing CI runs and compute a **per-file failure rate**. Seven runs separated three populations that are indistinguishable from a single local run: | rate on CI | meaning | action | |---|---|---| | **7/7** | consistent, real | fix, or diagnose and hand off with evidence | | **1/7** | intermittent | flake or race; two in one subsystem is a product-race smell | | **0/7** (fails only locally) | environment | fix your sandbox, change **nothing** in the repo | Measured on this repo's main while writing it: `planning-browser-e2e` **7/7**, `postgres/schema-applier` **1/7**, `report-store.pg` **1/7**. ## The fourth instance #3243 documented three reversals. A fourth happened after it merged: a component test with **2 failing cases locally, 0/7 on CI**. That makes **three separate local-only failures in a single session** — a model-routes test hanging offline, a component test with four failing cases, and a set of assertions I was ready to call a regression. Each felt like a finding. All three were my sandbox. That is frequent enough to be a habit rather than bad luck, which is why it is worth a row in a table rather than a mention. ## The cost asymmetry, which should drive the default Acting on a **0/7** by quarantining **deletes coverage that is green everywhere else**. Acting on a **7/7** by investigating costs an hour. The errors are not symmetric, so when unsure which row you are in, the cheap move is always more samples from the *other* environment — not more confidence about the one you have. This is the concrete form of the point the standing quarantine rule already encodes with *"without a corresponding real bug"*: **"I saw it fail" is not that clause**, and the failure-rate table is how you tell the difference before acting. ``` lint clean; fnxc-future-dates: none added (exit code checked before piping) ``` |
||
|
|
5365746d37 |
docs(solutions): record "one sample is not a diagnosis" (#3243)
## What A `docs/solutions` note recording three diagnoses I reversed **in one session**, all wrong the same way. Docs only. ## The three | observed | my story | what it was | |---|---|---| | `planning-browser-e2e` fails at width **769**, passes at **768** | layout regression at the tablet breakpoint, from FN-8606 | a **race** — 5 passes in 6 runs; on every pass the control sits inside the viewport at 769 (`right: 753 ≤ 769`) | | a model-routes test fails **3 of 3** locally | red on main; quarantine candidate | **green on CI**; a sandbox interaction. The fixture is configured offline, so a sandbox should not have changed the outcome — the tell was there from run one | | one approach could not cover a resolver | the site is **unpinnable** | a *different shape* covered it — a helper that **resolves** rather than one that **receives** | Each was plausible, mechanistic, and consistent with the evidence I had. That is what made each dangerous: **a diagnosis that explains your one data point feels finished.** Each survived exactly until a second measurement **of a different kind** — another environment, more samples, an instrumented probe. Re-running the same command is not a second measurement. ## The reusable part | observation | tempting story | check first | |---|---|---| | fails at boundary X, passes at X−1 | structural bug at the boundary | run it 5 more times — boundaries are where races surface | | **consistent** locally, green on CI | main is broken | the environment; consistency is not universality | | **intermittent** locally, consistent on CI | flaky test | a race the slower runner loses every time | | one approach failed | the site cannot be done | whether a different *shape* of the approach works | ## Why it matters beyond debugging hygiene Two of the three would have caused real damage if acted on: - Quarantining the model-routes test — the action the standing rule seems to license on "observed failing" — would have **deleted coverage that is green everywhere else**. The rule's *"without a corresponding real bug"* clause is load-bearing, and a local observation does not satisfy it. - "Unpinnable" hardened a single failed approach into a property of the site. Left standing, it becomes a permanent excuse not to look — the same failure I corrected in an inherited note earlier today, which had recorded four resolvers as unmeasurable for environment reasons that did not hold here. Hence the last rule: **record cautions as environment-scoped, not as properties of the code.** Say where you measured. ``` lint clean; fnxc-future-dates: none added (exit code checked before piping) ``` |
||
|
|
851369a480 |
docs(solutions): record "silence is not success" (#3241)
## What A new `docs/solutions` note recording a failure that hit **three different tools in one session**, each time reading as a pass. Docs only. ## The three costumes | what happened | looked like | was | |---|---|---| | `git stash --keep-index` swept the new test file out of the tree | "45 passed" | the pre-existing count; the new test never ran | | a blinding script hit an unmapped role and `sys.exit(2)` **with no message**; `&&` skipped the check, `;` let the run proceed | "375/375 green under blinding" | nothing blinded — run was against unmodified source | | a gate piped to `tail -1`, printing a blank line | "gate ran, no complaints" | exit code 1; the FNXC stamp check had failed, and **CI caught it in #3238** | ## Why it deserves its own note **A passing run and a run that never happened produce the same evidence: no failure text.** Every other bug announces itself; this one is defined by the absence of an announcement. The instinct that catches ordinary bugs — *"nothing looks wrong"* — is precisely the instinct that certifies this one. It gets worse under automation, where output is piped and skimmed. `| tail -1`, `| grep "Tests"`, `>/dev/null 2>&1` all discard the part that would have said `No test files found` or `command not found`. ## The five rules, each paid for above 1. **Assert the exit code before any pipe.** A pipeline's status is the *last* stage's — `cmd | tail -1` reports `tail`'s success, never `cmd`'s. 2. **Confirm the run did the work.** "Test Files 1 passed" when you expected 16 is a finding, not a pass. 3. **A tool that can no-op must say what it did** — print the substitution and location, fail loudly where it cannot act. 4. **Verify the mutation, not the tool's promise** — `git diff --stat`, not the exit code. 5. **Break the guard on purpose once** and watch it fail. A guard never observed failing has not been shown to work — the standard this repo already applies to product ratchets, turned on your own verification. ## The uncomfortable part, kept in The third instance was a rule **I added to AGENTS.md myself in #3174**, broken for the second time. I ran the gate. I read `tail -1`. I moved on. Writing a rule down does not make you follow it. The only reason it was caught is that **CI read the output when I did not** — an argument for the gate existing, not for me having been careful. Cross-linked from the resolver-audit note, whose every wrong reading came from a run that never happened rather than from the blinding itself. That connection is the point: I spent this session auditing a program whose subject is defects hiding behind green results, and reproduced the same class three times in my own tooling. ``` lint clean; fnxc-future-dates: none added (exit code checked before piping this time) ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Added workflow guidance explaining why silent or seemingly successful output does not confirm that a test, script, or validation gate ran. * Documented verification practices including checking exit codes, work counts, no-op detection, post-run changes, and intentional failure checks. * Added a case study highlighting how filtered output can conceal verification failures. * Added cross-references connecting resolver interpretation, test execution, and conversion coverage. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
05f09c29f8 |
docs(solutions): complete the repo-wide resolver audit; correct a superseded note (#3236)
## What Completes the repo-wide resolver audit and **corrects a note of mine that had gone stale**. Docs only. Every `resolveProjectColumnsForRoles` call site in the repository has now been blinded individually. ## Final results | package | sites | outcome | |---|---|---| | `engine` | 10 files | scheduler, triage, evaluator uncovered → pinned; executor, restart-recovery, notification already covered; self-healing 21 pinned / 1 inert | | `core` | 14 | 9 covered, **5 uncovered → all 5 pinned** (#3225, #3227, #3233, #3234, #3235) | | `dashboard` | 4 | `register-task-workflow-routes.ts:1268` covered; `server.ts` ×3 flagged | | `cli` | 1 | flagged | ## The correction A note recorded `workflow-analytics.ts` and `team-analytics.ts` — 4 resolvers — as **unmeasurable**, because `pgDescribe` probes TCP and the `.pg` suites skip without it. The caution is real and stays: a skipped suite reads exactly like a passing one. But on an environment where those suites **do** run, all 4 were measured, and `team-analytics.ts` turned out to have a half-covered pair — `completeLanes` covered, **`activeLanes` not** — in a file named `team-analytics-renamed-lanes`. That is now pinned (#3227, merged). Left standing, the note converts a real finding into a **permanent excuse for not looking**. It now says: confirm the suite actually skips *here* before recording a site as unmeasurable for environment reasons. ## A fourth measurement failure mode — the opposite direction The three already recorded all produce false *uncovered*. This one produces false *covered*: **A COVERED verdict needs a baseline.** The dashboard sweep reported 5 failing files under the global blind. **4 of them fail on clean `main`** and have nothing to do with lanes — a docs-inventory test and a model-routes test among them. Read as-is, that is four resolvers falsely credited as covered. Only `register-task-workflow-routes.awaiting-planning.test.ts` passes clean and fails blinded, so it is the sole real detector. Second time today a baseline changed a conclusion (the first found a genuine red on main, #3229). ## Why 4 sites are flagged rather than pinned - **`server.ts:1922/1923/1938`** — inside the `/api/health/reliability` route closure. No route-level test exists, and the only way in is booting `createServer(store)` behind a mock-the-world shell, which the slow-test rule forbids. The alternative is a refactor to expose a seam — its own commit, since moving code and changing behaviour do not ride together. (A note already in this doc reached the same conclusion independently; this confirms it by measurement.) - **`cli/commands/task.ts:660`** — worth its own warning. Extracting a pure helper and testing it **would look like coverage and would not be**: blinding the resolver leaves such a test green, because the helper *receives* the lane set rather than resolving it. The uncovered thing is the resolve call, not the decision it feeds. Its sibling test file already records the same limit honestly for `boardColumnsForDisplay`. ## Reported, not fixed: 4 pre-existing red dashboard files on main `lazy-loaded-views-docs.test.ts` (AGENTS lazy-view inventory drifted — 24 actual vs 18 documented), `ResearchView.test.tsx`, `planning-browser-e2e.test.ts`, `register-model-routes-kimi-k3-supplemental.test.ts` — 7 failing tests, all in the non-blocking suite. I am not fixing them here: the lazy-views inventory is a curated list other workers are actively adding to, and rewriting it mid-flight would collide. Flagging so it is visible rather than silently absorbed into my blind's noise. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Updated workflow guidance to require baseline comparisons and verification that all relevant tests run. * Added safeguards for detecting ineffective changes and distinguishing pre-existing failures. * Expanded PostgreSQL audit documentation with measured coverage results, including uncovered resolver paths. * Recorded completed coverage sweeps across core, dashboard, and CLI areas, including pinned and non-pinnable sites. * Clarified limitations when testing extracted decision helpers instead of resolver calls. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
01ab2400d0 |
docs(learnings): blinding measures the instrument you picked — rule 5, and where the measurement cannot be taken (#3222)
Extends `blind-the-resolver-to-find-uncovered-conversions.md` rather than forking a second doc on the same technique. ## Rule 5: blinding measures the instrument you picked, not the site A suite that never reaches the blinded site reports `0 failed` for the same reason a covered one does. The outputs are identical. This produced a **wrong answer twice in one sweep**, both times reading as a finding: | blinded | suite run | said | actually | |---|---|---|---| | `reads.ts` ×3 | `search-excludes-renamed-archive-lane.test.ts` | 3 uncovered | that file unit-tests `liveSearchPredicate` and never runs `reads.ts`; against `cold-storage-renamed-archive-lane.test.ts` one of the three is covered | | `server.ts` ×3 | `reliability-metrics.test.ts` | 3 uncovered | that file imports `../reliability-metrics`; nothing executes the route at all | The `reads.ts` case is the one to remember, because **the misleading suite was written for that exact conversion**. It proves the collaborator honours a resolved set — which says nothing about whether the caller passes one, and can never fail when the call site is blinded. That gap shipped as a real hole and was closed in #3220. Doc adds the cheap guard: make the blinded edit obviously fatal (`throw new Error("x")`) and re-run. Still green means the suite does not reach the site and the measurement is void. ## Where the measurement cannot be taken Per #3212's stance that recording *why* something cannot be pinned is a result, three groups are written down so nobody re-derives them: - **No TCP PostgreSQL** — `workflow-analytics.ts` / `team-analytics.ts` (4 resolvers) keep renamed-lane coverage in `.pg` suites. `pgDescribe` probes **TCP**; `pg_isready` succeeding on a **Unix socket** is not the same thing. I made exactly this mistake and reported PG as reachable one round before correcting it — mistaking the two turns 4 skipped suites into 4 false "uncovered" readings. - **No injectable seam** — `reads.ts`'s incremental-sync scan composes Drizzle conditions against `layer.db`. A test there asserts the query built, not the rows excluded: green, and blind to the bug. - **Logic inside a route closure** — `server.ts`'s three resolvers sit in the `/api/health/reliability` handler, which has no route-level test. The only harness in that package is a mock-the-world shell the slow-test rule forbids; the alternative is a refactor to expose a seam, which is its own commit. ## Census **Unchanged — `CONVERSION QUEUE EMPTY`, `AVAILABLE: 0`.** Documentation only. Gates verified green (`check-fnxc-future-dates`, `lifecycle-column-census --strict`). No changeset: internal docs, per AGENTS.md. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Added guidance for verifying the test instrument used during blinding. * Documented fatal-edit reachability checks. * Added troubleshooting guidance for situations where resolver coverage cannot be measured. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
206ff11874 |
docs(solutions): record the blinding audit's own failure modes (#3223)
## What Extends `docs/solutions/workflow-learnings/blind-the-resolver-to-find-uncovered-conversions.md` with what this session's audit work paid for. Docs only — no code, no changeset (internal doc). ## The main addition: the audit's own failure modes **Every wrong reading this method has produced came from test *selection*, not from the blind.** Three in one session, each of which reads exactly like coverage: | what I ran | why it lied | |---|---| | `vitest run src/__tests__ -t "executor"` | `-t` filters test **names**, not files. Reported two `executor.ts` resolvers uncovered; **both are covered.** | | `blind3.py <file> <var>` with an unmapped role | exited non-zero **silently**; `&&` skipped the check and `;` let vitest run against **unmodified source**. Reported "375/375 green under blinding" with nothing blinded. | | `vitest run src/__tests__/notification` | missed `src/notification/__tests__/` — a nested `__tests__` the glob never reached. Reported covered code as uncovered. | The rule that follows: an UNCOVERED verdict is a claim about the whole tree and needs the whole tree's tests. Confirm the blind actually modified the file with `git diff --stat` — *not* the tool's exit code — and that the run included every file importing the module. I am documenting my own instrument failing the standard I have been applying to product guards all phase: *a guard that reports success without checking anything is worse than no guard.* Mine reported success without checking anything. It now echoes what it substituted and where, and fails loudly on an unmapped role or missing variable; I self-tested both directions before trusting any number in #3219 and #3221. ## Rule 5: the resolver must be able to answer differently in the harness `resolveProjectColumnsForRoles` returns **legacy ids and nothing else** when the store has no `listWorkflowDefinitions` — an intentional degrade so an unreadable workflow list cannot fail a sweep. A harness omitting it makes the resolved set and the literal set **equal by construction**, so the conversion is unobservable however good the assertion is. This is not a test bug. It is correct production behaviour that erases the difference the test is trying to measure — and it alone left both the `scheduler.ts` and `triage.ts` conversions unpinnable. ## A correction to my own earlier rule I had "seed-then-union sites hide defects" too broad. Such a site hides a defect **only while every lane you assert on is already in the seed**. On a renamed board the resolver is the sole contributor of the renamed lane, so the legacy blind is *not* a no-op — I predicted it would be and it failed. Also: expand roles to legacy ids **per role** from `LEGACY_COLUMN_IDS_BY_ROLE`; `intake` is `["todo","triage"]`, not `["triage"]`, and a stricter-than-real blind manufactures failures that read as coverage. ## Inventory, so the gap is legible **116 non-test call sites across 30 files** — core 17, engine 10, dashboard 2, cli 1. Audited so far, all in engine: `self-healing.ts` (64 mapped / 21 pinned / 1 inert by construction), `executor.ts` (2, covered), `scheduler.ts` (uncovered → pinned in #3219), `triage.ts` (uncovered → pinned in #3221), `restart-recovery-coordinator.ts` (covered), `notification-service.ts` (covered). **`packages/core`'s 17 files are entirely unaudited.** Stated as a gap rather than left implied, so nobody reads engine's coverage as a repo-wide clean bill. ## Flagged, not guessed - `evaluator.ts`'s archived read is uncovered — **no test file imports that module at all.** Left unpinned deliberately: it is a thin pass-through into `collectDeterministicSignals`, which is testable directly, and it affects eval signal quality rather than task lifecycle. Recorded in the doc rather than silently skipped. - I did not audit core; it is outside my package and I am not claiming anything about it either way. |
||
|
|
2868eb4797 |
docs(learnings): blind the resolver to find uncovered conversions (#3214)
Sibling to #3203 (`a-falling-count-is-not-evidence`), which records that a metric moving is not proof the system moved. **This is the positive procedure**: how to find out whether a landed conversion is held by anything, and how to write a test that holds it. ## The measurement it is written from Of **64 resolved lane sets in `self-healing.ts`, 26 had no test that could distinguish them from the literal they replaced** — including three conversions I shipped that same day, and two halves of sweeps I had already recorded as covered. ## The procedure ``` - const reviewColumns = await resolveProjectColumnsForRoles(this.store, REVIEW_ROLES); + const reviewColumns = new Set<string>(["in-review"]); ``` Suite fails → covered. Suite passes → nothing in the tree can tell the conversion from the literal. One resolver, one 17-second run — cheaper than writing the conversion was. ## Why the census cannot answer this | instrument | question | |---|---| | census / lane-wiring ratchet | is this site written in the resolved vocabulary? | | blinding | does anything break if it stops being? | Neither substitutes for the other. A conversion merged with 204 green tests behind it and zero able to see it. ## Four rules, each paid for by a test that proved nothing 1. **Blind each resolver separately** — coverage is per-resolver, not per-sweep. Twice a sweep recorded as done was half-done, because control flow short-circuited before the second guard. 2. **The fixture must reach the branch the resolver gates.** A card in a renamed *wip* lane cannot exercise a *terminal* skip — it is caught by the wip∪review set first. 3. **Assert a path-specific side effect, never a return value.** `outcome === "reclaimed"` is reachable without the guarded branch. 4. **A store fake must honour `options.column`.** Flat and call-order stubs answer identically whatever column is requested — a fake that ignores its own filter cannot see a filter bug. ## The two shapes a ratchet cannot distinguish - **resolved gate, literal branch** — reads as *unwired*, was a live defect (#3208: a working agent lost its task link) - **passed-but-unread** — reads as *wired*, is dead code (#3212) A ratchet counting call sites scores the first as debt and the second as done. Both wrong. ## Why a doc and not more PR comments Everything above currently lives in ~20 PR descriptions. The next person to touch a lane conversion will not read those. `docs/solutions/` is where this project already keeps the things it learned the expensive way, and the frontmatter (`applies_when: deciding whether a lane conversion is actually protected by a test`) is what makes it findable. ## Verification `pnpm test:gate` 13 + 161 + 499 + 71 · lint · fnxc-dates (TZ=UTC) · `self-healing-docs` 2 passed. Docs only; no changeset, per the AGENTS.md rule for internal docs. |
||
|
|
e9a57ca8ba |
docs(learnings): a falling count is not evidence that anything changed (#3203)
## The metric counterpart to #3200 #3200 (merged) records the **shapes** an inert conversion takes, and its grammatical tell is the portable one: *if a claim can be written without running anything, it has not been tested.* I offered this material there and said I would write it as a sibling rather than bloat that doc; it merged without it, so here it is. That doc is about **claims**. This one is about **numbers**. ## The tell **A count that falls is not evidence that anything changed.** Every gate here reports a number, and a number goes down three ways — work happened, the code got denser and the scan stopped matching, or someone lowered the allowance. Only the first is progress, and from inside the check all three look identical. Four times in one phase: | what moved | what actually happened | |---|---| | census 12 → 2 (`scheduler.ts`, #3051) | ten guards routed through `resolveTaskWorkflowIrSync`, which answers with the DEFAULT board under PostgreSQL. Byte-identical. Refuted in #3058 | | census 45 → 44 (`triage.ts`, #3114) | converted the exact arm #3108 flagged hours earlier. #3126 reverted it — three PRs for one line | | ratchet 20 → 15 | not a conversion: #3065 rewrote `a === x \|\| a === y` as `set.has(a)`. It printed *"total fell — re-record"*, which would have **permanently retired live guards** | | ratchet 22 → 9 | `scheduler.ts` reported **0** while 13 guards still fell back to the default board | Rows three and four are the dangerous shape: **the gate went quiet exactly when someone improved the code**, and the remedy it suggested was to lower the allowance. ## Also recorded - **One defect, four spellings** (#3062, #3068, #3079, #3181) — each fix correct about the shape in front of it and blind to a respelling. The lesson is not "write a better regex": enumerating consuming syntax is a losing game, and the durable form keys on the *source*. - **An instrument that runs nowhere and one that cannot fail are the same defect.** `check:inert-sync-lanes` was invoked by nothing for six PRs; `check:quarantine-ledger` ran nowhere *and* omitted `--strict`, so wiring it alone would have been theatre. Includes the mechanical audit that finds both. - **Base drift makes branch numbers incomparable** — three false alarms, one of them mine, from comparing against a remembered figure. The procedure that works is extracting both scripts and running them against one tree; that is how #3169 and #3181 were shown additive (22 = 13 + 7 + 2), which decided merge order and collapsed one into six lines inside the other. - **A pick-work list at 100% false positives**, because under-reporting deferrals is the direction that manufactures the #3108 → #3114 collision. ## Every claim is a measurement No mechanism here is derived from reading. Nine PRs cited, each the one that produced or refuted the finding — including the ones where I was wrong: a stale number I mistook for a regression, and two future-dated stamps of my own that the full ratchet set caught before they shipped (one earlier one it did not, and that broke `main`). ## Census before / after ``` before: COLUMN guards (the backlog): 12 after: COLUMN guards (the backlog): 12 ``` Docs only. ## Verification `test:gate` exit 0 · `fnxc-future-dates`, `lifecycle-columns`, `inert-sync-lanes`, `quarantine-ledger`, `inert-flag-seams`, `lane-wiring`, `sql-column-literals` — all exit 0 · `pnpm lint` clean. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Added guidance for interpreting workflow metrics and avoiding misleading conclusions from declining counts. * Documented detection blind spots, branch comparison issues, false positives, and validation procedures. * Included a practical checklist for reviewing metrics, quality gates, comparisons, and potential conversions. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
5f97fbcb06 |
docs(learnings): a blocker described four times, wrong twice — instrument before you file (#3200)
Records the method that moved a `triage.ts` site flagged unconvertible for four cycles. The method transfers; the three conversions do not. ## Four mechanisms, split by derivation rather than care | # | claimed mechanism | derived from | held? | |---|---|---|---| | 1 | merged intake/hold vocabularies | reading | no | | 2 | orphan arm scoped to `source === "selection"` | reading + one test run | partly | | 3 | provenance verifies by `ir.id`, which builtins lack | reading a **comment** | **no — filed as #3187, closed as wrong** | | 4 | two test harnesses cannot answer a selection query | instrumented isolation | **yes** | (3) is the expensive one. The text I quoted was **historical prose describing code that had been removed**, sitting directly above a paragraph saying exactly that. I read a rationale as an implementation, and it reached an issue other lanes could have acted on. ## The isolation took three runs ``` flag only, no conversion 8 passed -> the orphan arm is not the cause flag + conversion 5 failed -> the conversion is same, with a realistic mock store 8 passed -> the mock was the cause ``` Change one variable, let the suite answer. Available from cycle one. ## Why this is not just "test more" Every wrong mechanism was plausible, specific, and consistent with the code as read. **Plausibility is what made them dangerous** — each was good enough to write down, publish and act on. The failure mode is not sloppiness; it is that a careful reading of a large file *feels* like evidence. The tell is grammatical: **a claim that can be written without running anything is a hypothesis, not a measurement.** "This cannot be converted because X" versus "reverting X fails these 3 of 8 cases." ## The corollary, including its negative result Once the harness was the suspect, a class fell out: a test that stubs a reader **broken in production** proves the call site's logic while unable to see that production resolves nothing. Eight files stubbed `resolveTaskWorkflowIrSync` — one masking a live defect, four redundant (#3198), one legitimate. The doc also records that the obvious generalisation **fails**: `getTaskWorkflowSelection` is equally degraded under PostgreSQL but stubbing it masks nothing, because the resolver prefers the async twin and both answer the same. The distinguishing property is that the reader returns something *incorrect*, not merely *unused*. Written down so nobody repeats the 120-file sweep. Docs only; `check-fnxc-future-dates` exit 0. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Added a case study for diagnosing an unconvertible workflow site. * Documented controlled-run findings identifying the realistic mock store as the cause. * Clarified the difference between reading-based hypotheses and instrumented evidence. * Added guidance for distinguishing conversion, orphan-arm, and mock-store issues. * Recorded an audit of related test stubs, including redundant, masking, legitimate, and unresolved cases. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
984b3ed0c1 |
docs(learnings): a seventh inert shape — reusing an already-resolved local (my #3114 turned main red) (#3130)
Records the failure shape behind #3126, from the person who caused it. ## What is new about it The three inert conversions this program had catalogued — #3051, #3062, #3068 — all called `resolveTaskWorkflowIrSync` **at the call site**, where the sync resolver is visible in the diff. That is what the existing entries describe, and it is why they read as avoidable. Mine was not that. #3114 converted a `triage.ts` arm to `disposeLanes.wip`, reusing a value `resolvePlannerLanes` had produced a few lines above. It merged, and `main` went red: `triage.ts: 7 -> 8`. My reasoning at the time, verbatim from the PR body: > `resolvePlannerLanes` already called immediately above — no new resolution/await. That sentence checks the **cost** question and skips the **correctness** one. I confirmed I was not adding an `await` to a synchronous listener — the usual blocker, and a real one — and never asked what kind of resolver had produced the local I was reusing. **Reusing an already-resolved value reads as strictly safer than resolving.** No new work, no new await, no new failure mode. That intuition is correct about cost and silent about correctness, and the sync-ness sits one hop away inside the helper, where a call-site reviewer does not see it. So the check is not *"am I calling a sync resolver here?"* but **"what produced every lane value I am about to compare against, transitively?"** A local is not evidence; the resolver behind it is. #3122 widened the gate to follow wrappers for precisely this reason — and I walked through the door it was widened to cover, during the same phase I was adding it. ## Two corollaries recorded with it 1. **A gate that catches the defect but does not block is a report.** `check-inert-sync-lanes` fired correctly and the PR merged anyway, because it is not in the blocking set. #3127 fixes that, and I would prioritise it over any individual conversion — this is the second time this phase a correct non-blocking signal was ignored. 2. **`triage.ts`'s remaining 7 are not backlog.** The revert takes it to 7, and those seven are the same shape: they need the emitter-side / async-threading work tracked in #3082, not another conversion pass. `--claims` (#3124) now marks sync-resolver files as inert-risk and keeps them out of the start-here list for exactly this reason. ## Scope Docs only — one section appended to the existing learnings file, placed with the other numbered shapes and before "The rule that produced every fix above". No code, no gate, no changeset (internal docs). The revert itself is #3126, which I confirmed on a clean detached `origin/main` checkout rather than on a branch; I did not open a competing fix. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
20e3731eb1 |
docs(workflow-learnings): a deferral's stated blocker is a claim, and it decays like a measurement (#3026)
Two pieces of work were filed rather than fixed in one session, each with a specific technical reason. **Both reasons were wrong**, and in both cases the real obstacle was smaller than the stated one. | filed rationale | reality | |---|---| | "the plugin has no scaffolding for faking its stores" (#3020) | `_harness.ts` builds a real `PluginContext` over a live PostgreSQL layer; the gap was **two missing readers on a stub** — fixed in #3022 | | "supplying this needs a published-API change" (#3003) | the type is dashboard-internal, `@fusion/plugin-sdk` is `private: true`; the actual obstacle is stale type declarations between two in-repo packages | The first one matters most: the filed issue was a **pipeline that stalls forever** on a renamed board. The cost of that excuse would have been a real stall sitting open behind a plausible-sounding note. ## The shape Both times the blocker was asserted **from the shape of the problem** rather than tested. *"This needs infrastructure that doesn't exist"* and *"this crosses a published boundary"* are each checkable in about five minutes, and neither was checked before I wrote a paragraph explaining why the work couldn't proceed. ## Why it's worth writing down Filing is often right — someone else owns the contract, the fix needs a decision, the data genuinely isn't there. What makes it wrong is filing on an **untested** blocker, because a filed issue with a confident rationale is the one thing nobody re-derives. It reads as settled. That's the same mechanism as a stale "do not re-probe" note (which this document already records, and which I had to correct in #3018), one level up: there a *measurement* went stale, here a *decision* did. ## The rule **Before writing the blocker down, spend five minutes trying to hit it.** If it's real you'll hit it immediately and can describe it precisely — which makes the issue more useful. If it isn't, you have the fix instead of the issue. Docs only. No code, no baselines. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
eecc87c31e |
docs(workflow-learnings): the "named legacy-id collections are clean" entry was wrong (#3018)
It hid two real defects — and it explicitly told the next reader not to re-probe them. ## What the entry did Counted **declarations** (48, then 49) and concluded the population was benign because each one is a fallback vocabulary, a builtin column list, or an already-converted seam. All true of the declarations. **The declaration isn't where the defect lives.** ## Measure the use, not the declaration A collection used as a **membership gate against a column**. Nine exist, and two were live user-visible defects sitting inside a population this doc had marked clean: | site | defect | |---|---| | `TIME_INDICATOR_COLUMNS.has(task.column)` — `TaskCard` | elapsed-time indicator never rendered on a renamed board (#3014) | | `PLANNER_ACTIVITY_COLUMN_IDS.has(task.column)` — `useTasks` | planning border and pulsing badge never appeared (#3017) | The other seven are genuinely fine, and the reasons are kept because they're the shapes worth recognising: the no-flags fallback *inside* a role helper, a seam that seeds the legacy pair then unions resolved lanes, a marked `DELIBERATE-LITERAL` fallback chain, and a plugin with no trait source at all. ## The tell One question separates the two groups: **does a flags path exist in this file at all?** Both defects had none — the gate was the only decision, with nothing to degrade from. Every benign case had a resolved path sitting right next to the literal. ## Why this is worth its own PR A "do not re-probe" note that is wrong is **worse than no note**: it converts one person's incomplete measurement into everybody's blind spot. That's the same failure this document already records for `sortTasksForDisplayColumn`, one level up — there an annotation told readers to skip a *row*, here it told them to skip a *population*. I wrote the original entry, and I'd read past it twice myself before #3014 forced the re-measurement. Docs only. No code, no baselines. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
e9f587c363 |
docs(workflow-learnings): correct the "bounded" heuristic — a clock-shaped dep is not a fast one (#3012)
The severity heuristic I wrote in #2998 sorted dependencies **by name**, and #3007 is the counterexample. ## What I got wrong I classified `lifecycleDates` as *bounded* because its dep list contains `lifecycleNowMs`, and deferred it in #3001 with the line *"any wrong answer there survives only until the next update."* That value is driven by a **local-midnight boundary timer** — one tick per card per day. So a finished card shows no completion date for up to **twenty-four hours**. @gsxdsm found it after I'd written it off. `nowMs`, `Ticker` and `lastFetchTimeMs` span a live 30-second ticker, a per-fetch stamp, and a daily boundary. Sorting them by name puts a day-long defect in the same bucket as a 30-second one. ## The sharper half A card in a **completion lane doesn't subscribe to the shared live ticker at all** — that's exactly what the ticker's eligibility check is for, and what #2996 fixed. So the "fast" dependency that would have rescued this population is the one thing that population never receives. The corrected question is: **which dependencies refresh *for this population*** — not which ones appear in the list. Two of my three severity calls in that sweep leaned on a dep that the affected cards structurally never get. ## Why this is worth a PR rather than a quiet edit The doc is what the next person triages against. #3001 explicitly told them the four "bounded" sites were deprioritised **by design** — on reasoning that was wrong for at least one of them. Leaving that in place means someone defers a day-long defect on my say-so. Docs only. No code, no baselines. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
e78bf20d55 |
docs(workflow-learnings): a sixth shape — the resolved value arrives after a memo has answered (#2998)
## The shape Three defects this session, all the same, none visible to any instrument here: A lane value resolved **asynchronously** (the board fetches workflow traits after first paint) is read inside a `useMemo`/`useCallback` whose dependency list omits it. The first computation runs with the flags `undefined`, the role helpers correctly fall back to legacy ids, and on a **renamed** board that answer is wrong. When the flags arrive nothing in the dep list changed, so the memo never recomputes. | defect | severity | |---|---| | blocker fan-out trait index (#2993) | permanent — empty index for the mount | | card live elapsed-time indicator (#2996) | permanent — never subscribes | | near-duplicate chip (#2997) | bounded — self-heals on the next task refresh | A legacy board hides all three: there the fallback already answers correctly on the first paint, so the stale list costs nothing. **Every instance is renamed-board-only**, which is why they accumulated — and this repo has no `react-hooks/exhaustive-deps` rule, so the class is invisible to lint. ## Two properties decide severity, both readable off the dep list 1. **Does any dependency refresh quickly?** `allTasks`, a live clock, a task identity — any of them rebuilds the closure on the next update, making the wrong answer a bounded window. The chip keys on `allTasks` and recovers; the indicator keys on `task.column`, which never changes, so it never does. 2. **Is the value covered transitively?** A dependency that itself lists the flags gets a new identity when they arrive, and that propagates. ## A gate was built and rejected — the part worth writing down The scanner reports **19 sites; two were real.** Property 2 is why: transitive coverage is invisible to any purely syntactic check and would need a real dependency graph. `TaskCard`'s context-menu memo omits all three role flags and is **nonetheless correct** — it depends on `taskActionMenuModel.actions`, and that model lists `taskColumnFlags`, so the whole chain recomputes. I checked that before filing it, which is the only reason this PR isn't a bug report about missing Archive/Revert menu entries. Freezing 19 would have baselined mostly noise and trained everyone to skip the report — the exact failure this document already records for `sortTasksForDisplayColumn`, where an annotation saying "ignore these" hid a real defect for days. **A good investigative tool is not automatically a good ratchet**, and the next person deserves to know the turn was considered rather than missed. The triage that does work is cheap: run the scan, then ask the two questions above. Nine of nineteen survive question 1; hand-checking those is an afternoon, not a project. Docs only — no code, no baselines. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
6a465e1006 |
docs(workflow-learnings): probe harnesses lie more often than the gates do (#2983)
## What Probing four gates with unimagined shapes this session (#2979, #2980, #2981) produced **two rounds of silently invalid results** — both from the harness rather than the instrument, and both agreeing with what I expected, which is why neither was noticed on the spot. 1. **`node gate.mjs | tail` then `echo $?` reads *tail's* exit status.** Every probe reported "caught". The gate was in fact failing on `main` for an unrelated reason, so the runs proved nothing. That fictional evidence nearly shipped a double-counting change to the SQL gate. 2. **A gate that lists files with `git ls-files` cannot see an untracked probe file.** Six census probes reported "missed" — including the shape the census is explicitly built for, which was the tell. Filesystem-walking gates (`check-sql-column-literals`, `check-inert-flag-seams`) see untracked files; the census does not. The rule that catches both in one step, now written down: > **A probe run needs its own control.** Include one shape the instrument is known to catch and one it must not flag. If the known-good shape doesn't come back caught, stop — you're measuring your harness. Worth stating plainly because the two failure modes have opposite costs: a probe that wrongly reports *caught* retires a real hole; one that wrongly reports *missed* sends you rewriting an instrument that was already correct. ## Two measured negative results, recorded so nobody re-runs them Added to the existing "Surfaces that were checked and are CLEAN" section: | shape | population | |---|---| | `switch (task.column)` with legacy `case` labels | **0 sites** | | a legacy id hoisted into a single const, then compared | **1 site — and it is correct code** | The one site is `self-healing.ts:2992`, which seeds `let holdColumn = "todo"` as its documented legacy floor and then overwrites it from `resolveLifecycleColumns(...).hold`. The census is right not to flag it; a naive version of this probe reports it as a defect. The second shape was worth measuring precisely because **the same shape had a real population in SQL** — it's what #2980 fixed. It did not transfer. Population is a property of how people write that particular kind of code, so each instrument has to be measured on its own rather than by analogy to a sibling that just turned something up. ## Why this is docs and not a gate change The census's comparison-only scope is adequate for this codebase: every blind shape I could construct has an effectively empty real population. Demanding new detection would have forced a large baseline change across the program's central instrument for **zero defects** — the same mistake as filing "48 uncounted sites" that the existing section already warns about. Docs only. No code, no baselines touched. All five gates green. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
b1bd571682 |
batch-sql-ratchet: the census / gate-ratchet family — collection branch, fold here (#2941)
## Family branch for consolidation directive item 4 `batch-sql-ratchet` did not exist and ~10 open PRs are waiting for a collection point, so this establishes it. **Fold your census/ratchet commit here and close your own PR as superseded.** ```bash git fetch origin batch-sql-ratchet git checkout -B batch-sql-ratchet origin/batch-sql-ratchet git cherry-pick <your-sha> # verify scoped, not full suite: pnpm --filter @fusion/core exec vitest run src/__tests__/archived-column-gate-parity.test.ts --silent=passed-only --reporter=dot git push origin HEAD:batch-sql-ratchet ``` **Candidates I can see open right now** (owners: please fold + close): | PR | branch | |---|---| | #2938 | `fix/comments-ops-sentinel` | | #2935 | `fix/task-artifacts-sentinels` | | #2933 | `chore/commit-tightened-census-baseline` | | #2931 | `fix/async-comments-sentinels` | | #2928 | `fix/audit-ops-sentinel-marker` | | #2925 | `live-task-column-lanes` | | #2923 | `fix/task-id-integrity-sentinel` | | #2921 | `fix/plugin-store-migration-marker` | | #2894 | `gate/sql-literals-match-census-placement` | That is **10 → 1** once folded. I have not cherry-picked anyone else's commits — folding someone's work without them verifying it is how a batch lands broken. --- ## What is in it so far (mine, from #2924) **Clears a live main red:** `archived-column-gate-parity` fails on `origin/main` today. ``` AssertionError: TypeScript encoding changed. async-comments-attachments.ts: 8 → 5 ``` #2886 fixed a real bug — archived-document guards failing in *opposite* directions on a renamed lane — by replacing three `column === "archived"` comparisons with `isArchivedLane(column, archivedColumns)`. The AST scan counts raw comparisons, so the tally dropped. **What I did not do is record it as three sites converted**, because measured, it is not: ``` grep -rn "archivedColumns:" packages/core/src packages/engine/src --include="*.ts" | grep -v __tests__ → (no matches) ``` No caller passes it. The parameter defaults to `LEGACY_ARCHIVED_LANES = new Set(["archived"])`, so every call resolves to the literal it replaced — byte-identical behaviour, resolved branch dead. That matters for this guard's whole argument: its header warns that converting the TypeScript half while the Drizzle and raw-`sql` halves still compare the string is a split brain *"no test would catch, because every builtin workflow spells the column `archived` so the two halves agree by accident on every board we ship."* **There is no split brain today precisely because the resolved half is unwired** — it becomes one the moment a caller threads real lanes in without the SQL sides moving. Recorded inline so `5` cannot be read as "3 sites done"; flagged on #2886. Verified not a split brain: the Drizzle and raw-sql inventories are unchanged and both pass — worth stating because those assertions run *after* the TypeScript one, so a plain red says nothing about them. Scoped edit to `AUDITED_TS_SITES` by line range: these paths appear in more than one inventory here, and an unscoped replace would quietly edit the raw-sql side too, making the parity guard agree with itself (the trap I hit in #2817). Guard still bites: appending a real `task.column === "archived"` to an audited file fails it. Core **4852 passed / 0 failed**, lint clean, test-only. Closing #2924 as superseded by this. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved task delegation messages when workflow pickup cannot be confirmed. * Delegation results now clearly indicate when a task has not been verified for pickup. * **Quality Improvements** * Added validation checks to catch future-dated markers and inconsistent SQL-column usage. * Refined workflow checks to distinguish stale configuration from incomplete configuration. * **Documentation** * Updated lifecycle conversion guidance with more accurate audit findings and limitations. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
7b68f20501 |
batch(docs): fold the three workflow-learnings / annotation PRs into one (#2942)
## Family batch — replaces #2926, #2892, #2887 Per the consolidation directive: the u9/e2e **docs family**, folded into one branch and one CI run. Three PRs, five commits, **five files, comment and markdown only**. | folded PR | commits | |---|---| | #2892 `docs/union-vs-per-task` | the project union and the per-task answer are not ranked; date correction | | #2926 `docs/date-my-measured-claims` | date the measured claims (one was wrong); date the grep-vs-AST measurement in the SQL gate header | | #2887 `docs/archived-state-literals` | mark the three archived STATE literals as deliberate | Cherry-picked in original order with authorship preserved; all five applied clean, no conflicts. ## Scope is provably comment-only ``` docs/solutions/workflow-learnings/lifecycle-conversions-that-score-as-wins.md docs/solutions/workflow-learnings/project-union-versus-per-task-lanes.md packages/core/src/task-store/async-maintenance.ts ← FNXC DELIBERATE-LITERAL annotation packages/core/src/task-store/workflow-definitions.ts ← FNXC DELIBERATE-LITERAL annotation scripts/check-sql-column-literals.mjs ← header prose only ``` Every added line in `packages/` and `scripts/` is inside a comment — checked by filtering the diff for declarations, conditionals and returns, which returns nothing. The two core files gain `DELIBERATE-LITERAL` markers explaining that `'archived'` is a **state** marker there, not a lane: the sweep collects rows Fusion itself archived or soft-deleted, so widening to the resolved archived set would pull live cards into a cleanup pass. ## Verification (scoped, per the directive — not the full suite) - `pnpm lint` — clean - `check-sql-column-literals` — exit 0 (the file it annotates) - `check:lifecycle-columns` — exit 0 (the markers it adds are census-visible) - `sync-workflow-ir-callsite-allowlist.test.ts` — 3/3 ## A correction worth recording Mid-fold I saw a changeset, `self-healing.ts` and a test file in `git diff origin/main..HEAD` and nearly reported the batch as impure. They were **main's own commits** — `origin/main` advanced between branch creation and the diff, so the comparison was against a stale base. Rebasing onto current `main` reduced it to the five files above. Worth flagging for anyone else folding a family today: with `main` moving this fast, diff the branch **after** rebasing or the file list will lie to you. ## Closing the originals #2926, #2892 and #2887 are superseded by this and are being closed. I hold no PRs of my own in this family — all mine merged — so this fold is on behalf of the family rather than a rollup of my own work. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |