@open-agent-toolkit/cli 0.2.50 → 0.2.52

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 (263) hide show
  1. package/assets/bundle-metadata.json +1 -1
  2. package/assets/docs/cli-utilities/bootstrap.md +6 -2
  3. package/assets/docs/cli-utilities/tool-packs.md +87 -33
  4. package/assets/docs/provider-sync/commands.md +30 -2
  5. package/assets/docs/provider-sync/config.md +19 -0
  6. package/assets/docs/provider-sync/manifest-and-drift.md +37 -0
  7. package/assets/docs/provider-sync/providers.md +69 -2
  8. package/assets/docs/provider-sync/scope-and-surface.md +44 -0
  9. package/assets/docs/reference/cli-reference.md +21 -5
  10. package/assets/docs/reference/file-locations.md +8 -2
  11. package/assets/docs/reference/oat-directory-structure.md +25 -9
  12. package/assets/docs/workflows/projects/artifacts.md +9 -4
  13. package/assets/docs/workflows/projects/evidence-layers.md +110 -1
  14. package/assets/docs/workflows/projects/implementation-execution.md +13 -0
  15. package/assets/docs/workflows/projects/lifecycle.md +17 -5
  16. package/assets/docs/workflows/projects/orchestration-model.md +18 -0
  17. package/assets/docs/workflows/projects/picking-up-projects.md +26 -9
  18. package/assets/docs/workflows/projects/pr-flow.md +5 -3
  19. package/assets/docs/workflows/projects/reviewing-oat-prs.md +12 -8
  20. package/assets/public-package-versions.json +4 -4
  21. package/assets/skills/oat-dispatch-subagents/SKILL.md +57 -1
  22. package/assets/skills/oat-dispatch-subagents/references/record-schema.md +77 -1
  23. package/assets/skills/oat-project-autonomous/references/gate-inventory.md +3 -3
  24. package/assets/skills/oat-project-complete/SKILL.md +193 -26
  25. package/assets/skills/oat-project-complete/scripts/execute-synced-archive-entry.mjs +306 -0
  26. package/assets/skills/oat-project-complete/scripts/finalize-synced-archive.mjs +125 -0
  27. package/assets/skills/oat-project-complete/scripts/parse-synced-archive-resume-fields.mjs +106 -0
  28. package/assets/skills/oat-project-complete/scripts/recover-completion-receipts.mjs +110 -0
  29. package/assets/skills/oat-project-complete/scripts/resolve-synced-archive-entry.mjs +164 -0
  30. package/assets/skills/oat-project-dispatch-subagents/SKILL.md +24 -1
  31. package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +3 -3
  32. package/assets/skills/oat-project-implement/references/dispatch-and-dry-run.md +11 -0
  33. package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +3 -3
  34. package/assets/skills/oat-project-plan-writing/SKILL.md +12 -1
  35. package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +3 -3
  36. package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +3 -3
  37. package/assets/skills/oat-project-review-provide/SKILL.md +12 -1
  38. package/assets/skills/oat-project-review-provide-remote/SKILL.md +12 -1
  39. package/dist/agents/canonical/index.d.ts +2 -0
  40. package/dist/agents/canonical/index.d.ts.map +1 -1
  41. package/dist/agents/canonical/index.js +1 -0
  42. package/dist/agents/canonical/resolve.d.ts +45 -0
  43. package/dist/agents/canonical/resolve.d.ts.map +1 -0
  44. package/dist/agents/canonical/resolve.js +175 -0
  45. package/dist/commands/decision/agents-guidance.d.ts +2 -2
  46. package/dist/commands/decision/agents-guidance.d.ts.map +1 -1
  47. package/dist/commands/decision/agents-guidance.js +1 -4
  48. package/dist/commands/decision/index.d.ts.map +1 -1
  49. package/dist/commands/decision/index.js +25 -6
  50. package/dist/commands/docs/init/index.d.ts +7 -1
  51. package/dist/commands/docs/init/index.d.ts.map +1 -1
  52. package/dist/commands/docs/init/index.js +53 -20
  53. package/dist/commands/doctor/index.d.ts +8 -2
  54. package/dist/commands/doctor/index.d.ts.map +1 -1
  55. package/dist/commands/doctor/index.js +157 -46
  56. package/dist/commands/init/index.d.ts +8 -3
  57. package/dist/commands/init/index.d.ts.map +1 -1
  58. package/dist/commands/init/index.js +64 -26
  59. package/dist/commands/init/tools/index.d.ts +12 -12
  60. package/dist/commands/init/tools/index.d.ts.map +1 -1
  61. package/dist/commands/init/tools/index.js +430 -99
  62. package/dist/commands/init/tools/install-state.d.ts +2 -0
  63. package/dist/commands/init/tools/install-state.d.ts.map +1 -1
  64. package/dist/commands/init/tools/install-state.js +11 -0
  65. package/dist/commands/init/tools/project-guidance.d.ts +49 -0
  66. package/dist/commands/init/tools/project-guidance.d.ts.map +1 -0
  67. package/dist/commands/init/tools/project-guidance.js +144 -0
  68. package/dist/commands/init/tools/workflows/index.d.ts +7 -0
  69. package/dist/commands/init/tools/workflows/index.d.ts.map +1 -1
  70. package/dist/commands/init/tools/workflows/index.js +160 -7
  71. package/dist/commands/pjm/index.d.ts.map +1 -1
  72. package/dist/commands/pjm/index.js +28 -3
  73. package/dist/commands/pjm/init.d.ts +5 -0
  74. package/dist/commands/pjm/init.d.ts.map +1 -1
  75. package/dist/commands/pjm/init.js +19 -3
  76. package/dist/commands/project/archive/archive-utils.d.ts +8 -1
  77. package/dist/commands/project/archive/archive-utils.d.ts.map +1 -1
  78. package/dist/commands/project/archive/archive-utils.js +147 -50
  79. package/dist/commands/project/archive/push-runner.d.ts.map +1 -1
  80. package/dist/commands/project/archive/push-runner.js +15 -2
  81. package/dist/commands/project/dispatch/index.d.ts +10 -0
  82. package/dist/commands/project/dispatch/index.d.ts.map +1 -0
  83. package/dist/commands/project/dispatch/index.js +137 -0
  84. package/dist/commands/project/dispatch/record.d.ts +91 -0
  85. package/dist/commands/project/dispatch/record.d.ts.map +1 -0
  86. package/dist/commands/project/dispatch/record.js +402 -0
  87. package/dist/commands/project/index.d.ts.map +1 -1
  88. package/dist/commands/project/index.js +2 -0
  89. package/dist/commands/project/links/compute.d.ts +1 -0
  90. package/dist/commands/project/links/compute.d.ts.map +1 -1
  91. package/dist/commands/project/links/compute.js +5 -4
  92. package/dist/commands/project/links/index.d.ts +9 -0
  93. package/dist/commands/project/links/index.d.ts.map +1 -1
  94. package/dist/commands/project/links/index.js +71 -2
  95. package/dist/commands/project/list.d.ts +10 -2
  96. package/dist/commands/project/list.d.ts.map +1 -1
  97. package/dist/commands/project/list.js +205 -29
  98. package/dist/commands/project/open/index.d.ts +2 -0
  99. package/dist/commands/project/open/index.d.ts.map +1 -1
  100. package/dist/commands/project/open/index.js +16 -1
  101. package/dist/commands/project/prune/index.d.ts +10 -4
  102. package/dist/commands/project/prune/index.d.ts.map +1 -1
  103. package/dist/commands/project/prune/index.js +85 -15
  104. package/dist/commands/project/pull/index.d.ts +4 -0
  105. package/dist/commands/project/pull/index.d.ts.map +1 -1
  106. package/dist/commands/project/pull/index.js +82 -3
  107. package/dist/commands/project/sync/git.d.ts +9 -0
  108. package/dist/commands/project/sync/git.d.ts.map +1 -1
  109. package/dist/commands/project/sync/git.js +18 -0
  110. package/dist/commands/project/sync/ref-sync.d.ts +16 -1
  111. package/dist/commands/project/sync/ref-sync.d.ts.map +1 -1
  112. package/dist/commands/project/sync/ref-sync.js +288 -17
  113. package/dist/commands/project/sync/resolve-target.d.ts +10 -0
  114. package/dist/commands/project/sync/resolve-target.d.ts.map +1 -1
  115. package/dist/commands/project/sync/resolve-target.js +56 -1
  116. package/dist/commands/providers/inspect/inspect.d.ts +8 -1
  117. package/dist/commands/providers/inspect/inspect.d.ts.map +1 -1
  118. package/dist/commands/providers/inspect/inspect.js +146 -26
  119. package/dist/commands/providers/list/list.d.ts +8 -1
  120. package/dist/commands/providers/list/list.d.ts.map +1 -1
  121. package/dist/commands/providers/list/list.js +139 -23
  122. package/dist/commands/providers/providers.types.d.ts +25 -1
  123. package/dist/commands/providers/providers.types.d.ts.map +1 -1
  124. package/dist/commands/providers/set/index.d.ts +9 -1
  125. package/dist/commands/providers/set/index.d.ts.map +1 -1
  126. package/dist/commands/providers/set/index.js +43 -33
  127. package/dist/commands/remove/skill/remove-skill.d.ts.map +1 -1
  128. package/dist/commands/remove/skill/remove-skill.js +11 -0
  129. package/dist/commands/shared/agents-md.d.ts +42 -14
  130. package/dist/commands/shared/agents-md.d.ts.map +1 -1
  131. package/dist/commands/shared/agents-md.js +364 -70
  132. package/dist/commands/shared/project-scope.d.ts +2 -0
  133. package/dist/commands/shared/project-scope.d.ts.map +1 -1
  134. package/dist/commands/shared/project-scope.js +5 -0
  135. package/dist/commands/state/generate.d.ts +2 -0
  136. package/dist/commands/state/generate.d.ts.map +1 -1
  137. package/dist/commands/state/generate.js +93 -28
  138. package/dist/commands/status/index.d.ts +8 -3
  139. package/dist/commands/status/index.d.ts.map +1 -1
  140. package/dist/commands/status/index.js +147 -32
  141. package/dist/commands/sync/apply.d.ts.map +1 -1
  142. package/dist/commands/sync/apply.js +220 -29
  143. package/dist/commands/sync/dry-run.d.ts.map +1 -1
  144. package/dist/commands/sync/dry-run.js +30 -6
  145. package/dist/commands/sync/index.d.ts +17 -2
  146. package/dist/commands/sync/index.d.ts.map +1 -1
  147. package/dist/commands/sync/index.js +106 -41
  148. package/dist/commands/sync/sync.types.d.ts +39 -2
  149. package/dist/commands/sync/sync.types.d.ts.map +1 -1
  150. package/dist/commands/sync/sync.utils.d.ts +5 -1
  151. package/dist/commands/sync/sync.utils.d.ts.map +1 -1
  152. package/dist/commands/sync/sync.utils.js +73 -0
  153. package/dist/commands/tools/info/info-tool.d.ts +2 -0
  154. package/dist/commands/tools/info/info-tool.d.ts.map +1 -1
  155. package/dist/commands/tools/info/info-tool.js +34 -12
  156. package/dist/commands/tools/install/index.d.ts.map +1 -1
  157. package/dist/commands/tools/install/index.js +19 -3
  158. package/dist/commands/tools/list/list-tools.d.ts +2 -0
  159. package/dist/commands/tools/list/list-tools.d.ts.map +1 -1
  160. package/dist/commands/tools/list/list-tools.js +36 -7
  161. package/dist/commands/tools/remove/index.d.ts.map +1 -1
  162. package/dist/commands/tools/remove/index.js +150 -21
  163. package/dist/commands/tools/remove/remove-tools.d.ts +20 -0
  164. package/dist/commands/tools/remove/remove-tools.d.ts.map +1 -1
  165. package/dist/commands/tools/remove/remove-tools.js +122 -1
  166. package/dist/commands/tools/shared/format-pack-inventory.d.ts +19 -0
  167. package/dist/commands/tools/shared/format-pack-inventory.d.ts.map +1 -1
  168. package/dist/commands/tools/shared/format-pack-inventory.js +109 -0
  169. package/dist/commands/tools/shared/pack-evidence.d.ts +66 -0
  170. package/dist/commands/tools/shared/pack-evidence.d.ts.map +1 -0
  171. package/dist/commands/tools/shared/pack-evidence.js +132 -0
  172. package/dist/commands/tools/shared/pack-inventory.d.ts +1 -0
  173. package/dist/commands/tools/shared/pack-inventory.d.ts.map +1 -1
  174. package/dist/commands/tools/shared/pack-lifecycle-outcome.d.ts +44 -0
  175. package/dist/commands/tools/shared/pack-lifecycle-outcome.d.ts.map +1 -0
  176. package/dist/commands/tools/shared/pack-lifecycle-outcome.js +91 -0
  177. package/dist/commands/tools/update/index.d.ts.map +1 -1
  178. package/dist/commands/tools/update/index.js +74 -4
  179. package/dist/commands/tools/update/update-tools.d.ts +5 -0
  180. package/dist/commands/tools/update/update-tools.d.ts.map +1 -1
  181. package/dist/commands/tools/update/update-tools.js +64 -2
  182. package/dist/config/oat-config.d.ts +5 -0
  183. package/dist/config/oat-config.d.ts.map +1 -1
  184. package/dist/config/oat-config.js +17 -5
  185. package/dist/config/user-sync-config.d.ts +1 -0
  186. package/dist/config/user-sync-config.d.ts.map +1 -1
  187. package/dist/config/user-sync-config.js +8 -0
  188. package/dist/drift/detector.d.ts +2 -2
  189. package/dist/drift/detector.d.ts.map +1 -1
  190. package/dist/drift/detector.js +18 -0
  191. package/dist/drift/strays.d.ts.map +1 -1
  192. package/dist/drift/strays.js +5 -0
  193. package/dist/engine/collection-sync.d.ts +14 -0
  194. package/dist/engine/collection-sync.d.ts.map +1 -0
  195. package/dist/engine/collection-sync.js +282 -0
  196. package/dist/engine/compute-plan.d.ts +8 -1
  197. package/dist/engine/compute-plan.d.ts.map +1 -1
  198. package/dist/engine/compute-plan.js +303 -12
  199. package/dist/engine/engine.types.d.ts +61 -0
  200. package/dist/engine/engine.types.d.ts.map +1 -1
  201. package/dist/engine/execute-plan.d.ts +22 -2
  202. package/dist/engine/execute-plan.d.ts.map +1 -1
  203. package/dist/engine/execute-plan.js +459 -27
  204. package/dist/engine/provider-path-safety.d.ts +1 -0
  205. package/dist/engine/provider-path-safety.d.ts.map +1 -1
  206. package/dist/engine/provider-path-safety.js +15 -0
  207. package/dist/engine/scanner.d.ts +2 -2
  208. package/dist/engine/scanner.d.ts.map +1 -1
  209. package/dist/engine/scanner.js +21 -4
  210. package/dist/fs/io.d.ts +67 -0
  211. package/dist/fs/io.d.ts.map +1 -1
  212. package/dist/fs/io.js +536 -3
  213. package/dist/manifest/index.d.ts +2 -2
  214. package/dist/manifest/index.d.ts.map +1 -1
  215. package/dist/manifest/index.js +1 -1
  216. package/dist/manifest/manager.d.ts +2 -2
  217. package/dist/manifest/manager.d.ts.map +1 -1
  218. package/dist/manifest/manager.js +20 -3
  219. package/dist/manifest/manifest.types.d.ts +704 -13
  220. package/dist/manifest/manifest.types.d.ts.map +1 -1
  221. package/dist/manifest/manifest.types.js +131 -15
  222. package/dist/providers/codex/codec/sync-extension.d.ts.map +1 -1
  223. package/dist/providers/codex/codec/sync-extension.js +41 -10
  224. package/dist/providers/cursor/codec/sync-extension.d.ts.map +1 -1
  225. package/dist/providers/cursor/codec/sync-extension.js +41 -10
  226. package/dist/providers/identity/absolute-paths.d.ts +68 -0
  227. package/dist/providers/identity/absolute-paths.d.ts.map +1 -0
  228. package/dist/providers/identity/absolute-paths.js +144 -0
  229. package/dist/providers/identity/claude-runtime-observation.d.ts +105 -0
  230. package/dist/providers/identity/claude-runtime-observation.d.ts.map +1 -0
  231. package/dist/providers/identity/claude-runtime-observation.fixtures.d.ts +40 -0
  232. package/dist/providers/identity/claude-runtime-observation.fixtures.d.ts.map +1 -0
  233. package/dist/providers/identity/claude-runtime-observation.fixtures.js +146 -0
  234. package/dist/providers/identity/claude-runtime-observation.js +229 -0
  235. package/dist/providers/identity/codex-runtime-observation.d.ts +75 -0
  236. package/dist/providers/identity/codex-runtime-observation.d.ts.map +1 -0
  237. package/dist/providers/identity/codex-runtime-observation.fixtures.d.ts +37 -0
  238. package/dist/providers/identity/codex-runtime-observation.fixtures.d.ts.map +1 -0
  239. package/dist/providers/identity/codex-runtime-observation.fixtures.js +539 -0
  240. package/dist/providers/identity/codex-runtime-observation.js +255 -0
  241. package/dist/providers/identity/generic-dispatch-record.d.ts +313 -0
  242. package/dist/providers/identity/generic-dispatch-record.d.ts.map +1 -0
  243. package/dist/providers/identity/generic-dispatch-record.js +539 -0
  244. package/dist/providers/identity/oat-dispatch-record.d.ts +1964 -0
  245. package/dist/providers/identity/oat-dispatch-record.d.ts.map +1 -0
  246. package/dist/providers/identity/oat-dispatch-record.js +691 -0
  247. package/dist/providers/identity/runtime-observation.d.ts +100 -0
  248. package/dist/providers/identity/runtime-observation.d.ts.map +1 -0
  249. package/dist/providers/identity/runtime-observation.js +169 -0
  250. package/dist/providers/shared/index.d.ts +2 -0
  251. package/dist/providers/shared/index.d.ts.map +1 -1
  252. package/dist/providers/shared/index.js +1 -0
  253. package/dist/providers/shared/materialization-extension.d.ts +12 -0
  254. package/dist/providers/shared/materialization-extension.d.ts.map +1 -1
  255. package/dist/providers/shared/registry.d.ts +69 -0
  256. package/dist/providers/shared/registry.d.ts.map +1 -0
  257. package/dist/providers/shared/registry.js +252 -0
  258. package/dist/providers/shared/restart-adviser.d.ts +26 -0
  259. package/dist/providers/shared/restart-adviser.d.ts.map +1 -0
  260. package/dist/providers/shared/restart-adviser.js +96 -0
  261. package/dist/shared/types.d.ts +4 -0
  262. package/dist/shared/types.d.ts.map +1 -1
  263. package/package.json +2 -2
@@ -0,0 +1,539 @@
1
+ import { serializeJsonDocument } from '../../fs/io.js';
2
+ import { z } from 'zod';
3
+ import { assertNoAbsolutePath, redactAbsolutePathsDeep, } from './absolute-paths.js';
4
+ /**
5
+ * Length bounds for caller-authored text. `identifier` covers names, selectors,
6
+ * routes, and statuses; `reason` covers a single explanatory sentence; `prose`
7
+ * covers the longest legitimate free-form field. Nothing in a dispatch record
8
+ * is a narrative document.
9
+ */
10
+ const MAX_IDENTIFIER_LENGTH = 256;
11
+ const MAX_REASON_LENGTH = 512;
12
+ const MAX_PROSE_LENGTH = 1024;
13
+ const identifier = () => z.string().min(1).max(MAX_IDENTIFIER_LENGTH);
14
+ const reasonText = () => z.string().min(1).max(MAX_REASON_LENGTH);
15
+ const proseText = () => z.string().min(1).max(MAX_PROSE_LENGTH);
16
+ const requestIdSchema = z
17
+ .string()
18
+ .regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/, 'request_id must be a stable contained identifier');
19
+ const catalogSnapshotSchema = z
20
+ .object({
21
+ id: identifier(),
22
+ source: identifier(),
23
+ observed_at: z.string().datetime(),
24
+ })
25
+ .strict();
26
+ const genericFallbackSchema = z
27
+ .object({
28
+ mode: identifier(),
29
+ target: identifier().optional(),
30
+ allow_below_task_class_floor: z.boolean().optional(),
31
+ })
32
+ .strict();
33
+ const optionalSelector = identifier().nullable();
34
+ /**
35
+ * Every generic container field is validated as a closed, bounded JSON
36
+ * projection so prompt bodies, transcripts, and other free-form content cannot
37
+ * ride into a durable journal through an unbounded `unknown` value. `payload`
38
+ * carries configured sandbox/tool controls; `candidates_considered`,
39
+ * `configured_invocation_evidence`, `continuation_events`, `diagnostics`, and
40
+ * `escalate_when` carry short references and identifiers, not narrative text.
41
+ *
42
+ * The projection bounds four independent axes so that no single one can be
43
+ * defeated by trading against another: nesting depth, per-string length, node
44
+ * count (which closes chunking into many short strings), and serialized bytes
45
+ * per field. `assertBoundedDispatchRecordSize` adds a whole-record ceiling on
46
+ * top, so the sum of every bounded and unbounded field is capped too.
47
+ */
48
+ const BOUNDED_PROJECTION_FIELDS = [
49
+ 'payload',
50
+ 'candidates_considered',
51
+ 'configured_invocation_evidence',
52
+ 'continuation_events',
53
+ 'diagnostics',
54
+ 'escalate_when',
55
+ ];
56
+ const MAX_PROJECTION_DEPTH = 4;
57
+ const MAX_PROJECTION_STRING_LENGTH = 512;
58
+ const MAX_PROJECTION_NODES = 512;
59
+ const MAX_PROJECTION_BYTES = 16 * 1024;
60
+ const MAX_RECORD_BYTES = 64 * 1024;
61
+ function boundedProjectionViolation(value, path, depth) {
62
+ if (value === undefined) {
63
+ // An absent optional field has nothing to project.
64
+ return null;
65
+ }
66
+ if (value === null ||
67
+ typeof value === 'boolean' ||
68
+ typeof value === 'number') {
69
+ return null;
70
+ }
71
+ if (typeof value === 'string') {
72
+ return value.length > MAX_PROJECTION_STRING_LENGTH
73
+ ? `${path} exceeds the ${MAX_PROJECTION_STRING_LENGTH}-character control projection limit`
74
+ : null;
75
+ }
76
+ if (typeof value !== 'object') {
77
+ return `${path} is not a JSON control value`;
78
+ }
79
+ if (depth >= MAX_PROJECTION_DEPTH) {
80
+ return `${path} exceeds the ${MAX_PROJECTION_DEPTH}-level control projection depth`;
81
+ }
82
+ if (Array.isArray(value)) {
83
+ for (const [index, entry] of value.entries()) {
84
+ const violation = boundedProjectionViolation(entry, `${path}[${index}]`, depth + 1);
85
+ if (violation)
86
+ return violation;
87
+ }
88
+ return null;
89
+ }
90
+ for (const [key, entry] of Object.entries(value)) {
91
+ const violation = boundedProjectionViolation(entry, `${path}.${key}`, depth + 1);
92
+ if (violation)
93
+ return violation;
94
+ }
95
+ return null;
96
+ }
97
+ function serializedByteLength(value) {
98
+ return new TextEncoder().encode(JSON.stringify(value) ?? 'null').length;
99
+ }
100
+ /**
101
+ * Bytes as published. The whole-record ceiling constrains a file on disk, so it
102
+ * measures the writer's own serialization rather than a compact approximation
103
+ * of it. Per-field projection bounds below stay compact: they bound the shape
104
+ * of a field, not the size of a file.
105
+ */
106
+ function publishedByteLength(value) {
107
+ return new TextEncoder().encode(serializeJsonDocument(value)).length;
108
+ }
109
+ function projectionNodeCount(value) {
110
+ if (value === null || typeof value !== 'object')
111
+ return 1;
112
+ if (Array.isArray(value)) {
113
+ return value.reduce((total, entry) => total + projectionNodeCount(entry), 1);
114
+ }
115
+ return Object.values(value).reduce((total, entry) => total + projectionNodeCount(entry), 1);
116
+ }
117
+ /**
118
+ * Breadth and aggregate bound. Depth and per-string limits alone are defeated
119
+ * by chunking one large body into many small values, so a field is also capped
120
+ * by how many nodes it contains and how many bytes it serializes to.
121
+ */
122
+ function projectionAggregateViolation(value, path) {
123
+ if (value === undefined)
124
+ return null;
125
+ const nodes = projectionNodeCount(value);
126
+ if (nodes > MAX_PROJECTION_NODES) {
127
+ return `${path} holds ${nodes} values, above the ${MAX_PROJECTION_NODES}-value control projection limit`;
128
+ }
129
+ const bytes = serializedByteLength(value);
130
+ if (bytes > MAX_PROJECTION_BYTES) {
131
+ return `${path} serializes to ${bytes} bytes, above the ${MAX_PROJECTION_BYTES}-byte control projection limit`;
132
+ }
133
+ return null;
134
+ }
135
+ /**
136
+ * Whole-record ceiling, applied at every parse entry point so no combination
137
+ * of individually legal fields can produce an unbounded journal revision.
138
+ */
139
+ export function assertBoundedDispatchRecordSize(value) {
140
+ const bytes = publishedByteLength(value);
141
+ if (bytes > MAX_RECORD_BYTES) {
142
+ throw new Error(`A dispatch record serializes to ${bytes} bytes, above the ${MAX_RECORD_BYTES}-byte record limit.`);
143
+ }
144
+ }
145
+ const TASK_CLASS_FIELDS = [
146
+ 'task_class',
147
+ 'model_class_floor',
148
+ 'classification_source',
149
+ 'classification_reason',
150
+ 'floor_satisfaction',
151
+ ];
152
+ export const genericDispatchRecordSchema = z
153
+ .object({
154
+ request_id: requestIdSchema,
155
+ caller: identifier(),
156
+ scope: identifier(),
157
+ objective: proseText(),
158
+ action: identifier(),
159
+ role_name: identifier(),
160
+ role_class: identifier(),
161
+ provider: identifier(),
162
+ dispatch_context: identifier(),
163
+ dispatch_policy: identifier().nullable().optional(),
164
+ dispatch_ceiling: identifier().nullable().optional(),
165
+ catalog_snapshot: catalogSnapshotSchema,
166
+ authority: identifier(),
167
+ role_selector: optionalSelector,
168
+ model_selector: optionalSelector,
169
+ model_selector_granularity: identifier().nullable(),
170
+ effort_selector: optionalSelector,
171
+ reasoning_mode_selector: optionalSelector.optional(),
172
+ service_tier_selector: optionalSelector.optional(),
173
+ guidance_reference: identifier().optional(),
174
+ guidance_version: identifier().optional(),
175
+ guidance_verified_at: identifier().optional(),
176
+ guidance_status: z.enum(['fresh', 'review-required', 'stale']).optional(),
177
+ selection_source: z.enum([
178
+ 'native-default',
179
+ 'policy-resolved',
180
+ 'explicit-user',
181
+ ]),
182
+ candidates_considered: z.array(identifier()),
183
+ selection_reason: z.enum([
184
+ 'native-catalog',
185
+ 'native-catalog-unsatisfying',
186
+ 'pre-start-rejection',
187
+ 'inherit',
188
+ 'gate-target',
189
+ ]),
190
+ selected_route: identifier(),
191
+ deadline_seconds: z.number().int().nonnegative(),
192
+ retry_limit: z.number().int().nonnegative(),
193
+ payload: z.record(z.unknown()),
194
+ launch_status: z.enum(['planned', 'accepted', 'blocked-before-start']),
195
+ child_outcome: identifier().nullable(),
196
+ configured_invocation_evidence: z.array(z.unknown()),
197
+ runtime_confirmation: identifier(),
198
+ diagnostics: z.array(z.string()),
199
+ continuation_events: z.array(z.unknown()),
200
+ fallback: genericFallbackSchema.optional(),
201
+ task_class: z
202
+ .enum([
203
+ 'mechanical-recon',
204
+ 'intelligent-recon',
205
+ 'default-implementation',
206
+ 'hard-reasoning',
207
+ 'consequential',
208
+ ])
209
+ .optional(),
210
+ model_class_floor: identifier().optional(),
211
+ classification_source: z.literal('caller').optional(),
212
+ classification_reason: reasonText().optional(),
213
+ floor_satisfaction: z.enum(['satisfied', 'unsatisfied']).optional(),
214
+ authorization_scope: identifier().optional(),
215
+ expected_output: proseText().optional(),
216
+ verification_evidence: proseText().optional(),
217
+ escalate_when: z.array(reasonText()).optional(),
218
+ })
219
+ .strict()
220
+ .superRefine((record, context) => {
221
+ if (record.launch_status === 'accepted' && record.child_outcome === null) {
222
+ context.addIssue({
223
+ code: z.ZodIssueCode.custom,
224
+ message: 'An accepted dispatch must report a child outcome.',
225
+ path: ['child_outcome'],
226
+ });
227
+ }
228
+ for (const field of BOUNDED_PROJECTION_FIELDS) {
229
+ const violation = boundedProjectionViolation(record[field], field, 1) ??
230
+ projectionAggregateViolation(record[field], field);
231
+ if (violation) {
232
+ context.addIssue({
233
+ code: z.ZodIssueCode.custom,
234
+ message: `Dispatch ${field} must stay inside the closed control projection: ${violation}.`,
235
+ path: [field],
236
+ });
237
+ }
238
+ }
239
+ const presentClassFields = TASK_CLASS_FIELDS.filter((field) => record[field] !== undefined);
240
+ if (presentClassFields.length !== 0 &&
241
+ presentClassFields.length !== TASK_CLASS_FIELDS.length) {
242
+ const missing = TASK_CLASS_FIELDS.filter((field) => record[field] === undefined);
243
+ context.addIssue({
244
+ code: z.ZodIssueCode.custom,
245
+ message: `A class-constrained dispatch record requires every task-class field; missing ${missing.join(', ')}. A legacy record omits all five.`,
246
+ path: [missing[0] ?? 'task_class'],
247
+ });
248
+ return;
249
+ }
250
+ if (presentClassFields.length === 0) {
251
+ return;
252
+ }
253
+ if (record.classification_source !== 'caller') {
254
+ context.addIssue({
255
+ code: z.ZodIssueCode.custom,
256
+ message: 'Task-class classification_source must be caller.',
257
+ path: ['classification_source'],
258
+ });
259
+ }
260
+ if (record.model_class_floor !== record.task_class) {
261
+ context.addIssue({
262
+ code: z.ZodIssueCode.custom,
263
+ message: 'model_class_floor must equal the requested task_class; a floor may never be recorded below the requested class.',
264
+ path: ['model_class_floor'],
265
+ });
266
+ }
267
+ });
268
+ /**
269
+ * Latin look-alikes from other scripts, folded to their Latin counterpart so a
270
+ * homoglyph spelling classifies as the token it imitates. This map is a
271
+ * convenience for common cases, not the safety boundary: anything it does not
272
+ * cover is reported as unrecognized and fails closed.
273
+ */
274
+ const CONFUSABLE_FOLDING = new Map([
275
+ ['\u0430', 'a'],
276
+ ['\u0432', 'b'],
277
+ ['\u0441', 'c'],
278
+ ['\u0501', 'd'],
279
+ ['\u0435', 'e'],
280
+ ['\u0433', 'r'],
281
+ ['\u043d', 'h'],
282
+ ['\u0456', 'i'],
283
+ ['\u0458', 'j'],
284
+ ['\u043a', 'k'],
285
+ ['\u043c', 'm'],
286
+ ['\u043e', 'o'],
287
+ ['\u0440', 'p'],
288
+ ['\u0455', 's'],
289
+ ['\u0442', 't'],
290
+ ['\u0443', 'y'],
291
+ ['\u0445', 'x'],
292
+ ['\u04bb', 'h'],
293
+ ['\u03b1', 'a'],
294
+ ['\u03b5', 'e'],
295
+ ['\u03b3', 'y'],
296
+ ['\u03b9', 'i'],
297
+ ['\u03ba', 'k'],
298
+ ['\u03bc', 'm'],
299
+ ['\u03bd', 'v'],
300
+ ['\u03bf', 'o'],
301
+ ['\u03c1', 'p'],
302
+ ['\u03c4', 't'],
303
+ ['\u03c7', 'x'],
304
+ ['\u0261', 'g'],
305
+ ['\u0131', 'i'],
306
+ ['\u026a', 'i'],
307
+ ['\u0269', 'i'],
308
+ ['\u2113', 'l'],
309
+ ['\u0578', 'n'],
310
+ ]);
311
+ /**
312
+ * Recognized separators: ASCII punctuation and space. Anything outside
313
+ * `[a-z0-9]` and this set survives folding as an unrecognized character.
314
+ */
315
+ const RECOGNIZED_SEPARATOR = /[\x20-\x2f\x3a-\x40\x5b-\x60\x7b-\x7e]/;
316
+ /**
317
+ * Fold a key to its classified token.
318
+ *
319
+ * NFKC collapses full-width and mathematical alphanumerics; case folding
320
+ * collapses spelling; NFKD plus combining-mark removal collapses diacritics
321
+ * (`pàssword`); the confusable map collapses cross-script look-alikes
322
+ * (Cyrillic-a `apiKey`). Whatever survives that and is neither `[a-z0-9]` nor a
323
+ * recognized separator is reported as unrecognized, because silently deleting
324
+ * it would unmask nothing and let spellings such as small-capital `ᴘᴀssᴡᴏʀᴅ`
325
+ * evade the family list. Callers fail closed on it rather than growing the map
326
+ * indefinitely.
327
+ */
328
+ function foldDispatchKey(key) {
329
+ const decomposed = key
330
+ .normalize('NFKC')
331
+ .toLowerCase()
332
+ .normalize('NFKD')
333
+ .replace(/\p{M}/gu, '');
334
+ let normalized = '';
335
+ let unrecognized = false;
336
+ for (const character of decomposed) {
337
+ const folded = CONFUSABLE_FOLDING.get(character) ?? character;
338
+ if (/^[a-z0-9]$/.test(folded)) {
339
+ normalized += folded;
340
+ }
341
+ else if (!RECOGNIZED_SEPARATOR.test(folded)) {
342
+ unrecognized = true;
343
+ }
344
+ }
345
+ return { normalized, unrecognized };
346
+ }
347
+ export function normalizeDispatchKey(key) {
348
+ return foldDispatchKey(key).normalized;
349
+ }
350
+ /**
351
+ * Credential, token, prompt/message, transcript, and role-content families.
352
+ * Every entry is checked as a substring of the normalized key, so no entry may
353
+ * be a substring of a legitimate record key (for example `content` cannot live
354
+ * here because `contentDigest` is legitimate).
355
+ */
356
+ const SENSITIVE_KEY_FAMILIES = [
357
+ 'credential',
358
+ 'secret',
359
+ 'password',
360
+ 'passwd',
361
+ 'passphrase',
362
+ 'token',
363
+ 'apikey',
364
+ 'accesskey',
365
+ 'privatekey',
366
+ 'sessionkey',
367
+ 'signingkey',
368
+ 'encryptionkey',
369
+ 'bearer',
370
+ 'cookie',
371
+ 'prompt',
372
+ 'message',
373
+ 'transcript',
374
+ 'conversation',
375
+ 'chatlog',
376
+ 'chathistory',
377
+ 'rolecontent',
378
+ 'systemcontent',
379
+ 'usercontent',
380
+ 'assistantcontent',
381
+ 'messagecontent',
382
+ 'instructiontext',
383
+ 'instructionbody',
384
+ 'instruction',
385
+ 'assistanttext',
386
+ 'usertext',
387
+ 'systemtext',
388
+ 'pwd',
389
+ 'privkey',
390
+ 'sshkey',
391
+ 'gpgkey',
392
+ 'cred',
393
+ 'oauth',
394
+ 'session',
395
+ ];
396
+ /**
397
+ * Evidence keys that legitimately collide with a denied family. `roleInstructions`
398
+ * carries canonical role identity (dependency, tier, path, version, digest) and
399
+ * never role content, so it is admitted explicitly rather than by relying on the
400
+ * family list happening to miss it.
401
+ */
402
+ const SENSITIVE_KEY_ALLOWLIST = new Set([
403
+ 'roleinstructions',
404
+ ]);
405
+ /**
406
+ * Generic content words that are only sensitive as a complete key. They are
407
+ * matched exactly so that `contentDigest`, `expected_output`, and
408
+ * `authorization_scope` stay legal.
409
+ */
410
+ const SENSITIVE_KEY_EXACT = new Set([
411
+ 'auth',
412
+ 'pass',
413
+ 'authorization',
414
+ 'body',
415
+ 'content',
416
+ 'contents',
417
+ 'jwt',
418
+ 'key',
419
+ 'keys',
420
+ 'otp',
421
+ 'pat',
422
+ 'pin',
423
+ 'salt',
424
+ 'session',
425
+ 'signature',
426
+ 'text',
427
+ ]);
428
+ /**
429
+ * Best-effort second layer only. Key-family classification above is the primary
430
+ * defence; value shapes change constantly and this list will never be complete.
431
+ */
432
+ const SENSITIVE_VALUE_PATTERNS = [
433
+ /authorization\s*:\s*bearer\s+\S+/i,
434
+ /-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----/i,
435
+ /\bsk-[A-Za-z0-9_-]{16,}/,
436
+ /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{20,}/,
437
+ /\bgithub_pat_[A-Za-z0-9_]{20,}/,
438
+ /\bxox[abprs]-[A-Za-z0-9-]{10,}/,
439
+ /\bAKIA[0-9A-Z]{16}\b/,
440
+ /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\./,
441
+ /\bAIzaSy[A-Za-z0-9_-]{20,}/,
442
+ /\bglpat-[A-Za-z0-9_-]{16,}/,
443
+ /\bdop_v1_[a-f0-9]{32,}/,
444
+ /\bnpm_[A-Za-z0-9]{30,}/,
445
+ /\b[a-z][a-z0-9+.-]*:\/\/[^\s:/?#@]+:[^\s:/?#@]+@/i,
446
+ ];
447
+ export function isSensitiveDispatchKey(key) {
448
+ const { normalized, unrecognized } = foldDispatchKey(key);
449
+ // Fail closed: a key this normalizer cannot fully account for is treated as
450
+ // sensitive rather than classified on a partial token.
451
+ if (unrecognized)
452
+ return true;
453
+ if (normalized === '')
454
+ return false;
455
+ if (SENSITIVE_KEY_ALLOWLIST.has(normalized))
456
+ return false;
457
+ if (SENSITIVE_KEY_EXACT.has(normalized))
458
+ return true;
459
+ return SENSITIVE_KEY_FAMILIES.some((family) => normalized.includes(family));
460
+ }
461
+ export function assertNoSensitiveDispatchContent(value, path = '<record>') {
462
+ if (typeof value === 'string') {
463
+ if (SENSITIVE_VALUE_PATTERNS.some((pattern) => pattern.test(value))) {
464
+ throw new Error(`Sensitive dispatch content is forbidden at ${path}.`);
465
+ }
466
+ return;
467
+ }
468
+ if (Array.isArray(value)) {
469
+ value.forEach((entry, index) => assertNoSensitiveDispatchContent(entry, `${path}[${index}]`));
470
+ return;
471
+ }
472
+ if (value === null || typeof value !== 'object') {
473
+ return;
474
+ }
475
+ for (const [key, entry] of Object.entries(value)) {
476
+ if (isSensitiveDispatchKey(key)) {
477
+ throw new Error(`Sensitive dispatch content is forbidden at ${path}.${key}.`);
478
+ }
479
+ assertNoSensitiveDispatchContent(entry, `${path}.${key}`);
480
+ }
481
+ }
482
+ /**
483
+ * Fields where an absolute path is incidental content rather than a malformed
484
+ * identifier: free-form prose a human wrote, and the nested evidence containers
485
+ * that quote commands and diagnostics. A path here is redacted, because the
486
+ * surrounding text is still meaningful without it.
487
+ *
488
+ * Every other field in the record is identity or control — a caller, a scope, a
489
+ * selector, a route, a guidance reference. A path is never a legitimate value
490
+ * for those, and rewriting one in place would silently corrupt the identifier
491
+ * it claims to be, so those are rejected instead.
492
+ */
493
+ const PATH_REDACTED_FIELDS = [
494
+ 'objective',
495
+ 'expected_output',
496
+ 'verification_evidence',
497
+ 'classification_reason',
498
+ 'escalate_when',
499
+ 'payload',
500
+ 'diagnostics',
501
+ 'configured_invocation_evidence',
502
+ 'continuation_events',
503
+ ];
504
+ /**
505
+ * The persistence-boundary path sanitizer for the generic record.
506
+ *
507
+ * NFR1 is enforced here rather than in each producer, for the same reason
508
+ * redaction became a boundary in Phase 6: a guarantee implemented as a
509
+ * per-producer obligation regresses the moment a call site is added.
510
+ */
511
+ export function identityFieldsOf(record) {
512
+ const redacted = new Set(PATH_REDACTED_FIELDS);
513
+ return Object.fromEntries(Object.entries(record).filter(([key]) => !redacted.has(key)));
514
+ }
515
+ export function sanitizeDispatchRecordPaths(record) {
516
+ const redacted = new Set(PATH_REDACTED_FIELDS);
517
+ const sanitized = {};
518
+ for (const [key, value] of Object.entries(record)) {
519
+ if (redacted.has(key)) {
520
+ sanitized[key] = redactAbsolutePathsDeep(value);
521
+ continue;
522
+ }
523
+ assertNoAbsolutePath(value, key);
524
+ sanitized[key] = value;
525
+ }
526
+ return sanitized;
527
+ }
528
+ export function parseGenericDispatchRecord(value) {
529
+ assertNoSensitiveDispatchContent(value);
530
+ assertBoundedDispatchRecordSize(value);
531
+ const sanitized = sanitizeDispatchRecordPaths(genericDispatchRecordSchema.parse(value));
532
+ // Redaction is a transform that runs after validation, and `<redacted-path>`
533
+ // is far longer than a short path like `/a`, so it can only grow a record.
534
+ // Bounds are therefore re-run on the transform's output: a postcondition
535
+ // checked against pre-transform data is not a postcondition.
536
+ const revalidated = genericDispatchRecordSchema.parse(sanitized);
537
+ assertBoundedDispatchRecordSize(revalidated);
538
+ return revalidated;
539
+ }