stacks: the box converts a PostgreSQL major as a guarded-update step (09 6.4 part 10, decisions 35/37/38)
gates / gates (push) Successful in 25s

A step whose ladder entry carries engine_conversion {service, engine, from, to}
converts the database: the old engine alone, the check (owners, roles,
extensions, per-table row counts), pg_dumpall validated by its completion line,
the volume emptied only after the undo copy's marker is validated again, the new
engine alone, the load with ON_ERROR_STOP, the check again + PG_VERSION. Any
failure goes to the existing undo; a restart during converting is undone.
A PostgreSQL major move without the mark is refused before anything moves.
The old datadir's copy is kept until a backup is proven after the conversion.
17 tests, 9 red-proofs (audits/night-2026-09-26/B/).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-09-25 12:57:03 +02:00
parent 3d49df1e5a
commit 2caae38a71
13 changed files with 1523 additions and 12 deletions
+4 -1
View File
@@ -88,7 +88,9 @@ const appliedMetaDir = "applied-meta"
// AppliedMetaFile is the pinned version's .felhom.yml record (R-669, v0.270.0: the recovery unit captures
// it, so a restore brings back the PINNED version's health check, not the sync's newer one).
func AppliedMetaFile(stackDir string) string { return filepath.Join(stackDir, appliedMetaDir, ".felhom.yml") }
func AppliedMetaFile(stackDir string) string {
return filepath.Join(stackDir, appliedMetaDir, ".felhom.yml")
}
// RecordRestoredAppliedMeta makes the .felhom.yml a restore just wrote the pinned version's record
// (R-669, v0.270.0). MEASURED 2026-09-24 (A2): after a failed step ended in a restore, applied-meta still
@@ -555,6 +557,7 @@ func (m *Manager) tryUndo(ctx context.Context, name, dir, why string, entry *upd
m.recordUpdateUndone(name, dir, &UpdateUndone{To: entry.NewPin, At: start.UTC().Format(time.RFC3339), Why: why})
m.recordFailedStep(name, dir, entry.NewPin, "undone") // R-680
m.removePreUpdateCopies(dir)
_ = os.RemoveAll(filepath.Join(dir, preUpdateConvertDir)) // v0.273.0: the conversion's dump, if any
_ = m.RefreshStatus()
m.clearJournal(name)
if err := m.ScanStacks(); err != nil { // R-678: the page reads the pin the undo put back