@archmax-ai/harness 1.0.1 → 2.0.0

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 (94) hide show
  1. package/README.md +167 -165
  2. package/dist/agent.d.ts +2 -2
  3. package/dist/agent.js +2 -2
  4. package/dist/assembly/compose.js +5 -3
  5. package/dist/assembly/compose.js.map +1 -1
  6. package/dist/authoring-skill/archmax-harness/README.md +13 -15
  7. package/dist/authoring-skill/archmax-harness/SKILL.md +174 -261
  8. package/dist/authoring-skill/archmax-harness/references/backend-integration.md +244 -764
  9. package/dist/authoring-skill/archmax-harness/references/cases.md +178 -53
  10. package/dist/authoring-skill/archmax-harness/references/code-states-and-scripts.md +211 -100
  11. package/dist/authoring-skill/archmax-harness/references/diagnostics.md +57 -78
  12. package/dist/authoring-skill/archmax-harness/references/workflow-schema.md +614 -1977
  13. package/dist/core/attachments.d.ts +2 -0
  14. package/dist/core/attachments.js +2 -1
  15. package/dist/core/attachments.js.map +1 -1
  16. package/dist/core/deepagents.d.ts +1 -2
  17. package/dist/core/deepagents.js +1 -2
  18. package/dist/core/deepagents.js.map +1 -1
  19. package/dist/core/file-operations.d.ts +6 -0
  20. package/dist/core/file-operations.js +29 -0
  21. package/dist/core/file-operations.js.map +1 -1
  22. package/dist/index.d.ts +5 -1
  23. package/dist/index.js +4 -0
  24. package/dist/index.js.map +1 -1
  25. package/dist/kernel/kernel.js +2 -2
  26. package/dist/kernel/kernel.js.map +1 -1
  27. package/dist/machine/lint-spec.js +6 -5
  28. package/dist/machine/lint-spec.js.map +1 -1
  29. package/dist/machine/machine.d.ts +8 -10
  30. package/dist/machine/machine.js +11 -12
  31. package/dist/machine/machine.js.map +1 -1
  32. package/dist/machine/signature-pattern.d.ts +24 -0
  33. package/dist/machine/signature-pattern.js +67 -0
  34. package/dist/machine/signature-pattern.js.map +1 -0
  35. package/dist/machine/signature.d.ts +137 -35
  36. package/dist/machine/signature.js +476 -25
  37. package/dist/machine/signature.js.map +1 -1
  38. package/dist/machine/spec-schema.d.ts +203 -5
  39. package/dist/machine/spec-schema.js +209 -51
  40. package/dist/machine/spec-schema.js.map +1 -1
  41. package/dist/machine/tool-names.d.ts +24 -34
  42. package/dist/machine/tool-names.js +43 -63
  43. package/dist/machine/tool-names.js.map +1 -1
  44. package/dist/machine/types.d.ts +11 -10
  45. package/dist/machine/types.js.map +1 -1
  46. package/dist/public/spec.d.ts +10 -6
  47. package/dist/public/spec.js +9 -5
  48. package/dist/public/spec.js.map +1 -1
  49. package/dist/validate/validate.js +3 -3
  50. package/dist/validate/validate.js.map +1 -1
  51. package/dist/workflow/automated.js +6 -1
  52. package/dist/workflow/automated.js.map +1 -1
  53. package/dist/workflow/control-tools.js +3 -3
  54. package/dist/workflow/control-tools.js.map +1 -1
  55. package/dist/workflow/file-values.d.ts +4 -1
  56. package/dist/workflow/file-values.js +2 -0
  57. package/dist/workflow/file-values.js.map +1 -1
  58. package/dist/workflow/grader-state.d.ts +2 -2
  59. package/dist/workflow/grader-state.js +5 -15
  60. package/dist/workflow/grader-state.js.map +1 -1
  61. package/dist/workflow/middleware.d.ts +2 -0
  62. package/dist/workflow/middleware.js +3 -26
  63. package/dist/workflow/middleware.js.map +1 -1
  64. package/dist/workflow/on-error.js +0 -1
  65. package/dist/workflow/on-error.js.map +1 -1
  66. package/dist/workflow/parks.js +0 -1
  67. package/dist/workflow/parks.js.map +1 -1
  68. package/dist/workflow/render-prompt.d.ts +4 -5
  69. package/dist/workflow/render-prompt.js +31 -29
  70. package/dist/workflow/render-prompt.js.map +1 -1
  71. package/dist/workflow/signature-checks.d.ts +8 -3
  72. package/dist/workflow/signature-checks.js +17 -4
  73. package/dist/workflow/signature-checks.js.map +1 -1
  74. package/dist/workflow/state.d.ts +0 -9
  75. package/dist/workflow/state.js +0 -3
  76. package/dist/workflow/state.js.map +1 -1
  77. package/dist/workflow/sub-workflow.js +4 -1
  78. package/dist/workflow/sub-workflow.js.map +1 -1
  79. package/dist/workflow/turn-boundary.js +0 -1
  80. package/dist/workflow/turn-boundary.js.map +1 -1
  81. package/package.json +2 -2
  82. package/skills/archmax-harness/README.md +13 -15
  83. package/skills/archmax-harness/SKILL.md +174 -261
  84. package/skills/archmax-harness/references/backend-integration.md +244 -764
  85. package/skills/archmax-harness/references/cases.md +178 -53
  86. package/skills/archmax-harness/references/code-states-and-scripts.md +211 -100
  87. package/skills/archmax-harness/references/diagnostics.md +57 -78
  88. package/skills/archmax-harness/references/workflow-schema.md +614 -1977
  89. package/dist/authoring-skill/archmax-harness/references/code-state-and-test-scripts.md +0 -1424
  90. package/dist/authoring-skill/archmax-harness/references/wiring.md +0 -119
  91. package/dist/authoring-skill/archmax-harness/references/workflow-yaml.md +0 -399
  92. package/skills/archmax-harness/references/code-state-and-test-scripts.md +0 -1424
  93. package/skills/archmax-harness/references/wiring.md +0 -119
  94. package/skills/archmax-harness/references/workflow-yaml.md +0 -399
@@ -57,13 +57,12 @@ export declare const machineTransitionSchema: z.ZodObject<{
57
57
  none: "none";
58
58
  }>>;
59
59
  }, z.core.$strict>;
60
- /** Whether an edge carries no description the agent could route on: absent, empty or blank. */
60
+ /** Whether an edge carries no description: absent, empty or blank. */
61
61
  export declare function isUndescribed(transition: {
62
62
  description?: string;
63
63
  }): boolean;
64
64
  /** @see StateBudget */
65
65
  export declare const stateBudgetSchema: z.ZodObject<{
66
- maxTurns: z.ZodOptional<z.ZodNumber>;
67
66
  timeoutMs: z.ZodOptional<z.ZodNumber>;
68
67
  maxParks: z.ZodOptional<z.ZodNumber>;
69
68
  }, z.core.$strict>;
@@ -173,7 +172,8 @@ export declare const stateTypeSchema: z.ZodEnum<{
173
172
  *
174
173
  * The object spelling of a signature entry. Strict, unlike the loose declaration
175
174
  * around it: an unknown key here is most likely a misspelled `type` or
176
- * `description`, and read loosely it would silently mean "untyped".
175
+ * `description`, and read loosely it would silently mean "untyped". A rule key
176
+ * is shape-checked here and held to its entry's type by {@link refineRules}.
177
177
  */
178
178
  export declare const signatureEntryObjectSchema: z.ZodObject<{
179
179
  name: z.ZodString;
@@ -189,6 +189,26 @@ export declare const signatureEntryObjectSchema: z.ZodObject<{
189
189
  array: "array";
190
190
  }>>;
191
191
  description: z.ZodOptional<z.ZodString>;
192
+ format: z.ZodOptional<z.ZodEnum<{
193
+ email: "email";
194
+ url: "url";
195
+ phone: "phone";
196
+ }>>;
197
+ pattern: z.ZodOptional<z.ZodString>;
198
+ minLength: z.ZodOptional<z.ZodNumber>;
199
+ maxLength: z.ZodOptional<z.ZodNumber>;
200
+ enum: z.ZodOptional<z.ZodArray<z.ZodString>>;
201
+ content: z.ZodOptional<z.ZodEnum<{
202
+ text: "text";
203
+ markdown: "markdown";
204
+ html: "html";
205
+ }>>;
206
+ minimum: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
207
+ maximum: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
208
+ minItems: z.ZodOptional<z.ZodNumber>;
209
+ maxItems: z.ZodOptional<z.ZodNumber>;
210
+ accept: z.ZodOptional<z.ZodArray<z.ZodString>>;
211
+ maxSize: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
192
212
  }, z.core.$strict>;
193
213
  /**
194
214
  * @see SignatureEntryDeclaration
@@ -211,6 +231,26 @@ export declare const signatureEntrySchema: z.ZodUnion<readonly [z.ZodString, z.Z
211
231
  array: "array";
212
232
  }>>;
213
233
  description: z.ZodOptional<z.ZodString>;
234
+ format: z.ZodOptional<z.ZodEnum<{
235
+ email: "email";
236
+ url: "url";
237
+ phone: "phone";
238
+ }>>;
239
+ pattern: z.ZodOptional<z.ZodString>;
240
+ minLength: z.ZodOptional<z.ZodNumber>;
241
+ maxLength: z.ZodOptional<z.ZodNumber>;
242
+ enum: z.ZodOptional<z.ZodArray<z.ZodString>>;
243
+ content: z.ZodOptional<z.ZodEnum<{
244
+ text: "text";
245
+ markdown: "markdown";
246
+ html: "html";
247
+ }>>;
248
+ minimum: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
249
+ maximum: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
250
+ minItems: z.ZodOptional<z.ZodNumber>;
251
+ maxItems: z.ZodOptional<z.ZodNumber>;
252
+ accept: z.ZodOptional<z.ZodArray<z.ZodString>>;
253
+ maxSize: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
214
254
  }, z.core.$strict>]>;
215
255
  /**
216
256
  * @see TriggerDeclaration
@@ -238,6 +278,26 @@ export declare const triggerDeclarationSchema: z.ZodObject<{
238
278
  array: "array";
239
279
  }>>;
240
280
  description: z.ZodOptional<z.ZodString>;
281
+ format: z.ZodOptional<z.ZodEnum<{
282
+ email: "email";
283
+ url: "url";
284
+ phone: "phone";
285
+ }>>;
286
+ pattern: z.ZodOptional<z.ZodString>;
287
+ minLength: z.ZodOptional<z.ZodNumber>;
288
+ maxLength: z.ZodOptional<z.ZodNumber>;
289
+ enum: z.ZodOptional<z.ZodArray<z.ZodString>>;
290
+ content: z.ZodOptional<z.ZodEnum<{
291
+ text: "text";
292
+ markdown: "markdown";
293
+ html: "html";
294
+ }>>;
295
+ minimum: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
296
+ maximum: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
297
+ minItems: z.ZodOptional<z.ZodNumber>;
298
+ maxItems: z.ZodOptional<z.ZodNumber>;
299
+ accept: z.ZodOptional<z.ZodArray<z.ZodString>>;
300
+ maxSize: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
241
301
  }, z.core.$strict>]>>>;
242
302
  returns: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
243
303
  name: z.ZodString;
@@ -253,6 +313,26 @@ export declare const triggerDeclarationSchema: z.ZodObject<{
253
313
  array: "array";
254
314
  }>>;
255
315
  description: z.ZodOptional<z.ZodString>;
316
+ format: z.ZodOptional<z.ZodEnum<{
317
+ email: "email";
318
+ url: "url";
319
+ phone: "phone";
320
+ }>>;
321
+ pattern: z.ZodOptional<z.ZodString>;
322
+ minLength: z.ZodOptional<z.ZodNumber>;
323
+ maxLength: z.ZodOptional<z.ZodNumber>;
324
+ enum: z.ZodOptional<z.ZodArray<z.ZodString>>;
325
+ content: z.ZodOptional<z.ZodEnum<{
326
+ text: "text";
327
+ markdown: "markdown";
328
+ html: "html";
329
+ }>>;
330
+ minimum: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
331
+ maximum: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
332
+ minItems: z.ZodOptional<z.ZodNumber>;
333
+ maxItems: z.ZodOptional<z.ZodNumber>;
334
+ accept: z.ZodOptional<z.ZodArray<z.ZodString>>;
335
+ maxSize: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
256
336
  }, z.core.$strict>]>>>;
257
337
  }, z.core.$loose>;
258
338
  /**
@@ -280,6 +360,26 @@ export declare const stateTriggersSchema: z.ZodRecord<z.ZodString, z.ZodNullable
280
360
  array: "array";
281
361
  }>>;
282
362
  description: z.ZodOptional<z.ZodString>;
363
+ format: z.ZodOptional<z.ZodEnum<{
364
+ email: "email";
365
+ url: "url";
366
+ phone: "phone";
367
+ }>>;
368
+ pattern: z.ZodOptional<z.ZodString>;
369
+ minLength: z.ZodOptional<z.ZodNumber>;
370
+ maxLength: z.ZodOptional<z.ZodNumber>;
371
+ enum: z.ZodOptional<z.ZodArray<z.ZodString>>;
372
+ content: z.ZodOptional<z.ZodEnum<{
373
+ text: "text";
374
+ markdown: "markdown";
375
+ html: "html";
376
+ }>>;
377
+ minimum: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
378
+ maximum: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
379
+ minItems: z.ZodOptional<z.ZodNumber>;
380
+ maxItems: z.ZodOptional<z.ZodNumber>;
381
+ accept: z.ZodOptional<z.ZodArray<z.ZodString>>;
382
+ maxSize: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
283
383
  }, z.core.$strict>]>>>;
284
384
  returns: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
285
385
  name: z.ZodString;
@@ -295,6 +395,26 @@ export declare const stateTriggersSchema: z.ZodRecord<z.ZodString, z.ZodNullable
295
395
  array: "array";
296
396
  }>>;
297
397
  description: z.ZodOptional<z.ZodString>;
398
+ format: z.ZodOptional<z.ZodEnum<{
399
+ email: "email";
400
+ url: "url";
401
+ phone: "phone";
402
+ }>>;
403
+ pattern: z.ZodOptional<z.ZodString>;
404
+ minLength: z.ZodOptional<z.ZodNumber>;
405
+ maxLength: z.ZodOptional<z.ZodNumber>;
406
+ enum: z.ZodOptional<z.ZodArray<z.ZodString>>;
407
+ content: z.ZodOptional<z.ZodEnum<{
408
+ text: "text";
409
+ markdown: "markdown";
410
+ html: "html";
411
+ }>>;
412
+ minimum: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
413
+ maximum: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
414
+ minItems: z.ZodOptional<z.ZodNumber>;
415
+ maxItems: z.ZodOptional<z.ZodNumber>;
416
+ accept: z.ZodOptional<z.ZodArray<z.ZodString>>;
417
+ maxSize: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
298
418
  }, z.core.$strict>]>>>;
299
419
  }, z.core.$loose>>>;
300
420
  /** @see MachineState */
@@ -329,6 +449,26 @@ export declare const machineStateSchema: z.ZodObject<{
329
449
  array: "array";
330
450
  }>>;
331
451
  description: z.ZodOptional<z.ZodString>;
452
+ format: z.ZodOptional<z.ZodEnum<{
453
+ email: "email";
454
+ url: "url";
455
+ phone: "phone";
456
+ }>>;
457
+ pattern: z.ZodOptional<z.ZodString>;
458
+ minLength: z.ZodOptional<z.ZodNumber>;
459
+ maxLength: z.ZodOptional<z.ZodNumber>;
460
+ enum: z.ZodOptional<z.ZodArray<z.ZodString>>;
461
+ content: z.ZodOptional<z.ZodEnum<{
462
+ text: "text";
463
+ markdown: "markdown";
464
+ html: "html";
465
+ }>>;
466
+ minimum: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
467
+ maximum: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
468
+ minItems: z.ZodOptional<z.ZodNumber>;
469
+ maxItems: z.ZodOptional<z.ZodNumber>;
470
+ accept: z.ZodOptional<z.ZodArray<z.ZodString>>;
471
+ maxSize: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
332
472
  }, z.core.$strict>]>>>;
333
473
  returns: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
334
474
  name: z.ZodString;
@@ -344,11 +484,30 @@ export declare const machineStateSchema: z.ZodObject<{
344
484
  array: "array";
345
485
  }>>;
346
486
  description: z.ZodOptional<z.ZodString>;
487
+ format: z.ZodOptional<z.ZodEnum<{
488
+ email: "email";
489
+ url: "url";
490
+ phone: "phone";
491
+ }>>;
492
+ pattern: z.ZodOptional<z.ZodString>;
493
+ minLength: z.ZodOptional<z.ZodNumber>;
494
+ maxLength: z.ZodOptional<z.ZodNumber>;
495
+ enum: z.ZodOptional<z.ZodArray<z.ZodString>>;
496
+ content: z.ZodOptional<z.ZodEnum<{
497
+ text: "text";
498
+ markdown: "markdown";
499
+ html: "html";
500
+ }>>;
501
+ minimum: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
502
+ maximum: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
503
+ minItems: z.ZodOptional<z.ZodNumber>;
504
+ maxItems: z.ZodOptional<z.ZodNumber>;
505
+ accept: z.ZodOptional<z.ZodArray<z.ZodString>>;
506
+ maxSize: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
347
507
  }, z.core.$strict>]>>>;
348
508
  }, z.core.$loose>>>>;
349
509
  script: z.ZodOptional<z.ZodString>;
350
510
  budget: z.ZodOptional<z.ZodObject<{
351
- maxTurns: z.ZodOptional<z.ZodNumber>;
352
511
  timeoutMs: z.ZodOptional<z.ZodNumber>;
353
512
  maxParks: z.ZodOptional<z.ZodNumber>;
354
513
  }, z.core.$strict>>;
@@ -586,6 +745,26 @@ export declare const machineSpecSchema: z.ZodObject<{
586
745
  array: "array";
587
746
  }>>;
588
747
  description: z.ZodOptional<z.ZodString>;
748
+ format: z.ZodOptional<z.ZodEnum<{
749
+ email: "email";
750
+ url: "url";
751
+ phone: "phone";
752
+ }>>;
753
+ pattern: z.ZodOptional<z.ZodString>;
754
+ minLength: z.ZodOptional<z.ZodNumber>;
755
+ maxLength: z.ZodOptional<z.ZodNumber>;
756
+ enum: z.ZodOptional<z.ZodArray<z.ZodString>>;
757
+ content: z.ZodOptional<z.ZodEnum<{
758
+ text: "text";
759
+ markdown: "markdown";
760
+ html: "html";
761
+ }>>;
762
+ minimum: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
763
+ maximum: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
764
+ minItems: z.ZodOptional<z.ZodNumber>;
765
+ maxItems: z.ZodOptional<z.ZodNumber>;
766
+ accept: z.ZodOptional<z.ZodArray<z.ZodString>>;
767
+ maxSize: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
589
768
  }, z.core.$strict>]>>>;
590
769
  returns: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
591
770
  name: z.ZodString;
@@ -601,11 +780,30 @@ export declare const machineSpecSchema: z.ZodObject<{
601
780
  array: "array";
602
781
  }>>;
603
782
  description: z.ZodOptional<z.ZodString>;
783
+ format: z.ZodOptional<z.ZodEnum<{
784
+ email: "email";
785
+ url: "url";
786
+ phone: "phone";
787
+ }>>;
788
+ pattern: z.ZodOptional<z.ZodString>;
789
+ minLength: z.ZodOptional<z.ZodNumber>;
790
+ maxLength: z.ZodOptional<z.ZodNumber>;
791
+ enum: z.ZodOptional<z.ZodArray<z.ZodString>>;
792
+ content: z.ZodOptional<z.ZodEnum<{
793
+ text: "text";
794
+ markdown: "markdown";
795
+ html: "html";
796
+ }>>;
797
+ minimum: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
798
+ maximum: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
799
+ minItems: z.ZodOptional<z.ZodNumber>;
800
+ maxItems: z.ZodOptional<z.ZodNumber>;
801
+ accept: z.ZodOptional<z.ZodArray<z.ZodString>>;
802
+ maxSize: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
604
803
  }, z.core.$strict>]>>>;
605
804
  }, z.core.$loose>>>>;
606
805
  script: z.ZodOptional<z.ZodString>;
607
806
  budget: z.ZodOptional<z.ZodObject<{
608
- maxTurns: z.ZodOptional<z.ZodNumber>;
609
807
  timeoutMs: z.ZodOptional<z.ZodNumber>;
610
808
  maxParks: z.ZodOptional<z.ZodNumber>;
611
809
  }, z.core.$strict>>;
@@ -9,7 +9,8 @@
9
9
  import { z } from "zod";
10
10
  import { normalizeAllowEntry } from "./allow.js";
11
11
  import { isSlug } from "./slug.js";
12
- import { SIGNATURE_TYPES } from "./signature.js";
12
+ import { boundsInOrder, isBound, isCount, parseSize, RULE_TYPES, SIGNATURE_CONTENTS, SIGNATURE_FORMATS, SIGNATURE_RULE_NAMES, SIGNATURE_TYPES, } from "./signature.js";
13
+ import { compilePattern, jsonSchemaPatternError } from "./signature-pattern.js";
13
14
  import { MANUAL_TRIGGER, parseSessionPath, stateTriggerIds } from "./triggers.js";
14
15
  import { parseReferences, referenceError, TITLE_VARIABLE, TRIGGER_VARIABLE, VARIABLE_NAME_PATTERN, } from "./variables.js";
15
16
  /**
@@ -156,41 +157,16 @@ export const transitionTypeSchema = z.enum(["approve", "reject", "refine", "none
156
157
  });
157
158
  /**
158
159
  * The agent is disclosed the active state's edges and nothing else of the graph,
159
- * so an edge must give it something to route on: its `description`, or — for an
160
- * **undescribed** edge (absent, empty or blank) — the target state's own
161
- * `instructions`, which the edge then discloses in its place. An edge with
162
- * neither hands the agent a bare slug and asks it to route: an unnavigable graph,
163
- * which is worse to warn about than to refuse, so {@link refineSpec} refuses it.
164
- * It refuses an undescribed transition of a human state, where the description
165
- * is the person's button label, and an undescribed edge into a grader or a code
166
- * state: a grader's instructions are its criteria, which the work it judges is
167
- * never shown, and a code state has none. A code state's own edges are exempt —
168
- * its script routes, and nothing reads their descriptions. A non-string is a
169
- * typo and refused here. The issue path
160
+ * so an edge's `description` is the only thing it is told about the edge — no
161
+ * attribute of the target state is disclosed — and at a human state it is also
162
+ * the decision's button label. A non-string is a typo and refused here; an
163
+ * absent or blank one is an **undescribed** edge, which {@link refineSpec}
164
+ * accepts only where there is nothing to choose and no condition to say (see
165
+ * {@link undescribedEdgeIssue}). The issue path
170
166
  * (`states.<slug>.transitions.<index>.description`) names the edge.
171
167
  */
172
- const TRANSITION_DESCRIPTION = `must be a string: what the agent routes on when it considers this edge. Say when to take it ` +
173
- `(e.g. 'Refunds over $50'), not where it goes — or omit it to route on the target state's ` +
174
- `instructions instead.`;
175
- /** An undescribed edge whose target has no instructions: nothing to route on. */
176
- function undescribedEdgeMessage(target) {
177
- return (`must be a non-empty string here: the agent routes on an edge's description, or, when it has ` +
178
- `none, on the target state's instructions — and '${target}' declares none. Say when to take ` +
179
- `this edge (e.g. 'Refunds over $50'), or give '${target}' instructions.`);
180
- }
181
- /** An undescribed edge into an automated state: there are no instructions the agent may be shown. */
182
- function undescribedAutomatedEdgeMessage(target, type) {
183
- const why = type === "grader"
184
- ? `'${target}' is a grader, whose instructions are its criteria and never shown to the work it judges`
185
- : `'${target}' is a code state, which has none`;
186
- return (`must be a non-empty string here: the agent routes on an edge's description, or, when it has ` +
187
- `none, on the target state's instructions — and ${why}. Say when to take this edge ` +
188
- `(e.g. 'The reply is drafted').`);
189
- }
190
- /** An undescribed transition on a human state: the decision's button has no label. */
191
- const HUMAN_TRANSITION_DESCRIPTION = `must be a non-empty string on a human state's transition: it is the label of the decision's ` +
192
- `button for the person deciding, and a target's instructions do not tell them what choosing it ` +
193
- `means. Say what the choice is (e.g. 'Approve the refund').`;
168
+ const TRANSITION_DESCRIPTION = `must be a string: it is the only thing the agent is told about this edge, so say when to ` +
169
+ `take it (e.g. 'Refunds over $50'), not where it goes.`;
194
170
  /** @see MachineTransition */
195
171
  export const machineTransitionSchema = z
196
172
  .object({
@@ -199,14 +175,13 @@ export const machineTransitionSchema = z
199
175
  type: transitionTypeSchema.optional(),
200
176
  })
201
177
  .strict();
202
- /** Whether an edge carries no description the agent could route on: absent, empty or blank. */
178
+ /** Whether an edge carries no description: absent, empty or blank. */
203
179
  export function isUndescribed(transition) {
204
180
  return !transition.description?.trim();
205
181
  }
206
182
  /** @see StateBudget */
207
183
  export const stateBudgetSchema = z
208
184
  .object({
209
- maxTurns: positiveInt.optional(),
210
185
  timeoutMs: positiveInt.optional(),
211
186
  maxParks: positiveInt.optional(),
212
187
  })
@@ -300,15 +275,39 @@ export const stateTypeSchema = z.enum(["agent", "human", "grader", "code"], {
300
275
  });
301
276
  // --- Triggers ------------------------------------------------------------------
302
277
  /** The keys a signature entry object may carry, as a refusal lists them. */
303
- const SIGNATURE_ENTRY_KEYS = "'name', 'type' and 'description'";
278
+ const SIGNATURE_ENTRY_KEYS = `'name', 'type', 'description' and the rules ` +
279
+ `${SIGNATURE_RULE_NAMES.slice(0, -1).map((rule) => `'${rule}'`).join(", ")} and '${SIGNATURE_RULE_NAMES.at(-1)}'`;
304
280
  /** The accepted type words, as a refusal lists them. */
305
281
  const SIGNATURE_TYPE_LIST = `${SIGNATURE_TYPES.slice(0, -1).join(", ")} or ${SIGNATURE_TYPES.at(-1)}`;
282
+ /** What a `maxSize` must look like, as a refusal says it. */
283
+ const SIZE_SHAPE = "a size in bytes: an integer, or '<n>KB' / '<n>MB' (e.g. 10MB)";
284
+ /** One file extension as `accept` lists it: a dot and a name holding no further dot or separator. */
285
+ const EXTENSION = /^\.[^./\\]+$/;
286
+ /** A length or count rule's shape; that it is a non-negative integer is the refinement's. */
287
+ function countRule(rule) {
288
+ return z.number({ error: `'${rule}' must be a non-negative integer` }).optional();
289
+ }
290
+ /** A `minimum`/`maximum` rule's shape; which of the two kinds the entry's type takes is the refinement's. */
291
+ function boundRule(rule) {
292
+ return z
293
+ .union([z.number(), z.string()], {
294
+ error: `'${rule}' must be a number, a date (YYYY-MM-DD or 'today') or a date-time (RFC 3339 or 'now')`,
295
+ })
296
+ .optional();
297
+ }
298
+ /** A list rule's shape: strings, with emptiness and repeats left to the refinement. */
299
+ function listRule(rule) {
300
+ return z
301
+ .array(z.string({ error: `'${rule}' must list strings` }), { error: `'${rule}' must be a list of strings` })
302
+ .optional();
303
+ }
306
304
  /**
307
305
  * @see SignatureEntryDeclaration
308
306
  *
309
307
  * The object spelling of a signature entry. Strict, unlike the loose declaration
310
308
  * around it: an unknown key here is most likely a misspelled `type` or
311
- * `description`, and read loosely it would silently mean "untyped".
309
+ * `description`, and read loosely it would silently mean "untyped". A rule key
310
+ * is shape-checked here and held to its entry's type by {@link refineRules}.
312
311
  */
313
312
  export const signatureEntryObjectSchema = z.strictObject({
314
313
  name: z
@@ -328,12 +327,126 @@ export const signatureEntryObjectSchema = z.strictObject({
328
327
  .string({ error: `'description' ${NON_EMPTY}` })
329
328
  .refine((value) => value.trim() !== "", `'description' ${NON_EMPTY}`)
330
329
  .optional(),
330
+ format: z
331
+ .enum(SIGNATURE_FORMATS, {
332
+ error: (issue) => `'format' must be one of email, url or phone; got ${JSON.stringify(issue.input)}`,
333
+ })
334
+ .optional(),
335
+ pattern: z.string({ error: "'pattern' must be a string (a regular expression)" }).optional(),
336
+ minLength: countRule("minLength"),
337
+ maxLength: countRule("maxLength"),
338
+ enum: listRule("enum"),
339
+ content: z
340
+ .enum(SIGNATURE_CONTENTS, {
341
+ error: (issue) => `'content' must be one of text, markdown or html; got ${JSON.stringify(issue.input)}`,
342
+ })
343
+ .optional(),
344
+ minimum: boundRule("minimum"),
345
+ maximum: boundRule("maximum"),
346
+ minItems: countRule("minItems"),
347
+ maxItems: countRule("maxItems"),
348
+ accept: listRule("accept"),
349
+ maxSize: z.union([z.number(), z.string()], { error: `'maxSize' must be ${SIZE_SHAPE}` }).optional(),
331
350
  }, {
332
351
  error: (issue) => issue.code === "unrecognized_keys"
333
352
  ? `${entryLabel(issue.input)} declares ${issue.keys.map((k) => `'${k}'`).join(", ")}, which a ` +
334
353
  `signature entry does not take: it has ${SIGNATURE_ENTRY_KEYS} only.`
335
354
  : undefined,
336
- });
355
+ }).superRefine(refineRules);
356
+ /** `string`, `integer, number, date and date-time`. */
357
+ function typeList(types) {
358
+ return types.length === 1 ? types[0] : `${types.slice(0, -1).join(", ")} and ${types.at(-1)}`;
359
+ }
360
+ /**
361
+ * Hold an entry's rules to its type and to each other. Each issue sits at the
362
+ * rule's key and names the entry itself: the union around the entry hands a
363
+ * refinement's issues on unwrapped.
364
+ */
365
+ function refineRules(entry, ctx) {
366
+ const declared = SIGNATURE_RULE_NAMES.filter((rule) => entry[rule] !== undefined);
367
+ if (declared.length === 0)
368
+ return;
369
+ const label = entryLabel(entry);
370
+ const issue = (rule, message) => ctx.addIssue({ code: "custom", path: [rule], message: `${label} ${message}` });
371
+ const { type } = entry;
372
+ const applicable = [];
373
+ for (const rule of declared) {
374
+ const types = RULE_TYPES[rule];
375
+ if (!type)
376
+ issue(rule, `declares '${rule}', but has no 'type': it applies to ${typeList(types)} entries only.`);
377
+ else if (!types.includes(type))
378
+ issue(rule, `declares '${rule}', which applies to ${typeList(types)} entries only.`);
379
+ else
380
+ applicable.push(rule);
381
+ }
382
+ if (!type)
383
+ return;
384
+ const has = (rule) => applicable.includes(rule);
385
+ for (const rule of ["minLength", "maxLength", "minItems", "maxItems"]) {
386
+ if (has(rule) && !isCount(entry[rule])) {
387
+ issue(rule, `declares '${rule}' as ${String(entry[rule])}, which is not a non-negative integer.`);
388
+ applicable.splice(applicable.indexOf(rule), 1);
389
+ }
390
+ }
391
+ if (has("format") && has("pattern")) {
392
+ issue("pattern", `declares both 'format' and 'pattern': an entry declares one or the other.`);
393
+ }
394
+ if (has("pattern")) {
395
+ const pattern = entry.pattern;
396
+ const compiled = compilePattern(pattern);
397
+ const error = compiled.ok ? jsonSchemaPatternError(pattern) : compiled.error;
398
+ if (error)
399
+ issue("pattern", `declares a 'pattern' that cannot be used: ${error}.`);
400
+ }
401
+ for (const rule of ["minimum", "maximum"]) {
402
+ if (!has(rule) || isBound(type, entry[rule]))
403
+ continue;
404
+ const shape = type === "date"
405
+ ? "a date (YYYY-MM-DD, a real calendar day) or 'today'"
406
+ : type === "date-time"
407
+ ? "an RFC 3339 date-time with an offset (e.g. 2026-11-01T09:00:00Z) or 'now'"
408
+ : "a number";
409
+ issue(rule, `declares '${rule}' as ${JSON.stringify(entry[rule])}, but on a ${type} entry it must be ${shape}.`);
410
+ }
411
+ for (const [low, high] of [
412
+ ["minLength", "maxLength"],
413
+ ["minimum", "maximum"],
414
+ ["minItems", "maxItems"],
415
+ ]) {
416
+ if (!has(low) || !has(high))
417
+ continue;
418
+ const ordered = low === "minimum"
419
+ ? boundsInOrder(type, entry[low], entry[high])
420
+ : entry[low] <= entry[high];
421
+ if (!ordered) {
422
+ issue(low, `declares '${low}' ${JSON.stringify(entry[low])} above '${high}' ${JSON.stringify(entry[high])}, ` +
423
+ `so no value could conform.`);
424
+ }
425
+ }
426
+ for (const rule of ["enum", "accept"]) {
427
+ if (!has(rule))
428
+ continue;
429
+ const list = entry[rule];
430
+ if (list.length === 0) {
431
+ issue(rule, `declares an empty '${rule}', so no value could conform.`);
432
+ continue;
433
+ }
434
+ const malformed = rule === "accept" ? list.find((extension) => !EXTENSION.test(extension)) : undefined;
435
+ if (malformed !== undefined) {
436
+ issue(rule, `lists ${JSON.stringify(malformed)} in 'accept', which is not a file extension: write a dot and ` +
437
+ `a name with no further dot, e.g. '.pdf'.`);
438
+ continue;
439
+ }
440
+ // Extensions compare case-insensitively, as `accept` is checked.
441
+ const keys = list.map((item) => (rule === "accept" ? item.toLowerCase() : item));
442
+ const repeated = list.find((_, index) => keys.indexOf(keys[index]) !== index);
443
+ if (repeated !== undefined)
444
+ issue(rule, `lists ${JSON.stringify(repeated)} more than once in '${rule}'.`);
445
+ }
446
+ if (has("maxSize") && parseSize(entry.maxSize) === undefined) {
447
+ issue("maxSize", `declares 'maxSize' as ${JSON.stringify(entry.maxSize)}, which is not ${SIZE_SHAPE}.`);
448
+ }
449
+ }
337
450
  /** An entry as a message names it: by its `name` when it has a readable one. */
338
451
  function entryLabel(input) {
339
452
  const name = input?.name;
@@ -659,6 +772,59 @@ function lockIssues(states) {
659
772
  }
660
773
  return issues;
661
774
  }
775
+ /** The type a state declares, `agent` when it declares none. */
776
+ function stateTypeOf(state) {
777
+ return state?.type ?? "agent";
778
+ }
779
+ /** Why an edge of a state that is not an agent state needs a description, and what to write. */
780
+ const NON_AGENT_EDGE = {
781
+ human: (slug) => `'${slug}' is a human state, where an edge's description is the label of the person's ` +
782
+ `button. Say what choosing it means (e.g. 'Approve the refund').`,
783
+ grader: (slug) => `'${slug}' is a grader state, which chooses its edge by the descriptions. Say when to take ` +
784
+ `it (e.g. 'The email is ready to send').`,
785
+ code: (slug) => `'${slug}' is a code state: its script routes, and the description is what a reader of the ` +
786
+ `spec and the trail goes by. Say when the script takes it (e.g. 'The decision matches the policy').`,
787
+ };
788
+ /** What an edge into a state that is not an agent state hands the session to. */
789
+ const HANDED_TO = {
790
+ human: "a person's decision",
791
+ grader: "a grader's check",
792
+ code: "a code state's check",
793
+ };
794
+ /**
795
+ * Why `slug`'s undescribed edge at `index` is refused, or `undefined` when it
796
+ * stands. An edge may go undescribed only where the agent has nothing to choose
797
+ * and nothing to wait for beyond the state's own work: it is the **only** edge
798
+ * of an **agent** state that has `instructions`, and it leads into **another
799
+ * agent** state. The agent takes it as soon as those instructions are done, and
800
+ * is told so in the edge's place. Every refusal says what to write instead.
801
+ */
802
+ function undescribedEdgeIssue(states, slug, index) {
803
+ const state = states[slug];
804
+ const transitions = state.transitions ?? [];
805
+ const to = transitions[index].to;
806
+ const type = stateTypeOf(state);
807
+ const target = stateTypeOf(states[to]);
808
+ const refuse = (why) => `must be a non-empty string here: ${why}`;
809
+ const say = `Say when to take it (e.g. 'The customer confirmed the address').`;
810
+ if (type !== "agent")
811
+ return refuse(NON_AGENT_EDGE[type](slug));
812
+ if (transitions.length > 1) {
813
+ return refuse(`'${slug}' has ${transitions.length} transitions, and the agent chooses between them by their ` +
814
+ `descriptions; only a state's single transition may go without one. ${say}`);
815
+ }
816
+ if (to === slug)
817
+ return refuse(`this edge leads back into '${slug}' itself, so it needs a condition. ${say}`);
818
+ if (target !== "agent") {
819
+ return refuse(`'${to}' is a ${target} state, so this edge hands the session to ${HANDED_TO[target]}. Say ` +
820
+ `what must be ready for it (e.g. 'The reply is drafted').`);
821
+ }
822
+ if (!state.instructions?.trim()) {
823
+ return refuse(`'${slug}' declares no instructions, and an undescribed edge is taken once they are done. ` +
824
+ `Give '${slug}' instructions, or say when to take this edge.`);
825
+ }
826
+ return undefined;
827
+ }
662
828
  export function refineSpec(spec) {
663
829
  const issues = [];
664
830
  const issue = (path, message) => issues.push({ path, message });
@@ -675,19 +841,11 @@ export function refineSpec(spec) {
675
841
  issue(`${at}.transitions.${index}.to`, `State '${slug}' transitions to undefined state '${transition.to}'`);
676
842
  return;
677
843
  }
678
- // A code state's script routes: nothing reads its edges' descriptions.
679
- if (!isUndescribed(transition) || state.type === "code")
844
+ if (!isUndescribed(transition))
680
845
  return;
681
- const target = states[transition.to];
682
- if (state.type === "human") {
683
- issue(`${at}.transitions.${index}.description`, HUMAN_TRANSITION_DESCRIPTION);
684
- }
685
- else if (target?.type === "grader" || target?.type === "code") {
686
- issue(`${at}.transitions.${index}.description`, undescribedAutomatedEdgeMessage(transition.to, target.type));
687
- }
688
- else if (!target?.instructions?.trim()) {
689
- issue(`${at}.transitions.${index}.description`, undescribedEdgeMessage(transition.to));
690
- }
846
+ const refusal = undescribedEdgeIssue(states, slug, index);
847
+ if (refusal)
848
+ issue(`${at}.transitions.${index}.description`, refusal);
691
849
  });
692
850
  if (state.on_error !== undefined && !(state.on_error in states)) {
693
851
  issue(`${at}.on_error`, `State '${slug}' on_error routes to undefined state '${state.on_error}'`);