@topogram/cli 0.3.64 → 0.3.66
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 +716 -0
- package/src/adoption/plan.js +12 -703
- package/src/adoption/reporting.js +1 -1
- package/src/agent-brief.js +7 -21
- 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 +677 -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/archive/jsonl.js +2 -2
- package/src/archive/resolver-bridge.js +1 -1
- package/src/archive/unarchive.js +2 -1
- package/src/catalog/constants.js +10 -0
- package/src/catalog/copy.js +65 -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 +123 -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/command-parsers/project.js +3 -0
- package/src/cli/command-parsers/shared.js +1 -1
- package/src/cli/commands/agent.js +2 -2
- 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/check.js +3 -3
- package/src/cli/commands/doctor.js +2 -9
- 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 +269 -0
- package/src/cli/commands/import/plan.js +292 -0
- package/src/cli/commands/import/refresh.js +471 -0
- package/src/cli/commands/import/status-history.js +196 -0
- package/src/cli/commands/import/workspace.js +233 -0
- package/src/cli/commands/import.js +33 -1732
- package/src/cli/commands/migrate.js +153 -0
- 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 +270 -0
- package/src/cli/commands/query.js +9 -1300
- package/src/cli/commands/source.js +3 -12
- package/src/cli/commands/template/baseline.js +100 -0
- package/src/cli/commands/template/check.js +467 -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-runner.js +6 -6
- package/src/cli/commands/template.js +41 -2143
- package/src/cli/commands/trust.js +1 -1
- package/src/cli/commands/workflow.js +6 -1
- package/src/cli/dispatcher.js +6 -1
- package/src/cli/help.js +15 -14
- package/src/cli/migration-guidance.js +1 -1
- package/src/cli/output-safety.js +2 -1
- package/src/cli/path-normalization.js +3 -13
- 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/domain-page.js +1 -1
- 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/context/task-mode.js +2 -2
- 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/sdlc/doc-page.js +1 -1
- package/src/generator/shared.d.ts +2 -0
- package/src/generator/surfaces/databases/lifecycle-shared.js +1 -1
- package/src/generator/surfaces/native/swiftui-templates/README.generated.md +1 -1
- 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/context.js +5 -7
- 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 +337 -0
- package/src/import/core/runner/options.js +22 -0
- package/src/import/core/runner/reports.js +51 -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 +393 -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 +90 -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 +351 -0
- package/src/new-project/template-policy.js +269 -0
- package/src/new-project/template-resolution.js +370 -0
- package/src/new-project/template-snapshots.js +442 -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 +591 -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/sdlc/adopt.js +6 -5
- package/src/sdlc/paths.js +3 -5
- package/src/sdlc/scaffold.js +2 -1
- 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 +212 -0
- package/src/workflows/reconcile/adoption-plan/dependencies.js +75 -0
- package/src/workflows/reconcile/adoption-plan/outputs.js +153 -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/candidate-model.js +18 -2
- package/src/workflows/reconcile/canonical-surface.js +1 -1
- package/src/workflows/reconcile/impacts/adoption-plan.js +196 -0
- package/src/workflows/reconcile/impacts/indexes.js +105 -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/workflows/reconcile/renderers.js +41 -6
- package/src/workflows/shared.js +5 -11
- package/src/workspace-docs.d.ts +29 -0
- package/src/workspace-paths.js +328 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
blockEntries,
|
|
5
|
+
blockSymbolItems,
|
|
6
|
+
getFieldValue,
|
|
7
|
+
pushError,
|
|
8
|
+
valueAsArray
|
|
9
|
+
} from "./utils.js";
|
|
10
|
+
import {
|
|
11
|
+
resolveShapeBaseFieldNames,
|
|
12
|
+
statementFieldNames
|
|
13
|
+
} from "./model-helpers.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {ValidationErrors} errors
|
|
17
|
+
* @param {TopogramStatement} statement
|
|
18
|
+
* @param {TopogramRegistry} registry
|
|
19
|
+
* @returns {void}
|
|
20
|
+
*/
|
|
21
|
+
export function validateShapeFrom(errors, statement, registry) {
|
|
22
|
+
if (statement.kind !== "shape" || !statement.from) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const target = registry.get(statement.from.value);
|
|
27
|
+
if (!target) {
|
|
28
|
+
pushError(errors, `Shape ${statement.id} derives from missing statement '${statement.from.value}'`, statement.from.loc);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (target.kind !== "entity") {
|
|
33
|
+
pushError(errors, `Shape ${statement.id} can only derive from an entity, found ${target.kind} '${target.id}'`, statement.from.loc);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {ValidationErrors} errors
|
|
39
|
+
* @param {TopogramStatement} statement
|
|
40
|
+
* @param {TopogramFieldMap} fieldMap
|
|
41
|
+
* @param {TopogramRegistry} registry
|
|
42
|
+
* @returns {void}
|
|
43
|
+
*/
|
|
44
|
+
function validateEntityRelations(errors, statement, fieldMap, registry) {
|
|
45
|
+
if (statement.kind !== "entity") {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const field = fieldMap.get("relations")?.[0];
|
|
50
|
+
if (!field || field.value.type !== "block") {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
for (const entry of field.value.entries) {
|
|
55
|
+
const [left, operator, target] = entry.items;
|
|
56
|
+
if (!left || !operator || !target) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (left.type !== "symbol" || operator.type !== "symbol" || target.type !== "symbol") {
|
|
61
|
+
pushError(errors, `Relation entries on entity ${statement.id} must use symbols`, entry.loc);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (operator.value !== "references") {
|
|
66
|
+
pushError(errors, `Relation entries on entity ${statement.id} must use 'references'`, operator.loc);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const [entityId] = target.value.split(".");
|
|
70
|
+
const related = registry.get(entityId);
|
|
71
|
+
if (!related) {
|
|
72
|
+
pushError(errors, `Relation on entity ${statement.id} references missing entity '${entityId}'`, target.loc);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (related.kind !== "entity") {
|
|
77
|
+
pushError(errors, `Relation on entity ${statement.id} must target an entity, found ${related.kind} '${related.id}'`, target.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
|
+
*/
|
|
89
|
+
function validateShapeTransforms(errors, statement, fieldMap, registry) {
|
|
90
|
+
if (statement.kind !== "shape") {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const baseFieldNames = resolveShapeBaseFieldNames(statement, registry);
|
|
95
|
+
const baseFieldSet = new Set(baseFieldNames);
|
|
96
|
+
const source = statement.from ? registry.get(statement.from.value) : null;
|
|
97
|
+
const sourceFieldSet = new Set(source ? statementFieldNames(source) : []);
|
|
98
|
+
const includeField = fieldMap.get("include")?.[0];
|
|
99
|
+
const excludeField = fieldMap.get("exclude")?.[0];
|
|
100
|
+
|
|
101
|
+
/** @type {Array<[string, TopogramField | undefined]>} */
|
|
102
|
+
const inheritedFieldLists = [
|
|
103
|
+
["include", includeField],
|
|
104
|
+
["exclude", excludeField]
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
for (const [fieldKey, field] of inheritedFieldLists) {
|
|
108
|
+
if (!field) {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
for (const item of valueAsArray(field.value)) {
|
|
113
|
+
if (item.type !== "symbol") {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (statement.from && !sourceFieldSet.has(item.value) && fieldKey === "include") {
|
|
118
|
+
pushError(errors, `Shape ${statement.id} includes unknown field '${item.value}' from ${statement.from.value}`, item.loc);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (statement.from && fieldKey === "exclude") {
|
|
122
|
+
if (!sourceFieldSet.has(item.value)) {
|
|
123
|
+
pushError(errors, `Shape ${statement.id} excludes unknown field '${item.value}' from ${statement.from.value}`, item.loc);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const renameEntries = blockEntries(getFieldValue(statement, "rename"));
|
|
130
|
+
const renameFrom = new Map();
|
|
131
|
+
const renameTo = new Map();
|
|
132
|
+
|
|
133
|
+
for (const entry of renameEntries) {
|
|
134
|
+
const items = blockSymbolItems(entry);
|
|
135
|
+
if (items.length !== 2) {
|
|
136
|
+
pushError(errors, `Each 'rename' entry on shape ${statement.id} must be exactly '<from> <to>'`, entry.loc);
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const [fromItem, toItem] = items;
|
|
141
|
+
if (!baseFieldSet.has(fromItem.value)) {
|
|
142
|
+
pushError(errors, `Shape ${statement.id} renames unknown field '${fromItem.value}'`, fromItem.loc);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (renameFrom.has(fromItem.value)) {
|
|
146
|
+
pushError(errors, `Shape ${statement.id} renames field '${fromItem.value}' more than once`, fromItem.loc);
|
|
147
|
+
} else {
|
|
148
|
+
renameFrom.set(fromItem.value, toItem.value);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (renameTo.has(toItem.value)) {
|
|
152
|
+
pushError(errors, `Shape ${statement.id} renames multiple fields to '${toItem.value}'`, toItem.loc);
|
|
153
|
+
} else {
|
|
154
|
+
renameTo.set(toItem.value, fromItem.value);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const finalFieldNames = baseFieldNames.map((fieldName) => renameFrom.get(fieldName) || fieldName);
|
|
159
|
+
const finalFieldSet = new Set();
|
|
160
|
+
for (const fieldName of finalFieldNames) {
|
|
161
|
+
if (finalFieldSet.has(fieldName)) {
|
|
162
|
+
pushError(errors, `Shape ${statement.id} produces duplicate projected field '${fieldName}'`, statement.loc);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
finalFieldSet.add(fieldName);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const sourceNameSet = new Set(baseFieldNames);
|
|
169
|
+
const overrideEntries = blockEntries(getFieldValue(statement, "overrides"));
|
|
170
|
+
const seenOverrides = new Set();
|
|
171
|
+
|
|
172
|
+
for (const entry of overrideEntries) {
|
|
173
|
+
const items = blockSymbolItems(entry);
|
|
174
|
+
if (items.length < 2) {
|
|
175
|
+
pushError(errors, `Each 'overrides' entry on shape ${statement.id} must include a field and at least one override`, entry.loc);
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const [fieldItem, ...rest] = items;
|
|
180
|
+
if (!finalFieldSet.has(fieldItem.value) && !sourceNameSet.has(fieldItem.value)) {
|
|
181
|
+
pushError(errors, `Shape ${statement.id} overrides unknown field '${fieldItem.value}'`, fieldItem.loc);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (seenOverrides.has(fieldItem.value)) {
|
|
185
|
+
pushError(errors, `Shape ${statement.id} overrides field '${fieldItem.value}' more than once`, fieldItem.loc);
|
|
186
|
+
} else {
|
|
187
|
+
seenOverrides.add(fieldItem.value);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
let sawChange = false;
|
|
191
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
192
|
+
const token = rest[i];
|
|
193
|
+
if (token.value === "required" || token.value === "optional") {
|
|
194
|
+
sawChange = true;
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (token.value === "type") {
|
|
199
|
+
sawChange = true;
|
|
200
|
+
if (!rest[i + 1]) {
|
|
201
|
+
pushError(errors, `Shape ${statement.id} override for '${fieldItem.value}' is missing a type value`, token.loc);
|
|
202
|
+
} else {
|
|
203
|
+
i += 1;
|
|
204
|
+
}
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (token.value === "default") {
|
|
209
|
+
sawChange = true;
|
|
210
|
+
if (!rest[i + 1]) {
|
|
211
|
+
pushError(errors, `Shape ${statement.id} override for '${fieldItem.value}' is missing a default value`, token.loc);
|
|
212
|
+
} else {
|
|
213
|
+
i += 1;
|
|
214
|
+
}
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
pushError(errors, `Shape ${statement.id} override for '${fieldItem.value}' has unknown directive '${token.value}'`, token.loc);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (!sawChange) {
|
|
222
|
+
pushError(errors, `Shape ${statement.id} override for '${fieldItem.value}' must specify at least one valid directive`, entry.loc);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* @param {ValidationErrors} errors
|
|
229
|
+
* @param {TopogramStatement} statement
|
|
230
|
+
* @param {TopogramFieldMap} fieldMap
|
|
231
|
+
* @param {TopogramRegistry} registry
|
|
232
|
+
* @returns {void}
|
|
233
|
+
*/
|
|
234
|
+
export function validateDataModelStatement(errors, statement, fieldMap, registry) {
|
|
235
|
+
validateEntityRelations(errors, statement, fieldMap, registry);
|
|
236
|
+
validateShapeTransforms(errors, statement, fieldMap, registry);
|
|
237
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
DOC_ARRAY_FIELDS,
|
|
5
|
+
DOC_CONFIDENCE,
|
|
6
|
+
DOC_KINDS,
|
|
7
|
+
DOC_REFERENCE_FIELDS,
|
|
8
|
+
DOC_STATUSES
|
|
9
|
+
} from "../workspace-docs.js";
|
|
10
|
+
import { IDENTIFIER_PATTERN } from "./kinds.js";
|
|
11
|
+
import { pushError } from "./utils.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {import("../parser.js").WorkspaceAst} workspaceAst
|
|
15
|
+
* @param {TopogramRegistry} registry
|
|
16
|
+
* @param {ValidationErrors} errors
|
|
17
|
+
* @returns {void}
|
|
18
|
+
*/
|
|
19
|
+
export function validateDocs(workspaceAst, registry, errors) {
|
|
20
|
+
const docs = workspaceAst.docs || [];
|
|
21
|
+
const docRegistry = new Map();
|
|
22
|
+
|
|
23
|
+
for (const doc of docs) {
|
|
24
|
+
if (doc.parseError) {
|
|
25
|
+
pushError(errors, doc.parseError.message, doc.parseError.loc);
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const { metadata } = doc;
|
|
30
|
+
for (const required of ["id", "kind", "title", "status"]) {
|
|
31
|
+
if (!metadata[required]) {
|
|
32
|
+
pushError(errors, `Missing required doc metadata '${required}'`, doc.loc);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (metadata.id && !IDENTIFIER_PATTERN.test(metadata.id)) {
|
|
37
|
+
pushError(errors, `Invalid doc identifier '${metadata.id}'`, doc.loc);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (metadata.kind && !DOC_KINDS.has(metadata.kind)) {
|
|
41
|
+
pushError(errors, `Unsupported doc kind '${metadata.kind}'`, doc.loc);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (metadata.status && !DOC_STATUSES.has(metadata.status)) {
|
|
45
|
+
pushError(errors, `Unsupported doc status '${metadata.status}'`, doc.loc);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (metadata.confidence && !DOC_CONFIDENCE.has(metadata.confidence)) {
|
|
49
|
+
pushError(errors, `Unsupported doc confidence '${metadata.confidence}'`, doc.loc);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (metadata.review_required != null && typeof metadata.review_required !== "boolean") {
|
|
53
|
+
pushError(errors, "Doc metadata 'review_required' must be a boolean", doc.loc);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
for (const key of DOC_ARRAY_FIELDS) {
|
|
57
|
+
if (metadata[key] != null && !Array.isArray(metadata[key])) {
|
|
58
|
+
pushError(errors, `Doc metadata '${key}' must be a list`, doc.loc);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (metadata.id) {
|
|
63
|
+
if (docRegistry.has(metadata.id)) {
|
|
64
|
+
pushError(errors, `Duplicate doc id '${metadata.id}'`, doc.loc);
|
|
65
|
+
} else {
|
|
66
|
+
docRegistry.set(metadata.id, doc);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
for (const doc of docs) {
|
|
72
|
+
if (doc.parseError) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const { metadata } = doc;
|
|
76
|
+
|
|
77
|
+
for (const entityId of metadata.related_entities || []) {
|
|
78
|
+
const statement = registry.get(entityId);
|
|
79
|
+
if (!statement || statement.kind !== "entity") {
|
|
80
|
+
pushError(errors, `Doc '${metadata.id}' references missing entity '${entityId}'`, doc.loc);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
for (const capabilityId of metadata.related_capabilities || []) {
|
|
85
|
+
const statement = registry.get(capabilityId);
|
|
86
|
+
if (!statement || statement.kind !== "capability") {
|
|
87
|
+
pushError(errors, `Doc '${metadata.id}' references missing capability '${capabilityId}'`, doc.loc);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
for (const actorId of metadata.related_actors || []) {
|
|
92
|
+
const statement = registry.get(actorId);
|
|
93
|
+
if (!statement || statement.kind !== "actor") {
|
|
94
|
+
pushError(errors, `Doc '${metadata.id}' references missing actor '${actorId}'`, doc.loc);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
for (const roleId of metadata.related_roles || []) {
|
|
99
|
+
const statement = registry.get(roleId);
|
|
100
|
+
if (!statement || statement.kind !== "role") {
|
|
101
|
+
pushError(errors, `Doc '${metadata.id}' references missing role '${roleId}'`, doc.loc);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
for (const ruleId of metadata.related_rules || []) {
|
|
106
|
+
const statement = registry.get(ruleId);
|
|
107
|
+
if (!statement || statement.kind !== "rule") {
|
|
108
|
+
pushError(errors, `Doc '${metadata.id}' references missing rule '${ruleId}'`, doc.loc);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
for (const workflowDocId of metadata.related_workflows || []) {
|
|
113
|
+
const relatedDoc = docRegistry.get(workflowDocId);
|
|
114
|
+
if (!relatedDoc || relatedDoc.metadata.kind !== "workflow") {
|
|
115
|
+
pushError(errors, `Doc '${metadata.id}' references missing workflow doc '${workflowDocId}'`, doc.loc);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
for (const decisionId of metadata.related_decisions || []) {
|
|
120
|
+
const statement = registry.get(decisionId);
|
|
121
|
+
if (!statement || statement.kind !== "decision") {
|
|
122
|
+
pushError(errors, `Doc '${metadata.id}' references missing decision '${decisionId}'`, doc.loc);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
for (const shapeId of metadata.related_shapes || []) {
|
|
127
|
+
const statement = registry.get(shapeId);
|
|
128
|
+
if (!statement || statement.kind !== "shape") {
|
|
129
|
+
pushError(errors, `Doc '${metadata.id}' references missing shape '${shapeId}'`, doc.loc);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
for (const projectionId of metadata.related_projections || []) {
|
|
134
|
+
const statement = registry.get(projectionId);
|
|
135
|
+
if (!statement || statement.kind !== "projection") {
|
|
136
|
+
pushError(errors, `Doc '${metadata.id}' references missing projection '${projectionId}'`, doc.loc);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
for (const relatedDocId of metadata.related_docs || []) {
|
|
141
|
+
if (!docRegistry.has(relatedDocId)) {
|
|
142
|
+
pushError(errors, `Doc '${metadata.id}' references missing doc '${relatedDocId}'`, doc.loc);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
for (const [fieldName, expectedKind] of Object.entries(DOC_REFERENCE_FIELDS)) {
|
|
147
|
+
const value = metadata[fieldName];
|
|
148
|
+
if (value == null) continue;
|
|
149
|
+
if (typeof value !== "string") {
|
|
150
|
+
pushError(errors, `Doc metadata '${fieldName}' must be a single id`, doc.loc);
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
const target = registry.get(value);
|
|
154
|
+
if (!target) {
|
|
155
|
+
pushError(errors, `Doc '${metadata.id}' references missing ${expectedKind} '${value}'`, doc.loc);
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (target.kind !== expectedKind) {
|
|
159
|
+
pushError(
|
|
160
|
+
errors,
|
|
161
|
+
`Doc '${metadata.id}' ${fieldName} must reference a ${expectedKind}, found ${target.kind} '${target.id}'`,
|
|
162
|
+
doc.loc
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -1 +1,146 @@
|
|
|
1
|
-
//
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
blockSymbolItems,
|
|
5
|
+
pushError,
|
|
6
|
+
valueAsArray
|
|
7
|
+
} from "./utils.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {unknown} token
|
|
11
|
+
* @returns {token is string}
|
|
12
|
+
*/
|
|
13
|
+
function isIdentifierLike(token) {
|
|
14
|
+
return typeof token === "string" && token.length > 0;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {unknown} token
|
|
19
|
+
* @returns {token is string}
|
|
20
|
+
*/
|
|
21
|
+
function isComparator(token) {
|
|
22
|
+
return typeof token === "string" && ["==", "!=", "<", "<=", ">", ">="].includes(token);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {ValidationErrors} errors
|
|
27
|
+
* @param {TopogramStatement} statement
|
|
28
|
+
* @param {TopogramBlockEntry} entry
|
|
29
|
+
* @returns {void}
|
|
30
|
+
*/
|
|
31
|
+
function validateInvariantEntry(errors, statement, entry) {
|
|
32
|
+
const tokens = blockSymbolItems(entry).map((item) => item.value);
|
|
33
|
+
if (tokens.length < 2) {
|
|
34
|
+
pushError(errors, `Invariant on ${statement.kind} ${statement.id} is too short`, entry.loc);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const [left, op, ...rest] = tokens;
|
|
39
|
+
if (!isIdentifierLike(left)) {
|
|
40
|
+
pushError(errors, `Invariant on ${statement.kind} ${statement.id} must start with a field or expression target`, entry.loc);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (op === "requires") {
|
|
45
|
+
if (rest.length < 3) {
|
|
46
|
+
pushError(errors, `Invariant '${tokens.join(" ")}' on ${statement.kind} ${statement.id} must be '<field> requires <field> <op> <value>'`, entry.loc);
|
|
47
|
+
} else if (!isComparator(rest[1])) {
|
|
48
|
+
pushError(errors, `Invariant '${tokens.join(" ")}' on ${statement.kind} ${statement.id} uses an invalid comparator '${rest[1]}'`, entry.loc);
|
|
49
|
+
}
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (op === "length") {
|
|
54
|
+
if (rest.length !== 2 || !["<", "<=", ">", ">=", "=="].includes(rest[0])) {
|
|
55
|
+
pushError(errors, `Invariant '${tokens.join(" ")}' on ${statement.kind} ${statement.id} must be '<field> length <op> <number>'`, entry.loc);
|
|
56
|
+
}
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (op === "format") {
|
|
61
|
+
if (rest.length !== 2 || rest[0] !== "==") {
|
|
62
|
+
pushError(errors, `Invariant '${tokens.join(" ")}' on ${statement.kind} ${statement.id} must be '<field> format == <format>'`, entry.loc);
|
|
63
|
+
}
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (isComparator(op)) {
|
|
68
|
+
if (rest.length < 1) {
|
|
69
|
+
pushError(errors, `Invariant '${tokens.join(" ")}' on ${statement.kind} ${statement.id} is missing a right-hand value`, entry.loc);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (rest[1] === "implies") {
|
|
74
|
+
const [, , impliedField, impliedOperator, impliedValue] = rest;
|
|
75
|
+
if (!impliedField || !impliedOperator || !impliedValue) {
|
|
76
|
+
pushError(errors, `Invariant '${tokens.join(" ")}' on ${statement.kind} ${statement.id} must fully specify the implied clause`, entry.loc);
|
|
77
|
+
} else if (!(impliedOperator === "is" || isComparator(impliedOperator))) {
|
|
78
|
+
pushError(errors, `Invariant '${tokens.join(" ")}' on ${statement.kind} ${statement.id} has invalid implied operator '${impliedOperator}'`, entry.loc);
|
|
79
|
+
}
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
pushError(errors, `Invariant '${tokens.join(" ")}' on ${statement.kind} ${statement.id} uses unsupported form`, entry.loc);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @param {ValidationErrors} errors
|
|
91
|
+
* @param {TopogramStatement} statement
|
|
92
|
+
* @param {TopogramField | null | undefined} field
|
|
93
|
+
* @param {string} label
|
|
94
|
+
* @returns {void}
|
|
95
|
+
*/
|
|
96
|
+
function validateRuleExpressionValue(errors, statement, field, label) {
|
|
97
|
+
if (!field) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const items = valueAsArray(field.value);
|
|
102
|
+
if (items.length !== 1) {
|
|
103
|
+
pushError(errors, `Field '${label}' on rule ${statement.id} must contain a single expression`, field.loc);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const item = items[0];
|
|
108
|
+
if (item.type !== "string" && item.type !== "symbol") {
|
|
109
|
+
pushError(errors, `Field '${label}' on rule ${statement.id} must be a string or symbol expression`, field.loc);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const text = item.value.trim();
|
|
114
|
+
if (text.length === 0) {
|
|
115
|
+
pushError(errors, `Field '${label}' on rule ${statement.id} must not be empty`, field.loc);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (label === "requirement" || label === "condition") {
|
|
120
|
+
if (!/(==|!=|<=|>=|<|>)/.test(text)) {
|
|
121
|
+
pushError(errors, `Field '${label}' on rule ${statement.id} must include a comparison operator`, field.loc);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* @param {ValidationErrors} errors
|
|
128
|
+
* @param {TopogramStatement} statement
|
|
129
|
+
* @param {TopogramFieldMap} fieldMap
|
|
130
|
+
* @returns {void}
|
|
131
|
+
*/
|
|
132
|
+
export function validateExpressions(errors, statement, fieldMap) {
|
|
133
|
+
if (statement.kind === "entity") {
|
|
134
|
+
const invariantsField = fieldMap.get("invariants")?.[0];
|
|
135
|
+
if (invariantsField?.value.type === "block") {
|
|
136
|
+
for (const entry of invariantsField.value.entries) {
|
|
137
|
+
validateInvariantEntry(errors, statement, entry);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (statement.kind === "rule") {
|
|
143
|
+
validateRuleExpressionValue(errors, statement, fieldMap.get("condition")?.[0], "condition");
|
|
144
|
+
validateRuleExpressionValue(errors, statement, fieldMap.get("requirement")?.[0], "requirement");
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
TopogramBlockEntry,
|
|
3
|
+
TopogramField,
|
|
4
|
+
TopogramFieldMap,
|
|
5
|
+
TopogramRegistry,
|
|
6
|
+
TopogramStatement,
|
|
7
|
+
TopogramToken,
|
|
8
|
+
ValidationErrors
|
|
9
|
+
} from "../topogram-types.js";
|
|
10
|
+
|
|
11
|
+
export function pushError(errors: ValidationErrors, message: string, loc?: any): void;
|
|
12
|
+
export function formatLoc(loc: any): string;
|
|
13
|
+
export function valueAsArray(value: TopogramToken | null | undefined): TopogramToken[];
|
|
14
|
+
export function symbolValues(value: TopogramToken | null | undefined): string[];
|
|
15
|
+
export function collectFieldMap(statement: TopogramStatement): TopogramFieldMap;
|
|
16
|
+
export function getField(statement: TopogramStatement, key: string): TopogramField | null;
|
|
17
|
+
export function getFieldValue(statement: TopogramStatement, key: string): TopogramToken | null;
|
|
18
|
+
export function stringValue(value: TopogramToken | null | undefined): string | null;
|
|
19
|
+
export function symbolValue(value: TopogramToken | null | undefined): string | null;
|
|
20
|
+
export function blockEntries(value: TopogramToken | null | undefined): TopogramBlockEntry[];
|
|
21
|
+
export function buildRegistry(workspaceAst: any, errors: ValidationErrors): TopogramRegistry;
|
|
22
|
+
export function validateWorkspace(workspaceAst: any): any;
|
|
23
|
+
export function formatValidationErrors(result: any): string;
|