@webpieces/nx-webpieces-rules 0.4.912 → 0.4.913
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/README.md +11 -0
- package/executors.json +5 -0
- package/package.json +7 -7
- package/src/executors/help/executor.js +2 -0
- package/src/executors/help/executor.js.map +1 -1
- package/src/executors/visualize-runtime/executor.d.ts +3 -1
- package/src/executors/visualize-runtime/executor.js +9 -3
- package/src/executors/visualize-runtime/executor.js.map +1 -1
- package/src/executors/visualize-runtime-goal/executor.d.ts +27 -0
- package/src/executors/visualize-runtime-goal/executor.js +98 -0
- package/src/executors/visualize-runtime-goal/executor.js.map +1 -0
- package/src/executors/visualize-runtime-goal/schema.json +13 -0
- package/src/lib/graph-color-modes.d.ts +8 -1
- package/src/lib/graph-color-modes.js +11 -1
- package/src/lib/graph-color-modes.js.map +1 -1
- package/src/lib/graph-filter.client.d.ts +9 -2
- package/src/lib/graph-filter.client.js +15 -0
- package/src/lib/graph-filter.client.js.map +1 -1
- package/src/lib/graph-mode-state.client.d.ts +9 -2
- package/src/lib/graph-mode-state.client.js +17 -4
- package/src/lib/graph-mode-state.client.js.map +1 -1
- package/src/lib/graph-page-shell.js +6 -5
- package/src/lib/graph-page-shell.js.map +1 -1
- package/src/lib/graph-render-model.d.ts +41 -0
- package/src/lib/graph-render-model.js +45 -1
- package/src/lib/graph-render-model.js.map +1 -1
- package/src/lib/graph-visualizer.client.d.ts +7 -1
- package/src/lib/graph-visualizer.client.js +37 -4
- package/src/lib/graph-visualizer.client.js.map +1 -1
- package/src/lib/runtime-goal-comparison.d.ts +55 -0
- package/src/lib/runtime-goal-comparison.js +209 -0
- package/src/lib/runtime-goal-comparison.js.map +1 -0
- package/src/lib/runtime-goal-details.d.ts +17 -0
- package/src/lib/runtime-goal-details.js +118 -0
- package/src/lib/runtime-goal-details.js.map +1 -0
- package/src/lib/runtime-goal-file.d.ts +72 -0
- package/src/lib/runtime-goal-file.js +201 -0
- package/src/lib/runtime-goal-file.js.map +1 -0
- package/src/lib/runtime-goal-styles.d.ts +10 -0
- package/src/lib/runtime-goal-styles.js +25 -0
- package/src/lib/runtime-goal-styles.js.map +1 -0
- package/src/lib/runtime-goal-validator.d.ts +39 -0
- package/src/lib/runtime-goal-validator.js +236 -0
- package/src/lib/runtime-goal-validator.js.map +1 -0
- package/src/lib/runtime-goal-view.d.ts +22 -0
- package/src/lib/runtime-goal-view.js +64 -0
- package/src/lib/runtime-goal-view.js.map +1 -0
- package/src/lib/runtime-graph-diff.d.ts +107 -0
- package/src/lib/runtime-graph-diff.js +346 -0
- package/src/lib/runtime-graph-diff.js.map +1 -0
- package/src/lib/runtime-html-page.d.ts +2 -0
- package/src/lib/runtime-html-page.js +28 -3
- package/src/lib/runtime-html-page.js.map +1 -1
- package/src/lib/runtime-layout.d.ts +2 -0
- package/src/lib/runtime-layout.js +4 -0
- package/src/lib/runtime-layout.js.map +1 -1
- package/src/lib/runtime-union-graph.d.ts +20 -0
- package/src/lib/runtime-union-graph.js +94 -0
- package/src/lib/runtime-union-graph.js.map +1 -0
- package/src/lib/runtime-visualizer.client.js +7 -3
- package/src/lib/runtime-visualizer.client.js.map +1 -1
- package/src/lib/runtime-visualizer.d.ts +8 -3
- package/src/lib/runtime-visualizer.js +18 -10
- package/src/lib/runtime-visualizer.js.map +1 -1
- package/src/lib/runtime-viz-options.d.ts +28 -1
- package/src/lib/runtime-viz-options.js +36 -2
- package/src/lib/runtime-viz-options.js.map +1 -1
- package/src/plugin.js +2 -2
- package/src/plugin.js.map +1 -1
- package/src/runtime-targets.d.ts +6 -0
- package/src/runtime-targets.js +17 -0
- package/src/runtime-targets.js.map +1 -1
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The runtime architecture GOAL (#1239): `architecture/runtime-dependencies-goal.json`.
|
|
4
|
+
*
|
|
5
|
+
* A proposal, not a fact. `architecture/runtime-dependencies.json` is GENERATED from source and its
|
|
6
|
+
* unchanged-validator rightly treats any hand edit as drift, so a proposal needs its own durable,
|
|
7
|
+
* reviewable file in the same RuntimeGraph shape. Nothing in the generator reads, writes or deletes
|
|
8
|
+
* this file: only a person (or an AI) edits it, and only the explicit `--prepare` step below rewrites
|
|
9
|
+
* its derived fields.
|
|
10
|
+
*
|
|
11
|
+
* Errors here never tell anyone to regenerate from source — a goal has no source. They name the goal
|
|
12
|
+
* path, the broken element, and the edit or the explicit preparation command that repairs it.
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.RuntimeGoalProvenance = exports.RuntimeGoalFile = exports.RuntimeGoalDrift = exports.RuntimeGoalPreparation = exports.RUNTIME_GOAL_INIT_COMMAND = exports.RUNTIME_GOAL_PREPARE_COMMAND = exports.RUNTIME_GOAL_RULE_NAME = exports.DEFAULT_RUNTIME_GOAL_PATH = void 0;
|
|
16
|
+
const tslib_1 = require("tslib");
|
|
17
|
+
const fs = tslib_1.__importStar(require("fs"));
|
|
18
|
+
const path = tslib_1.__importStar(require("path"));
|
|
19
|
+
const rules_config_1 = require("@webpieces/rules-config");
|
|
20
|
+
const runtime_graph_io_1 = require("./runtime-graph-io");
|
|
21
|
+
const runtime_node_levels_1 = require("./runtime-node-levels");
|
|
22
|
+
const runtime_goal_validator_1 = require("./runtime-goal-validator");
|
|
23
|
+
const toError_1 = require("../toError");
|
|
24
|
+
exports.DEFAULT_RUNTIME_GOAL_PATH = 'architecture/runtime-dependencies-goal.json';
|
|
25
|
+
exports.RUNTIME_GOAL_RULE_NAME = 'runtime-architecture-goal';
|
|
26
|
+
exports.RUNTIME_GOAL_PREPARE_COMMAND = 'pnpm nx run architecture:visualize-runtime-goal --prepare';
|
|
27
|
+
exports.RUNTIME_GOAL_INIT_COMMAND = `cp ${runtime_graph_io_1.DEFAULT_RUNTIME_GRAPH_PATH} ${exports.DEFAULT_RUNTIME_GOAL_PATH}`;
|
|
28
|
+
/** The derived fields `--prepare` rewrote, as `field: before → after` lines; empty when none moved. */
|
|
29
|
+
class RuntimeGoalPreparation {
|
|
30
|
+
graph;
|
|
31
|
+
changes;
|
|
32
|
+
written;
|
|
33
|
+
constructor(graph, changes,
|
|
34
|
+
/** Whether the goal file was rewritten (also true when only explanatory levelBecause text moved). */
|
|
35
|
+
written) {
|
|
36
|
+
this.graph = graph;
|
|
37
|
+
this.changes = changes;
|
|
38
|
+
this.written = written;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
exports.RuntimeGoalPreparation = RuntimeGoalPreparation;
|
|
42
|
+
/**
|
|
43
|
+
* What viewing found about the goal's derived facts: a CYCLE (only an edit of runtimeEdges fixes it,
|
|
44
|
+
* `--prepare` refuses it too) or field DIFFERENCES that `--prepare` would rewrite.
|
|
45
|
+
*/
|
|
46
|
+
class RuntimeGoalDrift {
|
|
47
|
+
cycle;
|
|
48
|
+
differences;
|
|
49
|
+
constructor(cycle, differences) {
|
|
50
|
+
this.cycle = cycle;
|
|
51
|
+
this.differences = differences;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
exports.RuntimeGoalDrift = RuntimeGoalDrift;
|
|
55
|
+
/** Loads, checks and (only on request) prepares the hand-authored runtime goal. */
|
|
56
|
+
class RuntimeGoalFile {
|
|
57
|
+
goalPath;
|
|
58
|
+
validator;
|
|
59
|
+
constructor(goalPath = exports.DEFAULT_RUNTIME_GOAL_PATH) {
|
|
60
|
+
this.goalPath = goalPath;
|
|
61
|
+
this.validator = new runtime_goal_validator_1.RuntimeGoalValidator(goalPath);
|
|
62
|
+
}
|
|
63
|
+
exists(workspaceRoot) {
|
|
64
|
+
return fs.existsSync(path.join(workspaceRoot, this.goalPath));
|
|
65
|
+
}
|
|
66
|
+
/** The goal as saved, after its shape, references and derived facts check out. Never writes anything. */
|
|
67
|
+
load(workspaceRoot) {
|
|
68
|
+
const parsed = this.structurallyValid(workspaceRoot);
|
|
69
|
+
const derived = this.validator.derived(parsed);
|
|
70
|
+
if (derived.length > 0)
|
|
71
|
+
throw this.failure(`${this.goalPath} lacks derived facts the viewer needs:\n ${derived.join('\n ')}`, [
|
|
72
|
+
new rules_config_1.Option(`Compute them from the goal's relationships: ${exports.RUNTIME_GOAL_PREPARE_COMMAND}`, true),
|
|
73
|
+
this.editOption(false),
|
|
74
|
+
]);
|
|
75
|
+
return parsed;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Where the saved derived facts disagree with what the existing runtime level machinery
|
|
79
|
+
* (RuntimeNodeLevels — the generator's own) computes from the goal's relationships. Reported, not
|
|
80
|
+
* fixed: viewing is read-only, and `--prepare` is the explicit step that rewrites them.
|
|
81
|
+
*/
|
|
82
|
+
drift(graph) {
|
|
83
|
+
const recomputed = this.recompute(graph);
|
|
84
|
+
if (typeof recomputed === 'string')
|
|
85
|
+
return new RuntimeGoalDrift(recomputed, []);
|
|
86
|
+
return new RuntimeGoalDrift(null, this.differences(graph, recomputed));
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Recompute levels, self-queue flags, contract queues and maxLevel, and WRITE the goal. Gated on
|
|
90
|
+
* STRUCTURAL problems only: missing or stale derived facts are exactly what this step computes.
|
|
91
|
+
*/
|
|
92
|
+
prepare(workspaceRoot) {
|
|
93
|
+
const saved = this.structurallyValid(workspaceRoot);
|
|
94
|
+
const recomputed = this.recompute(saved);
|
|
95
|
+
if (typeof recomputed === 'string')
|
|
96
|
+
throw this.failure(recomputed, [this.cycleOption()]);
|
|
97
|
+
const changes = this.differences(saved, recomputed);
|
|
98
|
+
const written = JSON.stringify(saved) !== JSON.stringify(recomputed);
|
|
99
|
+
if (written)
|
|
100
|
+
(0, runtime_graph_io_1.saveRuntimeGraph)(recomputed, workspaceRoot, this.goalPath);
|
|
101
|
+
return new RuntimeGoalPreparation(recomputed, changes, written);
|
|
102
|
+
}
|
|
103
|
+
/** The edit that clears a cycle verdict — `--prepare` cannot. */
|
|
104
|
+
cycleOption() {
|
|
105
|
+
return new rules_config_1.Option(`Break the cycle by editing "runtimeEdges" (or the queues it flows through) in ${this.goalPath}.`, true);
|
|
106
|
+
}
|
|
107
|
+
structurallyValid(workspaceRoot) {
|
|
108
|
+
if (!this.exists(workspaceRoot))
|
|
109
|
+
throw this.missing();
|
|
110
|
+
let parsed;
|
|
111
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- hand-authored JSON decoding boundary adds the goal path and repair while preserving the cause
|
|
112
|
+
try {
|
|
113
|
+
parsed = JSON.parse(fs.readFileSync(path.join(workspaceRoot, this.goalPath), 'utf-8'));
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
const error = (0, toError_1.toError)(err);
|
|
117
|
+
throw this.failure(`${this.goalPath} is not valid JSON: ${error.message}`, [this.editOption(true)], error);
|
|
118
|
+
}
|
|
119
|
+
const problems = this.validator.structural(parsed);
|
|
120
|
+
if (problems.length > 0)
|
|
121
|
+
throw this.failure(`${this.goalPath} is not a usable runtime goal:\n ${problems.join('\n ')}`, [this.editOption(true)]);
|
|
122
|
+
return parsed;
|
|
123
|
+
}
|
|
124
|
+
/** A deep copy with the generator's level machinery applied, or the cycle it refused. */
|
|
125
|
+
recompute(graph) {
|
|
126
|
+
const copy = JSON.parse(JSON.stringify(graph));
|
|
127
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- the shared level machinery throws its cycle verdict; the goal reports it against its own path
|
|
128
|
+
try {
|
|
129
|
+
new runtime_node_levels_1.RuntimeNodeLevels().apply(copy);
|
|
130
|
+
return copy;
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
const error = (0, toError_1.toError)(err);
|
|
134
|
+
if (!(error instanceof rules_config_1.RuleFailError))
|
|
135
|
+
throw error;
|
|
136
|
+
return `${this.goalPath} describes a runtime graph the level machinery cannot order: ${error.message}`;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
differences(saved, recomputed) {
|
|
140
|
+
const lines = [];
|
|
141
|
+
const level = (where, before, after) => {
|
|
142
|
+
if (before !== after)
|
|
143
|
+
lines.push(`${where} level: L${before ?? '?'} → L${after}`);
|
|
144
|
+
};
|
|
145
|
+
for (const [name, service] of Object.entries(recomputed.services))
|
|
146
|
+
level(`services["${name}"]`, saved.services[name].level, service.level);
|
|
147
|
+
for (const [key, queue] of Object.entries(recomputed.queues)) {
|
|
148
|
+
level(`queues["${key}"]`, saved.queues[key].level, queue.level);
|
|
149
|
+
if (saved.queues[key].selfQueue !== queue.selfQueue)
|
|
150
|
+
lines.push(`queues["${key}"] selfQueue: ${saved.queues[key].selfQueue} → ${queue.selfQueue}`);
|
|
151
|
+
}
|
|
152
|
+
lines.push(...this.contractDifferences(saved.contractQueues ?? {}, recomputed.contractQueues));
|
|
153
|
+
recomputed.triggers.forEach((trigger, index) => level(`triggers[${index}]`, saved.triggers[index].level, trigger.level));
|
|
154
|
+
if (saved.maxLevel !== recomputed.maxLevel)
|
|
155
|
+
lines.push(`maxLevel: ${saved.maxLevel} → ${recomputed.maxLevel}`);
|
|
156
|
+
return lines;
|
|
157
|
+
}
|
|
158
|
+
contractDifferences(saved, recomputed) {
|
|
159
|
+
const lines = [];
|
|
160
|
+
const fields = (flow) => flow === undefined
|
|
161
|
+
? '(absent)'
|
|
162
|
+
: `L${flow.level} self=${flow.selfQueue} producers=[${flow.producedBy.join(', ')}] consumers=[${flow.consumedBy.join(', ')}]`;
|
|
163
|
+
for (const api of [...new Set([...Object.keys(saved), ...Object.keys(recomputed)])].sort()) {
|
|
164
|
+
const before = fields(saved[api]);
|
|
165
|
+
const after = fields(recomputed[api]);
|
|
166
|
+
if (before !== after)
|
|
167
|
+
lines.push(`contractQueues["${api}"]: ${before} → ${after}`);
|
|
168
|
+
}
|
|
169
|
+
return lines;
|
|
170
|
+
}
|
|
171
|
+
missing() {
|
|
172
|
+
return this.failure(`No runtime goal at ${this.goalPath}. Viewing never creates or replaces the goal, and never renders the current graph in its place.`, [
|
|
173
|
+
new rules_config_1.Option(`Start a proposal from the saved current runtime graph: ${exports.RUNTIME_GOAL_INIT_COMMAND} — then edit the copy and commit it.`, true),
|
|
174
|
+
new rules_config_1.Option(`Write ${this.goalPath} by hand in the RuntimeGraph shape of ${runtime_graph_io_1.DEFAULT_RUNTIME_GRAPH_PATH}.`),
|
|
175
|
+
]);
|
|
176
|
+
}
|
|
177
|
+
editOption(preferred) {
|
|
178
|
+
return new rules_config_1.Option(`Edit ${this.goalPath} by hand — it is a hand-authored proposal, so the fix is in the file, not in source.`, preferred);
|
|
179
|
+
}
|
|
180
|
+
failure(message, options, cause) {
|
|
181
|
+
return new rules_config_1.RuleFailError(exports.RUNTIME_GOAL_RULE_NAME, message, undefined, undefined, options, undefined, cause);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
exports.RuntimeGoalFile = RuntimeGoalFile;
|
|
185
|
+
/** What a goal page says it is: a hand-authored proposal, not a generated snapshot (#1239). */
|
|
186
|
+
class RuntimeGoalProvenance {
|
|
187
|
+
message() {
|
|
188
|
+
return (`Goal / Proposed runtime architecture from ${exports.DEFAULT_RUNTIME_GOAL_PATH}. Hand-authored; not generated from source, ` +
|
|
189
|
+
'and never written by architecture:generate. Viewing changes no file. ' +
|
|
190
|
+
`After editing relationships, recompute derived levels explicitly: ${exports.RUNTIME_GOAL_PREPARE_COMMAND}`);
|
|
191
|
+
}
|
|
192
|
+
html() {
|
|
193
|
+
return (`<p class="hint"><strong>Goal / Proposed runtime architecture.</strong> Hand-authored proposal read from ` +
|
|
194
|
+
`<code>${exports.DEFAULT_RUNTIME_GOAL_PATH}</code>; not generated from source and never written by ` +
|
|
195
|
+
`<code>architecture:generate</code>. Viewing changes no file. After editing relationships, recompute derived ` +
|
|
196
|
+
`levels explicitly: <code>${exports.RUNTIME_GOAL_PREPARE_COMMAND}</code>. Compare with the saved current graph ` +
|
|
197
|
+
`(<code>${runtime_graph_io_1.DEFAULT_RUNTIME_GRAPH_PATH}</code>) in Color by → Goal vs Current.</p>`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
exports.RuntimeGoalProvenance = RuntimeGoalProvenance;
|
|
201
|
+
//# sourceMappingURL=runtime-goal-file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-goal-file.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-goal-file.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The runtime architecture GOAL (#1239): `architecture/runtime-dependencies-goal.json`.\n *\n * A proposal, not a fact. `architecture/runtime-dependencies.json` is GENERATED from source and its\n * unchanged-validator rightly treats any hand edit as drift, so a proposal needs its own durable,\n * reviewable file in the same RuntimeGraph shape. Nothing in the generator reads, writes or deletes\n * this file: only a person (or an AI) edits it, and only the explicit `--prepare` step below rewrites\n * its derived fields.\n *\n * Errors here never tell anyone to regenerate from source — a goal has no source. They name the goal\n * path, the broken element, and the edit or the explicit preparation command that repairs it.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { RuleFailError, Option } from '@webpieces/rules-config';\nimport type { RuntimeGraph, RuntimeQueueFlow, RuntimeTrigger } from './runtime-graph-model';\nimport { DEFAULT_RUNTIME_GRAPH_PATH, saveRuntimeGraph } from './runtime-graph-io';\nimport { RuntimeNodeLevels } from './runtime-node-levels';\nimport { RuntimeGoalValidator } from './runtime-goal-validator';\nimport { toError } from '../toError';\n\nexport const DEFAULT_RUNTIME_GOAL_PATH = 'architecture/runtime-dependencies-goal.json';\nexport const RUNTIME_GOAL_RULE_NAME = 'runtime-architecture-goal';\nexport const RUNTIME_GOAL_PREPARE_COMMAND = 'pnpm nx run architecture:visualize-runtime-goal --prepare';\nexport const RUNTIME_GOAL_INIT_COMMAND = `cp ${DEFAULT_RUNTIME_GRAPH_PATH} ${DEFAULT_RUNTIME_GOAL_PATH}`;\n\n/** The derived fields `--prepare` rewrote, as `field: before → after` lines; empty when none moved. */\nexport class RuntimeGoalPreparation {\n constructor(\n public readonly graph: RuntimeGraph,\n public readonly changes: string[],\n /** Whether the goal file was rewritten (also true when only explanatory levelBecause text moved). */\n public readonly written: boolean,\n ) {}\n}\n\n/**\n * What viewing found about the goal's derived facts: a CYCLE (only an edit of runtimeEdges fixes it,\n * `--prepare` refuses it too) or field DIFFERENCES that `--prepare` would rewrite.\n */\nexport class RuntimeGoalDrift {\n constructor(\n public readonly cycle: string | null,\n public readonly differences: string[],\n ) {}\n}\n\n/** Loads, checks and (only on request) prepares the hand-authored runtime goal. */\nexport class RuntimeGoalFile {\n private readonly validator: RuntimeGoalValidator;\n\n constructor(public readonly goalPath: string = DEFAULT_RUNTIME_GOAL_PATH) {\n this.validator = new RuntimeGoalValidator(goalPath);\n }\n\n exists(workspaceRoot: string): boolean {\n return fs.existsSync(path.join(workspaceRoot, this.goalPath));\n }\n\n /** The goal as saved, after its shape, references and derived facts check out. Never writes anything. */\n load(workspaceRoot: string): RuntimeGraph {\n const parsed = this.structurallyValid(workspaceRoot);\n const derived = this.validator.derived(parsed);\n if (derived.length > 0)\n throw this.failure(`${this.goalPath} lacks derived facts the viewer needs:\\n ${derived.join('\\n ')}`, [\n new Option(`Compute them from the goal's relationships: ${RUNTIME_GOAL_PREPARE_COMMAND}`, true),\n this.editOption(false),\n ]);\n return parsed;\n }\n\n /**\n * Where the saved derived facts disagree with what the existing runtime level machinery\n * (RuntimeNodeLevels — the generator's own) computes from the goal's relationships. Reported, not\n * fixed: viewing is read-only, and `--prepare` is the explicit step that rewrites them.\n */\n drift(graph: RuntimeGraph): RuntimeGoalDrift {\n const recomputed = this.recompute(graph);\n if (typeof recomputed === 'string') return new RuntimeGoalDrift(recomputed, []);\n return new RuntimeGoalDrift(null, this.differences(graph, recomputed));\n }\n\n /**\n * Recompute levels, self-queue flags, contract queues and maxLevel, and WRITE the goal. Gated on\n * STRUCTURAL problems only: missing or stale derived facts are exactly what this step computes.\n */\n prepare(workspaceRoot: string): RuntimeGoalPreparation {\n const saved = this.structurallyValid(workspaceRoot);\n const recomputed = this.recompute(saved);\n if (typeof recomputed === 'string') throw this.failure(recomputed, [this.cycleOption()]);\n const changes = this.differences(saved, recomputed);\n const written = JSON.stringify(saved) !== JSON.stringify(recomputed);\n if (written) saveRuntimeGraph(recomputed, workspaceRoot, this.goalPath);\n return new RuntimeGoalPreparation(recomputed, changes, written);\n }\n\n /** The edit that clears a cycle verdict — `--prepare` cannot. */\n cycleOption(): Option {\n return new Option(`Break the cycle by editing \"runtimeEdges\" (or the queues it flows through) in ${this.goalPath}.`, true);\n }\n\n private structurallyValid(workspaceRoot: string): RuntimeGraph {\n if (!this.exists(workspaceRoot)) throw this.missing();\n let parsed: RuntimeGraph;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- hand-authored JSON decoding boundary adds the goal path and repair while preserving the cause\n try {\n parsed = JSON.parse(fs.readFileSync(path.join(workspaceRoot, this.goalPath), 'utf-8')) as RuntimeGraph;\n } catch (err: unknown) {\n const error = toError(err);\n throw this.failure(`${this.goalPath} is not valid JSON: ${error.message}`, [this.editOption(true)], error);\n }\n const problems = this.validator.structural(parsed);\n if (problems.length > 0)\n throw this.failure(`${this.goalPath} is not a usable runtime goal:\\n ${problems.join('\\n ')}`, [this.editOption(true)]);\n return parsed;\n }\n\n /** A deep copy with the generator's level machinery applied, or the cycle it refused. */\n private recompute(graph: RuntimeGraph): RuntimeGraph | string {\n const copy = JSON.parse(JSON.stringify(graph)) as RuntimeGraph;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- the shared level machinery throws its cycle verdict; the goal reports it against its own path\n try {\n new RuntimeNodeLevels().apply(copy);\n return copy;\n } catch (err: unknown) {\n const error = toError(err);\n if (!(error instanceof RuleFailError)) throw error;\n return `${this.goalPath} describes a runtime graph the level machinery cannot order: ${error.message}`;\n }\n }\n\n private differences(saved: RuntimeGraph, recomputed: RuntimeGraph): string[] {\n const lines: string[] = [];\n const level = (where: string, before: number | undefined, after: number): void => {\n if (before !== after) lines.push(`${where} level: L${before ?? '?'} → L${after}`);\n };\n for (const [name, service] of Object.entries(recomputed.services)) level(`services[\"${name}\"]`, saved.services[name].level, service.level);\n for (const [key, queue] of Object.entries(recomputed.queues)) {\n level(`queues[\"${key}\"]`, saved.queues[key].level, queue.level);\n if (saved.queues[key].selfQueue !== queue.selfQueue)\n lines.push(`queues[\"${key}\"] selfQueue: ${saved.queues[key].selfQueue} → ${queue.selfQueue}`);\n }\n lines.push(...this.contractDifferences(saved.contractQueues ?? {}, recomputed.contractQueues));\n recomputed.triggers.forEach((trigger: RuntimeTrigger, index: number) =>\n level(`triggers[${index}]`, saved.triggers[index].level, trigger.level),\n );\n if (saved.maxLevel !== recomputed.maxLevel) lines.push(`maxLevel: ${saved.maxLevel} → ${recomputed.maxLevel}`);\n return lines;\n }\n\n private contractDifferences(saved: Record<string, RuntimeQueueFlow>, recomputed: Record<string, RuntimeQueueFlow>): string[] {\n const lines: string[] = [];\n const fields = (flow: RuntimeQueueFlow | undefined): string =>\n flow === undefined\n ? '(absent)'\n : `L${flow.level} self=${flow.selfQueue} producers=[${flow.producedBy.join(', ')}] consumers=[${flow.consumedBy.join(', ')}]`;\n for (const api of [...new Set([...Object.keys(saved), ...Object.keys(recomputed)])].sort()) {\n const before = fields(saved[api]);\n const after = fields(recomputed[api]);\n if (before !== after) lines.push(`contractQueues[\"${api}\"]: ${before} → ${after}`);\n }\n return lines;\n }\n\n private missing(): RuleFailError {\n return this.failure(\n `No runtime goal at ${this.goalPath}. Viewing never creates or replaces the goal, and never renders the current graph in its place.`,\n [\n new Option(`Start a proposal from the saved current runtime graph: ${RUNTIME_GOAL_INIT_COMMAND} — then edit the copy and commit it.`, true),\n new Option(`Write ${this.goalPath} by hand in the RuntimeGraph shape of ${DEFAULT_RUNTIME_GRAPH_PATH}.`),\n ],\n );\n }\n\n private editOption(preferred: boolean): Option {\n return new Option(`Edit ${this.goalPath} by hand — it is a hand-authored proposal, so the fix is in the file, not in source.`, preferred);\n }\n\n private failure(message: string, options: Option[], cause?: Error): RuleFailError {\n return new RuleFailError(RUNTIME_GOAL_RULE_NAME, message, undefined, undefined, options, undefined, cause);\n }\n}\n\n/** What a goal page says it is: a hand-authored proposal, not a generated snapshot (#1239). */\nexport class RuntimeGoalProvenance {\n message(): string {\n return (\n `Goal / Proposed runtime architecture from ${DEFAULT_RUNTIME_GOAL_PATH}. Hand-authored; not generated from source, ` +\n 'and never written by architecture:generate. Viewing changes no file. ' +\n `After editing relationships, recompute derived levels explicitly: ${RUNTIME_GOAL_PREPARE_COMMAND}`\n );\n }\n\n html(): string {\n return (\n `<p class=\"hint\"><strong>Goal / Proposed runtime architecture.</strong> Hand-authored proposal read from ` +\n `<code>${DEFAULT_RUNTIME_GOAL_PATH}</code>; not generated from source and never written by ` +\n `<code>architecture:generate</code>. Viewing changes no file. After editing relationships, recompute derived ` +\n `levels explicitly: <code>${RUNTIME_GOAL_PREPARE_COMMAND}</code>. Compare with the saved current graph ` +\n `(<code>${DEFAULT_RUNTIME_GRAPH_PATH}</code>) in Color by → Goal vs Current.</p>`\n );\n }\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { RuntimeDiffStatus } from './runtime-graph-diff';
|
|
2
|
+
/** One status's colors. Text color stays readable on the fill. */
|
|
3
|
+
export declare class DiffStyle {
|
|
4
|
+
readonly fill: string;
|
|
5
|
+
readonly border: string;
|
|
6
|
+
readonly text: string;
|
|
7
|
+
readonly word: string;
|
|
8
|
+
constructor(fill: string, border: string, text: string, word: string);
|
|
9
|
+
}
|
|
10
|
+
export declare const DIFF_STYLES: Record<RuntimeDiffStatus, DiffStyle>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DIFF_STYLES = exports.DiffStyle = void 0;
|
|
4
|
+
const runtime_graph_diff_1 = require("./runtime-graph-diff");
|
|
5
|
+
/** One status's colors. Text color stays readable on the fill. */
|
|
6
|
+
class DiffStyle {
|
|
7
|
+
fill;
|
|
8
|
+
border;
|
|
9
|
+
text;
|
|
10
|
+
word;
|
|
11
|
+
constructor(fill, border, text, word) {
|
|
12
|
+
this.fill = fill;
|
|
13
|
+
this.border = border;
|
|
14
|
+
this.text = text;
|
|
15
|
+
this.word = word;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
exports.DiffStyle = DiffStyle;
|
|
19
|
+
exports.DIFF_STYLES = {
|
|
20
|
+
[runtime_graph_diff_1.RuntimeDiffStatus.ADDED]: new DiffStyle('#c9f0d3', '#1f8a3b', '#1a1c22', 'added in goal'),
|
|
21
|
+
[runtime_graph_diff_1.RuntimeDiffStatus.REMOVED]: new DiffStyle('#fde0e0', '#c62828', '#7a1c1c', 'removed from goal'),
|
|
22
|
+
[runtime_graph_diff_1.RuntimeDiffStatus.CHANGED]: new DiffStyle('#fde7b0', '#d88a00', '#1a1c22', 'changed'),
|
|
23
|
+
[runtime_graph_diff_1.RuntimeDiffStatus.UNCHANGED]: new DiffStyle('#eef0f4', '#b4b9c4', '#6b7180', 'unchanged'),
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=runtime-goal-styles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-goal-styles.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-goal-styles.ts"],"names":[],"mappings":";;;AAAA,6DAAyD;AAEzD,kEAAkE;AAClE,MAAa,SAAS;IAEE;IACA;IACA;IACA;IAJpB,YACoB,IAAY,EACZ,MAAc,EACd,IAAY,EACZ,IAAY;QAHZ,SAAI,GAAJ,IAAI,CAAQ;QACZ,WAAM,GAAN,MAAM,CAAQ;QACd,SAAI,GAAJ,IAAI,CAAQ;QACZ,SAAI,GAAJ,IAAI,CAAQ;IAC7B,CAAC;CACP;AAPD,8BAOC;AAEY,QAAA,WAAW,GAAyC;IAC7D,CAAC,sCAAiB,CAAC,KAAK,CAAC,EAAE,IAAI,SAAS,CAAC,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,eAAe,CAAC;IAC1F,CAAC,sCAAiB,CAAC,OAAO,CAAC,EAAE,IAAI,SAAS,CAAC,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,mBAAmB,CAAC;IAChG,CAAC,sCAAiB,CAAC,OAAO,CAAC,EAAE,IAAI,SAAS,CAAC,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,CAAC;IACtF,CAAC,sCAAiB,CAAC,SAAS,CAAC,EAAE,IAAI,SAAS,CAAC,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,CAAC;CAC7F,CAAC","sourcesContent":["import { RuntimeDiffStatus } from './runtime-graph-diff';\n\n/** One status's colors. Text color stays readable on the fill. */\nexport class DiffStyle {\n constructor(\n public readonly fill: string,\n public readonly border: string,\n public readonly text: string,\n public readonly word: string,\n ) {}\n}\n\nexport const DIFF_STYLES: Record<RuntimeDiffStatus, DiffStyle> = {\n [RuntimeDiffStatus.ADDED]: new DiffStyle('#c9f0d3', '#1f8a3b', '#1a1c22', 'added in goal'),\n [RuntimeDiffStatus.REMOVED]: new DiffStyle('#fde0e0', '#c62828', '#7a1c1c', 'removed from goal'),\n [RuntimeDiffStatus.CHANGED]: new DiffStyle('#fde7b0', '#d88a00', '#1a1c22', 'changed'),\n [RuntimeDiffStatus.UNCHANGED]: new DiffStyle('#eef0f4', '#b4b9c4', '#6b7180', 'unchanged'),\n};\n"]}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { RuntimeGraph } from './runtime-graph-model';
|
|
2
|
+
/**
|
|
3
|
+
* Shape and internal-reference checks for a HAND-AUTHORED runtime goal (#1239).
|
|
4
|
+
*
|
|
5
|
+
* The generated current graph is validated by regenerating it from source; a goal has no source to
|
|
6
|
+
* regenerate from, so every problem is named against the goal file itself, with the element that is
|
|
7
|
+
* wrong and the edit that fixes it. Problems are collected, never thrown one at a time, so one run
|
|
8
|
+
* names every broken reference.
|
|
9
|
+
*
|
|
10
|
+
* Two kinds of problem, because they have different cures:
|
|
11
|
+
* - STRUCTURAL (shape, dangling references): only an edit of the goal file fixes them.
|
|
12
|
+
* - DERIVED (a missing or invalid level, a missing contract queue, maxLevel): the explicit
|
|
13
|
+
* `--prepare` step computes them from the relationships, so it must accept a goal that has them.
|
|
14
|
+
* Facts that are present but merely disagree with the relationships are drift, reported by
|
|
15
|
+
* RuntimeGoalFile.drift().
|
|
16
|
+
*/
|
|
17
|
+
export declare class RuntimeGoalValidator {
|
|
18
|
+
private readonly goalPath;
|
|
19
|
+
constructor(goalPath: string);
|
|
20
|
+
/** Problems only an edit of the goal file can fix; empty when `--prepare` can run. */
|
|
21
|
+
structural(graph: unknown): string[];
|
|
22
|
+
/** Derived facts `--prepare` computes; call only on a structurally valid goal. */
|
|
23
|
+
derived(graph: RuntimeGraph): string[];
|
|
24
|
+
private shape;
|
|
25
|
+
/** The renderer places every node by its saved level, so each needs one. */
|
|
26
|
+
private levels;
|
|
27
|
+
private references;
|
|
28
|
+
private services;
|
|
29
|
+
private edges;
|
|
30
|
+
private edge;
|
|
31
|
+
/** Every contract-level pubsub edge needs its contract queue, which `--prepare` derives. */
|
|
32
|
+
private contractQueues;
|
|
33
|
+
private queues;
|
|
34
|
+
private triggers;
|
|
35
|
+
private externals;
|
|
36
|
+
private remotes;
|
|
37
|
+
private strings;
|
|
38
|
+
private isMap;
|
|
39
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RuntimeGoalValidator = void 0;
|
|
4
|
+
/** One queue flow and the goal path segment that names it. */
|
|
5
|
+
class NamedFlow {
|
|
6
|
+
where;
|
|
7
|
+
flow;
|
|
8
|
+
constructor(where, flow) {
|
|
9
|
+
this.where = where;
|
|
10
|
+
this.flow = flow;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Shape and internal-reference checks for a HAND-AUTHORED runtime goal (#1239).
|
|
15
|
+
*
|
|
16
|
+
* The generated current graph is validated by regenerating it from source; a goal has no source to
|
|
17
|
+
* regenerate from, so every problem is named against the goal file itself, with the element that is
|
|
18
|
+
* wrong and the edit that fixes it. Problems are collected, never thrown one at a time, so one run
|
|
19
|
+
* names every broken reference.
|
|
20
|
+
*
|
|
21
|
+
* Two kinds of problem, because they have different cures:
|
|
22
|
+
* - STRUCTURAL (shape, dangling references): only an edit of the goal file fixes them.
|
|
23
|
+
* - DERIVED (a missing or invalid level, a missing contract queue, maxLevel): the explicit
|
|
24
|
+
* `--prepare` step computes them from the relationships, so it must accept a goal that has them.
|
|
25
|
+
* Facts that are present but merely disagree with the relationships are drift, reported by
|
|
26
|
+
* RuntimeGoalFile.drift().
|
|
27
|
+
*/
|
|
28
|
+
class RuntimeGoalValidator {
|
|
29
|
+
goalPath;
|
|
30
|
+
constructor(goalPath) {
|
|
31
|
+
this.goalPath = goalPath;
|
|
32
|
+
}
|
|
33
|
+
/** Problems only an edit of the goal file can fix; empty when `--prepare` can run. */
|
|
34
|
+
// webpieces-disable no-any-unknown -- hand-authored JSON is untrusted until its shape has been checked
|
|
35
|
+
structural(graph) {
|
|
36
|
+
if (!this.isMap(graph))
|
|
37
|
+
return [`${this.goalPath} must contain a JSON object (a RuntimeGraph).`];
|
|
38
|
+
const typed = graph;
|
|
39
|
+
const shape = this.shape(typed);
|
|
40
|
+
if (shape.length > 0)
|
|
41
|
+
return shape;
|
|
42
|
+
return this.references(typed);
|
|
43
|
+
}
|
|
44
|
+
/** Derived facts `--prepare` computes; call only on a structurally valid goal. */
|
|
45
|
+
derived(graph) {
|
|
46
|
+
const problems = [];
|
|
47
|
+
if (!Number.isInteger(graph.maxLevel))
|
|
48
|
+
problems.push(`${this.goalPath}: "maxLevel" must be an integer.`);
|
|
49
|
+
if (graph.contractQueues === undefined)
|
|
50
|
+
problems.push(`${this.goalPath}: "contractQueues" is missing.`);
|
|
51
|
+
return [...problems, ...this.levels(graph), ...this.contractQueues(graph)];
|
|
52
|
+
}
|
|
53
|
+
shape(graph) {
|
|
54
|
+
const problems = [];
|
|
55
|
+
for (const key of ['services', 'apis', 'queues']) {
|
|
56
|
+
if (!this.isMap(graph[key]))
|
|
57
|
+
problems.push(`${this.goalPath}: "${key}" must be an object keyed by identity.`);
|
|
58
|
+
}
|
|
59
|
+
for (const key of ['runtimeEdges', 'unresolvedUses', 'triggers']) {
|
|
60
|
+
if (!Array.isArray(graph[key]))
|
|
61
|
+
problems.push(`${this.goalPath}: "${key}" must be an array.`);
|
|
62
|
+
}
|
|
63
|
+
for (const key of ['externalSystems', 'remoteServices', 'contractQueues']) {
|
|
64
|
+
if (graph[key] !== undefined && !this.isMap(graph[key]))
|
|
65
|
+
problems.push(`${this.goalPath}: "${key}" must be an object when present.`);
|
|
66
|
+
}
|
|
67
|
+
return problems;
|
|
68
|
+
}
|
|
69
|
+
/** The renderer places every node by its saved level, so each needs one. */
|
|
70
|
+
levels(graph) {
|
|
71
|
+
const problems = [];
|
|
72
|
+
const check = (where, node) => {
|
|
73
|
+
if (!this.isMap(node) || !Number.isInteger(node.level) || node.level < 0 || typeof node.levelBecause !== 'string')
|
|
74
|
+
problems.push(`${this.goalPath}: ${where} needs an integer "level" >= 0 and a string "levelBecause".`);
|
|
75
|
+
};
|
|
76
|
+
for (const [name, service] of Object.entries(graph.services))
|
|
77
|
+
check(`services["${name}"]`, service);
|
|
78
|
+
for (const [key, queue] of Object.entries(graph.queues))
|
|
79
|
+
check(`queues["${key}"]`, queue);
|
|
80
|
+
for (const [api, queue] of Object.entries(graph.contractQueues ?? {}))
|
|
81
|
+
check(`contractQueues["${api}"]`, queue);
|
|
82
|
+
graph.triggers.forEach((trigger, index) => check(`triggers[${index}]`, trigger));
|
|
83
|
+
graph.unresolvedUses.forEach((use, index) => check(`unresolvedUses[${index}]`, use));
|
|
84
|
+
for (const [id, system] of Object.entries(graph.externalSystems ?? {}))
|
|
85
|
+
check(`externalSystems["${id}"]`, system);
|
|
86
|
+
for (const [name, remote] of Object.entries(graph.remoteServices ?? {}))
|
|
87
|
+
check(`remoteServices["${name}"]`, remote);
|
|
88
|
+
return problems;
|
|
89
|
+
}
|
|
90
|
+
references(graph) {
|
|
91
|
+
return [
|
|
92
|
+
...this.services(graph),
|
|
93
|
+
...this.edges(graph),
|
|
94
|
+
...this.queues(graph),
|
|
95
|
+
...this.triggers(graph),
|
|
96
|
+
...this.externals(graph),
|
|
97
|
+
...this.remotes(graph),
|
|
98
|
+
];
|
|
99
|
+
}
|
|
100
|
+
services(graph) {
|
|
101
|
+
const problems = [];
|
|
102
|
+
for (const [name, service] of Object.entries(graph.services)) {
|
|
103
|
+
for (const key of ['implements', 'uses', 'dependsOn']) {
|
|
104
|
+
if (!this.strings(service[key]))
|
|
105
|
+
problems.push(`${this.goalPath}: services["${name}"].${key} must be an array of strings (use [] for none).`);
|
|
106
|
+
}
|
|
107
|
+
if (service.products !== undefined && !this.strings(service.products))
|
|
108
|
+
problems.push(`${this.goalPath}: services["${name}"].products must be an array of strings when present.`);
|
|
109
|
+
}
|
|
110
|
+
return problems;
|
|
111
|
+
}
|
|
112
|
+
edges(graph) {
|
|
113
|
+
const problems = [];
|
|
114
|
+
graph.runtimeEdges.forEach((edge, index) => problems.push(...this.edge(graph, edge, index)));
|
|
115
|
+
return problems;
|
|
116
|
+
}
|
|
117
|
+
edge(graph, edge, index) {
|
|
118
|
+
const where = `${this.goalPath}: runtimeEdges[${index}] (${edge?.from} -> ${edge?.to})`;
|
|
119
|
+
if (!this.isMap(edge) || !this.strings(edge.via) || edge.via.length === 0)
|
|
120
|
+
return [`${where} needs "from", "to" and a non-empty "via" contract list.`];
|
|
121
|
+
const problems = [];
|
|
122
|
+
for (const end of [edge.from, edge.to]) {
|
|
123
|
+
if (graph.services[end] === undefined)
|
|
124
|
+
problems.push(`${where} names '${end}', which is not in "services". Add services["${end}"] or remove the edge.`);
|
|
125
|
+
}
|
|
126
|
+
if (edge.type !== undefined && edge.type !== 'rpc' && edge.type !== 'pubsub')
|
|
127
|
+
problems.push(`${where} has type '${edge.type}'; use "rpc" or "pubsub".`);
|
|
128
|
+
if (edge.type === 'pubsub' && edge.queue !== undefined && graph.queues[edge.queue] === undefined)
|
|
129
|
+
problems.push(`${where} flows through queue '${edge.queue}', which is not in "queues". Add queues["${edge.queue}"] or drop "queue" for a contract-level edge.`);
|
|
130
|
+
return problems;
|
|
131
|
+
}
|
|
132
|
+
/** Every contract-level pubsub edge needs its contract queue, which `--prepare` derives. */
|
|
133
|
+
contractQueues(graph) {
|
|
134
|
+
const problems = [];
|
|
135
|
+
graph.runtimeEdges.forEach((edge, index) => {
|
|
136
|
+
if (edge.type !== 'pubsub' || edge.queue !== undefined)
|
|
137
|
+
return;
|
|
138
|
+
for (const api of edge.via) {
|
|
139
|
+
if (graph.contractQueues?.[api] === undefined)
|
|
140
|
+
problems.push(`${this.goalPath}: runtimeEdges[${index}] (${edge.from} -> ${edge.to}) is a contract-level pubsub edge over ${api}, but contractQueues["${api}"] is missing.`);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
return problems;
|
|
144
|
+
}
|
|
145
|
+
queues(graph) {
|
|
146
|
+
const problems = [];
|
|
147
|
+
const flows = [];
|
|
148
|
+
for (const key of Object.keys(graph.queues))
|
|
149
|
+
flows.push(new NamedFlow(`queues["${key}"]`, graph.queues[key]));
|
|
150
|
+
const contracts = graph.contractQueues ?? {};
|
|
151
|
+
for (const api of Object.keys(contracts))
|
|
152
|
+
flows.push(new NamedFlow(`contractQueues["${api}"]`, contracts[api]));
|
|
153
|
+
for (const named of flows) {
|
|
154
|
+
const where = named.where;
|
|
155
|
+
const flow = named.flow;
|
|
156
|
+
for (const key of ['producedBy', 'consumedBy']) {
|
|
157
|
+
if (!this.strings(flow[key])) {
|
|
158
|
+
problems.push(`${this.goalPath}: ${where}.${key} must be an array of service names.`);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
for (const name of flow[key]) {
|
|
162
|
+
if (graph.services[name] === undefined)
|
|
163
|
+
problems.push(`${this.goalPath}: ${where}.${key} names '${name}', which is not in "services".`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
for (const [key, queue] of Object.entries(graph.queues)) {
|
|
168
|
+
if (typeof queue.api !== 'string' || typeof queue.method !== 'string' || typeof queue.queueName !== 'string')
|
|
169
|
+
problems.push(`${this.goalPath}: queues["${key}"] needs string "api", "method" and "queueName".`);
|
|
170
|
+
}
|
|
171
|
+
return problems;
|
|
172
|
+
}
|
|
173
|
+
triggers(graph) {
|
|
174
|
+
const problems = [];
|
|
175
|
+
graph.triggers.forEach((trigger, index) => {
|
|
176
|
+
const where = `${this.goalPath}: triggers[${index}]`;
|
|
177
|
+
if (trigger.kind !== 'cron' && trigger.kind !== 'external')
|
|
178
|
+
problems.push(`${where} has kind '${trigger.kind}'; use "cron" or "external".`);
|
|
179
|
+
if (typeof trigger.api !== 'string' || typeof trigger.method !== 'string')
|
|
180
|
+
problems.push(`${where} needs string "api" and "method".`);
|
|
181
|
+
if (graph.services[trigger.service] === undefined)
|
|
182
|
+
problems.push(`${where} targets '${trigger.service}', which is not in "services".`);
|
|
183
|
+
});
|
|
184
|
+
graph.unresolvedUses.forEach((use, index) => {
|
|
185
|
+
if (graph.services[use.service] === undefined)
|
|
186
|
+
problems.push(`${this.goalPath}: unresolvedUses[${index}] names '${use.service}', which is not in "services".`);
|
|
187
|
+
if (typeof use.api !== 'string')
|
|
188
|
+
problems.push(`${this.goalPath}: unresolvedUses[${index}] needs a string "api".`);
|
|
189
|
+
});
|
|
190
|
+
return problems;
|
|
191
|
+
}
|
|
192
|
+
externals(graph) {
|
|
193
|
+
const problems = [];
|
|
194
|
+
for (const [id, system] of Object.entries(graph.externalSystems ?? {})) {
|
|
195
|
+
const where = `${this.goalPath}: externalSystems["${id}"]`;
|
|
196
|
+
if (typeof system.kind !== 'string' || typeof system.label !== 'string' || !this.strings(system.apis))
|
|
197
|
+
problems.push(`${where} needs string "kind" and "label" and an "apis" string array.`);
|
|
198
|
+
if (!this.strings(system.usedBy)) {
|
|
199
|
+
problems.push(`${where}.usedBy must be an array of service names.`);
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
for (const name of system.usedBy) {
|
|
203
|
+
if (graph.services[name] === undefined)
|
|
204
|
+
problems.push(`${where}.usedBy names '${name}', which is not in "services".`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return problems;
|
|
208
|
+
}
|
|
209
|
+
remotes(graph) {
|
|
210
|
+
const problems = [];
|
|
211
|
+
for (const [name, remote] of Object.entries(graph.remoteServices ?? {})) {
|
|
212
|
+
const where = `${this.goalPath}: remoteServices["${name}"]`;
|
|
213
|
+
if (!this.strings(remote.implements) || !Array.isArray(remote.calledBy)) {
|
|
214
|
+
problems.push(`${where} needs an "implements" string array and a "calledBy" array.`);
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
remote.calledBy.forEach((call, index) => {
|
|
218
|
+
if (!this.isMap(call) || !this.strings(call.via) || (call.type !== 'rpc' && call.type !== 'pubsub'))
|
|
219
|
+
problems.push(`${where}.calledBy[${index}] needs "from", a "via" string array and type "rpc" or "pubsub".`);
|
|
220
|
+
else if (graph.services[call.from] === undefined)
|
|
221
|
+
problems.push(`${where}.calledBy[${index}] names '${call.from}', which is not in "services".`);
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
return problems;
|
|
225
|
+
}
|
|
226
|
+
// webpieces-disable no-any-unknown -- hand-authored JSON is untrusted until its shape has been checked
|
|
227
|
+
strings(value) {
|
|
228
|
+
return Array.isArray(value) && value.every((entry) => typeof entry === 'string');
|
|
229
|
+
}
|
|
230
|
+
// webpieces-disable no-any-unknown -- hand-authored JSON is untrusted until its shape has been checked
|
|
231
|
+
isMap(value) {
|
|
232
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
exports.RuntimeGoalValidator = RuntimeGoalValidator;
|
|
236
|
+
//# sourceMappingURL=runtime-goal-validator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-goal-validator.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-goal-validator.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n RuntimeEdge,\n RuntimeGraph,\n RuntimeLevel,\n RuntimeQueueFlow,\n RuntimeRemoteCall,\n RuntimeTrigger,\n RuntimeUnresolved,\n} from './runtime-graph-model';\n\n/** One queue flow and the goal path segment that names it. */\nclass NamedFlow {\n constructor(\n public readonly where: string,\n public readonly flow: RuntimeQueueFlow,\n ) {}\n}\n\n/**\n * Shape and internal-reference checks for a HAND-AUTHORED runtime goal (#1239).\n *\n * The generated current graph is validated by regenerating it from source; a goal has no source to\n * regenerate from, so every problem is named against the goal file itself, with the element that is\n * wrong and the edit that fixes it. Problems are collected, never thrown one at a time, so one run\n * names every broken reference.\n *\n * Two kinds of problem, because they have different cures:\n * - STRUCTURAL (shape, dangling references): only an edit of the goal file fixes them.\n * - DERIVED (a missing or invalid level, a missing contract queue, maxLevel): the explicit\n * `--prepare` step computes them from the relationships, so it must accept a goal that has them.\n * Facts that are present but merely disagree with the relationships are drift, reported by\n * RuntimeGoalFile.drift().\n */\nexport class RuntimeGoalValidator {\n constructor(private readonly goalPath: string) {}\n\n /** Problems only an edit of the goal file can fix; empty when `--prepare` can run. */\n // webpieces-disable no-any-unknown -- hand-authored JSON is untrusted until its shape has been checked\n structural(graph: unknown): string[] {\n if (!this.isMap(graph)) return [`${this.goalPath} must contain a JSON object (a RuntimeGraph).`];\n const typed = graph as RuntimeGraph;\n const shape = this.shape(typed);\n if (shape.length > 0) return shape;\n return this.references(typed);\n }\n\n /** Derived facts `--prepare` computes; call only on a structurally valid goal. */\n derived(graph: RuntimeGraph): string[] {\n const problems: string[] = [];\n if (!Number.isInteger(graph.maxLevel)) problems.push(`${this.goalPath}: \"maxLevel\" must be an integer.`);\n if (graph.contractQueues === undefined) problems.push(`${this.goalPath}: \"contractQueues\" is missing.`);\n return [...problems, ...this.levels(graph), ...this.contractQueues(graph)];\n }\n\n private shape(graph: RuntimeGraph): string[] {\n const problems: string[] = [];\n for (const key of ['services', 'apis', 'queues'] as const) {\n if (!this.isMap(graph[key])) problems.push(`${this.goalPath}: \"${key}\" must be an object keyed by identity.`);\n }\n for (const key of ['runtimeEdges', 'unresolvedUses', 'triggers'] as const) {\n if (!Array.isArray(graph[key])) problems.push(`${this.goalPath}: \"${key}\" must be an array.`);\n }\n for (const key of ['externalSystems', 'remoteServices', 'contractQueues'] as const) {\n if (graph[key] !== undefined && !this.isMap(graph[key]))\n problems.push(`${this.goalPath}: \"${key}\" must be an object when present.`);\n }\n return problems;\n }\n\n /** The renderer places every node by its saved level, so each needs one. */\n private levels(graph: RuntimeGraph): string[] {\n const problems: string[] = [];\n const check = (where: string, node: RuntimeLevel): void => {\n if (!this.isMap(node) || !Number.isInteger(node.level) || node.level < 0 || typeof node.levelBecause !== 'string')\n problems.push(`${this.goalPath}: ${where} needs an integer \"level\" >= 0 and a string \"levelBecause\".`);\n };\n for (const [name, service] of Object.entries(graph.services)) check(`services[\"${name}\"]`, service);\n for (const [key, queue] of Object.entries(graph.queues)) check(`queues[\"${key}\"]`, queue);\n for (const [api, queue] of Object.entries(graph.contractQueues ?? {})) check(`contractQueues[\"${api}\"]`, queue);\n graph.triggers.forEach((trigger: RuntimeTrigger, index: number) => check(`triggers[${index}]`, trigger));\n graph.unresolvedUses.forEach((use: RuntimeUnresolved, index: number) => check(`unresolvedUses[${index}]`, use));\n for (const [id, system] of Object.entries(graph.externalSystems ?? {})) check(`externalSystems[\"${id}\"]`, system);\n for (const [name, remote] of Object.entries(graph.remoteServices ?? {})) check(`remoteServices[\"${name}\"]`, remote);\n return problems;\n }\n\n private references(graph: RuntimeGraph): string[] {\n return [\n ...this.services(graph),\n ...this.edges(graph),\n ...this.queues(graph),\n ...this.triggers(graph),\n ...this.externals(graph),\n ...this.remotes(graph),\n ];\n }\n\n private services(graph: RuntimeGraph): string[] {\n const problems: string[] = [];\n for (const [name, service] of Object.entries(graph.services)) {\n for (const key of ['implements', 'uses', 'dependsOn'] as const) {\n if (!this.strings(service[key]))\n problems.push(`${this.goalPath}: services[\"${name}\"].${key} must be an array of strings (use [] for none).`);\n }\n if (service.products !== undefined && !this.strings(service.products))\n problems.push(`${this.goalPath}: services[\"${name}\"].products must be an array of strings when present.`);\n }\n return problems;\n }\n\n private edges(graph: RuntimeGraph): string[] {\n const problems: string[] = [];\n graph.runtimeEdges.forEach((edge: RuntimeEdge, index: number) => problems.push(...this.edge(graph, edge, index)));\n return problems;\n }\n\n private edge(graph: RuntimeGraph, edge: RuntimeEdge, index: number): string[] {\n const where = `${this.goalPath}: runtimeEdges[${index}] (${edge?.from} -> ${edge?.to})`;\n if (!this.isMap(edge) || !this.strings(edge.via) || edge.via.length === 0)\n return [`${where} needs \"from\", \"to\" and a non-empty \"via\" contract list.`];\n const problems: string[] = [];\n for (const end of [edge.from, edge.to]) {\n if (graph.services[end] === undefined)\n problems.push(`${where} names '${end}', which is not in \"services\". Add services[\"${end}\"] or remove the edge.`);\n }\n if (edge.type !== undefined && edge.type !== 'rpc' && edge.type !== 'pubsub')\n problems.push(`${where} has type '${edge.type}'; use \"rpc\" or \"pubsub\".`);\n if (edge.type === 'pubsub' && edge.queue !== undefined && graph.queues[edge.queue] === undefined)\n problems.push(`${where} flows through queue '${edge.queue}', which is not in \"queues\". Add queues[\"${edge.queue}\"] or drop \"queue\" for a contract-level edge.`);\n return problems;\n }\n\n /** Every contract-level pubsub edge needs its contract queue, which `--prepare` derives. */\n private contractQueues(graph: RuntimeGraph): string[] {\n const problems: string[] = [];\n graph.runtimeEdges.forEach((edge: RuntimeEdge, index: number) => {\n if (edge.type !== 'pubsub' || edge.queue !== undefined) return;\n for (const api of edge.via) {\n if (graph.contractQueues?.[api] === undefined)\n problems.push(`${this.goalPath}: runtimeEdges[${index}] (${edge.from} -> ${edge.to}) is a contract-level pubsub edge over ${api}, but contractQueues[\"${api}\"] is missing.`);\n }\n });\n return problems;\n }\n\n private queues(graph: RuntimeGraph): string[] {\n const problems: string[] = [];\n const flows: NamedFlow[] = [];\n for (const key of Object.keys(graph.queues)) flows.push(new NamedFlow(`queues[\"${key}\"]`, graph.queues[key]));\n const contracts = graph.contractQueues ?? {};\n for (const api of Object.keys(contracts)) flows.push(new NamedFlow(`contractQueues[\"${api}\"]`, contracts[api]));\n for (const named of flows) {\n const where = named.where;\n const flow = named.flow;\n for (const key of ['producedBy', 'consumedBy'] as const) {\n if (!this.strings(flow[key])) {\n problems.push(`${this.goalPath}: ${where}.${key} must be an array of service names.`);\n continue;\n }\n for (const name of flow[key]) {\n if (graph.services[name] === undefined)\n problems.push(`${this.goalPath}: ${where}.${key} names '${name}', which is not in \"services\".`);\n }\n }\n }\n for (const [key, queue] of Object.entries(graph.queues)) {\n if (typeof queue.api !== 'string' || typeof queue.method !== 'string' || typeof queue.queueName !== 'string')\n problems.push(`${this.goalPath}: queues[\"${key}\"] needs string \"api\", \"method\" and \"queueName\".`);\n }\n return problems;\n }\n\n private triggers(graph: RuntimeGraph): string[] {\n const problems: string[] = [];\n graph.triggers.forEach((trigger: RuntimeTrigger, index: number) => {\n const where = `${this.goalPath}: triggers[${index}]`;\n if (trigger.kind !== 'cron' && trigger.kind !== 'external')\n problems.push(`${where} has kind '${trigger.kind}'; use \"cron\" or \"external\".`);\n if (typeof trigger.api !== 'string' || typeof trigger.method !== 'string')\n problems.push(`${where} needs string \"api\" and \"method\".`);\n if (graph.services[trigger.service] === undefined)\n problems.push(`${where} targets '${trigger.service}', which is not in \"services\".`);\n });\n graph.unresolvedUses.forEach((use: RuntimeUnresolved, index: number) => {\n if (graph.services[use.service] === undefined)\n problems.push(`${this.goalPath}: unresolvedUses[${index}] names '${use.service}', which is not in \"services\".`);\n if (typeof use.api !== 'string') problems.push(`${this.goalPath}: unresolvedUses[${index}] needs a string \"api\".`);\n });\n return problems;\n }\n\n private externals(graph: RuntimeGraph): string[] {\n const problems: string[] = [];\n for (const [id, system] of Object.entries(graph.externalSystems ?? {})) {\n const where = `${this.goalPath}: externalSystems[\"${id}\"]`;\n if (typeof system.kind !== 'string' || typeof system.label !== 'string' || !this.strings(system.apis))\n problems.push(`${where} needs string \"kind\" and \"label\" and an \"apis\" string array.`);\n if (!this.strings(system.usedBy)) {\n problems.push(`${where}.usedBy must be an array of service names.`);\n continue;\n }\n for (const name of system.usedBy) {\n if (graph.services[name] === undefined) problems.push(`${where}.usedBy names '${name}', which is not in \"services\".`);\n }\n }\n return problems;\n }\n\n private remotes(graph: RuntimeGraph): string[] {\n const problems: string[] = [];\n for (const [name, remote] of Object.entries(graph.remoteServices ?? {})) {\n const where = `${this.goalPath}: remoteServices[\"${name}\"]`;\n if (!this.strings(remote.implements) || !Array.isArray(remote.calledBy)) {\n problems.push(`${where} needs an \"implements\" string array and a \"calledBy\" array.`);\n continue;\n }\n remote.calledBy.forEach((call: RuntimeRemoteCall, index: number) => {\n if (!this.isMap(call) || !this.strings(call.via) || (call.type !== 'rpc' && call.type !== 'pubsub'))\n problems.push(`${where}.calledBy[${index}] needs \"from\", a \"via\" string array and type \"rpc\" or \"pubsub\".`);\n else if (graph.services[call.from] === undefined)\n problems.push(`${where}.calledBy[${index}] names '${call.from}', which is not in \"services\".`);\n });\n }\n return problems;\n }\n\n // webpieces-disable no-any-unknown -- hand-authored JSON is untrusted until its shape has been checked\n private strings(value: unknown): value is string[] {\n return Array.isArray(value) && value.every((entry: string | number | boolean | object | null) => typeof entry === 'string');\n }\n\n // webpieces-disable no-any-unknown -- hand-authored JSON is untrusted until its shape has been checked\n private isMap(value: unknown): boolean {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n }\n}\n"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { RuntimeGraph } from './runtime-graph-model';
|
|
2
|
+
import { SavedSnapshot } from './saved-snapshot';
|
|
3
|
+
import { RuntimeGoalFile } from './runtime-goal-file';
|
|
4
|
+
import { RuntimeComparisonView } from './graph-render-model';
|
|
5
|
+
import type { RuntimeVizOptions } from './runtime-viz-options';
|
|
6
|
+
/**
|
|
7
|
+
* Loads either saved runtime file for the OTHER page's comparison, turning a missing or unusable file
|
|
8
|
+
* into the reason Goal vs Current is unavailable — never into "no differences".
|
|
9
|
+
*/
|
|
10
|
+
export declare class RuntimeComparisonInputs {
|
|
11
|
+
private readonly snapshot;
|
|
12
|
+
private readonly goalFile;
|
|
13
|
+
constructor(snapshot?: SavedSnapshot, goalFile?: RuntimeGoalFile);
|
|
14
|
+
/** The saved current graph, or why it cannot be compared. */
|
|
15
|
+
current(workspaceRoot: string): RuntimeGraph | string;
|
|
16
|
+
/** The saved goal, or why it cannot be compared (missing: how to start one). */
|
|
17
|
+
goal(workspaceRoot: string): RuntimeGraph | string;
|
|
18
|
+
view(current: RuntimeGraph | string, goal: RuntimeGraph | string, options: RuntimeVizOptions): RuntimeComparisonView;
|
|
19
|
+
/** Goal vs Current with no overlay: the mode is disabled and says `reason` instead. */
|
|
20
|
+
unavailable(reason: string): RuntimeComparisonView;
|
|
21
|
+
private text;
|
|
22
|
+
}
|