@deksden-com/dd-flow-cli 0.9.0-beta.1 → 0.9.0-beta.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/dist/build-info.json +5 -5
  3. package/dist/cli/help.js +3 -3
  4. package/dist/cli/run-cli.js +127 -8
  5. package/dist/runtime/context.js +3 -1
  6. package/dist/schemas/code-review-result.schema.json +1 -1
  7. package/dist/schemas/code-work-batch.schema.json +4 -3
  8. package/dist/schemas/code-work-result.schema.json +1 -1
  9. package/dist/schemas/harness-config.schema.json +23 -0
  10. package/dist/schemas/plan-review-decision.schema.json +1 -1
  11. package/dist/schemas/vnext-protocol-plan.schema.json +1 -1
  12. package/dist/services/cleanup.js +18 -8
  13. package/dist/services/code-checks.js +194 -44
  14. package/dist/services/engines.js +4 -4
  15. package/dist/services/eval-snapshots.js +10 -5
  16. package/dist/services/harness-config.js +66 -0
  17. package/dist/services/hooks.js +62 -28
  18. package/dist/services/lanes.js +1 -0
  19. package/dist/services/managed-processes.js +169 -0
  20. package/dist/services/merge-server.js +8 -2
  21. package/dist/services/portable-refs.js +57 -0
  22. package/dist/services/prompts.js +4 -2
  23. package/dist/services/run-engine-bindings.js +19 -61
  24. package/dist/services/run-projection.js +10 -8
  25. package/dist/services/runs.js +71 -9
  26. package/dist/services/schema-validation.js +11 -11
  27. package/dist/services/session-identity.js +19 -0
  28. package/dist/services/sessions.js +26 -11
  29. package/dist/services/stage-lifecycle.js +15 -8
  30. package/dist/services/stage-pause.js +35 -20
  31. package/dist/services/usage.js +74 -42
  32. package/dist/services/vnext-code-review.js +82 -41
  33. package/dist/services/vnext-code.js +98 -34
  34. package/dist/services/vnext-fanout.js +5 -12
  35. package/dist/services/vnext-merge.js +144 -65
  36. package/dist/services/vnext-plan-review.js +50 -35
  37. package/dist/services/vnext-plan.js +69 -21
  38. package/dist/services/vnext-protocolize.js +6 -6
  39. package/dist/services/vnext-specify.js +6 -6
  40. package/dist/services/work-registry.js +150 -40
  41. package/dist/storage/database.js +128 -2
  42. package/package.json +1 -1
  43. package/tools/audit-runtime-fix-boundaries.mjs +96 -0
@@ -0,0 +1,96 @@
1
+ // Adversarial acceptance checks, independent of the long CLI fixture suite.
2
+ // Run after building: pnpm build && node tools/audit-runtime-fix-boundaries.mjs
3
+ // Uses only disposable local workspaces; no provider or production DB calls.
4
+ import assert from "node:assert/strict";
5
+ import fs from "node:fs";
6
+ import os from "node:os";
7
+ import path from "node:path";
8
+ import process from "node:process";
9
+ import { log } from "node:console";
10
+ import { setTimeout, clearTimeout } from "node:timers";
11
+ import { spawn } from "node:child_process";
12
+ import { once } from "node:events";
13
+ import { createContext } from "../dist/runtime/context.js";
14
+ import { getResourceDatabase } from "../dist/storage/database.js";
15
+ import { checksForRunAt, runCodeChecks, checkReceipts } from "../dist/services/code-checks.js";
16
+ import { claimExpiredManagedProcesses, confirmManagedProcess, finishManagedProcess, managedProcessStatus, reconcileExpiredManagedProcesses, registerManagedProcess, reservePorts } from "../dist/services/managed-processes.js";
17
+ import { selectRepairChecks } from "../dist/services/vnext-code.js";
18
+
19
+ const results = [];
20
+ const observations = {};
21
+ async function check(name, action) {
22
+ try { await action(); results.push({ name, passed: true }); }
23
+ catch (error) { results.push({ name, passed: false, expected: error.expected, actual: error.actual, message: error.message }); }
24
+ }
25
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "dd-flow-fix-audit-"));
26
+ const resources = path.join(root, "resources");
27
+ const context = createContext({ DD_FLOW_HOME: path.join(root, "flow"), DD_FLOW_RESOURCE_HOME: resources });
28
+ const projectRoot = path.join(root, "project");
29
+ fs.mkdirSync(projectRoot);
30
+ try {
31
+ const request = {
32
+ projectId: "PRJ-AUDIT", runId: "RUN-AUDIT", runHome: path.join(root, "run"), workspaceRoot: projectRoot, scope: "aggregate",
33
+ checks: [{ id: "CHK-AUDIT", command: "sleep 0.05", purpose: "concurrent claim audit", run_at: "code", availability: "available" }]
34
+ };
35
+ const calls = await Promise.allSettled([runCodeChecks(context, request), runCodeChecks(context, request)]);
36
+ const rows = checkReceipts(context, { projectId: request.projectId, runId: request.runId });
37
+ observations.concurrent = calls.map((call) => call.status === "fulfilled" ? { status: call.status, receipts: call.value.map(({ id, status }) => ({ id, status })) } : { status: call.status, code: call.reason.code });
38
+ await check("a concurrent caller receives check_in_progress during the spawn window", () => assert.equal(calls.filter((call) => call.status === "rejected" && call.reason.code === "check_in_progress").length, 1));
39
+ await check("returned and persisted terminal check statuses agree", () => {
40
+ for (const call of calls.filter((call) => call.status === "fulfilled")) {
41
+ for (const receipt of call.value) assert.equal(rows.find((row) => row.id === receipt.id)?.status, receipt.status);
42
+ }
43
+ });
44
+
45
+ const source = { id: "CHK-QUALITY", command: "true", purpose: "aggregate quality gate", run_at: "code", availability: "available" };
46
+ await check("semantic repair retains an aggregate gate for context but does not run it in a Work", () => assert.deepEqual(checksForRunAt(selectRepairChecks({ semanticChecks: [source] }), "work"), []));
47
+
48
+ const record = registerManagedProcess(context, { kind: "audit", ownerId: "audit-owner", leaseMs: -1 });
49
+ const claimed = claimExpiredManagedProcesses(context, "interrupted-reconciler");
50
+ await check("orphan cleanup can be reclaimed after a reconciler interruption", () => {
51
+ assert.ok(claimed.some((item) => item.id === record.id));
52
+ context.now = () => "2099-01-01T00:00:00.000Z";
53
+ assert.ok(claimExpiredManagedProcesses(context, "next-reconciler").some((item) => item.id === record.id));
54
+ });
55
+ context.now = () => new Date().toISOString();
56
+ const ownedProcess = registerManagedProcess(context, { kind: "audit", ownerId: "audit-with-port" });
57
+ const allocation = await reservePorts(context, { ownerId: ownedProcess.owner_id, processId: ownedProcess.id, names: ["api"] });
58
+ try {
59
+ finishManagedProcess(context, { id: ownedProcess.id, leaseToken: ownedProcess.lease_token, state: "stopped" });
60
+ await check("terminal process cleanup also releases its durable port claims", () => {
61
+ const rows = getResourceDatabase(resources).all("SELECT * FROM managed_resources WHERE process_id = ?", [ownedProcess.id]);
62
+ assert.equal(rows.length, 0);
63
+ });
64
+ } finally { allocation.release(); }
65
+ await check("orphan reconciliation also terminates the process-owned child tree", async () => {
66
+ // Both processes belong only to this temporary test. A private process
67
+ // group makes the finally block able to clean up even a failed assertion.
68
+ const tree = spawn(process.execPath, ["-e", `const {spawn}=require('node:child_process'); const child=spawn(process.execPath,['-e','setInterval(()=>{},1000)'],{stdio:'ignore'}); console.log(child.pid); setInterval(()=>{},1000);`], { detached: true, stdio: ["ignore", "pipe", "ignore"] });
69
+ const closed = once(tree, "close");
70
+ let timer;
71
+ try {
72
+ const data = await Promise.race([once(tree.stdout, "data"), new Promise((_, reject) => { timer = setTimeout(() => reject(new Error("test process did not start")), 2000); })]);
73
+ clearTimeout(timer);
74
+ const childPid = Number(String(data[0]).trim());
75
+ assert.ok(Number.isInteger(childPid) && childPid > 0);
76
+ const record = registerManagedProcess(context, { kind: "audit-tree", ownerId: "audit-tree-owner" });
77
+ confirmManagedProcess(context, { id: record.id, leaseToken: record.lease_token, pid: tree.pid, processGroupId: tree.pid });
78
+ getResourceDatabase(resources).run("UPDATE managed_processes SET lease_expires_at = ? WHERE id = ?", ["2000-01-01T00:00:00.000Z", record.id]);
79
+ await reconcileExpiredManagedProcesses(context, "audit-tree-cleanup", 20);
80
+ let alive = true;
81
+ try { process.kill(childPid, 0); } catch { alive = false; }
82
+ assert.equal(alive, false);
83
+ } finally {
84
+ clearTimeout(timer);
85
+ try { process.kill(-tree.pid, "SIGKILL"); } catch { /* our group has already stopped */ }
86
+ await closed;
87
+ }
88
+ });
89
+ await check("the check owner reaches a terminal process state on ordinary completion", () => assert.ok(managedProcessStatus(context).filter((item) => item.kind === "check").every((item) => ["stopped", "failed"].includes(item.state))));
90
+ } finally {
91
+ context.db.close();
92
+ getResourceDatabase(resources).close();
93
+ fs.rmSync(root, { recursive: true, force: true });
94
+ }
95
+ log(JSON.stringify({ checks: results.length, passed: results.filter((item) => item.passed).length, results, observations }, null, 2));
96
+ process.exitCode = results.some((item) => !item.passed) ? 1 : 0;