Protect ancestors of locked snapshots during pruning

Pruning could delete unlocked intermediate nodes between locked
snapshots and the root, creating orphan branches. Now ancestors of
all locked snapshots are added to the protected set in both regular
and node capture paths.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-27 00:39:23 +01:00
parent fd14d4f1a6
commit 320ab8647d

View File

@@ -1453,16 +1453,24 @@ async function captureSnapshot(label = "Auto") {
try {
await db_put(record);
if (branchingEnabled) {
// Compute protected IDs: ancestors of this capture + fork points
// Compute protected IDs: ancestors of this capture + fork points + ancestors of locked snapshots
const allRecs = await db_getAllForWorkflow(workflowKey);
const tempTree = buildSnapshotTree(allRecs);
const ancestors = getAncestorIds(record.id, tempTree.parentOf);
const protectedIds = getAncestorIds(record.id, tempTree.parentOf);
// Protect fork points (snapshots with >1 child)
for (const [pid, children] of tempTree.childrenOf) {
if (children.length > 1) ancestors.add(pid);
if (children.length > 1) protectedIds.add(pid);
}
ancestors.add(record.id); // protect the just-captured snapshot
await pruneSnapshots(workflowKey, [...ancestors]);
// Protect ancestors of locked snapshots to prevent orphan branches
for (const rec of allRecs) {
if (rec.locked) {
for (const aid of getAncestorIds(rec.id, tempTree.parentOf)) {
protectedIds.add(aid);
}
}
}
protectedIds.add(record.id); // protect the just-captured snapshot
await pruneSnapshots(workflowKey, [...protectedIds]);
} else {
await pruneSnapshots(workflowKey);
}
@@ -1525,13 +1533,20 @@ async function captureNodeSnapshot(label = "Node Trigger") {
try {
await db_put(record);
if (branchingEnabled) {
// Compute protected IDs: ancestors + fork points
// Compute protected IDs: ancestors + fork points + ancestors of locked snapshots
const allRecs = await db_getAllForWorkflow(workflowKey);
const tempTree = buildSnapshotTree(allRecs);
const protectedNodeIds = getAncestorIds(record.id, tempTree.parentOf);
for (const [pid, children] of tempTree.childrenOf) {
if (children.length > 1) protectedNodeIds.add(pid);
}
for (const rec of allRecs) {
if (rec.locked) {
for (const aid of getAncestorIds(rec.id, tempTree.parentOf)) {
protectedNodeIds.add(aid);
}
}
}
protectedNodeIds.add(record.id);
await pruneNodeSnapshots(workflowKey, [...protectedNodeIds]);
} else {