@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.
- package/package.json +1 -1
- package/src/adoption/plan/index.js +703 -0
- package/src/adoption/plan.js +12 -703
- package/src/agent-ops/query-builders/auth.js +375 -0
- package/src/agent-ops/query-builders/change-risk/change-plan.js +123 -0
- package/src/agent-ops/query-builders/change-risk/import-plan.js +49 -0
- package/src/agent-ops/query-builders/change-risk/maintained.js +286 -0
- package/src/agent-ops/query-builders/change-risk/review-packets.js +123 -0
- package/src/agent-ops/query-builders/change-risk/risk.js +189 -0
- package/src/agent-ops/query-builders/change-risk.js +25 -0
- package/src/agent-ops/query-builders/common.js +149 -0
- package/src/agent-ops/query-builders/maintained-risk.js +539 -0
- package/src/agent-ops/query-builders/maintained-shared.js +120 -0
- package/src/agent-ops/query-builders/multi-agent.js +547 -0
- package/src/agent-ops/query-builders/projection-impacts.js +514 -0
- package/src/agent-ops/query-builders/work-packets.js +417 -0
- package/src/agent-ops/query-builders/workflow-context-shared.js +300 -0
- package/src/agent-ops/query-builders/workflow-context.js +398 -0
- package/src/agent-ops/query-builders/workflow-presets-core.js +676 -0
- package/src/agent-ops/query-builders/workflow-presets.js +341 -0
- package/src/agent-ops/query-builders.d.ts +26 -26
- package/src/agent-ops/query-builders.js +42 -5021
- package/src/catalog/constants.js +10 -0
- package/src/catalog/copy.js +60 -0
- package/src/catalog/diagnostics.js +15 -0
- package/src/catalog/entries.js +42 -0
- package/src/catalog/files.js +67 -0
- package/src/catalog/provenance.js +122 -0
- package/src/catalog/source.js +150 -0
- package/src/catalog/validation.js +252 -0
- package/src/catalog.d.ts +2 -0
- package/src/catalog.js +18 -746
- package/src/cli/commands/catalog/check.js +31 -0
- package/src/cli/commands/catalog/copy.js +59 -0
- package/src/cli/commands/catalog/doctor.js +248 -0
- package/src/cli/commands/catalog/help.js +21 -0
- package/src/cli/commands/catalog/list.js +52 -0
- package/src/cli/commands/catalog/runner.js +92 -0
- package/src/cli/commands/catalog/shared.js +17 -0
- package/src/cli/commands/catalog/show.js +134 -0
- package/src/cli/commands/catalog.js +30 -615
- package/src/cli/commands/generator-policy/package-info.js +162 -0
- package/src/cli/commands/generator-policy/payloads.js +372 -0
- package/src/cli/commands/generator-policy/printers.js +159 -0
- package/src/cli/commands/generator-policy/runner.js +81 -0
- package/src/cli/commands/generator-policy/shared.js +39 -0
- package/src/cli/commands/generator-policy.js +15 -783
- package/src/cli/commands/import/adopt.js +170 -0
- package/src/cli/commands/import/check.js +91 -0
- package/src/cli/commands/import/diff.js +84 -0
- package/src/cli/commands/import/help.js +47 -0
- package/src/cli/commands/import/paths.js +277 -0
- package/src/cli/commands/import/plan.js +284 -0
- package/src/cli/commands/import/refresh.js +470 -0
- package/src/cli/commands/import/status-history.js +196 -0
- package/src/cli/commands/import/workspace.js +230 -0
- package/src/cli/commands/import.js +33 -1732
- package/src/cli/commands/package/constants.js +17 -0
- package/src/cli/commands/package/doctor.js +240 -0
- package/src/cli/commands/package/help.js +27 -0
- package/src/cli/commands/package/lockfile.js +135 -0
- package/src/cli/commands/package/npm.js +97 -0
- package/src/cli/commands/package/reporting.js +35 -0
- package/src/cli/commands/package/runner.js +33 -0
- package/src/cli/commands/package/shared.js +9 -0
- package/src/cli/commands/package/update-cli.js +252 -0
- package/src/cli/commands/package/versions.js +35 -0
- package/src/cli/commands/package.js +29 -813
- package/src/cli/commands/query/change-plan.js +68 -0
- package/src/cli/commands/query/definitions.js +202 -0
- package/src/cli/commands/query/import-adopt.js +121 -0
- package/src/cli/commands/query/runner/artifacts.js +102 -0
- package/src/cli/commands/query/runner/boundaries.js +211 -0
- package/src/cli/commands/query/runner/change.js +182 -0
- package/src/cli/commands/query/runner/import-adopt.js +111 -0
- package/src/cli/commands/query/runner/index.js +31 -0
- package/src/cli/commands/query/runner/output.js +12 -0
- package/src/cli/commands/query/runner/workflow.js +241 -0
- package/src/cli/commands/query/runner.js +3 -0
- package/src/cli/commands/query/workflow-context.js +5 -0
- package/src/cli/commands/query/workspace.js +274 -0
- package/src/cli/commands/query.js +9 -1300
- package/src/cli/commands/template/baseline.js +100 -0
- package/src/cli/commands/template/check.js +466 -0
- package/src/cli/commands/template/constants.js +8 -0
- package/src/cli/commands/template/diagnostics.js +26 -0
- package/src/cli/commands/template/help.js +28 -0
- package/src/cli/commands/template/lifecycle.js +404 -0
- package/src/cli/commands/template/list-show.js +287 -0
- package/src/cli/commands/template/policy.js +422 -0
- package/src/cli/commands/template/shared.js +127 -0
- package/src/cli/commands/template/updates.js +352 -0
- package/src/cli/commands/template.js +41 -2143
- package/src/generator/api/contracts.js +497 -0
- package/src/generator/api/metadata.js +221 -0
- package/src/generator/api/openapi.js +559 -0
- package/src/generator/api/schema.js +124 -0
- package/src/generator/api/types.d.ts +98 -0
- package/src/generator/api.js +3 -1195
- package/src/generator/context/shared/domain-sdlc.js +282 -0
- package/src/generator/context/shared/maintained-boundary.js +665 -0
- package/src/generator/context/shared/metrics.js +85 -0
- package/src/generator/context/shared/primitives.js +64 -0
- package/src/generator/context/shared/relationships.js +453 -0
- package/src/generator/context/shared/summaries.js +263 -0
- package/src/generator/context/shared/types.d.ts +207 -0
- package/src/generator/context/shared.d.ts +42 -0
- package/src/generator/context/shared.js +80 -1390
- package/src/generator/context/slice/core.js +397 -0
- package/src/generator/context/slice/sdlc.js +417 -0
- package/src/generator/context/slice/ui-packets.js +183 -0
- package/src/generator/context/slice.js +2 -859
- package/src/generator/registry/index.js +507 -0
- package/src/generator/registry.js +18 -504
- package/src/generator/runtime/environment/index.js +666 -0
- package/src/generator/runtime/environment.js +4 -666
- package/src/generator/runtime/runtime-check/index.js +554 -0
- package/src/generator/runtime/runtime-check.js +4 -554
- package/src/generator/runtime/shared/index.js +572 -0
- package/src/generator/runtime/shared.js +19 -570
- package/src/generator/shared.d.ts +2 -0
- package/src/generator/surfaces/shared.d.ts +3 -0
- package/src/generator/widget-conformance/behavior-report.js +258 -0
- package/src/generator/widget-conformance/checks.js +371 -0
- package/src/generator/widget-conformance/projection-context.js +200 -0
- package/src/generator/widget-conformance/report.js +166 -0
- package/src/generator/widget-conformance/types.d.ts +121 -0
- package/src/generator/widget-conformance.js +3 -824
- package/src/import/core/context.d.ts +3 -0
- package/src/import/core/contracts.d.ts +1 -0
- package/src/import/core/registry.d.ts +4 -0
- package/src/import/core/runner/candidates.js +217 -0
- package/src/import/core/runner/options.js +22 -0
- package/src/import/core/runner/reports.js +50 -0
- package/src/import/core/runner/run.js +79 -0
- package/src/import/core/runner/tracks.js +150 -0
- package/src/import/core/runner/ui-drafts.js +337 -0
- package/src/import/core/runner.js +3 -698
- package/src/import/core/shared/api-routes.js +221 -0
- package/src/import/core/shared/candidates.js +97 -0
- package/src/import/core/shared/files.js +177 -0
- package/src/import/core/shared/next-app.js +389 -0
- package/src/import/core/shared/types.d.ts +51 -0
- package/src/import/core/shared/ui-routes.js +230 -0
- package/src/import/core/shared.js +60 -861
- package/src/new-project/constants.js +128 -0
- package/src/new-project/create.js +83 -0
- package/src/new-project/json.js +28 -0
- package/src/new-project/metadata.js +96 -0
- package/src/new-project/package-spec.js +161 -0
- package/src/new-project/project-files.js +348 -0
- package/src/new-project/template-policy.js +269 -0
- package/src/new-project/template-resolution.js +368 -0
- package/src/new-project/template-snapshots.js +430 -0
- package/src/new-project/template-updates.js +512 -0
- package/src/new-project/types.d.ts +83 -0
- package/src/new-project.js +6 -2277
- package/src/parser.d.ts +87 -1
- package/src/parser.js +118 -0
- package/src/policy/review-boundaries.d.ts +15 -0
- package/src/project-config/index.js +564 -0
- package/src/project-config.js +19 -561
- package/src/resolver/enrich/acceptance-criterion.js +2 -0
- package/src/resolver/enrich/bug.js +2 -0
- package/src/resolver/enrich/pitch.js +2 -0
- package/src/resolver/enrich/requirement.js +2 -0
- package/src/resolver/enrich/task.js +2 -0
- package/src/resolver/index.js +19 -2089
- package/src/resolver/normalize.js +384 -1
- package/src/resolver/plans.js +168 -0
- package/src/resolver/projections-api.js +494 -0
- package/src/resolver/projections-db.js +133 -0
- package/src/resolver/projections-ui.js +317 -0
- package/src/resolver/shapes.js +251 -0
- package/src/resolver/shared.js +278 -0
- package/src/resolver/widgets.js +132 -0
- package/src/template-trust/constants.js +62 -0
- package/src/template-trust/content.js +258 -0
- package/src/template-trust/diff.js +92 -0
- package/src/template-trust/policy.js +61 -0
- package/src/template-trust/record.js +90 -0
- package/src/template-trust/status.js +182 -0
- package/src/template-trust.js +24 -687
- package/src/text-helpers.d.ts +1 -0
- package/src/topogram-types.d.ts +69 -0
- package/src/validator/common.js +488 -0
- package/src/validator/data-model.js +237 -0
- package/src/validator/docs.js +167 -0
- package/src/validator/expressions.js +146 -1
- package/src/validator/index.d.ts +23 -0
- package/src/validator/index.js +32 -3585
- package/src/validator/kinds.d.ts +41 -0
- package/src/validator/kinds.js +2 -0
- package/src/validator/model-helpers.js +46 -0
- package/src/validator/per-kind/acceptance-criterion.js +5 -0
- package/src/validator/per-kind/bug.js +6 -0
- package/src/validator/per-kind/domain.js +15 -2
- package/src/validator/per-kind/pitch.js +7 -0
- package/src/validator/per-kind/requirement.js +5 -0
- package/src/validator/per-kind/task.js +7 -0
- package/src/validator/per-kind/widget.js +14 -0
- package/src/validator/projections/api-http-async.js +410 -0
- package/src/validator/projections/api-http-authz.js +88 -0
- package/src/validator/projections/api-http-core.js +205 -0
- package/src/validator/projections/api-http-policies.js +339 -0
- package/src/validator/projections/api-http-responses.js +233 -0
- package/src/validator/projections/api-http.js +44 -0
- package/src/validator/projections/db.js +353 -0
- package/src/validator/projections/generator-defaults.js +45 -0
- package/src/validator/projections/helpers.js +87 -0
- package/src/validator/projections/ui-helpers.js +214 -0
- package/src/validator/projections/ui-navigation.js +344 -0
- package/src/validator/projections/ui-structure.js +364 -0
- package/src/validator/projections/ui-widgets.js +493 -0
- package/src/validator/projections/ui.js +46 -0
- package/src/validator/registry.js +48 -1
- package/src/validator/utils.d.ts +20 -0
- package/src/validator/utils.js +115 -12
- package/src/widget-behavior.d.ts +1 -0
- package/src/workflows/import-app/api/collect.js +221 -0
- package/src/workflows/import-app/api/openapi.js +257 -0
- package/src/workflows/import-app/api/routes.js +327 -0
- package/src/workflows/import-app/api/sources.js +22 -0
- package/src/workflows/import-app/api.js +2 -797
- package/src/workflows/reconcile/adoption-plan/build.js +208 -0
- package/src/workflows/reconcile/adoption-plan/dependencies.js +75 -0
- package/src/workflows/reconcile/adoption-plan/outputs.js +143 -0
- package/src/workflows/reconcile/adoption-plan/paths.js +58 -0
- package/src/workflows/reconcile/adoption-plan/projection-patches.js +177 -0
- package/src/workflows/reconcile/adoption-plan/reasons.js +107 -0
- package/src/workflows/reconcile/adoption-plan.js +30 -740
- package/src/workflows/reconcile/auth/closures.js +115 -0
- package/src/workflows/reconcile/auth/formatters.js +142 -0
- package/src/workflows/reconcile/auth/inference.js +330 -0
- package/src/workflows/reconcile/auth/roles.js +122 -0
- package/src/workflows/reconcile/auth.js +35 -690
- package/src/workflows/reconcile/bundle-core/index.js +600 -0
- package/src/workflows/reconcile/bundle-core.js +12 -598
- package/src/workflows/reconcile/canonical-surface.js +1 -1
- package/src/workflows/reconcile/impacts/adoption-plan.js +192 -0
- package/src/workflows/reconcile/impacts/indexes.js +101 -0
- package/src/workflows/reconcile/impacts/patches.js +252 -0
- package/src/workflows/reconcile/impacts/reports.js +80 -0
- package/src/workflows/reconcile/impacts.js +14 -623
- package/src/workspace-docs.d.ts +29 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
blockSymbolItems,
|
|
5
|
+
getFieldValue,
|
|
6
|
+
pushError,
|
|
7
|
+
symbolValues
|
|
8
|
+
} from "../utils.js";
|
|
9
|
+
import { resolveCapabilityContractFields } from "./helpers.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {ValidationErrors} errors
|
|
13
|
+
* @param {TopogramStatement} statement
|
|
14
|
+
* @param {TopogramFieldMap} fieldMap
|
|
15
|
+
* @param {TopogramRegistry} registry
|
|
16
|
+
* @returns {void}
|
|
17
|
+
*/
|
|
18
|
+
export function validateProjectionHttp(errors, statement, fieldMap, registry) {
|
|
19
|
+
if (statement.kind !== "projection") {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const httpField = fieldMap.get("endpoints")?.[0];
|
|
24
|
+
if (!httpField || httpField.value.type !== "block") {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const realized = new Set(symbolValues(getFieldValue(statement, "realizes")));
|
|
29
|
+
|
|
30
|
+
for (const entry of httpField.value.entries) {
|
|
31
|
+
const tokens = blockSymbolItems(entry).map((item) => item.value);
|
|
32
|
+
const capabilityId = tokens[0];
|
|
33
|
+
if (!capabilityId) {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const target = registry.get(capabilityId);
|
|
38
|
+
if (!target) {
|
|
39
|
+
pushError(errors, `Projection ${statement.id} http metadata references missing capability '${capabilityId}'`, entry.loc);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (target.kind !== "capability") {
|
|
43
|
+
pushError(errors, `Projection ${statement.id} http metadata must target a capability, found ${target.kind} '${target.id}'`, entry.loc);
|
|
44
|
+
}
|
|
45
|
+
if (!realized.has(capabilityId)) {
|
|
46
|
+
pushError(errors, `Projection ${statement.id} http metadata for '${capabilityId}' must also appear in 'realizes'`, entry.loc);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const directives = new Map();
|
|
50
|
+
for (let i = 1; i < tokens.length; i += 2) {
|
|
51
|
+
const key = tokens[i];
|
|
52
|
+
const value = tokens[i + 1];
|
|
53
|
+
if (!value) {
|
|
54
|
+
pushError(errors, `Projection ${statement.id} http metadata for '${capabilityId}' is missing a value for '${key}'`, entry.loc);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
directives.set(key, value);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
for (const requiredKey of ["method", "path", "success"]) {
|
|
61
|
+
if (!directives.has(requiredKey)) {
|
|
62
|
+
pushError(errors, `Projection ${statement.id} http metadata for '${capabilityId}' must include '${requiredKey}'`, entry.loc);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
for (const key of directives.keys()) {
|
|
67
|
+
if (!["method", "path", "success", "auth", "request"].includes(key)) {
|
|
68
|
+
pushError(errors, `Projection ${statement.id} http metadata for '${capabilityId}' has unknown directive '${key}'`, entry.loc);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const method = directives.get("method");
|
|
73
|
+
if (method && !["GET", "POST", "PUT", "PATCH", "DELETE"].includes(method)) {
|
|
74
|
+
pushError(errors, `Projection ${statement.id} http metadata for '${capabilityId}' has invalid method '${method}'`, entry.loc);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const path = directives.get("path");
|
|
78
|
+
if (path && !path.startsWith("/")) {
|
|
79
|
+
pushError(errors, `Projection ${statement.id} http metadata for '${capabilityId}' must use an absolute path`, entry.loc);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const success = directives.get("success");
|
|
83
|
+
if (success && !/^\d{3}$/.test(success)) {
|
|
84
|
+
pushError(errors, `Projection ${statement.id} http metadata for '${capabilityId}' must use a 3-digit success status`, entry.loc);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const auth = directives.get("auth");
|
|
88
|
+
if (auth && !["none", "user", "manager", "admin"].includes(auth)) {
|
|
89
|
+
pushError(errors, `Projection ${statement.id} http metadata for '${capabilityId}' has invalid auth mode '${auth}'`, entry.loc);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const request = directives.get("request");
|
|
93
|
+
if (request && !["body", "query", "path", "none"].includes(request)) {
|
|
94
|
+
pushError(errors, `Projection ${statement.id} http metadata for '${capabilityId}' has invalid request placement '${request}'`, entry.loc);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* @param {ValidationErrors} errors
|
|
101
|
+
* @param {TopogramStatement} statement
|
|
102
|
+
* @param {TopogramFieldMap} fieldMap
|
|
103
|
+
* @param {TopogramRegistry} registry
|
|
104
|
+
* @returns {void}
|
|
105
|
+
*/
|
|
106
|
+
|
|
107
|
+
export function validateProjectionHttpErrors(errors, statement, fieldMap, registry) {
|
|
108
|
+
if (statement.kind !== "projection") {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const httpErrorsField = fieldMap.get("error_responses")?.[0];
|
|
113
|
+
if (!httpErrorsField || httpErrorsField.value.type !== "block") {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const realized = new Set(symbolValues(getFieldValue(statement, "realizes")));
|
|
118
|
+
for (const entry of httpErrorsField.value.entries) {
|
|
119
|
+
const tokens = blockSymbolItems(entry).map((item) => item.value);
|
|
120
|
+
const [capabilityId, errorCode, status] = tokens;
|
|
121
|
+
|
|
122
|
+
const target = registry.get(capabilityId);
|
|
123
|
+
if (!target) {
|
|
124
|
+
pushError(errors, `Projection ${statement.id} error_responses references missing capability '${capabilityId}'`, entry.loc);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (target.kind !== "capability") {
|
|
128
|
+
pushError(errors, `Projection ${statement.id} error_responses must target a capability, found ${target.kind} '${target.id}'`, entry.loc);
|
|
129
|
+
}
|
|
130
|
+
if (!realized.has(capabilityId)) {
|
|
131
|
+
pushError(errors, `Projection ${statement.id} error_responses for '${capabilityId}' must also appear in 'realizes'`, entry.loc);
|
|
132
|
+
}
|
|
133
|
+
if (!/^\d{3}$/.test(status || "")) {
|
|
134
|
+
pushError(errors, `Projection ${statement.id} error_responses for '${capabilityId}' must use a 3-digit status`, entry.loc);
|
|
135
|
+
}
|
|
136
|
+
if (!errorCode) {
|
|
137
|
+
pushError(errors, `Projection ${statement.id} error_responses for '${capabilityId}' must include an error code`, entry.loc);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* @param {ValidationErrors} errors
|
|
144
|
+
* @param {TopogramStatement} statement
|
|
145
|
+
* @param {TopogramFieldMap} fieldMap
|
|
146
|
+
* @param {TopogramRegistry} registry
|
|
147
|
+
* @returns {void}
|
|
148
|
+
*/
|
|
149
|
+
|
|
150
|
+
export function validateProjectionHttpFields(errors, statement, fieldMap, registry) {
|
|
151
|
+
if (statement.kind !== "projection") {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const httpFieldsField = fieldMap.get("wire_fields")?.[0];
|
|
156
|
+
if (!httpFieldsField || httpFieldsField.value.type !== "block") {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const realized = new Set(symbolValues(getFieldValue(statement, "realizes")));
|
|
161
|
+
for (const entry of httpFieldsField.value.entries) {
|
|
162
|
+
const tokens = blockSymbolItems(entry).map((item) => item.value);
|
|
163
|
+
const [capabilityId, direction, fieldName, keywordIn, location, maybeAs, maybeWireName] = tokens;
|
|
164
|
+
|
|
165
|
+
const capability = registry.get(capabilityId);
|
|
166
|
+
if (!capability) {
|
|
167
|
+
pushError(errors, `Projection ${statement.id} wire_fields references missing capability '${capabilityId}'`, entry.loc);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (capability.kind !== "capability") {
|
|
171
|
+
pushError(errors, `Projection ${statement.id} wire_fields must target a capability, found ${capability.kind} '${capability.id}'`, entry.loc);
|
|
172
|
+
}
|
|
173
|
+
if (!realized.has(capabilityId)) {
|
|
174
|
+
pushError(errors, `Projection ${statement.id} wire_fields for '${capabilityId}' must also appear in 'realizes'`, entry.loc);
|
|
175
|
+
}
|
|
176
|
+
if (!["input", "output"].includes(direction)) {
|
|
177
|
+
pushError(errors, `Projection ${statement.id} wire_fields for '${capabilityId}' has invalid direction '${direction}'`, entry.loc);
|
|
178
|
+
}
|
|
179
|
+
if (keywordIn !== "in") {
|
|
180
|
+
pushError(errors, `Projection ${statement.id} wire_fields for '${capabilityId}' must use 'in' before the location`, entry.loc);
|
|
181
|
+
}
|
|
182
|
+
if (!["path", "query", "header", "body"].includes(location)) {
|
|
183
|
+
pushError(errors, `Projection ${statement.id} wire_fields for '${capabilityId}' has invalid location '${location}'`, entry.loc);
|
|
184
|
+
}
|
|
185
|
+
if (maybeAs && maybeAs !== "as") {
|
|
186
|
+
pushError(errors, `Projection ${statement.id} wire_fields for '${capabilityId}' has unexpected token '${maybeAs}'`, entry.loc);
|
|
187
|
+
}
|
|
188
|
+
if (maybeAs === "as" && !maybeWireName) {
|
|
189
|
+
pushError(errors, `Projection ${statement.id} wire_fields for '${capabilityId}' must provide a wire name after 'as'`, entry.loc);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const availableFields = resolveCapabilityContractFields(registry, capabilityId, direction);
|
|
193
|
+
if (fieldName && availableFields.size > 0 && !availableFields.has(fieldName)) {
|
|
194
|
+
pushError(errors, `Projection ${statement.id} wire_fields references unknown ${direction} field '${fieldName}' on ${capabilityId}`, entry.loc);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* @param {ValidationErrors} errors
|
|
201
|
+
* @param {TopogramStatement} statement
|
|
202
|
+
* @param {TopogramFieldMap} fieldMap
|
|
203
|
+
* @param {TopogramRegistry} registry
|
|
204
|
+
* @returns {void}
|
|
205
|
+
*/
|
|
@@ -0,0 +1,339 @@
|
|
|
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 validateProjectionHttpPreconditions(errors, statement, fieldMap, registry) {
|
|
20
|
+
if (statement.kind !== "projection") {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const httpPreconditionsField = fieldMap.get("preconditions")?.[0];
|
|
25
|
+
if (!httpPreconditionsField || httpPreconditionsField.value.type !== "block") {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const realized = new Set(symbolValues(getFieldValue(statement, "realizes")));
|
|
30
|
+
for (const entry of httpPreconditionsField.value.entries) {
|
|
31
|
+
const tokens = blockSymbolItems(entry).map((item) => item.value);
|
|
32
|
+
const [capabilityId] = tokens;
|
|
33
|
+
const capability = registry.get(capabilityId);
|
|
34
|
+
|
|
35
|
+
if (!capability) {
|
|
36
|
+
pushError(errors, `Projection ${statement.id} preconditions references missing capability '${capabilityId}'`, entry.loc);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (capability.kind !== "capability") {
|
|
40
|
+
pushError(errors, `Projection ${statement.id} preconditions must target a capability, found ${capability.kind} '${capability.id}'`, entry.loc);
|
|
41
|
+
}
|
|
42
|
+
if (!realized.has(capabilityId)) {
|
|
43
|
+
pushError(errors, `Projection ${statement.id} preconditions for '${capabilityId}' must also appear in 'realizes'`, entry.loc);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const directives = new Map();
|
|
47
|
+
for (let i = 1; i < tokens.length; i += 2) {
|
|
48
|
+
const key = tokens[i];
|
|
49
|
+
const value = tokens[i + 1];
|
|
50
|
+
if (!value) {
|
|
51
|
+
pushError(errors, `Projection ${statement.id} preconditions for '${capabilityId}' is missing a value for '${key}'`, entry.loc);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
directives.set(key, value);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
for (const requiredKey of ["header", "required", "error", "source", "code"]) {
|
|
58
|
+
if (!directives.has(requiredKey)) {
|
|
59
|
+
pushError(errors, `Projection ${statement.id} preconditions for '${capabilityId}' must include '${requiredKey}'`, entry.loc);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
for (const key of directives.keys()) {
|
|
64
|
+
if (!["header", "required", "error", "source", "code"].includes(key)) {
|
|
65
|
+
pushError(errors, `Projection ${statement.id} preconditions for '${capabilityId}' has unknown directive '${key}'`, entry.loc);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const required = directives.get("required");
|
|
70
|
+
if (required && !["true", "false"].includes(required)) {
|
|
71
|
+
pushError(errors, `Projection ${statement.id} preconditions for '${capabilityId}' has invalid required value '${required}'`, entry.loc);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const errorStatus = directives.get("error");
|
|
75
|
+
if (errorStatus && !/^\d{3}$/.test(errorStatus)) {
|
|
76
|
+
pushError(errors, `Projection ${statement.id} preconditions for '${capabilityId}' must use a 3-digit error status`, entry.loc);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const sourceField = directives.get("source");
|
|
80
|
+
const outputFields = resolveCapabilityContractFields(registry, capabilityId, "output");
|
|
81
|
+
if (sourceField && outputFields.size > 0 && !outputFields.has(sourceField)) {
|
|
82
|
+
pushError(errors, `Projection ${statement.id} preconditions references unknown output field '${sourceField}' on ${capabilityId}`, entry.loc);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @param {ValidationErrors} errors
|
|
89
|
+
* @param {TopogramStatement} statement
|
|
90
|
+
* @param {TopogramFieldMap} fieldMap
|
|
91
|
+
* @param {TopogramRegistry} registry
|
|
92
|
+
* @returns {void}
|
|
93
|
+
*/
|
|
94
|
+
|
|
95
|
+
export function validateProjectionHttpIdempotency(errors, statement, fieldMap, registry) {
|
|
96
|
+
if (statement.kind !== "projection") {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const httpIdempotencyField = fieldMap.get("idempotency")?.[0];
|
|
101
|
+
if (!httpIdempotencyField || httpIdempotencyField.value.type !== "block") {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const realized = new Set(symbolValues(getFieldValue(statement, "realizes")));
|
|
106
|
+
for (const entry of httpIdempotencyField.value.entries) {
|
|
107
|
+
const tokens = blockSymbolItems(entry).map((item) => item.value);
|
|
108
|
+
const [capabilityId] = tokens;
|
|
109
|
+
const capability = registry.get(capabilityId);
|
|
110
|
+
|
|
111
|
+
if (!capability) {
|
|
112
|
+
pushError(errors, `Projection ${statement.id} idempotency references missing capability '${capabilityId}'`, entry.loc);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (capability.kind !== "capability") {
|
|
116
|
+
pushError(errors, `Projection ${statement.id} idempotency must target a capability, found ${capability.kind} '${capability.id}'`, entry.loc);
|
|
117
|
+
}
|
|
118
|
+
if (!realized.has(capabilityId)) {
|
|
119
|
+
pushError(errors, `Projection ${statement.id} idempotency for '${capabilityId}' must also appear in 'realizes'`, entry.loc);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const directives = new Map();
|
|
123
|
+
for (let i = 1; i < tokens.length; i += 2) {
|
|
124
|
+
const key = tokens[i];
|
|
125
|
+
const value = tokens[i + 1];
|
|
126
|
+
if (!value) {
|
|
127
|
+
pushError(errors, `Projection ${statement.id} idempotency for '${capabilityId}' is missing a value for '${key}'`, entry.loc);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
directives.set(key, value);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
for (const requiredKey of ["header", "required", "error", "code"]) {
|
|
134
|
+
if (!directives.has(requiredKey)) {
|
|
135
|
+
pushError(errors, `Projection ${statement.id} idempotency for '${capabilityId}' must include '${requiredKey}'`, entry.loc);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
for (const key of directives.keys()) {
|
|
140
|
+
if (!["header", "required", "error", "code"].includes(key)) {
|
|
141
|
+
pushError(errors, `Projection ${statement.id} idempotency for '${capabilityId}' has unknown directive '${key}'`, entry.loc);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const required = directives.get("required");
|
|
146
|
+
if (required && !["true", "false"].includes(required)) {
|
|
147
|
+
pushError(errors, `Projection ${statement.id} idempotency for '${capabilityId}' has invalid required value '${required}'`, entry.loc);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const errorStatus = directives.get("error");
|
|
151
|
+
if (errorStatus && !/^\d{3}$/.test(errorStatus)) {
|
|
152
|
+
pushError(errors, `Projection ${statement.id} idempotency for '${capabilityId}' must use a 3-digit error status`, entry.loc);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* @param {ValidationErrors} errors
|
|
159
|
+
* @param {TopogramStatement} statement
|
|
160
|
+
* @param {TopogramFieldMap} fieldMap
|
|
161
|
+
* @param {TopogramRegistry} registry
|
|
162
|
+
* @returns {void}
|
|
163
|
+
*/
|
|
164
|
+
|
|
165
|
+
export function validateProjectionHttpCache(errors, statement, fieldMap, registry) {
|
|
166
|
+
if (statement.kind !== "projection") {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const httpCacheField = fieldMap.get("cache")?.[0];
|
|
171
|
+
if (!httpCacheField || httpCacheField.value.type !== "block") {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const realized = new Set(symbolValues(getFieldValue(statement, "realizes")));
|
|
176
|
+
const httpEntries = blockEntries(getFieldValue(statement, "endpoints"));
|
|
177
|
+
const httpMethodsByCapability = new Map();
|
|
178
|
+
|
|
179
|
+
for (const entry of httpEntries) {
|
|
180
|
+
const tokens = blockSymbolItems(entry).map((item) => item.value);
|
|
181
|
+
const capabilityId = tokens[0];
|
|
182
|
+
for (let i = 1; i < tokens.length - 1; i += 1) {
|
|
183
|
+
if (tokens[i] === "method") {
|
|
184
|
+
httpMethodsByCapability.set(capabilityId, tokens[i + 1]);
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
for (const entry of httpCacheField.value.entries) {
|
|
191
|
+
const tokens = blockSymbolItems(entry).map((item) => item.value);
|
|
192
|
+
const [capabilityId] = tokens;
|
|
193
|
+
const capability = registry.get(capabilityId);
|
|
194
|
+
|
|
195
|
+
if (!capability) {
|
|
196
|
+
pushError(errors, `Projection ${statement.id} cache references missing capability '${capabilityId}'`, entry.loc);
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
if (capability.kind !== "capability") {
|
|
200
|
+
pushError(errors, `Projection ${statement.id} cache must target a capability, found ${capability.kind} '${capability.id}'`, entry.loc);
|
|
201
|
+
}
|
|
202
|
+
if (!realized.has(capabilityId)) {
|
|
203
|
+
pushError(errors, `Projection ${statement.id} cache for '${capabilityId}' must also appear in 'realizes'`, entry.loc);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const directives = new Map();
|
|
207
|
+
for (let i = 1; i < tokens.length; i += 2) {
|
|
208
|
+
const key = tokens[i];
|
|
209
|
+
const value = tokens[i + 1];
|
|
210
|
+
if (!value) {
|
|
211
|
+
pushError(errors, `Projection ${statement.id} cache for '${capabilityId}' is missing a value for '${key}'`, entry.loc);
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
directives.set(key, value);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
for (const requiredKey of ["response_header", "request_header", "required", "not_modified", "source", "code"]) {
|
|
218
|
+
if (!directives.has(requiredKey)) {
|
|
219
|
+
pushError(errors, `Projection ${statement.id} cache for '${capabilityId}' must include '${requiredKey}'`, entry.loc);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
for (const key of directives.keys()) {
|
|
224
|
+
if (!["response_header", "request_header", "required", "not_modified", "source", "code"].includes(key)) {
|
|
225
|
+
pushError(errors, `Projection ${statement.id} cache for '${capabilityId}' has unknown directive '${key}'`, entry.loc);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const required = directives.get("required");
|
|
230
|
+
if (required && !["true", "false"].includes(required)) {
|
|
231
|
+
pushError(errors, `Projection ${statement.id} cache for '${capabilityId}' has invalid required value '${required}'`, entry.loc);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const notModifiedStatus = directives.get("not_modified");
|
|
235
|
+
if (notModifiedStatus && notModifiedStatus !== "304") {
|
|
236
|
+
pushError(errors, `Projection ${statement.id} cache for '${capabilityId}' must use 304 for 'not_modified'`, entry.loc);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const sourceField = directives.get("source");
|
|
240
|
+
const outputFields = resolveCapabilityContractFields(registry, capabilityId, "output");
|
|
241
|
+
if (sourceField && outputFields.size > 0 && !outputFields.has(sourceField)) {
|
|
242
|
+
pushError(errors, `Projection ${statement.id} cache references unknown output field '${sourceField}' on ${capabilityId}`, entry.loc);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const method = httpMethodsByCapability.get(capabilityId);
|
|
246
|
+
if (method && method !== "GET") {
|
|
247
|
+
pushError(errors, `Projection ${statement.id} cache for '${capabilityId}' requires an HTTP GET realization, found '${method}'`, entry.loc);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* @param {ValidationErrors} errors
|
|
254
|
+
* @param {TopogramStatement} statement
|
|
255
|
+
* @param {TopogramFieldMap} fieldMap
|
|
256
|
+
* @param {TopogramRegistry} registry
|
|
257
|
+
* @returns {void}
|
|
258
|
+
*/
|
|
259
|
+
|
|
260
|
+
export function validateProjectionHttpDelete(errors, statement, fieldMap, registry) {
|
|
261
|
+
if (statement.kind !== "projection") {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const httpDeleteField = fieldMap.get("delete_semantics")?.[0];
|
|
266
|
+
if (!httpDeleteField || httpDeleteField.value.type !== "block") {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const realized = new Set(symbolValues(getFieldValue(statement, "realizes")));
|
|
271
|
+
for (const entry of httpDeleteField.value.entries) {
|
|
272
|
+
const tokens = blockSymbolItems(entry).map((item) => item.value);
|
|
273
|
+
const [capabilityId] = tokens;
|
|
274
|
+
const capability = registry.get(capabilityId);
|
|
275
|
+
|
|
276
|
+
if (!capability) {
|
|
277
|
+
pushError(errors, `Projection ${statement.id} delete_semantics references missing capability '${capabilityId}'`, entry.loc);
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
if (capability.kind !== "capability") {
|
|
281
|
+
pushError(errors, `Projection ${statement.id} delete_semantics must target a capability, found ${capability.kind} '${capability.id}'`, entry.loc);
|
|
282
|
+
}
|
|
283
|
+
if (!realized.has(capabilityId)) {
|
|
284
|
+
pushError(errors, `Projection ${statement.id} delete_semantics for '${capabilityId}' must also appear in 'realizes'`, entry.loc);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const directives = new Map();
|
|
288
|
+
for (let i = 1; i < tokens.length; i += 2) {
|
|
289
|
+
const key = tokens[i];
|
|
290
|
+
const value = tokens[i + 1];
|
|
291
|
+
if (!value) {
|
|
292
|
+
pushError(errors, `Projection ${statement.id} delete_semantics for '${capabilityId}' is missing a value for '${key}'`, entry.loc);
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
directives.set(key, value);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
for (const requiredKey of ["mode", "response"]) {
|
|
299
|
+
if (!directives.has(requiredKey)) {
|
|
300
|
+
pushError(errors, `Projection ${statement.id} delete_semantics for '${capabilityId}' must include '${requiredKey}'`, entry.loc);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
for (const key of directives.keys()) {
|
|
305
|
+
if (!["mode", "field", "value", "response"].includes(key)) {
|
|
306
|
+
pushError(errors, `Projection ${statement.id} delete_semantics for '${capabilityId}' has unknown directive '${key}'`, entry.loc);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const mode = directives.get("mode");
|
|
311
|
+
if (mode && !["soft", "hard"].includes(mode)) {
|
|
312
|
+
pushError(errors, `Projection ${statement.id} delete_semantics for '${capabilityId}' has invalid mode '${mode}'`, entry.loc);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const response = directives.get("response");
|
|
316
|
+
if (response && !["none", "body"].includes(response)) {
|
|
317
|
+
pushError(errors, `Projection ${statement.id} delete_semantics for '${capabilityId}' has invalid response '${response}'`, entry.loc);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (mode === "soft") {
|
|
321
|
+
if (!directives.has("field") || !directives.has("value")) {
|
|
322
|
+
pushError(errors, `Projection ${statement.id} delete_semantics for '${capabilityId}' must include 'field' and 'value' for soft deletes`, entry.loc);
|
|
323
|
+
}
|
|
324
|
+
const outputFields = resolveCapabilityContractFields(registry, capabilityId, "output");
|
|
325
|
+
const fieldName = directives.get("field");
|
|
326
|
+
if (fieldName && outputFields.size > 0 && !outputFields.has(fieldName)) {
|
|
327
|
+
pushError(errors, `Projection ${statement.id} delete_semantics references unknown output field '${fieldName}' on ${capabilityId}`, entry.loc);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* @param {ValidationErrors} errors
|
|
335
|
+
* @param {TopogramStatement} statement
|
|
336
|
+
* @param {TopogramFieldMap} fieldMap
|
|
337
|
+
* @param {TopogramRegistry} registry
|
|
338
|
+
* @returns {void}
|
|
339
|
+
*/
|