@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
@@ -2,5 +2,6 @@ export const canonicalCandidateTerm: (value: unknown, options?: { technicalStopw
2
2
  export const ensureTrailingNewline: (value: string) => string;
3
3
  export const extractRankedTerms: (markdown: string, options?: { technicalStopwords?: boolean }) => string[];
4
4
  export const idHintify: (value: unknown) => string;
5
+ export const pluralizeCandidateTerm: (value: unknown) => string;
5
6
  export const slugify: (value: unknown) => string;
6
7
  export const titleCase: (value: unknown) => string;
@@ -0,0 +1,69 @@
1
+ export interface TopogramLocation {
2
+ file?: string;
3
+ start?: {
4
+ line?: number;
5
+ column?: number;
6
+ offset?: number;
7
+ };
8
+ end?: {
9
+ line?: number;
10
+ column?: number;
11
+ offset?: number;
12
+ };
13
+ }
14
+
15
+ export interface TopogramToken {
16
+ type: string;
17
+ value: any;
18
+ items: TopogramToken[];
19
+ entries: TopogramBlockEntry[];
20
+ loc: TopogramLocation;
21
+ [key: string]: any;
22
+ }
23
+
24
+ export interface TopogramBlockEntry {
25
+ items: TopogramToken[];
26
+ loc: TopogramLocation;
27
+ [key: string]: any;
28
+ }
29
+
30
+ export interface TopogramField {
31
+ key: string;
32
+ value: TopogramToken;
33
+ loc: TopogramLocation;
34
+ [key: string]: any;
35
+ }
36
+
37
+ export interface TopogramStatement {
38
+ id: string;
39
+ kind: string;
40
+ fields: TopogramField[];
41
+ loc: TopogramLocation;
42
+ [key: string]: any;
43
+ }
44
+
45
+ export interface ValidationError {
46
+ message: string;
47
+ loc?: TopogramLocation | null;
48
+ }
49
+
50
+ export type ValidationErrors = ValidationError[];
51
+
52
+ export type TopogramFieldMap = Map<string, TopogramField[]>;
53
+
54
+ export type TopogramRegistry = Map<string, TopogramStatement>;
55
+
56
+ export type ResolverBacklinkIndex = Record<string, Map<string, string[]>>;
57
+
58
+ declare global {
59
+ type TopogramLocation = import("./topogram-types.js").TopogramLocation;
60
+ type TopogramToken = import("./topogram-types.js").TopogramToken;
61
+ type TopogramBlockEntry = import("./topogram-types.js").TopogramBlockEntry;
62
+ type TopogramField = import("./topogram-types.js").TopogramField;
63
+ type TopogramStatement = import("./topogram-types.js").TopogramStatement;
64
+ type ValidationError = import("./topogram-types.js").ValidationError;
65
+ type ValidationErrors = import("./topogram-types.js").ValidationErrors;
66
+ type TopogramFieldMap = import("./topogram-types.js").TopogramFieldMap;
67
+ type TopogramRegistry = import("./topogram-types.js").TopogramRegistry;
68
+ type ResolverBacklinkIndex = import("./topogram-types.js").ResolverBacklinkIndex;
69
+ }
@@ -0,0 +1,488 @@
1
+ // @ts-check
2
+
3
+ import {
4
+ FIELD_SPECS,
5
+ GLOBAL_STATUSES,
6
+ RULE_SEVERITIES,
7
+ STATEMENT_KINDS,
8
+ STATUS_SETS_BY_KIND,
9
+ VERIFICATION_METHODS
10
+ } from "./kinds.js";
11
+ import {
12
+ pushError,
13
+ symbolValue,
14
+ valueAsArray
15
+ } from "./utils.js";
16
+
17
+ /**
18
+ * @param {string} oldName
19
+ * @param {string} newName
20
+ * @param {string} example
21
+ * @returns {string}
22
+ */
23
+ function renameDiagnostic(oldName, newName, example) {
24
+ return `${oldName} was renamed to ${newName}. Example fix: ${example}`;
25
+ }
26
+
27
+ /**
28
+ * @param {ValidationErrors} errors
29
+ * @param {TopogramStatement} statement
30
+ * @param {TopogramFieldMap} fieldMap
31
+ * @param {string} key
32
+ * @param {string[]} expectedTypes
33
+ * @returns {void}
34
+ */
35
+ function ensureSingleValueField(errors, statement, fieldMap, key, expectedTypes) {
36
+ const fields = fieldMap.get(key) || [];
37
+ if (fields.length > 1) {
38
+ for (const field of fields.slice(1)) {
39
+ pushError(errors, `Duplicate field '${key}' on ${statement.kind} ${statement.id}`, field.loc);
40
+ }
41
+ }
42
+
43
+ const field = fields[0];
44
+ if (!field) {
45
+ return;
46
+ }
47
+
48
+ if (!expectedTypes.includes(field.value.type)) {
49
+ pushError(
50
+ errors,
51
+ `Field '${key}' on ${statement.kind} ${statement.id} must be ${expectedTypes.join(" or ")}, found ${field.value.type}`,
52
+ field.loc
53
+ );
54
+ }
55
+ }
56
+
57
+ /**
58
+ * @param {ValidationErrors} errors
59
+ * @param {TopogramStatement} statement
60
+ * @param {TopogramFieldMap} fieldMap
61
+ * @returns {void}
62
+ */
63
+ function validateFieldPresence(errors, statement, fieldMap) {
64
+ const spec = FIELD_SPECS[statement.kind];
65
+ if (!spec) {
66
+ return;
67
+ }
68
+
69
+ /** @type {Map<string, [string, string]>} */
70
+ const renamedFields = new Map([
71
+ ["platform", ["type", "type web_surface"]],
72
+ ["ui_components", ["widget_bindings", "widget_bindings { screen item_list region results widget widget_data_grid }"]],
73
+ ["ui_design", ["design_tokens", "design_tokens { density comfortable tone operational }"]],
74
+ ["ui_routes", ["screen_routes", "screen_routes { screen item_list path /items }"]],
75
+ ["ui_screens", ["screens", "screens { item_list list title \"Items\" }"]],
76
+ ["ui_screen_regions", ["screen_regions", "screen_regions { screen item_list region results pattern resource_table }"]],
77
+ ["ui_navigation", ["navigation", "navigation { primary item_list label \"Items\" }"]],
78
+ ["ui_app_shell", ["app_shell", "app_shell { shell sidebar }"]],
79
+ ["ui_collections", ["collection_views", "collection_views { item_list presentation table }"]],
80
+ ["ui_actions", ["screen_actions", "screen_actions { screen item_list action create target cap_create_item }"]],
81
+ ["ui_visibility", ["visibility_rules", "visibility_rules { screen item_list when cap_list_items }"]],
82
+ ["ui_lookups", ["field_lookups", "field_lookups { field owner_id source cap_list_users }"]],
83
+ ["web_surface", ["web_hints", "web_hints { router file_based }"]],
84
+ ["ios_surface", ["ios_hints", "ios_hints { navigation stack }"]],
85
+ ["http", ["endpoints", "endpoints { cap_list_items method GET path /items success 200 }"]],
86
+ ["http_errors", ["error_responses", "error_responses { cap_list_items 404 shape_error }"]],
87
+ ["http_fields", ["wire_fields", "wire_fields { shape_item title title }"]],
88
+ ["http_responses", ["responses", "responses { cap_list_items 200 shape_item_list }"]],
89
+ ["http_preconditions", ["preconditions", "preconditions { cap_update_item rule_item_exists }"]],
90
+ ["http_idempotency", ["idempotency", "idempotency { cap_create_item key request_id }"]],
91
+ ["http_cache", ["cache", "cache { cap_list_items max_age 60 }"]],
92
+ ["http_delete", ["delete_semantics", "delete_semantics { cap_delete_item mode soft_delete }"]],
93
+ ["http_async", ["async_jobs", "async_jobs { cap_export_items job task_export }"]],
94
+ ["http_status", ["async_status", "async_status { cap_export_items path /exports/{job_id} }"]],
95
+ ["http_download", ["downloads", "downloads { cap_download_export content_type text/csv }"]],
96
+ ["http_authz", ["authorization", "authorization { cap_update_item role editor }"]],
97
+ ["http_callbacks", ["callbacks", "callbacks { cap_export_items event completed }"]],
98
+ ["db_tables", ["tables", "tables { entity_item table items }"]],
99
+ ["db_columns", ["columns", "columns { entity_item field title column title }"]],
100
+ ["db_keys", ["keys", "keys { entity_item primary [id] }"]],
101
+ ["db_indexes", ["indexes", "indexes { entity_item index [title] }"]],
102
+ ["db_relations", ["relations", "relations { entity_item foreign_key owner_id references entity_user.id }"]],
103
+ ["db_lifecycle", ["lifecycle", "lifecycle { entity_item timestamps created_at created_at updated_at updated_at }"]]
104
+ ]);
105
+
106
+ for (const key of fieldMap.keys()) {
107
+ const field = fieldMap.get(key)?.[0];
108
+ const renamedField = renamedFields.get(key);
109
+ if (renamedField) {
110
+ const [newName, example] = renamedField;
111
+ pushError(errors, `Field '${key}' on ${statement.kind} ${statement.id} ${renameDiagnostic(`'${key}'`, `'${newName}'`, example)}`, field?.loc);
112
+ continue;
113
+ }
114
+ if (!spec.allowed.includes(key)) {
115
+ pushError(errors, `Field '${key}' is not allowed on ${statement.kind} ${statement.id}`, field?.loc);
116
+ }
117
+ }
118
+
119
+ for (const key of spec.required) {
120
+ if (!fieldMap.has(key)) {
121
+ pushError(errors, `Missing required field '${key}' on ${statement.kind} ${statement.id}`, statement.loc);
122
+ }
123
+ }
124
+ }
125
+
126
+ /**
127
+ * @param {ValidationErrors} errors
128
+ * @param {TopogramStatement} statement
129
+ * @param {TopogramFieldMap} fieldMap
130
+ * @returns {void}
131
+ */
132
+ function validateProjectionTypeRenames(errors, statement, fieldMap) {
133
+ if (statement.kind !== "projection") {
134
+ return;
135
+ }
136
+
137
+ const typeField = fieldMap.get("type")?.[0];
138
+ const typeValue = symbolValue(typeField?.value);
139
+ const renamedTypes = new Map([
140
+ ["ui_shared", "ui_contract"],
141
+ ["ui_web", "web_surface"],
142
+ ["ui_ios", "ios_surface"],
143
+ ["ui_android", "android_surface"],
144
+ ["dotnet", "api_contract"],
145
+ ["api", "api_contract"],
146
+ ["backend", "api_contract"],
147
+ ["db_postgres", "db_contract"],
148
+ ["db_sqlite", "db_contract"]
149
+ ]);
150
+ if (!typeField || !typeValue || !renamedTypes.has(typeValue)) {
151
+ return;
152
+ }
153
+
154
+ const nextType = renamedTypes.get(typeValue);
155
+ if (!nextType) {
156
+ return;
157
+ }
158
+ pushError(
159
+ errors,
160
+ `Projection ${statement.id} ${renameDiagnostic(`type value '${typeValue}'`, `'${nextType}'`, `type ${nextType}`)}`,
161
+ typeField.value.loc
162
+ );
163
+ }
164
+
165
+ /**
166
+ * @param {ValidationErrors} errors
167
+ * @param {TopogramStatement} statement
168
+ * @param {TopogramFieldMap} fieldMap
169
+ * @param {string} key
170
+ * @param {number} minimumWidth
171
+ * @returns {void}
172
+ */
173
+ function validateBlockEntryLengths(errors, statement, fieldMap, key, minimumWidth) {
174
+ const field = fieldMap.get(key)?.[0];
175
+ if (!field || field.value.type !== "block") {
176
+ return;
177
+ }
178
+
179
+ for (const entry of field.value.entries) {
180
+ if (entry.items.length < minimumWidth) {
181
+ pushError(errors, `Each '${key}' entry on ${statement.kind} ${statement.id} must have at least ${minimumWidth} token(s)`, entry.loc);
182
+ }
183
+ }
184
+ }
185
+
186
+ /**
187
+ * @param {ValidationErrors} errors
188
+ * @param {TopogramStatement} statement
189
+ * @param {TopogramFieldMap} fieldMap
190
+ * @returns {void}
191
+ */
192
+ function validateFieldShapes(errors, statement, fieldMap) {
193
+ ensureSingleValueField(errors, statement, fieldMap, "name", ["string"]);
194
+ ensureSingleValueField(errors, statement, fieldMap, "description", ["string"]);
195
+ ensureSingleValueField(errors, statement, fieldMap, "status", ["symbol"]);
196
+ ensureSingleValueField(errors, statement, fieldMap, "type", ["symbol"]);
197
+ ensureSingleValueField(errors, statement, fieldMap, "method", ["symbol"]);
198
+ ensureSingleValueField(errors, statement, fieldMap, "severity", ["symbol"]);
199
+ ensureSingleValueField(errors, statement, fieldMap, "category", ["symbol"]);
200
+ ensureSingleValueField(errors, statement, fieldMap, "version", ["string"]);
201
+
202
+ for (const key of [
203
+ "aliases",
204
+ "excludes",
205
+ "uses_terms",
206
+ "include",
207
+ "exclude",
208
+ "derived_from",
209
+ "applies_to",
210
+ "actors",
211
+ "roles",
212
+ "reads",
213
+ "creates",
214
+ "updates",
215
+ "deletes",
216
+ "input",
217
+ "output",
218
+ "context",
219
+ "consequences",
220
+ "realizes",
221
+ "outputs",
222
+ "inputs",
223
+ "steps",
224
+ "validates",
225
+ "scenarios",
226
+ "observes",
227
+ "metrics",
228
+ "alerts",
229
+ "source_of_truth",
230
+ "behavior",
231
+ "patterns",
232
+ "regions",
233
+ "lookups",
234
+ "dependencies",
235
+ "approvals"
236
+ ]) {
237
+ ensureSingleValueField(errors, statement, fieldMap, key, ["list"]);
238
+ }
239
+
240
+ for (const key of ["fields", "props", "events", "slots", "behaviors", "keys", "relations", "invariants", "rename", "overrides", "endpoints", "error_responses", "wire_fields", "responses", "preconditions", "idempotency", "cache", "delete_semantics", "async_jobs", "async_status", "downloads", "authorization", "callbacks", "screens", "collection_views", "screen_actions", "visibility_rules", "field_lookups", "screen_routes", "web_hints", "ios_hints", "app_shell", "navigation", "screen_regions", "widget_bindings", "design_tokens", "tables", "columns", "keys", "indexes", "relations", "lifecycle", "generator_defaults"]) {
241
+ ensureSingleValueField(errors, statement, fieldMap, key, ["block"]);
242
+ }
243
+
244
+ validateBlockEntryLengths(errors, statement, fieldMap, "fields", 2);
245
+ validateBlockEntryLengths(errors, statement, fieldMap, "props", 3);
246
+ validateBlockEntryLengths(errors, statement, fieldMap, "events", 2);
247
+ validateBlockEntryLengths(errors, statement, fieldMap, "slots", 2);
248
+ validateBlockEntryLengths(errors, statement, fieldMap, "invariants", 2);
249
+ validateBlockEntryLengths(errors, statement, fieldMap, "generator_defaults", 2);
250
+
251
+ if (statement.kind === "entity") {
252
+ validateBlockEntryLengths(errors, statement, fieldMap, "keys", 2);
253
+ validateBlockEntryLengths(errors, statement, fieldMap, "relations", 3);
254
+ }
255
+
256
+ if (statement.kind === "projection") {
257
+ validateBlockEntryLengths(errors, statement, fieldMap, "endpoints", 7);
258
+ validateBlockEntryLengths(errors, statement, fieldMap, "error_responses", 3);
259
+ validateBlockEntryLengths(errors, statement, fieldMap, "wire_fields", 5);
260
+ validateBlockEntryLengths(errors, statement, fieldMap, "responses", 3);
261
+ validateBlockEntryLengths(errors, statement, fieldMap, "preconditions", 9);
262
+ validateBlockEntryLengths(errors, statement, fieldMap, "idempotency", 7);
263
+ validateBlockEntryLengths(errors, statement, fieldMap, "cache", 11);
264
+ validateBlockEntryLengths(errors, statement, fieldMap, "delete_semantics", 7);
265
+ validateBlockEntryLengths(errors, statement, fieldMap, "async_jobs", 11);
266
+ validateBlockEntryLengths(errors, statement, fieldMap, "async_status", 11);
267
+ validateBlockEntryLengths(errors, statement, fieldMap, "downloads", 7);
268
+ validateBlockEntryLengths(errors, statement, fieldMap, "authorization", 3);
269
+ validateBlockEntryLengths(errors, statement, fieldMap, "callbacks", 11);
270
+ validateBlockEntryLengths(errors, statement, fieldMap, "screens", 4);
271
+ validateBlockEntryLengths(errors, statement, fieldMap, "collection_views", 4);
272
+ validateBlockEntryLengths(errors, statement, fieldMap, "screen_actions", 6);
273
+ validateBlockEntryLengths(errors, statement, fieldMap, "visibility_rules", 5);
274
+ validateBlockEntryLengths(errors, statement, fieldMap, "field_lookups", 8);
275
+ validateBlockEntryLengths(errors, statement, fieldMap, "screen_routes", 4);
276
+ validateBlockEntryLengths(errors, statement, fieldMap, "web_hints", 4);
277
+ validateBlockEntryLengths(errors, statement, fieldMap, "ios_hints", 4);
278
+ validateBlockEntryLengths(errors, statement, fieldMap, "app_shell", 2);
279
+ validateBlockEntryLengths(errors, statement, fieldMap, "navigation", 2);
280
+ validateBlockEntryLengths(errors, statement, fieldMap, "screen_regions", 4);
281
+ validateBlockEntryLengths(errors, statement, fieldMap, "tables", 3);
282
+ validateBlockEntryLengths(errors, statement, fieldMap, "columns", 5);
283
+ validateBlockEntryLengths(errors, statement, fieldMap, "keys", 3);
284
+ validateBlockEntryLengths(errors, statement, fieldMap, "indexes", 3);
285
+ validateBlockEntryLengths(errors, statement, fieldMap, "relations", 6);
286
+ validateBlockEntryLengths(errors, statement, fieldMap, "lifecycle", 3);
287
+ }
288
+ }
289
+
290
+ /**
291
+ * @param {ValidationErrors} errors
292
+ * @param {TopogramStatement} statement
293
+ * @param {TopogramFieldMap} fieldMap
294
+ * @returns {void}
295
+ */
296
+ function validateStatus(errors, statement, fieldMap) {
297
+ const field = fieldMap.get("status")?.[0];
298
+ if (!field || field.value.type !== "symbol") {
299
+ return;
300
+ }
301
+
302
+ // Per-kind status table takes precedence (decision and SDLC kinds), with
303
+ // GLOBAL_STATUSES as the default.
304
+ const allowed = STATUS_SETS_BY_KIND[statement.kind] || GLOBAL_STATUSES;
305
+ if (!allowed.has(field.value.value)) {
306
+ pushError(errors, `Invalid status '${field.value.value}' on ${statement.kind} ${statement.id}`, field.loc);
307
+ }
308
+ }
309
+
310
+ /**
311
+ * @param {ValidationErrors} errors
312
+ * @param {TopogramStatement} statement
313
+ * @param {TopogramFieldMap} fieldMap
314
+ * @returns {void}
315
+ */
316
+ function validateRuleSeverity(errors, statement, fieldMap) {
317
+ if (statement.kind !== "rule") {
318
+ return;
319
+ }
320
+
321
+ const field = fieldMap.get("severity")?.[0];
322
+ if (!field) {
323
+ return;
324
+ }
325
+
326
+ if (field.value.type === "symbol" && !RULE_SEVERITIES.has(field.value.value)) {
327
+ pushError(errors, `Invalid severity '${field.value.value}' on rule ${statement.id}`, field.loc);
328
+ }
329
+ }
330
+
331
+ /**
332
+ * @param {ValidationErrors} errors
333
+ * @param {TopogramStatement} statement
334
+ * @param {TopogramFieldMap} fieldMap
335
+ * @returns {void}
336
+ */
337
+ function validateVerification(errors, statement, fieldMap) {
338
+ if (statement.kind !== "verification") {
339
+ return;
340
+ }
341
+
342
+ const methodField = fieldMap.get("method")?.[0];
343
+ if (methodField?.value.type === "symbol" && !VERIFICATION_METHODS.has(methodField.value.value)) {
344
+ pushError(
345
+ errors,
346
+ `Invalid verification method '${methodField.value.value}' on verification ${statement.id}`,
347
+ methodField.loc
348
+ );
349
+ }
350
+
351
+ const scenariosField = fieldMap.get("scenarios")?.[0];
352
+ if (!scenariosField || scenariosField.value.type !== "list") {
353
+ return;
354
+ }
355
+
356
+ if (scenariosField.value.items.length === 0) {
357
+ pushError(errors, `Verification ${statement.id} must include at least one scenario`, scenariosField.loc);
358
+ return;
359
+ }
360
+
361
+ for (const item of scenariosField.value.items) {
362
+ if (item.type !== "symbol" && item.type !== "string") {
363
+ pushError(errors, `Verification ${statement.id} scenarios must use symbols or strings`, item.loc);
364
+ }
365
+ }
366
+ }
367
+
368
+ /**
369
+ * @param {ValidationErrors} errors
370
+ * @param {TopogramStatement} statement
371
+ * @param {TopogramFieldMap} fieldMap
372
+ * @param {TopogramRegistry} registry
373
+ * @returns {void}
374
+ */
375
+ function validateReferenceKinds(errors, statement, fieldMap, registry) {
376
+ // Phase 2: SDLC kinds add several reference fields. The `affects` field is
377
+ // polymorphic — pitches/requirements/tasks/bugs all use it, so we keep the
378
+ // target set wide. `pitch` is single-id but lives in the same map for
379
+ // uniform validation.
380
+ const expectedByField = {
381
+ uses_terms: ["term"],
382
+ derived_from: ["entity"],
383
+ applies_to: ["capability"],
384
+ source_of_truth: ["decision"],
385
+ actors: ["actor"],
386
+ roles: ["role"],
387
+ reads: ["entity"],
388
+ creates: ["entity"],
389
+ updates: ["entity"],
390
+ deletes: ["entity"],
391
+ input: ["shape"],
392
+ output: ["shape"],
393
+ dependencies: [...STATEMENT_KINDS],
394
+ realizes: ["capability", "projection", "entity"],
395
+ validates: [...STATEMENT_KINDS],
396
+ observes: [...STATEMENT_KINDS],
397
+ inputs: [...STATEMENT_KINDS],
398
+ outputs: null,
399
+ steps: null,
400
+ scenarios: null,
401
+ metrics: null,
402
+ alerts: null,
403
+ aliases: null,
404
+ excludes: null,
405
+ include: null,
406
+ exclude: null,
407
+ context: null,
408
+ consequences: null,
409
+ pitch: ["pitch"],
410
+ requirement: null,
411
+ from_requirement: ["requirement"],
412
+ affects: ["capability", "entity", "rule", "projection", "widget", "orchestration", "operation"],
413
+ introduces_rules: ["rule"],
414
+ respects_rules: ["rule"],
415
+ decisions: ["decision"],
416
+ introduces_decisions: ["decision"],
417
+ satisfies: ["requirement", "acceptance_criterion"],
418
+ acceptance_refs: ["acceptance_criterion"],
419
+ requirement_refs: ["requirement"],
420
+ fixes_bugs: ["bug"],
421
+ blocks: ["task"],
422
+ blocked_by: ["task"],
423
+ claimed_by: ["actor", "role"],
424
+ violates: ["rule"],
425
+ surfaces_rule: ["rule"],
426
+ introduced_in: ["task", "bug"],
427
+ fixed_in: ["task"],
428
+ fixed_in_verification: ["verification"],
429
+ supersedes: null,
430
+ modifies: [...STATEMENT_KINDS],
431
+ introduces: [...STATEMENT_KINDS],
432
+ removes: [...STATEMENT_KINDS]
433
+ };
434
+
435
+ for (const [key, allowedKinds] of Object.entries(expectedByField)) {
436
+ const field = fieldMap.get(key)?.[0];
437
+ if (!field || !allowedKinds) {
438
+ continue;
439
+ }
440
+
441
+ for (const item of valueAsArray(field.value)) {
442
+ if (item.type !== "symbol") {
443
+ pushError(errors, `Field '${key}' on ${statement.kind} ${statement.id} must only contain symbols`, item.loc);
444
+ continue;
445
+ }
446
+
447
+ const target = registry.get(item.value);
448
+ if (!target) {
449
+ pushError(errors, `Missing reference '${item.value}' in field '${key}' on ${statement.kind} ${statement.id}`, item.loc);
450
+ continue;
451
+ }
452
+
453
+ if (!allowedKinds.includes(target.kind)) {
454
+ pushError(
455
+ errors,
456
+ `Field '${key}' on ${statement.kind} ${statement.id} must reference ${allowedKinds.join(" or ")}, found ${target.kind} '${target.id}'`,
457
+ item.loc
458
+ );
459
+ }
460
+ }
461
+ }
462
+ }
463
+
464
+ /**
465
+ * @param {ValidationErrors} errors
466
+ * @param {TopogramStatement} statement
467
+ * @param {TopogramFieldMap} fieldMap
468
+ * @returns {void}
469
+ */
470
+ export function validateCoreStatement(errors, statement, fieldMap) {
471
+ validateFieldPresence(errors, statement, fieldMap);
472
+ validateProjectionTypeRenames(errors, statement, fieldMap);
473
+ validateFieldShapes(errors, statement, fieldMap);
474
+ validateStatus(errors, statement, fieldMap);
475
+ validateRuleSeverity(errors, statement, fieldMap);
476
+ validateVerification(errors, statement, fieldMap);
477
+ }
478
+
479
+ /**
480
+ * @param {ValidationErrors} errors
481
+ * @param {TopogramStatement} statement
482
+ * @param {TopogramFieldMap} fieldMap
483
+ * @param {TopogramRegistry} registry
484
+ * @returns {void}
485
+ */
486
+ export function validateReferenceRules(errors, statement, fieldMap, registry) {
487
+ validateReferenceKinds(errors, statement, fieldMap, registry);
488
+ }