fix(drift): reconcile the drift ledger on deploy and timestamp its history (#1405)

* fix(drift): reconcile the drift ledger on deploy and timestamp its history

The drift ledger (persisted history + activity timeline) only advanced
when someone clicked re-check on a stack's Drift tab, so the history could
sit indefinitely out of sync with the live status: a stack reading
"drifted" live while its history still said "resolved". Two corrections:

- Deploy and update reconcile the ledger against the just-deployed runtime
  (the rollback route re-deploys through deployStack, so it is covered),
  resolving what the change fixed and recording what it left.
- Every authoritative reconcile stamps the dossier last-checked time, and
  the Drift tab labels its history "checked {time}" so a stale finding
  reads as history, not a claim about the live status above it.

Adds the last_drift_check_at column and tests across the ledger reconcile
stamp, reconcileStack, the deploy hook, and the panel.

* fix(drift): stamp last-checked inside the ledger transaction

Move the dossier last-checked stamp into the same transaction as the
finding insert/resolve, so the "checked {time}" the Drift tab shows can
never persist without the ledger update it describes. The stamp still runs
on a no-op authoritative check (a transaction that only stamps), keeping
the history "as of" honest. Adds a test that a failed deploy does not
reconcile the ledger.
This commit is contained in:
Anso
2026-06-21 18:20:57 -04:00
committed by GitHub
parent b9d8e9f490
commit f9c6c5fd09
9 changed files with 193 additions and 10 deletions
+10 -1
View File
@@ -455,6 +455,13 @@ export class ComposeService {
// route: bulk, Git-source, App Store, scheduler, and webhook deploys all funnel
// through this method. Internally guarded; awaited so it cannot race later work.
await DriftLedgerService.getInstance().recordBaseline(this.nodeId, stackName);
// Reconcile the ledger against the just-deployed runtime: findings this deploy
// fixed are resolved and any it left are recorded (and surfaced in the activity
// feed) now, instead of waiting for someone to open the Drift tab. The rollback
// route re-deploys through this method, so it is covered; a failed atomic deploy
// instead restores the previous files and throws above, so that recovery path
// reconciles on its next deploy or scan, not here. Best-effort internally.
await DriftLedgerService.getInstance().reconcileStack(this.nodeId, stackName);
}
streamLogs(stackName: string, ws: WebSocket) {
@@ -652,8 +659,10 @@ export class ComposeService {
throw updateError;
}
// Reached only on a successful update; re-baseline so temporal drift compares
// against what is now deployed (see deployStack for why this lives here).
// against what is now deployed (see deployStack for why this lives here), then
// reconcile the ledger against the updated runtime.
await DriftLedgerService.getInstance().recordBaseline(this.nodeId, stackName);
await DriftLedgerService.getInstance().reconcileStack(this.nodeId, stackName);
}
public async downStack(stackName: string): Promise<void> {