dsh-openwrite 0.2.27 → 0.2.33

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 (130) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +55 -6
  3. package/THIRD_PARTY_NOTICES.md +4 -3
  4. package/companions.mjs +508 -0
  5. package/cordis.patch.yml +0 -2
  6. package/docs/CLOSED_ISSUE_FOLLOWUPS.md +5 -3
  7. package/docs/COMPANION_PLUGINS.md +142 -0
  8. package/docs/CONTEXT_SLOTS.md +60 -0
  9. package/docs/COST_MEASUREMENT.md +2 -2
  10. package/docs/DEVELOPMENT.md +6 -11
  11. package/docs/DSH_STORE.md +14 -2
  12. package/docs/EDITOR_SAFETY_REPAIRS.md +17 -0
  13. package/docs/INSTALL.md +112 -4
  14. package/docs/ISSUES_121_123.md +97 -0
  15. package/docs/ISSUES_140_142.md +58 -0
  16. package/docs/ISSUES_149_150_151.md +77 -0
  17. package/docs/ISSUES_98_100.md +2 -0
  18. package/docs/ISSUE_123_FOLLOWUP.md +55 -0
  19. package/docs/ISSUE_146_REVIEW_EVIDENCE.md +53 -0
  20. package/docs/LEARNING.md +1 -1
  21. package/docs/MOBILE_READINESS.md +62 -0
  22. package/docs/PLUGIN_MAINTENANCE.md +1 -3
  23. package/docs/RELEASE_ACCEPTANCE.md +90 -3
  24. package/docs/REVIEW_ROUND_22.md +67 -0
  25. package/docs/REVIEW_ROUND_23.md +48 -0
  26. package/docs/REVIEW_ROUND_25.md +51 -0
  27. package/docs/SELECTION_UPGRADE_REPAIRS.md +16 -0
  28. package/docs/TOOLS.md +933 -0
  29. package/docs/WORKFLOWS.md +5 -5
  30. package/package.json +18 -21
  31. package/packages/openwrite-bridge/lib/client.d.ts +0 -1
  32. package/packages/openwrite-bridge/lib/client.js +90 -9
  33. package/packages/openwrite-bridge/lib/contracts-generated.js +2 -1
  34. package/packages/openwrite-bridge/lib/dog-delivery.d.ts +1 -0
  35. package/packages/openwrite-bridge/lib/dog-delivery.js +10 -4
  36. package/packages/openwrite-bridge/lib/dog-review.js +15 -3
  37. package/packages/openwrite-bridge/lib/domain.d.ts +27 -0
  38. package/packages/openwrite-bridge/lib/domain.js +250 -29
  39. package/packages/openwrite-bridge/lib/host-llm.d.ts +188 -0
  40. package/packages/openwrite-bridge/lib/host-llm.js +657 -0
  41. package/packages/openwrite-bridge/lib/host-web.d.ts +61 -0
  42. package/packages/openwrite-bridge/lib/host-web.js +166 -0
  43. package/packages/openwrite-bridge/lib/index.d.ts +4 -0
  44. package/packages/openwrite-bridge/lib/index.js +150 -6
  45. package/packages/openwrite-bridge/lib/managed-runtime.d.ts +47 -2
  46. package/packages/openwrite-bridge/lib/managed-runtime.js +179 -35
  47. package/packages/openwrite-bridge/lib/tool-policy.js +39 -12
  48. package/packages/openwrite-bridge/lib/tools.js +1238 -1219
  49. package/packages/openwrite-bridge/package.json +7 -5
  50. package/packages/studio-panel/lib/client.js +116667 -114773
  51. package/packages/studio-panel/lib/client.js.map +1 -1
  52. package/packages/studio-panel/lib/types/client/LaunchOpenWrite.d.ts +10 -0
  53. package/packages/studio-panel/lib/types/client/ModelView.d.ts +28 -4
  54. package/packages/studio-panel/lib/types/client/OpenWriteSettings.d.ts +64 -0
  55. package/packages/studio-panel/lib/types/client/SceneWorkbench.d.ts +2 -0
  56. package/packages/studio-panel/lib/types/client/SearchSettings.d.ts +2 -0
  57. package/packages/studio-panel/lib/types/client/SerialWritingView.d.ts +2 -1
  58. package/packages/studio-panel/lib/types/client/TurnMutationSummary.d.ts +2 -0
  59. package/packages/studio-panel/lib/types/client/VditorBody.d.ts +8 -2
  60. package/packages/studio-panel/lib/types/client/api.d.ts +41 -2
  61. package/packages/studio-panel/lib/types/client/browser-download.d.ts +4 -0
  62. package/packages/studio-panel/lib/types/client/companions.d.ts +46 -0
  63. package/packages/studio-panel/lib/types/client/dto.d.ts +22 -0
  64. package/packages/studio-panel/lib/types/client/locales.d.ts +1 -1
  65. package/packages/studio-panel/lib/types/client/manuscript-annotations.d.ts +2 -1
  66. package/packages/studio-panel/lib/types/client/manuscript-overlay.d.ts +2 -1
  67. package/packages/studio-panel/lib/types/client/manuscript-selection.d.ts +18 -5
  68. package/packages/studio-panel/lib/types/client/selection-coordinates.d.ts +15 -0
  69. package/packages/studio-panel/lib/types/client/workspace-chrome.d.ts +41 -0
  70. package/packages/studio-panel/package.json +5 -3
  71. package/plugin.mjs +159 -20
  72. package/preset-definition.mjs +144 -4
  73. package/presets/openwrite/agent.cordis.yml +7 -9
  74. package/presets/openwrite/ops-integration.mjs +28 -0
  75. package/presets/openwrite/preset.yml +2 -1
  76. package/presets/openwrite/skills/canon/SKILL.md +3 -3
  77. package/presets/openwrite/skills/delivery-artifact-query/SKILL.md +41 -0
  78. package/presets/openwrite/skills/dialoguequality/SKILL.md +16 -17
  79. package/presets/openwrite/skills/dog-delivery-verification/SKILL.md +113 -0
  80. package/presets/openwrite/skills/export-book/SKILL.md +3 -3
  81. package/presets/openwrite/skills/foreshadow/SKILL.md +1 -1
  82. package/presets/openwrite/skills/foreshadowing-system/SKILL.md +16 -25
  83. package/presets/openwrite/skills/import-artifact-query/SKILL.md +30 -0
  84. package/presets/openwrite/skills/learn/SKILL.md +3 -3
  85. package/presets/openwrite/skills/novel-creator/SKILL.md +10 -10
  86. package/presets/openwrite/skills/novel-reviewer/SKILL.md +11 -11
  87. package/presets/openwrite/skills/progress/SKILL.md +1 -1
  88. package/presets/openwrite/skills/review-artifact-query/SKILL.md +41 -0
  89. package/presets/openwrite/skills/review-chapter/SKILL.md +2 -2
  90. package/presets/openwrite/skills/revise-span/SKILL.md +5 -5
  91. package/presets/openwrite/skills/style-system/SKILL.md +13 -6
  92. package/presets/openwrite/skills/truth-validation/SKILL.md +9 -12
  93. package/presets/openwrite/skills/workflow-manager/SKILL.md +13 -13
  94. package/presets/openwrite/skills/world-query/SKILL.md +16 -11
  95. package/presets/openwrite/skills/write-next/SKILL.md +1 -1
  96. package/release/{openwrite-5.8.16-py3-none-any.whl → openwrite-5.8.30-py3-none-any.whl} +0 -0
  97. package/release/runtime-manifest.json +4 -12
  98. package/release/source.json +21 -21
  99. package/release/wheels/licenses/OPENSSL-3.6.3-LICENSE.txt +225 -0
  100. package/scripts/maintenance.mjs +67 -7
  101. package/scripts/plugin-doctor.mjs +179 -0
  102. package/scripts/prepare.mjs +11 -2
  103. package/scripts/safe-upgrade.mjs +141 -13
  104. package/dog-tools.mjs +0 -11
  105. package/presets/openwrite/skills/dog-delivery-query/SKILL.md +0 -42
  106. package/presets/openwrite/skills/dog-import-query/SKILL.md +0 -29
  107. package/presets/openwrite/skills/dog-review-query/SKILL.md +0 -42
  108. package/scripts/dog/delivery-stage.js +0 -32
  109. package/scripts/dog/import-chapter.js +0 -20
  110. package/scripts/dog/import-record.js +0 -35
  111. package/scripts/dog/review-dimension.js +0 -41
  112. package/scripts/dog/review-record.js +0 -42
  113. package/vendor/dsh-dog/LICENSE +0 -29
  114. package/vendor/dsh-dog/README.md +0 -114
  115. package/vendor/dsh-dog/UPSTREAM.md +0 -30
  116. package/vendor/dsh-dog/cordis.patch.yml +0 -14
  117. package/vendor/dsh-dog/lib/client.js +0 -2960
  118. package/vendor/dsh-dog/lib/client.js.map +0 -1
  119. package/vendor/dsh-dog/lib/core-BAlgZpLq.js +0 -2074
  120. package/vendor/dsh-dog/lib/core-BiEBMLz3.d.ts +0 -495
  121. package/vendor/dsh-dog/lib/core.d.ts +0 -2
  122. package/vendor/dsh-dog/lib/core.js +0 -2
  123. package/vendor/dsh-dog/lib/index.d.ts +0 -143
  124. package/vendor/dsh-dog/lib/index.js +0 -1279
  125. package/vendor/dsh-dog/package.json +0 -110
  126. package/vendor/dsh-dog/schemas/schema-0.2/graph.schema.json +0 -139
  127. package/vendor/dsh-dog/schemas/schema-0.2/report.schema.json +0 -87
  128. package/vendor/dsh-dog/schemas/schema-0.2/run.schema.json +0 -259
  129. package/vendor/dsh-dog/schemas/schema-0.2/runtime-event.schema.json +0 -105
  130. package/vendor/dsh-dog/schemas/schema-0.2/verification.schema.json +0 -40
@@ -1,2074 +0,0 @@
1
- import { fileURLToPath } from "node:url";
2
- import { tmpdir } from "node:os";
3
- import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
4
- import { appendFile, lstat, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
5
- import { createHash, randomUUID } from "node:crypto";
6
- import { existsSync, readFileSync, readdirSync } from "node:fs";
7
- import { c } from "tar";
8
- import Ajv2020 from "ajv/dist/2020.js";
9
- import addFormats from "ajv-formats";
10
- //#region src/logic.ts
11
- /**
12
- * Parse and type-check the only expression language accepted by DoG.
13
- * @param value - untrusted JSON value from a graph draft.
14
- * @param allowedRefs - child IDs that the enclosing composite may reference.
15
- * @param limits - expression size limits.
16
- * @returns a typed AST or validation errors; never evaluates source text.
17
- */
18
- function parseBoolExpr(value, allowedRefs, limits) {
19
- const errors = [];
20
- let nodeCount = 0;
21
- function visit(candidate, depth, path) {
22
- if (depth > limits.maxDepth) {
23
- errors.push(`${path}: expression depth exceeds ${limits.maxDepth}`);
24
- return;
25
- }
26
- if (!isRecord(candidate)) {
27
- errors.push(`${path}: expression must be an object`);
28
- return;
29
- }
30
- nodeCount += 1;
31
- if (nodeCount > limits.maxNodes) {
32
- errors.push(`expression exceeds ${limits.maxNodes} nodes`);
33
- return;
34
- }
35
- const keys = Object.keys(candidate);
36
- if (!Object.prototype.hasOwnProperty.call(candidate, "op") || keys.length === 0) {
37
- errors.push(`${path}: expression requires exactly one operator`);
38
- return;
39
- }
40
- const op = candidate.op;
41
- if (typeof op !== "string") {
42
- errors.push(`${path}.op: operator must be a string`);
43
- return;
44
- }
45
- if (op === "ref") return parseRef(candidate, allowedRefs, errors, path);
46
- if (op === "not") return parseNot(candidate, depth, path, visit, errors);
47
- if (op === "all" || op === "any") return parseNary(candidate, op, depth, path, visit, errors);
48
- if (op === "atLeast") return parseThreshold(candidate, depth, path, visit, errors);
49
- errors.push(`${path}.op: unsupported operator ${JSON.stringify(op)}`);
50
- }
51
- const expression = visit(value, 0, "$");
52
- return errors.length === 0 && expression !== void 0 ? {
53
- expression,
54
- errors
55
- } : { errors };
56
- }
57
- /**
58
- * Evaluate a typed AST against settled child truth values.
59
- * @param expression - validated DoG Boolean AST.
60
- * @param values - child success values; missing values are unresolved.
61
- * @returns three-valued result so pending work cannot be mistaken for failure.
62
- */
63
- function evaluateBoolExpr(expression, values) {
64
- switch (expression.op) {
65
- case "ref": {
66
- const value = values.get(expression.id);
67
- return value === void 0 ? "unknown" : value;
68
- }
69
- case "not": {
70
- const value = evaluateBoolExpr(expression.item, values);
71
- return value === "unknown" ? "unknown" : !value;
72
- }
73
- case "all": return evaluateAll(expression, values);
74
- case "any": return evaluateAny(expression, values);
75
- case "atLeast": return evaluateAtLeast(expression, values);
76
- }
77
- }
78
- function parseRef(candidate, allowedRefs, errors, path) {
79
- if (Object.keys(candidate).length !== 2 || typeof candidate.id !== "string" || candidate.id.length === 0) {
80
- errors.push(`${path}: ref requires only a non-empty string id`);
81
- return;
82
- }
83
- if (!allowedRefs.has(candidate.id)) {
84
- errors.push(`${path}.id: ${JSON.stringify(candidate.id)} is not a containment child`);
85
- return;
86
- }
87
- return {
88
- op: "ref",
89
- id: candidate.id
90
- };
91
- }
92
- function parseNot(candidate, depth, path, visit, errors) {
93
- if (Object.keys(candidate).length !== 2 || !Object.prototype.hasOwnProperty.call(candidate, "item")) {
94
- errors.push(`${path}: not requires only item`);
95
- return;
96
- }
97
- const item = visit(candidate.item, depth + 1, `${path}.item`);
98
- return item === void 0 ? void 0 : {
99
- op: "not",
100
- item
101
- };
102
- }
103
- function parseNary(candidate, op, depth, path, visit, errors) {
104
- if (Object.keys(candidate).length !== 2 || !Array.isArray(candidate.items) || candidate.items.length === 0) {
105
- errors.push(`${path}: ${op} requires only a non-empty items array`);
106
- return;
107
- }
108
- const items = [];
109
- for (const [index, itemValue] of candidate.items.entries()) {
110
- const item = visit(itemValue, depth + 1, `${path}.items[${index}]`);
111
- if (item !== void 0) items.push(item);
112
- }
113
- if (items.length !== candidate.items.length) return void 0;
114
- return op === "all" ? {
115
- op,
116
- items
117
- } : {
118
- op,
119
- items
120
- };
121
- }
122
- function parseThreshold(candidate, depth, path, visit, errors) {
123
- if (Object.keys(candidate).length !== 3 || !Array.isArray(candidate.items) || candidate.items.length === 0 || typeof candidate.count !== "number" || !Number.isInteger(candidate.count) || candidate.count < 1 || candidate.count > candidate.items.length) {
124
- errors.push(`${path}: atLeast requires count and a non-empty items array with 1 <= count <= items.length`);
125
- return;
126
- }
127
- const items = [];
128
- for (const [index, itemValue] of candidate.items.entries()) {
129
- const item = visit(itemValue, depth + 1, `${path}.items[${index}]`);
130
- if (item !== void 0) items.push(item);
131
- }
132
- if (items.length !== candidate.items.length) return void 0;
133
- return {
134
- op: "atLeast",
135
- count: candidate.count,
136
- items
137
- };
138
- }
139
- function evaluateAll(expression, values) {
140
- let unknown = false;
141
- for (const item of expression.items) {
142
- const result = evaluateBoolExpr(item, values);
143
- if (result === false) return false;
144
- if (result === "unknown") unknown = true;
145
- }
146
- return unknown ? "unknown" : true;
147
- }
148
- function evaluateAny(expression, values) {
149
- let unknown = false;
150
- for (const item of expression.items) {
151
- const result = evaluateBoolExpr(item, values);
152
- if (result === true) return true;
153
- if (result === "unknown") unknown = true;
154
- }
155
- return unknown ? "unknown" : false;
156
- }
157
- function evaluateAtLeast(expression, values) {
158
- let passed = 0;
159
- let unknown = 0;
160
- for (const item of expression.items) {
161
- const result = evaluateBoolExpr(item, values);
162
- if (result === true) passed += 1;
163
- if (result === "unknown") unknown += 1;
164
- }
165
- if (passed >= expression.count) return true;
166
- return passed + unknown >= expression.count ? "unknown" : false;
167
- }
168
- function isRecord(value) {
169
- return value !== null && typeof value === "object" && !Array.isArray(value);
170
- }
171
- //#endregion
172
- //#region src/model.ts
173
- function isJsonValue(value) {
174
- if (value === null || typeof value === "string" || typeof value === "boolean") return true;
175
- if (typeof value === "number") return Number.isFinite(value);
176
- if (Array.isArray(value)) return value.every(isJsonValue);
177
- if (typeof value !== "object") return false;
178
- return Object.values(value).every(isJsonValue);
179
- }
180
- //#endregion
181
- //#region src/graph.ts
182
- /** Graph parser, structural validator, and cycle checker for DoG v0.2. */
183
- const ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
184
- /** Structured validation failure at the model-to-core boundary. */
185
- var DogValidationError = class extends Error {
186
- errors;
187
- constructor(errors) {
188
- super(`invalid DoG graph: ${errors.join("; ")}`);
189
- this.name = "DogValidationError";
190
- this.errors = errors;
191
- }
192
- };
193
- /** Parse an untrusted graph value into a normalized, acyclic DoG graph. */
194
- function parseGraph(value, limits) {
195
- const errors = [];
196
- const root = asRecord(value, "$", errors);
197
- if (root === void 0) throw new DogValidationError(errors);
198
- checkExactKeys(root, [
199
- "schemaVersion",
200
- "id",
201
- "root",
202
- "nodes",
203
- "contains",
204
- "dependsOn"
205
- ], "$", errors);
206
- if (root.schemaVersion !== "0.9") errors.push(`$.schemaVersion: expected 0.9`);
207
- const graphId = parseId(root.id, "$.id", errors);
208
- const rootId = parseId(root.root, "$.root", errors);
209
- const nodes = parseNodes(root.nodes, limits, errors);
210
- const contains = parseContains(root.contains, errors);
211
- const dependsOn = parseDependencies(root.dependsOn, errors);
212
- if (graphId === void 0 || rootId === void 0 || nodes === void 0 || contains === void 0 || dependsOn === void 0) throw new DogValidationError(errors);
213
- validateGraphReferences(rootId, nodes, contains, dependsOn, limits, errors);
214
- validateAcyclic(nodes, contains, dependsOn, errors);
215
- if (errors.length > 0) throw new DogValidationError(errors);
216
- return {
217
- schemaVersion: "0.9",
218
- id: graphId,
219
- root: rootId,
220
- nodes,
221
- contains,
222
- dependsOn
223
- };
224
- }
225
- function parseNodes(value, limits, errors) {
226
- const record = asRecord(value, "$.nodes", errors);
227
- if (record === void 0) return void 0;
228
- const ids = Object.keys(record);
229
- if (ids.length === 0) errors.push("$.nodes: must not be empty");
230
- if (ids.length > limits.maxGraphNodes) errors.push(`$.nodes: exceeds ${limits.maxGraphNodes} nodes`);
231
- const nodes = {};
232
- for (const id of ids) {
233
- if (!ID_PATTERN.test(id)) errors.push(`$.nodes.${id}: invalid goal ID`);
234
- const node = asRecord(record[id], `$.nodes.${id}`, errors);
235
- if (node === void 0) continue;
236
- checkExactKeys(node, [
237
- "kind",
238
- "title",
239
- "constraint",
240
- "target",
241
- "completion",
242
- "verifier"
243
- ], `$.nodes.${id}`, errors);
244
- const kind = parseEnum(node.kind, ["leaf", "composite"], `$.nodes.${id}.kind`, errors);
245
- const title = parseNonEmptyString(node.title, `$.nodes.${id}.title`, errors);
246
- const constraint = parseEnum(node.constraint, ["hard", "soft"], `$.nodes.${id}.constraint`, errors);
247
- const target = parseNonEmptyString(node.target, `$.nodes.${id}.target`, errors);
248
- const verifier = parseVerifier(node.verifier, `$.nodes.${id}.verifier`, errors);
249
- if (kind === void 0 || title === void 0 || constraint === void 0 || target === void 0) continue;
250
- if (kind === "leaf" && verifier === void 0) errors.push(`$.nodes.${id}: leaf requires verifier`);
251
- if (kind === "composite" && verifier !== void 0) {}
252
- if (kind === "composite" && node.completion === void 0) errors.push(`$.nodes.${id}: composite requires completion`);
253
- if (kind === "leaf" && node.completion !== void 0) errors.push(`$.nodes.${id}: leaf cannot declare completion`);
254
- nodes[id] = {
255
- kind,
256
- title,
257
- constraint,
258
- target,
259
- ...verifier === void 0 ? {} : { verifier },
260
- ...kind === "composite" && node.completion !== void 0 ? { completion: node.completion } : {}
261
- };
262
- }
263
- return nodes;
264
- }
265
- function parseContains(value, errors) {
266
- if (!Array.isArray(value)) {
267
- errors.push("$.contains: must be an array");
268
- return;
269
- }
270
- const edges = [];
271
- for (const [index, candidate] of value.entries()) {
272
- const path = `$.contains[${index}]`;
273
- const record = asRecord(candidate, path, errors);
274
- if (record === void 0) continue;
275
- checkExactKeys(record, [
276
- "parent",
277
- "child",
278
- "required",
279
- "failure",
280
- "degradeTo"
281
- ], path, errors);
282
- const parent = parseId(record.parent, `${path}.parent`, errors);
283
- const child = parseId(record.child, `${path}.child`, errors);
284
- const required = parseBoolean(record.required, `${path}.required`, errors);
285
- const failure = parseEnum(record.failure, [
286
- "fatal",
287
- "tolerable",
288
- "degrade"
289
- ], `${path}.failure`, errors);
290
- const degradeTo = record.degradeTo === void 0 ? void 0 : parseId(record.degradeTo, `${path}.degradeTo`, errors);
291
- if (failure !== "degrade" && degradeTo !== void 0) errors.push(`${path}.degradeTo: only valid with failure=degrade`);
292
- if (failure === "degrade" && degradeTo === void 0) errors.push(`${path}: failure=degrade requires degradeTo`);
293
- if (parent !== void 0 && child !== void 0 && required !== void 0 && failure !== void 0) edges.push({
294
- parent,
295
- child,
296
- required,
297
- failure,
298
- ...degradeTo === void 0 ? {} : { degradeTo }
299
- });
300
- }
301
- return edges;
302
- }
303
- function parseDependencies(value, errors) {
304
- if (!Array.isArray(value)) {
305
- errors.push("$.dependsOn: must be an array");
306
- return;
307
- }
308
- const edges = [];
309
- for (const [index, candidate] of value.entries()) {
310
- const path = `$.dependsOn[${index}]`;
311
- const record = asRecord(candidate, path, errors);
312
- if (record === void 0) continue;
313
- checkExactKeys(record, [
314
- "source",
315
- "target",
316
- "data"
317
- ], path, errors);
318
- const source = parseId(record.source, `${path}.source`, errors);
319
- const target = parseId(record.target, `${path}.target`, errors);
320
- const data = parseStringArray(record.data, `${path}.data`, errors);
321
- if (source !== void 0 && target !== void 0) edges.push({
322
- source,
323
- target,
324
- ...data === void 0 ? {} : { data }
325
- });
326
- }
327
- return edges;
328
- }
329
- function parseVerifier(value, path, errors) {
330
- if (value === void 0) return void 0;
331
- const record = asRecord(value, path, errors);
332
- if (record === void 0) return void 0;
333
- const mode = parseEnum(record.mode, ["programmatic", "agentic"], `${path}.mode`, errors);
334
- if (mode === void 0) return void 0;
335
- if (mode === "programmatic") {
336
- checkExactKeys(record, ["mode", "script"], path, errors);
337
- const script = parseNonEmptyString(record.script, `${path}.script`, errors);
338
- return script === void 0 ? void 0 : {
339
- mode: "programmatic",
340
- script
341
- };
342
- }
343
- checkExactKeys(record, ["mode", "instruction"], path, errors);
344
- const instruction = parseNonEmptyString(record.instruction, `${path}.instruction`, errors);
345
- return instruction === void 0 ? void 0 : {
346
- mode: "agentic",
347
- instruction
348
- };
349
- }
350
- function validateGraphReferences(root, nodes, contains, dependsOn, limits, errors) {
351
- if (!Object.prototype.hasOwnProperty.call(nodes, root)) errors.push(`$.root: unknown node ${root}`);
352
- const rootNode = nodes[root];
353
- if (rootNode !== void 0 && rootNode.constraint !== "hard") errors.push("$.root: root goal must be a hard constraint");
354
- const children = /* @__PURE__ */ new Map();
355
- const edgeKeys = /* @__PURE__ */ new Set();
356
- for (const edge of contains) {
357
- const key = `${edge.parent}\u0000${edge.child}`;
358
- if (edgeKeys.has(key)) errors.push(`$.contains: duplicate edge ${edge.parent}->${edge.child}`);
359
- edgeKeys.add(key);
360
- if (!Object.prototype.hasOwnProperty.call(nodes, edge.parent)) errors.push(`$.contains: unknown parent ${edge.parent}`);
361
- if (!Object.prototype.hasOwnProperty.call(nodes, edge.child)) errors.push(`$.contains: unknown child ${edge.child}`);
362
- const parent = nodes[edge.parent];
363
- if (parent !== void 0 && parent.kind !== "composite") errors.push(`$.contains: ${edge.parent} is not composite`);
364
- const siblings = children.get(edge.parent) ?? [];
365
- siblings.push(edge.child);
366
- children.set(edge.parent, siblings);
367
- }
368
- for (const edge of contains) if (edge.degradeTo === edge.child) errors.push(`$.contains: degradeTo cannot equal child ${edge.child}`);
369
- else if (edge.degradeTo !== void 0 && !(children.get(edge.parent) ?? []).includes(edge.degradeTo)) errors.push(`$.contains: degradeTo ${edge.degradeTo} is not a child of ${edge.parent}`);
370
- const dependencyKeys = /* @__PURE__ */ new Set();
371
- for (const edge of dependsOn) {
372
- const key = `${edge.source}\u0000${edge.target}`;
373
- if (dependencyKeys.has(key)) errors.push(`$.dependsOn: duplicate edge ${edge.source}->${edge.target}`);
374
- dependencyKeys.add(key);
375
- if (edge.source === edge.target) errors.push(`$.dependsOn: self-dependency ${edge.source}`);
376
- if (!Object.prototype.hasOwnProperty.call(nodes, edge.source)) errors.push(`$.dependsOn: unknown source ${edge.source}`);
377
- if (!Object.prototype.hasOwnProperty.call(nodes, edge.target)) errors.push(`$.dependsOn: unknown target ${edge.target}`);
378
- }
379
- for (const [parent, node] of Object.entries(nodes)) {
380
- if (node.kind !== "composite") continue;
381
- const childIds = children.get(parent) ?? [];
382
- if (childIds.length === 0) errors.push(`$.nodes.${parent}: composite requires at least one child`);
383
- if (node.completion === void 0) {
384
- errors.push(`$.nodes.${parent}.completion: missing completion expression`);
385
- continue;
386
- }
387
- const parsed = parseBoolExpr(node.completion, new Set(childIds), {
388
- maxNodes: limits.maxExpressionNodes,
389
- maxDepth: limits.maxExpressionDepth
390
- });
391
- if (parsed.errors.length > 0) errors.push(...parsed.errors.map((error) => `$.nodes.${parent}.completion${error.slice(1)}`));
392
- else if (parsed.expression !== void 0) nodes[parent] = {
393
- ...node,
394
- completion: parsed.expression
395
- };
396
- }
397
- const reachable = /* @__PURE__ */ new Set();
398
- const queue = [root];
399
- while (queue.length > 0) {
400
- const current = queue.shift();
401
- if (current === void 0 || reachable.has(current)) continue;
402
- reachable.add(current);
403
- for (const child of children.get(current) ?? []) queue.push(child);
404
- }
405
- for (const id of Object.keys(nodes)) if (!reachable.has(id)) errors.push(`$.nodes.${id}: unreachable from root through containment`);
406
- }
407
- function validateAcyclic(nodes, contains, dependsOn, errors) {
408
- const outgoing = /* @__PURE__ */ new Map();
409
- for (const id of Object.keys(nodes)) outgoing.set(id, []);
410
- for (const edge of contains) outgoing.get(edge.parent)?.push(edge.child);
411
- for (const edge of dependsOn) outgoing.get(edge.source)?.push(edge.target);
412
- const visiting = /* @__PURE__ */ new Set();
413
- const visited = /* @__PURE__ */ new Set();
414
- function visit(id, path) {
415
- if (visiting.has(id)) {
416
- errors.push(`graph contains a cycle: ${[...path, id].join(" -> ")}`);
417
- return;
418
- }
419
- if (visited.has(id)) return;
420
- visiting.add(id);
421
- for (const target of outgoing.get(id) ?? []) visit(target, [...path, id]);
422
- visiting.delete(id);
423
- visited.add(id);
424
- }
425
- for (const id of Object.keys(nodes)) visit(id, []);
426
- }
427
- function asRecord(value, path, errors) {
428
- if (value === null || typeof value !== "object" || Array.isArray(value)) {
429
- errors.push(`${path}: must be an object`);
430
- return;
431
- }
432
- return value;
433
- }
434
- function checkExactKeys(value, allowed, path, errors) {
435
- const allowedSet = new Set(allowed);
436
- for (const key of Object.keys(value)) if (!allowedSet.has(key)) errors.push(`${path}: unknown field ${key}`);
437
- }
438
- function parseId(value, path, errors) {
439
- if (typeof value !== "string" || !ID_PATTERN.test(value)) {
440
- errors.push(`${path}: must be an ASCII ID matching ${ID_PATTERN.source}`);
441
- return;
442
- }
443
- return value;
444
- }
445
- function parseNonEmptyString(value, path, errors) {
446
- if (typeof value !== "string" || value.trim().length === 0) {
447
- errors.push(`${path}: must be a non-empty string`);
448
- return;
449
- }
450
- return value;
451
- }
452
- function parseBoolean(value, path, errors) {
453
- if (typeof value !== "boolean") {
454
- errors.push(`${path}: must be boolean`);
455
- return;
456
- }
457
- return value;
458
- }
459
- function parseEnum(value, allowed, path, errors) {
460
- if (typeof value !== "string" || !allowed.includes(value)) {
461
- errors.push(`${path}: expected one of ${allowed.join(", ")}`);
462
- return;
463
- }
464
- return value;
465
- }
466
- function parseStringArray(value, path, errors) {
467
- if (value === void 0) return void 0;
468
- if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
469
- errors.push(`${path}: must be an array of strings`);
470
- return;
471
- }
472
- return [...value];
473
- }
474
- //#endregion
475
- //#region src/json.ts
476
- /** Deterministic JSON utilities used for graph and snapshot identities. */
477
- /**
478
- * Produce a deterministic JSON representation with recursively sorted object keys.
479
- * @param value - JSON-compatible input.
480
- * @returns canonical JSON text.
481
- */
482
- function canonicalJson(value) {
483
- return JSON.stringify(sortJson(value));
484
- }
485
- /**
486
- * Hash a JSON-compatible value with SHA-256.
487
- * @param value - value to hash.
488
- * @returns a lower-case hexadecimal SHA-256 digest.
489
- */
490
- function sha256Json(value) {
491
- return createHash("sha256").update(canonicalJson(value)).digest("hex");
492
- }
493
- function sortJson(value) {
494
- if (Array.isArray(value)) return value.map(sortJson);
495
- if (value !== null && typeof value === "object") {
496
- const result = {};
497
- for (const key of Object.keys(value).sort()) result[key] = sortJson(value[key]);
498
- return result;
499
- }
500
- return value;
501
- }
502
- //#endregion
503
- //#region src/lockfile.ts
504
- /** Cross-process per-record lock: exclusive-create lockfile with stale expiry. */
505
- var FileLock = class {
506
- path;
507
- constructor(path) {
508
- this.path = path;
509
- }
510
- /**
511
- * Try to acquire the lock. create-exclusive ('wx') is atomic across
512
- * processes; an existing lockfile older than `staleMs` is considered
513
- * abandoned (crashed holder) and replaced.
514
- */
515
- async tryAcquire(staleMs = 6e4) {
516
- try {
517
- const handle = await open(this.path, "wx");
518
- await handle.writeFile(`${process.pid} ${Date.now()}`);
519
- await handle.close();
520
- return true;
521
- } catch (error) {
522
- if (error.code !== "EEXIST") return false;
523
- try {
524
- const info = await stat(this.path);
525
- if (Date.now() - info.mtimeMs > staleMs) {
526
- await rm(this.path, { force: true });
527
- return this.tryAcquire(staleMs);
528
- }
529
- } catch {}
530
- return false;
531
- }
532
- }
533
- async release() {
534
- await rm(this.path, { force: true }).catch(() => void 0);
535
- }
536
- };
537
- /** Bounded wait for a FileLock; throws on timeout so callers fail loudly. */
538
- async function withFileLock(path, action, options = {}) {
539
- const lock = new FileLock(path);
540
- const deadline = Date.now() + (options.waitMs ?? 1500);
541
- for (;;) {
542
- if (await lock.tryAcquire(options.staleMs)) break;
543
- if (Date.now() >= deadline) throw new Error(`storage lock timeout: ${path}`);
544
- await new Promise((resolve) => setTimeout(resolve, 25));
545
- }
546
- try {
547
- return await action();
548
- } finally {
549
- await lock.release();
550
- }
551
- }
552
- //#endregion
553
- //#region src/schema.ts
554
- /** JSON Schema validation layer: every exchange payload is validated before parsing or persistence. */
555
- /** Load the five normative schemas from schemas/schema-0.2 (checked into the package root). */
556
- async function loadSchemaSet() {
557
- const schemasDir = fileURLToPath(new URL("../schemas/schema-0.2/", import.meta.url));
558
- const ajv = new Ajv2020({
559
- allErrors: true,
560
- strict: false
561
- });
562
- addFormats(ajv);
563
- const load = async (name) => JSON.parse(await readFile(join(schemasDir, name), "utf8"));
564
- const compile = (schema) => ajv.compile(schema);
565
- return {
566
- validateGraph: compile(await load("graph.schema.json")),
567
- validateRun: compile(await load("run.schema.json")),
568
- validateVerification: compile(await load("verification.schema.json")),
569
- validateRuntimeEvent: compile(await load("runtime-event.schema.json")),
570
- validateReport: compile(await load("report.schema.json"))
571
- };
572
- }
573
- /** Format ajv errors into one-line messages with instance paths. */
574
- function schemaErrorText(validate) {
575
- return (validate.errors ?? []).map((error) => `${error.instancePath || "$"} ${error.message ?? "is invalid"}${"params" in error && typeof error.params === "object" && error.params !== null ? ` (${JSON.stringify(error.params)})` : ""}`);
576
- }
577
- //#endregion
578
- //#region src/storage.ts
579
- /** Sandboxed input capture and durable DoG records. */
580
- var DogRepository = class {
581
- rootPath;
582
- runMutationTails = /* @__PURE__ */ new Map();
583
- schema;
584
- constructor(rootPath, schema = void 0) {
585
- this.rootPath = rootPath;
586
- this.schema = schema;
587
- }
588
- /** Create the repository directory layout. */
589
- async initialize() {
590
- await Promise.all([
591
- mkdir(join(this.rootPath, "graphs"), { recursive: true }),
592
- mkdir(join(this.rootPath, "graph-index"), { recursive: true }),
593
- mkdir(join(this.rootPath, "runs"), { recursive: true }),
594
- mkdir(join(this.rootPath, "verifications"), { recursive: true }),
595
- mkdir(join(this.rootPath, "runtime-events"), { recursive: true }),
596
- mkdir(join(this.rootPath, "artifacts"), { recursive: true }),
597
- mkdir(join(this.rootPath, "locks"), { recursive: true })
598
- ]);
599
- }
600
- /** Persist one immutable compiled graph revision and its ID index. */
601
- async saveGraph(compiled) {
602
- await this.initialize();
603
- await withFileLock(join(this.rootPath, "locks", `graph-${compiled.graphDigest}.lock`), async () => {
604
- const record = {
605
- schemaVersion: "0.2",
606
- graphDigest: compiled.graphDigest,
607
- input: compiled.input,
608
- acceptancePlans: compiled.acceptancePlans
609
- };
610
- await atomicWriteJson(join(this.rootPath, "graphs", `${compiled.graphDigest}.json`), record);
611
- await atomicWriteJson(join(this.rootPath, "graph-index", `${safeKey(compiled.input.id)}.json`), {
612
- graphId: compiled.input.id,
613
- graphDigest: compiled.graphDigest
614
- });
615
- });
616
- }
617
- /** Load the latest graph revision for a graph ID. */
618
- async loadGraph(graphId) {
619
- const index = await readJson(join(this.rootPath, "graph-index", `${safeKey(graphId)}.json`));
620
- if (index.graphId !== graphId || !isDigest(index.graphDigest)) throw new Error(`invalid graph index for ${graphId}`);
621
- return readCompiledGraph(this.rootPath, index.graphDigest);
622
- }
623
- /** Enumerate immutable graph revisions. Pre-v0.2 records (no grounding/gmDigest contract) are skipped, not deleted. */
624
- async listGraphs() {
625
- await this.initialize();
626
- const entries = await readdir(join(this.rootPath, "graphs"), { withFileTypes: true });
627
- const graphs = [];
628
- for (const entry of entries) {
629
- if (!entry.isFile() || !/^[a-f0-9]{64}\.json$/u.test(entry.name)) continue;
630
- try {
631
- graphs.push(await readCompiledGraph(this.rootPath, entry.name.slice(0, -5)));
632
- } catch (error) {
633
- if (!String(error instanceof Error ? error.message : error).startsWith("invalid persisted graph record")) throw error;
634
- }
635
- }
636
- return graphs.sort((left, right) => {
637
- const byId = left.input.id.localeCompare(right.input.id);
638
- return byId === 0 ? left.graphDigest.localeCompare(right.graphDigest) : byId;
639
- });
640
- }
641
- /** Persist a run snapshot. */
642
- async saveRun(run) {
643
- await this.initialize();
644
- await withFileLock(join(this.rootPath, "locks", `run-${safeKey(run.runId)}.lock`), () => atomicWriteJson(join(this.rootPath, "runs", `${safeKey(run.runId)}.json`), run));
645
- }
646
- /** Atomically read, transform, and replace one run within this repository process. */
647
- async updateRun(runId, update) {
648
- return this.withRunMutation(runId, async () => {
649
- const current = await this.loadRun(runId);
650
- const next = update(current);
651
- if (next.runId !== current.runId || next.graphId !== current.graphId || next.graphDigest !== current.graphDigest) throw new Error(`run update cannot change identity for ${runId}`);
652
- await atomicWriteJson(join(this.rootPath, "runs", `${safeKey(runId)}.json`), next);
653
- return next;
654
- });
655
- }
656
- async withRunMutation(runId, action) {
657
- const previous = this.runMutationTails.get(runId) ?? Promise.resolve();
658
- let release;
659
- const gate = new Promise((resolve) => {
660
- release = resolve;
661
- });
662
- const tail = previous.then(() => gate, () => gate);
663
- this.runMutationTails.set(runId, tail);
664
- await previous.catch(() => void 0);
665
- try {
666
- return await withFileLock(join(this.rootPath, "locks", `run-${safeKey(runId)}.lock`), action);
667
- } finally {
668
- release();
669
- if (this.runMutationTails.get(runId) === tail) this.runMutationTails.delete(runId);
670
- }
671
- }
672
- /** Load one run snapshot, fail-closed against the run schema. */
673
- async loadRun(runId) {
674
- const run = await readJson(join(this.rootPath, "runs", `${safeKey(runId)}.json`));
675
- if (this.schema !== void 0 && !this.schema.validateRun(run)) throw new Error(`run record does not validate: ${schemaErrorText(this.schema.validateRun).join("; ")}`);
676
- return run;
677
- }
678
- /**
679
- * Load the most recently updated non-running run for a graph ID
680
- * (revalidate_select input). Any terminal record may carry verification
681
- * settlements worth inheriting — including orphaned runs that a host restart
682
- * left running and that recovery later invalidated or cancelled.
683
- */
684
- async loadLatestSettledRun(graphId) {
685
- return (await this.listRuns()).find((run) => run.graphId === graphId && run.state !== "running");
686
- }
687
- /** Enumerate persisted run snapshots, newest update first. Pre-v0.2 records are skipped. */
688
- async listRuns() {
689
- await this.initialize();
690
- const entries = await readdir(join(this.rootPath, "runs"), { withFileTypes: true });
691
- const runs = [];
692
- for (const entry of entries) {
693
- if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
694
- const key = entry.name.slice(0, -5);
695
- if (!/^[a-f0-9]{64}$/u.test(key)) continue;
696
- try {
697
- const run = await readJson(join(this.rootPath, "runs", `${key}.json`));
698
- if (this.schema !== void 0 && !this.schema.validateRun(run)) continue;
699
- if (typeof run.runId !== "string" || run.runId.length === 0 || entry.name !== `${safeKey(run.runId)}.json`) continue;
700
- if (!isDigest(run.graphDigest)) continue;
701
- runs.push(run);
702
- } catch {
703
- continue;
704
- }
705
- }
706
- return runs.sort((left, right) => {
707
- const byTime = right.updatedAt.localeCompare(left.updatedAt);
708
- return byTime === 0 ? left.runId.localeCompare(right.runId) : byTime;
709
- });
710
- }
711
- /**
712
- * Cancel every persisted run still in `running` state. Called at host boot
713
- * (before any engine instance exists), so runs from a killed/restarted host
714
- * surface as cancelled instead of frozen "running" forever. Their settled
715
- * leaves stay inheritable by the next run of the same graph.
716
- */
717
- async markOrphanedRunningRuns(nowIso) {
718
- const runs = await this.listRuns();
719
- let marked = 0;
720
- for (const prior of runs) {
721
- const orphanedGoals = Object.values(prior.goals).some((goal) => goal.state === "pending" || goal.state === "running");
722
- if (prior.state !== "running" && !orphanedGoals) continue;
723
- await this.updateRun(prior.runId, (current) => ({
724
- ...current,
725
- ...current.state === "running" ? {
726
- state: "cancelled",
727
- rootState: "cancelled",
728
- runtimeWarning: `orphaned by host restart (last update ${prior.updatedAt})`
729
- } : {},
730
- goals: Object.fromEntries(Object.entries(current.goals).map(([goalId, goal]) => goal.state === "pending" || goal.state === "running" ? [goalId, {
731
- ...goal,
732
- state: "cancelled"
733
- }] : [goalId, goal])),
734
- updatedAt: nowIso
735
- }));
736
- marked++;
737
- }
738
- return marked;
739
- }
740
- /** Append one verification record without rewriting prior records. */
741
- async appendVerification(record) {
742
- await this.initialize();
743
- const path = join(this.rootPath, "verifications", `${safeKey(record.runId)}.jsonl`);
744
- await appendFile(path, `${canonicalJson(record)}\n`, {
745
- encoding: "utf8",
746
- flag: "a"
747
- });
748
- }
749
- /** Append diagnostic runtime context to the selected goal shard, separately from trusted verifier evidence. */
750
- async appendRuntimeEvent(event) {
751
- await this.initialize();
752
- const path = join(this.rootPath, "runtime-events", `${safeKey(event.runId)}-${safeKey(event.goalId)}.jsonl`);
753
- await appendFile(path, `${canonicalJson(event)}\n`, {
754
- encoding: "utf8",
755
- flag: "a"
756
- });
757
- }
758
- /** Load one goal's validated event shard. A torn final line is ignored. */
759
- async loadGoalRuntimeEvents(runId, goalId) {
760
- const path = join(this.rootPath, "runtime-events", `${safeKey(runId)}-${safeKey(goalId)}.jsonl`);
761
- let source;
762
- try {
763
- source = await readFile(path, "utf8");
764
- } catch (error) {
765
- if (isMissing(error)) return [];
766
- throw error;
767
- }
768
- return (source.endsWith("\n") ? source : source.slice(0, source.lastIndexOf("\n") + 1)).split("\n").filter((line) => line.length > 0).map((line, index) => parseRuntimeEvent(JSON.parse(line), runId, goalId, index));
769
- }
770
- /** Persist the verbatim verifier settlement text (survives workspace cleanup). */
771
- async saveSettlement(runId, goalId, content) {
772
- await this.initialize();
773
- await atomicWriteJson(join(this.rootPath, "settlements", `${safeKey(runId)}-${safeKey(goalId)}.json`), {
774
- runId,
775
- goalId,
776
- savedAt: (/* @__PURE__ */ new Date()).toISOString(),
777
- content
778
- });
779
- }
780
- /** Store captured input bytes and its metadata manifest under content identity. */
781
- async putSandboxFile(input, bytes) {
782
- await this.initialize();
783
- const key = capturedKey(input.digest);
784
- if (bytes !== void 0) await atomicWrite(join(this.rootPath, "artifacts", `${key}.bin`), bytes);
785
- await atomicWriteJson(join(this.rootPath, "artifacts", `${key}.json`), input);
786
- }
787
- /** Read and re-hash a captured input named by a verification plan. */
788
- async readCapturedFile(input) {
789
- if (!input.exists) throw new Error(`captured input ${input.digest} represents a missing file`);
790
- const bytes = await readFile(join(this.rootPath, "artifacts", `${capturedKey(input.digest)}.bin`));
791
- if (createHash("sha256").update(bytes).digest("hex") !== input.sha256 || bytes.byteLength !== input.byteLength) throw new Error(`captured input integrity mismatch for ${input.digest}`);
792
- return bytes;
793
- }
794
- /** InputReader contract: read one captured input. */
795
- async read(input) {
796
- return this.readCapturedFile(input);
797
- }
798
- /** Copy a captured input into one run workspace (or resolve to the workspace dir). */
799
- async copyCapturedInput(input, workspacePath) {
800
- if (input === void 0) return workspacePath;
801
- const bytes = await this.readCapturedFile(input);
802
- const target = join(workspacePath, input.path);
803
- await mkdir(dirname(target), { recursive: true });
804
- await writeFile(target, bytes);
805
- return target;
806
- }
807
- };
808
- /** Resolve one workspace-relative path to an absolute path, never accepting escapes. */
809
- async function resolveWorkspacePath(workspaceRoot, relPath) {
810
- if (!isAbsolute(workspaceRoot)) throw new Error("workspaceRoot must be absolute");
811
- if (relPath === "") throw new Error("empty workspace path has no single file to resolve");
812
- if (isAbsolute(relPath)) throw new Error("workspace path must be relative");
813
- const rootPath = resolve(workspaceRoot);
814
- const lexicalCandidate = resolve(rootPath, relPath);
815
- const lexicalRelative = relative(rootPath, lexicalCandidate);
816
- if (!isWithinRoot(lexicalRelative)) throw new Error(`workspace path ${relPath} escapes its configured root`);
817
- if (!(await stat(rootPath)).isDirectory()) throw new Error(`workspace root ${workspaceRoot} is not a directory`);
818
- return resolveBoundPath(await realpath(rootPath), lexicalRelative);
819
- }
820
- /**
821
- * Capture one target: a single file, or a directory packed into a .tar
822
- * (relative structure preserved). Roots are tried in order — the invoking
823
- * session cwd first (which is where the user is actually working), then the
824
- * configured workspace root as fallback. A non-missing error (escape, not a
825
- * regular file…) always aborts; only a target absent from every root settles
826
- * as `missing`.
827
- */
828
- async function captureWorkspaceTarget(captureRoots, target, maxBytes, repository) {
829
- for (const root of captureRoots) try {
830
- const absolutePath = await resolveWorkspacePath(root, target);
831
- const metadata = await stat(absolutePath);
832
- let bytes;
833
- let packed = false;
834
- if (metadata.isDirectory()) {
835
- const rootPath = resolve(root);
836
- assertTreeSafe(rootPath, absolutePath);
837
- const chunks = [];
838
- let length = 0;
839
- for await (const chunk of c({
840
- cwd: rootPath,
841
- portable: true
842
- }, [target])) {
843
- length += chunk.length;
844
- if (length > maxBytes) throw new Error(`target ${target} exceeds ${maxBytes} bytes`);
845
- chunks.push(chunk);
846
- }
847
- bytes = Buffer.concat(chunks);
848
- packed = true;
849
- } else if (metadata.isFile()) bytes = await readFile(absolutePath);
850
- else throw new Error(`target ${target} is not a regular file or directory`);
851
- if (bytes.byteLength > maxBytes) throw new Error(`target ${target} exceeds ${maxBytes} bytes`);
852
- const sha256 = createHash("sha256").update(bytes).digest("hex");
853
- const input = {
854
- path: target,
855
- digest: `sha256:${sha256}`,
856
- exists: true,
857
- byteLength: bytes.byteLength,
858
- sha256,
859
- ...packed ? { packed: true } : {}
860
- };
861
- await repository.putSandboxFile(input, bytes);
862
- return { input };
863
- } catch (error) {
864
- if (!isMissing(error)) throw error;
865
- }
866
- const input = {
867
- path: target,
868
- digest: `missing:${sha256Json({ path: target })}`,
869
- exists: false,
870
- byteLength: 0,
871
- sha256: ""
872
- };
873
- await repository.putSandboxFile(input, void 0);
874
- return { input };
875
- }
876
- /** Reject symlinks anywhere in the tree before packing (tar would otherwise carry them over). */
877
- function assertTreeSafe(rootPath, dirPath) {
878
- const entries = readdirSync(dirPath, { withFileTypes: true });
879
- for (const entry of entries) {
880
- const child = join(dirPath, entry.name);
881
- if (entry.isSymbolicLink()) throw new Error(`target tree contains a symbolic link: ${relative(rootPath, child)}`);
882
- if (entry.isDirectory()) assertTreeSafe(rootPath, child);
883
- }
884
- }
885
- function capturedKey(digest) {
886
- return digest.replaceAll(/[^a-zA-Z0-9_-]/gu, "_");
887
- }
888
- function safeKey(value) {
889
- return sha256Json(value);
890
- }
891
- function isWithinRoot(relativePath) {
892
- return relativePath !== ".." && !relativePath.startsWith(`..${sep}`) && !isAbsolute(relativePath);
893
- }
894
- async function resolveBoundPath(rootPath, relativePath) {
895
- const segments = relativePath.length === 0 ? [] : relativePath.split(sep);
896
- let current = rootPath;
897
- for (let index = 0; index < segments.length; index++) {
898
- const segment = segments[index];
899
- if (segment === void 0) continue;
900
- const candidate = join(current, segment);
901
- let metadata;
902
- try {
903
- metadata = await lstat(candidate);
904
- } catch (error) {
905
- if (!isMissing(error)) throw error;
906
- return join(current, ...segments.slice(index));
907
- }
908
- if (metadata.isSymbolicLink()) try {
909
- current = await realpath(candidate);
910
- } catch (error) {
911
- if (isMissing(error)) throw new Error("sandbox path contains a dangling symbolic link");
912
- throw error;
913
- }
914
- else current = candidate;
915
- if (!isWithinRoot(relative(rootPath, current))) throw new Error("sandbox path escapes its configured root through a symbolic link");
916
- }
917
- return current;
918
- }
919
- async function readCompiledGraph(rootPath, graphDigest) {
920
- if (!isDigest(graphDigest)) throw new Error(`invalid graph digest ${graphDigest}`);
921
- const record = await readJson(join(rootPath, "graphs", `${graphDigest}.json`));
922
- const actualDigest = sha256Json({
923
- input: record.input,
924
- acceptancePlans: record.acceptancePlans
925
- });
926
- if (record.schemaVersion !== "0.2" || record.graphDigest !== graphDigest || actualDigest !== graphDigest) throw new Error(`invalid persisted graph record ${graphDigest}`);
927
- return {
928
- input: record.input,
929
- graphDigest: record.graphDigest,
930
- acceptancePlans: record.acceptancePlans
931
- };
932
- }
933
- function isDigest(value) {
934
- return typeof value === "string" && /^[a-f0-9]{64}$/u.test(value);
935
- }
936
- const RUNTIME_PHASES = /* @__PURE__ */ new Set([
937
- "goal_started",
938
- "dependency_blocked",
939
- "grounding_extracted",
940
- "workspace_allocated",
941
- "verifier_started",
942
- "verifier_passed",
943
- "verifier_failed",
944
- "verifier_inconclusive",
945
- "composite_evaluated",
946
- "result_inherited",
947
- "verifier_released",
948
- "verifier_bind_failed",
949
- "structured_error",
950
- "goal_settled",
951
- "run_warning"
952
- ]);
953
- const GOAL_STATES = /* @__PURE__ */ new Set([
954
- "pending",
955
- "running",
956
- "success",
957
- "failure",
958
- "blocked",
959
- "needs_human",
960
- "cancelled",
961
- "invalidated",
962
- "partial",
963
- "inherited"
964
- ]);
965
- function parseRuntimeEvent(value, expectedRunId, expectedGoalId, index) {
966
- const path = `runtime event ${index}`;
967
- const record = requireRecord(value, path);
968
- const runId = requireString(record.runId, `${path}.runId`);
969
- const goalId = requireString(record.goalId, `${path}.goalId`);
970
- const phase = requireString(record.phase, `${path}.phase`);
971
- if (runId !== expectedRunId) throw new Error(`${path}.runId does not match its event log`);
972
- if (goalId !== expectedGoalId) throw new Error(`${path}.goalId does not match its event log`);
973
- if (!RUNTIME_PHASES.has(phase)) throw new Error(`${path}.phase is invalid`);
974
- const state = record.state === void 0 ? void 0 : requireString(record.state, `${path}.state`);
975
- if (state !== void 0 && !GOAL_STATES.has(state)) throw new Error(`${path}.state is invalid`);
976
- const reason = record.reason === void 0 ? void 0 : requireBoundedString(record.reason, `${path}.reason`);
977
- const durationMs = record.durationMs === void 0 ? void 0 : requireNonNegativeInteger(record.durationMs, `${path}.durationMs`);
978
- const attempt = record.attempt === void 0 ? void 0 : requireNonNegativeInteger(record.attempt, `${path}.attempt`);
979
- if (attempt !== void 0 && attempt < 1) throw new Error(`${path}.attempt must be positive`);
980
- const gmDigest = record.gmDigest === void 0 ? void 0 : requireString(record.gmDigest, `${path}.gmDigest`);
981
- const verifierRecord = record.verifier === void 0 ? void 0 : requireRecord(record.verifier, `${path}.verifier`);
982
- const verifier = verifierRecord === void 0 ? void 0 : { mode: requireEnum(verifierRecord.mode, ["programmatic", "agentic"], `${path}.verifier.mode`) };
983
- return {
984
- schemaVersion: record.schemaVersion === "0.1" ? "0.1" : fail(`${path}.schemaVersion must be 0.1`),
985
- runId,
986
- goalId,
987
- phase,
988
- at: requireString(record.at, `${path}.at`),
989
- ...state === void 0 ? {} : { state },
990
- ...reason === void 0 ? {} : { reason },
991
- ...verifier === void 0 ? {} : { verifier },
992
- ...gmDigest === void 0 ? {} : { gmDigest },
993
- ...attempt === void 0 ? {} : { attempt },
994
- ...durationMs === void 0 ? {} : { durationMs }
995
- };
996
- }
997
- function requireRecord(value, path) {
998
- if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error(`${path} must be an object`);
999
- return Object.fromEntries(Object.entries(value));
1000
- }
1001
- function requireString(value, path) {
1002
- if (typeof value !== "string") throw new Error(`${path} must be a string`);
1003
- return value;
1004
- }
1005
- function requireEnum(value, allowed, path) {
1006
- const candidate = requireString(value, path);
1007
- if (!allowed.includes(candidate)) throw new Error(`${path} is invalid`);
1008
- return candidate;
1009
- }
1010
- function requireBoundedString(value, path) {
1011
- const text = requireString(value, path);
1012
- if (text.length > 512) throw new Error(`${path} exceeds 512 characters`);
1013
- return text;
1014
- }
1015
- function requireNonNegativeInteger(value, path) {
1016
- if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) throw new Error(`${path} must be a non-negative safe integer`);
1017
- return value;
1018
- }
1019
- function fail(message) {
1020
- throw new Error(message);
1021
- }
1022
- async function atomicWriteJson(path, value) {
1023
- await atomicWrite(path, `${JSON.stringify(value)}\n`);
1024
- }
1025
- async function atomicWrite(path, value) {
1026
- const temporary = `${path}.tmp-${process.pid}-${randomUUID()}`;
1027
- await writeFile(temporary, value);
1028
- await rename(temporary, path);
1029
- }
1030
- async function readJson(path) {
1031
- return JSON.parse(await readFile(path, "utf8"));
1032
- }
1033
- function isMissing(error) {
1034
- return error instanceof Error && "code" in error && error.code === "ENOENT";
1035
- }
1036
- //#endregion
1037
- //#region src/verifiers.ts
1038
- /**
1039
- * Run one plan through the selected kernel. The engine holds none of the
1040
- * judgment semantics: programmatic goes to a script, agentic to a worker agent
1041
- * with only the instruction text — no parameter schema, no evidence format,
1042
- * no tool allow-list.
1043
- */
1044
- async function runPlan(plan, workspace, inputPath, env, programmatic, agentic) {
1045
- if (plan.verifier.mode === "programmatic") {
1046
- if (programmatic === void 0) return {
1047
- state: "inconclusive",
1048
- observation: { outcome: "programmatic kernel unavailable" }
1049
- };
1050
- const verdict = await programmatic(plan.verifier.script, inputPath, env);
1051
- return {
1052
- state: verdict.state,
1053
- observation: evidenceToObservation(verdict),
1054
- evidence: verdict.evidence
1055
- };
1056
- }
1057
- if (agentic === void 0) return {
1058
- state: "inconclusive",
1059
- observation: { outcome: "agentic kernel unavailable" }
1060
- };
1061
- const verdict = await agentic(plan.verifier.instruction, workspace, inputPath, env);
1062
- return {
1063
- state: verdict.state,
1064
- observation: evidenceToObservation(verdict),
1065
- evidence: verdict.evidence
1066
- };
1067
- }
1068
- function evidenceToObservation(verdict) {
1069
- const observation = { outcome: verdict.state };
1070
- if (verdict.reason !== void 0) observation.reason = verdict.reason;
1071
- if (verdict.evidence !== void 0) observation.evidence = verdict.evidence;
1072
- return observation;
1073
- }
1074
- //#endregion
1075
- //#region src/workspace.ts
1076
- /** Mutually exclusive isolated workspaces: one per verification, never shared, never merged. */
1077
- var WorkspaceManager = class {
1078
- baseDir;
1079
- pending = /* @__PURE__ */ new Set();
1080
- constructor(options = {}) {
1081
- this.baseDir = options.baseDir ?? join(tmpdir(), "dsh-dog-workspaces");
1082
- }
1083
- /**
1084
- * Allocate one mutually exclusive workspace directory.
1085
- *
1086
- * `baseDir` overrides the construction default: the engine passes the calling
1087
- * session's cwd so the workspace lands inside the session workspace, which is
1088
- * exactly the tree the verifier subagent's sandbox (workspace-write) permits.
1089
- * Defaults to the manager's configured base dir when no override is given.
1090
- */
1091
- async acquire(baseDir) {
1092
- const root = baseDir ?? this.baseDir;
1093
- await mkdir(root, { recursive: true });
1094
- return { path: await mkdtemp(join(root, `${randomUUID()}-`)) };
1095
- }
1096
- /** Release a workspace after verification completes; contents are discarded. */
1097
- async release(workspace) {
1098
- const tracked = rm(workspace.path, {
1099
- recursive: true,
1100
- force: true
1101
- }).catch(() => void 0).finally(() => this.pending.delete(tracked));
1102
- this.pending.add(tracked);
1103
- await tracked;
1104
- }
1105
- /** Wait for every in-flight release (host shutdown). */
1106
- async drain() {
1107
- await Promise.allSettled([...this.pending]);
1108
- }
1109
- /** Release a batch at once — used at run teardown, after every verifier settled. */
1110
- async releaseAll(workspaces) {
1111
- await Promise.allSettled(workspaces.map((workspace) => this.release(workspace)));
1112
- }
1113
- };
1114
- //#endregion
1115
- //#region src/core.ts
1116
- /** DoG v0.9 engine: graph compilation, two-kernel judgment, Agentic CI run. */
1117
- const GOAL_AGENT_ROLES = /* @__PURE__ */ new Set([
1118
- "orchestrator",
1119
- "verifier",
1120
- "reviewer"
1121
- ]);
1122
- /** Agentic CI engine used by the DSH tools and tests. */
1123
- var DogEngine = class {
1124
- config;
1125
- repository;
1126
- liveConfig;
1127
- onRunCancelled;
1128
- runControllers = /* @__PURE__ */ new Map();
1129
- programmatic;
1130
- agentic;
1131
- workspaces;
1132
- now;
1133
- nextRunId;
1134
- heartbeatMs;
1135
- workspaceRoot;
1136
- backgroundTasks = /* @__PURE__ */ new Set();
1137
- runStops = /* @__PURE__ */ new Map();
1138
- workspaceBaseDirs = /* @__PURE__ */ new Map();
1139
- resolveLivingAgent;
1140
- /** Install the two judgment kernels (host wiring; may arrive later than construction). */
1141
- setKernels(programmatic, agentic) {
1142
- this.programmatic = programmatic;
1143
- this.agentic = agentic;
1144
- }
1145
- constructor(options) {
1146
- validateHostConfig(options.config);
1147
- this.config = options.config;
1148
- this.repository = options.repository;
1149
- this.liveConfig = options.liveConfig;
1150
- this.onRunCancelled = options.onRunCancelled;
1151
- this.programmatic = options.programmatic;
1152
- this.agentic = options.agentic;
1153
- this.workspaces = options.workspaces ?? new WorkspaceManager();
1154
- this.now = options.now ?? (() => /* @__PURE__ */ new Date());
1155
- this.nextRunId = options.nextRunId ?? (() => randomUUID());
1156
- this.heartbeatMs = options.heartbeatMs ?? 3e4;
1157
- this.resolveLivingAgent = options.resolveLivingAgent;
1158
- this.workspaceRoot = options.config.workspaceRoot;
1159
- }
1160
- validate(value) {
1161
- try {
1162
- const graph = this.parse(value);
1163
- this.validateWorkspaceRefs(graph);
1164
- return {
1165
- valid: true,
1166
- errors: [],
1167
- warnings: graphWarnings(graph)
1168
- };
1169
- } catch (error) {
1170
- return {
1171
- valid: false,
1172
- errors: error instanceof DogValidationError ? [...error.errors] : [messageOf(error)],
1173
- warnings: []
1174
- };
1175
- }
1176
- }
1177
- /** Compile and persist one graph revision against the captured input files. */
1178
- async create(value, options = {}) {
1179
- const graph = this.parse(value);
1180
- this.validateWorkspaceRefs(graph);
1181
- const acceptancePlans = {};
1182
- const capturedCache = /* @__PURE__ */ new Map();
1183
- const captureRoots = options.captureBaseDir !== void 0 && options.captureBaseDir.length > 0 ? [options.captureBaseDir, this.workspaceRoot] : [this.workspaceRoot];
1184
- for (const [goalId, node] of Object.entries(graph.nodes)) {
1185
- if (node.verifier === void 0) continue;
1186
- const workspaceFile = requireTarget(node.target, goalId);
1187
- let captured = void 0;
1188
- if (workspaceFile !== "") {
1189
- captured = capturedCache.get(workspaceFile);
1190
- if (captured === void 0) {
1191
- captured = await captureWorkspaceTarget(captureRoots, workspaceFile, this.config.maxSandboxBytes, this.repository);
1192
- capturedCache.set(workspaceFile, captured);
1193
- }
1194
- }
1195
- acceptancePlans[goalId] = {
1196
- goalId,
1197
- verifier: node.verifier,
1198
- judgment: computeJudgment(node.verifier, this.config.scriptsDirectory),
1199
- target: node.target,
1200
- ...workspaceFile === "" || captured === void 0 ? {} : { input: captured.input }
1201
- };
1202
- }
1203
- const compiled = deepFreeze({
1204
- input: graph,
1205
- graphDigest: sha256Json({
1206
- input: graph,
1207
- acceptancePlans
1208
- }),
1209
- acceptancePlans
1210
- });
1211
- await this.repository.saveGraph(compiled);
1212
- return compiled;
1213
- }
1214
- /** Synchronous run: prepare + execute to completion (tests, CLI). */
1215
- async run(graphId, options = {}) {
1216
- const prepared = await this.prepareRun(graphId, options);
1217
- return this.executeRun(prepared, options);
1218
- }
1219
- /** Asynchronous run: returns after the initial record is persisted; verification continues in background. */
1220
- async startRun(graphId, options = {}) {
1221
- const prepared = await this.prepareRun(graphId, options);
1222
- const controller = new AbortController();
1223
- this.runControllers.set(prepared.runId, controller);
1224
- this.enqueueBackground(prepared, {
1225
- ...options,
1226
- signal: controller.signal
1227
- });
1228
- return prepared.initial;
1229
- }
1230
- /** Execute a prepared run on the engine's process-level loop, independent of any caller session. */
1231
- enqueueBackground(prepared, options) {
1232
- const task = this.executeRun(prepared, options).then(() => void 0).catch(async (cause) => {
1233
- try {
1234
- const live = await this.repository.loadRun(prepared.runId);
1235
- if (live.state === "running") await this.repository.saveRun({
1236
- ...live,
1237
- state: "failed",
1238
- rootState: "failure",
1239
- runtimeWarning: `background execution failed: ${boundedMessage(messageOf(cause))}`,
1240
- updatedAt: this.now().toISOString()
1241
- });
1242
- } catch {}
1243
- this.runStops.get(prepared.runId)?.();
1244
- this.runStops.delete(prepared.runId);
1245
- this.runControllers.delete(prepared.runId);
1246
- this.backgroundTasks.delete(task);
1247
- });
1248
- task.finally(() => this.backgroundTasks.delete(task));
1249
- this.backgroundTasks.add(task);
1250
- }
1251
- /** Stop this engine's owned runs before awaiting their settlement. */
1252
- async dispose() {
1253
- await Promise.allSettled([...this.runControllers.keys()].map((id) => this.cancelRun(id, "OpenWrite stopped")));
1254
- for (const stop of this.runStops.values()) stop();
1255
- this.runStops.clear();
1256
- await this.drainBackground();
1257
- }
1258
- /** Wait for every in-flight background run (host shutdown). */
1259
- async drainBackground() {
1260
- await Promise.allSettled([...this.backgroundTasks]);
1261
- }
1262
- /** Persist a bounded run-level warning (verifier binding/release diagnostics). */
1263
- async annotateRun(runId, warning) {
1264
- try {
1265
- await this.repository.updateRun(runId, (current) => ({
1266
- ...current,
1267
- runtimeWarning: boundedMessage(warning),
1268
- updatedAt: this.now().toISOString()
1269
- }));
1270
- } catch {}
1271
- }
1272
- /** Append a verifier worker lifecycle event (verifier_released / verifier_bind_failed). */
1273
- async recordVerifierLifecycle(runId, goalId, phase, _sessionId, reason) {
1274
- try {
1275
- await this.repository.appendRuntimeEvent({
1276
- schemaVersion: "0.1",
1277
- runId,
1278
- goalId,
1279
- phase,
1280
- at: this.now().toISOString(),
1281
- ...reason === void 0 ? {} : { reason: boundedMessage(reason) },
1282
- verifier: { mode: "agentic" }
1283
- });
1284
- } catch {}
1285
- }
1286
- /** Cancel one run and stop its heartbeat; partial records remain inspectable. */
1287
- async cancelRun(runId, reason) {
1288
- this.runControllers.get(runId)?.abort(new Error(reason));
1289
- this.runControllers.delete(runId);
1290
- this.runStops.get(runId)?.();
1291
- this.runStops.delete(runId);
1292
- const updated = await this.repository.updateRun(runId, (current) => ({
1293
- ...current,
1294
- state: "cancelled",
1295
- rootState: "cancelled",
1296
- runtimeWarning: `cancelled: ${boundedMessage(reason)}`,
1297
- updatedAt: this.now().toISOString()
1298
- }));
1299
- this.onRunCancelled?.(runId);
1300
- return updated;
1301
- }
1302
- /**
1303
- * Block on one run's settlement. Keeps the calling agent/session alive in
1304
- * the process while verifier workers (continuable children of this process)
1305
- * run to completion — the deterministic automation gate after dog_run. A
1306
- * one-shot process that returns before this settles kills the workers it
1307
- * spawned, so automation MUST wait through a dog_wait/monitor step.
1308
- * @param options - bounded wait: default 15 minutes; abort (tool cancel)
1309
- * and timeout both return the current run with a non-terminal state.
1310
- */
1311
- async waitRun(runId, options = {}) {
1312
- const deadline = Date.now() + (options.timeoutMs ?? 9e5);
1313
- const pollMs = options.pollMs ?? 15e3;
1314
- for (;;) {
1315
- const run = await this.repository.loadRun(runId);
1316
- if (run.state !== "running") return run;
1317
- options.signal?.throwIfAborted();
1318
- if (Date.now() >= deadline) return run;
1319
- await new Promise((resolve) => setTimeout(resolve, pollMs));
1320
- }
1321
- }
1322
- async prepareRun(graphId, options) {
1323
- const compiled = await this.repository.loadGraph(graphId);
1324
- const runId = this.nextRunId();
1325
- await this.supersedePriorRunningRuns(compiled.input.id, runId);
1326
- const createdAt = this.now().toISOString();
1327
- const invocation = options.invocation === void 0 ? void 0 : {
1328
- callId: requireContextId(options.invocation.callId, "invocation callId"),
1329
- ...options.invocation.agentSessionId === void 0 ? {} : { agentSessionId: requireContextId(options.invocation.agentSessionId, "invocation agentSessionId") },
1330
- ...options.invocation.parentSessionId === void 0 ? {} : { parentSessionId: requireContextId(options.invocation.parentSessionId, "invocation parentSessionId") },
1331
- invokedAt: createdAt
1332
- };
1333
- const goals = Object.fromEntries(Object.keys(compiled.input.nodes).map((id) => [id, { state: "pending" }]));
1334
- const gmDigests = {};
1335
- const workspaceBaseDir = options.invocation?.agentSessionId === void 0 || this.resolveLivingAgent === void 0 ? void 0 : (() => {
1336
- const cwd = this.resolveLivingAgent(options.invocation.agentSessionId)?.session.header.cwd;
1337
- return typeof cwd === "string" && cwd.length > 0 ? cwd : void 0;
1338
- })();
1339
- this.workspaceBaseDirs.set(runId, workspaceBaseDir);
1340
- const initial = {
1341
- runId,
1342
- graphId: compiled.input.id,
1343
- graphDigest: compiled.graphDigest,
1344
- ...workspaceBaseDir === void 0 ? {} : { workspaceBaseDir },
1345
- state: "running",
1346
- ...invocation === void 0 ? {} : { invocation },
1347
- gmDigests,
1348
- goals,
1349
- createdAt,
1350
- updatedAt: createdAt
1351
- };
1352
- await this.repository.saveRun(initial);
1353
- return {
1354
- compiled,
1355
- runId,
1356
- initial
1357
- };
1358
- }
1359
- /** The Agentic CI cycle: revalidate-select, parallel verification, recompute. */
1360
- async executeRun(prepared, options) {
1361
- const { compiled, runId } = prepared;
1362
- const signal = options.signal ?? new AbortController().signal;
1363
- const goals = { ...prepared.initial.goals };
1364
- const gmDigests = { ...prepared.initial.gmDigests };
1365
- let run = prepared.initial;
1366
- let stopHeartbeat = () => {};
1367
- const heartbeat = setInterval(() => {
1368
- if (run.state !== "running") {
1369
- stopHeartbeat();
1370
- return;
1371
- }
1372
- this.repository.updateRun(runId, (current) => ({
1373
- ...current,
1374
- updatedAt: this.now().toISOString()
1375
- })).catch(() => void 0);
1376
- }, this.heartbeatMs);
1377
- stopHeartbeat = () => clearInterval(heartbeat);
1378
- this.runStops.set(runId, stopHeartbeat);
1379
- const appendRuntimeEvent = async (goalId, phase, details = {}) => {
1380
- const event = {
1381
- schemaVersion: "0.1",
1382
- runId,
1383
- goalId,
1384
- phase,
1385
- at: details.at ?? this.now().toISOString(),
1386
- ...details.state === void 0 ? {} : { state: details.state },
1387
- ...details.reason === void 0 ? {} : { reason: boundedMessage(details.reason) },
1388
- ...details.verifier === void 0 ? {} : { verifier: details.verifier },
1389
- ...details.gmDigest === void 0 ? {} : { gmDigest: details.gmDigest },
1390
- ...details.attempt === void 0 ? {} : { attempt: details.attempt },
1391
- ...details.durationMs === void 0 ? {} : { durationMs: details.durationMs }
1392
- };
1393
- try {
1394
- await this.repository.appendRuntimeEvent(event);
1395
- } catch (error) {
1396
- if (run.runtimeWarning === void 0) run = {
1397
- ...run,
1398
- runtimeWarning: `runtime context write failed: ${boundedMessage(messageOf(error))}`
1399
- };
1400
- }
1401
- };
1402
- const saveProgress = async (updatedAt) => {
1403
- await this.repository.updateRun(runId, (current) => ({
1404
- ...current,
1405
- ...run.runtimeWarning !== void 0 ? { runtimeWarning: run.runtimeWarning } : {},
1406
- goals: this.mergeGoalsKeepingHostSessions(goals, current.goals),
1407
- gmDigests: { ...gmDigests },
1408
- updatedAt
1409
- }));
1410
- };
1411
- const dependencies = dependenciesBySource(compiled.input);
1412
- const children = childrenByParent(compiled.input);
1413
- const relations = relationsByParent(compiled.input);
1414
- const priorRun = await this.repository.loadLatestSettledRun(compiled.input.id);
1415
- const planFor = (goalId) => compiled.acceptancePlans[goalId];
1416
- const inheritedFor = (goalId) => {
1417
- if (priorRun === void 0) return void 0;
1418
- const plan = planFor(goalId);
1419
- if (plan === void 0) return void 0;
1420
- const anchor = anchorOfPlan(plan);
1421
- if (anchor === void 0) return void 0;
1422
- if (priorRun.gmDigests[goalId] !== anchor) return void 0;
1423
- const priorRecord = priorRun.goals[goalId]?.verification;
1424
- if (priorRecord === void 0) return void 0;
1425
- return {
1426
- state: "inherited",
1427
- inheritedFrom: priorRun.runId,
1428
- verification: priorRecord
1429
- };
1430
- };
1431
- const leaves = Object.keys(compiled.input.nodes).filter((id) => compiled.input.nodes[id]?.kind === "leaf");
1432
- for (const goalId of leaves) {
1433
- const plan = planFor(goalId);
1434
- if (plan === void 0) continue;
1435
- const anchor = anchorOfPlan(plan);
1436
- if (anchor !== void 0) {
1437
- gmDigests[goalId] = anchor;
1438
- const inherited = inheritedFor(goalId);
1439
- if (inherited !== void 0) {
1440
- goals[goalId] = inherited;
1441
- await appendRuntimeEvent(goalId, "result_inherited", {
1442
- state: "inherited",
1443
- reason: `reused ${inherited.inheritedFrom ?? "prior run"} verification record`
1444
- });
1445
- await saveProgress(this.now().toISOString());
1446
- }
1447
- }
1448
- }
1449
- const mergeWholeAssertion = (logical, settled) => {
1450
- if (settled.state === "fail") return {
1451
- state: "failure",
1452
- reason: "whole-object assertion failed"
1453
- };
1454
- if (settled.state === "inconclusive") return {
1455
- state: "needs_human",
1456
- reason: "whole-object assertion inconclusive"
1457
- };
1458
- return logical;
1459
- };
1460
- const evaluateComposite = (goalId) => {
1461
- const node = compiled.input.nodes[goalId];
1462
- const parentRelations = relations.get(goalId) ?? [];
1463
- const childIds = children.get(goalId) ?? [];
1464
- const values = relationTruthValues(parentRelations, goals);
1465
- if (node?.completion === void 0) return {
1466
- state: "needs_human",
1467
- reason: "validated composite lost its completion expression"
1468
- };
1469
- const requiredFailures = parentRelations.filter((edge) => edge.required && relationTruth(edge, goals) === false);
1470
- const fatalRequiredFailure = requiredFailures.find((edge) => edge.failure === "fatal");
1471
- const requiredUnknown = parentRelations.find((edge) => edge.required && relationTruth(edge, goals) === void 0);
1472
- if (fatalRequiredFailure !== void 0) return {
1473
- state: "failure",
1474
- reason: `required non-tolerable child ${fatalRequiredFailure.child} failed`
1475
- };
1476
- if (requiredUnknown !== void 0) return relationNeedsHuman(requiredUnknown, goals) ? {
1477
- state: "needs_human",
1478
- reason: `required child relation ${requiredUnknown.child} requires human review`
1479
- } : {
1480
- state: "blocked",
1481
- reason: `required child ${requiredUnknown.child} is unresolved`
1482
- };
1483
- if (requiredFailures.length > 0) return {
1484
- state: "partial",
1485
- reason: `required child ${requiredFailures[0]?.child ?? "unknown"} failed under a tolerable policy`
1486
- };
1487
- const completion = evaluateBoolExpr(node.completion, values);
1488
- if (completion === true) return { state: "success" };
1489
- if (completion === "unknown") {
1490
- const humanChild = childIds.find((childId) => dependencyNeedsHuman(goals[childId]));
1491
- return humanChild === void 0 ? {
1492
- state: "blocked",
1493
- reason: "one or more child outcomes are unresolved"
1494
- } : {
1495
- state: "needs_human",
1496
- reason: `child ${humanChild} requires human review`
1497
- };
1498
- }
1499
- const fatal = parentRelations.filter((edge) => relationTruth(edge, goals) === false).find((edge) => edge.failure === "fatal");
1500
- return fatal === void 0 ? {
1501
- state: "partial",
1502
- reason: "completion rule is false, but every failed child relation is tolerable or optional"
1503
- } : {
1504
- state: "failure",
1505
- reason: `non-tolerable child ${fatal.child} failed`
1506
- };
1507
- };
1508
- const settleLeaf = async (goalId) => {
1509
- const node = compiled.input.nodes[goalId];
1510
- const plan = compiled.acceptancePlans[goalId];
1511
- if (node === void 0 || node.kind !== "leaf") return goals[goalId] ?? { state: "pending" };
1512
- if (plan === void 0) return {
1513
- state: "needs_human",
1514
- reason: "compiled acceptance plan is missing"
1515
- };
1516
- let workspace;
1517
- try {
1518
- const startedAt = this.now().toISOString();
1519
- goals[goalId] = { state: "running" };
1520
- await appendRuntimeEvent(goalId, "goal_started", {
1521
- at: startedAt,
1522
- state: "running"
1523
- });
1524
- await saveProgress(startedAt);
1525
- workspace = await this.workspaces.acquire(this.workspaceBaseDirs.get(runId));
1526
- runWorkspaces.add(workspace);
1527
- await appendRuntimeEvent(goalId, "workspace_allocated", {
1528
- state: "running",
1529
- attempt: 1
1530
- });
1531
- const verifier = { mode: plan.verifier.mode };
1532
- await appendRuntimeEvent(goalId, "verifier_started", {
1533
- state: "running",
1534
- verifier,
1535
- attempt: 1
1536
- });
1537
- const inputPath = await prepareInputPath(plan, workspace, this.repository);
1538
- const settled = await runPlan(plan, workspace, inputPath, {
1539
- parent: options.agent,
1540
- signal,
1541
- runId,
1542
- goalId
1543
- }, this.programmatic, this.agentic);
1544
- if (signal.aborted) return {
1545
- state: "cancelled",
1546
- reason: "run cancelled; verifier aborted"
1547
- };
1548
- const verdict = settled.state === "pass" ? "success" : settled.state === "fail" ? "failure" : "needs_human";
1549
- const record = {
1550
- schemaVersion: "0.1",
1551
- goalId,
1552
- runId,
1553
- graphId: compiled.input.id,
1554
- graphDigest: compiled.graphDigest,
1555
- judgment: plan.judgment,
1556
- ...plan.gmDigest === void 0 ? {} : { gmDigest: plan.gmDigest },
1557
- passed: settled.state === "pass" ? true : settled.state === "fail" ? false : null,
1558
- ...settled.evidence === void 0 ? {} : { evidence: settled.evidence },
1559
- at: this.now().toISOString()
1560
- };
1561
- const result = verdict === "success" ? {
1562
- state: "success",
1563
- verification: record
1564
- } : verdict === "failure" ? {
1565
- state: "failure",
1566
- verification: record
1567
- } : {
1568
- state: "needs_human",
1569
- reason: "verifier was inconclusive; evidence insufficient",
1570
- verification: record
1571
- };
1572
- const phase = settled.state === "pass" ? "verifier_passed" : settled.state === "fail" ? "verifier_failed" : "verifier_inconclusive";
1573
- await this.repository.appendVerification(record);
1574
- await appendRuntimeEvent(goalId, phase, {
1575
- state: result.state,
1576
- verifier,
1577
- attempt: 1,
1578
- ...result.state === "failure" ? { reason: "trusted verifier rejected the captured input" } : {},
1579
- ...result.state === "needs_human" ? { reason: "verifier inconclusive" } : {}
1580
- });
1581
- return result;
1582
- } catch (cause) {
1583
- const error = {
1584
- kind: "verification_error",
1585
- stage: "verification",
1586
- message: boundedMessage(messageOf(cause))
1587
- };
1588
- const failed = {
1589
- state: "needs_human",
1590
- reason: error.message
1591
- };
1592
- await appendRuntimeEvent(goalId, "structured_error", {
1593
- state: "needs_human",
1594
- reason: error.kind
1595
- });
1596
- goals[goalId] = failed;
1597
- return failed;
1598
- }
1599
- };
1600
- const pendingLeaves = new Set(leaves.filter((id) => goals[id]?.state === "pending"));
1601
- const configuredConcurrency = this.liveConfig?.()?.maxConcurrentVerifications ?? this.config.maxConcurrentVerifications;
1602
- const concurrency = Math.max(1, Math.floor(configuredConcurrency));
1603
- const inFlight = /* @__PURE__ */ new Set();
1604
- const inFlightAgentic = /* @__PURE__ */ new Set();
1605
- const runWorkspaces = /* @__PURE__ */ new Set();
1606
- const drain = async () => {
1607
- await Promise.allSettled([...inFlight]);
1608
- inFlight.clear();
1609
- inFlightAgentic.clear();
1610
- };
1611
- const pump = async () => {
1612
- for (const goalId of [...pendingLeaves]) {
1613
- const plan = planFor(goalId);
1614
- if ((dependencies.get(goalId) ?? []).find((target) => !dependencyComplete(goals[target])) !== void 0) continue;
1615
- const isAgentic = plan?.verifier.mode === "agentic";
1616
- if (isAgentic && inFlightAgentic.size >= concurrency) continue;
1617
- pendingLeaves.delete(goalId);
1618
- const task = settleLeaf(goalId).then((result) => {
1619
- goals[goalId] = result;
1620
- const settledAt = this.now().toISOString();
1621
- return appendRuntimeEvent(goalId, "goal_settled", {
1622
- at: settledAt,
1623
- state: result.state,
1624
- ...result.reason === void 0 ? {} : { reason: result.reason },
1625
- attempt: 1
1626
- }).then(() => saveProgress(settledAt));
1627
- }).finally(() => {
1628
- inFlight.delete(task);
1629
- inFlightAgentic.delete(task);
1630
- });
1631
- inFlight.add(task);
1632
- if (isAgentic) inFlightAgentic.add(task);
1633
- }
1634
- };
1635
- let aborted = false;
1636
- signal.addEventListener("abort", () => {
1637
- aborted = true;
1638
- }, { once: true });
1639
- while (!aborted) {
1640
- await pump();
1641
- if (inFlight.size === 0 || pendingLeaves.size === 0) break;
1642
- await Promise.race([...inFlight]);
1643
- }
1644
- await drain();
1645
- const settleComposite = async (goalId) => {
1646
- this.now().toISOString();
1647
- goals[goalId] = { state: "running" };
1648
- await saveProgress(this.now().toISOString());
1649
- let result = evaluateComposite(goalId);
1650
- const wholePlan = planFor(goalId);
1651
- if (wholePlan !== void 0 && result.state !== "failure") try {
1652
- const workspace = await this.workspaces.acquire(this.workspaceBaseDirs.get(runId));
1653
- runWorkspaces.add(workspace);
1654
- await appendRuntimeEvent(goalId, "verifier_started", {
1655
- state: "running",
1656
- verifier: { mode: wholePlan.verifier.mode },
1657
- attempt: 1
1658
- });
1659
- const settled = await runPlan(wholePlan, workspace, await prepareInputPath(wholePlan, workspace, this.repository), {
1660
- parent: options.agent,
1661
- signal,
1662
- runId,
1663
- goalId
1664
- }, this.programmatic, this.agentic);
1665
- const phase = settled.state === "pass" ? "verifier_passed" : settled.state === "fail" ? "verifier_failed" : "verifier_inconclusive";
1666
- await appendRuntimeEvent(goalId, phase, {
1667
- at: this.now().toISOString(),
1668
- state: settled.state === "pass" ? "success" : settled.state === "fail" ? "failure" : "needs_human",
1669
- verifier: { mode: wholePlan.verifier.mode }
1670
- });
1671
- result = mergeWholeAssertion(result, settled);
1672
- const record = {
1673
- schemaVersion: "0.1",
1674
- goalId,
1675
- runId,
1676
- graphId: compiled.input.id,
1677
- graphDigest: compiled.graphDigest,
1678
- judgment: wholePlan.judgment,
1679
- ...wholePlan.gmDigest === void 0 ? {} : { gmDigest: wholePlan.gmDigest },
1680
- passed: settled.state === "pass" ? true : settled.state === "fail" ? false : null,
1681
- ...settled.evidence === void 0 ? {} : { evidence: settled.evidence },
1682
- at: this.now().toISOString()
1683
- };
1684
- await this.repository.appendVerification(record);
1685
- result = {
1686
- ...result,
1687
- verification: record
1688
- };
1689
- } catch (error) {
1690
- result = {
1691
- state: "failure",
1692
- reason: `whole-object assertion failed: ${boundedMessage(messageOf(error))}`
1693
- };
1694
- }
1695
- await appendRuntimeEvent(goalId, "composite_evaluated", {
1696
- state: result.state,
1697
- ...result.reason === void 0 ? {} : { reason: result.reason }
1698
- });
1699
- await appendRuntimeEvent(goalId, "goal_settled", {
1700
- at: this.now().toISOString(),
1701
- state: result.state,
1702
- ...result.reason === void 0 ? {} : { reason: result.reason }
1703
- });
1704
- goals[goalId] = result;
1705
- await saveProgress(this.now().toISOString());
1706
- };
1707
- const compositePending = new Set(postorder(compiled.input).filter((id) => compiled.input.nodes[id]?.kind === "composite"));
1708
- const compositeInFlight = /* @__PURE__ */ new Set();
1709
- while (!aborted && (compositePending.size > 0 || compositeInFlight.size > 0)) {
1710
- for (const goalId of [...compositePending]) {
1711
- if ((children.get(goalId) ?? []).find((id) => !dependencyComplete(goals[id])) !== void 0) continue;
1712
- const blockedDependency = (dependencies.get(goalId) ?? []).find((target) => !dependencyComplete(goals[target]));
1713
- if (blockedDependency !== void 0) {
1714
- goals[goalId] = {
1715
- state: dependencyNeedsHuman(goals[blockedDependency]) ? "needs_human" : "blocked",
1716
- reason: `waiting for dependency ${blockedDependency} to complete`
1717
- };
1718
- compositePending.delete(goalId);
1719
- await saveProgress(this.now().toISOString());
1720
- continue;
1721
- }
1722
- const isAgentic = planFor(goalId)?.verifier.mode === "agentic";
1723
- if (isAgentic && inFlightAgentic.size >= concurrency) continue;
1724
- compositePending.delete(goalId);
1725
- const task = settleComposite(goalId).then(() => void 0).finally(() => {
1726
- compositeInFlight.delete(task);
1727
- if (isAgentic) inFlightAgentic.delete(task);
1728
- });
1729
- compositeInFlight.add(task);
1730
- if (isAgentic) inFlightAgentic.add(task);
1731
- }
1732
- if (compositeInFlight.size === 0) break;
1733
- await Promise.race([...compositeInFlight]);
1734
- }
1735
- await Promise.allSettled([...compositeInFlight]);
1736
- const rootResult = goals[compiled.input.root];
1737
- const rootState = terminalState(rootResult, this.config.allowPartialRoot);
1738
- const updatedAt = this.now().toISOString();
1739
- const inheritableLeaves = leaves.filter((id) => planFor(id)?.gmDigest !== void 0);
1740
- const revalidatedCount = inheritableLeaves.filter((id) => goals[id]?.state !== "inherited").length;
1741
- if (inheritableLeaves.some((id) => goals[id]?.state === "inherited") && inheritableLeaves.length > 0 && revalidatedCount / inheritableLeaves.length > this.config.revalidateThreshold) run = {
1742
- ...run,
1743
- runtimeWarning: `this submission re-triggers ${revalidatedCount}/${leaves.length} leaves; exceeds revalidateThreshold — confirm unrelated parts were not changed`
1744
- };
1745
- if (signal.aborted) await this.repository.updateRun(runId, (current) => ({
1746
- ...current,
1747
- state: "cancelled",
1748
- rootState: "cancelled",
1749
- goals: current.goals,
1750
- updatedAt
1751
- }));
1752
- else await this.repository.updateRun(runId, (current) => ({
1753
- ...current,
1754
- state: "completed",
1755
- rootState,
1756
- ...run.runtimeWarning !== void 0 ? { runtimeWarning: run.runtimeWarning } : {},
1757
- goals: this.mergeGoalsKeepingHostSessions(goals, current.goals),
1758
- gmDigests: { ...gmDigests },
1759
- updatedAt
1760
- }));
1761
- run = signal.aborted ? {
1762
- ...run,
1763
- state: "cancelled",
1764
- rootState: "cancelled",
1765
- updatedAt
1766
- } : {
1767
- ...run,
1768
- state: "completed",
1769
- rootState,
1770
- goals: { ...goals },
1771
- gmDigests: { ...gmDigests },
1772
- updatedAt
1773
- };
1774
- await this.workspaces.releaseAll([...runWorkspaces]);
1775
- stopHeartbeat();
1776
- this.runStops.delete(runId);
1777
- this.runControllers.delete(runId);
1778
- return deepFreeze(run);
1779
- }
1780
- /**
1781
- * Merge in-memory goal progress with host-agent-session bindings recorded
1782
- * concurrently inside an atomic read-modify-write: bindAgent (and future
1783
- * host writers) update the same row under updateRun's serialization, so a
1784
- * progress save can never overwrite a binding it never saw.
1785
- */
1786
- mergeGoalsKeepingHostSessions(target, current) {
1787
- const merged = { ...current };
1788
- for (const [goalId, value] of Object.entries(target)) {
1789
- const prior = current[goalId];
1790
- merged[goalId] = prior !== void 0 && (prior.agentSessions?.length ?? 0) > 0 ? {
1791
- ...value,
1792
- agentSessions: prior.agentSessions
1793
- } : value;
1794
- }
1795
- return merged;
1796
- }
1797
- /** Mark any prior run of the same graph that never reached a terminal state, so a host restart cannot leave zombies. */
1798
- async supersedePriorRunningRuns(graphId, nextRunId) {
1799
- const runs = await this.repository.listRuns();
1800
- for (const prior of runs) {
1801
- if (prior.graphId !== graphId || prior.state !== "running" || prior.runId === nextRunId) continue;
1802
- await this.repository.updateRun(prior.runId, (current) => ({
1803
- ...current,
1804
- state: "cancelled",
1805
- rootState: "cancelled",
1806
- runtimeWarning: `superseded by run ${nextRunId} (host restart or previous run was interrupted)`,
1807
- updatedAt: this.now().toISOString()
1808
- }));
1809
- }
1810
- }
1811
- async status(runId) {
1812
- return this.repository.loadRun(runId);
1813
- }
1814
- async assertAgentCanDelegate(options) {
1815
- const runId = requireContextId(options.runId, "runId");
1816
- const goalId = requireContextId(options.goalId, "goalId");
1817
- const parentSessionId = requireContextId(options.parentSessionId, "delegating Agent sessionId");
1818
- const run = await this.repository.loadRun(runId);
1819
- if (run.goals[goalId] === void 0) throw new Error(`run ${runId} has no goal ${goalId}`);
1820
- const ownerSessionId = run.invocation?.agentSessionId;
1821
- if (ownerSessionId === void 0) throw new Error(`run ${runId} has no trusted invocation Agent session`);
1822
- const knownSessions = /* @__PURE__ */ new Set([ownerSessionId]);
1823
- for (const goal of Object.values(run.goals)) for (const linked of goal.agentSessions ?? []) knownSessions.add(linked.sessionId);
1824
- if (!knownSessions.has(parentSessionId)) throw new Error(`Agent session ${parentSessionId} is not rooted in run ${runId}`);
1825
- }
1826
- async bindAgent(options, policy = {}) {
1827
- const runId = requireContextId(options.runId, "runId");
1828
- const goalId = requireContextId(options.goalId, "goalId");
1829
- const sessionId = requireContextId(options.sessionId, "agent sessionId");
1830
- const parentSessionId = options.parentSessionId === void 0 ? void 0 : requireContextId(options.parentSessionId, "agent parentSessionId");
1831
- if (!GOAL_AGENT_ROLES.has(options.role)) throw new Error(`unsupported goal Agent role ${String(options.role)}`);
1832
- const boundAt = this.now().toISOString();
1833
- let agent;
1834
- const run = await this.repository.updateRun(runId, (current) => {
1835
- const result = current.goals[goalId];
1836
- if (result === void 0) throw new Error(`run ${runId} has no goal ${goalId}`);
1837
- const ownerSessionId = current.invocation?.agentSessionId;
1838
- if (ownerSessionId === void 0) throw new Error(`run ${runId} has no trusted invocation Agent session`);
1839
- const knownSessions = /* @__PURE__ */ new Set([ownerSessionId]);
1840
- for (const goal of Object.values(current.goals)) for (const linked of goal.agentSessions ?? []) knownSessions.add(linked.sessionId);
1841
- if (sessionId !== ownerSessionId && (parentSessionId === void 0 || !knownSessions.has(parentSessionId))) {
1842
- if (policy.allowUnrooted === true && parentSessionId === void 0) {} else throw new Error(`Agent session ${sessionId} is not rooted in run ${runId}`);
1843
- }
1844
- agent = {
1845
- sessionId,
1846
- ...parentSessionId === void 0 ? {} : { parentSessionId },
1847
- role: options.role,
1848
- boundAt
1849
- };
1850
- const agentSessions = [...(result.agentSessions ?? []).filter((linked) => linked.sessionId !== sessionId), agent];
1851
- return {
1852
- ...current,
1853
- goals: {
1854
- ...current.goals,
1855
- [goalId]: {
1856
- ...result,
1857
- agentSessions
1858
- }
1859
- },
1860
- updatedAt: boundAt
1861
- };
1862
- });
1863
- if (agent === void 0) throw new Error(`run ${runId} Agent binding did not commit`);
1864
- return {
1865
- run,
1866
- agent
1867
- };
1868
- }
1869
- parse(value) {
1870
- return parseGraph(value, {
1871
- maxGraphNodes: this.config.maxGraphNodes,
1872
- maxExpressionNodes: this.config.maxExpressionNodes,
1873
- maxExpressionDepth: this.config.maxExpressionDepth
1874
- });
1875
- }
1876
- validateWorkspaceRefs(graph) {
1877
- for (const [goalId, node] of Object.entries(graph.nodes)) {
1878
- if (node.verifier === void 0) {
1879
- if (node.kind === "leaf") throw new Error(`goal ${goalId} is a leaf but declares no verifier`);
1880
- continue;
1881
- }
1882
- requireTarget(node.target, goalId);
1883
- if (node.verifier.mode === "programmatic" && node.verifier.script.length === 0) throw new Error(`goal ${goalId} programmatic verifier requires a script`);
1884
- if (node.verifier.mode === "agentic" && node.verifier.instruction.length === 0) throw new Error(`goal ${goalId} agentic verifier requires an instruction`);
1885
- }
1886
- }
1887
- };
1888
- /** SPEC §4 programmatic-subtree rule: every composite must have at least one non-programmatic descendant. */
1889
- function validateHostConfig(config) {
1890
- if (config.maxGraphNodes < 1 || !Number.isInteger(config.maxGraphNodes)) throw new Error("maxGraphNodes must be a positive integer");
1891
- if (config.maxExpressionNodes < 1 || !Number.isInteger(config.maxExpressionNodes)) throw new Error("maxExpressionNodes must be a positive integer");
1892
- if (config.maxExpressionDepth < 1 || !Number.isInteger(config.maxExpressionDepth)) throw new Error("maxExpressionDepth must be a positive integer");
1893
- if (config.maxSandboxBytes < 1 || !Number.isInteger(config.maxSandboxBytes)) throw new Error("maxSandboxBytes must be a positive integer");
1894
- if (config.maxConcurrentVerifications < 1 || !Number.isInteger(config.maxConcurrentVerifications)) throw new Error("maxConcurrentVerifications must be a positive integer");
1895
- if (config.revalidateThreshold < 0 || config.revalidateThreshold > 1 || !Number.isFinite(config.revalidateThreshold)) throw new Error("revalidateThreshold must be a ratio in [0, 1]");
1896
- if (config.gmDigestAlgo.length === 0) throw new Error("gmDigestAlgo must not be empty");
1897
- if (config.storageDirectory.length === 0 || isAbsolute(config.storageDirectory) || hasTraversal(config.storageDirectory)) throw new Error("storageDirectory must be a non-empty relative path without traversal");
1898
- if (!isAbsolute(config.workspaceRoot)) throw new Error(`workspaceRoot must be absolute`);
1899
- }
1900
- function requireContextId(value, label) {
1901
- if (value.length === 0 || value.length > 512) throw new Error(`${label} must contain 1-512 characters`);
1902
- return value;
1903
- }
1904
- function requireTarget(target, goalId) {
1905
- if (typeof target !== "string" || target.length === 0) throw new Error(`goal ${goalId} must declare a target`);
1906
- if (isAbsolute(target)) throw new Error(`goal ${goalId} target must be workspace-relative (absolute given)`);
1907
- return target;
1908
- }
1909
- function computeJudgment(verifier, scriptsDirectory) {
1910
- if (verifier.mode === "programmatic") {
1911
- const scriptDigest = readScriptDigest(resolveScriptPath(scriptsDirectory, verifier.script));
1912
- return {
1913
- mode: "programmatic",
1914
- script: verifier.script,
1915
- scriptDigest
1916
- };
1917
- }
1918
- return {
1919
- mode: "agentic",
1920
- instructionHash: `sha256:${sha256Json({ instruction: verifier.instruction })}`
1921
- };
1922
- }
1923
- /** Inheritance anchor: the captured object plus the judgment identity. */
1924
- function anchorOfPlan(plan) {
1925
- if (plan.input === void 0 || !plan.input.exists) return void 0;
1926
- return sha256Json({
1927
- object: plan.input.digest,
1928
- judgment: plan.judgment
1929
- });
1930
- }
1931
- function resolveScriptPath(scriptsDirectory, script) {
1932
- const direct = join(scriptsDirectory, script);
1933
- if (existsSync(direct)) return direct;
1934
- if (!script.endsWith(".js")) {
1935
- const withJs = `${direct}.js`;
1936
- if (existsSync(withJs)) return withJs;
1937
- }
1938
- throw new Error(`programmatic script not found in library: ${direct}`);
1939
- }
1940
- function readScriptDigest(scriptPath) {
1941
- try {
1942
- return `sha256:${createHash("sha256").update(readFileSync(scriptPath)).digest("hex")}`;
1943
- } catch {
1944
- throw new Error(`programmatic script not found in library: ${scriptPath}`);
1945
- }
1946
- }
1947
- async function prepareInputPath(plan, workspace, repository) {
1948
- if (plan.input === void 0) return workspace.path;
1949
- return repository.copyCapturedInput(plan.input, workspace.path);
1950
- }
1951
- function graphWarnings(graph) {
1952
- const warnings = [];
1953
- for (const [goalId, node] of Object.entries(graph.nodes)) {
1954
- if (goalId === graph.root) continue;
1955
- if (node.constraint === "soft") warnings.push(`goal ${goalId} is soft; v0.2 records this but has no soft-score model`);
1956
- }
1957
- return warnings;
1958
- }
1959
- function messageOf(error) {
1960
- return error instanceof Error ? error.message : String(error);
1961
- }
1962
- function boundedMessage(message) {
1963
- return message.length > 512 ? message.slice(0, 512) : message;
1964
- }
1965
- function postorder(input) {
1966
- const children = /* @__PURE__ */ new Map();
1967
- for (const edge of input.contains) {
1968
- const list = children.get(edge.parent) ?? [];
1969
- list.push(edge.child);
1970
- children.set(edge.parent, list);
1971
- }
1972
- const visited = /* @__PURE__ */ new Set();
1973
- const order = [];
1974
- const visit = (id) => {
1975
- if (visited.has(id)) return;
1976
- visited.add(id);
1977
- for (const child of children.get(id) ?? []) visit(child);
1978
- order.push(id);
1979
- };
1980
- visit(input.root);
1981
- return order;
1982
- }
1983
- function dependenciesBySource(input) {
1984
- const map = /* @__PURE__ */ new Map();
1985
- for (const edge of input.dependsOn) {
1986
- const list = map.get(edge.source) ?? [];
1987
- list.push(edge.target);
1988
- map.set(edge.source, list);
1989
- }
1990
- return map;
1991
- }
1992
- function childrenByParent(input) {
1993
- const map = /* @__PURE__ */ new Map();
1994
- for (const edge of input.contains) {
1995
- const list = map.get(edge.parent) ?? [];
1996
- list.push(edge.child);
1997
- map.set(edge.parent, list);
1998
- }
1999
- return map;
2000
- }
2001
- function relationsByParent(input) {
2002
- const map = /* @__PURE__ */ new Map();
2003
- for (const edge of input.contains) {
2004
- const list = map.get(edge.parent) ?? [];
2005
- list.push(edge);
2006
- map.set(edge.parent, list);
2007
- }
2008
- return map;
2009
- }
2010
- function relationTruth(edge, goals) {
2011
- const state = goals[edge.child]?.state;
2012
- const inheritedVerification = state === "inherited" ? goals[edge.child]?.verification?.passed : void 0;
2013
- if (state === "success" || state === "inherited" && inheritedVerification === true || state === "partial") return true;
2014
- if (state === "failure" || state === "blocked" || state === "inherited" && inheritedVerification === false) {
2015
- if (edge.failure === "degrade" && edge.degradeTo !== void 0) {
2016
- const substitute = goals[edge.degradeTo]?.state;
2017
- if (substitute === "success") return true;
2018
- if (substitute === "inherited") {
2019
- const substitutePassed = goals[edge.degradeTo]?.verification?.passed;
2020
- if (substitutePassed === true) return true;
2021
- if (substitutePassed === null || substitutePassed === void 0) return void 0;
2022
- }
2023
- if (substitute === "needs_human" || substitute === "invalidated") return void 0;
2024
- }
2025
- return false;
2026
- }
2027
- }
2028
- function relationTruthValues(edges, goals) {
2029
- const values = /* @__PURE__ */ new Map();
2030
- for (const edge of edges) values.set(edge.child, relationTruth(edge, goals));
2031
- return values;
2032
- }
2033
- function relationNeedsHuman(edge, goals) {
2034
- const childState = goals[edge.child]?.state;
2035
- if (childState === "needs_human" || childState === "invalidated") return true;
2036
- if (childState === "inherited" && goals[edge.child]?.verification?.passed === null) return true;
2037
- if (edge.failure === "degrade" && edge.degradeTo !== void 0) {
2038
- const substitute = goals[edge.degradeTo]?.state;
2039
- return substitute === "needs_human" || substitute === "invalidated" || substitute === "inherited" && goals[edge.degradeTo]?.verification?.passed === null;
2040
- }
2041
- return false;
2042
- }
2043
- function dependencyNeedsHuman(result) {
2044
- return result?.state === "needs_human" || result?.state === "invalidated" || result?.state === "inherited" && result.verification?.passed === null;
2045
- }
2046
- /**
2047
- * A dependency is satisfied for scheduling once its target has reached a
2048
- * terminal state — success, failure, needs_human, cancelled, invalidated,
2049
- * partial, or an inherited verdict. This is a *completion* gate, not a
2050
- * success gate: the waiter is released to run its own judgment even when the
2051
- * dependency failed (its verdict is independent evidence).
2052
- */
2053
- function dependencyComplete(result) {
2054
- const state = result?.state;
2055
- if (state === "success" || state === "failure" || state === "needs_human" || state === "cancelled" || state === "invalidated" || state === "partial" || state === "inherited") return true;
2056
- return false;
2057
- }
2058
- function terminalState(rootResult, allowPartialRoot) {
2059
- if (rootResult?.state === "success" || rootResult?.state === "inherited") return "success";
2060
- if (rootResult?.state === "partial" && allowPartialRoot) return "partial_success";
2061
- if (rootResult?.state === "needs_human") return "needs_replan";
2062
- return "failure";
2063
- }
2064
- function deepFreeze(value) {
2065
- if (value === null || typeof value !== "object") return value;
2066
- Object.freeze(value);
2067
- for (const item of Object.values(value)) deepFreeze(item);
2068
- return value;
2069
- }
2070
- function hasTraversal(value) {
2071
- return value.split(/[\\/]/u).some((segment) => segment === ".." || segment === ".");
2072
- }
2073
- //#endregion
2074
- export { DogRepository as a, parseGraph as c, parseBoolExpr as d, runPlan as i, isJsonValue as l, resolveScriptPath as n, loadSchemaSet as o, WorkspaceManager as r, DogValidationError as s, DogEngine as t, evaluateBoolExpr as u };