mirror of
https://github.com/mvschwarz/openrig.git
synced 2026-11-07 18:34:36 -10:00
fix compaction enforcement decision boundaries
This commit is contained in:
1 parent
56858cbea5
commit
d789fbe515
7 files changed
+298
-36
No files matched your search
@@ -99,7 +99,35 @@ export function compactionControlCommand(depsOverride?: CompactionControlDeps):
|
||||
const response = await client.get(`/api/compaction/control${query}`, {
|
||||
headers: terminalAuthHeaders(),
|
||||
});
|
||||
printResponse(response, options.json, "Compaction decisions listed.");
|
||||
if (options.json || response.status >= 400) {
|
||||
printResponse(response, options.json, "Compaction decisions listed.");
|
||||
return;
|
||||
}
|
||||
const data = response.data as {
|
||||
decisions?: Array<{
|
||||
decisionId?: string;
|
||||
sessionName?: string;
|
||||
direction?: string;
|
||||
active?: boolean;
|
||||
lastObservedAt?: string | null;
|
||||
lastObservedOutcome?: string | null;
|
||||
}>;
|
||||
};
|
||||
const decisions = data.decisions ?? [];
|
||||
if (decisions.length === 0) {
|
||||
console.log("No compaction decisions.");
|
||||
return;
|
||||
}
|
||||
for (const decision of decisions) {
|
||||
console.log([
|
||||
decision.decisionId ?? "unknown-id",
|
||||
decision.sessionName ?? "unknown-session",
|
||||
decision.direction ?? "unknown-direction",
|
||||
decision.active === false ? "inactive" : "active",
|
||||
`lastObservedAt=${decision.lastObservedAt ?? "never"}`,
|
||||
`lastObservedOutcome=${decision.lastObservedOutcome ?? "none"}`,
|
||||
].join(" "));
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -143,6 +143,7 @@ describe("ConfigStore — extended namespaces (User Settings v0)", () => {
|
||||
"policies.claude_compaction.message_inline",
|
||||
"policies.claude_compaction.message_file_path",
|
||||
"policies.claude_compaction.post_restore_audit_instruction",
|
||||
"policies.claude_compaction.authorize_ttl_minutes",
|
||||
// OPR.0.5.1 51-06 W2c — tunable watchdog auto-registration cadence.
|
||||
"policies.idle_gate_qitem.scan_interval_seconds",
|
||||
"policies.idle_gate_qitem.active_wake_interval_seconds",
|
||||
|
||||
@@ -149,6 +149,20 @@ describe("rig compaction-control", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("human list output exposes decision identity and durable observation fields", async () => {
|
||||
const factory = await loadCommand();
|
||||
const { deps } = makeDeps();
|
||||
const command = factory(deps);
|
||||
await command.parseAsync([
|
||||
"node", "rig", "list", "--session", "claude-seat@rig",
|
||||
]);
|
||||
const rendered = logs.join("\n");
|
||||
expect(rendered).toContain("hold-1");
|
||||
expect(rendered).toContain("claude-seat@rig");
|
||||
expect(rendered).toContain("2026-08-08T18:00:30.000Z");
|
||||
expect(rendered).toContain("human_hold");
|
||||
});
|
||||
|
||||
it("clear records a human reason through the decision-specific route", async () => {
|
||||
const factory = await loadCommand();
|
||||
const { calls, deps } = makeDeps();
|
||||
|
||||
@@ -102,7 +102,7 @@ export interface ManualCompactionStatus {
|
||||
|
||||
export type ManualCompactionOutcome =
|
||||
| { triggered: true; stage: "compact-sent" }
|
||||
| { triggered: false; stage: "skipped-or-failed"; reason: string };
|
||||
| { triggered: false; stage: "skipped-or-failed"; reason: string; decisionId?: string };
|
||||
|
||||
export type EnforcerSkipReason =
|
||||
| "runtime_filter"
|
||||
@@ -372,16 +372,8 @@ export class ClaudeCompactionEnforcer {
|
||||
}
|
||||
|
||||
const liveGenerationUuid = this.resolveOccupantGeneration?.(input.sessionName) ?? null;
|
||||
const activeHold = this.decisionStore?.findActiveHold({
|
||||
enforcerKind: "claude_compaction",
|
||||
sessionName: input.sessionName,
|
||||
liveGenerationUuid,
|
||||
});
|
||||
const activeHold = this.findAndObserveHold(input.sessionName, liveGenerationUuid);
|
||||
if (activeHold) {
|
||||
this.decisionStore?.observeHold({
|
||||
decisionId: activeHold.decisionId,
|
||||
outcome: "human_hold",
|
||||
});
|
||||
return {
|
||||
triggered: false,
|
||||
reason: "human_hold",
|
||||
@@ -407,40 +399,49 @@ export class ClaudeCompactionEnforcer {
|
||||
const sendAttempt = async (
|
||||
message: string,
|
||||
opts?: SendOpts,
|
||||
consumeAuthorization = true,
|
||||
): Promise<{ ok: boolean; result: SendResult | null; outcome: EnforcerOutcome }> => {
|
||||
const lifted = authorization as {
|
||||
decisionId: string;
|
||||
liftedReason: AuthorizableCompactionReason;
|
||||
} | null;
|
||||
if (lifted && !this.decisionStore?.consumeAuthorizationForAttempt({
|
||||
decisionId: lifted.decisionId,
|
||||
enforcerKind: "claude_compaction",
|
||||
liftedReason: lifted.liftedReason,
|
||||
})) {
|
||||
return {
|
||||
ok: false,
|
||||
result: null,
|
||||
outcome: { triggered: false, reason: lifted.liftedReason },
|
||||
};
|
||||
let consumed = false;
|
||||
if (lifted && consumeAuthorization) {
|
||||
consumed = this.decisionStore?.consumeAuthorizationForAttempt({
|
||||
decisionId: lifted.decisionId,
|
||||
enforcerKind: "claude_compaction",
|
||||
liftedReason: lifted.liftedReason,
|
||||
}) ?? false;
|
||||
if (!consumed) {
|
||||
return {
|
||||
ok: false,
|
||||
result: null,
|
||||
outcome: { triggered: false, reason: lifted.liftedReason },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let result: SendResult;
|
||||
try {
|
||||
result = await this.sessionTransport.send(input.sessionName, message, opts);
|
||||
result = opts === undefined
|
||||
? await this.sessionTransport.send(input.sessionName, message)
|
||||
: await this.sessionTransport.send(input.sessionName, message, opts);
|
||||
} catch (error) {
|
||||
if (!lifted) throw error;
|
||||
this.decisionStore?.recordAuthorizationAttempt({
|
||||
decisionId: lifted.decisionId,
|
||||
outcome: "failed",
|
||||
failureReason: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
if (consumed) {
|
||||
this.decisionStore?.recordAuthorizationAttempt({
|
||||
decisionId: lifted.decisionId,
|
||||
outcome: "failed",
|
||||
failureReason: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
}
|
||||
return {
|
||||
ok: false,
|
||||
result: null,
|
||||
outcome: { triggered: false, reason: "send_failed" },
|
||||
};
|
||||
}
|
||||
if (lifted) {
|
||||
if (lifted && consumed) {
|
||||
this.decisionStore?.recordAuthorizationAttempt({
|
||||
decisionId: lifted.decisionId,
|
||||
outcome: result.ok ? "succeeded" : "failed",
|
||||
@@ -621,6 +622,8 @@ export class ClaudeCompactionEnforcer {
|
||||
thresholdPercent: policy.thresholdPercent,
|
||||
preCompactInstruction: policy.preCompactInstruction,
|
||||
}),
|
||||
undefined,
|
||||
false,
|
||||
);
|
||||
if (!prep.ok) {
|
||||
return prep.outcome;
|
||||
@@ -667,6 +670,17 @@ export class ClaudeCompactionEnforcer {
|
||||
input: EnforcerInput,
|
||||
opts: { operatorInitiated?: boolean } = {},
|
||||
): Promise<ManualCompactionOutcome> {
|
||||
const liveGenerationUuid = this.resolveOccupantGeneration?.(input.sessionName) ?? null;
|
||||
const activeHold = this.findAndObserveHold(input.sessionName, liveGenerationUuid);
|
||||
if (activeHold) {
|
||||
return {
|
||||
triggered: false,
|
||||
stage: "skipped-or-failed",
|
||||
reason: "human_hold",
|
||||
decisionId: activeHold.decisionId,
|
||||
};
|
||||
}
|
||||
|
||||
// OPR.0.4.3.14 rev1-r2 fix — SAME-SEAT IN-PROGRESS GUARD (race-safe), at the
|
||||
// VERY TOP before ANY recordManualFailure path. This synchronous check-and-set
|
||||
// runs BEFORE the first await; because JS is run-to-completion, two concurrent
|
||||
@@ -796,6 +810,24 @@ export class ClaudeCompactionEnforcer {
|
||||
return { triggered: false, stage: "skipped-or-failed", reason };
|
||||
}
|
||||
|
||||
private findAndObserveHold(
|
||||
sessionName: string,
|
||||
liveGenerationUuid: string | null,
|
||||
): EnforcerDecision | null {
|
||||
const hold = this.decisionStore?.findActiveHold({
|
||||
enforcerKind: "claude_compaction",
|
||||
sessionName,
|
||||
liveGenerationUuid,
|
||||
}) ?? null;
|
||||
if (hold) {
|
||||
this.decisionStore?.observeHold({
|
||||
decisionId: hold.decisionId,
|
||||
outcome: "human_hold",
|
||||
});
|
||||
}
|
||||
return hold;
|
||||
}
|
||||
|
||||
/**
|
||||
* Advance the surfaced manual stage monotonically, and ONLY when the current
|
||||
* stage matches `from`. This makes the back-half updates a no-op for auto
|
||||
|
||||
@@ -63,6 +63,28 @@ export function compactionRoutes(opts?: { bearerToken?: string | null }): Hono {
|
||||
}
|
||||
}
|
||||
|
||||
const currentSession = sessionRegistry.db.prepare(`
|
||||
WITH target AS (
|
||||
SELECT node_id
|
||||
FROM sessions
|
||||
WHERE session_name = ?
|
||||
ORDER BY id DESC
|
||||
LIMIT 1
|
||||
)
|
||||
SELECT session_name
|
||||
FROM sessions
|
||||
WHERE node_id = (SELECT node_id FROM target)
|
||||
ORDER BY id DESC
|
||||
LIMIT 1
|
||||
`).get(sessionName) as { session_name: string } | undefined;
|
||||
if (currentSession && currentSession.session_name !== sessionName) {
|
||||
return c.json({
|
||||
ok: false,
|
||||
error: "session_not_current",
|
||||
currentSession: currentSession.session_name,
|
||||
}, 409);
|
||||
}
|
||||
|
||||
const generationUuid = sessionRegistry.currentOccupantGenerationForSession(sessionName);
|
||||
if (!generationUuid) {
|
||||
return c.json({ ok: false, error: "generation_unavailable" }, 409);
|
||||
@@ -199,6 +221,7 @@ export function compactionRoutes(opts?: { bearerToken?: string | null }): Hono {
|
||||
tmux_unavailable: 503,
|
||||
send_failed: 502,
|
||||
submit_failed: 502,
|
||||
human_hold: 409,
|
||||
};
|
||||
const status = (statusMap[outcome.reason] ?? 409) as 400 | 404 | 409 | 422 | 502 | 503;
|
||||
return c.json({
|
||||
@@ -206,6 +229,7 @@ export function compactionRoutes(opts?: { bearerToken?: string | null }): Hono {
|
||||
session: sessionName,
|
||||
stage: outcome.stage,
|
||||
reason: outcome.reason,
|
||||
...(outcome.decisionId ? { decisionId: outcome.decisionId } : {}),
|
||||
error: manualReasonMessage(sessionName, outcome.reason),
|
||||
}, status);
|
||||
});
|
||||
@@ -239,6 +263,8 @@ function manualReasonMessage(sessionName: string, reason: string): string {
|
||||
return `Refused: '${sessionName}' activity could not be determined; failing closed so /compact cannot land on a prompt.`;
|
||||
case "wait_for_idle_timeout":
|
||||
return `Prep was sent to '${sessionName}' but it did not go idle in time, so /compact was NOT sent. Retry once the prep turn completes.`;
|
||||
case "human_hold":
|
||||
return `Refused: '${sessionName}' has an active human compaction hold.`;
|
||||
default:
|
||||
return `Manual compaction for '${sessionName}' did not complete (${reason}).`;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import { migrate } from "../src/db/migrate.js";
|
||||
import { ALL_MIGRATIONS } from "../src/db/all-migrations.js";
|
||||
import { RigRepository } from "../src/domain/rig-repository.js";
|
||||
import { SessionRegistry } from "../src/domain/session-registry.js";
|
||||
import { ClaudeCompactionEnforcer } from "../src/domain/claude-compaction-enforcer.js";
|
||||
import { EnforcerDecisionStore } from "../src/domain/enforcer-decision-store.js";
|
||||
import { compactionRoutes } from "../src/routes/compaction.js";
|
||||
|
||||
const IDENTITY = "orch-lead@rig";
|
||||
@@ -14,6 +16,7 @@ const SEAT = "claude-seat@rig";
|
||||
describe("W4 compaction-control routes", () => {
|
||||
let db: Database.Database;
|
||||
let sessionRegistry: SessionRegistry;
|
||||
let nodeId: string;
|
||||
let generationUuid: string;
|
||||
let decisions: {
|
||||
create: ReturnType<typeof vi.fn>;
|
||||
@@ -21,6 +24,11 @@ describe("W4 compaction-control routes", () => {
|
||||
clear: ReturnType<typeof vi.fn>;
|
||||
};
|
||||
let app: Hono;
|
||||
let manualDeps: {
|
||||
enforcer: ClaudeCompactionEnforcer;
|
||||
transport: Record<string, unknown>;
|
||||
usageStore: Record<string, unknown>;
|
||||
} | null;
|
||||
|
||||
beforeEach(() => {
|
||||
db = createDb();
|
||||
@@ -29,6 +37,7 @@ describe("W4 compaction-control routes", () => {
|
||||
sessionRegistry = new SessionRegistry(db);
|
||||
const rig = rigs.createRig("rig");
|
||||
const node = rigs.addNode(rig.id, "claude.seat", { runtime: "claude-code" });
|
||||
nodeId = node.id;
|
||||
sessionRegistry.registerSession(node.id, SEAT);
|
||||
generationUuid = sessionRegistry.currentOccupantGenerationForSession(SEAT)!;
|
||||
decisions = {
|
||||
@@ -43,11 +52,17 @@ describe("W4 compaction-control routes", () => {
|
||||
}]),
|
||||
clear: vi.fn((input) => ({ decisionId: input.decisionId, active: false, releaseKind: "cleared" })),
|
||||
};
|
||||
manualDeps = null;
|
||||
app = new Hono();
|
||||
app.use("*", async (c, next) => {
|
||||
c.set("db" as never, db);
|
||||
c.set("sessionRegistry" as never, sessionRegistry);
|
||||
c.set("enforcerDecisionStore" as never, decisions);
|
||||
if (manualDeps) {
|
||||
c.set("compactionEnforcer" as never, manualDeps.enforcer);
|
||||
c.set("sessionTransport" as never, manualDeps.transport);
|
||||
c.set("contextUsageStore" as never, manualDeps.usageStore);
|
||||
}
|
||||
await next();
|
||||
});
|
||||
app.route("/api/compaction", compactionRoutes());
|
||||
@@ -170,6 +185,106 @@ describe("W4 compaction-control routes", () => {
|
||||
expect(decisions.create).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each(["hold", "authorize"] as const)(
|
||||
"rejects historical session aliases for %s and accepts only the current alias",
|
||||
async (direction) => {
|
||||
const currentSeat = "claude-seat-next@rig";
|
||||
sessionRegistry.registerSession(nodeId, currentSeat);
|
||||
const automaticReason = direction === "authorize" ? "disabled" : undefined;
|
||||
const body = {
|
||||
session: SEAT,
|
||||
direction,
|
||||
...(automaticReason ? { automaticReason } : {}),
|
||||
reason: `current-generation ${direction}`,
|
||||
};
|
||||
|
||||
const stale = await request("/api/compaction/control", body);
|
||||
expect(stale.status).toBe(409);
|
||||
expect(await stale.json()).toMatchObject({
|
||||
error: "session_not_current",
|
||||
currentSession: currentSeat,
|
||||
});
|
||||
expect(decisions.create).not.toHaveBeenCalled();
|
||||
|
||||
const current = await request("/api/compaction/control", { ...body, session: currentSeat });
|
||||
expect(current.status).toBe(201);
|
||||
expect(decisions.create).toHaveBeenCalledWith(expect.objectContaining({
|
||||
sessionName: currentSeat,
|
||||
generationUuid: sessionRegistry.currentOccupantGenerationForSession(currentSeat),
|
||||
direction,
|
||||
}));
|
||||
},
|
||||
);
|
||||
|
||||
it("the public manual trigger honors a current hold, records observation, and sends nothing", async () => {
|
||||
const store = new EnforcerDecisionStore(db, {
|
||||
now: () => new Date("2026-08-08T18:00:00.000Z"),
|
||||
authorizeTtlMinutes: () => 15,
|
||||
});
|
||||
const hold = store.create({
|
||||
enforcerKind: "claude_compaction",
|
||||
sessionName: SEAT,
|
||||
generationUuid,
|
||||
direction: "hold",
|
||||
automaticReason: null,
|
||||
reason: "finish the current atomic action",
|
||||
actorSession: IDENTITY,
|
||||
identityProvenance: "transport:v1",
|
||||
});
|
||||
const send = vi.fn(async () => ({ ok: true as const }));
|
||||
const transport = {
|
||||
send,
|
||||
resolveSessions: vi.fn(async () => ({ ok: true as const, sessions: [SEAT] })),
|
||||
};
|
||||
const enforcer = new ClaudeCompactionEnforcer(
|
||||
{
|
||||
resolveClaudeCompactionPolicy: () => ({
|
||||
enabled: true,
|
||||
thresholdPercent: 80,
|
||||
preCompactInstruction: "prepare",
|
||||
compactInstruction: "",
|
||||
messageInline: "",
|
||||
messageFilePath: "",
|
||||
postRestoreAuditInstruction: "audit",
|
||||
authorizeTtlMinutes: 15,
|
||||
}),
|
||||
} as never,
|
||||
transport as never,
|
||||
{
|
||||
resolveOccupantGeneration: (sessionName: string) =>
|
||||
sessionRegistry.currentOccupantGenerationForSession(sessionName),
|
||||
decisionStore: store,
|
||||
},
|
||||
);
|
||||
manualDeps = {
|
||||
enforcer,
|
||||
transport,
|
||||
usageStore: {
|
||||
getForNode: () => ({
|
||||
availability: "known",
|
||||
usedPercentage: 90,
|
||||
transcriptPath: "/tmp/claude.jsonl",
|
||||
sessionId: "claude-session",
|
||||
}),
|
||||
},
|
||||
};
|
||||
decisions = store as never;
|
||||
|
||||
const response = await request("/api/compaction/trigger", { session: SEAT });
|
||||
expect(response.status).toBe(409);
|
||||
expect(await response.json()).toMatchObject({
|
||||
ok: false,
|
||||
reason: "human_hold",
|
||||
decisionId: hold.decisionId,
|
||||
});
|
||||
expect(send).not.toHaveBeenCalled();
|
||||
expect(store.list({ sessionName: SEAT })).toContainEqual(expect.objectContaining({
|
||||
decisionId: hold.decisionId,
|
||||
lastObservedOutcome: "human_hold",
|
||||
lastObservedAt: "2026-08-08T18:00:00.000Z",
|
||||
}));
|
||||
});
|
||||
|
||||
it("lists durable observation fields without requiring a write identity", async () => {
|
||||
const response = await request(`/api/compaction/control?session=${encodeURIComponent(SEAT)}`);
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
@@ -440,6 +440,16 @@ describe("W4 ClaudeCompactionEnforcer decision consumption", () => {
|
||||
expect(tx.send).not.toHaveBeenCalled();
|
||||
|
||||
liveGeneration = "gen-N";
|
||||
await expect(enforcer.maybeAutoCompact(highInput())).resolves.toEqual({
|
||||
triggered: true,
|
||||
decisionId: authorization["decisionId"],
|
||||
liftedReason: "disabled",
|
||||
});
|
||||
expect(row()).toMatchObject({ active: true });
|
||||
expect(row()?.["consumedAt"] ?? null).toBeNull();
|
||||
expect(tx.send).toHaveBeenCalledTimes(1);
|
||||
expect(tx.send.mock.calls[0]?.[1]).toContain("automatic compaction preparation");
|
||||
|
||||
await expect(enforcer.maybeAutoCompact(highInput())).resolves.toEqual({
|
||||
triggered: true,
|
||||
decisionId: authorization["decisionId"],
|
||||
@@ -451,13 +461,14 @@ describe("W4 ClaudeCompactionEnforcer decision consumption", () => {
|
||||
liftedReason: "disabled",
|
||||
attemptOutcome: "succeeded",
|
||||
});
|
||||
expect(tx.send).toHaveBeenCalledTimes(1);
|
||||
expect(tx.send).toHaveBeenCalledTimes(2);
|
||||
expect(tx.send.mock.calls[1]?.[1]).toMatch(/^\/compact/);
|
||||
|
||||
await expect(enforcer.maybeAutoCompact(highInput())).resolves.toEqual({
|
||||
triggered: false,
|
||||
reason: "disabled",
|
||||
});
|
||||
expect(tx.send).toHaveBeenCalledTimes(1);
|
||||
expect(tx.send).toHaveBeenCalledTimes(2);
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
@@ -481,6 +492,14 @@ describe("W4 ClaudeCompactionEnforcer decision consumption", () => {
|
||||
liftedReason: "disabled",
|
||||
});
|
||||
expect(tx.send).toHaveBeenCalledTimes(1);
|
||||
expect(decisions.consumeAuthorizationForAttempt).not.toHaveBeenCalled();
|
||||
|
||||
await expect(enforcer.maybeAutoCompact(highInput())).resolves.toEqual({
|
||||
triggered: true,
|
||||
decisionId: "authorize-1",
|
||||
liftedReason: "disabled",
|
||||
});
|
||||
expect(tx.send).toHaveBeenCalledTimes(2);
|
||||
expect(decisions.consumeAuthorizationForAttempt).toHaveBeenCalledTimes(1);
|
||||
expect(decisions.recordAuthorizationAttempt).toHaveBeenCalledWith(expect.objectContaining({
|
||||
decisionId: "authorize-1",
|
||||
@@ -528,6 +547,13 @@ describe("W4 ClaudeCompactionEnforcer decision consumption", () => {
|
||||
decisionId: "authorize-1",
|
||||
liftedReason: "post_restore_cooldown",
|
||||
});
|
||||
expect(decisions.consumeAuthorizationForAttempt).not.toHaveBeenCalled();
|
||||
now += 1_000;
|
||||
await expect(enforcer.maybeAutoCompact(highInput())).resolves.toEqual({
|
||||
triggered: true,
|
||||
decisionId: "authorize-1",
|
||||
liftedReason: "post_restore_cooldown",
|
||||
});
|
||||
expect(decisions.consumeAuthorizationForAttempt).toHaveBeenCalledTimes(1);
|
||||
} finally {
|
||||
vi.restoreAllMocks();
|
||||
@@ -558,8 +584,10 @@ describe("W4 ClaudeCompactionEnforcer decision consumption", () => {
|
||||
expect(decisions.consumeAuthorizationForAttempt).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("consumes authorization on a failed attempt and records the actual failure", async () => {
|
||||
const send = vi.fn(async () => ({ ok: false as const, reason: "send_failed" }));
|
||||
it("consumes authorization on a failed compact attempt and records the actual failure", async () => {
|
||||
const send = vi.fn()
|
||||
.mockResolvedValueOnce({ ok: true as const })
|
||||
.mockResolvedValueOnce({ ok: false as const, reason: "send_failed" });
|
||||
const decisions = decisionHarness({ authorizationReason: "disabled" });
|
||||
const enforcer = new ClaudeCompactionEnforcer(
|
||||
settings({ ...BASE_POLICY, enabled: false }),
|
||||
@@ -570,6 +598,13 @@ describe("W4 ClaudeCompactionEnforcer decision consumption", () => {
|
||||
} as never,
|
||||
);
|
||||
|
||||
await expect(enforcer.maybeAutoCompact(highInput())).resolves.toEqual({
|
||||
triggered: true,
|
||||
decisionId: "authorize-1",
|
||||
liftedReason: "disabled",
|
||||
});
|
||||
expect(decisions.consumeAuthorizationForAttempt).not.toHaveBeenCalled();
|
||||
|
||||
await expect(enforcer.maybeAutoCompact(highInput())).resolves.toEqual({
|
||||
triggered: false,
|
||||
reason: "send_failed",
|
||||
@@ -596,9 +631,9 @@ describe("W4 ClaudeCompactionEnforcer decision consumption", () => {
|
||||
automaticReason: "disabled",
|
||||
reason: "allow one attempted send",
|
||||
}));
|
||||
const send = vi.fn(async () => {
|
||||
throw new Error("transport exploded");
|
||||
});
|
||||
const send = vi.fn()
|
||||
.mockResolvedValueOnce({ ok: true as const })
|
||||
.mockRejectedValueOnce(new Error("transport exploded"));
|
||||
const enforcer = new ClaudeCompactionEnforcer(
|
||||
settings({ ...BASE_POLICY, enabled: false }),
|
||||
{ send } as unknown as SessionTransport,
|
||||
@@ -608,6 +643,17 @@ describe("W4 ClaudeCompactionEnforcer decision consumption", () => {
|
||||
} as never,
|
||||
);
|
||||
|
||||
await expect(enforcer.maybeAutoCompact(highInput())).resolves.toEqual({
|
||||
triggered: true,
|
||||
decisionId: authorization["decisionId"],
|
||||
liftedReason: "disabled",
|
||||
});
|
||||
expect(store.list({ sessionName: SEAT })).toContainEqual(expect.objectContaining({
|
||||
decisionId: authorization["decisionId"],
|
||||
active: true,
|
||||
consumedAt: null,
|
||||
}));
|
||||
|
||||
await expect(enforcer.maybeAutoCompact(highInput())).resolves.toEqual({
|
||||
triggered: false,
|
||||
reason: "send_failed",
|
||||
@@ -625,7 +671,7 @@ describe("W4 ClaudeCompactionEnforcer decision consumption", () => {
|
||||
triggered: false,
|
||||
reason: "disabled",
|
||||
});
|
||||
expect(send).toHaveBeenCalledTimes(1);
|
||||
expect(send).toHaveBeenCalledTimes(2);
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user