@aigne/ash 0.0.1

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 (146) hide show
  1. package/DESIGN.md +41 -0
  2. package/dist/ai-dev-loop/ash-run-result.cjs +12 -0
  3. package/dist/ai-dev-loop/ash-run-result.d.cts +28 -0
  4. package/dist/ai-dev-loop/ash-run-result.d.cts.map +1 -0
  5. package/dist/ai-dev-loop/ash-run-result.d.mts +28 -0
  6. package/dist/ai-dev-loop/ash-run-result.d.mts.map +1 -0
  7. package/dist/ai-dev-loop/ash-run-result.mjs +11 -0
  8. package/dist/ai-dev-loop/ash-run-result.mjs.map +1 -0
  9. package/dist/ai-dev-loop/ash-typed-error.cjs +51 -0
  10. package/dist/ai-dev-loop/ash-typed-error.d.cts +54 -0
  11. package/dist/ai-dev-loop/ash-typed-error.d.cts.map +1 -0
  12. package/dist/ai-dev-loop/ash-typed-error.d.mts +54 -0
  13. package/dist/ai-dev-loop/ash-typed-error.d.mts.map +1 -0
  14. package/dist/ai-dev-loop/ash-typed-error.mjs +50 -0
  15. package/dist/ai-dev-loop/ash-typed-error.mjs.map +1 -0
  16. package/dist/ai-dev-loop/ash-validate.cjs +27 -0
  17. package/dist/ai-dev-loop/ash-validate.d.cts +7 -0
  18. package/dist/ai-dev-loop/ash-validate.d.cts.map +1 -0
  19. package/dist/ai-dev-loop/ash-validate.d.mts +7 -0
  20. package/dist/ai-dev-loop/ash-validate.d.mts.map +1 -0
  21. package/dist/ai-dev-loop/ash-validate.mjs +28 -0
  22. package/dist/ai-dev-loop/ash-validate.mjs.map +1 -0
  23. package/dist/ai-dev-loop/dev-loop.cjs +134 -0
  24. package/dist/ai-dev-loop/dev-loop.d.cts +28 -0
  25. package/dist/ai-dev-loop/dev-loop.d.cts.map +1 -0
  26. package/dist/ai-dev-loop/dev-loop.d.mts +28 -0
  27. package/dist/ai-dev-loop/dev-loop.d.mts.map +1 -0
  28. package/dist/ai-dev-loop/dev-loop.mjs +135 -0
  29. package/dist/ai-dev-loop/dev-loop.mjs.map +1 -0
  30. package/dist/ai-dev-loop/index.cjs +24 -0
  31. package/dist/ai-dev-loop/index.d.cts +9 -0
  32. package/dist/ai-dev-loop/index.d.mts +9 -0
  33. package/dist/ai-dev-loop/index.mjs +10 -0
  34. package/dist/ai-dev-loop/live-mode.cjs +17 -0
  35. package/dist/ai-dev-loop/live-mode.d.cts +24 -0
  36. package/dist/ai-dev-loop/live-mode.d.cts.map +1 -0
  37. package/dist/ai-dev-loop/live-mode.d.mts +24 -0
  38. package/dist/ai-dev-loop/live-mode.d.mts.map +1 -0
  39. package/dist/ai-dev-loop/live-mode.mjs +17 -0
  40. package/dist/ai-dev-loop/live-mode.mjs.map +1 -0
  41. package/dist/ai-dev-loop/meta-tools.cjs +123 -0
  42. package/dist/ai-dev-loop/meta-tools.d.cts +24 -0
  43. package/dist/ai-dev-loop/meta-tools.d.cts.map +1 -0
  44. package/dist/ai-dev-loop/meta-tools.d.mts +24 -0
  45. package/dist/ai-dev-loop/meta-tools.d.mts.map +1 -0
  46. package/dist/ai-dev-loop/meta-tools.mjs +120 -0
  47. package/dist/ai-dev-loop/meta-tools.mjs.map +1 -0
  48. package/dist/ai-dev-loop/structured-runner.cjs +154 -0
  49. package/dist/ai-dev-loop/structured-runner.d.cts +12 -0
  50. package/dist/ai-dev-loop/structured-runner.d.cts.map +1 -0
  51. package/dist/ai-dev-loop/structured-runner.d.mts +12 -0
  52. package/dist/ai-dev-loop/structured-runner.d.mts.map +1 -0
  53. package/dist/ai-dev-loop/structured-runner.mjs +155 -0
  54. package/dist/ai-dev-loop/structured-runner.mjs.map +1 -0
  55. package/dist/ai-dev-loop/system-prompt.cjs +55 -0
  56. package/dist/ai-dev-loop/system-prompt.d.cts +20 -0
  57. package/dist/ai-dev-loop/system-prompt.d.cts.map +1 -0
  58. package/dist/ai-dev-loop/system-prompt.d.mts +20 -0
  59. package/dist/ai-dev-loop/system-prompt.d.mts.map +1 -0
  60. package/dist/ai-dev-loop/system-prompt.mjs +54 -0
  61. package/dist/ai-dev-loop/system-prompt.mjs.map +1 -0
  62. package/dist/ast.d.cts +140 -0
  63. package/dist/ast.d.cts.map +1 -0
  64. package/dist/ast.d.mts +140 -0
  65. package/dist/ast.d.mts.map +1 -0
  66. package/dist/compiler.cjs +802 -0
  67. package/dist/compiler.d.cts +103 -0
  68. package/dist/compiler.d.cts.map +1 -0
  69. package/dist/compiler.d.mts +103 -0
  70. package/dist/compiler.d.mts.map +1 -0
  71. package/dist/compiler.mjs +802 -0
  72. package/dist/compiler.mjs.map +1 -0
  73. package/dist/index.cjs +14 -0
  74. package/dist/index.d.cts +7 -0
  75. package/dist/index.d.mts +7 -0
  76. package/dist/index.mjs +7 -0
  77. package/dist/lexer.cjs +451 -0
  78. package/dist/lexer.d.cts +14 -0
  79. package/dist/lexer.d.cts.map +1 -0
  80. package/dist/lexer.d.mts +14 -0
  81. package/dist/lexer.d.mts.map +1 -0
  82. package/dist/lexer.mjs +451 -0
  83. package/dist/lexer.mjs.map +1 -0
  84. package/dist/parser.cjs +734 -0
  85. package/dist/parser.d.cts +40 -0
  86. package/dist/parser.d.cts.map +1 -0
  87. package/dist/parser.d.mts +40 -0
  88. package/dist/parser.d.mts.map +1 -0
  89. package/dist/parser.mjs +734 -0
  90. package/dist/parser.mjs.map +1 -0
  91. package/dist/reference.cjs +130 -0
  92. package/dist/reference.d.cts +11 -0
  93. package/dist/reference.d.cts.map +1 -0
  94. package/dist/reference.d.mts +11 -0
  95. package/dist/reference.d.mts.map +1 -0
  96. package/dist/reference.mjs +130 -0
  97. package/dist/reference.mjs.map +1 -0
  98. package/dist/template.cjs +85 -0
  99. package/dist/template.mjs +84 -0
  100. package/dist/template.mjs.map +1 -0
  101. package/dist/type-checker.cjs +582 -0
  102. package/dist/type-checker.d.cts +31 -0
  103. package/dist/type-checker.d.cts.map +1 -0
  104. package/dist/type-checker.d.mts +31 -0
  105. package/dist/type-checker.d.mts.map +1 -0
  106. package/dist/type-checker.mjs +573 -0
  107. package/dist/type-checker.mjs.map +1 -0
  108. package/package.json +29 -0
  109. package/src/ai-dev-loop/ash-run-result.test.ts +113 -0
  110. package/src/ai-dev-loop/ash-run-result.ts +46 -0
  111. package/src/ai-dev-loop/ash-typed-error.test.ts +136 -0
  112. package/src/ai-dev-loop/ash-typed-error.ts +50 -0
  113. package/src/ai-dev-loop/ash-validate.test.ts +54 -0
  114. package/src/ai-dev-loop/ash-validate.ts +34 -0
  115. package/src/ai-dev-loop/dev-loop.test.ts +364 -0
  116. package/src/ai-dev-loop/dev-loop.ts +156 -0
  117. package/src/ai-dev-loop/dry-run.test.ts +107 -0
  118. package/src/ai-dev-loop/e2e-multi-fix.test.ts +473 -0
  119. package/src/ai-dev-loop/e2e.test.ts +324 -0
  120. package/src/ai-dev-loop/index.ts +15 -0
  121. package/src/ai-dev-loop/invariants.test.ts +253 -0
  122. package/src/ai-dev-loop/live-mode.test.ts +63 -0
  123. package/src/ai-dev-loop/live-mode.ts +33 -0
  124. package/src/ai-dev-loop/meta-tools.test.ts +120 -0
  125. package/src/ai-dev-loop/meta-tools.ts +142 -0
  126. package/src/ai-dev-loop/structured-runner.test.ts +159 -0
  127. package/src/ai-dev-loop/structured-runner.ts +209 -0
  128. package/src/ai-dev-loop/system-prompt.test.ts +102 -0
  129. package/src/ai-dev-loop/system-prompt.ts +81 -0
  130. package/src/ast.ts +186 -0
  131. package/src/compiler.test.ts +2933 -0
  132. package/src/compiler.ts +1103 -0
  133. package/src/e2e.test.ts +552 -0
  134. package/src/index.ts +16 -0
  135. package/src/lexer.test.ts +538 -0
  136. package/src/lexer.ts +222 -0
  137. package/src/parser.test.ts +1024 -0
  138. package/src/parser.ts +835 -0
  139. package/src/reference.test.ts +166 -0
  140. package/src/reference.ts +125 -0
  141. package/src/template.test.ts +210 -0
  142. package/src/template.ts +139 -0
  143. package/src/type-checker.test.ts +1494 -0
  144. package/src/type-checker.ts +785 -0
  145. package/tsconfig.json +9 -0
  146. package/tsdown.config.ts +12 -0
@@ -0,0 +1,573 @@
1
+ //#region src/type-checker.ts
2
+ const BUILTIN_MANIFESTS = {
3
+ find: {
4
+ name: "find",
5
+ stdin: "none",
6
+ stdout: "object_stream"
7
+ },
8
+ where: {
9
+ name: "where",
10
+ stdin: "object_stream",
11
+ stdout: "object_stream"
12
+ },
13
+ map: {
14
+ name: "map",
15
+ stdin: "object_stream",
16
+ stdout: "object_stream"
17
+ },
18
+ save: {
19
+ name: "save",
20
+ stdin: "object_stream",
21
+ stdout: "none"
22
+ },
23
+ publish: {
24
+ name: "publish",
25
+ stdin: "object_stream",
26
+ stdout: "none"
27
+ },
28
+ tee: {
29
+ name: "tee",
30
+ stdin: "object_stream",
31
+ stdout: "object_stream"
32
+ },
33
+ fanout: {
34
+ name: "fanout",
35
+ stdin: "object_stream",
36
+ stdout: "object_stream"
37
+ },
38
+ output: {
39
+ name: "output",
40
+ stdin: "object_stream",
41
+ stdout: "object_stream"
42
+ },
43
+ input: {
44
+ name: "input",
45
+ stdin: "none",
46
+ stdout: "object_stream"
47
+ },
48
+ count: {
49
+ name: "count",
50
+ stdin: "object_stream",
51
+ stdout: "object_stream"
52
+ },
53
+ "group-by": {
54
+ name: "group-by",
55
+ stdin: "object_stream",
56
+ stdout: "object_stream"
57
+ },
58
+ action: {
59
+ name: "action",
60
+ stdin: "object_stream",
61
+ stdout: "object_stream"
62
+ },
63
+ route: {
64
+ name: "route",
65
+ stdin: "object_stream",
66
+ stdout: "none"
67
+ },
68
+ lookup: {
69
+ name: "lookup",
70
+ stdin: "object_stream",
71
+ stdout: "object_stream"
72
+ }
73
+ };
74
+ function checkPipelineTypes(stages) {
75
+ const errors = [];
76
+ if (stages.length <= 1) return errors;
77
+ for (let i = 1; i < stages.length; i++) {
78
+ const prev = stages[i - 1];
79
+ const curr = stages[i];
80
+ const prevManifest = BUILTIN_MANIFESTS[prev.kind];
81
+ const currManifest = BUILTIN_MANIFESTS[curr.kind];
82
+ if (!prevManifest || !currManifest) {
83
+ errors.push({
84
+ message: `Unknown command at stage ${i}`,
85
+ stage: i,
86
+ kind: "unknown_command"
87
+ });
88
+ continue;
89
+ }
90
+ if (prevManifest.stdout === "none" && currManifest.stdin === "object_stream") errors.push({
91
+ message: `Type mismatch at stage ${i}: ${prev.kind} outputs 'none' but ${curr.kind} expects 'object_stream'`,
92
+ stage: i,
93
+ expected: currManifest.stdin,
94
+ actual: prevManifest.stdout
95
+ });
96
+ }
97
+ return errors;
98
+ }
99
+ function checkProhibitedPatterns(program) {
100
+ const errors = [];
101
+ const jobNames = new Set(program.jobs.map((j) => j.name));
102
+ const seenJobs = /* @__PURE__ */ new Set();
103
+ for (const job of program.jobs) {
104
+ if (seenJobs.has(job.name)) errors.push({
105
+ message: `Duplicate job name '${job.name}' — each job must have a unique name`,
106
+ kind: "prohibited"
107
+ });
108
+ if (job.name.length > 64) errors.push({
109
+ message: `Job name '${job.name.slice(0, 20)}...' exceeds 64 characters — keep job names concise`,
110
+ kind: "prohibited",
111
+ severity: "warning"
112
+ });
113
+ seenJobs.add(job.name);
114
+ }
115
+ if (program.jobs.length > 50) errors.push({
116
+ message: `Program has ${program.jobs.length} jobs — programs with over 50 jobs may indicate over-complexity`,
117
+ kind: "prohibited",
118
+ severity: "warning"
119
+ });
120
+ for (const job of program.jobs) {
121
+ if (job.pipeline.length === 0) errors.push({
122
+ message: `Job '${job.name}' has empty pipeline — did you forget to add stages?`,
123
+ kind: "prohibited",
124
+ severity: "warning"
125
+ });
126
+ if (job.pipeline.length > 30) errors.push({
127
+ message: `Job '${job.name}' has ${job.pipeline.length} stages — pipelines over 30 stages may indicate over-complexity`,
128
+ kind: "prohibited",
129
+ severity: "warning"
130
+ });
131
+ for (const stage of job.pipeline) {
132
+ if (!Object.hasOwn(BUILTIN_MANIFESTS, stage.kind)) errors.push({
133
+ message: `Unknown command '${stage.kind}' — not registered in manifest`,
134
+ kind: "unknown_command"
135
+ });
136
+ if ("path" in stage && typeof stage.path === "string") {
137
+ const path = stage.path;
138
+ if (path.includes("..")) errors.push({
139
+ message: `Path '${path}' contains '..' — path traversal is not allowed`,
140
+ kind: "prohibited"
141
+ });
142
+ if (path.replace(/\$\{[a-zA-Z_][a-zA-Z0-9_.]*\}/g, "").includes("$")) errors.push({
143
+ message: `Path '${path}' contains '$' — variable interpolation in paths is not allowed`,
144
+ kind: "prohibited",
145
+ severity: "warning"
146
+ });
147
+ }
148
+ checkExpressionDivZero(stage, errors);
149
+ if (stage.kind === "route") {
150
+ for (const branch of stage.branches) if (!jobNames.has(branch.targetJob)) errors.push({
151
+ message: `Route target job '${branch.targetJob}' not found in program`,
152
+ kind: "prohibited"
153
+ });
154
+ if (stage.fallback && !jobNames.has(stage.fallback)) errors.push({
155
+ message: `Route fallback job '${stage.fallback}' not found in program`,
156
+ kind: "prohibited"
157
+ });
158
+ if (!stage.fallback) errors.push({
159
+ message: `Job '${job.name}': route has no default branch — unmatched items are silently dropped, add '_ -> fallback_job' to handle unexpected values`,
160
+ kind: "prohibited",
161
+ severity: "warning"
162
+ });
163
+ }
164
+ }
165
+ }
166
+ for (const job of program.jobs) {
167
+ checkFanoutDepth(job.pipeline, 0, errors);
168
+ checkFanoutDuplicateWrites(job, errors);
169
+ }
170
+ for (const job of program.jobs) {
171
+ const teeCount = job.pipeline.filter((s) => s.kind === "tee").length;
172
+ if (teeCount > 5) errors.push({
173
+ message: `Job '${job.name}' has ${teeCount} tee stages — more than 5 tee stages risks data amplification`,
174
+ kind: "prohibited",
175
+ severity: "warning"
176
+ });
177
+ }
178
+ checkRouteDag(program, errors);
179
+ for (const job of program.jobs) if (job.trigger) {
180
+ if (job.trigger.kind === "event") {
181
+ if (!job.trigger.path || !job.trigger.event) errors.push({
182
+ message: `Job '${job.name}' has incomplete trigger declaration`,
183
+ kind: "prohibited"
184
+ });
185
+ } else if (job.trigger.kind === "cron") if (!job.trigger.expression) errors.push({
186
+ message: `Job '${job.name}' has empty cron expression`,
187
+ kind: "prohibited"
188
+ });
189
+ else {
190
+ const fields = job.trigger.expression.trim().split(/\s+/);
191
+ if (fields.length < 5 || fields.length > 7) errors.push({
192
+ message: `Job '${job.name}' has invalid cron expression "${job.trigger.expression}" — expected 5-7 fields, got ${fields.length}`,
193
+ kind: "prohibited"
194
+ });
195
+ }
196
+ }
197
+ for (const job of program.jobs) if (job.trigger?.kind === "event") {
198
+ const triggerPath = job.trigger.path;
199
+ for (const stage of job.pipeline) if ((stage.kind === "save" || stage.kind === "tee") && stage.path === triggerPath) errors.push({
200
+ message: `Job '${job.name}' writes to its own trigger path '${triggerPath}' — this creates an event loop`,
201
+ kind: "prohibited"
202
+ });
203
+ }
204
+ const SELF_EXEC_BLOCKED = new Set(["/.actions/run", "/.actions/exec"]);
205
+ for (const job of program.jobs) for (const stage of job.pipeline) if (stage.kind === "action" && SELF_EXEC_BLOCKED.has(stage.path)) errors.push({
206
+ message: `Job '${job.name}' calls action '${stage.path}' — self-referencing ASH execution risks recursion and bypasses compile-time checks`,
207
+ kind: "prohibited"
208
+ });
209
+ const PROTO_POLLUTION_FIELDS = new Set([
210
+ "constructor",
211
+ "__proto__",
212
+ "prototype",
213
+ "__defineGetter__",
214
+ "__defineSetter__"
215
+ ]);
216
+ const JS_RESERVED_FIELDS = new Set([
217
+ ...PROTO_POLLUTION_FIELDS,
218
+ "toString",
219
+ "valueOf",
220
+ "hasOwnProperty",
221
+ "class",
222
+ "return",
223
+ "delete",
224
+ "void",
225
+ "typeof",
226
+ "new"
227
+ ]);
228
+ for (const job of program.jobs) for (const stage of job.pipeline) {
229
+ const protoFields = [];
230
+ const jsFields = [];
231
+ if (stage.kind === "map") {
232
+ if (stage.field) classifyField(stage.field, PROTO_POLLUTION_FIELDS, JS_RESERVED_FIELDS, protoFields, jsFields);
233
+ if (stage.expression) collectJsReservedFields(stage.expression, JS_RESERVED_FIELDS, jsFields);
234
+ if (stage.exprMappings) {
235
+ for (const key of Object.keys(stage.exprMappings)) classifyField(key, PROTO_POLLUTION_FIELDS, JS_RESERVED_FIELDS, protoFields, jsFields);
236
+ for (const expr of Object.values(stage.exprMappings)) collectJsReservedFields(expr, JS_RESERVED_FIELDS, jsFields);
237
+ }
238
+ if (stage.mappings) for (const key of Object.keys(stage.mappings)) classifyField(key, PROTO_POLLUTION_FIELDS, JS_RESERVED_FIELDS, protoFields, jsFields);
239
+ if (stage.exprMappings) checkMapDuplicateKeys(stage.exprMappings, job.name, errors);
240
+ if (stage.mappings) checkMapDuplicateKeys(stage.mappings, job.name, errors);
241
+ }
242
+ if (stage.kind === "where" && JS_RESERVED_FIELDS.has(stage.left)) classifyField(stage.left, PROTO_POLLUTION_FIELDS, JS_RESERVED_FIELDS, protoFields, jsFields);
243
+ for (const field of protoFields) errors.push({
244
+ message: `Job '${job.name}': field '${field}' is a prototype pollution vector — not allowed as map key or field name`,
245
+ kind: "prohibited"
246
+ });
247
+ for (const field of jsFields) errors.push({
248
+ message: `Job '${job.name}': field '${field}' is a JavaScript reserved identifier — may expose runtime internals`,
249
+ kind: "prohibited",
250
+ severity: "warning"
251
+ });
252
+ }
253
+ for (const job of program.jobs) for (const stage of job.pipeline) if (stage.kind === "map") {
254
+ if (stage.expression) checkDivisionRisk(stage.expression, job.name, errors);
255
+ if (stage.exprMappings) for (const expr of Object.values(stage.exprMappings)) checkDivisionRisk(expr, job.name, errors);
256
+ }
257
+ return errors;
258
+ }
259
+ function checkDivisionRisk(expr, jobName, errors) {
260
+ if (expr.kind === "binary" && expr.op === "/") {
261
+ if (expr.right.kind === "field_access") errors.push({
262
+ message: `Job '${jobName}': division by field '${expr.right.path}' may produce NaN/Infinity if zero — consider adding a where clause to filter`,
263
+ kind: "prohibited",
264
+ severity: "warning"
265
+ });
266
+ }
267
+ if (expr.kind === "binary") {
268
+ checkDivisionRisk(expr.left, jobName, errors);
269
+ checkDivisionRisk(expr.right, jobName, errors);
270
+ }
271
+ }
272
+ function collectJsReservedFields(expr, reserved, out) {
273
+ if (expr.kind === "field_access" && reserved.has(expr.path)) out.push(expr.path);
274
+ if (expr.kind === "binary") {
275
+ collectJsReservedFields(expr.left, reserved, out);
276
+ collectJsReservedFields(expr.right, reserved, out);
277
+ }
278
+ }
279
+ function classifyField(field, proto, jsReserved, protoOut, jsOut) {
280
+ if (proto.has(field)) protoOut.push(field);
281
+ else if (jsReserved.has(field)) jsOut.push(field);
282
+ }
283
+ function checkMapDuplicateKeys(obj, jobName, errors) {
284
+ const seen = /* @__PURE__ */ new Set();
285
+ for (const key of Object.keys(obj)) {
286
+ if (seen.has(key)) errors.push({
287
+ message: `Job '${jobName}': map has duplicate key '${key}' — last value wins silently, potential data confusion`,
288
+ kind: "prohibited",
289
+ severity: "warning"
290
+ });
291
+ seen.add(key);
292
+ }
293
+ }
294
+ function checkFanoutDepth(stages, depth, errors) {
295
+ for (const stage of stages) if (stage.kind === "fanout") {
296
+ if (stage.branches.length > 10) errors.push({
297
+ message: `Fanout has ${stage.branches.length} branches — more than 10 branches may cause excessive parallelism`,
298
+ kind: "prohibited",
299
+ severity: "warning"
300
+ });
301
+ if (depth + 1 > 3) {
302
+ errors.push({
303
+ message: `Fanout nesting depth exceeds 3 levels — deeply nested fanout may indicate over-complexity`,
304
+ kind: "prohibited",
305
+ severity: "warning"
306
+ });
307
+ return;
308
+ }
309
+ for (const branch of stage.branches) checkFanoutDepth(branch, depth + 1, errors);
310
+ }
311
+ }
312
+ function checkFanoutDuplicateWrites(job, errors) {
313
+ for (const stage of job.pipeline) {
314
+ if (stage.kind !== "fanout") continue;
315
+ const writePaths = /* @__PURE__ */ new Map();
316
+ for (const branch of stage.branches) for (const s of branch) if ((s.kind === "save" || s.kind === "tee") && "path" in s) {
317
+ const path = s.path;
318
+ writePaths.set(path, (writePaths.get(path) ?? 0) + 1);
319
+ }
320
+ for (const [path, count] of writePaths) if (count > 1) errors.push({
321
+ message: `Job '${job.name}': fanout has ${count} branches writing to '${path}' — last write wins silently, audit trail lost`,
322
+ kind: "prohibited",
323
+ severity: "warning"
324
+ });
325
+ }
326
+ }
327
+ function checkExpressionDivZero(stage, errors) {
328
+ if (stage.kind !== "map") return;
329
+ if (stage.exprMappings) {
330
+ for (const expr of Object.values(stage.exprMappings)) if (hasLiteralDivByZero(expr)) errors.push({
331
+ message: "Expression contains literal division by zero",
332
+ kind: "prohibited",
333
+ severity: "warning"
334
+ });
335
+ }
336
+ if (stage.expression && hasLiteralDivByZero(stage.expression)) errors.push({
337
+ message: "Expression contains literal division by zero",
338
+ kind: "prohibited",
339
+ severity: "warning"
340
+ });
341
+ }
342
+ function hasLiteralDivByZero(expr) {
343
+ if (expr.kind === "binary" && expr.op === "/" && expr.right.kind === "literal" && expr.right.value === 0) return true;
344
+ if (expr.kind === "binary") return hasLiteralDivByZero(expr.left) || hasLiteralDivByZero(expr.right);
345
+ return false;
346
+ }
347
+ function checkRouteDag(program, errors) {
348
+ const graph = /* @__PURE__ */ new Map();
349
+ for (const job of program.jobs) {
350
+ const targets = /* @__PURE__ */ new Set();
351
+ for (const stage of job.pipeline) if (stage.kind === "route") {
352
+ for (const branch of stage.branches) targets.add(branch.targetJob);
353
+ if (stage.fallback) targets.add(stage.fallback);
354
+ }
355
+ if (targets.size > 0) graph.set(job.name, targets);
356
+ }
357
+ const visited = /* @__PURE__ */ new Set();
358
+ const inStack = /* @__PURE__ */ new Set();
359
+ function dfs(node) {
360
+ if (inStack.has(node)) return true;
361
+ if (visited.has(node)) return false;
362
+ visited.add(node);
363
+ inStack.add(node);
364
+ const neighbors = graph.get(node);
365
+ if (neighbors) {
366
+ for (const n of neighbors) if (dfs(n)) {
367
+ errors.push({
368
+ message: `Route cycle detected: job '${node}' → job '${n}' forms a cycle`,
369
+ kind: "prohibited"
370
+ });
371
+ return true;
372
+ }
373
+ }
374
+ inStack.delete(node);
375
+ return false;
376
+ }
377
+ for (const node of graph.keys()) dfs(node);
378
+ }
379
+ const ANNOTATION_VALIDATORS = {
380
+ approval: (args) => {
381
+ if (args.length === 0) return null;
382
+ if (!new Set(["human", "auto"]).has(args[0])) return `@approval accepts 'human' or 'auto', got '${args[0]}'`;
383
+ return null;
384
+ },
385
+ readonly: () => null,
386
+ retry: (args) => {
387
+ if (args.length === 0) return null;
388
+ const n = Number(args[0]);
389
+ if (isNaN(n) || !Number.isInteger(n) || n < 0) return `@retry requires a non-negative integer, got '${args[0]}'`;
390
+ return null;
391
+ },
392
+ timeout: (args) => {
393
+ if (args.length === 0) return null;
394
+ const n = Number(args[0]);
395
+ if (isNaN(n) || n <= 0) return `@timeout requires a positive number, got '${args[0]}'`;
396
+ if (n > 36e5) return `@timeout(${args[0]}) exceeds hard limit of 3600000ms (1 hour) — reduce timeout`;
397
+ return null;
398
+ },
399
+ on_error: (args) => {
400
+ if (args.length === 0) return `@on_error requires a strategy: skip, fail, or save <path>`;
401
+ if (!new Set([
402
+ "skip",
403
+ "fail",
404
+ "save"
405
+ ]).has(args[0])) return `@on_error accepts 'skip', 'fail', or 'save', got '${args[0]}'`;
406
+ return null;
407
+ },
408
+ budget: (args) => {
409
+ if (args.length === 0) return `@budget requires at least one limit: @budget(actions 50, records 10000)`;
410
+ if (args.length % 2 !== 0) return `@budget requires dimension/value pairs — got odd number of args`;
411
+ const validDims = new Set([
412
+ "actions",
413
+ "writes",
414
+ "records",
415
+ "tokens",
416
+ "cost"
417
+ ]);
418
+ for (let i = 0; i < args.length; i += 2) {
419
+ if (!validDims.has(args[i])) return `@budget dimension must be one of: ${[...validDims].join(", ")} — got '${args[i]}'`;
420
+ const n = Number(args[i + 1]);
421
+ if (isNaN(n) || n <= 0) return `@budget value must be a positive number, got '${args[i + 1]}'`;
422
+ }
423
+ return null;
424
+ },
425
+ caps: (args) => {
426
+ if (args.length === 0) return `@caps requires at least one capability: @caps(read /path/*, write /out/*)`;
427
+ if (args.length % 2 !== 0) return `@caps requires operation/path pairs — got odd number of args`;
428
+ const validOps = new Set([
429
+ "read",
430
+ "write",
431
+ "exec"
432
+ ]);
433
+ for (let i = 0; i < args.length; i += 2) {
434
+ if (!validOps.has(args[i])) return `@caps operation must be 'read', 'write', or 'exec', got '${args[i]}'`;
435
+ if (!args[i + 1].startsWith("/")) return `@caps path must start with '/', got '${args[i + 1]}'`;
436
+ }
437
+ return null;
438
+ }
439
+ };
440
+ const BUILTIN_COMMAND_NAMES = Object.keys(BUILTIN_MANIFESTS);
441
+ function typeErrorsToDiagnostics(errors) {
442
+ return errors.map((e) => ({
443
+ code: "ASH_TYPE_MISMATCH",
444
+ message: e.message,
445
+ stage: e.stage
446
+ }));
447
+ }
448
+ function compileErrorsToDiagnostics(errors) {
449
+ return errors.map((e) => ({
450
+ code: e.kind === "unknown_command" ? "ASH_UNKNOWN_COMMAND" : "ASH_ANNOTATION_INVALID",
451
+ severity: e.severity,
452
+ message: e.message,
453
+ suggestion: e.kind === "unknown_command" ? BUILTIN_COMMAND_NAMES : void 0
454
+ }));
455
+ }
456
+ function annotationErrorsToDiagnostics(errors) {
457
+ return errors.map((e) => ({
458
+ code: "ASH_ANNOTATION_INVALID",
459
+ severity: e.severity,
460
+ message: e.message
461
+ }));
462
+ }
463
+ function parseSyntaxError(errorMessage) {
464
+ const isUnterminated = /unterminated/i.test(errorMessage);
465
+ const isDuplicate = /duplicate variable/i.test(errorMessage);
466
+ const lineMatch = errorMessage.match(/line\s+(\d+)/i);
467
+ const colMatch = errorMessage.match(/column\s+(\d+)/i);
468
+ const cleanMsg = errorMessage.replace(/^Error:\s*/i, "");
469
+ return {
470
+ code: isDuplicate ? "ASH_DUPLICATE_VAR" : isUnterminated ? "ASH_SYNTAX_UNTERMINATED" : "ASH_SYNTAX_UNEXPECTED",
471
+ message: cleanMsg,
472
+ line: lineMatch ? Number(lineMatch[1]) : void 0,
473
+ column: colMatch ? Number(colMatch[1]) : void 0
474
+ };
475
+ }
476
+ function checkAnnotations(job) {
477
+ const errors = [];
478
+ for (const ann of job.annotations) {
479
+ const validator = Object.hasOwn(ANNOTATION_VALIDATORS, ann.name) ? ANNOTATION_VALIDATORS[ann.name] : void 0;
480
+ if (!validator) {
481
+ errors.push({
482
+ message: `Unknown annotation '@${ann.name}' — not in allowed set`,
483
+ kind: "prohibited"
484
+ });
485
+ continue;
486
+ }
487
+ const err = validator(ann.args);
488
+ if (err) errors.push({
489
+ message: err,
490
+ kind: "prohibited"
491
+ });
492
+ }
493
+ const annCounts = /* @__PURE__ */ new Map();
494
+ for (const ann of job.annotations) annCounts.set(ann.name, (annCounts.get(ann.name) ?? 0) + 1);
495
+ for (const [name, count] of annCounts) if (count > 1) errors.push({
496
+ message: `Job '${job.name}': @${name} appears ${count} times — last value wins silently, potential audit deception`,
497
+ kind: "prohibited"
498
+ });
499
+ const retryAnns = job.annotations.filter((a) => a.name === "retry");
500
+ for (const ann of retryAnns) if (ann.args.length > 0) {
501
+ const n = Number(ann.args[0]);
502
+ if (!isNaN(n) && n > 100) errors.push({
503
+ message: `@retry(${n}) exceeds hard limit of 100 — reduce retry count`,
504
+ kind: "prohibited"
505
+ });
506
+ }
507
+ const approvalAnns = job.annotations.filter((a) => a.name === "approval");
508
+ if (approvalAnns.length > 1) {
509
+ const values = approvalAnns.map((a) => a.args[0]).filter(Boolean);
510
+ if (values.includes("human") && values.includes("auto")) errors.push({
511
+ message: `@approval conflict: 'human' and 'auto' cannot both be set on job '${job.name}'`,
512
+ kind: "prohibited"
513
+ });
514
+ }
515
+ return errors;
516
+ }
517
+ function parseCaps(ann) {
518
+ const caps = [];
519
+ for (let i = 0; i < ann.args.length; i += 2) caps.push({
520
+ op: ann.args[i],
521
+ pathGlob: ann.args[i + 1]
522
+ });
523
+ return caps;
524
+ }
525
+ function capMatches(pathGlob, actual) {
526
+ if (pathGlob === actual) return true;
527
+ if (pathGlob.endsWith("/*")) {
528
+ const prefix = pathGlob.slice(0, -1);
529
+ return actual.startsWith(prefix) || actual === pathGlob.slice(0, -2);
530
+ }
531
+ if (pathGlob.endsWith("*")) return actual.startsWith(pathGlob.slice(0, -1));
532
+ return false;
533
+ }
534
+ function hasCapFor(caps, op, path) {
535
+ return caps.some((c) => c.op === op && capMatches(c.pathGlob, path));
536
+ }
537
+ const STAGE_OP_MAP = {
538
+ find: "read",
539
+ lookup: "read",
540
+ save: "write",
541
+ publish: "write",
542
+ tee: "write",
543
+ action: "exec"
544
+ };
545
+ function checkStagesCaps(stages, caps, diagnostics) {
546
+ for (const stage of stages) {
547
+ if (stage.kind === "action" && stage.relative) continue;
548
+ if ("path" in stage && typeof stage.path === "string" && stage.path.includes("${")) continue;
549
+ const op = Object.hasOwn(STAGE_OP_MAP, stage.kind) ? STAGE_OP_MAP[stage.kind] : void 0;
550
+ if (op && "path" in stage && typeof stage.path === "string") {
551
+ const path = stage.path;
552
+ if (!hasCapFor(caps, op, path)) diagnostics.push({
553
+ code: "ASH_CAP_DENIED",
554
+ message: `@caps denied: '${stage.kind} ${path}' requires '${op}' capability for '${path}'`
555
+ });
556
+ }
557
+ if (stage.kind === "fanout") for (const branch of stage.branches) checkStagesCaps(branch, caps, diagnostics);
558
+ }
559
+ }
560
+ function checkJobCaps(job) {
561
+ const capsAnns = job.annotations.filter((a) => a.name === "caps");
562
+ if (capsAnns.length === 0) return [];
563
+ const diagnostics = [];
564
+ const allCaps = [];
565
+ for (const ann of capsAnns) if (ann.args.length > 0 && ann.args.length % 2 === 0) allCaps.push(...parseCaps(ann));
566
+ if (allCaps.length === 0) return diagnostics;
567
+ checkStagesCaps(job.pipeline, allCaps, diagnostics);
568
+ return diagnostics;
569
+ }
570
+
571
+ //#endregion
572
+ export { annotationErrorsToDiagnostics, checkAnnotations, checkJobCaps, checkPipelineTypes, checkProhibitedPatterns, compileErrorsToDiagnostics, hasCapFor, parseCaps, parseSyntaxError, typeErrorsToDiagnostics };
573
+ //# sourceMappingURL=type-checker.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"type-checker.mjs","names":[],"sources":["../src/type-checker.ts"],"sourcesContent":["import type { Program, PipelineStage, JobDeclaration, Expression } from \"./ast.js\";\n\n// ── AshDiagnostic ──\n\nexport type DiagnosticCode =\n | \"ASH_SYNTAX_UNTERMINATED\"\n | \"ASH_SYNTAX_UNEXPECTED\"\n | \"ASH_UNKNOWN_COMMAND\"\n | \"ASH_TYPE_MISMATCH\"\n | \"ASH_UNDEFINED_VAR\"\n | \"ASH_DUPLICATE_VAR\"\n | \"ASH_DUPLICATE_JOB\"\n | \"ASH_ANNOTATION_INVALID\"\n | \"ASH_ANNOTATION_CONFLICT\"\n | \"ASH_READONLY_VIOLATION\"\n | \"ASH_CAP_DENIED\"\n | \"ASH_PATH_TRAVERSAL\"\n | \"ASH_EMPTY_JOB\"\n | \"ASH_DIV_ZERO\"\n | \"ASH_EXCESSIVE_RETRY\"\n | \"ASH_EVENT_LOOP\"\n | \"ASH_SELF_RECURSION\"\n | \"ASH_DIVISION_RISK\"\n | \"ASH_MIXED_SECURITY\"\n | \"ASH_JS_GLOBAL_FIELD\"\n | \"ASH_LET_WRITE\"\n | \"ASH_LET_PRE_APPROVAL\"\n | \"ASH_PARAM_WRITE_GATE\"\n | \"ASH_UNCAPPED_ACTION\"\n | \"ASH_ACTION_AMPLIFICATION\"\n | \"ASH_CROSS_PROVIDER_ACTION\"\n | \"ASH_RELATIVE_ACTION_NO_FIND\"\n | \"ASH_UNCAPPED_WRITE\"\n | \"ASH_BUDGET_EXCESSIVE\"\n;\n\nexport interface AshDiagnostic {\n code: DiagnosticCode;\n severity?: \"error\" | \"warning\";\n message: string;\n line?: number;\n column?: number;\n stage?: number;\n suggestion?: string[];\n}\n\nexport type StreamType = \"object_stream\" | \"single_object\" | \"none\";\n\nexport interface CommandManifest {\n name: string;\n stdin: StreamType;\n stdout: StreamType;\n}\n\nexport interface TypeError {\n message: string;\n stage: number;\n expected?: StreamType;\n actual?: StreamType;\n}\n\nexport interface CompileError {\n message: string;\n kind: \"prohibited\" | \"unknown_command\";\n severity?: \"error\" | \"warning\";\n}\n\nconst BUILTIN_MANIFESTS: Record<string, CommandManifest> = {\n find: { name: \"find\", stdin: \"none\", stdout: \"object_stream\" },\n where: { name: \"where\", stdin: \"object_stream\", stdout: \"object_stream\" },\n map: { name: \"map\", stdin: \"object_stream\", stdout: \"object_stream\" },\n save: { name: \"save\", stdin: \"object_stream\", stdout: \"none\" },\n publish: { name: \"publish\", stdin: \"object_stream\", stdout: \"none\" },\n tee: { name: \"tee\", stdin: \"object_stream\", stdout: \"object_stream\" },\n fanout: { name: \"fanout\", stdin: \"object_stream\", stdout: \"object_stream\" },\n output: { name: \"output\", stdin: \"object_stream\", stdout: \"object_stream\" },\n input: { name: \"input\", stdin: \"none\", stdout: \"object_stream\" },\n count: { name: \"count\", stdin: \"object_stream\", stdout: \"object_stream\" },\n \"group-by\": { name: \"group-by\", stdin: \"object_stream\", stdout: \"object_stream\" },\n action: { name: \"action\", stdin: \"object_stream\", stdout: \"object_stream\" },\n route: { name: \"route\", stdin: \"object_stream\", stdout: \"none\" },\n lookup: { name: \"lookup\", stdin: \"object_stream\", stdout: \"object_stream\" },\n};\n\nexport function checkPipelineTypes(stages: PipelineStage[]): TypeError[] {\n const errors: TypeError[] = [];\n if (stages.length <= 1) return errors;\n\n for (let i = 1; i < stages.length; i++) {\n const prev = stages[i - 1];\n const curr = stages[i];\n const prevManifest = BUILTIN_MANIFESTS[prev.kind];\n const currManifest = BUILTIN_MANIFESTS[curr.kind];\n\n if (!prevManifest || !currManifest) {\n errors.push({ message: `Unknown command at stage ${i}`, stage: i, kind: \"unknown_command\" } as any);\n continue;\n }\n\n if (prevManifest.stdout === \"none\" && currManifest.stdin === \"object_stream\") {\n errors.push({\n message: `Type mismatch at stage ${i}: ${prev.kind} outputs 'none' but ${curr.kind} expects 'object_stream'`,\n stage: i,\n expected: currManifest.stdin,\n actual: prevManifest.stdout,\n });\n }\n }\n\n return errors;\n}\n\nexport function checkProhibitedPatterns(program: Program): CompileError[] {\n const errors: CompileError[] = [];\n const jobNames = new Set(program.jobs.map(j => j.name));\n\n // 1. Duplicate job names + job name length\n const seenJobs = new Set<string>();\n for (const job of program.jobs) {\n if (seenJobs.has(job.name)) {\n errors.push({\n message: `Duplicate job name '${job.name}' — each job must have a unique name`,\n kind: \"prohibited\",\n });\n }\n if (job.name.length > 64) {\n errors.push({\n message: `Job name '${job.name.slice(0, 20)}...' exceeds 64 characters — keep job names concise`,\n kind: \"prohibited\",\n severity: \"warning\",\n });\n }\n seenJobs.add(job.name);\n }\n\n // Program job count limit\n if (program.jobs.length > 50) {\n errors.push({\n message: `Program has ${program.jobs.length} jobs — programs with over 50 jobs may indicate over-complexity`,\n kind: \"prohibited\",\n severity: \"warning\",\n });\n }\n\n for (const job of program.jobs) {\n // 5. Empty job → warning\n if (job.pipeline.length === 0) {\n errors.push({\n message: `Job '${job.name}' has empty pipeline — did you forget to add stages?`,\n kind: \"prohibited\",\n severity: \"warning\",\n });\n }\n\n // Pipeline depth limit\n if (job.pipeline.length > 30) {\n errors.push({\n message: `Job '${job.name}' has ${job.pipeline.length} stages — pipelines over 30 stages may indicate over-complexity`,\n kind: \"prohibited\",\n severity: \"warning\",\n });\n }\n\n for (const stage of job.pipeline) {\n // Check for prohibited patterns in stage names/kinds\n if (!Object.hasOwn(BUILTIN_MANIFESTS, stage.kind)) {\n errors.push({\n message: `Unknown command '${stage.kind}' — not registered in manifest`,\n kind: \"unknown_command\",\n });\n }\n // 4. Path traversal warning + $ in paths\n if (\"path\" in stage && typeof (stage as any).path === \"string\") {\n const path = (stage as any).path as string;\n if (path.includes(\"..\")) {\n errors.push({\n message: `Path '${path}' contains '..' — path traversal is not allowed`,\n kind: \"prohibited\",\n });\n }\n // Allow ${template.field} patterns in paths; block bare $ (e.g., $evil)\n const pathWithoutTemplates = path.replace(/\\$\\{[a-zA-Z_][a-zA-Z0-9_.]*\\}/g, \"\");\n if (pathWithoutTemplates.includes(\"$\")) {\n errors.push({\n message: `Path '${path}' contains '$' — variable interpolation in paths is not allowed`,\n kind: \"prohibited\",\n severity: \"warning\",\n });\n }\n }\n // 6. Division by zero in literal expressions\n checkExpressionDivZero(stage, errors);\n // Route: validate branch targets exist + require fallback\n if (stage.kind === \"route\") {\n for (const branch of stage.branches) {\n if (!jobNames.has(branch.targetJob)) {\n errors.push({\n message: `Route target job '${branch.targetJob}' not found in program`,\n kind: \"prohibited\",\n });\n }\n }\n if (stage.fallback && !jobNames.has(stage.fallback)) {\n errors.push({\n message: `Route fallback job '${stage.fallback}' not found in program`,\n kind: \"prohibited\",\n });\n }\n if (!stage.fallback) {\n errors.push({\n message: `Job '${job.name}': route has no default branch — unmatched items are silently dropped, add '_ -> fallback_job' to handle unexpected values`,\n kind: \"prohibited\",\n severity: \"warning\",\n });\n }\n }\n }\n }\n\n // Fanout nesting depth validation + duplicate write path detection\n for (const job of program.jobs) {\n checkFanoutDepth(job.pipeline, 0, errors);\n checkFanoutDuplicateWrites(job, errors);\n }\n\n // Tee chain limit: too many tee stages in one pipeline = data amplification risk\n for (const job of program.jobs) {\n const teeCount = job.pipeline.filter(s => s.kind === \"tee\").length;\n if (teeCount > 5) {\n errors.push({\n message: `Job '${job.name}' has ${teeCount} tee stages — more than 5 tee stages risks data amplification`,\n kind: \"prohibited\",\n severity: \"warning\",\n });\n }\n }\n\n // Route DAG validation: check for cycles\n checkRouteDag(program, errors);\n\n // Trigger validation: trigger declarations are metadata-only\n for (const job of program.jobs) {\n if (job.trigger) {\n if (job.trigger.kind === \"event\") {\n if (!job.trigger.path || !job.trigger.event) {\n errors.push({\n message: `Job '${job.name}' has incomplete trigger declaration`,\n kind: \"prohibited\",\n });\n }\n } else if (job.trigger.kind === \"cron\") {\n if (!job.trigger.expression) {\n errors.push({\n message: `Job '${job.name}' has empty cron expression`,\n kind: \"prohibited\",\n });\n } else {\n // Basic cron field count validation (5, 6, or 7 fields expected)\n const fields = job.trigger.expression.trim().split(/\\s+/);\n if (fields.length < 5 || fields.length > 7) {\n errors.push({\n message: `Job '${job.name}' has invalid cron expression \"${job.trigger.expression}\" — expected 5-7 fields, got ${fields.length}`,\n kind: \"prohibited\",\n });\n }\n }\n }\n }\n }\n\n // Event loop detection: on /path:event + save/tee to same path (event triggers only)\n for (const job of program.jobs) {\n if (job.trigger?.kind === \"event\") {\n const triggerPath = job.trigger.path;\n for (const stage of job.pipeline) {\n if ((stage.kind === \"save\" || stage.kind === \"tee\") && stage.path === triggerPath) {\n errors.push({\n message: `Job '${job.name}' writes to its own trigger path '${triggerPath}' — this creates an event loop`,\n kind: \"prohibited\",\n });\n }\n }\n }\n }\n\n // Self-referencing action: action /.actions/* → potential recursion\n // Uses provider-relative path (/.actions/) since the absolute mount point varies.\n // Dangerous: /.actions/run (arbitrary code), /.actions/exec (script exec).\n // Allowed: /.actions/agent-run (budget-constrained LLM loop, not arbitrary code).\n const SELF_EXEC_BLOCKED = new Set([\"/.actions/run\", \"/.actions/exec\"]);\n for (const job of program.jobs) {\n for (const stage of job.pipeline) {\n if (stage.kind === \"action\" && SELF_EXEC_BLOCKED.has(stage.path)) {\n errors.push({\n message: `Job '${job.name}' calls action '${stage.path}' — self-referencing ASH execution risks recursion and bypasses compile-time checks`,\n kind: \"prohibited\",\n });\n }\n }\n }\n\n // Proto-pollution fields — ERROR (these can modify object prototype chain)\n const PROTO_POLLUTION_FIELDS = new Set([\n \"constructor\", \"__proto__\", \"prototype\",\n \"__defineGetter__\", \"__defineSetter__\",\n ]);\n // JS reserved identifiers — WARNING (runtime risk if eval-like constructs used)\n const JS_RESERVED_FIELDS = new Set([\n ...PROTO_POLLUTION_FIELDS,\n \"toString\", \"valueOf\", \"hasOwnProperty\",\n \"class\", \"return\", \"delete\", \"void\", \"typeof\", \"new\",\n ]);\n for (const job of program.jobs) {\n for (const stage of job.pipeline) {\n const protoFields: string[] = []; // error-level\n const jsFields: string[] = []; // warning-level\n if (stage.kind === \"map\") {\n if (stage.field) classifyField(stage.field, PROTO_POLLUTION_FIELDS, JS_RESERVED_FIELDS, protoFields, jsFields);\n if (stage.expression) collectJsReservedFields(stage.expression, JS_RESERVED_FIELDS, jsFields);\n if (stage.exprMappings) {\n // Check both KEYS and VALUES of map expressions\n for (const key of Object.keys(stage.exprMappings)) {\n classifyField(key, PROTO_POLLUTION_FIELDS, JS_RESERVED_FIELDS, protoFields, jsFields);\n }\n for (const expr of Object.values(stage.exprMappings)) {\n collectJsReservedFields(expr, JS_RESERVED_FIELDS, jsFields);\n }\n }\n if (stage.mappings) {\n for (const key of Object.keys(stage.mappings)) {\n classifyField(key, PROTO_POLLUTION_FIELDS, JS_RESERVED_FIELDS, protoFields, jsFields);\n }\n }\n // Map duplicate key detection\n if (stage.exprMappings) {\n checkMapDuplicateKeys(stage.exprMappings, job.name, errors);\n }\n if (stage.mappings) {\n checkMapDuplicateKeys(stage.mappings, job.name, errors);\n }\n }\n if (stage.kind === \"where\" && JS_RESERVED_FIELDS.has(stage.left)) {\n classifyField(stage.left, PROTO_POLLUTION_FIELDS, JS_RESERVED_FIELDS, protoFields, jsFields);\n }\n // Proto-pollution fields → error (blocks compilation)\n for (const field of protoFields) {\n errors.push({\n message: `Job '${job.name}': field '${field}' is a prototype pollution vector — not allowed as map key or field name`,\n kind: \"prohibited\",\n });\n }\n // Other JS reserved fields → warning\n for (const field of jsFields) {\n errors.push({\n message: `Job '${job.name}': field '${field}' is a JavaScript reserved identifier — may expose runtime internals`,\n kind: \"prohibited\",\n severity: \"warning\",\n });\n }\n }\n }\n\n // Division risk: map expressions with field / field (could produce NaN)\n for (const job of program.jobs) {\n for (const stage of job.pipeline) {\n if (stage.kind === \"map\") {\n if (stage.expression) {\n checkDivisionRisk(stage.expression, job.name, errors);\n }\n if (stage.exprMappings) {\n for (const expr of Object.values(stage.exprMappings)) {\n checkDivisionRisk(expr, job.name, errors);\n }\n }\n }\n }\n }\n\n return errors;\n}\n\nfunction checkDivisionRisk(expr: Expression, jobName: string, errors: CompileError[]): void {\n if (expr.kind === \"binary\" && expr.op === \"/\") {\n // If the divisor is a field reference (not a literal), it could be zero → NaN risk\n if (expr.right.kind === \"field_access\") {\n errors.push({\n message: `Job '${jobName}': division by field '${expr.right.path}' may produce NaN/Infinity if zero — consider adding a where clause to filter`,\n kind: \"prohibited\",\n severity: \"warning\",\n });\n }\n }\n // Recurse into sub-expressions\n if (expr.kind === \"binary\") {\n checkDivisionRisk(expr.left, jobName, errors);\n checkDivisionRisk(expr.right, jobName, errors);\n }\n}\n\nfunction collectJsReservedFields(expr: Expression, reserved: Set<string>, out: string[]): void {\n if (expr.kind === \"field_access\" && reserved.has(expr.path)) out.push(expr.path);\n if (expr.kind === \"binary\") {\n collectJsReservedFields(expr.left, reserved, out);\n collectJsReservedFields(expr.right, reserved, out);\n }\n}\n\nfunction classifyField(\n field: string,\n proto: Set<string>,\n jsReserved: Set<string>,\n protoOut: string[],\n jsOut: string[],\n): void {\n if (proto.has(field)) {\n protoOut.push(field);\n } else if (jsReserved.has(field)) {\n jsOut.push(field);\n }\n}\n\nfunction checkMapDuplicateKeys(obj: Record<string, unknown>, jobName: string, errors: CompileError[]): void {\n // Object.create(null) maps already deduplicate at parser level (last-write-wins).\n // But we track keys during parsing — duplicates are caught at parse time.\n // This is a defense-in-depth check for any keys that slip through.\n const seen = new Set<string>();\n for (const key of Object.keys(obj)) {\n if (seen.has(key)) {\n errors.push({\n message: `Job '${jobName}': map has duplicate key '${key}' — last value wins silently, potential data confusion`,\n kind: \"prohibited\",\n severity: \"warning\",\n });\n }\n seen.add(key);\n }\n}\n\nfunction checkFanoutDepth(stages: PipelineStage[], depth: number, errors: CompileError[]): void {\n for (const stage of stages) {\n if (stage.kind === \"fanout\") {\n if (stage.branches.length > 10) {\n errors.push({\n message: `Fanout has ${stage.branches.length} branches — more than 10 branches may cause excessive parallelism`,\n kind: \"prohibited\",\n severity: \"warning\",\n });\n }\n if (depth + 1 > 3) {\n errors.push({\n message: `Fanout nesting depth exceeds 3 levels — deeply nested fanout may indicate over-complexity`,\n kind: \"prohibited\",\n severity: \"warning\",\n });\n return;\n }\n for (const branch of stage.branches) {\n checkFanoutDepth(branch, depth + 1, errors);\n }\n }\n }\n}\n\nfunction checkFanoutDuplicateWrites(job: JobDeclaration, errors: CompileError[]): void {\n for (const stage of job.pipeline) {\n if (stage.kind !== \"fanout\") continue;\n // Collect write paths from each branch\n const writePaths = new Map<string, number>(); // path → branch count\n for (const branch of stage.branches) {\n for (const s of branch) {\n if ((s.kind === \"save\" || s.kind === \"tee\") && \"path\" in s) {\n const path = (s as any).path as string;\n writePaths.set(path, (writePaths.get(path) ?? 0) + 1);\n }\n }\n }\n for (const [path, count] of writePaths) {\n if (count > 1) {\n errors.push({\n message: `Job '${job.name}': fanout has ${count} branches writing to '${path}' — last write wins silently, audit trail lost`,\n kind: \"prohibited\",\n severity: \"warning\",\n });\n }\n }\n }\n}\n\nfunction checkExpressionDivZero(stage: PipelineStage, errors: CompileError[]): void {\n if (stage.kind !== \"map\") return;\n if (stage.exprMappings) {\n for (const expr of Object.values(stage.exprMappings)) {\n if (hasLiteralDivByZero(expr)) {\n errors.push({ message: \"Expression contains literal division by zero\", kind: \"prohibited\", severity: \"warning\" });\n }\n }\n }\n if (stage.expression && hasLiteralDivByZero(stage.expression)) {\n errors.push({ message: \"Expression contains literal division by zero\", kind: \"prohibited\", severity: \"warning\" });\n }\n}\n\nfunction hasLiteralDivByZero(expr: Expression): boolean {\n if (expr.kind === \"binary\" && expr.op === \"/\" && expr.right.kind === \"literal\" && expr.right.value === 0) {\n return true;\n }\n if (expr.kind === \"binary\") {\n return hasLiteralDivByZero(expr.left) || hasLiteralDivByZero(expr.right);\n }\n return false;\n}\n\nfunction checkRouteDag(program: Program, errors: CompileError[]): void {\n // Build adjacency: job -> [target jobs via route]\n const graph = new Map<string, Set<string>>();\n for (const job of program.jobs) {\n const targets = new Set<string>();\n for (const stage of job.pipeline) {\n if (stage.kind === \"route\") {\n for (const branch of stage.branches) targets.add(branch.targetJob);\n if (stage.fallback) targets.add(stage.fallback);\n }\n }\n if (targets.size > 0) graph.set(job.name, targets);\n }\n\n // DFS cycle detection\n const visited = new Set<string>();\n const inStack = new Set<string>();\n\n function dfs(node: string): boolean {\n if (inStack.has(node)) return true; // cycle\n if (visited.has(node)) return false;\n visited.add(node);\n inStack.add(node);\n const neighbors = graph.get(node);\n if (neighbors) {\n for (const n of neighbors) {\n if (dfs(n)) {\n errors.push({\n message: `Route cycle detected: job '${node}' → job '${n}' forms a cycle`,\n kind: \"prohibited\",\n });\n return true;\n }\n }\n }\n inStack.delete(node);\n return false;\n }\n\n for (const node of graph.keys()) {\n dfs(node);\n }\n}\n\nconst ANNOTATION_VALIDATORS: Record<string, (args: string[]) => string | null> = {\n approval: (args) => {\n if (args.length === 0) return null;\n const valid = new Set([\"human\", \"auto\"]);\n if (!valid.has(args[0])) return `@approval accepts 'human' or 'auto', got '${args[0]}'`;\n return null;\n },\n readonly: () => null,\n retry: (args) => {\n if (args.length === 0) return null;\n const n = Number(args[0]);\n if (isNaN(n) || !Number.isInteger(n) || n < 0) return `@retry requires a non-negative integer, got '${args[0]}'`;\n return null;\n },\n timeout: (args) => {\n if (args.length === 0) return null;\n const n = Number(args[0]);\n if (isNaN(n) || n <= 0) return `@timeout requires a positive number, got '${args[0]}'`;\n if (n > 3600000) return `@timeout(${args[0]}) exceeds hard limit of 3600000ms (1 hour) — reduce timeout`;\n return null;\n },\n on_error: (args) => {\n if (args.length === 0) return `@on_error requires a strategy: skip, fail, or save <path>`;\n const valid = new Set([\"skip\", \"fail\", \"save\"]);\n if (!valid.has(args[0])) return `@on_error accepts 'skip', 'fail', or 'save', got '${args[0]}'`;\n return null;\n },\n budget: (args) => {\n if (args.length === 0) return `@budget requires at least one limit: @budget(actions 50, records 10000)`;\n if (args.length % 2 !== 0) return `@budget requires dimension/value pairs — got odd number of args`;\n const validDims = new Set([\"actions\", \"writes\", \"records\", \"tokens\", \"cost\"]);\n for (let i = 0; i < args.length; i += 2) {\n if (!validDims.has(args[i])) return `@budget dimension must be one of: ${[...validDims].join(\", \")} — got '${args[i]}'`;\n const n = Number(args[i + 1]);\n if (isNaN(n) || n <= 0) return `@budget value must be a positive number, got '${args[i + 1]}'`;\n }\n return null;\n },\n caps: (args) => {\n if (args.length === 0) return `@caps requires at least one capability: @caps(read /path/*, write /out/*)`;\n if (args.length % 2 !== 0) return `@caps requires operation/path pairs — got odd number of args`;\n const validOps = new Set([\"read\", \"write\", \"exec\"]);\n for (let i = 0; i < args.length; i += 2) {\n if (!validOps.has(args[i])) return `@caps operation must be 'read', 'write', or 'exec', got '${args[i]}'`;\n if (!args[i + 1].startsWith(\"/\")) return `@caps path must start with '/', got '${args[i + 1]}'`;\n }\n return null;\n },\n};\n\nexport const BUILTIN_COMMAND_NAMES = Object.keys(BUILTIN_MANIFESTS);\n\nexport function typeErrorsToDiagnostics(errors: TypeError[]): AshDiagnostic[] {\n return errors.map(e => ({\n code: \"ASH_TYPE_MISMATCH\" as DiagnosticCode,\n message: e.message,\n stage: e.stage,\n }));\n}\n\nexport function compileErrorsToDiagnostics(errors: CompileError[]): AshDiagnostic[] {\n return errors.map(e => ({\n code: (e.kind === \"unknown_command\" ? \"ASH_UNKNOWN_COMMAND\" : \"ASH_ANNOTATION_INVALID\") as DiagnosticCode,\n severity: e.severity,\n message: e.message,\n suggestion: e.kind === \"unknown_command\" ? BUILTIN_COMMAND_NAMES : undefined,\n }));\n}\n\nexport function annotationErrorsToDiagnostics(errors: CompileError[]): AshDiagnostic[] {\n return errors.map(e => ({\n code: \"ASH_ANNOTATION_INVALID\" as DiagnosticCode,\n severity: e.severity,\n message: e.message,\n }));\n}\n\nexport function parseSyntaxError(errorMessage: string): AshDiagnostic {\n const isUnterminated = /unterminated/i.test(errorMessage);\n const isDuplicate = /duplicate variable/i.test(errorMessage);\n const lineMatch = errorMessage.match(/line\\s+(\\d+)/i);\n const colMatch = errorMessage.match(/column\\s+(\\d+)/i);\n // Strip \"Error:\" prefix if present\n const cleanMsg = errorMessage.replace(/^Error:\\s*/i, \"\");\n return {\n code: isDuplicate ? \"ASH_DUPLICATE_VAR\" : isUnterminated ? \"ASH_SYNTAX_UNTERMINATED\" : \"ASH_SYNTAX_UNEXPECTED\",\n message: cleanMsg,\n line: lineMatch ? Number(lineMatch[1]) : undefined,\n column: colMatch ? Number(colMatch[1]) : undefined,\n };\n}\n\nexport function checkAnnotations(job: JobDeclaration): CompileError[] {\n const errors: CompileError[] = [];\n\n for (const ann of job.annotations) {\n const validator = Object.hasOwn(ANNOTATION_VALIDATORS, ann.name) ? ANNOTATION_VALIDATORS[ann.name] : undefined;\n if (!validator) {\n errors.push({\n message: `Unknown annotation '@${ann.name}' — not in allowed set`,\n kind: \"prohibited\",\n });\n continue;\n }\n const err = validator(ann.args);\n if (err) {\n errors.push({ message: err, kind: \"prohibited\" });\n }\n }\n\n // 2. Duplicate annotation detection — last-wins is an audit deception vector\n const annCounts = new Map<string, number>();\n for (const ann of job.annotations) {\n annCounts.set(ann.name, (annCounts.get(ann.name) ?? 0) + 1);\n }\n for (const [name, count] of annCounts) {\n if (count > 1) {\n errors.push({\n message: `Job '${job.name}': @${name} appears ${count} times — last value wins silently, potential audit deception`,\n kind: \"prohibited\",\n });\n }\n }\n\n // 3. @retry hard limit (error, not warning)\n const retryAnns = job.annotations.filter(a => a.name === \"retry\");\n for (const ann of retryAnns) {\n if (ann.args.length > 0) {\n const n = Number(ann.args[0]);\n if (!isNaN(n) && n > 100) {\n errors.push({ message: `@retry(${n}) exceeds hard limit of 100 — reduce retry count`, kind: \"prohibited\" });\n }\n }\n }\n\n // 4. @approval conflict: human and auto on same job\n const approvalAnns = job.annotations.filter(a => a.name === \"approval\");\n if (approvalAnns.length > 1) {\n const values = approvalAnns.map(a => a.args[0]).filter(Boolean);\n if (values.includes(\"human\") && values.includes(\"auto\")) {\n errors.push({ message: `@approval conflict: 'human' and 'auto' cannot both be set on job '${job.name}'`, kind: \"prohibited\" });\n }\n }\n\n return errors;\n}\n\n// ── @caps: capability-based path security ──\n\nexport interface CapEntry {\n op: \"read\" | \"write\" | \"exec\";\n pathGlob: string;\n}\n\nexport function parseCaps(ann: { args: string[] }): CapEntry[] {\n const caps: CapEntry[] = [];\n for (let i = 0; i < ann.args.length; i += 2) {\n caps.push({ op: ann.args[i] as CapEntry[\"op\"], pathGlob: ann.args[i + 1] });\n }\n return caps;\n}\n\nexport function capMatches(pathGlob: string, actual: string): boolean {\n if (pathGlob === actual) return true;\n if (pathGlob.endsWith(\"/*\")) {\n const prefix = pathGlob.slice(0, -1); // \"/ha/sensors/\" (keep trailing /)\n return actual.startsWith(prefix) || actual === pathGlob.slice(0, -2); // exact parent match\n }\n if (pathGlob.endsWith(\"*\")) {\n return actual.startsWith(pathGlob.slice(0, -1));\n }\n return false;\n}\n\nexport function hasCapFor(caps: CapEntry[], op: CapEntry[\"op\"], path: string): boolean {\n return caps.some(c => c.op === op && capMatches(c.pathGlob, path));\n}\n\nconst STAGE_OP_MAP: Record<string, CapEntry[\"op\"]> = {\n find: \"read\",\n lookup: \"read\",\n save: \"write\",\n publish: \"write\",\n tee: \"write\",\n action: \"exec\",\n};\n\nfunction checkStagesCaps(stages: PipelineStage[], caps: CapEntry[], diagnostics: AshDiagnostic[]): void {\n for (const stage of stages) {\n // Skip static caps check for relative actions (path resolved at runtime, caps checked per-exec)\n if (stage.kind === \"action\" && stage.relative) continue;\n // Skip static caps check for template paths (resolved at runtime, caps checked post-resolution)\n if (\"path\" in stage && typeof (stage as any).path === \"string\" && (stage as any).path.includes(\"${\")) continue;\n\n const op = Object.hasOwn(STAGE_OP_MAP, stage.kind) ? STAGE_OP_MAP[stage.kind] : undefined;\n if (op && \"path\" in stage && typeof (stage as any).path === \"string\") {\n const path = (stage as any).path as string;\n if (!hasCapFor(caps, op, path)) {\n diagnostics.push({\n code: \"ASH_CAP_DENIED\",\n message: `@caps denied: '${stage.kind} ${path}' requires '${op}' capability for '${path}'`,\n });\n }\n }\n if (stage.kind === \"fanout\") {\n for (const branch of stage.branches) {\n checkStagesCaps(branch, caps, diagnostics);\n }\n }\n }\n}\n\nexport function checkJobCaps(job: JobDeclaration): AshDiagnostic[] {\n const capsAnns = job.annotations.filter(a => a.name === \"caps\");\n if (capsAnns.length === 0) return [];\n\n const diagnostics: AshDiagnostic[] = [];\n const allCaps: CapEntry[] = [];\n for (const ann of capsAnns) {\n if (ann.args.length > 0 && ann.args.length % 2 === 0) {\n allCaps.push(...parseCaps(ann));\n }\n }\n\n if (allCaps.length === 0) return diagnostics;\n\n checkStagesCaps(job.pipeline, allCaps, diagnostics);\n return diagnostics;\n}\n"],"mappings":"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"}