mirror of
https://github.com/mvschwarz/openrig.git
synced 2026-11-09 17:32:41 -10:00
fix(daemon): remove conveyor starter launch cycle
QA fresh-install dogfood for release-0.3.2 slice 01 found that the built-in conveyor starter could not launch on a clean isolated daemon. The reviewer-to-builder rework path was modeled as delegates_to, which is also a launch-order dependency, creating a hard build/review cycle. Model that rework visibility as can_observe instead. The workflow spec still owns the review-to-build rework path. Add a bundled starter-spec regression gate so shipped rig specs cannot carry launch-dependency cycles again. Verification: - npm test --workspace @openrig/daemon -- spec-library-starters.test.ts - npm test --workspace @openrig/cli -- test/workspace-max-files-validation.test.ts test/workflow-exit-validation.test.ts test/workflow-ux.test.ts - npm run build --workspace @openrig/daemon - HG-7 isolated daemon fresh-install dogfood through workflow instantiate
This commit is contained in:
1 parent
2b4772c785
commit
f3449bafdd
2 files changed
+77
-1
No files matched your search
@@ -58,7 +58,7 @@ edges:
|
||||
- kind: delegates_to
|
||||
from: build.builder
|
||||
to: review.reviewer
|
||||
- kind: delegates_to
|
||||
- kind: can_observe
|
||||
from: review.reviewer
|
||||
to: build.builder
|
||||
- kind: can_observe
|
||||
|
||||
@@ -77,6 +77,74 @@ const STARTER_AGENT_SPECS = [
|
||||
"agents/orchestration/orchestrator/agent.yaml",
|
||||
];
|
||||
|
||||
function assertAcyclicLaunchDependencyGraph(file: string, parsed: Record<string, unknown>): void {
|
||||
const pods = (parsed["pods"] as Array<Record<string, unknown>> | undefined) ?? [];
|
||||
const allIds: string[] = [];
|
||||
const inDegree: Record<string, number> = {};
|
||||
const adjacency: Record<string, string[]> = {};
|
||||
|
||||
for (const pod of pods) {
|
||||
const podId = String(pod["id"]);
|
||||
const members = (pod["members"] as Array<Record<string, unknown>> | undefined) ?? [];
|
||||
for (const member of members) {
|
||||
const qid = `${podId}.${String(member["id"])}`;
|
||||
allIds.push(qid);
|
||||
inDegree[qid] = 0;
|
||||
adjacency[qid] = [];
|
||||
}
|
||||
}
|
||||
|
||||
const addDependency = (from: string, to: string) => {
|
||||
if (!adjacency[from] || !adjacency[to]) return;
|
||||
adjacency[from]!.push(to);
|
||||
inDegree[to] = (inDegree[to] ?? 0) + 1;
|
||||
};
|
||||
|
||||
const addEdge = (edge: Record<string, unknown>, qualify?: (id: string) => string) => {
|
||||
const kind = String(edge["kind"]);
|
||||
if (kind !== "delegates_to" && kind !== "spawned_by") return;
|
||||
const rawFrom = String(edge["from"]);
|
||||
const rawTo = String(edge["to"]);
|
||||
const from = qualify ? qualify(rawFrom) : rawFrom;
|
||||
const to = qualify ? qualify(rawTo) : rawTo;
|
||||
if (kind === "delegates_to") {
|
||||
addDependency(from, to);
|
||||
} else {
|
||||
addDependency(to, from);
|
||||
}
|
||||
};
|
||||
|
||||
for (const pod of pods) {
|
||||
const podId = String(pod["id"]);
|
||||
const edges = (pod["edges"] as Array<Record<string, unknown>> | undefined) ?? [];
|
||||
for (const edge of edges) {
|
||||
addEdge(edge, (id) => `${podId}.${id}`);
|
||||
}
|
||||
}
|
||||
|
||||
const edges = (parsed["edges"] as Array<Record<string, unknown>> | undefined) ?? [];
|
||||
for (const edge of edges) {
|
||||
addEdge(edge);
|
||||
}
|
||||
|
||||
const queue = allIds.filter((id) => inDegree[id] === 0).sort();
|
||||
const visited: string[] = [];
|
||||
while (queue.length > 0) {
|
||||
const current = queue.shift()!;
|
||||
visited.push(current);
|
||||
for (const next of (adjacency[current] ?? []).sort()) {
|
||||
inDegree[next] = (inDegree[next] ?? 1) - 1;
|
||||
if (inDegree[next] === 0) queue.push(next);
|
||||
queue.sort();
|
||||
}
|
||||
}
|
||||
|
||||
if (visited.length !== allIds.length) {
|
||||
const cycled = allIds.filter((id) => !visited.includes(id));
|
||||
throw new Error(`${file} has a launch dependency cycle among: ${cycled.join(", ")}`);
|
||||
}
|
||||
}
|
||||
|
||||
describe("Starter specs", () => {
|
||||
const specReviewService = new SpecReviewService();
|
||||
|
||||
@@ -203,6 +271,14 @@ describe("Starter specs", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("all rig specs have acyclic launch-dependency edges", () => {
|
||||
for (const file of RIG_SPECS) {
|
||||
const yaml = readFileSync(join(SPECS_ROOT, file), "utf-8");
|
||||
const parsed = parseYaml(yaml) as Record<string, unknown>;
|
||||
assertAcyclicLaunchDependencyGraph(file, parsed);
|
||||
}
|
||||
});
|
||||
|
||||
it("service-backed proof rigs pass canonical rigPreflight with explicit cwdOverride", () => {
|
||||
const fsOps = {
|
||||
readFile: (p: string) => readFileSync(p, "utf-8"),
|
||||
|
||||
Reference in new issue
Block a user