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>
This commit is contained in:
gsxdsm
2026-08-11 01:30:04 -07:00
parent eacd6687bc
commit 7ddcf7e2a6
17 changed files with 127 additions and 82 deletions

View File

@@ -154,7 +154,7 @@ Mission, milestone, slice, and feature read-only text surfaces in Mission Manage
Use **Clear blocked status** when the mission-level `blocked` badge is stale. It recomputes and records the mission status with an attributed audit event, but does **not** resume the mission, unpause linked tasks, re-arm autopilot, or clear lineage stops. **Resume** remains the separate operation that reactivates execution.
`MissionBlockerDescriptor` is the canonical diagnosis shape: `{ featureId, reason, source }`, where `source` is `feature-stop` or `lineage-stop`. The diagnostics and clear responses use this deduplicated shape. For backward compatibility, a `POST /api/missions/:missionId/resume` `409 MISSION_RESUME_CONFLICT` continues to return its legacy undeduplicated `{ id, reason }[]` blockers.
`MissionBlockerDescriptor` is the canonical diagnosis shape: `{ rootFeatureId, reason, source }`, where `source` is `feature-row` or `lineage-stop`. Diagnostics, clear responses, and `POST /api/missions/:missionId/resume` `409 MISSION_RESUME_CONFLICT` use the same array, deduplicated on `(rootFeatureId, source, reason)` while preserving same-root entries with different sources.
Feature-validation repair controls repair feature state only; they intentionally do not modify a mission-level status badge.
@@ -643,7 +643,7 @@ A feature transitions to `blocked` when:
- `MilestoneValidationRollup.state` reflects `blocked` assertions
- The feature remains in `blocked` state until operator intervention
- Deleting a generated fix, or archiving/deleting its generated task, records a durable root-scoped `operator-intervention` stop in the same transaction as unlink/removal. Recovery, duplicate delivery, unarchive, task/root recreation, and relinking cannot mint a sibling. The stop remains even if a hierarchy cascade removes root and lineage rows.
- `POST /api/missions/:missionId/resume` is the sole resume seam. It atomically clears only operator-intervention stops, preserves attempt counts, moves extant roots to `needs_fix`, and activates the mission. If any root is non-resumable, it changes no root, tombstone, counter, or mission state and returns HTTP 409 with `code: "MISSION_RESUME_CONFLICT"`, `blockerSchemaVersion: 1`, and `blockers: MissionBlockerDescriptor[]`. A descriptor has `schemaVersion: 1`, `kind: "mission-resume-conflict"`, `rootFeatureId`, closed `reason` (`budget-exhausted`, `operator-intervention`, or fail-closed `legacy-unknown-stop`), and `source` (`feature-row` or `lineage-stop`); lineage stops also retain `stoppedAt` and `origin`. Unknown or empty persisted reasons normalize to `legacy-unknown-stop`, retaining a non-empty persisted value as `rawReason`. Consumers must treat an unrecognized `blockerSchemaVersion` as non-resumable and ask an operator rather than guessing. `legacyBlockers: Array<{ id, reason }>` mirrors the v0 shape for one deprecation window and will be removed after supported readers migrate.
- `POST /api/missions/:missionId/resume` is the sole resume seam. It atomically clears only operator-intervention stops, preserves attempt counts, moves extant roots to `needs_fix`, and activates the mission. If any root is non-resumable, it changes no root, tombstone, counter, or mission state and returns HTTP 409 with `code: "MISSION_RESUME_CONFLICT"`, `blockerSchemaVersion: 1`, and `blockers: MissionBlockerDescriptor[]`. A descriptor has `schemaVersion: 1`, `kind: "mission-resume-conflict"`, `rootFeatureId`, closed `reason` (`budget-exhausted`, `operator-intervention`, or fail-closed `legacy-unknown-stop`), and `source` (`feature-row` or `lineage-stop`); lineage stops also retain `stoppedAt` and `origin`. Unknown or empty persisted reasons normalize to `legacy-unknown-stop`, retaining a non-empty persisted value as `rawReason`. The canonical array is deduplicated on `(rootFeatureId, source, reason)` while preserving distinct cross-source provenance. Consumers must treat an unrecognized `blockerSchemaVersion` as non-resumable and ask an operator rather than guessing. FN-8979 retired the v0 mirror; `legacyBlockers` is not part of this response.
On engine restart, `recoverActiveMissions()` re-enqueues features in `validating` or `needs_fix` states, ensuring no validation work is lost. It also re-triggers `implementing` features whose linked task is already `done`/`archived` and whose assertion validation has not passed yet. When the stale-run reaper has already converted an abandoned validator run into `needs_fix`, `processTaskOutcome()` promotes the feature back through `implementing` and re-validates instead of skipping it. The same recovery path is replayed during periodic self-heal maintenance, so historically stranded `implementing` features can self-heal without requiring an engine restart.

View File

@@ -122,3 +122,17 @@ Source: [Runfusion/Fusion issue #2862](https://github.com/Runfusion/Fusion/issue
| shape C: uncapped default-config PostgreSQL directory ×5 | 153 files / 1263 passed plus 1 skipped each run |
FN-8928 evicted the file from the blocking gate under the AGENTS.md gate rule; default-core discovery preserves its regression coverage. Shape C was clean, so no quarantine escalation was required. A later non-blocking-core failure is an ordinary on-sight quarantine decision. `FNXC:PgTestTemplateDb 2026-07-19-17:20` (run-shared golden template) and `FNXC:PgTestWorkerCap 2026-07-18-18:00` (four-fork PG-gate cap) are already-landed mitigations for this same 15s setup-hook timeout mode.
## 7. Mission store PostgreSQL teardown hook
- **File:** `packages/core/src/__tests__/postgres/mission-store.pg.test.ts`
- **Exact test:** `MissionStore (PostgreSQL backend mode)` suite `afterAll` hook (`h.afterAll`).
- **Observed tree/SHA:** `32f677bbc207e421fd260ae2ba22fcefeeef4d86` (FN-8979 worktree).
- **Observed frequency:** first observation in a direct targeted rerun; 61 tests in the file passed.
| run | result |
|---|---|
| targeted file with `--silent=passed-only` | passed (exit 0) |
| targeted file with dot reporter | **afterAll hook timed out** at 15s; 61 tests passed |
The timeout occurred after all test assertions and is unrelated to FN-8979's canonical mission-blocker contract. This file retains substantial coverage, so this first observation is recorded rather than quarantined. A second sighting requires the normal file-level quarantine decision.