feat: add AgentRun cancel lifecycle policy (#859)
Co-authored-by: Codex <codex@noreply.local>
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
// SPEC: PJ2026-01060305 AgentRun execution policy + PJ2026-01020108 cancel lifecycle draft-2026-06-25-p0.
|
||||
// Parses AgentRun YAML lane policy, including cancel lifecycle values owned by config/agentrun.yaml.
|
||||
import { rootPath } from "./config";
|
||||
import {
|
||||
asRecord,
|
||||
@@ -113,6 +115,7 @@ export interface AgentRunLaneSpec {
|
||||
readonly egressProxyUrl: string | null;
|
||||
readonly noProxyExtra: readonly string[];
|
||||
readonly retention: AgentRunRunnerRetentionSpec;
|
||||
readonly cancelLifecycle: AgentRunCancelLifecycleSpec;
|
||||
};
|
||||
readonly localPostgres: {
|
||||
readonly enabled: boolean;
|
||||
@@ -195,6 +198,19 @@ export interface AgentRunRunnerRetentionSpec {
|
||||
};
|
||||
}
|
||||
|
||||
export type AgentRunCancelLifecycleStage = "accepted" | "persisted" | "delivered" | "aborting" | "terminalized" | "fenced" | "late-write-rejected";
|
||||
|
||||
export interface AgentRunCancelLifecycleSpec {
|
||||
readonly deliveryMode: "manager-epoch";
|
||||
readonly gracefulAbortMs: number;
|
||||
readonly killEscalationMs: number;
|
||||
readonly staleHeartbeatFencingMs: number;
|
||||
readonly lateWriteFencing: {
|
||||
readonly enabled: boolean;
|
||||
};
|
||||
readonly eventStages: readonly AgentRunCancelLifecycleStage[];
|
||||
}
|
||||
|
||||
export interface AgentRunLaneTarget {
|
||||
readonly configPath: string;
|
||||
readonly spec: AgentRunLaneSpec;
|
||||
@@ -312,6 +328,7 @@ export function agentRunLaneSummary(spec: AgentRunLaneSpec): Record<string, unkn
|
||||
egressProxyUrl: spec.deployment.runner.egressProxyUrl,
|
||||
noProxyExtra: spec.deployment.runner.noProxyExtra,
|
||||
retention: spec.deployment.runner.retention,
|
||||
cancelLifecycle: spec.deployment.runner.cancelLifecycle,
|
||||
},
|
||||
localPostgres: spec.deployment.localPostgres,
|
||||
},
|
||||
@@ -551,11 +568,39 @@ function parseDeployment(input: Record<string, unknown>, path: string): AgentRun
|
||||
egressProxyUrl: optionalStringField(runner, "egressProxyUrl", `${path}.runner`) ?? null,
|
||||
noProxyExtra: optionalStringArrayField(runner, "noProxyExtra", `${path}.runner`),
|
||||
retention: parseRunnerRetention(recordField(runner, "retention", `${path}.runner`), `${path}.runner.retention`),
|
||||
cancelLifecycle: parseCancelLifecycle(recordField(runner, "cancelLifecycle", `${path}.runner`), `${path}.runner.cancelLifecycle`),
|
||||
},
|
||||
localPostgres: parseLocalPostgres(localPostgres, `${path}.localPostgres`),
|
||||
};
|
||||
}
|
||||
|
||||
function parseCancelLifecycle(input: Record<string, unknown>, path: string): AgentRunCancelLifecycleSpec {
|
||||
const lateWriteFencing = recordField(input, "lateWriteFencing", path);
|
||||
return {
|
||||
deliveryMode: enumField(input, "deliveryMode", path, ["manager-epoch"]),
|
||||
gracefulAbortMs: positiveIntegerField(input, "gracefulAbortMs", path),
|
||||
killEscalationMs: positiveIntegerField(input, "killEscalationMs", path),
|
||||
staleHeartbeatFencingMs: positiveIntegerField(input, "staleHeartbeatFencingMs", path),
|
||||
lateWriteFencing: {
|
||||
enabled: booleanField(lateWriteFencing, "enabled", `${path}.lateWriteFencing`),
|
||||
},
|
||||
eventStages: parseCancelLifecycleStages(input.eventStages, `${path}.eventStages`),
|
||||
};
|
||||
}
|
||||
|
||||
function parseCancelLifecycleStages(input: unknown, path: string): readonly AgentRunCancelLifecycleStage[] {
|
||||
const values: readonly AgentRunCancelLifecycleStage[] = ["accepted", "persisted", "delivered", "aborting", "terminalized", "fenced", "late-write-rejected"];
|
||||
if (!Array.isArray(input)) throw new Error(`${path} must be an array`);
|
||||
if (input.length === 0) throw new Error(`${path} must declare at least one stage`);
|
||||
const result = input.map((value, index) => {
|
||||
if (typeof value !== "string" || !values.includes(value as AgentRunCancelLifecycleStage)) throw new Error(`${path}[${index}] must be one of ${values.join(", ")}`);
|
||||
return value as AgentRunCancelLifecycleStage;
|
||||
});
|
||||
const duplicates = result.filter((value, index) => result.indexOf(value) !== index);
|
||||
if (duplicates.length > 0) throw new Error(`${path} must not contain duplicate stages: ${[...new Set(duplicates)].join(", ")}`);
|
||||
return result;
|
||||
}
|
||||
|
||||
function parseRunnerRetention(input: Record<string, unknown>, path: string): AgentRunRunnerRetentionSpec {
|
||||
const selectors = recordField(input, "selectors", path);
|
||||
const ageBasedCleanup = recordField(input, "ageBasedCleanup", path);
|
||||
|
||||
Reference in New Issue
Block a user