@topogram/cli 0.3.63 → 0.3.65

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 (344) hide show
  1. package/package.json +1 -1
  2. package/src/adoption/plan/index.js +703 -0
  3. package/src/adoption/plan.d.ts +6 -0
  4. package/src/adoption/plan.js +12 -703
  5. package/src/adoption/reporting.d.ts +10 -0
  6. package/src/adoption/review-groups.d.ts +6 -0
  7. package/src/agent-brief.d.ts +3 -0
  8. package/src/agent-brief.js +495 -0
  9. package/src/agent-ops/query-builders/auth.js +375 -0
  10. package/src/agent-ops/query-builders/change-risk/change-plan.js +123 -0
  11. package/src/agent-ops/query-builders/change-risk/import-plan.js +49 -0
  12. package/src/agent-ops/query-builders/change-risk/maintained.js +286 -0
  13. package/src/agent-ops/query-builders/change-risk/review-packets.js +123 -0
  14. package/src/agent-ops/query-builders/change-risk/risk.js +189 -0
  15. package/src/agent-ops/query-builders/change-risk.js +25 -0
  16. package/src/agent-ops/query-builders/common.js +149 -0
  17. package/src/agent-ops/query-builders/maintained-risk.js +539 -0
  18. package/src/agent-ops/query-builders/maintained-shared.js +120 -0
  19. package/src/agent-ops/query-builders/multi-agent.js +547 -0
  20. package/src/agent-ops/query-builders/projection-impacts.js +514 -0
  21. package/src/agent-ops/query-builders/work-packets.js +417 -0
  22. package/src/agent-ops/query-builders/workflow-context-shared.js +300 -0
  23. package/src/agent-ops/query-builders/workflow-context.js +398 -0
  24. package/src/agent-ops/query-builders/workflow-presets-core.js +676 -0
  25. package/src/agent-ops/query-builders/workflow-presets.js +341 -0
  26. package/src/agent-ops/query-builders.d.ts +26 -0
  27. package/src/agent-ops/query-builders.js +42 -5021
  28. package/src/archive/archive.d.ts +2 -0
  29. package/src/archive/compact.d.ts +1 -0
  30. package/src/archive/unarchive.d.ts +1 -0
  31. package/src/catalog/constants.js +10 -0
  32. package/src/catalog/copy.js +60 -0
  33. package/src/catalog/diagnostics.js +15 -0
  34. package/src/catalog/entries.js +42 -0
  35. package/src/catalog/files.js +67 -0
  36. package/src/catalog/provenance.js +122 -0
  37. package/src/catalog/source.js +150 -0
  38. package/src/catalog/validation.js +252 -0
  39. package/src/catalog.d.ts +12 -0
  40. package/src/catalog.js +18 -750
  41. package/src/cli/catalog-alias.d.ts +1 -0
  42. package/src/cli/command-parser.js +38 -0
  43. package/src/cli/command-parsers/core.js +102 -0
  44. package/src/cli/command-parsers/generator.js +39 -0
  45. package/src/cli/command-parsers/import.js +44 -0
  46. package/src/cli/command-parsers/legacy-workflow.js +21 -0
  47. package/src/cli/command-parsers/project.js +47 -0
  48. package/src/cli/command-parsers/sdlc.js +47 -0
  49. package/src/cli/command-parsers/shared.js +51 -0
  50. package/src/cli/command-parsers/template.js +48 -0
  51. package/src/cli/commands/agent.js +47 -0
  52. package/src/cli/commands/catalog/check.js +31 -0
  53. package/src/cli/commands/catalog/copy.js +59 -0
  54. package/src/cli/commands/catalog/doctor.js +248 -0
  55. package/src/cli/commands/catalog/help.js +21 -0
  56. package/src/cli/commands/catalog/list.js +52 -0
  57. package/src/cli/commands/catalog/runner.js +92 -0
  58. package/src/cli/commands/catalog/shared.js +17 -0
  59. package/src/cli/commands/catalog/show.js +134 -0
  60. package/src/cli/commands/catalog.js +32 -0
  61. package/src/cli/commands/check.js +268 -0
  62. package/src/cli/commands/doctor.js +268 -0
  63. package/src/cli/commands/emit.js +149 -0
  64. package/src/cli/commands/generate.js +96 -0
  65. package/src/cli/commands/generator-policy/package-info.js +162 -0
  66. package/src/cli/commands/generator-policy/payloads.js +372 -0
  67. package/src/cli/commands/generator-policy/printers.js +159 -0
  68. package/src/cli/commands/generator-policy/runner.js +81 -0
  69. package/src/cli/commands/generator-policy/shared.js +39 -0
  70. package/src/cli/commands/generator-policy.js +17 -0
  71. package/src/cli/commands/generator.js +443 -0
  72. package/src/cli/commands/import/adopt.js +170 -0
  73. package/src/cli/commands/import/check.js +91 -0
  74. package/src/cli/commands/import/diff.js +84 -0
  75. package/src/cli/commands/import/help.js +47 -0
  76. package/src/cli/commands/import/paths.js +277 -0
  77. package/src/cli/commands/import/plan.js +284 -0
  78. package/src/cli/commands/import/refresh.js +470 -0
  79. package/src/cli/commands/import/status-history.js +196 -0
  80. package/src/cli/commands/import/workspace.js +230 -0
  81. package/src/cli/commands/import-runner.js +157 -0
  82. package/src/cli/commands/import.js +35 -0
  83. package/src/cli/commands/inspect.js +55 -0
  84. package/src/cli/commands/new.js +94 -0
  85. package/src/cli/commands/package/constants.js +17 -0
  86. package/src/cli/commands/package/doctor.js +240 -0
  87. package/src/cli/commands/package/help.js +27 -0
  88. package/src/cli/commands/package/lockfile.js +135 -0
  89. package/src/cli/commands/package/npm.js +97 -0
  90. package/src/cli/commands/package/reporting.js +35 -0
  91. package/src/cli/commands/package/runner.js +33 -0
  92. package/src/cli/commands/package/shared.js +9 -0
  93. package/src/cli/commands/package/update-cli.js +252 -0
  94. package/src/cli/commands/package/versions.js +35 -0
  95. package/src/cli/commands/package.js +31 -0
  96. package/src/cli/commands/query/change-plan.js +68 -0
  97. package/src/cli/commands/query/definitions.js +202 -0
  98. package/src/cli/commands/query/import-adopt.js +121 -0
  99. package/src/cli/commands/query/runner/artifacts.js +102 -0
  100. package/src/cli/commands/query/runner/boundaries.js +211 -0
  101. package/src/cli/commands/query/runner/change.js +182 -0
  102. package/src/cli/commands/query/runner/import-adopt.js +111 -0
  103. package/src/cli/commands/query/runner/index.js +31 -0
  104. package/src/cli/commands/query/runner/output.js +12 -0
  105. package/src/cli/commands/query/runner/workflow.js +241 -0
  106. package/src/cli/commands/query/runner.js +3 -0
  107. package/src/cli/commands/query/workflow-context.js +5 -0
  108. package/src/cli/commands/query/workspace.js +274 -0
  109. package/src/cli/commands/query.js +11 -0
  110. package/src/cli/commands/release-rollout.js +257 -0
  111. package/src/cli/commands/release-shared.js +528 -0
  112. package/src/cli/commands/release-status.js +429 -0
  113. package/src/cli/commands/release.js +107 -0
  114. package/src/cli/commands/sdlc.js +168 -0
  115. package/src/cli/commands/setup.js +76 -0
  116. package/src/cli/commands/source.js +291 -0
  117. package/src/cli/commands/template/baseline.js +100 -0
  118. package/src/cli/commands/template/check.js +466 -0
  119. package/src/cli/commands/template/constants.js +8 -0
  120. package/src/cli/commands/template/diagnostics.js +26 -0
  121. package/src/cli/commands/template/help.js +28 -0
  122. package/src/cli/commands/template/lifecycle.js +404 -0
  123. package/src/cli/commands/template/list-show.js +287 -0
  124. package/src/cli/commands/template/policy.js +422 -0
  125. package/src/cli/commands/template/shared.js +127 -0
  126. package/src/cli/commands/template/updates.js +352 -0
  127. package/src/cli/commands/template-runner.js +198 -0
  128. package/src/cli/commands/template.js +43 -0
  129. package/src/cli/commands/trust.js +219 -0
  130. package/src/cli/commands/version.js +40 -0
  131. package/src/cli/commands/widget.js +168 -0
  132. package/src/cli/commands/workflow.js +63 -0
  133. package/src/cli/dispatcher.js +392 -0
  134. package/src/cli/help-dispatch.js +188 -0
  135. package/src/cli/help.js +296 -0
  136. package/src/cli/migration-guidance.js +59 -0
  137. package/src/cli/options.js +96 -0
  138. package/src/cli/output-safety.js +107 -0
  139. package/src/cli/path-normalization.js +29 -0
  140. package/src/cli.js +47 -11711
  141. package/src/example-implementation.d.ts +2 -0
  142. package/src/format.d.ts +1 -0
  143. package/src/generator/api/contracts.js +497 -0
  144. package/src/generator/api/metadata.js +221 -0
  145. package/src/generator/api/openapi.js +559 -0
  146. package/src/generator/api/schema.js +124 -0
  147. package/src/generator/api/types.d.ts +98 -0
  148. package/src/generator/api.js +3 -1195
  149. package/src/generator/check.d.ts +1 -0
  150. package/src/generator/context/bundle.d.ts +1 -0
  151. package/src/generator/context/shared/domain-sdlc.js +282 -0
  152. package/src/generator/context/shared/maintained-boundary.js +665 -0
  153. package/src/generator/context/shared/metrics.js +85 -0
  154. package/src/generator/context/shared/primitives.js +64 -0
  155. package/src/generator/context/shared/relationships.js +453 -0
  156. package/src/generator/context/shared/summaries.js +263 -0
  157. package/src/generator/context/shared/types.d.ts +207 -0
  158. package/src/generator/context/shared.d.ts +44 -0
  159. package/src/generator/context/shared.js +80 -1390
  160. package/src/generator/context/slice/core.js +397 -0
  161. package/src/generator/context/slice/sdlc.js +417 -0
  162. package/src/generator/context/slice/ui-packets.js +183 -0
  163. package/src/generator/context/slice.js +2 -859
  164. package/src/generator/native/parity-bundle.js +2 -1
  165. package/src/generator/registry/index.js +507 -0
  166. package/src/generator/registry.js +18 -504
  167. package/src/generator/runtime/environment/index.js +666 -0
  168. package/src/generator/runtime/environment.js +4 -666
  169. package/src/generator/runtime/runtime-check/index.js +554 -0
  170. package/src/generator/runtime/runtime-check.js +4 -554
  171. package/src/generator/runtime/shared/index.js +572 -0
  172. package/src/generator/runtime/shared.js +19 -570
  173. package/src/generator/shared.d.ts +2 -0
  174. package/src/generator/surfaces/shared.d.ts +3 -0
  175. package/src/generator/surfaces/web/html-escape.js +22 -0
  176. package/src/generator/surfaces/web/react.js +10 -8
  177. package/src/generator/surfaces/web/sveltekit.js +7 -5
  178. package/src/generator/surfaces/web/vanilla.js +8 -4
  179. package/src/generator/widget-conformance/behavior-report.js +258 -0
  180. package/src/generator/widget-conformance/checks.js +371 -0
  181. package/src/generator/widget-conformance/projection-context.js +200 -0
  182. package/src/generator/widget-conformance/report.js +166 -0
  183. package/src/generator/widget-conformance/types.d.ts +121 -0
  184. package/src/generator/widget-conformance.js +3 -824
  185. package/src/generator.d.ts +2 -0
  186. package/src/github-client.js +520 -0
  187. package/src/import/core/context.d.ts +3 -0
  188. package/src/import/core/contracts.d.ts +1 -0
  189. package/src/import/core/registry.d.ts +4 -0
  190. package/src/import/core/runner/candidates.js +217 -0
  191. package/src/import/core/runner/options.js +22 -0
  192. package/src/import/core/runner/reports.js +50 -0
  193. package/src/import/core/runner/run.js +79 -0
  194. package/src/import/core/runner/tracks.js +150 -0
  195. package/src/import/core/runner/ui-drafts.js +337 -0
  196. package/src/import/core/runner.js +3 -698
  197. package/src/import/core/shared/api-routes.js +221 -0
  198. package/src/import/core/shared/candidates.js +97 -0
  199. package/src/import/core/shared/files.js +177 -0
  200. package/src/import/core/shared/next-app.js +389 -0
  201. package/src/import/core/shared/types.d.ts +51 -0
  202. package/src/import/core/shared/ui-routes.js +230 -0
  203. package/src/import/core/shared.js +67 -910
  204. package/src/import/extractors/api/flutter-dio.js +4 -8
  205. package/src/import/extractors/api/react-native-repository.js +4 -8
  206. package/src/import/index.d.ts +4 -0
  207. package/src/import/provenance.d.ts +4 -0
  208. package/src/new-project/constants.js +128 -0
  209. package/src/new-project/create.js +83 -0
  210. package/src/new-project/json.js +28 -0
  211. package/src/new-project/metadata.js +96 -0
  212. package/src/new-project/package-spec.js +161 -0
  213. package/src/new-project/project-files.js +348 -0
  214. package/src/new-project/template-policy.js +269 -0
  215. package/src/new-project/template-resolution.js +368 -0
  216. package/src/new-project/template-snapshots.js +430 -0
  217. package/src/new-project/template-updates.js +512 -0
  218. package/src/new-project/types.d.ts +83 -0
  219. package/src/new-project.js +6 -2188
  220. package/src/npm-safety.js +79 -0
  221. package/src/parser.d.ts +87 -0
  222. package/src/parser.js +118 -0
  223. package/src/path-helpers.d.ts +1 -0
  224. package/src/path-helpers.js +20 -0
  225. package/src/policy/review-boundaries.d.ts +15 -0
  226. package/src/project-config/index.js +564 -0
  227. package/src/project-config.js +19 -560
  228. package/src/reconcile/docs.d.ts +8 -0
  229. package/src/reconcile/journeys.d.ts +1 -0
  230. package/src/resolver/enrich/acceptance-criterion.js +2 -0
  231. package/src/resolver/enrich/bug.js +2 -0
  232. package/src/resolver/enrich/pitch.js +2 -0
  233. package/src/resolver/enrich/requirement.js +2 -0
  234. package/src/resolver/enrich/task.js +2 -0
  235. package/src/resolver/index.js +19 -2089
  236. package/src/resolver/normalize.js +384 -1
  237. package/src/resolver/plans.js +168 -0
  238. package/src/resolver/projections-api.js +494 -0
  239. package/src/resolver/projections-db.js +133 -0
  240. package/src/resolver/projections-ui.js +317 -0
  241. package/src/resolver/shapes.js +251 -0
  242. package/src/resolver/shared.js +278 -0
  243. package/src/resolver/widgets.js +132 -0
  244. package/src/resolver.d.ts +1 -0
  245. package/src/runtime-support.js +29 -0
  246. package/src/sdlc/adopt.d.ts +1 -0
  247. package/src/sdlc/check.d.ts +1 -0
  248. package/src/sdlc/explain.d.ts +1 -0
  249. package/src/sdlc/release.d.ts +1 -0
  250. package/src/sdlc/scaffold.d.ts +1 -0
  251. package/src/sdlc/transition.d.ts +1 -0
  252. package/src/template-trust/constants.js +62 -0
  253. package/src/template-trust/content.js +258 -0
  254. package/src/template-trust/diff.js +92 -0
  255. package/src/template-trust/policy.js +61 -0
  256. package/src/template-trust/record.js +90 -0
  257. package/src/template-trust/status.js +182 -0
  258. package/src/template-trust.js +24 -687
  259. package/src/text-helpers.d.ts +7 -0
  260. package/src/text-helpers.js +245 -0
  261. package/src/topogram-config.js +306 -0
  262. package/src/topogram-types.d.ts +69 -0
  263. package/src/validator/common.js +488 -0
  264. package/src/validator/data-model.js +237 -0
  265. package/src/validator/docs.js +167 -0
  266. package/src/validator/expressions.js +146 -1
  267. package/src/validator/index.d.ts +23 -0
  268. package/src/validator/index.js +32 -3585
  269. package/src/validator/kinds.d.ts +41 -0
  270. package/src/validator/kinds.js +2 -0
  271. package/src/validator/model-helpers.js +46 -0
  272. package/src/validator/per-kind/acceptance-criterion.js +5 -0
  273. package/src/validator/per-kind/bug.js +6 -0
  274. package/src/validator/per-kind/domain.js +15 -2
  275. package/src/validator/per-kind/pitch.js +7 -0
  276. package/src/validator/per-kind/requirement.js +5 -0
  277. package/src/validator/per-kind/task.js +7 -0
  278. package/src/validator/per-kind/widget.js +14 -0
  279. package/src/validator/projections/api-http-async.js +410 -0
  280. package/src/validator/projections/api-http-authz.js +88 -0
  281. package/src/validator/projections/api-http-core.js +205 -0
  282. package/src/validator/projections/api-http-policies.js +339 -0
  283. package/src/validator/projections/api-http-responses.js +233 -0
  284. package/src/validator/projections/api-http.js +44 -0
  285. package/src/validator/projections/db.js +353 -0
  286. package/src/validator/projections/generator-defaults.js +45 -0
  287. package/src/validator/projections/helpers.js +87 -0
  288. package/src/validator/projections/ui-helpers.js +214 -0
  289. package/src/validator/projections/ui-navigation.js +344 -0
  290. package/src/validator/projections/ui-structure.js +364 -0
  291. package/src/validator/projections/ui-widgets.js +493 -0
  292. package/src/validator/projections/ui.js +46 -0
  293. package/src/validator/registry.js +48 -1
  294. package/src/validator/utils.d.ts +20 -0
  295. package/src/validator/utils.js +115 -12
  296. package/src/validator.d.ts +2 -0
  297. package/src/widget-behavior.d.ts +1 -0
  298. package/src/workflows/adoption/index.js +26 -0
  299. package/src/workflows/docs-generate.js +262 -0
  300. package/src/workflows/docs-scan.js +703 -0
  301. package/src/workflows/docs.js +15 -0
  302. package/src/workflows/import-app/api/collect.js +221 -0
  303. package/src/workflows/import-app/api/openapi.js +257 -0
  304. package/src/workflows/import-app/api/routes.js +327 -0
  305. package/src/workflows/import-app/api/sources.js +22 -0
  306. package/src/workflows/import-app/api.js +4 -0
  307. package/src/workflows/import-app/db.js +538 -0
  308. package/src/workflows/import-app/index.js +30 -0
  309. package/src/workflows/import-app/shared.js +218 -0
  310. package/src/workflows/import-app/ui.js +443 -0
  311. package/src/workflows/import-app/workflow.js +159 -0
  312. package/src/workflows/reconcile/adoption-plan/build.js +208 -0
  313. package/src/workflows/reconcile/adoption-plan/dependencies.js +75 -0
  314. package/src/workflows/reconcile/adoption-plan/outputs.js +143 -0
  315. package/src/workflows/reconcile/adoption-plan/paths.js +58 -0
  316. package/src/workflows/reconcile/adoption-plan/projection-patches.js +177 -0
  317. package/src/workflows/reconcile/adoption-plan/reasons.js +107 -0
  318. package/src/workflows/reconcile/adoption-plan.js +32 -0
  319. package/src/workflows/reconcile/auth/closures.js +115 -0
  320. package/src/workflows/reconcile/auth/formatters.js +142 -0
  321. package/src/workflows/reconcile/auth/inference.js +330 -0
  322. package/src/workflows/reconcile/auth/roles.js +122 -0
  323. package/src/workflows/reconcile/auth.js +37 -0
  324. package/src/workflows/reconcile/bundle-core/index.js +600 -0
  325. package/src/workflows/reconcile/bundle-core.js +14 -0
  326. package/src/workflows/reconcile/bundle-shared.js +75 -0
  327. package/src/workflows/reconcile/candidate-model.js +477 -0
  328. package/src/workflows/reconcile/canonical-surface.js +264 -0
  329. package/src/workflows/reconcile/gap-report.js +333 -0
  330. package/src/workflows/reconcile/ids.js +6 -0
  331. package/src/workflows/reconcile/impacts/adoption-plan.js +192 -0
  332. package/src/workflows/reconcile/impacts/indexes.js +101 -0
  333. package/src/workflows/reconcile/impacts/patches.js +252 -0
  334. package/src/workflows/reconcile/impacts/reports.js +80 -0
  335. package/src/workflows/reconcile/impacts.js +16 -0
  336. package/src/workflows/reconcile/index.js +7 -0
  337. package/src/workflows/reconcile/renderers.js +461 -0
  338. package/src/workflows/reconcile/summary.js +90 -0
  339. package/src/workflows/reconcile/workflow.js +309 -0
  340. package/src/workflows/shared.js +189 -0
  341. package/src/workflows/types.d.ts +93 -0
  342. package/src/workflows.d.ts +1 -0
  343. package/src/workflows.js +10 -7652
  344. package/src/workspace-docs.d.ts +29 -0
@@ -0,0 +1,410 @@
1
+ // @ts-check
2
+
3
+ import {
4
+ blockEntries,
5
+ blockSymbolItems,
6
+ getFieldValue,
7
+ pushError,
8
+ symbolValues
9
+ } from "../utils.js";
10
+ import { resolveCapabilityContractFields } from "./helpers.js";
11
+
12
+ /**
13
+ * @param {ValidationErrors} errors
14
+ * @param {TopogramStatement} statement
15
+ * @param {TopogramFieldMap} fieldMap
16
+ * @param {TopogramRegistry} registry
17
+ * @returns {void}
18
+ */
19
+ export function validateProjectionHttpAsync(errors, statement, fieldMap, registry) {
20
+ if (statement.kind !== "projection") {
21
+ return;
22
+ }
23
+
24
+ const httpAsyncField = fieldMap.get("async_jobs")?.[0];
25
+ if (!httpAsyncField || httpAsyncField.value.type !== "block") {
26
+ return;
27
+ }
28
+
29
+ const realized = new Set(symbolValues(getFieldValue(statement, "realizes")));
30
+ const httpEntries = blockEntries(getFieldValue(statement, "endpoints"));
31
+ const httpDirectivesByCapability = new Map();
32
+ for (const entry of httpEntries) {
33
+ const tokens = blockSymbolItems(entry).map((item) => item.value);
34
+ const capabilityId = tokens[0];
35
+ const directives = new Map();
36
+ for (let i = 1; i < tokens.length - 1; i += 2) {
37
+ directives.set(tokens[i], tokens[i + 1]);
38
+ }
39
+ httpDirectivesByCapability.set(capabilityId, directives);
40
+ }
41
+ for (const entry of httpAsyncField.value.entries) {
42
+ const tokens = blockSymbolItems(entry).map((item) => item.value);
43
+ const [capabilityId] = tokens;
44
+ const capability = registry.get(capabilityId);
45
+
46
+ if (!capability) {
47
+ pushError(errors, `Projection ${statement.id} async_jobs references missing capability '${capabilityId}'`, entry.loc);
48
+ continue;
49
+ }
50
+ if (capability.kind !== "capability") {
51
+ pushError(errors, `Projection ${statement.id} async_jobs must target a capability, found ${capability.kind} '${capability.id}'`, entry.loc);
52
+ }
53
+ if (!realized.has(capabilityId)) {
54
+ pushError(errors, `Projection ${statement.id} async_jobs for '${capabilityId}' must also appear in 'realizes'`, entry.loc);
55
+ }
56
+
57
+ const directives = new Map();
58
+ for (let i = 1; i < tokens.length; i += 2) {
59
+ const key = tokens[i];
60
+ const value = tokens[i + 1];
61
+ if (!value) {
62
+ pushError(errors, `Projection ${statement.id} async_jobs for '${capabilityId}' is missing a value for '${key}'`, entry.loc);
63
+ continue;
64
+ }
65
+ directives.set(key, value);
66
+ }
67
+
68
+ for (const requiredKey of ["mode", "accepted", "location_header", "retry_after_header", "status_path", "status_capability", "job"]) {
69
+ if (!directives.has(requiredKey)) {
70
+ pushError(errors, `Projection ${statement.id} async_jobs for '${capabilityId}' must include '${requiredKey}'`, entry.loc);
71
+ }
72
+ }
73
+
74
+ for (const key of directives.keys()) {
75
+ if (!["mode", "accepted", "location_header", "retry_after_header", "status_path", "status_capability", "job"].includes(key)) {
76
+ pushError(errors, `Projection ${statement.id} async_jobs for '${capabilityId}' has unknown directive '${key}'`, entry.loc);
77
+ }
78
+ }
79
+
80
+ const mode = directives.get("mode");
81
+ if (mode && mode !== "job") {
82
+ pushError(errors, `Projection ${statement.id} async_jobs for '${capabilityId}' has invalid mode '${mode}'`, entry.loc);
83
+ }
84
+
85
+ const accepted = directives.get("accepted");
86
+ if (accepted && accepted !== "202") {
87
+ pushError(errors, `Projection ${statement.id} async_jobs for '${capabilityId}' must use 202 for 'accepted'`, entry.loc);
88
+ }
89
+
90
+ const jobShapeId = directives.get("job");
91
+ if (jobShapeId) {
92
+ const jobShape = registry.get(jobShapeId);
93
+ if (!jobShape) {
94
+ pushError(errors, `Projection ${statement.id} async_jobs for '${capabilityId}' references missing shape '${jobShapeId}'`, entry.loc);
95
+ } else if (jobShape.kind !== "shape") {
96
+ pushError(errors, `Projection ${statement.id} async_jobs for '${capabilityId}' must reference a shape for 'job', found ${jobShape.kind} '${jobShape.id}'`, entry.loc);
97
+ }
98
+ }
99
+
100
+ const statusCapabilityId = directives.get("status_capability");
101
+ if (statusCapabilityId) {
102
+ const statusCapability = registry.get(statusCapabilityId);
103
+ if (!statusCapability) {
104
+ pushError(errors, `Projection ${statement.id} async_jobs for '${capabilityId}' references missing status capability '${statusCapabilityId}'`, entry.loc);
105
+ } else if (statusCapability.kind !== "capability") {
106
+ pushError(errors, `Projection ${statement.id} async_jobs for '${capabilityId}' must reference a capability for 'status_capability', found ${statusCapability.kind} '${statusCapability.id}'`, entry.loc);
107
+ } else if (!realized.has(statusCapabilityId)) {
108
+ pushError(errors, `Projection ${statement.id} async_jobs for '${capabilityId}' status capability '${statusCapabilityId}' must also appear in 'realizes'`, entry.loc);
109
+ }
110
+
111
+ const statusHttp = httpDirectivesByCapability.get(statusCapabilityId);
112
+ if (statusHttp?.get("method") && statusHttp.get("method") !== "GET") {
113
+ pushError(errors, `Projection ${statement.id} async_jobs for '${capabilityId}' status capability '${statusCapabilityId}' must use HTTP GET`, entry.loc);
114
+ }
115
+ if (statusHttp?.get("path") && directives.get("status_path") && statusHttp.get("path") !== directives.get("status_path")) {
116
+ pushError(errors, `Projection ${statement.id} async_jobs for '${capabilityId}' status_path must match the path for '${statusCapabilityId}'`, entry.loc);
117
+ }
118
+ }
119
+
120
+ const statusPath = directives.get("status_path");
121
+ if (statusPath && !statusPath.startsWith("/")) {
122
+ pushError(errors, `Projection ${statement.id} async_jobs for '${capabilityId}' must use an absolute path for 'status_path'`, entry.loc);
123
+ }
124
+ }
125
+ }
126
+
127
+ /**
128
+ * @param {ValidationErrors} errors
129
+ * @param {TopogramStatement} statement
130
+ * @param {TopogramFieldMap} fieldMap
131
+ * @param {TopogramRegistry} registry
132
+ * @returns {void}
133
+ */
134
+
135
+ export function validateProjectionHttpStatus(errors, statement, fieldMap, registry) {
136
+ if (statement.kind !== "projection") {
137
+ return;
138
+ }
139
+
140
+ const httpStatusField = fieldMap.get("async_status")?.[0];
141
+ if (!httpStatusField || httpStatusField.value.type !== "block") {
142
+ return;
143
+ }
144
+
145
+ const realized = new Set(symbolValues(getFieldValue(statement, "realizes")));
146
+ const httpEntries = blockEntries(getFieldValue(statement, "endpoints"));
147
+ const httpMethodsByCapability = new Map();
148
+ for (const entry of httpEntries) {
149
+ const tokens = blockSymbolItems(entry).map((item) => item.value);
150
+ const capabilityId = tokens[0];
151
+ for (let i = 1; i < tokens.length - 1; i += 2) {
152
+ if (tokens[i] === "method") {
153
+ httpMethodsByCapability.set(capabilityId, tokens[i + 1]);
154
+ break;
155
+ }
156
+ }
157
+ }
158
+ for (const entry of httpStatusField.value.entries) {
159
+ const tokens = blockSymbolItems(entry).map((item) => item.value);
160
+ const [capabilityId] = tokens;
161
+ const capability = registry.get(capabilityId);
162
+
163
+ if (!capability) {
164
+ pushError(errors, `Projection ${statement.id} async_status references missing capability '${capabilityId}'`, entry.loc);
165
+ continue;
166
+ }
167
+ if (capability.kind !== "capability") {
168
+ pushError(errors, `Projection ${statement.id} async_status must target a capability, found ${capability.kind} '${capability.id}'`, entry.loc);
169
+ }
170
+ if (!realized.has(capabilityId)) {
171
+ pushError(errors, `Projection ${statement.id} async_status for '${capabilityId}' must also appear in 'realizes'`, entry.loc);
172
+ }
173
+
174
+ const directives = new Map();
175
+ for (let i = 1; i < tokens.length; i += 2) {
176
+ const key = tokens[i];
177
+ const value = tokens[i + 1];
178
+ if (!value) {
179
+ pushError(errors, `Projection ${statement.id} async_status for '${capabilityId}' is missing a value for '${key}'`, entry.loc);
180
+ continue;
181
+ }
182
+ directives.set(key, value);
183
+ }
184
+
185
+ for (const requiredKey of ["async_for", "state_field", "completed", "failed"]) {
186
+ if (!directives.has(requiredKey)) {
187
+ pushError(errors, `Projection ${statement.id} async_status for '${capabilityId}' must include '${requiredKey}'`, entry.loc);
188
+ }
189
+ }
190
+
191
+ for (const key of directives.keys()) {
192
+ if (!["async_for", "state_field", "completed", "failed", "expired", "download_capability", "download_field", "error_field"].includes(key)) {
193
+ pushError(errors, `Projection ${statement.id} async_status for '${capabilityId}' has unknown directive '${key}'`, entry.loc);
194
+ }
195
+ }
196
+
197
+ const asyncCapabilityId = directives.get("async_for");
198
+ if (asyncCapabilityId) {
199
+ const asyncCapability = registry.get(asyncCapabilityId);
200
+ if (!asyncCapability) {
201
+ pushError(errors, `Projection ${statement.id} async_status for '${capabilityId}' references missing async capability '${asyncCapabilityId}'`, entry.loc);
202
+ } else if (asyncCapability.kind !== "capability") {
203
+ pushError(errors, `Projection ${statement.id} async_status for '${capabilityId}' must reference a capability for 'async_for', found ${asyncCapability.kind} '${asyncCapability.id}'`, entry.loc);
204
+ } else if (!realized.has(asyncCapabilityId)) {
205
+ pushError(errors, `Projection ${statement.id} async_status for '${capabilityId}' async capability '${asyncCapabilityId}' must also appear in 'realizes'`, entry.loc);
206
+ }
207
+ }
208
+
209
+ const outputFields = resolveCapabilityContractFields(registry, capabilityId, "output");
210
+ for (const [directive, fieldName] of [
211
+ ["state_field", directives.get("state_field")],
212
+ ["download_field", directives.get("download_field")],
213
+ ["error_field", directives.get("error_field")]
214
+ ]) {
215
+ if (fieldName && outputFields.size > 0 && !outputFields.has(fieldName)) {
216
+ pushError(errors, `Projection ${statement.id} async_status references unknown output field '${fieldName}' for '${directive}' on ${capabilityId}`, entry.loc);
217
+ }
218
+ }
219
+
220
+ const downloadCapabilityId = directives.get("download_capability");
221
+ if (downloadCapabilityId) {
222
+ const downloadCapability = registry.get(downloadCapabilityId);
223
+ if (!downloadCapability) {
224
+ pushError(errors, `Projection ${statement.id} async_status for '${capabilityId}' references missing download capability '${downloadCapabilityId}'`, entry.loc);
225
+ } else if (downloadCapability.kind !== "capability") {
226
+ pushError(errors, `Projection ${statement.id} async_status for '${capabilityId}' must reference a capability for 'download_capability', found ${downloadCapability.kind} '${downloadCapability.id}'`, entry.loc);
227
+ } else if (!realized.has(downloadCapabilityId)) {
228
+ pushError(errors, `Projection ${statement.id} async_status for '${capabilityId}' download capability '${downloadCapabilityId}' must also appear in 'realizes'`, entry.loc);
229
+ }
230
+
231
+ const method = httpMethodsByCapability.get(downloadCapabilityId);
232
+ if (method && method !== "GET") {
233
+ pushError(errors, `Projection ${statement.id} async_status for '${capabilityId}' download capability '${downloadCapabilityId}' must use HTTP GET`, entry.loc);
234
+ }
235
+ }
236
+ }
237
+ }
238
+
239
+ /**
240
+ * @param {ValidationErrors} errors
241
+ * @param {TopogramStatement} statement
242
+ * @param {TopogramFieldMap} fieldMap
243
+ * @param {TopogramRegistry} registry
244
+ * @returns {void}
245
+ */
246
+
247
+ export function validateProjectionHttpDownload(errors, statement, fieldMap, registry) {
248
+ if (statement.kind !== "projection") {
249
+ return;
250
+ }
251
+
252
+ const httpDownloadField = fieldMap.get("downloads")?.[0];
253
+ if (!httpDownloadField || httpDownloadField.value.type !== "block") {
254
+ return;
255
+ }
256
+
257
+ const realized = new Set(symbolValues(getFieldValue(statement, "realizes")));
258
+ for (const entry of httpDownloadField.value.entries) {
259
+ const tokens = blockSymbolItems(entry).map((item) => item.value);
260
+ const [capabilityId] = tokens;
261
+ const capability = registry.get(capabilityId);
262
+
263
+ if (!capability) {
264
+ pushError(errors, `Projection ${statement.id} downloads references missing capability '${capabilityId}'`, entry.loc);
265
+ continue;
266
+ }
267
+ if (capability.kind !== "capability") {
268
+ pushError(errors, `Projection ${statement.id} downloads must target a capability, found ${capability.kind} '${capability.id}'`, entry.loc);
269
+ }
270
+ if (!realized.has(capabilityId)) {
271
+ pushError(errors, `Projection ${statement.id} downloads for '${capabilityId}' must also appear in 'realizes'`, entry.loc);
272
+ }
273
+
274
+ const directives = new Map();
275
+ for (let i = 1; i < tokens.length; i += 2) {
276
+ const key = tokens[i];
277
+ const value = tokens[i + 1];
278
+ if (!value) {
279
+ pushError(errors, `Projection ${statement.id} downloads for '${capabilityId}' is missing a value for '${key}'`, entry.loc);
280
+ continue;
281
+ }
282
+ directives.set(key, value);
283
+ }
284
+
285
+ for (const requiredKey of ["async_for", "media", "disposition"]) {
286
+ if (!directives.has(requiredKey)) {
287
+ pushError(errors, `Projection ${statement.id} downloads for '${capabilityId}' must include '${requiredKey}'`, entry.loc);
288
+ }
289
+ }
290
+
291
+ for (const key of directives.keys()) {
292
+ if (!["async_for", "media", "filename", "disposition"].includes(key)) {
293
+ pushError(errors, `Projection ${statement.id} downloads for '${capabilityId}' has unknown directive '${key}'`, entry.loc);
294
+ }
295
+ }
296
+
297
+ const asyncCapabilityId = directives.get("async_for");
298
+ if (asyncCapabilityId) {
299
+ const asyncCapability = registry.get(asyncCapabilityId);
300
+ if (!asyncCapability) {
301
+ pushError(errors, `Projection ${statement.id} downloads for '${capabilityId}' references missing async capability '${asyncCapabilityId}'`, entry.loc);
302
+ } else if (asyncCapability.kind !== "capability") {
303
+ pushError(errors, `Projection ${statement.id} downloads for '${capabilityId}' must reference a capability for 'async_for', found ${asyncCapability.kind} '${asyncCapability.id}'`, entry.loc);
304
+ } else if (!realized.has(asyncCapabilityId)) {
305
+ pushError(errors, `Projection ${statement.id} downloads for '${capabilityId}' async capability '${asyncCapabilityId}' must also appear in 'realizes'`, entry.loc);
306
+ }
307
+ }
308
+
309
+ const media = directives.get("media");
310
+ if (media && !media.includes("/")) {
311
+ pushError(errors, `Projection ${statement.id} downloads for '${capabilityId}' must use a valid media type`, entry.loc);
312
+ }
313
+
314
+ const disposition = directives.get("disposition");
315
+ if (disposition && !["attachment", "inline"].includes(disposition)) {
316
+ pushError(errors, `Projection ${statement.id} downloads for '${capabilityId}' has invalid disposition '${disposition}'`, entry.loc);
317
+ }
318
+ }
319
+ }
320
+
321
+ /**
322
+ * @param {ValidationErrors} errors
323
+ * @param {TopogramStatement} statement
324
+ * @param {TopogramFieldMap} fieldMap
325
+ * @param {TopogramRegistry} registry
326
+ * @returns {void}
327
+ */
328
+
329
+ export function validateProjectionHttpCallbacks(errors, statement, fieldMap, registry) {
330
+ if (statement.kind !== "projection") {
331
+ return;
332
+ }
333
+
334
+ const httpCallbacksField = fieldMap.get("callbacks")?.[0];
335
+ if (!httpCallbacksField || httpCallbacksField.value.type !== "block") {
336
+ return;
337
+ }
338
+
339
+ const realized = new Set(symbolValues(getFieldValue(statement, "realizes")));
340
+ for (const entry of httpCallbacksField.value.entries) {
341
+ const tokens = blockSymbolItems(entry).map((item) => item.value);
342
+ const [capabilityId] = tokens;
343
+ const capability = registry.get(capabilityId);
344
+
345
+ if (!capability) {
346
+ pushError(errors, `Projection ${statement.id} callbacks references missing capability '${capabilityId}'`, entry.loc);
347
+ continue;
348
+ }
349
+ if (capability.kind !== "capability") {
350
+ pushError(errors, `Projection ${statement.id} callbacks must target a capability, found ${capability.kind} '${capability.id}'`, entry.loc);
351
+ }
352
+ if (!realized.has(capabilityId)) {
353
+ pushError(errors, `Projection ${statement.id} callbacks for '${capabilityId}' must also appear in 'realizes'`, entry.loc);
354
+ }
355
+
356
+ const directives = new Map();
357
+ for (let i = 1; i < tokens.length; i += 2) {
358
+ const key = tokens[i];
359
+ const value = tokens[i + 1];
360
+ if (!value) {
361
+ pushError(errors, `Projection ${statement.id} callbacks for '${capabilityId}' is missing a value for '${key}'`, entry.loc);
362
+ continue;
363
+ }
364
+ directives.set(key, value);
365
+ }
366
+
367
+ for (const requiredKey of ["event", "target_field", "method", "payload", "success"]) {
368
+ if (!directives.has(requiredKey)) {
369
+ pushError(errors, `Projection ${statement.id} callbacks for '${capabilityId}' must include '${requiredKey}'`, entry.loc);
370
+ }
371
+ }
372
+
373
+ for (const key of directives.keys()) {
374
+ if (!["event", "target_field", "method", "payload", "success"].includes(key)) {
375
+ pushError(errors, `Projection ${statement.id} callbacks for '${capabilityId}' has unknown directive '${key}'`, entry.loc);
376
+ }
377
+ }
378
+
379
+ const method = directives.get("method");
380
+ if (method && !["POST", "PUT", "PATCH"].includes(method)) {
381
+ pushError(errors, `Projection ${statement.id} callbacks for '${capabilityId}' has invalid method '${method}'`, entry.loc);
382
+ }
383
+
384
+ const success = directives.get("success");
385
+ if (success && !/^\d{3}$/.test(success)) {
386
+ pushError(errors, `Projection ${statement.id} callbacks for '${capabilityId}' must use a 3-digit success status`, entry.loc);
387
+ }
388
+
389
+ const payloadShapeId = directives.get("payload");
390
+ if (payloadShapeId) {
391
+ const payloadShape = registry.get(payloadShapeId);
392
+ if (!payloadShape) {
393
+ pushError(errors, `Projection ${statement.id} callbacks for '${capabilityId}' references missing shape '${payloadShapeId}'`, entry.loc);
394
+ } else if (payloadShape.kind !== "shape") {
395
+ pushError(errors, `Projection ${statement.id} callbacks for '${capabilityId}' must reference a shape for 'payload', found ${payloadShape.kind} '${payloadShape.id}'`, entry.loc);
396
+ }
397
+ }
398
+
399
+ const targetField = directives.get("target_field");
400
+ const inputFields = resolveCapabilityContractFields(registry, capabilityId, "input");
401
+ if (targetField && inputFields.size > 0 && !inputFields.has(targetField)) {
402
+ pushError(errors, `Projection ${statement.id} callbacks references unknown input field '${targetField}' on ${capabilityId}`, entry.loc);
403
+ }
404
+ }
405
+ }
406
+
407
+ /**
408
+ * @param {string[]} tokens
409
+ * @returns {any}
410
+ */
@@ -0,0 +1,88 @@
1
+ // @ts-check
2
+
3
+ import {
4
+ blockSymbolItems,
5
+ getFieldValue,
6
+ pushError,
7
+ symbolValues
8
+ } from "../utils.js";
9
+
10
+ /**
11
+ * @param {ValidationErrors} errors
12
+ * @param {TopogramStatement} statement
13
+ * @param {TopogramFieldMap} fieldMap
14
+ * @param {TopogramRegistry} registry
15
+ * @returns {void}
16
+ */
17
+ export function validateProjectionHttpAuthz(errors, statement, fieldMap, registry) {
18
+ if (statement.kind !== "projection") {
19
+ return;
20
+ }
21
+
22
+ const httpAuthzField = fieldMap.get("authorization")?.[0];
23
+ if (!httpAuthzField || httpAuthzField.value.type !== "block") {
24
+ return;
25
+ }
26
+
27
+ const realized = new Set(symbolValues(getFieldValue(statement, "realizes")));
28
+ for (const entry of httpAuthzField.value.entries) {
29
+ const tokens = blockSymbolItems(entry).map((item) => item.value);
30
+ const [capabilityId] = tokens;
31
+ const capability = registry.get(capabilityId);
32
+
33
+ if (!capability) {
34
+ pushError(errors, `Projection ${statement.id} authorization references missing capability '${capabilityId}'`, entry.loc);
35
+ continue;
36
+ }
37
+ if (capability.kind !== "capability") {
38
+ pushError(errors, `Projection ${statement.id} authorization must target a capability, found ${capability.kind} '${capability.id}'`, entry.loc);
39
+ }
40
+ if (!realized.has(capabilityId)) {
41
+ pushError(errors, `Projection ${statement.id} authorization for '${capabilityId}' must also appear in 'realizes'`, entry.loc);
42
+ }
43
+
44
+ const directives = new Map();
45
+ for (let i = 1; i < tokens.length; i += 2) {
46
+ const key = tokens[i];
47
+ const value = tokens[i + 1];
48
+ if (!value) {
49
+ pushError(errors, `Projection ${statement.id} authorization for '${capabilityId}' is missing a value for '${key}'`, entry.loc);
50
+ continue;
51
+ }
52
+ directives.set(key, value);
53
+ }
54
+
55
+ for (const key of directives.keys()) {
56
+ if (!["role", "permission", "claim", "claim_value", "ownership", "ownership_field"].includes(key)) {
57
+ pushError(errors, `Projection ${statement.id} authorization for '${capabilityId}' has unknown directive '${key}'`, entry.loc);
58
+ }
59
+ }
60
+
61
+ if (directives.size === 0) {
62
+ pushError(errors, `Projection ${statement.id} authorization for '${capabilityId}' must include at least one directive`, entry.loc);
63
+ }
64
+
65
+ const ownership = directives.get("ownership");
66
+ if (ownership && !["owner", "owner_or_admin", "project_member", "none"].includes(ownership)) {
67
+ pushError(errors, `Projection ${statement.id} authorization for '${capabilityId}' has invalid ownership '${ownership}'`, entry.loc);
68
+ }
69
+
70
+ const ownershipField = directives.get("ownership_field");
71
+ if (ownershipField && (!ownership || ownership === "none")) {
72
+ pushError(errors, `Projection ${statement.id} authorization for '${capabilityId}' cannot declare ownership_field without ownership`, entry.loc);
73
+ }
74
+
75
+ const claimValue = directives.get("claim_value");
76
+ if (claimValue && !directives.get("claim")) {
77
+ pushError(errors, `Projection ${statement.id} authorization for '${capabilityId}' cannot declare claim_value without claim`, entry.loc);
78
+ }
79
+ }
80
+ }
81
+
82
+ /**
83
+ * @param {ValidationErrors} errors
84
+ * @param {TopogramStatement} statement
85
+ * @param {TopogramFieldMap} fieldMap
86
+ * @param {TopogramRegistry} registry
87
+ * @returns {void}
88
+ */