fix(hashline): validate unique stale anchor remaps
This commit is contained in:
@@ -168,6 +168,20 @@ function validateDuplicateAnchorContext(
|
||||
return checked;
|
||||
}
|
||||
|
||||
function validateUniqueAnchorContext(
|
||||
line: number,
|
||||
mapped: number,
|
||||
previousLines: readonly string[],
|
||||
lineMap: ReadonlyMap<number, number>,
|
||||
anchorLines: ReadonlySet<number>,
|
||||
): boolean {
|
||||
const offset = mapped - line;
|
||||
const after = nearestContextLine(line, 1, anchorLines, previousLines.length);
|
||||
if (after !== undefined) return lineMap.get(after) === after + offset;
|
||||
const before = nearestContextLine(line, -1, anchorLines, previousLines.length);
|
||||
return before !== undefined && lineMap.get(before) === before + offset;
|
||||
}
|
||||
|
||||
function validateRemappedAnchorContext(
|
||||
previousText: string,
|
||||
currentText: string,
|
||||
@@ -181,7 +195,12 @@ function validateRemappedAnchorContext(
|
||||
for (const line of anchorLines) {
|
||||
const mapped = lineMap.get(line);
|
||||
if (mapped === undefined) return false;
|
||||
if (!lineIsDuplicated(previousLines, line) && !lineIsDuplicated(currentLines, mapped)) continue;
|
||||
if (!lineIsDuplicated(previousLines, line) && !lineIsDuplicated(currentLines, mapped)) {
|
||||
if (!validateUniqueAnchorContext(line, mapped, previousLines, lineMap, anchorLines)) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!validateDuplicateAnchorContext(line, mapped, previousLines, lineMap, anchorLines)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -139,4 +139,22 @@ describe("Recovery — session-chain replay anchor-content gate", () => {
|
||||
|
||||
expect(recovered).toBeNull();
|
||||
});
|
||||
|
||||
it("refuses unique-line remaps when following context no longer matches", () => {
|
||||
const store = new InMemorySnapshotStore();
|
||||
const v0Text = lines("L1", "L2", "L3", "L4", "T", "L6");
|
||||
const h0 = store.record(PATH, v0Text);
|
||||
const v1Text = lines("X", "L1", "L2", "L3", "L4", "T", "T_CHANGED", "L6");
|
||||
store.record(PATH, v1Text);
|
||||
const { edits } = parsePatch("SWAP 5.=5:\n+MODEL");
|
||||
|
||||
const recovered = new Recovery(store).tryRecover({
|
||||
path: PATH,
|
||||
currentText: v1Text,
|
||||
fileHash: h0,
|
||||
edits,
|
||||
});
|
||||
|
||||
expect(recovered).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user