mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-09-02 05:38:00 +00:00
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:
@@ -6,6 +6,7 @@ import type { DeclaredCompose } from '../helpers/composeDependencyParse';
|
||||
import { sha256Hex } from '../utils/hashing';
|
||||
import { sanitizeForLog } from '../utils/safeLog';
|
||||
import { getErrorMessage } from '../utils/errors';
|
||||
import { buildStackDriftReport } from './DriftDetectionService';
|
||||
import type { StackDriftReport, StackDriftFinding } from './DriftDetectionService';
|
||||
|
||||
/**
|
||||
@@ -130,6 +131,7 @@ export class DriftLedgerService {
|
||||
return { detected: 0, resolved: 0 };
|
||||
}
|
||||
const db = DatabaseService.getInstance();
|
||||
const now = Date.now();
|
||||
const openByKey = new Map(db.getOpenDriftFindings(nodeId, stackName).map(r => [findingKey(r.service, r.finding_type), r]));
|
||||
const currentByKey = new Map(report.findings.map(f => [findingKey(f.service, f.kind), f]));
|
||||
|
||||
@@ -141,12 +143,13 @@ export class DriftLedgerService {
|
||||
for (const [key, row] of openByKey) {
|
||||
if (!currentByKey.has(key)) toResolve.push(row);
|
||||
}
|
||||
if (toInsert.length === 0 && toResolve.length === 0) {
|
||||
return { detected: 0, resolved: 0 };
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
// Stamp the check time and apply any transitions in one transaction, so the
|
||||
// "checked {time ago}" the Drift tab shows can never persist without the ledger
|
||||
// update it describes. The stamp runs even on a no-op authoritative check (no
|
||||
// transitions), so the history's "as of" stays honest while a stale finding
|
||||
// reads as history rather than as live truth.
|
||||
db.getDb().transaction(() => {
|
||||
db.setStackDossierDriftCheck(nodeId, stackName, now);
|
||||
for (const f of toInsert) {
|
||||
db.insertDriftFinding({
|
||||
node_id: nodeId,
|
||||
@@ -176,6 +179,23 @@ export class DriftLedgerService {
|
||||
return { detected: toInsert.length, resolved: toResolve.length };
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the spatial report for one stack and reconcile it into the ledger.
|
||||
* Used by the deploy and update success hooks (and the rollback route, which
|
||||
* re-deploys through deployStack) so a change resolves the findings it fixed and
|
||||
* records what it left behind. Best-effort: a build or reconcile failure is
|
||||
* logged and swallowed so it never fails the deploy that triggered it.
|
||||
*/
|
||||
async reconcileStack(nodeId: number, stackName: string): Promise<DriftReconcileResult> {
|
||||
try {
|
||||
const report = await buildStackDriftReport(nodeId, stackName);
|
||||
return this.reconcile(nodeId, stackName, report);
|
||||
} catch (error) {
|
||||
console.error('[DriftLedger] reconcileStack failed for %s:', sanitizeForLog(stackName), sanitizeForLog(getErrorMessage(error, 'unknown')));
|
||||
return { detected: 0, resolved: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a drift transition to the stack activity timeline. History-only (no
|
||||
* external channel dispatch): a drift signal belongs in the activity feed, not
|
||||
|
||||
Reference in New Issue
Block a user