Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
229 changes: 227 additions & 2 deletions src/session/compaction-verify.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
repairSummary,
verifyCompactionSummary,
verifyOrRepair,
verifyAbortNoticeLabel,
VERIFY_REPAIR_HEADING,
} from "./compaction-verify.js";
import type {
Expand Down Expand Up @@ -421,7 +422,7 @@ describe("verifyCompactionSummary", () => {
}
});

test("uppercase HTTP URL scores hostname case-insensitively", () => {
test("a URL path is required; hostname alone does not cover it", () => {
const facts = extractContinuationFacts([
textTurn("user", "Fix auth now"),
{
Expand All @@ -442,6 +443,12 @@ describe("verifyCompactionSummary", () => {
"Fix auth now. Called api.com.",
facts,
).misses.some((m) => m.kind === "exactName"),
).toBe(true);
expect(
verifyCompactionSummary(
"Fix auth now. Called API.COM/v1.",
facts,
).misses.some((m) => m.kind === "exactName"),
).toBe(false);
expect(
verifyCompactionSummary(
Expand All @@ -453,6 +460,12 @@ describe("verifyCompactionSummary", () => {
});

describe("verifyOrRepair", () => {
test("names contradiction and repair overflow instead of a bare verify", () => {
expect(verifyAbortNoticeLabel("contradiction")).toBe("contradiction");
expect(verifyAbortNoticeLabel("repair-overflow")).toBe("repair overflow");
expect(verifyAbortNoticeLabel(undefined)).toBe("kept-prior");
});

test("repairs a lossy handoff instead of shipping it", () => {
const facts = extractContinuationFacts(droppedTurns());
const outcome = verifyOrRepair(
Expand All @@ -477,6 +490,7 @@ describe("verifyOrRepair", () => {
);
expect(outcome.aborted).toBe(true);
expect(outcome.repaired).toBe(false);
expect(outcome.abortReason).toBe("contradiction");
});

test("truncating repair that still misses exactName aborts", () => {
Expand All @@ -495,9 +509,10 @@ describe("verifyOrRepair", () => {
timestamp: 2,
},
]);
const outcome = verifyOrRepair("Fix auth now. Work continues.", facts, 90);
const outcome = verifyOrRepair("Fix auth now. Work continues.", facts, 80);
expect(outcome.aborted).toBe(true);
expect(outcome.repaired).toBe(false);
expect(outcome.abortReason).toBe("repair-overflow");
expect(outcome.misses.some((m) => m.kind === "exactName")).toBe(true);
});

Expand Down Expand Up @@ -527,6 +542,26 @@ describe("verifyOrRepair", () => {
expect(outcome.summary).toContain("exact-file.ts");
});

test("shrinks model prose so a carry-forward that fits the cap still verifies", () => {
const facts = extractContinuationFacts(droppedTurns());
const prose = "Padded model prose. ".repeat(40);
const misses = verifyCompactionSummary(prose, facts).misses;
const full = repairSummary(prose, facts, misses);
const appendixAt = full.indexOf(VERIFY_REPAIR_HEADING);
expect(appendixAt).toBeGreaterThan(200);
const appendix = full.slice(appendixAt).replace(/^\n+/, "");
const cap = appendix.length + 8;
expect(full.length).toBeGreaterThan(cap);
const outcome = verifyOrRepair(prose, facts, cap);
expect(outcome.aborted).toBe(false);
expect(outcome.repaired).toBe(true);
expect(outcome.summary.length).toBeLessThanOrEqual(cap);
expect(outcome.summary).toContain(VERIFY_REPAIR_HEADING);
expect(verifyCompactionSummary(outcome.summary, facts).supported).toBe(
true,
);
});

test("a constraint-only residual after a sliced repair aborts", () => {
const facts = extractContinuationFacts([
textTurn("user", "Fix auth now"),
Expand Down Expand Up @@ -662,10 +697,200 @@ describe("pruning compactor verify pass", () => {
expect(allText(result.output)).toContain("auth.ts");
} else if (kind === "aborted") {
expect(result.output).toBe(turns);
expect(result.record.decisions.verifyAbortReason).toBe("contradiction");
} else {
expect(allText(result.output)).not.toContain(VERIFY_REPAIR_HEADING);
}
});

test("an over-cap repair commits the fold and the handoff keeps critical facts", async () => {
const folded: ConversationTurn[] = [
textTurn("user", "Fix auth now"),
{
role: "assistant",
content: [
{
type: "tool_call",
id: "c1",
name: "read_file",
arguments: { path: "src/very-long-unique-path/exact-file.ts" },
},
],
timestamp: 2,
},
{
role: "user",
content: [
{
type: "tool_result",
callId: "c1",
isError: true,
content: [
{
type: "text",
text: "ZEBRA-BLOCKER-UNIQUE token refresh exploded",
},
],
},
],
timestamp: 3,
},
textTurn(
"assistant",
"Next step is to patch the zebra handler QUOKKA-NEXT",
),
textTurn("user", "Do not commit generated artifacts ever."),
];
const turns: ConversationTurn[] = [
...folded,
textTurn("user", "recent ask"),
textTurn("assistant", "recent reply"),
];
const compactor = createPruningCompactor({
summaryMaxChars: 80,
compactionShape: { tailBudgetTokens: 10 },
summarize: async () => "Work continues.",
});
const result = await compactor.apply(turns, mockStrategyCtx);
expect(result.record.decisions.verifyAborted).toBeUndefined();
expect(result.record.reason.startsWith("compacted")).toBe(true);
expect(result.output.length).toBeLessThan(turns.length);
const file = handoffFileText(result);
expect(file).toContain("exact-file.ts");
expect(file).toContain("ZEBRA-BLOCKER-UNIQUE");
expect(file).toContain("QUOKKA-NEXT");
expect(file).toContain("generated artifacts");
expect(file.toLowerCase()).toContain("fix auth");
const spine = allText(result.output);
expect(spine).toContain(COMPACTED_PREFIX);
expect(spine).not.toMatch(/\bno\s+errors?\b/i);
});

test("shrink-to-fit does not ship a spine that denies recorded failures", async () => {
const folded: ConversationTurn[] = [
textTurn("user", "Fix auth now"),
{
role: "assistant",
content: [
{
type: "tool_call",
id: "c1",
name: "read_file",
arguments: { path: "src/auth.ts" },
},
],
timestamp: 2,
},
{
role: "user",
content: [
{
type: "tool_result",
callId: "c1",
isError: true,
content: [{ type: "text", text: "token refresh assertion failed" }],
},
],
timestamp: 3,
},
textTurn("assistant", "Fix the token refresh assertion next."),
textTurn("user", "No errors remain"),
textTurn("user", "Keep going on the handler"),
];
const turns: ConversationTurn[] = [
...folded,
textTurn("user", "recent ask"),
textTurn("assistant", "recent reply"),
];
const prose = "Padded model prose. ".repeat(40);
const facts = extractContinuationFacts(folded);
const misses = verifyCompactionSummary(prose, facts).misses;
const full = repairSummary(prose, facts, misses);
const appendixAt = full.indexOf(VERIFY_REPAIR_HEADING);
const appendix = full.slice(appendixAt).replace(/^\n+/, "");
const cap = appendix.length + 8;
expect(full.length).toBeGreaterThan(cap);
const shrunk = verifyOrRepair(prose, facts, cap);
expect(shrunk.aborted).toBe(false);
expect(shrunk.summary.length).toBeLessThanOrEqual(cap);

const apply = (summaryMaxChars: number) =>
createPruningCompactor({
summaryMaxChars,
compactionShape: { tailBudgetTokens: 10 },
summarize: async () => prose,
}).apply(turns, mockStrategyCtx);

const fitted = await apply(cap);
const fittedSpine = allText(fitted.output);
const committed = fitted.output.length < turns.length;
if (committed) {
expect(fittedSpine).not.toMatch(/\bno\s+errors?\s+remain\b/i);
} else {
expect(fitted.output).toBe(turns);
expect(fitted.record.decisions.verifyAbortReason).toBe("contradiction");
}
expect(committed && /\bno\s+errors?\s+remain\b/i.test(fittedSpine)).toBe(
false,
);

const tight = await apply(80);
expect(tight.output).toBe(turns);
expect(tight.record.decisions.verifyAbortReason).toBe("contradiction");
});

test("an over-cap fold does not commit without the exact URL path", async () => {
const url = "https://api.example/hooks/QUOKKA-CALLBACK";
const folded: ConversationTurn[] = [
textTurn("user", "Register the callback host api.example"),
{
role: "assistant",
content: [
{
type: "tool_call",
id: "c1",
name: "web_fetch",
arguments: { url },
},
],
timestamp: 2,
},
textTurn("assistant", "Next step is to patch the handler QUOKKA-NEXT"),
textTurn("user", "Do not commit generated artifacts ever."),
];
const turns: ConversationTurn[] = [
...folded,
textTurn("user", "recent ask"),
textTurn("assistant", "recent reply"),
];
const prose = "Work continues.";
const facts = extractContinuationFacts(folded);
expect(facts.exactNames).toContain(url);
const misses = verifyCompactionSummary(prose, facts).misses;
const full = repairSummary(prose, facts, misses);
const exactAt = full.indexOf("Exact references:");
expect(exactAt).toBeGreaterThan(0);
expect(full.slice(0, exactAt)).toContain("api.example");
expect(full.slice(0, exactAt)).not.toContain("QUOKKA-CALLBACK");

const result = await createPruningCompactor({
summaryMaxChars: exactAt,
compactionShape: { tailBudgetTokens: 10 },
summarize: async () => prose,
}).apply(turns, mockStrategyCtx);

const file = handoffFileText(result);
const spine = allText(result.output);
const keptPrior = result.output === turns;
const pathKept =
file.includes("hooks/QUOKKA-CALLBACK") ||
spine.includes("hooks/QUOKKA-CALLBACK");
expect(keptPrior || pathKept).toBe(true);
if (!keptPrior) {
expect(result.record.decisions.verifyAborted).toBeUndefined();
expect(result.record.reason.startsWith("compacted")).toBe(true);
}
});
});

describe("CL-9007 budgeted tail (shared auto+manual pipeline)", () => {
Expand Down
Loading
Loading