/*
action-pack — certification action pack types and failure contracts for gate remediation.
- Do not execute actions — this module defines the pack contract only.
- RFC-1097: sweep — werkstatt-engine clean
Sweep batch 4: 73 Compass headers on headerless engine files (certification, component-runtime, isolation, evolution, testing), real KEY_DECISIONS on 75 files (kernel, cache, dht, swim, gitmesh, runtime), ~80 purpose expansions (CONTRACT-02/PURPOSE-02), non-goals on 13 CONTRACT-03 files, CS-07 history literal fix repo-wide (253 files). Policy: .template.ts/.template.astro excludedPaths. werkstatt-engine now 0 diagnostics.
*/
import type {
ActionTaskV1,
ActionAnchorV1,
ActionDependencyV1,
CertificationActionPackV1,
} from "./contracts/index.ts";
import type { RequirementEvaluationV1 } from "./aggregation.ts";
export type CertificationActionPackFailureV1 = {
readonly ok: false;
readonly code: "CERT-ACTION-01" | "CERT-LIMIT-03";
readonly message: string;
};
export type CertificationActionPackInputV1 = {
readonly actionPackId: string;
readonly candidateId: string;
readonly decisionId: string;
readonly requirements: readonly RequirementEvaluationV1[];
readonly remediationMetadata: ReadonlyMap;
readonly createdAt: string;
};
export type RemediationMetadataV1 = {
readonly remediationClass: "product-fix" | "infrastructure-retry" | "policy-defect";
readonly description: string;
readonly verificationCommand: string;
readonly anchors: readonly ActionAnchorV1[];
readonly dependencies: readonly ActionDependencyV1[];
};
const MAX_TASKS = 1000;
function topologicalSort(tasks: readonly ActionTaskV1[]): ActionTaskV1[] | { cycle: true } {
const taskMap = new Map();
for (const t of tasks) taskMap.set(t.taskId, t);
const visited = new Map();
const result: ActionTaskV1[] = [];
function visit(id: string): boolean {
const state = visited.get(id);
if (state === "done") return true;
if (state === "visiting") return false;
visited.set(id, "visiting");
const task = taskMap.get(id);
if (task) {
for (const dep of task.dependencies) {
if (!visit(dep.dependsOn)) return false;
}
}
visited.set(id, "done");
const t = taskMap.get(id);
if (t) result.push(t);
return true;
}
const sortedIds = [...taskMap.keys()].sort();
for (const id of sortedIds) {
if (!visit(id)) return { cycle: true };
}
return result;
}
export function buildCertificationActionPack(
input: CertificationActionPackInputV1,
): CertificationActionPackV1 | CertificationActionPackFailureV1 {
const actionableReqs = input.requirements.filter(
(r) => r.status !== "pass" && r.status !== "not-applicable",
);
const tasks: ActionTaskV1[] = [];
for (const req of actionableReqs) {
const meta = input.remediationMetadata.get(req.requirementId);
if (!meta) {
return {
ok: false,
code: "CERT-ACTION-01",
message: `requirement ${req.requirementId} has non-pass status but no remediation metadata`,
};
}
if (meta.anchors.length === 0) {
return {
ok: false,
code: "CERT-ACTION-01",
message: `requirement ${req.requirementId} remediation has no anchors`,
};
}
if (meta.description.length === 0) {
return {
ok: false,
code: "CERT-ACTION-01",
message: `requirement ${req.requirementId} remediation has empty description`,
};
}
if (meta.verificationCommand.length === 0) {
return {
ok: false,
code: "CERT-ACTION-01",
message: `requirement ${req.requirementId} remediation has empty verification command`,
};
}
const taskId = `act-${req.requirementId}`.toLowerCase().replace(/[^a-z0-9-]/g, "-");
tasks.push({
taskId,
remediationClass: meta.remediationClass,
description: meta.description,
verificationCommand: meta.verificationCommand,
anchors: [...meta.anchors].sort((a, b) =>
a.anchorId < b.anchorId ? -1 : a.anchorId > b.anchorId ? 1 : 0,
),
dependencies: [...meta.dependencies].sort((a, b) =>
a.dependsOn < b.dependsOn ? -1 : a.dependsOn > b.dependsOn ? 1 : 0,
),
});
}
if (tasks.length > MAX_TASKS) {
return {
ok: false,
code: "CERT-LIMIT-03",
message: `action tasks count ${tasks.length} exceeds hard limit ${MAX_TASKS}`,
};
}
const sorted = topologicalSort(tasks);
if ("cycle" in sorted) {
return {
ok: false,
code: "CERT-ACTION-01",
message: "dependency cycle detected in action pack tasks",
};
}
return {
schema: "werkstatt/certification-action-pack@1",
actionPackId: input.actionPackId,
candidateId: input.candidateId,
decisionId: input.decisionId,
tasks: sorted,
createdAt: input.createdAt,
};
}
export const ACTION_PACK_LIMITS = {
MAX_TASKS,
} as const;