@topogram/cli 0.3.64 → 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 (245) hide show
  1. package/package.json +1 -1
  2. package/src/adoption/plan/index.js +703 -0
  3. package/src/adoption/plan.js +12 -703
  4. package/src/agent-ops/query-builders/auth.js +375 -0
  5. package/src/agent-ops/query-builders/change-risk/change-plan.js +123 -0
  6. package/src/agent-ops/query-builders/change-risk/import-plan.js +49 -0
  7. package/src/agent-ops/query-builders/change-risk/maintained.js +286 -0
  8. package/src/agent-ops/query-builders/change-risk/review-packets.js +123 -0
  9. package/src/agent-ops/query-builders/change-risk/risk.js +189 -0
  10. package/src/agent-ops/query-builders/change-risk.js +25 -0
  11. package/src/agent-ops/query-builders/common.js +149 -0
  12. package/src/agent-ops/query-builders/maintained-risk.js +539 -0
  13. package/src/agent-ops/query-builders/maintained-shared.js +120 -0
  14. package/src/agent-ops/query-builders/multi-agent.js +547 -0
  15. package/src/agent-ops/query-builders/projection-impacts.js +514 -0
  16. package/src/agent-ops/query-builders/work-packets.js +417 -0
  17. package/src/agent-ops/query-builders/workflow-context-shared.js +300 -0
  18. package/src/agent-ops/query-builders/workflow-context.js +398 -0
  19. package/src/agent-ops/query-builders/workflow-presets-core.js +676 -0
  20. package/src/agent-ops/query-builders/workflow-presets.js +341 -0
  21. package/src/agent-ops/query-builders.d.ts +26 -26
  22. package/src/agent-ops/query-builders.js +42 -5021
  23. package/src/catalog/constants.js +10 -0
  24. package/src/catalog/copy.js +60 -0
  25. package/src/catalog/diagnostics.js +15 -0
  26. package/src/catalog/entries.js +42 -0
  27. package/src/catalog/files.js +67 -0
  28. package/src/catalog/provenance.js +122 -0
  29. package/src/catalog/source.js +150 -0
  30. package/src/catalog/validation.js +252 -0
  31. package/src/catalog.d.ts +2 -0
  32. package/src/catalog.js +18 -746
  33. package/src/cli/commands/catalog/check.js +31 -0
  34. package/src/cli/commands/catalog/copy.js +59 -0
  35. package/src/cli/commands/catalog/doctor.js +248 -0
  36. package/src/cli/commands/catalog/help.js +21 -0
  37. package/src/cli/commands/catalog/list.js +52 -0
  38. package/src/cli/commands/catalog/runner.js +92 -0
  39. package/src/cli/commands/catalog/shared.js +17 -0
  40. package/src/cli/commands/catalog/show.js +134 -0
  41. package/src/cli/commands/catalog.js +30 -615
  42. package/src/cli/commands/generator-policy/package-info.js +162 -0
  43. package/src/cli/commands/generator-policy/payloads.js +372 -0
  44. package/src/cli/commands/generator-policy/printers.js +159 -0
  45. package/src/cli/commands/generator-policy/runner.js +81 -0
  46. package/src/cli/commands/generator-policy/shared.js +39 -0
  47. package/src/cli/commands/generator-policy.js +15 -783
  48. package/src/cli/commands/import/adopt.js +170 -0
  49. package/src/cli/commands/import/check.js +91 -0
  50. package/src/cli/commands/import/diff.js +84 -0
  51. package/src/cli/commands/import/help.js +47 -0
  52. package/src/cli/commands/import/paths.js +277 -0
  53. package/src/cli/commands/import/plan.js +284 -0
  54. package/src/cli/commands/import/refresh.js +470 -0
  55. package/src/cli/commands/import/status-history.js +196 -0
  56. package/src/cli/commands/import/workspace.js +230 -0
  57. package/src/cli/commands/import.js +33 -1732
  58. package/src/cli/commands/package/constants.js +17 -0
  59. package/src/cli/commands/package/doctor.js +240 -0
  60. package/src/cli/commands/package/help.js +27 -0
  61. package/src/cli/commands/package/lockfile.js +135 -0
  62. package/src/cli/commands/package/npm.js +97 -0
  63. package/src/cli/commands/package/reporting.js +35 -0
  64. package/src/cli/commands/package/runner.js +33 -0
  65. package/src/cli/commands/package/shared.js +9 -0
  66. package/src/cli/commands/package/update-cli.js +252 -0
  67. package/src/cli/commands/package/versions.js +35 -0
  68. package/src/cli/commands/package.js +29 -813
  69. package/src/cli/commands/query/change-plan.js +68 -0
  70. package/src/cli/commands/query/definitions.js +202 -0
  71. package/src/cli/commands/query/import-adopt.js +121 -0
  72. package/src/cli/commands/query/runner/artifacts.js +102 -0
  73. package/src/cli/commands/query/runner/boundaries.js +211 -0
  74. package/src/cli/commands/query/runner/change.js +182 -0
  75. package/src/cli/commands/query/runner/import-adopt.js +111 -0
  76. package/src/cli/commands/query/runner/index.js +31 -0
  77. package/src/cli/commands/query/runner/output.js +12 -0
  78. package/src/cli/commands/query/runner/workflow.js +241 -0
  79. package/src/cli/commands/query/runner.js +3 -0
  80. package/src/cli/commands/query/workflow-context.js +5 -0
  81. package/src/cli/commands/query/workspace.js +274 -0
  82. package/src/cli/commands/query.js +9 -1300
  83. package/src/cli/commands/template/baseline.js +100 -0
  84. package/src/cli/commands/template/check.js +466 -0
  85. package/src/cli/commands/template/constants.js +8 -0
  86. package/src/cli/commands/template/diagnostics.js +26 -0
  87. package/src/cli/commands/template/help.js +28 -0
  88. package/src/cli/commands/template/lifecycle.js +404 -0
  89. package/src/cli/commands/template/list-show.js +287 -0
  90. package/src/cli/commands/template/policy.js +422 -0
  91. package/src/cli/commands/template/shared.js +127 -0
  92. package/src/cli/commands/template/updates.js +352 -0
  93. package/src/cli/commands/template.js +41 -2143
  94. package/src/generator/api/contracts.js +497 -0
  95. package/src/generator/api/metadata.js +221 -0
  96. package/src/generator/api/openapi.js +559 -0
  97. package/src/generator/api/schema.js +124 -0
  98. package/src/generator/api/types.d.ts +98 -0
  99. package/src/generator/api.js +3 -1195
  100. package/src/generator/context/shared/domain-sdlc.js +282 -0
  101. package/src/generator/context/shared/maintained-boundary.js +665 -0
  102. package/src/generator/context/shared/metrics.js +85 -0
  103. package/src/generator/context/shared/primitives.js +64 -0
  104. package/src/generator/context/shared/relationships.js +453 -0
  105. package/src/generator/context/shared/summaries.js +263 -0
  106. package/src/generator/context/shared/types.d.ts +207 -0
  107. package/src/generator/context/shared.d.ts +42 -0
  108. package/src/generator/context/shared.js +80 -1390
  109. package/src/generator/context/slice/core.js +397 -0
  110. package/src/generator/context/slice/sdlc.js +417 -0
  111. package/src/generator/context/slice/ui-packets.js +183 -0
  112. package/src/generator/context/slice.js +2 -859
  113. package/src/generator/registry/index.js +507 -0
  114. package/src/generator/registry.js +18 -504
  115. package/src/generator/runtime/environment/index.js +666 -0
  116. package/src/generator/runtime/environment.js +4 -666
  117. package/src/generator/runtime/runtime-check/index.js +554 -0
  118. package/src/generator/runtime/runtime-check.js +4 -554
  119. package/src/generator/runtime/shared/index.js +572 -0
  120. package/src/generator/runtime/shared.js +19 -570
  121. package/src/generator/shared.d.ts +2 -0
  122. package/src/generator/surfaces/shared.d.ts +3 -0
  123. package/src/generator/widget-conformance/behavior-report.js +258 -0
  124. package/src/generator/widget-conformance/checks.js +371 -0
  125. package/src/generator/widget-conformance/projection-context.js +200 -0
  126. package/src/generator/widget-conformance/report.js +166 -0
  127. package/src/generator/widget-conformance/types.d.ts +121 -0
  128. package/src/generator/widget-conformance.js +3 -824
  129. package/src/import/core/context.d.ts +3 -0
  130. package/src/import/core/contracts.d.ts +1 -0
  131. package/src/import/core/registry.d.ts +4 -0
  132. package/src/import/core/runner/candidates.js +217 -0
  133. package/src/import/core/runner/options.js +22 -0
  134. package/src/import/core/runner/reports.js +50 -0
  135. package/src/import/core/runner/run.js +79 -0
  136. package/src/import/core/runner/tracks.js +150 -0
  137. package/src/import/core/runner/ui-drafts.js +337 -0
  138. package/src/import/core/runner.js +3 -698
  139. package/src/import/core/shared/api-routes.js +221 -0
  140. package/src/import/core/shared/candidates.js +97 -0
  141. package/src/import/core/shared/files.js +177 -0
  142. package/src/import/core/shared/next-app.js +389 -0
  143. package/src/import/core/shared/types.d.ts +51 -0
  144. package/src/import/core/shared/ui-routes.js +230 -0
  145. package/src/import/core/shared.js +60 -861
  146. package/src/new-project/constants.js +128 -0
  147. package/src/new-project/create.js +83 -0
  148. package/src/new-project/json.js +28 -0
  149. package/src/new-project/metadata.js +96 -0
  150. package/src/new-project/package-spec.js +161 -0
  151. package/src/new-project/project-files.js +348 -0
  152. package/src/new-project/template-policy.js +269 -0
  153. package/src/new-project/template-resolution.js +368 -0
  154. package/src/new-project/template-snapshots.js +430 -0
  155. package/src/new-project/template-updates.js +512 -0
  156. package/src/new-project/types.d.ts +83 -0
  157. package/src/new-project.js +6 -2277
  158. package/src/parser.d.ts +87 -1
  159. package/src/parser.js +118 -0
  160. package/src/policy/review-boundaries.d.ts +15 -0
  161. package/src/project-config/index.js +564 -0
  162. package/src/project-config.js +19 -561
  163. package/src/resolver/enrich/acceptance-criterion.js +2 -0
  164. package/src/resolver/enrich/bug.js +2 -0
  165. package/src/resolver/enrich/pitch.js +2 -0
  166. package/src/resolver/enrich/requirement.js +2 -0
  167. package/src/resolver/enrich/task.js +2 -0
  168. package/src/resolver/index.js +19 -2089
  169. package/src/resolver/normalize.js +384 -1
  170. package/src/resolver/plans.js +168 -0
  171. package/src/resolver/projections-api.js +494 -0
  172. package/src/resolver/projections-db.js +133 -0
  173. package/src/resolver/projections-ui.js +317 -0
  174. package/src/resolver/shapes.js +251 -0
  175. package/src/resolver/shared.js +278 -0
  176. package/src/resolver/widgets.js +132 -0
  177. package/src/template-trust/constants.js +62 -0
  178. package/src/template-trust/content.js +258 -0
  179. package/src/template-trust/diff.js +92 -0
  180. package/src/template-trust/policy.js +61 -0
  181. package/src/template-trust/record.js +90 -0
  182. package/src/template-trust/status.js +182 -0
  183. package/src/template-trust.js +24 -687
  184. package/src/text-helpers.d.ts +1 -0
  185. package/src/topogram-types.d.ts +69 -0
  186. package/src/validator/common.js +488 -0
  187. package/src/validator/data-model.js +237 -0
  188. package/src/validator/docs.js +167 -0
  189. package/src/validator/expressions.js +146 -1
  190. package/src/validator/index.d.ts +23 -0
  191. package/src/validator/index.js +32 -3585
  192. package/src/validator/kinds.d.ts +41 -0
  193. package/src/validator/kinds.js +2 -0
  194. package/src/validator/model-helpers.js +46 -0
  195. package/src/validator/per-kind/acceptance-criterion.js +5 -0
  196. package/src/validator/per-kind/bug.js +6 -0
  197. package/src/validator/per-kind/domain.js +15 -2
  198. package/src/validator/per-kind/pitch.js +7 -0
  199. package/src/validator/per-kind/requirement.js +5 -0
  200. package/src/validator/per-kind/task.js +7 -0
  201. package/src/validator/per-kind/widget.js +14 -0
  202. package/src/validator/projections/api-http-async.js +410 -0
  203. package/src/validator/projections/api-http-authz.js +88 -0
  204. package/src/validator/projections/api-http-core.js +205 -0
  205. package/src/validator/projections/api-http-policies.js +339 -0
  206. package/src/validator/projections/api-http-responses.js +233 -0
  207. package/src/validator/projections/api-http.js +44 -0
  208. package/src/validator/projections/db.js +353 -0
  209. package/src/validator/projections/generator-defaults.js +45 -0
  210. package/src/validator/projections/helpers.js +87 -0
  211. package/src/validator/projections/ui-helpers.js +214 -0
  212. package/src/validator/projections/ui-navigation.js +344 -0
  213. package/src/validator/projections/ui-structure.js +364 -0
  214. package/src/validator/projections/ui-widgets.js +493 -0
  215. package/src/validator/projections/ui.js +46 -0
  216. package/src/validator/registry.js +48 -1
  217. package/src/validator/utils.d.ts +20 -0
  218. package/src/validator/utils.js +115 -12
  219. package/src/widget-behavior.d.ts +1 -0
  220. package/src/workflows/import-app/api/collect.js +221 -0
  221. package/src/workflows/import-app/api/openapi.js +257 -0
  222. package/src/workflows/import-app/api/routes.js +327 -0
  223. package/src/workflows/import-app/api/sources.js +22 -0
  224. package/src/workflows/import-app/api.js +2 -797
  225. package/src/workflows/reconcile/adoption-plan/build.js +208 -0
  226. package/src/workflows/reconcile/adoption-plan/dependencies.js +75 -0
  227. package/src/workflows/reconcile/adoption-plan/outputs.js +143 -0
  228. package/src/workflows/reconcile/adoption-plan/paths.js +58 -0
  229. package/src/workflows/reconcile/adoption-plan/projection-patches.js +177 -0
  230. package/src/workflows/reconcile/adoption-plan/reasons.js +107 -0
  231. package/src/workflows/reconcile/adoption-plan.js +30 -740
  232. package/src/workflows/reconcile/auth/closures.js +115 -0
  233. package/src/workflows/reconcile/auth/formatters.js +142 -0
  234. package/src/workflows/reconcile/auth/inference.js +330 -0
  235. package/src/workflows/reconcile/auth/roles.js +122 -0
  236. package/src/workflows/reconcile/auth.js +35 -690
  237. package/src/workflows/reconcile/bundle-core/index.js +600 -0
  238. package/src/workflows/reconcile/bundle-core.js +12 -598
  239. package/src/workflows/reconcile/canonical-surface.js +1 -1
  240. package/src/workflows/reconcile/impacts/adoption-plan.js +192 -0
  241. package/src/workflows/reconcile/impacts/indexes.js +101 -0
  242. package/src/workflows/reconcile/impacts/patches.js +252 -0
  243. package/src/workflows/reconcile/impacts/reports.js +80 -0
  244. package/src/workflows/reconcile/impacts.js +14 -623
  245. 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
+ */