Skip to content

feat(migration): freeze reversible cutover protocol - #677

Open
bcdonadio wants to merge 25 commits into
mainfrom
feat/621-migration-protocol
Open

feat(migration): freeze reversible cutover protocol#677
bcdonadio wants to merge 25 commits into
mainfrom
feat/621-migration-protocol

Conversation

@bcdonadio

Copy link
Copy Markdown
Contributor

Summary

Freeze the reversible SQLite/PostgreSQL migration protocol before any data-movement or cutover implementation begins. This adds strict checksum-sealed manifests, legal effect reducers, deterministic recovery classification, and a private crash-recoverable immutable revision journal.

Motivation / Why

Epic #92 needs one durable, fail-closed contract shared by planning, copy, reconciliation, activation, and rollback. Without this foundation, later stages could disagree about legal transitions, evidence requirements, crash recovery, or which persisted state is authoritative.

Type of change

  • Bug fix
  • New feature
  • Refactoring (no behavior change)
  • Chore / infra / config
  • Documentation

Release-note title

feat(migration): freeze reversible cutover protocol

What changed

  • Add the strict versioned migration manifest, canonical SHA-256 sealing, immutable reducers, abandonment rules, and pure recovery classifier.
  • Add a descriptor- and ownership-aware durable store with exact 0700/0600 modes, immutable checksum-named revisions, exact-head compare-and-swap, revision exhaustion handling, and bounded one-hop recovery.
  • Extract the shared authenticated home-lock topology from backend publication and thread explicit expected UID policy through the underlying private-directory durability helpers.
  • Export the protocol/store through src/migration/index.ts and add exclusive Codecov ownership (unit-migration-cutover, 30 components, 195 production TypeScript files).
  • Add operator guidance and a minor Changeset. This PR does not copy, activate, or roll back data; those remain separate dependent issues.

Testing done

  • npm run test:ci
    • 6,574 tests
    • 0 failures, 0 errors
    • 100% statements, branches, functions, and lines across the complete collected production scope
  • npm run typecheck
  • npm run lint
  • git diff --check
  • MoM adversarial review: GLM-5.2 max APPROVE; xai/grok-4.6 max was attempted but provider-rejected its reasoning effort, so the required Luna max fallback APPROVED; Opus second pass APPROVED.

Risk / rollout

  • Risk: security-sensitive local journal and recovery behavior; malformed, ambiguous, raced, or unsafe evidence must fail closed.
  • Mitigation: exhaustive tamper/crash tests, exact CAS tests, per-file 100% coverage, two independent exact-recovery approvals, complete-range adversarial review, and a second-pass review.
  • Rollout: additive foundation only. Existing SQLite storage behavior remains unchanged, and no production cutover command is enabled by this PR.

Related issues

Closes #621

Copilot review focus areas

  • Validate canonical checksum/exact-key invariants and every legal phase/effect/evidence row.
  • Validate retained-directory identity checks, owner/mode/link/size bounds, publication ordering, exact raw-head CAS, and evidence-preserving one-hop recovery.
  • Confirm backend publication behavior is unchanged by the shared home-topology extraction.
  • Confirm Codecov ownership remains complete and exclusive with no flags, ignores, statuses, or threshold changes.

Checklist

  • No new dependency or lockfile change
  • No implicit any — all public seams are typed
  • No new route or hot-path collectStats() use
  • No database schema migration
  • Complete npm run test:ci passes at 100% coverage
  • User-facing behavior is documented and has a Changeset

Signed-off-by: Bernardo Donadio <bcdonadio@bcdonadio.com>
Introduce the frozen migration manifest contract with exact-key parsing,
bounded identifier and hash validation, ISO timestamp round-trip checks,
cross-field phase invariants, and a deterministic canonical SHA-256 seal
computed over the payload with the checksum field omitted.

Checkpoints and reports are canonically ordered before hashing and before
freezing, so the seal is invariant to input collection order.

Signed-off-by: Bernardo Donadio <bcdonadio@bcdonadio.com>
…k lineage

Checkpoint ordinals are scoped to their domain, so two distinct domains may
share an ordinal. Remove the incorrect global ordinal uniqueness check while
keeping domain uniqueness (latest checkpoint per domain).

Retain the returnPhase sealed by prepare-rollback once publish-rollback
settles: rolled-back manifests now require both a rollback mode and a sealed
return phase instead of forcing the return phase back to null.

Cover the remaining protocol branches, including valid rolling-back and
rolled-back manifests, the legal effect table rows, canonical cycle
rejection, comparator equality, identifier length bounds, and
createMigrationManifest invalid-input rejection. Fix the missing-key test to
delete the key rather than assign undefined.

Signed-off-by: Bernardo Donadio <bcdonadio@bcdonadio.com>
Signed-off-by: Bernardo Donadio <bcdonadio@bcdonadio.com>
Signed-off-by: Bernardo Donadio <bcdonadio@bcdonadio.com>
Signed-off-by: Bernardo Donadio <bcdonadio@bcdonadio.com>
Signed-off-by: Bernardo Donadio <bcdonadio@bcdonadio.com>
Signed-off-by: Bernardo Donadio <bcdonadio@bcdonadio.com>
Signed-off-by: Bernardo Donadio <bcdonadio@bcdonadio.com>
Signed-off-by: Bernardo Donadio <bcdonadio@bcdonadio.com>
Signed-off-by: Bernardo Donadio <bcdonadio@bcdonadio.com>
Signed-off-by: Bernardo Donadio <bcdonadio@bcdonadio.com>
Signed-off-by: Bernardo Donadio <bcdonadio@bcdonadio.com>
Signed-off-by: Bernardo Donadio <bcdonadio@bcdonadio.com>
Signed-off-by: Bernardo Donadio <bcdonadio@bcdonadio.com>
Signed-off-by: Bernardo Donadio <bcdonadio@bcdonadio.com>
Signed-off-by: Bernardo Donadio <bcdonadio@bcdonadio.com>
Signed-off-by: Bernardo Donadio <bcdonadio@bcdonadio.com>
Signed-off-by: Bernardo Donadio <bcdonadio@bcdonadio.com>
Signed-off-by: Bernardo Donadio <bcdonadio@bcdonadio.com>
Signed-off-by: Bernardo Donadio <bcdonadio@bcdonadio.com>
Signed-off-by: Bernardo Donadio <bcdonadio@bcdonadio.com>
Copilot AI lite review requested due to automatic review settings August 13, 2026 00:17
Comment thread test/backend-publication.test.ts Fixed
Comment thread test/backend-publication.test.ts Fixed
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Freeze reversible migration protocol with sealed manifests and durable journal

✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Add checksum-sealed migration manifest contract with strict phase/effect legality reducers.
• Introduce crash-recoverable immutable revision journal with CAS head pointer under a home lock.
• Extract and reuse HOME lock topology authentication; expand docs, tests, and Codecov ownership.
Diagram

graph TD
  Caller([Migration workflow]) --> Store["MigrationManifestStore"] --> Sec["security-files"] --> Journal[("~/.lcm/migrations")]
  Store --> Proto["migration/protocol"] --> Classify["Recovery classification"]
  Store --> Topo["home-lock-topology"] --> Lock[".lcm.migration-manifest.lock"]
  subgraph Legend
    direction LR
    _actor(["Caller"]) ~~~ _mod["Module"] ~~~ _fs[("Private FS")]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Store journal in a small SQLite DB (append-only table + CAS head row)
  • ➕ Leverages transactional durability and concurrency primitives
  • ➕ Simplifies atomicity (no directory fsync choreography) and listing/recovery logic
  • ➖ Adds a new persistence format and bootstrapping story (and potentially new dependency/ops complexity)
  • ➖ Still needs strict owner/mode enforcement and tamper detection; may conflict with the 'no new dependencies' constraint
2. Single append-only log file (JSON Lines) instead of per-revision directories
  • ➕ Fewer filesystem objects; simpler on-disk layout
  • ➕ Natural chronological ordering for audit
  • ➖ Harder to guarantee immutability and safe truncation semantics across crashes
  • ➖ CAS and bounded recovery become trickier without rewriting/compaction; corruption can poison the whole log
3. Use a canonical JSON / schema validation library
  • ➕ Less bespoke parsing/canonicalization code; potentially fewer edge-case bugs
  • ➕ Could standardize validation errors
  • ➖ Introduces dependency and lock-in; may violate project constraints
  • ➖ Still requires custom canonical ordering rules and exact-field enforcement semantics

Recommendation: Given the stated constraints (fail-closed, no new dependencies, strict ownership/mode requirements, bounded recovery), the chosen design is appropriate: a pure protocol module + an explicit immutable revision store with checksum-derived filenames and CAS head replacement. Review focus should be on the invariants (canonicalization, checksum payload exclusion, legal transition table, and one-hop recovery refusal rules) rather than proposing a different storage substrate.

Files changed (17) +5190 / -143

Enhancement (4) +1794 / -0
index.tsExport migration protocol and manifest store from a single entrypoint +2/-0

Export migration protocol and manifest store from a single entrypoint

• Adds an index module that re-exports the protocol and durable store for downstream cutover work.

src/migration/index.ts

manifest-store.tsImplement durable immutable manifest revision journal with CAS head + recovery +832/-0

Implement durable immutable manifest revision journal with CAS head + recovery

• Adds a private filesystem-backed store that publishes immutable checksum-named revisions, updates head.json via compare-and-swap, enforces strict ownership/mode/link/size/canonical JSON invariants, and provides bounded one-hop crash recovery.

src/migration/manifest-store.ts

protocol.tsDefine strict migration manifest schema, sealing, reducers, and recovery classifier +806/-0

Define strict migration manifest schema, sealing, reducers, and recovery classifier

• Introduces versioned types, strict parsing with exact-field validation, canonical SHA-256 sealing (excluding checksum field), legal phase/effect transition rules, begin/complete/abandon reducers with evidence validation, and deterministic recovery classification.

src/migration/protocol.ts

home-lock-topology.tsAdd reusable HOME lock topology authentication and mode restoration +154/-0

Add reusable HOME lock topology authentication and mode restoration

• Introduces retained-descriptor HOME + parent authentication (no-follow), expected-UID policy, and exact mode restoration/cleanup utilities used by home-level mutation locks.

src/storage/home-lock-topology.ts

Refactor (2) +39 / -137
security-files.tsThread explicit expected-UID policy through private directory assertions and durable writes +15/-7

Thread explicit expected-UID policy through private directory assertions and durable writes

• Extends private directory validation and sync/durable write helpers to accept an explicit expected UID (defaulting to current uid) so higher-level stores can enforce consistent ownership policy across retained descriptors and precondition reads.

src/security-files.ts

backend-publication.tsRefactor backend publication HOME authentication to shared topology helper +24/-130

Refactor backend publication HOME authentication to shared topology helper

• Replaces local HOME/parent descriptor validation and mode restoration logic with the new shared home-lock-topology module while preserving backend publication behavior and error sanitization.

src/storage/backend-publication.ts

Tests (6) +2713 / -6
backend-publication.test.tsAdd coverage for shared HOME topology behavior and restoration invariants +40/-2

Add coverage for shared HOME topology behavior and restoration invariants

• Extends backend publication tests to exercise open/assert/restore/close of the shared HOME lock topology helper and to ensure HOME mode restoration even across failures.

test/backend-publication.test.ts

codecov-config.test.tsUpdate Codecov ownership contract expectations (30 components, 195 files) +10/-4

Update Codecov ownership contract expectations (30 components, 195 files)

• Adjusts the Codecov config test to include the new migration component and the added production files, and validates updated ownership paths.

test/codecov-config.test.ts

index.test.tsTest migration module surface exports +12/-0

Test migration module surface exports

• Adds a small contract test ensuring the migration index exports the protocol creator and manifest store.

test/migration/index.test.ts

manifest-store.test.tsAdd exhaustive tamper/crash/CAS tests for durable manifest store +1174/-0

Add exhaustive tamper/crash/CAS tests for durable manifest store

• Introduces extensive filesystem-based tests covering directory/file mode and ownership enforcement, immutable revision publication, CAS head updates, bounded recovery behavior, and refusal modes for ambiguous or unsafe evidence.

test/migration/manifest-store.test.ts

protocol.test.tsAdd exhaustive reducer and parsing tests for migration protocol contract +1446/-0

Add exhaustive reducer and parsing tests for migration protocol contract

• Adds comprehensive tests for strict parsing, canonical sealing, legal transition rules, evidence constraints, abandonment rules, timestamp monotonicity, and deterministic recovery classification.

test/migration/protocol.test.ts

security-files.test.tsTest expected-UID threading through private directory and durable write helpers +31/-0

Test expected-UID threading through private directory and durable write helpers

• Adds unit tests ensuring explicit expected UID policy is honored by directory assertions, directory sync, and durable private file publication.

test/security-files.test.ts

Documentation (4) +639 / -0
freeze-reversible-migration-protocol.mdAdd minor Changeset for migration protocol foundation +5/-0

Add minor Changeset for migration protocol foundation

• Introduces a minor release note describing the new checksum-sealed manifest protocol and durable revision journal used by future cutover workflows.

.changeset/freeze-reversible-migration-protocol.md

README.mdLink new migration-cutover operator documentation +2/-0

Link new migration-cutover operator documentation

• Adds the reversible migration and cutover journal guide to the documentation index.

docs/README.md

migration-cutover.mdDocument reversible migration phases, evidence, and journal recovery contract +178/-0

Document reversible migration phases, evidence, and journal recovery contract

• Adds operator-facing documentation for the manifest schema, legal phase/effect transitions, evidence requirements, journal layout, CAS head semantics, and bounded crash recovery behavior.

docs/migration-cutover.md

2026-08-12-issue-621-migration-protocol.mdAdd implementation plan for issue #621 migration protocol +454/-0

Add implementation plan for issue #621 migration protocol

• Captures the frozen constraints, file map, and public contract for implementing the protocol and durable journal prior to any data movement.

docs/superpowers/plans/2026-08-12-issue-621-migration-protocol.md

Other (1) +5 / -0
codecov.ymlAdd Codecov component ownership for migration/cutover module +5/-0

Add Codecov component ownership for migration/cutover module

• Registers a new exclusive component (unit-migration-cutover) owning src/migration/, and includes the new home-lock-topology module under storage coverage ownership.

codecov.yml

@codecov

codecov Bot commented Aug 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ All tests successful. No failed tests found.

📢 Thoughts on this report? Let us know!

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5797f91991

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/migration/manifest-store.ts Outdated
Comment thread src/migration/manifest-store.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Freezes the reversible SQLite→PostgreSQL cutover contract by introducing a strict, checksum-sealed migration manifest state machine plus a crash-recoverable, immutable revision journal, while extracting shared $HOME lock-topology validation and threading explicit UID policy through filesystem durability primitives.

Changes:

  • Add src/migration/protocol.ts (strict manifest schema, canonical SHA-256 sealing, legal reducers, and deterministic recovery classification) and src/migration/manifest-store.ts (immutable revisions + head CAS + bounded one-hop recovery).
  • Extract retained $HOME/parent descriptor authentication into src/storage/home-lock-topology.ts and refactor backend publication to consume it.
  • Update docs/changeset and Codecov ownership taxonomy; add exhaustive unit tests for protocol/store and new UID-policy seams.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
test/security-files.test.ts Adds regressions for expected-UID threading through directory assertions and durable writes.
test/migration/protocol.test.ts Exhaustive protocol schema/reducer/recovery classification coverage.
test/migration/manifest-store.test.ts End-to-end durable store, CAS, crash boundary, tamper, and recovery tests.
test/migration/index.test.ts Verifies the migration barrel export surface.
test/codecov-config.test.ts Updates component contract (29→30) and production ownership counts (191→195).
test/backend-publication.test.ts Adds regressions for extracted HOME topology helper behavior + restoration on failure.
src/storage/home-lock-topology.ts New shared retained-descriptor HOME topology validator/restorer.
src/storage/backend-publication.ts Refactors HOME topology logic to shared helper without intended behavior changes.
src/security-files.ts Threads explicit expectedUid into retained directory assert + sync + durable publication reads.
src/migration/protocol.ts New sealed manifest protocol types, parsing, reducers, and recovery classifier.
src/migration/manifest-store.ts New private immutable manifest revision journal with head CAS and bounded recovery.
src/migration/index.ts New barrel export for migration protocol + store.
docs/superpowers/plans/2026-08-12-issue-621-migration-protocol.md Implementation plan documentation for issue #621.
docs/README.md Links the new operator guide.
docs/migration-cutover.md Operator guide for the protocol, layout, and recovery semantics.
codecov.yml Adds unit-migration-cutover component and assigns home-lock-topology ownership.
.changeset/freeze-reversible-migration-protocol.md Minor release note for the new protocol foundation.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/migration/protocol.ts
Signed-off-by: Bernardo Donadio <bcdonadio@bcdonadio.com>
Copilot AI review requested due to automatic review settings August 13, 2026 00:24
@qodo-code-review

qodo-code-review Bot commented Aug 13, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (1) 📜 Skill insights (0)

Grey Divider


Action required

1. Direct transitions bypass protocol ✓ Resolved 🐞 Bug ≡ Correctness
Description
MigrationManifestStore.update accepts any checksum-valid reducer output after checking only
generation, revision, and predecessor, so it can durably record plannedactive or mutate
immutable witnesses without the required effects and evidence. This does not execute activation in
this PR, but later workflows can trust an authoritative journal that falsely claims the work
completed.
Code

src/migration/manifest-store.ts[R731-734]

+      const candidate = parseMigrationManifest(reduce(current.manifest));
+      if (
+        candidate.generationId !== generationId
+        || candidate.revision !== previousRevision + 1
Relevance

●● Moderate

Non-trivial protocol invariant/transition enforcement change; no close precedent for reducer-output
validation in a store API.

PR-#544

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The store parses and seals caller-controlled reducer output but compares only three chain fields. In
contrast, protocol successors are created through narrow overrides, and the documented contract
states that only listed effect transitions are legal and direct phase edits are refused.

src/migration/manifest-store.ts[713-780]
src/migration/protocol.ts[582-600]
docs/migration-cutover.md[39-59]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`MigrationManifestStore.update` persists arbitrary checksum-valid successors without proving they follow the migration state machine or preserve predecessor-bound fields.

## Issue Context
A checksum authenticates one manifest's contents, not its legal relationship to the current manifest. Restrict the update seam to protocol-controlled operations or validate every predecessor-relative transition, including immutable witnesses, phases, pending effects, evidence, checkpoints, reports, timestamps, and rollback lineage.

## Fix Focus Areas
- src/migration/manifest-store.ts[713-780]
- src/migration/protocol.ts[544-600]
- src/migration/protocol.ts[694-790]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Migration doc missing usage/config ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
docs/migration-cutover.md explains the protocol but lacks a clear end-user "how to use" section
and does not list configuration options or explicitly state there are none. This makes the doc
incomplete under the feature-doc requirements for purpose/usage/configuration.
Code

docs/migration-cutover.md[R1-12]

+# Reversible migration and cutover journal
+
+LCM has a checksum-sealed protocol for future SQLite-to-PostgreSQL migration
+and cutover workflows. It records what a migration generation intends to do,
+which external effect is pending, and which immutable evidence was accepted at
+each step. A private durable journal makes an interrupted protocol run
+recoverable without guessing from timestamps or partially changed data.
+
+This foundation does **not** copy data, activate PostgreSQL, change the current
+storage backend, or execute rollback by itself. Later migration commands will
+perform those effects and use this protocol to record their boundaries.
+
Relevance

●●● Strong

Docs checklist “usage/config or none” is repeatedly enforced and accepted in recent docs PRs.

PR-#670
PR-#666
PR-#592

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The added documentation describes the protocol’s concepts and constraints, but it does not provide a
dedicated end-user usage section (steps/commands/API calls) and does not document configuration
options or explicitly state that there are none, violating the checklist’s completeness requirement.

Rule 2446384: Feature docs in docs/ must cover purpose, usage, and configuration for end users
docs/migration-cutover.md[1-18]
docs/migration-cutover.md[160-178]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new feature doc under `docs/` does not clearly include end-user usage instructions and does not describe configuration options (or explicitly state there are none).

## Issue Context
Compliance requires feature docs in `docs/` to cover: what the feature does, how to use it, and configuration options (or an explicit statement that there are no configurable options).

## Fix Focus Areas
- docs/migration-cutover.md[1-40]
- docs/migration-cutover.md[170-178]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Publication crash blocks recovery ✓ Resolved 🐞 Bug ☼ Reliability
Description
The store creates the final numeric revision directory before writing its manifest, so SIGKILL,
process death, or power loss during the durable write can leave an empty or temporary-only
directory. Recovery intentionally rejects that directory as ambiguous, while subsequent
create/update calls require it to be absent, leaving the generation unavailable until operator
intervention despite the automatic crash-recovery goal.
Code

src/migration/manifest-store.ts[R748-751]

+      const revisionDirectory = ensurePrivateChild(
+        current.revisions,
+        revisionName,
+        this.#expectedUid,
Relevance

●● Moderate

Crash-safety semantics change is substantial; repo likes hardening, but no direct precedent on
revision-dir publication ordering.

PR-#182
PR-#544

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Both genesis and update create the final revision directory before calling the durable file writer.
That writer first creates a temporary file inside the directory, while recovery accepts only exactly
one checksum-shaped filename; retries also reject the already-existing generation or revision
directory.

src/migration/manifest-store.ts[563-625]
src/migration/manifest-store.ts[713-780]
src/migration/manifest-store.ts[257-301]
src/security-files.ts[876-926]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Abrupt termination after creating the final successor directory but before linking the immutable manifest leaves a state that is neither automatically recoverable nor retryable.

## Issue Context
Ordinary thrown errors receive helper cleanup; the problematic case is process or machine termination. Preserve fail-closed handling for unknown evidence while ensuring the final numeric directory becomes visible only after a complete, durable checksum-named manifest exists, or add a safely distinguishable staging protocol.

## Fix Focus Areas
- src/migration/manifest-store.ts[563-625]
- src/migration/manifest-store.ts[713-780]
- src/migration/manifest-store.ts[257-301]
- src/security-files.ts[876-926]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

4. Plan doc not end-user 📘 Rule violation ⚙ Maintainability
Description
The new file is written as an internal implementation plan for agentic workers rather than end-user
documentation, and it does not provide end-user purpose/usage/configuration coverage. This violates
the requirement that feature docs under docs/ be end-user oriented and include purpose, usage, and
configuration guidance (or explicitly state none).
Code

docs/superpowers/plans/2026-08-12-issue-621-migration-protocol.md[R1-4]

+# Issue #621 Reversible Migration Protocol Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
Relevance

● Weak

Team previously rejected moving/rewriting “For agentic workers” plan docs to be end-user docs under
docs/.

PR-#611

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The file explicitly frames itself as an implementation plan "for agentic workers" and proceeds as
internal engineering guidance rather than end-user usage/configuration documentation, which fails
the checklist requirement for feature docs under docs/.

Rule 2446384: Feature docs in docs/ must cover purpose, usage, and configuration for end users
docs/superpowers/plans/2026-08-12-issue-621-migration-protocol.md[1-18]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A newly added file under `docs/` is an internal implementation plan (aimed at agentic workers) and does not meet the end-user feature-doc requirements (purpose, usage, configuration/explicit none).

## Issue Context
Compliance requires that feature documentation under `docs/` be written for end users and include: what it does, how to use it, and configuration options (or explicitly state there are none).

## Fix Focus Areas
- docs/superpowers/plans/2026-08-12-issue-621-migration-protocol.md[1-60]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context
✅ Compliance rules (platform): 143 rules
Review mode: 🧠 Deep: This security-sensitive migration protocol introduces substantial new state-machine, cryptographic sealing, crash recovery, filesystem durability, ownership, and CAS logic across multiple independent code paths, creating a dense set of easy-to-miss defects with high rollback and data-integrity risk.

Grey Divider

Tip of the day
💡 Did you know, you can type 'qodo, fix this' on a finding and the fix lands right on your PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread docs/migration-cutover.md
Comment thread src/migration/manifest-store.ts Outdated
Comment thread src/migration/manifest-store.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.

Suppressed comments (2)

src/migration/protocol.ts:188

  • isSafeNonNegativeInteger treats -0 as a valid integer (Number.isSafeInteger(-0) and -0 >= 0), which allows manifests with revision: -0 and other integer fields to pass parsing. Because JSON serialization and your canonical hashing normalize -0 to 0, this can produce non-idempotent values (e.g., a manifest accepted with -0 will round-trip/persist as 0, so create()/read() can disagree under strict equality). Consider rejecting -0 (or normalizing it to 0) for all protocol integer fields to keep the in-memory object stable across serialization and storage.
function isSafeNonNegativeInteger(value: unknown): value is number {
  return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
}

src/migration/manifest-store.ts:49

  • isSafeRevision accepts -0 for head revisions (Number.isSafeInteger(-0) and -0 >= 0). Since head/manifest content is canonicalized via string serialization (which normalizes -0 to 0), accepting -0 here can lead to non-canonical or non-idempotent in-memory values vs persisted bytes. It’s safer to either reject -0 or normalize it to 0 when validating/parsing head and revision numbers.
function isSafeRevision(revision: unknown): revision is number {
  return typeof revision === "number" && Number.isSafeInteger(revision) && revision >= 0;
}

Signed-off-by: Bernardo Donadio <bcdonadio@bcdonadio.com>
Copilot AI review requested due to automatic review settings August 13, 2026 07:38
[root, migrations, generation, revisions],
this.#expectedUid,
() => {
const headPath = migrationManifestHeadPath(generationId, this.#homeDir);

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 777b80e26d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/migration/protocol.ts
Comment on lines +518 to +519
const source = parseWitness(input.source, "source", "invalid-input");
const destination = parseWitness(input.destination, "destination", "invalid-input");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Enforce SQLite-to-PostgreSQL witness roles

Reject manifests unless the source witness is sqlite and the destination witness is postgresql. These calls validate each backend only against the shared two-value enum, so reversed or same-backend pairs are accepted and can be durably stored even though docs/migration-cutover.md:35-37 defines their directional roles; later copy and cutover code would consequently operate against incorrectly typed endpoints.

Useful? React with 👍 / 👎.

Comment on lines +659 to +663
consumeBoundedRegularFile(join(revisionDirectory, scratch), {
allowedRoot: revisionDirectory,
maxBytes: MAX_MANIFEST_BYTES,
expectedUid,
allowedModes: [0o600],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Authenticate scratch contents before consuming them

When a crash leaves a truncated scratch, or the expected scratch pathname contains unrelated bytes, this consumes it based only on its filename, ownership, mode, and link count. The retry therefore erases ambiguous evidence and replaces it instead of failing closed as required for immutable publication; bind the scratch's raw bytes to the candidate's canonical content before unlinking it.

Useful? React with 👍 / 👎.

requireSingleLink: true,
});
}
beforeContentPublication(revisionDirectory);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Sync scratch removal before starting a replacement write

If the process or host crashes after the scratch is unlinked but before the replacement writer synchronizes this directory, the deletion is unordered with the new temporary entry. Recovery may then observe both the resurrected old scratch and a new scratch or manifest on different inodes and permanently reject the revision as ambiguous; fsync the revision directory immediately after consuming the scratch and before this publication callback/write boundary.

Useful? React with 👍 / 👎.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.

Signed-off-by: Bernardo Donadio <bcdonadio@bcdonadio.com>
Copilot AI review requested due to automatic review settings August 13, 2026 08:29

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1aba84e911

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1131 to +1134
atomicWritePrivateFileDurable(headPath, migrationManifestHeadContent(nextHead), {
expectedUid: this.#expectedUid,
expectedContentSha256: sha256(current.headContent),
maxExistingBytes: MAX_MANIFEST_BYTES,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Make head replacement an atomic compare-and-swap

When another same-UID process or operator rewrites head.json after atomicWritePrivateFileDurable performs its initial expected-content check but before publication, this update still overwrites those new bytes. The helper checks the hash only at src/security-files.ts:1074-1089 and later performs an unconditional renameSync at line 1140, so supplying expectedContentSha256 here does not provide the documented exact-head CAS and can erase conflicting journal evidence; bind the replacement atomically to the authenticated head identity or revalidate it immediately before a no-clobber publication.

Useful? React with 👍 / 👎.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.

Suppressed comments (2)

src/migration/manifest-store.ts:1151

  • recover() will currently throw a raw ENOENT if the generation directory does not exist, instead of returning a deterministic protocol error (e.g., "no recoverable genesis"). This makes recovery behavior depend on host filesystem error strings/codes and diverges from the documented sanitized MigrationProtocolError reasons.
      const generation = migrationManifestGenerationDirectory(generationId, this.#homeDir);
      const root = lcmRoot(this.#homeDir);
      const migrations = migrationRoot(this.#homeDir);
      const revisions = join(generation, "revisions");
      return withAuthenticatedDirectories(

src/migration/manifest-store.ts:1005

  • #readOptionalState treats a missing generation directory as a raw filesystem ENOENT (from openPrivateDirectory) rather than a protocol-level "head is absent" state. This makes the public read()/update() error contract depend on filesystem error details for the common case of an unknown generation ID.
    const generation = migrationManifestGenerationDirectory(generationId, this.#homeDir);
    const root = lcmRoot(this.#homeDir);
    const migrations = migrationRoot(this.#homeDir);
    const revisions = join(generation, "revisions");
    return withAuthenticatedDirectories(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Freeze the reversible migration manifest and state machine

2 participants