Files
suaveplan/openspec/changes/_superseded-narrow-deferrals-2026-05-17/add-installer-upgrade/tasks.md
2026-05-27 23:29:53 +01:00

135 lines
6.4 KiB
Markdown

# Tasks: add-installer-upgrade
## 1. Error codes
- [ ] 1.1 Append `UPGRADE_NOT_INSTALLED`, `UPGRADE_VERSION_NOT_FOUND`,
`UPGRADE_SAME_VERSION`, `UPGRADE_DIRTY_TREE`, `UPGRADE_STATE_INVALID`,
`UPGRADE_MIGRATION_FAILED`, `UPGRADE_LOCKFILE_MISSING` to the
`defineCodes("INSTALLER", [...])` tuple in
`packages/tooling/installer/src/error-codes.ts`.
- [ ] 1.2 Extend `src/error-codes.spec-conformance.test.ts` with an
`INSTALLER-UPGRADE-09` block asserting the codes are present, frozen,
and prefixed `INSTALLER.`.
## 2. State file (`src/state/`)
- [ ] 2.1 `src/state/state.types.ts` — `InstallerState`, `InstalledPlugin`,
`FileHash` types.
- [ ] 2.2 `src/state/state.schemas.ts` — Zod schemas for boundary validation.
- [ ] 2.3 `src/state/state-file.ts` — `loadState(fs, cwd)`,
`writeState(fs, cwd, state)` (atomic write → fsync → rename),
`updatePlugin(state, entry)`. Failure → `INSTALLER.UPGRADE_STATE_INVALID`.
- [ ] 2.4 `src/state/hash-tree.ts` — `hashTree(fs, dir)` → `Record<path, hex>`
using BLAKE3 via `@suaveplan/crypto-hash`.
- [ ] 2.5 `src/state/state-file.spec-conformance.test.ts` —
`INSTALLER-UPGRADE-08`.
- [ ] 2.6 `.md` siblings for every implementation file.
## 3. Pre-flight checks (`src/host/upgrade/preflight.ts`)
- [ ] 3.1 `detectLockfile(fs, cwd)` returning `"bun" | "npm" | "pnpm" | "yarn"`
or throwing `INSTALLER.UPGRADE_LOCKFILE_MISSING`.
- [ ] 3.2 `detectDirtyTree(state, currentHashes)` returning the list of
user-modified paths. Throws `INSTALLER.UPGRADE_DIRTY_TREE` unless
`--allow-dirty` or `--force` was supplied.
- [ ] 3.3 `src/host/upgrade/preflight.spec-conformance.test.ts` —
`INSTALLER-UPGRADE-04`.
- [ ] 3.4 `src/host/upgrade/dirty-tree.spec-conformance.test.ts` —
`INSTALLER-UPGRADE-03`.
## 4. Diff engine (`src/host/upgrade/diff.ts`)
- [ ] 4.1 `computeDiff(fromOps, toOps)` returning
`{ added: FileOp[], removed: FileOp[], changed: FileOp[], unchanged: FileOp[] }`.
- [ ] 4.2 `materialiseDiff(diff, options)` translating the diff into the
existing `FileOp` union: removals → `{ kind: "delete" }`, additions →
`{ kind: "write" }`, changes → `{ kind: "write", conflict: "overwrite" }`
(or `merge` for `patch-json` ops).
- [ ] 4.3 Sibling `diff.md` covering algorithm and tie-breaking rules.
## 5. Migration runner (`src/host/upgrade/migrations.ts`)
- [ ] 5.1 `MigrationScript`, `MigrationContext`, `MigrationTable` types in
`src/host/upgrade/migrations.types.ts`.
- [ ] 5.2 `loadMigrationTable(plugin)` dynamic-imports the plugin's
`./upgrade.js` if declared in the manifest; absent table → no-op.
- [ ] 5.3 `selectMigrations(table, from, to)` returns the ordered semver path.
- [ ] 5.4 `runMigrations(selected, ctx)` runs each script inside an
`AbortSignal`-propagating sandbox. Throw → `INSTALLER.UPGRADE_MIGRATION_FAILED`.
- [ ] 5.5 `src/host/upgrade/migrations.spec-conformance.test.ts` —
`INSTALLER-UPGRADE-06`, `INSTALLER-UPGRADE-07`.
## 6. Upgrade orchestration (`src/host/upgrade/upgrade.ts`)
- [ ] 6.1 `Host.upgrade(plugin, options)` method:
1. Load state-file entry; throw `UPGRADE_NOT_INSTALLED` if absent.
2. Resolve target version; throw `UPGRADE_VERSION_NOT_FOUND` if
`node_modules` resolution fails.
3. Short-circuit with `UPGRADE_SAME_VERSION` when installed === target
and not `--force`.
4. Run preflight (lockfile + dirty-tree).
5. Hash current tree; compute snapshot for rollback.
6. Run `plugin.plan` for both versions (or load cached bundle manifests).
7. `computeDiff` → `materialiseDiff` → `Host.applyOps` (existing
transactional path).
8. Run migrations between the same staged-tmp transaction window.
9. On success, `writeState` atomically.
- [ ] 6.2 OTel span `installer.upgrade` wrapping the whole flow with
`plugin.id`, `plugin.version.from`, `plugin.version.to`, `dry_run`.
- [ ] 6.3 `src/host/upgrade/upgrade.spec-conformance.test.ts` —
`INSTALLER-UPGRADE-01`, `INSTALLER-UPGRADE-02`, `INSTALLER-UPGRADE-10`,
`INSTALLER-UPGRADE-11`.
- [ ] 6.4 `src/host/upgrade/apply-diff.spec-conformance.test.ts` —
`INSTALLER-UPGRADE-05`.
## 7. CLI wiring (`src/bin/suaveplan-install.ts`)
- [ ] 7.1 Add `app.command("upgrade <plugin>")` with flags `--dry-run`,
`--force`, `--no-interactive`, `--answers <file>`, `--from <version>`,
`--to <version>`, `--allow-dirty`.
- [ ] 7.2 `runUpgrade(pluginId, flags)` mirrors `runAdd` but calls
`host.upgrade(...)`.
- [ ] 7.3 `src/bin/suaveplan-install.spec-conformance.test.ts` —
`INSTALLER-UPGRADE-12`.
## 8. Testing helpers (`src/testing/`)
- [ ] 8.1 `src/testing/upgrade-plugin.ts` — `upgradePlugin(plugin, { fromState, toBundle, answers? })`
mirroring the existing `runPlugin` harness for upgrade tests.
- [ ] 8.2 `src/testing/fixtures/upgrade-fixture-plugin.ts` — two-version
fixture plugin (`1.0.0` + `2.0.0`) with one migration script, used by
every upgrade conformance test for end-to-end coverage at 100%.
## 9. Docs
- [ ] 9.1 Sibling `.md` for every new implementation `.ts` per
`openspec/AGENTS.md` §4 (Purpose, Features, Basic, Advanced, API, Notes).
- [ ] 9.2 Update root `packages/tooling/installer/README.md` Quickstart to
include the `upgrade` verb and link the new module docs.
## 10. Quality gates
- [ ] 10.1 `bunx turbo lint typecheck test --filter=@suaveplan/installer` green.
- [ ] 10.2 `bunx turbo test:coverage --filter=@suaveplan/installer` 100%
istanbul on every new module.
- [ ] 10.3 No banned patterns (`openspec/AGENTS.md` §2) in new files.
- [ ] 10.4 No `biome-ignore` in new files (`openspec/AGENTS.md` §2a).
- [ ] 10.5 File-size check: every `src/host/upgrade/*.ts` ≤ 300 lines,
every `*.test.ts` ≤ 500 lines.
- [ ] 10.6 Sibling `.md` audit returns zero gaps for new files.
## 11. Dependent sweep
- [ ] 11.1 `bunx turbo test --filter=...@suaveplan/installer` green
(validates `@suaveplan/install-agent-skills` against the new surface).
- [ ] 11.2 Add empty `upgrade.ts` migration table export to
`@suaveplan/install-agent-skills` and wire it through its manifest so
the migration-loader path is exercised even with zero migrations.
## 12. Archive
- [ ] 12.1 Fold `specs/installer/spec.md` delta into
`openspec/specs/installer/spec.md`.
- [ ] 12.2 Move `openspec/changes/add-installer-upgrade/` to
`openspec/archive/<yyyy-mm-dd>-add-installer-upgrade/`.