@webpieces/nx-webpieces-rules 0.4.853 → 0.4.855

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/README.md +14 -0
  2. package/executors.json +5 -0
  3. package/package.json +7 -7
  4. package/src/executors/generate/executor.js +31 -55
  5. package/src/executors/generate/executor.js.map +1 -1
  6. package/src/executors/runtime-wiring-check/executor.d.ts +10 -0
  7. package/src/executors/runtime-wiring-check/executor.js +42 -0
  8. package/src/executors/runtime-wiring-check/executor.js.map +1 -0
  9. package/src/executors/runtime-wiring-check/schema.json +14 -0
  10. package/src/executors/validate-api-lib-tag/executor.js +5 -3
  11. package/src/executors/validate-api-lib-tag/executor.js.map +1 -1
  12. package/src/executors/validate-api-relations/executor.js +11 -8
  13. package/src/executors/validate-api-relations/executor.js.map +1 -1
  14. package/src/executors/validate-architecture-unchanged/executor.d.ts +1 -1
  15. package/src/executors/validate-architecture-unchanged/executor.js +22 -24
  16. package/src/executors/validate-architecture-unchanged/executor.js.map +1 -1
  17. package/src/index.d.ts +1 -0
  18. package/src/index.js +5 -1
  19. package/src/index.js.map +1 -1
  20. package/src/lib/api-usage/api-contract-approval.d.ts +7 -0
  21. package/src/lib/api-usage/api-contract-approval.js +50 -0
  22. package/src/lib/api-usage/api-contract-approval.js.map +1 -0
  23. package/src/lib/api-usage/api-contract-evidence.d.ts +6 -0
  24. package/src/lib/api-usage/api-contract-evidence.js +41 -0
  25. package/src/lib/api-usage/api-contract-evidence.js.map +1 -0
  26. package/src/lib/api-usage/api-relations-validator.d.ts +13 -2
  27. package/src/lib/api-usage/api-relations-validator.js +23 -0
  28. package/src/lib/api-usage/api-relations-validator.js.map +1 -1
  29. package/src/lib/api-usage/api-relations.d.ts +3 -0
  30. package/src/lib/api-usage/api-relations.js.map +1 -1
  31. package/src/lib/api-usage/api-scanner.d.ts +40 -0
  32. package/src/lib/api-usage/api-scanner.js +7 -1
  33. package/src/lib/api-usage/api-scanner.js.map +1 -1
  34. package/src/lib/graph-comparator.js +11 -1
  35. package/src/lib/graph-comparator.js.map +1 -1
  36. package/src/lib/graph-filter.client.d.ts +40 -0
  37. package/src/lib/graph-filter.client.js +190 -0
  38. package/src/lib/graph-filter.client.js.map +1 -0
  39. package/src/lib/graph-level-bands.d.ts +0 -18
  40. package/src/lib/graph-level-bands.js +4 -3
  41. package/src/lib/graph-level-bands.js.map +1 -1
  42. package/src/lib/graph-loader.js +12 -6
  43. package/src/lib/graph-loader.js.map +1 -1
  44. package/src/lib/graph-navigation.d.ts +5 -0
  45. package/src/lib/graph-navigation.js +102 -0
  46. package/src/lib/graph-navigation.js.map +1 -0
  47. package/src/lib/graph-node-menu.d.ts +1 -0
  48. package/src/lib/graph-node-menu.js +29 -0
  49. package/src/lib/graph-node-menu.js.map +1 -1
  50. package/src/lib/graph-render-model.d.ts +32 -0
  51. package/src/lib/graph-render-model.js +76 -0
  52. package/src/lib/graph-render-model.js.map +1 -0
  53. package/src/lib/graph-sorter.d.ts +2 -0
  54. package/src/lib/graph-sorter.js.map +1 -1
  55. package/src/lib/graph-visualizer.client.d.ts +14 -73
  56. package/src/lib/graph-visualizer.client.js +71 -243
  57. package/src/lib/graph-visualizer.client.js.map +1 -1
  58. package/src/lib/graph-visualizer.d.ts +8 -15
  59. package/src/lib/graph-visualizer.js +48 -26
  60. package/src/lib/graph-visualizer.js.map +1 -1
  61. package/src/lib/runtime-details.d.ts +13 -0
  62. package/src/lib/runtime-details.js +191 -0
  63. package/src/lib/runtime-details.js.map +1 -0
  64. package/src/lib/runtime-graph-model.d.ts +4 -1
  65. package/src/lib/runtime-graph-model.js.map +1 -1
  66. package/src/lib/runtime-graph.js +10 -4
  67. package/src/lib/runtime-graph.js.map +1 -1
  68. package/src/lib/runtime-html-page.d.ts +7 -3
  69. package/src/lib/runtime-html-page.js +19 -6
  70. package/src/lib/runtime-html-page.js.map +1 -1
  71. package/src/lib/runtime-visualizer.client.d.ts +8 -19
  72. package/src/lib/runtime-visualizer.client.js +28 -41
  73. package/src/lib/runtime-visualizer.client.js.map +1 -1
  74. package/src/lib/runtime-visualizer.d.ts +3 -45
  75. package/src/lib/runtime-visualizer.js +77 -79
  76. package/src/lib/runtime-visualizer.js.map +1 -1
  77. package/src/lib/runtime-viz-theme.js +4 -3
  78. package/src/lib/runtime-viz-theme.js.map +1 -1
  79. package/src/lib/runtime-wiring/DeclarationCompileAssertions.d.ts +4 -0
  80. package/src/lib/runtime-wiring/DeclarationCompileAssertions.js +27 -0
  81. package/src/lib/runtime-wiring/DeclarationCompileAssertions.js.map +1 -0
  82. package/src/lib/runtime-wiring/approved-graph.d.ts +8 -0
  83. package/src/lib/runtime-wiring/approved-graph.js +115 -0
  84. package/src/lib/runtime-wiring/approved-graph.js.map +1 -0
  85. package/src/lib/runtime-wiring/assembler.d.ts +24 -0
  86. package/src/lib/runtime-wiring/assembler.js +116 -0
  87. package/src/lib/runtime-wiring/assembler.js.map +1 -0
  88. package/src/lib/runtime-wiring/codec.d.ts +14 -0
  89. package/src/lib/runtime-wiring/codec.js +162 -0
  90. package/src/lib/runtime-wiring/codec.js.map +1 -0
  91. package/src/lib/runtime-wiring/declaration.d.ts +68 -0
  92. package/src/lib/runtime-wiring/declaration.js +88 -0
  93. package/src/lib/runtime-wiring/declaration.js.map +1 -0
  94. package/src/lib/runtime-wiring/source-extractor.d.ts +28 -0
  95. package/src/lib/runtime-wiring/source-extractor.js +253 -0
  96. package/src/lib/runtime-wiring/source-extractor.js.map +1 -0
  97. package/src/lib/runtime-wiring/source-values.d.ts +21 -0
  98. package/src/lib/runtime-wiring/source-values.js +204 -0
  99. package/src/lib/runtime-wiring/source-values.js.map +1 -0
  100. package/src/lib/runtime-wiring/verification.d.ts +9 -0
  101. package/src/lib/runtime-wiring/verification.js +56 -0
  102. package/src/lib/runtime-wiring/verification.js.map +1 -0
  103. package/src/plugin.d.ts +0 -21
  104. package/src/plugin.js +12 -7
  105. package/src/plugin.js.map +1 -1
  106. package/src/runtime-wiring-targets.d.ts +7 -0
  107. package/src/runtime-wiring-targets.js +58 -0
  108. package/src/runtime-wiring-targets.js.map +1 -0
  109. package/src/validation-targets.js +10 -2
  110. package/src/validation-targets.js.map +1 -1
@@ -19,10 +19,7 @@ const graph_comparator_1 = require("../../lib/graph-comparator");
19
19
  const graph_loader_1 = require("../../lib/graph-loader");
20
20
  const graph_metadata_1 = require("../../lib/graph-metadata");
21
21
  const tag_truth_1 = require("../../lib/tag-truth");
22
- const api_scanner_1 = require("../../lib/api-usage/api-scanner");
23
- const external_systems_1 = require("../../lib/api-usage/external-systems");
24
- const api_contract_files_1 = require("../../lib/api-contract-files");
25
- const runtime_config_1 = require("../../lib/runtime-config");
22
+ const approved_graph_1 = require("../../lib/runtime-wiring/approved-graph");
26
23
  const rule_gate_1 = require("../../lib/rule-gate");
27
24
  const toError_1 = require("../../toError");
28
25
  const TMP_MD_FILE = 'webpieces.dependencies.md';
@@ -113,7 +110,7 @@ function describeExternalSystemDrift(current, saved) {
113
110
  * apiRelations — so this validator compares like-for-like against the committed file.
114
111
  */
115
112
  class CurrentGraphBuilder {
116
- async build(workspaceRoot) {
113
+ async build(workspaceRoot, graphPath) {
117
114
  console.log('📊 Generating current dependency graph...');
118
115
  const reducedGraph = await (0, graph_generator_1.generateReducedGraph)();
119
116
  console.log('🔄 Computing topological layers...');
@@ -122,12 +119,13 @@ class CurrentGraphBuilder {
122
119
  const projectInfos = await (0, graph_metadata_1.collectProjectInfo)();
123
120
  (0, graph_metadata_1.enrichGraph)(currentGraph, projectInfos, workspaceRoot);
124
121
  new tag_truth_1.TagTruthCheck().assertTrue(currentGraph, projectInfos, workspaceRoot);
125
- console.log('🔎 Scanning source for implements/uses API relations...');
126
- // The SAME externalApiPaths the generator uses: scanning without them would drop every vendor
127
- // relation from the regenerated graph and report drift against a perfectly fresh file.
128
- const externalApiPaths = (0, runtime_config_1.loadRuntimeConfig)(workspaceRoot).externalApiPaths;
129
- const scan = (0, api_scanner_1.scanAndAttachApiRelations)(workspaceRoot, currentGraph, projectInfos, externalApiPaths);
130
- return new CurrentArchitecture(currentGraph, new api_contract_files_1.ApiContractFiles().refsFor((0, api_scanner_1.buildApiContracts)(scan)), (0, external_systems_1.buildExternalSystems)(scan.apiIndex, projectInfos));
122
+ new approved_graph_1.ApprovedWiringGraph().attach(workspaceRoot, currentGraph, projectInfos, graphPath);
123
+ const saved = (0, graph_loader_1.loadBlessedGraph)(workspaceRoot, graphPath);
124
+ if (saved === null)
125
+ throw new rules_config_1.RuleFailError('validate-runtime-architecture', 'Missing approved graph.', undefined, undefined, [
126
+ new rules_config_1.Option('Migrate src/wiring.ts and review runtime-deps.json and API contract approvals.', true),
127
+ ]);
128
+ return new CurrentArchitecture(currentGraph, saved.apiContractFiles, saved.externalSystems);
131
129
  }
132
130
  }
133
131
  exports.CurrentGraphBuilder = CurrentGraphBuilder;
@@ -146,13 +144,9 @@ exports.CurrentArchitecture = CurrentArchitecture;
146
144
  /** The bootstrap path: there is nothing to diff against yet, so say how to create it. */
147
145
  // webpieces-disable no-function-outside-class -- executor step helper, matches reportMismatch in this file
148
146
  function reportMissingGraph() {
149
- console.error('❌ No saved graph found at architecture/dependencies.json');
150
- console.error('');
151
- console.error('To initialize:');
152
- console.error(' 1. Run: nx run architecture:generate');
153
- console.error(' 2. Run: nx run architecture:visualize');
154
- console.error(' 3. Manually inspect the generated graph to confirm it is the desired architecture');
155
- console.error(' 4. Commit architecture/dependencies.json');
147
+ throw new rules_config_1.RuleFailError('validate-runtime-architecture', 'Missing reviewed architecture/dependencies.json.', undefined, undefined, [
148
+ new rules_config_1.Option('Run owning API source build checks to emit contract candidates, review and copy them into architecture/apis/<ApiName>.json, and create dependencies.json with projects:{}, apiContractFiles:{"<ApiName>":"apis/<ApiName>.json"}, and reviewed externalSystems. Approve tagged owners runtime-deps.json candidates, then run architecture:generate and review the resulting graph.', true),
149
+ ]);
156
150
  }
157
151
  async function runExecutor(options, context) {
158
152
  const graphPath = options.graphPath;
@@ -172,7 +166,7 @@ async function runExecutor(options, context) {
172
166
  }
173
167
  // Steps 1-3: build + enrich + scan the current graph (same pipeline the
174
168
  // generator runs, so any drift is caught).
175
- const currentGraph = await new CurrentGraphBuilder().build(workspaceRoot);
169
+ const currentGraph = await new CurrentGraphBuilder().build(workspaceRoot, graphPath);
176
170
  // Step 4: Load saved graph
177
171
  console.log('📂 Loading saved graph...');
178
172
  const savedGraph = (0, graph_loader_1.loadBlessedGraph)(workspaceRoot, graphPath);
@@ -204,12 +198,16 @@ async function runExecutor(options, context) {
204
198
  // carries its cures in Option[]; render them rather than dropping them with `.message`.
205
199
  const rendered = error instanceof rules_config_1.RuleFailError ? (0, rules_config_1.renderRuleFailForHuman)(error) : error.message;
206
200
  console.error('❌ Architecture validation failed:', rendered);
207
- if (error instanceof graph_metadata_1.MetadataValidationError) {
208
- const mdPath = (0, rules_config_1.writeTemplate)(workspaceRoot, 'webpieces.responsibilities.md');
209
- console.error('');
210
- console.error('⚠️ *** Refer to ' + mdPath + ' for how to author responsibilities.md files *** ⚠️');
211
- }
201
+ reportMetadataFailure(error, workspaceRoot);
212
202
  return { success: false };
213
203
  }
214
204
  }
205
+ // webpieces-disable no-function-outside-class -- executor diagnostic formatting
206
+ function reportMetadataFailure(error, workspaceRoot) {
207
+ if (error instanceof graph_metadata_1.MetadataValidationError) {
208
+ const mdPath = (0, rules_config_1.writeTemplate)(workspaceRoot, 'webpieces.responsibilities.md');
209
+ console.error('');
210
+ console.error('⚠️ *** Refer to ' + mdPath + ' for how to author responsibilities.md files *** ⚠️');
211
+ }
212
+ }
215
213
  //# sourceMappingURL=executor.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-architecture-unchanged/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate Architecture Unchanged Executor\n *\n * Validates that the current architecture graph matches the saved blessed graph.\n * This ensures no unapproved architecture changes have been made.\n *\n * Usage:\n * nx run architecture:validate-architecture-unchanged\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { writeTemplate, RuleFailError, renderRuleFailForHuman } from '@webpieces/rules-config';\nimport { generateReducedGraph } from '../../lib/graph-generator';\nimport { sortGraphTopologically } from '../../lib/graph-sorter';\nimport { compareGraphs } from '../../lib/graph-comparator';\nimport { loadBlessedGraph, graphFileExists } from '../../lib/graph-loader';\nimport type { DependenciesFile } from '../../lib/graph-loader';\nimport { collectProjectInfo, enrichGraph, MetadataValidationError } from '../../lib/graph-metadata';\nimport { TagTruthCheck } from '../../lib/tag-truth';\nimport { scanAndAttachApiRelations, buildApiContracts } from '../../lib/api-usage/api-scanner';\nimport { buildExternalSystems } from '../../lib/api-usage/external-systems';\nimport type { ExternalSystemDecls } from '../../lib/api-usage/api-relations';\nimport { ApiContractFiles } from '../../lib/api-contract-files';\nimport type { ApiContractFileRefs } from '../../lib/api-contract-files';\nimport { loadRuntimeConfig } from '../../lib/runtime-config';\nimport { RuleGate } from '../../lib/rule-gate';\nimport type { EnhancedGraph } from '../../lib/graph-sorter';\nimport { toError } from '../../toError';\n\nexport interface ValidateArchitectureUnchangedOptions {\n graphPath?: string;\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\nconst TMP_MD_FILE = 'webpieces.dependencies.md';\n\n/**\n * Write the instructions documentation to .webpieces/instruct-ai/.\n * Sourced from @webpieces/rules-config.\n */\nfunction writeTmpInstructionsFile(workspaceRoot: string): string {\n const mdPath = writeTemplate(workspaceRoot, TMP_MD_FILE);\n\n return mdPath;\n}\n\n/**\n * Report a current-vs-saved graph mismatch and write the AI instructions file.\n */\nfunction reportMismatch(summary: string, workspaceRoot: string): void {\n const mdPath = writeTmpInstructionsFile(workspaceRoot);\n\n console.error('❌ Architecture has changed since last update!');\n console.error('\\nDifferences:');\n console.error(summary);\n console.error('');\n console.error('⚠️ *** Refer to ' + mdPath + ' for instructions on how to fix *** ⚠️');\n console.error('');\n console.error('To fix:');\n console.error(' 1. Review the changes above');\n console.error(' 2. If intentional, ASK USER to run: nx run architecture:generate since this is a critical change');\n console.error(' 3. Commit the updated architecture/dependencies.json');\n}\n\n/**\n * Which APIs dependencies.json links to a contract file, compared by LINK only — an added or removed\n * API. The endpoints inside `architecture/apis/<Api>.json` are deliberately NOT compared (#949): an\n * endpoint change must not fail this rule. Null when the links match.\n */\n// webpieces-disable no-function-outside-class -- executor step helper, matches reportMismatch in this file\nfunction describeContractLinkDrift(\n current: ApiContractFileRefs,\n saved: ApiContractFileRefs,\n): string | null {\n const names = [...new Set([...Object.keys(current), ...Object.keys(saved)])].sort();\n const changes: string[] = [];\n for (const name of names) {\n const a = current[name];\n const b = saved[name];\n if (a === undefined)\n changes.push(` - ${name}: linked in dependencies.json but no longer in source`);\n else if (b === undefined)\n changes.push(` + ${name}: in source but not linked from dependencies.json`);\n else if (a !== b) changes.push(` ~ ${name}: contract file link changed (${b} -> ${a})`);\n }\n if (changes.length === 0) return null;\n return `apiContractFiles drift (${changes.length} api(s)):\\n${changes.join('\\n')}`;\n}\n\n/**\n * Drift in EITHER side table of dependencies.json — the api contract-file links or the\n * external-system declarations — or null when both match. The first difference found is reported;\n * fixing it is the same single command either way, so listing both adds noise rather than information.\n */\n// webpieces-disable no-function-outside-class -- executor step helper, matches describeContractLinkDrift above\nexport function describeTableDrift(current: CurrentArchitecture, saved: DependenciesFile): string | null {\n const linkDrift = describeContractLinkDrift(current.apiContractFiles, saved.apiContractFiles);\n if (linkDrift !== null) return linkDrift;\n return describeExternalSystemDrift(current.externalSystems, saved.externalSystems);\n}\n\n/**\n * The same drift report for the declared external systems, or null when they match. Named per\n * system so the message points at the database that changed rather than dumping two JSON blobs.\n */\n// webpieces-disable no-function-outside-class -- executor step helper, matches describeContractLinkDrift above\nfunction describeExternalSystemDrift(\n current: ExternalSystemDecls,\n saved: ExternalSystemDecls,\n): string | null {\n const names = [...new Set([...Object.keys(current), ...Object.keys(saved)])].sort();\n const changes: string[] = [];\n for (const name of names) {\n const a = current[name];\n const b = saved[name];\n if (a === undefined)\n changes.push(` - ${name}: in dependencies.json but no longer declared in source`);\n else if (b === undefined)\n changes.push(` + ${name}: declared in source but missing from dependencies.json`);\n else if (JSON.stringify(a) !== JSON.stringify(b))\n changes.push(` ~ ${name}: kind/label/declarers changed`);\n }\n if (changes.length === 0) return null;\n return `externalSystems drift (${changes.length} system(s)):\\n${changes.join('\\n')}`;\n}\n\n/**\n * Build the current dependency graph exactly as the generator does: reduce the nx\n * graph, sort into levels, enrich with metadata, and attach the derived\n * apiRelations — so this validator compares like-for-like against the committed file.\n */\nexport class CurrentGraphBuilder {\n async build(workspaceRoot: string): Promise<CurrentArchitecture> {\n console.log('📊 Generating current dependency graph...');\n const reducedGraph = await generateReducedGraph();\n console.log('🔄 Computing topological layers...');\n const currentGraph = sortGraphTopologically(reducedGraph);\n console.log('🏷️ Enriching graph with framework + responsibilities metadata...');\n const projectInfos = await collectProjectInfo();\n enrichGraph(currentGraph, projectInfos, workspaceRoot);\n new TagTruthCheck().assertTrue(currentGraph, projectInfos, workspaceRoot);\n console.log('🔎 Scanning source for implements/uses API relations...');\n // The SAME externalApiPaths the generator uses: scanning without them would drop every vendor\n // relation from the regenerated graph and report drift against a perfectly fresh file.\n const externalApiPaths = loadRuntimeConfig(workspaceRoot).externalApiPaths;\n const scan = scanAndAttachApiRelations(workspaceRoot, currentGraph, projectInfos, externalApiPaths);\n return new CurrentArchitecture(\n currentGraph,\n new ApiContractFiles().refsFor(buildApiContracts(scan)),\n buildExternalSystems(scan.apiIndex, projectInfos),\n );\n }\n}\n\n/** The regenerated graph plus the two tables beside it — everything dependencies.json holds. */\nexport class CurrentArchitecture {\n constructor(\n public readonly graph: EnhancedGraph,\n public readonly apiContractFiles: ApiContractFileRefs,\n public readonly externalSystems: ExternalSystemDecls,\n ) {}\n}\n\n/** The bootstrap path: there is nothing to diff against yet, so say how to create it. */\n// webpieces-disable no-function-outside-class -- executor step helper, matches reportMismatch in this file\nfunction reportMissingGraph(): void {\n console.error('❌ No saved graph found at architecture/dependencies.json');\n console.error('');\n console.error('To initialize:');\n console.error(' 1. Run: nx run architecture:generate');\n console.error(' 2. Run: nx run architecture:visualize');\n console.error(' 3. Manually inspect the generated graph to confirm it is the desired architecture');\n console.error(' 4. Commit architecture/dependencies.json');\n}\n\nexport default async function runExecutor(\n options: ValidateArchitectureUnchangedOptions,\n context: ExecutorContext\n): Promise<ExecutorResult> {\n const graphPath = options.graphPath;\n const workspaceRoot = context.root;\n\n // Epoch-gateable: this rule diffs against the blessed architecture/dependencies.json, so\n // \"grandfather the current drift until <epoch>\" is meaningful — hence honorEpoch = true.\n if (new RuleGate().isDisabled(workspaceRoot, 'validate-architecture-unchanged', true)) {\n return { success: true };\n }\n\n console.log('\\n🔍 Validating Architecture Unchanged\\n');\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // Check if saved graph exists\n if (!graphFileExists(workspaceRoot, graphPath)) {\n reportMissingGraph();\n return { success: false };\n }\n\n // Steps 1-3: build + enrich + scan the current graph (same pipeline the\n // generator runs, so any drift is caught).\n const currentGraph = await new CurrentGraphBuilder().build(workspaceRoot);\n\n // Step 4: Load saved graph\n console.log('📂 Loading saved graph...');\n const savedGraph = loadBlessedGraph(workspaceRoot, graphPath);\n\n if (!savedGraph) {\n console.error('❌ Could not load saved graph');\n return { success: false };\n }\n\n // Step 5: Compare graphs\n console.log('🔍 Comparing current graph to saved graph...');\n const comparison = compareGraphs(currentGraph.graph, savedGraph.projects);\n\n if (!comparison.identical) {\n reportMismatch(comparison.summary, workspaceRoot);\n return { success: false };\n }\n\n // Step 6: Compare the two side tables as well: which APIs are linked to a contract file, and\n // the external-system declarations. The endpoints inside architecture/apis/<Api>.json are\n // NOT compared — an endpoint change must never fail this rule (#949).\n const tableDrift = describeTableDrift(currentGraph, savedGraph);\n if (tableDrift !== null) {\n reportMismatch(tableDrift, workspaceRoot);\n return { success: false };\n }\n\n console.log('✅ Architecture unchanged - current graph matches saved graph');\n return { success: true };\n } catch (err: unknown) {\n const error = toError(err);\n // A RuleFailError (e.g. a dependencies.json still carrying the moved `apiContracts` key)\n // carries its cures in Option[]; render them rather than dropping them with `.message`.\n const rendered = error instanceof RuleFailError ? renderRuleFailForHuman(error) : error.message;\n console.error('❌ Architecture validation failed:', rendered);\n if (error instanceof MetadataValidationError) {\n const mdPath = writeTemplate(workspaceRoot, 'webpieces.responsibilities.md');\n console.error('');\n console.error('⚠️ *** Refer to ' + mdPath + ' for how to author responsibilities.md files *** ⚠️');\n }\n return { success: false };\n }\n}\n"]}
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-architecture-unchanged/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate Architecture Unchanged Executor\n *\n * Validates that the current architecture graph matches the saved blessed graph.\n * This ensures no unapproved architecture changes have been made.\n *\n * Usage:\n * nx run architecture:validate-architecture-unchanged\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport {\n writeTemplate,\n Option,\n RuleFailError,\n renderRuleFailForHuman,\n} from '@webpieces/rules-config';\nimport { generateReducedGraph } from '../../lib/graph-generator';\nimport { sortGraphTopologically } from '../../lib/graph-sorter';\nimport { compareGraphs } from '../../lib/graph-comparator';\nimport { loadBlessedGraph, graphFileExists } from '../../lib/graph-loader';\nimport type { DependenciesFile } from '../../lib/graph-loader';\nimport { collectProjectInfo, enrichGraph, MetadataValidationError } from '../../lib/graph-metadata';\nimport { TagTruthCheck } from '../../lib/tag-truth';\nimport { ApprovedWiringGraph } from '../../lib/runtime-wiring/approved-graph';\nimport type { ExternalSystemDecls } from '../../lib/api-usage/api-relations';\nimport { ApiContractFiles } from '../../lib/api-contract-files';\nimport type { ApiContractFileRefs } from '../../lib/api-contract-files';\nimport { RuleGate } from '../../lib/rule-gate';\nimport type { EnhancedGraph } from '../../lib/graph-sorter';\nimport { toError } from '../../toError';\n\nexport interface ValidateArchitectureUnchangedOptions {\n graphPath?: string;\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\nconst TMP_MD_FILE = 'webpieces.dependencies.md';\n\n/**\n * Write the instructions documentation to .webpieces/instruct-ai/.\n * Sourced from @webpieces/rules-config.\n */\nfunction writeTmpInstructionsFile(workspaceRoot: string): string {\n const mdPath = writeTemplate(workspaceRoot, TMP_MD_FILE);\n\n return mdPath;\n}\n\n/**\n * Report a current-vs-saved graph mismatch and write the AI instructions file.\n */\nfunction reportMismatch(summary: string, workspaceRoot: string): void {\n const mdPath = writeTmpInstructionsFile(workspaceRoot);\n\n console.error('❌ Architecture has changed since last update!');\n console.error('\\nDifferences:');\n console.error(summary);\n console.error('');\n console.error('⚠️ *** Refer to ' + mdPath + ' for instructions on how to fix *** ⚠️');\n console.error('');\n console.error('To fix:');\n console.error(' 1. Review the changes above');\n console.error(\n ' 2. If intentional, ASK USER to run: nx run architecture:generate since this is a critical change',\n );\n console.error(' 3. Commit the updated architecture/dependencies.json');\n}\n\n/**\n * Which APIs dependencies.json links to a contract file, compared by LINK only — an added or removed\n * API. The endpoints inside `architecture/apis/<Api>.json` are deliberately NOT compared (#949): an\n * endpoint change must not fail this rule. Null when the links match.\n */\n// webpieces-disable no-function-outside-class -- executor step helper, matches reportMismatch in this file\nfunction describeContractLinkDrift(\n current: ApiContractFileRefs,\n saved: ApiContractFileRefs,\n): string | null {\n const names = [...new Set([...Object.keys(current), ...Object.keys(saved)])].sort();\n const changes: string[] = [];\n for (const name of names) {\n const a = current[name];\n const b = saved[name];\n if (a === undefined)\n changes.push(` - ${name}: linked in dependencies.json but no longer in source`);\n else if (b === undefined)\n changes.push(` + ${name}: in source but not linked from dependencies.json`);\n else if (a !== b) changes.push(` ~ ${name}: contract file link changed (${b} -> ${a})`);\n }\n if (changes.length === 0) return null;\n return `apiContractFiles drift (${changes.length} api(s)):\\n${changes.join('\\n')}`;\n}\n\n/**\n * Drift in EITHER side table of dependencies.json — the api contract-file links or the\n * external-system declarations — or null when both match. The first difference found is reported;\n * fixing it is the same single command either way, so listing both adds noise rather than information.\n */\n// webpieces-disable no-function-outside-class -- executor step helper, matches describeContractLinkDrift above\nexport function describeTableDrift(\n current: CurrentArchitecture,\n saved: DependenciesFile,\n): string | null {\n const linkDrift = describeContractLinkDrift(current.apiContractFiles, saved.apiContractFiles);\n if (linkDrift !== null) return linkDrift;\n return describeExternalSystemDrift(current.externalSystems, saved.externalSystems);\n}\n\n/**\n * The same drift report for the declared external systems, or null when they match. Named per\n * system so the message points at the database that changed rather than dumping two JSON blobs.\n */\n// webpieces-disable no-function-outside-class -- executor step helper, matches describeContractLinkDrift above\nfunction describeExternalSystemDrift(\n current: ExternalSystemDecls,\n saved: ExternalSystemDecls,\n): string | null {\n const names = [...new Set([...Object.keys(current), ...Object.keys(saved)])].sort();\n const changes: string[] = [];\n for (const name of names) {\n const a = current[name];\n const b = saved[name];\n if (a === undefined)\n changes.push(` - ${name}: in dependencies.json but no longer declared in source`);\n else if (b === undefined)\n changes.push(` + ${name}: declared in source but missing from dependencies.json`);\n else if (JSON.stringify(a) !== JSON.stringify(b))\n changes.push(` ~ ${name}: kind/label/declarers changed`);\n }\n if (changes.length === 0) return null;\n return `externalSystems drift (${changes.length} system(s)):\\n${changes.join('\\n')}`;\n}\n\n/**\n * Build the current dependency graph exactly as the generator does: reduce the nx\n * graph, sort into levels, enrich with metadata, and attach the derived\n * apiRelations — so this validator compares like-for-like against the committed file.\n */\nexport class CurrentGraphBuilder {\n async build(workspaceRoot: string, graphPath?: string): Promise<CurrentArchitecture> {\n console.log('📊 Generating current dependency graph...');\n const reducedGraph = await generateReducedGraph();\n console.log('🔄 Computing topological layers...');\n const currentGraph = sortGraphTopologically(reducedGraph);\n console.log('🏷️ Enriching graph with framework + responsibilities metadata...');\n const projectInfos = await collectProjectInfo();\n enrichGraph(currentGraph, projectInfos, workspaceRoot);\n new TagTruthCheck().assertTrue(currentGraph, projectInfos, workspaceRoot);\n new ApprovedWiringGraph().attach(workspaceRoot, currentGraph, projectInfos, graphPath);\n const saved = loadBlessedGraph(workspaceRoot, graphPath);\n if (saved === null)\n throw new RuleFailError(\n 'validate-runtime-architecture',\n 'Missing approved graph.',\n undefined,\n undefined,\n [\n new Option(\n 'Migrate src/wiring.ts and review runtime-deps.json and API contract approvals.',\n true,\n ),\n ],\n );\n return new CurrentArchitecture(currentGraph, saved.apiContractFiles, saved.externalSystems);\n }\n}\n\n/** The regenerated graph plus the two tables beside it — everything dependencies.json holds. */\nexport class CurrentArchitecture {\n constructor(\n public readonly graph: EnhancedGraph,\n public readonly apiContractFiles: ApiContractFileRefs,\n public readonly externalSystems: ExternalSystemDecls,\n ) {}\n}\n\n/** The bootstrap path: there is nothing to diff against yet, so say how to create it. */\n// webpieces-disable no-function-outside-class -- executor step helper, matches reportMismatch in this file\nfunction reportMissingGraph(): never {\n throw new RuleFailError(\n 'validate-runtime-architecture',\n 'Missing reviewed architecture/dependencies.json.',\n undefined,\n undefined,\n [\n new Option(\n 'Run owning API source build checks to emit contract candidates, review and copy them into architecture/apis/<ApiName>.json, and create dependencies.json with projects:{}, apiContractFiles:{\"<ApiName>\":\"apis/<ApiName>.json\"}, and reviewed externalSystems. Approve tagged owners runtime-deps.json candidates, then run architecture:generate and review the resulting graph.',\n true,\n ),\n ],\n );\n}\n\nexport default async function runExecutor(\n options: ValidateArchitectureUnchangedOptions,\n context: ExecutorContext,\n): Promise<ExecutorResult> {\n const graphPath = options.graphPath;\n const workspaceRoot = context.root;\n\n // Epoch-gateable: this rule diffs against the blessed architecture/dependencies.json, so\n // \"grandfather the current drift until <epoch>\" is meaningful — hence honorEpoch = true.\n if (new RuleGate().isDisabled(workspaceRoot, 'validate-architecture-unchanged', true)) {\n return { success: true };\n }\n\n console.log('\\n🔍 Validating Architecture Unchanged\\n');\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // Check if saved graph exists\n if (!graphFileExists(workspaceRoot, graphPath)) {\n reportMissingGraph();\n return { success: false };\n }\n\n // Steps 1-3: build + enrich + scan the current graph (same pipeline the\n // generator runs, so any drift is caught).\n const currentGraph = await new CurrentGraphBuilder().build(workspaceRoot, graphPath);\n\n // Step 4: Load saved graph\n console.log('📂 Loading saved graph...');\n const savedGraph = loadBlessedGraph(workspaceRoot, graphPath);\n\n if (!savedGraph) {\n console.error('❌ Could not load saved graph');\n return { success: false };\n }\n\n // Step 5: Compare graphs\n console.log('🔍 Comparing current graph to saved graph...');\n const comparison = compareGraphs(currentGraph.graph, savedGraph.projects);\n\n if (!comparison.identical) {\n reportMismatch(comparison.summary, workspaceRoot);\n return { success: false };\n }\n\n // Step 6: Compare the two side tables as well: which APIs are linked to a contract file, and\n // the external-system declarations. The endpoints inside architecture/apis/<Api>.json are\n // NOT compared — an endpoint change must never fail this rule (#949).\n const tableDrift = describeTableDrift(currentGraph, savedGraph);\n if (tableDrift !== null) {\n reportMismatch(tableDrift, workspaceRoot);\n return { success: false };\n }\n\n console.log('✅ Architecture unchanged - current graph matches saved graph');\n return { success: true };\n } catch (err: unknown) {\n const error = toError(err);\n // A RuleFailError (e.g. a dependencies.json still carrying the moved `apiContracts` key)\n // carries its cures in Option[]; render them rather than dropping them with `.message`.\n const rendered =\n error instanceof RuleFailError ? renderRuleFailForHuman(error) : error.message;\n console.error('❌ Architecture validation failed:', rendered);\n reportMetadataFailure(error, workspaceRoot);\n return { success: false };\n }\n}\n\n// webpieces-disable no-function-outside-class -- executor diagnostic formatting\nfunction reportMetadataFailure(error: Error, workspaceRoot: string): void {\n if (error instanceof MetadataValidationError) {\n const mdPath = writeTemplate(workspaceRoot, 'webpieces.responsibilities.md');\n console.error('');\n console.error(\n '⚠️ *** Refer to ' + mdPath + ' for how to author responsibilities.md files *** ⚠️',\n );\n }\n}\n"]}
package/src/index.d.ts CHANGED
@@ -25,3 +25,4 @@ export { NoRootUnionApiTypeConfig } from './configs/no-root-union-config';
25
25
  export { API_DOC_MODES, ApiRulesForOpenApiConfig, ApiRulesForMcpConfig } from './configs/api-doc-rules-config';
26
26
  export type { ApiDocMode } from './configs/api-doc-rules-config';
27
27
  export { ApiClientPackagesEntry, ApiLibDependenciesConfig, ApiLibPathConfig, FrameworkFolderEntry, FrameworkFolderConfig } from './configs/tag-truth-configs';
28
+ export { GraphRenderModel, RenderNode, RenderEdge } from './lib/graph-render-model';
package/src/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.FrameworkFolderConfig = exports.FrameworkFolderEntry = exports.ApiLibPathConfig = exports.ApiLibDependenciesConfig = exports.ApiClientPackagesEntry = exports.ApiRulesForMcpConfig = exports.ApiRulesForOpenApiConfig = exports.API_DOC_MODES = exports.NoRootUnionApiTypeConfig = exports.ValidateEslintSyncConfig = exports.ValidateVersionsLockedConfig = exports.ValidatePackageJsonConfig = exports.ValidateNoArchitectureCyclesConfig = exports.ValidateArchitectureUnchangedConfig = exports.MissingDesignAnnotationConfig = exports.DiGraphConfig = exports.NxWiringConfig = exports.RuntimeArchitectureConfig = exports.NoFileImportCyclesConfig = exports.STRUCTURAL_MODES = exports.createNodesV2 = void 0;
3
+ exports.RenderEdge = exports.RenderNode = exports.GraphRenderModel = exports.FrameworkFolderConfig = exports.FrameworkFolderEntry = exports.ApiLibPathConfig = exports.ApiLibDependenciesConfig = exports.ApiClientPackagesEntry = exports.ApiRulesForMcpConfig = exports.ApiRulesForOpenApiConfig = exports.API_DOC_MODES = exports.NoRootUnionApiTypeConfig = exports.ValidateEslintSyncConfig = exports.ValidateVersionsLockedConfig = exports.ValidatePackageJsonConfig = exports.ValidateNoArchitectureCyclesConfig = exports.ValidateArchitectureUnchangedConfig = exports.MissingDesignAnnotationConfig = exports.DiGraphConfig = exports.NxWiringConfig = exports.RuntimeArchitectureConfig = exports.NoFileImportCyclesConfig = exports.STRUCTURAL_MODES = exports.createNodesV2 = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  tslib_1.__exportStar(require("./lib/graph-generator"), exports);
6
6
  tslib_1.__exportStar(require("./lib/graph-sorter"), exports);
@@ -43,4 +43,8 @@ Object.defineProperty(exports, "ApiLibDependenciesConfig", { enumerable: true, g
43
43
  Object.defineProperty(exports, "ApiLibPathConfig", { enumerable: true, get: function () { return tag_truth_configs_1.ApiLibPathConfig; } });
44
44
  Object.defineProperty(exports, "FrameworkFolderEntry", { enumerable: true, get: function () { return tag_truth_configs_1.FrameworkFolderEntry; } });
45
45
  Object.defineProperty(exports, "FrameworkFolderConfig", { enumerable: true, get: function () { return tag_truth_configs_1.FrameworkFolderConfig; } });
46
+ var graph_render_model_1 = require("./lib/graph-render-model");
47
+ Object.defineProperty(exports, "GraphRenderModel", { enumerable: true, get: function () { return graph_render_model_1.GraphRenderModel; } });
48
+ Object.defineProperty(exports, "RenderNode", { enumerable: true, get: function () { return graph_render_model_1.RenderNode; } });
49
+ Object.defineProperty(exports, "RenderEdge", { enumerable: true, get: function () { return graph_render_model_1.RenderEdge; } });
46
50
  //# sourceMappingURL=index.js.map
package/src/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/tooling/nx-webpieces-rules/src/index.ts"],"names":[],"mappings":";;;;AAAA,gEAAsC;AACtC,6DAAmC;AACnC,iEAAuC;AACvC,kEAAwC;AACxC,6DAAmC;AACnC,iEAAuC;AACvC,+DAAqC;AACrC,mEAAyC;AACzC,6EAAmD;AACnD,6DAAmC;AACnC,iEAAuC;AACvC,6DAAmC;AACnC,2EAAiD;AACjD,uEAA6C;AAC7C,qCAAyC;AAChC,8FADA,sBAAa,OACA;AACtB,kBAAe,EAAE,IAAI,EAAE,+BAA+B,EAAE,aAAa,EAAb,sBAAa,EAAE,CAAC;AAExE,uDAAyU;AAAhU,gHAAA,gBAAgB,OAAA;AAAE,wHAAA,wBAAwB,OAAA;AAAE,yHAAA,yBAAyB,OAAA;AAAE,8GAAA,cAAc,OAAA;AAAE,6GAAA,aAAa,OAAA;AAAE,6HAAA,6BAA6B,OAAA;AAAE,mIAAA,mCAAmC,OAAA;AAAE,kIAAA,kCAAkC,OAAA;AAAE,yHAAA,yBAAyB,OAAA;AAAE,4HAAA,4BAA4B,OAAA;AAAE,wHAAA,wBAAwB,OAAA;AAExS,uEAA0E;AAAjE,gIAAA,wBAAwB,OAAA;AACjC,uEAA+G;AAAtG,qHAAA,aAAa,OAAA;AAAE,gIAAA,wBAAwB,OAAA;AAAE,4HAAA,oBAAoB,OAAA;AAEtE,iEAA8J;AAArJ,2HAAA,sBAAsB,OAAA;AAAE,6HAAA,wBAAwB,OAAA;AAAE,qHAAA,gBAAgB,OAAA;AAAE,yHAAA,oBAAoB,OAAA;AAAE,0HAAA,qBAAqB,OAAA","sourcesContent":["export * from './lib/graph-generator';\nexport * from './lib/graph-sorter';\nexport * from './lib/graph-comparator';\nexport * from './lib/package-validator';\nexport * from './lib/graph-loader';\nexport * from './lib/graph-visualizer';\nexport * from './lib/graph-metadata';\nexport * from './lib/framework-resolver';\nexport * from './lib/runtime-participant-resolver';\nexport * from './lib/project-info';\nexport * from './lib/responsibilities';\nexport * from './lib/di-graph/dot';\nexport * from './lib/di-graph/design-visualizer';\nexport * from './lib/di-graph/design-finder';\nimport { createNodesV2 } from './plugin';\nexport { createNodesV2 };\nexport default { name: '@webpieces/nx-webpieces-rules', createNodesV2 };\n\nexport { STRUCTURAL_MODES, NoFileImportCyclesConfig, RuntimeArchitectureConfig, NxWiringConfig, DiGraphConfig, MissingDesignAnnotationConfig, ValidateArchitectureUnchangedConfig, ValidateNoArchitectureCyclesConfig, ValidatePackageJsonConfig, ValidateVersionsLockedConfig, ValidateEslintSyncConfig } from './configs/rule-configs';\nexport type { StructuralMode } from './configs/rule-configs';\nexport { NoRootUnionApiTypeConfig } from './configs/no-root-union-config';\nexport { API_DOC_MODES, ApiRulesForOpenApiConfig, ApiRulesForMcpConfig } from './configs/api-doc-rules-config';\nexport type { ApiDocMode } from './configs/api-doc-rules-config';\nexport { ApiClientPackagesEntry, ApiLibDependenciesConfig, ApiLibPathConfig, FrameworkFolderEntry, FrameworkFolderConfig } from './configs/tag-truth-configs';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/tooling/nx-webpieces-rules/src/index.ts"],"names":[],"mappings":";;;;AAAA,gEAAsC;AACtC,6DAAmC;AACnC,iEAAuC;AACvC,kEAAwC;AACxC,6DAAmC;AACnC,iEAAuC;AACvC,+DAAqC;AACrC,mEAAyC;AACzC,6EAAmD;AACnD,6DAAmC;AACnC,iEAAuC;AACvC,6DAAmC;AACnC,2EAAiD;AACjD,uEAA6C;AAC7C,qCAAyC;AAChC,8FADA,sBAAa,OACA;AACtB,kBAAe,EAAE,IAAI,EAAE,+BAA+B,EAAE,aAAa,EAAb,sBAAa,EAAE,CAAC;AAExE,uDAAyU;AAAhU,gHAAA,gBAAgB,OAAA;AAAE,wHAAA,wBAAwB,OAAA;AAAE,yHAAA,yBAAyB,OAAA;AAAE,8GAAA,cAAc,OAAA;AAAE,6GAAA,aAAa,OAAA;AAAE,6HAAA,6BAA6B,OAAA;AAAE,mIAAA,mCAAmC,OAAA;AAAE,kIAAA,kCAAkC,OAAA;AAAE,yHAAA,yBAAyB,OAAA;AAAE,4HAAA,4BAA4B,OAAA;AAAE,wHAAA,wBAAwB,OAAA;AAExS,uEAA0E;AAAjE,gIAAA,wBAAwB,OAAA;AACjC,uEAA+G;AAAtG,qHAAA,aAAa,OAAA;AAAE,gIAAA,wBAAwB,OAAA;AAAE,4HAAA,oBAAoB,OAAA;AAEtE,iEAA8J;AAArJ,2HAAA,sBAAsB,OAAA;AAAE,6HAAA,wBAAwB,OAAA;AAAE,qHAAA,gBAAgB,OAAA;AAAE,yHAAA,oBAAoB,OAAA;AAAE,0HAAA,qBAAqB,OAAA;AAExH,+DAAoF;AAA3E,sHAAA,gBAAgB,OAAA;AAAE,gHAAA,UAAU,OAAA;AAAE,gHAAA,UAAU,OAAA","sourcesContent":["export * from './lib/graph-generator';\nexport * from './lib/graph-sorter';\nexport * from './lib/graph-comparator';\nexport * from './lib/package-validator';\nexport * from './lib/graph-loader';\nexport * from './lib/graph-visualizer';\nexport * from './lib/graph-metadata';\nexport * from './lib/framework-resolver';\nexport * from './lib/runtime-participant-resolver';\nexport * from './lib/project-info';\nexport * from './lib/responsibilities';\nexport * from './lib/di-graph/dot';\nexport * from './lib/di-graph/design-visualizer';\nexport * from './lib/di-graph/design-finder';\nimport { createNodesV2 } from './plugin';\nexport { createNodesV2 };\nexport default { name: '@webpieces/nx-webpieces-rules', createNodesV2 };\n\nexport { STRUCTURAL_MODES, NoFileImportCyclesConfig, RuntimeArchitectureConfig, NxWiringConfig, DiGraphConfig, MissingDesignAnnotationConfig, ValidateArchitectureUnchangedConfig, ValidateNoArchitectureCyclesConfig, ValidatePackageJsonConfig, ValidateVersionsLockedConfig, ValidateEslintSyncConfig } from './configs/rule-configs';\nexport type { StructuralMode } from './configs/rule-configs';\nexport { NoRootUnionApiTypeConfig } from './configs/no-root-union-config';\nexport { API_DOC_MODES, ApiRulesForOpenApiConfig, ApiRulesForMcpConfig } from './configs/api-doc-rules-config';\nexport type { ApiDocMode } from './configs/api-doc-rules-config';\nexport { ApiClientPackagesEntry, ApiLibDependenciesConfig, ApiLibPathConfig, FrameworkFolderEntry, FrameworkFolderConfig } from './configs/tag-truth-configs';\n\nexport { GraphRenderModel, RenderNode, RenderEdge } from './lib/graph-render-model';\n"]}
@@ -0,0 +1,7 @@
1
+ import type { ApiContracts, ExternalSystemDecls } from './api-relations';
2
+ import type { ProjectInfo } from '../project-info';
3
+ /** Source-sensitive build proof; graph generation reads these approvals without extracting source. */
4
+ export declare class ApiContractApproval {
5
+ verify(workspaceRoot: string, infos: ReadonlyMap<string, ProjectInfo>, contracts: ApiContracts, systems: ExternalSystemDecls): void;
6
+ private fail;
7
+ }
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ApiContractApproval = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const fs = tslib_1.__importStar(require("fs"));
6
+ const path = tslib_1.__importStar(require("path"));
7
+ const util_1 = require("util");
8
+ const rules_config_1 = require("@webpieces/rules-config");
9
+ const api_contract_files_1 = require("../api-contract-files");
10
+ const graph_loader_1 = require("../graph-loader");
11
+ /** Source-sensitive build proof; graph generation reads these approvals without extracting source. */
12
+ class ApiContractApproval {
13
+ verify(workspaceRoot, infos, contracts, systems) {
14
+ const owners = new Set(Object.values(contracts).map((contract) => contract.owner));
15
+ const candidates = [];
16
+ for (const owner of owners) {
17
+ const info = infos.get(owner);
18
+ if (info === undefined)
19
+ this.fail(`Missing owning project ${owner} for API contract proof.`);
20
+ const project = JSON.parse(fs.readFileSync(path.join(workspaceRoot, info.root, 'project.json'), 'utf8'));
21
+ const outputPath = project.targets?.build?.options?.outputPath;
22
+ if (typeof outputPath !== 'string' || outputPath.length === 0)
23
+ this.fail(`${owner} must declare its build.options.outputPath for contract candidates.`);
24
+ const own = Object.fromEntries(Object.entries(contracts).filter((entry) => entry[1].owner === owner));
25
+ const result = new api_contract_files_1.ApiContractFiles().write(workspaceRoot, `${outputPath}/runtime-contracts.candidate.json`, own);
26
+ candidates.push(...result.written);
27
+ fs.writeFileSync(path.join(workspaceRoot, outputPath, 'external-systems.candidate.json'), JSON.stringify(systems, null, 4) + '\n');
28
+ }
29
+ const saved = (0, graph_loader_1.loadBlessedGraph)(workspaceRoot);
30
+ if (saved === null)
31
+ this.fail('Missing saved API contract references; create reviewed contract declarations before graph generation.');
32
+ const expected = new api_contract_files_1.ApiContractFiles().load(workspaceRoot, 'architecture/dependencies.json', saved.apiContractFiles);
33
+ if (!(0, util_1.isDeepStrictEqual)(JSON.parse(JSON.stringify(expected)), JSON.parse(JSON.stringify(contracts))) ||
34
+ !(0, util_1.isDeepStrictEqual)(JSON.parse(JSON.stringify(saved.externalSystems)), JSON.parse(JSON.stringify(systems))))
35
+ this.fail(`Current API/DTO contracts or external declarations differ from reviewed graph metadata. Candidates:\n${candidates.join('\n')}\nReview each owning contract's candidate and explicitly update architecture/apis and contract references; generation never extracts source or approves changes.`);
36
+ }
37
+ fail(message) {
38
+ throw new rules_config_1.RuleFailError('validate-runtime-architecture', message, undefined, undefined, [
39
+ new rules_config_1.Option('Review the owning project output candidates, correct source defects, and explicitly update the approved API metadata before generation.', true),
40
+ ]);
41
+ }
42
+ }
43
+ exports.ApiContractApproval = ApiContractApproval;
44
+ class ProjectBuildOutput {
45
+ targets;
46
+ constructor(targets) {
47
+ this.targets = targets;
48
+ }
49
+ }
50
+ //# sourceMappingURL=api-contract-approval.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-contract-approval.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/api-usage/api-contract-approval.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport { isDeepStrictEqual } from 'util';\nimport { Option, RuleFailError } from '@webpieces/rules-config';\nimport type { ApiContracts, ExternalSystemDecls } from './api-relations';\nimport type { ProjectInfo } from '../project-info';\nimport { ApiContractFiles } from '../api-contract-files';\nimport { loadBlessedGraph } from '../graph-loader';\n\n/** Source-sensitive build proof; graph generation reads these approvals without extracting source. */\nexport class ApiContractApproval {\n verify(\n workspaceRoot: string,\n infos: ReadonlyMap<string, ProjectInfo>,\n contracts: ApiContracts,\n systems: ExternalSystemDecls,\n ): void {\n const owners = new Set(Object.values(contracts).map((contract) => contract.owner));\n const candidates: string[] = [];\n for (const owner of owners) {\n const info = infos.get(owner);\n if (info === undefined)\n this.fail(`Missing owning project ${owner} for API contract proof.`);\n const project = JSON.parse(\n fs.readFileSync(path.join(workspaceRoot, info!.root, 'project.json'), 'utf8'),\n ) as ProjectBuildOutput;\n const outputPath = project.targets?.build?.options?.outputPath;\n if (typeof outputPath !== 'string' || outputPath.length === 0)\n this.fail(\n `${owner} must declare its build.options.outputPath for contract candidates.`,\n );\n const own = Object.fromEntries(\n Object.entries(contracts).filter((entry) => entry[1].owner === owner),\n );\n const result = new ApiContractFiles().write(\n workspaceRoot,\n `${outputPath}/runtime-contracts.candidate.json`,\n own,\n );\n candidates.push(...result.written);\n fs.writeFileSync(\n path.join(workspaceRoot, outputPath, 'external-systems.candidate.json'),\n JSON.stringify(systems, null, 4) + '\\n',\n );\n }\n const saved = loadBlessedGraph(workspaceRoot);\n if (saved === null)\n this.fail(\n 'Missing saved API contract references; create reviewed contract declarations before graph generation.',\n );\n const expected = new ApiContractFiles().load(\n workspaceRoot,\n 'architecture/dependencies.json',\n saved!.apiContractFiles,\n );\n if (\n !isDeepStrictEqual(\n JSON.parse(JSON.stringify(expected)),\n JSON.parse(JSON.stringify(contracts)),\n ) ||\n !isDeepStrictEqual(\n JSON.parse(JSON.stringify(saved!.externalSystems)),\n JSON.parse(JSON.stringify(systems)),\n )\n )\n this.fail(\n `Current API/DTO contracts or external declarations differ from reviewed graph metadata. Candidates:\\n${candidates.join('\\n')}\\nReview each owning contract's candidate and explicitly update architecture/apis and contract references; generation never extracts source or approves changes.`,\n );\n }\n\n private fail(message: string): never {\n throw new RuleFailError('validate-runtime-architecture', message, undefined, undefined, [\n new Option(\n 'Review the owning project output candidates, correct source defects, and explicitly update the approved API metadata before generation.',\n true,\n ),\n ]);\n }\n}\n\nclass ProjectBuildOutput {\n constructor(public readonly targets?: { build?: { options?: { outputPath?: string } } }) {}\n}\n"]}
@@ -0,0 +1,6 @@
1
+ import type { ApiScanResult } from './api-scanner';
2
+ import type { ProjectInfo } from '../project-info';
3
+ /** Build evidence for API/DTO correctness. No runtime relationship scan or project compiler loop. */
4
+ export declare class ApiContractEvidence {
5
+ collect(workspaceRoot: string, infos: Map<string, ProjectInfo>, externalApiPaths: readonly string[]): ApiScanResult;
6
+ }
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ApiContractEvidence = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const api_contract_approval_1 = require("./api-contract-approval");
6
+ const external_systems_1 = require("./external-systems");
7
+ const fs = tslib_1.__importStar(require("fs"));
8
+ const path = tslib_1.__importStar(require("path"));
9
+ const api_scanner_1 = require("./api-scanner");
10
+ const api_ast_1 = require("./api-ast");
11
+ const root_union_scan_1 = require("./root-union-scan");
12
+ const api_doc_rules_1 = require("./api-doc-rules");
13
+ const api_doc_rules_scan_1 = require("./api-doc-rules-scan");
14
+ const wire_closure_1 = require("./wire-closure");
15
+ /** Build evidence for API/DTO correctness. No runtime relationship scan or project compiler loop. */
16
+ class ApiContractEvidence {
17
+ collect(workspaceRoot, infos, externalApiPaths) {
18
+ const diagnostics = new api_ast_1.DecoratorArgDiagnostics(workspaceRoot);
19
+ const index = new api_scanner_1.ApiSourceIndexBuilder(workspaceRoot, infos, externalApiPaths, diagnostics).build();
20
+ const result = {
21
+ relationsByProject: new Map(),
22
+ apiLibProjects: index.owners,
23
+ apiIndex: index.byName,
24
+ scannedProjects: new Set([...infos.values()]
25
+ .filter((info) => fs.existsSync(path.join(workspaceRoot, info.root, 'src')))
26
+ .map((info) => info.name)),
27
+ unresolvedApiCalls: [],
28
+ nonLiteralDecoratorArgs: diagnostics.all(),
29
+ unresolvedEndpointPaths: diagnostics.unresolvedEndpointPaths(),
30
+ emptiedApiContracts: diagnostics.emptiedContracts(),
31
+ undeclaredExternalCallers: diagnostics.undeclaredExternalCallers(),
32
+ undeclaredEndpointOperations: diagnostics.undeclaredEndpointOperations(),
33
+ rootUnions: new root_union_scan_1.RootUnionScan(workspaceRoot, infos, root_union_scan_1.RootUnionRule.fromConfig(workspaceRoot)).run(),
34
+ apiDocRules: new api_doc_rules_scan_1.ApiDocRulesScan(workspaceRoot, infos, api_doc_rules_1.ApiDocRule.fromConfig(workspaceRoot, api_doc_rules_1.OPENAPI_RULE), api_doc_rules_1.ApiDocRule.fromConfig(workspaceRoot, api_doc_rules_1.MCP_RULE), wire_closure_1.WireClosureRule.fromConfig(workspaceRoot)).run(),
35
+ };
36
+ new api_contract_approval_1.ApiContractApproval().verify(workspaceRoot, infos, (0, api_scanner_1.buildApiContracts)(result), (0, external_systems_1.buildExternalSystems)(index.byName, infos)); // Preserves the previous generator's API/DTO correctness failures.
37
+ return result;
38
+ }
39
+ }
40
+ exports.ApiContractEvidence = ApiContractEvidence;
41
+ //# sourceMappingURL=api-contract-evidence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-contract-evidence.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/api-usage/api-contract-evidence.ts"],"names":[],"mappings":";;;;AAAA,mEAA8D;AAC9D,yDAA0D;AAC1D,+CAAyB;AACzB,mDAA6B;AAC7B,+CAAyE;AACzE,uCAAoD;AACpD,uDAAiE;AACjE,mDAAqE;AACrE,6DAAuD;AACvD,iDAAiD;AAIjD,qGAAqG;AACrG,MAAa,mBAAmB;IAC5B,OAAO,CACH,aAAqB,EACrB,KAA+B,EAC/B,gBAAmC;QAEnC,MAAM,WAAW,GAAG,IAAI,iCAAuB,CAAC,aAAa,CAAC,CAAC;QAC/D,MAAM,KAAK,GAAG,IAAI,mCAAqB,CACnC,aAAa,EACb,KAAK,EACL,gBAAgB,EAChB,WAAW,CACd,CAAC,KAAK,EAAE,CAAC;QACV,MAAM,MAAM,GAAkB;YAC1B,kBAAkB,EAAE,IAAI,GAAG,EAAE;YAC7B,cAAc,EAAE,KAAK,CAAC,MAAM;YAC5B,QAAQ,EAAE,KAAK,CAAC,MAAM;YACtB,eAAe,EAAE,IAAI,GAAG,CACpB,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;iBACd,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;iBAC3E,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAChC;YACD,kBAAkB,EAAE,EAAE;YACtB,uBAAuB,EAAE,WAAW,CAAC,GAAG,EAAE;YAC1C,uBAAuB,EAAE,WAAW,CAAC,uBAAuB,EAAE;YAC9D,mBAAmB,EAAE,WAAW,CAAC,gBAAgB,EAAE;YACnD,yBAAyB,EAAE,WAAW,CAAC,yBAAyB,EAAE;YAClE,4BAA4B,EAAE,WAAW,CAAC,4BAA4B,EAAE;YACxE,UAAU,EAAE,IAAI,+BAAa,CACzB,aAAa,EACb,KAAK,EACL,+BAAa,CAAC,UAAU,CAAC,aAAa,CAAC,CAC1C,CAAC,GAAG,EAAE;YACP,WAAW,EAAE,IAAI,oCAAe,CAC5B,aAAa,EACb,KAAK,EACL,0BAAU,CAAC,UAAU,CAAC,aAAa,EAAE,4BAAY,CAAC,EAClD,0BAAU,CAAC,UAAU,CAAC,aAAa,EAAE,wBAAQ,CAAC,EAC9C,8BAAe,CAAC,UAAU,CAAC,aAAa,CAAC,CAC5C,CAAC,GAAG,EAAE;SACV,CAAC;QACF,IAAI,2CAAmB,EAAE,CAAC,MAAM,CAC5B,aAAa,EACb,KAAK,EACL,IAAA,+BAAiB,EAAC,MAAM,CAAC,EACzB,IAAA,uCAAoB,EAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,CAC5C,CAAC,CAAC,mEAAmE;QACtE,OAAO,MAAM,CAAC;IAClB,CAAC;CACJ;AAjDD,kDAiDC","sourcesContent":["import { ApiContractApproval } from './api-contract-approval';\nimport { buildExternalSystems } from './external-systems';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { ApiSourceIndexBuilder, buildApiContracts } from './api-scanner';\nimport { DecoratorArgDiagnostics } from './api-ast';\nimport { RootUnionRule, RootUnionScan } from './root-union-scan';\nimport { ApiDocRule, MCP_RULE, OPENAPI_RULE } from './api-doc-rules';\nimport { ApiDocRulesScan } from './api-doc-rules-scan';\nimport { WireClosureRule } from './wire-closure';\nimport type { ApiScanResult } from './api-scanner';\nimport type { ProjectInfo } from '../project-info';\n\n/** Build evidence for API/DTO correctness. No runtime relationship scan or project compiler loop. */\nexport class ApiContractEvidence {\n collect(\n workspaceRoot: string,\n infos: Map<string, ProjectInfo>,\n externalApiPaths: readonly string[],\n ): ApiScanResult {\n const diagnostics = new DecoratorArgDiagnostics(workspaceRoot);\n const index = new ApiSourceIndexBuilder(\n workspaceRoot,\n infos,\n externalApiPaths,\n diagnostics,\n ).build();\n const result: ApiScanResult = {\n relationsByProject: new Map(),\n apiLibProjects: index.owners,\n apiIndex: index.byName,\n scannedProjects: new Set(\n [...infos.values()]\n .filter((info) => fs.existsSync(path.join(workspaceRoot, info.root, 'src')))\n .map((info) => info.name),\n ),\n unresolvedApiCalls: [],\n nonLiteralDecoratorArgs: diagnostics.all(),\n unresolvedEndpointPaths: diagnostics.unresolvedEndpointPaths(),\n emptiedApiContracts: diagnostics.emptiedContracts(),\n undeclaredExternalCallers: diagnostics.undeclaredExternalCallers(),\n undeclaredEndpointOperations: diagnostics.undeclaredEndpointOperations(),\n rootUnions: new RootUnionScan(\n workspaceRoot,\n infos,\n RootUnionRule.fromConfig(workspaceRoot),\n ).run(),\n apiDocRules: new ApiDocRulesScan(\n workspaceRoot,\n infos,\n ApiDocRule.fromConfig(workspaceRoot, OPENAPI_RULE),\n ApiDocRule.fromConfig(workspaceRoot, MCP_RULE),\n WireClosureRule.fromConfig(workspaceRoot),\n ).run(),\n };\n new ApiContractApproval().verify(\n workspaceRoot,\n infos,\n buildApiContracts(result),\n buildExternalSystems(index.byName, infos),\n ); // Preserves the previous generator's API/DTO correctness failures.\n return result;\n }\n}\n"]}
@@ -12,7 +12,6 @@
12
12
  */
13
13
  import type { EnhancedGraph } from '../graph-sorter';
14
14
  import { ProjectInfo } from '../project-info';
15
- import { ApiScanResult } from './api-scanner';
16
15
  import { UnresolvedApiCall } from './api-relations';
17
16
  /** One `role:server`/`role:client` project that depends on an api-lib it neither implements nor uses. */
18
17
  export interface UnclassifiedApiDep {
@@ -28,10 +27,22 @@ export interface UnclassifiedApiDep {
28
27
  */
29
28
  unresolved: UnresolvedApiCall[];
30
29
  }
30
+ export declare class ApiOwnerIdentity {
31
+ readonly api: string;
32
+ readonly owner: string;
33
+ constructor(api: string, owner: string);
34
+ }
35
+ export declare class ApiRelationEvidence {
36
+ readonly scannedProjects: Set<string>;
37
+ readonly apiLibProjects: Set<string>;
38
+ readonly unresolvedApiCalls: UnresolvedApiCall[];
39
+ readonly apiIndex: ReadonlyMap<string, ApiOwnerIdentity>;
40
+ constructor(scannedProjects: Set<string>, apiLibProjects: Set<string>, unresolvedApiCalls: UnresolvedApiCall[], apiIndex: ReadonlyMap<string, ApiOwnerIdentity>);
41
+ }
31
42
  /**
32
43
  * Every server/client → api-lib edge for which the scan found NO implements and
33
44
  * NO uses. `graph` must already carry `apiRelations` (call scanAndAttachApiRelations first).
34
45
  */
35
- export declare function findUnclassifiedApiDeps(graph: EnhancedGraph, projectInfos: Map<string, ProjectInfo>, scan: ApiScanResult): UnclassifiedApiDep[];
46
+ export declare function findUnclassifiedApiDeps(graph: EnhancedGraph, projectInfos: Map<string, ProjectInfo>, scan: ApiRelationEvidence): UnclassifiedApiDep[];
36
47
  /** Human-readable, fix-oriented report for one unclassified dependency. */
37
48
  export declare function describeUnclassifiedApiDep(violation: UnclassifiedApiDep): string;
@@ -12,11 +12,34 @@
12
12
  * relate to api-lib D" is answered by real code, never a declaration.
13
13
  */
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.ApiRelationEvidence = exports.ApiOwnerIdentity = void 0;
15
16
  exports.findUnclassifiedApiDeps = findUnclassifiedApiDeps;
16
17
  exports.describeUnclassifiedApiDep = describeUnclassifiedApiDep;
17
18
  const role_resolver_1 = require("../role-resolver");
18
19
  /** Roles that must justify every api-lib dependency (top-level runnables). */
19
20
  const CHECKED_ROLES = ['server', 'client'];
21
+ class ApiOwnerIdentity {
22
+ api;
23
+ owner;
24
+ constructor(api, owner) {
25
+ this.api = api;
26
+ this.owner = owner;
27
+ }
28
+ }
29
+ exports.ApiOwnerIdentity = ApiOwnerIdentity;
30
+ class ApiRelationEvidence {
31
+ scannedProjects;
32
+ apiLibProjects;
33
+ unresolvedApiCalls;
34
+ apiIndex;
35
+ constructor(scannedProjects, apiLibProjects, unresolvedApiCalls, apiIndex) {
36
+ this.scannedProjects = scannedProjects;
37
+ this.apiLibProjects = apiLibProjects;
38
+ this.unresolvedApiCalls = unresolvedApiCalls;
39
+ this.apiIndex = apiIndex;
40
+ }
41
+ }
42
+ exports.ApiRelationEvidence = ApiRelationEvidence;
20
43
  /** The API class names owned by `apiLib`, sorted (for a stable fix hint). */
21
44
  // webpieces-disable no-function-outside-class -- pure lookup helper, matches the validator-lib style
22
45
  function apisOwnedBy(scan, apiLib) {
@@ -1 +1 @@
1
- {"version":3,"file":"api-relations-validator.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/api-usage/api-relations-validator.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * API Relations Validator\n *\n * Enforces that the dependency graph is TRUTHFUL: a runnable project (role server\n * or client) that compiles against an api-lib must actually IMPLEMENT (serve) or\n * USE (call) at least one of its APIs. A dependency that is neither is almost\n * always a mistake — a forgotten client wiring, a dead import, or a controller\n * that was never registered — and it would draw an unexplained edge in the graph.\n *\n * The check reuses the same source scan that produces `apiRelations`, so \"does P\n * relate to api-lib D\" is answered by real code, never a declaration.\n */\n\nimport type { EnhancedGraph } from '../graph-sorter';\nimport { ProjectInfo } from '../project-info';\nimport { resolveRole } from '../role-resolver';\nimport { ApiScanResult } from './api-scanner';\n// UnresolvedApiCall moved beside its sibling diagnostic DTOs when api-scanner.ts reached its limit.\nimport { UnresolvedApiCall } from './api-relations';\n\n/** Roles that must justify every api-lib dependency (top-level runnables). */\nconst CHECKED_ROLES: ReadonlyArray<string> = ['server', 'client'];\n\n/** One `role:server`/`role:client` project that depends on an api-lib it neither implements nor uses. */\nexport interface UnclassifiedApiDep {\n project: string;\n role: string;\n apiLib: string;\n /** The API contract class names that api-lib exports (for the fix hint). */\n apis: string[];\n /**\n * Contracts this project DOES name at a call site but which never resolved to decorated\n * source. When non-empty the wiring almost certainly exists and the scan is blind — advice\n * to \"add a controller\" or \"remove the dependency\" would be actively wrong.\n */\n unresolved: UnresolvedApiCall[];\n}\n\n/** The API class names owned by `apiLib`, sorted (for a stable fix hint). */\n// webpieces-disable no-function-outside-class -- pure lookup helper, matches the validator-lib style\nfunction apisOwnedBy(scan: ApiScanResult, apiLib: string): string[] {\n const names: string[] = [];\n for (const info of scan.apiIndex.values()) {\n if (info.owner === apiLib) names.push(info.api);\n }\n return names.sort();\n}\n\n/**\n * Every server/client → api-lib edge for which the scan found NO implements and\n * NO uses. `graph` must already carry `apiRelations` (call scanAndAttachApiRelations first).\n */\n// webpieces-disable no-function-outside-class -- module entry point, mirrors findUnclassified-style validators\nexport function findUnclassifiedApiDeps(\n graph: EnhancedGraph,\n projectInfos: Map<string, ProjectInfo>,\n scan: ApiScanResult,\n): UnclassifiedApiDep[] {\n const violations: UnclassifiedApiDep[] = [];\n for (const projectName of Object.keys(graph)) {\n const info = projectInfos.get(projectName);\n if (!info) continue;\n const role = resolveRole(info).role;\n if (role === null || !CHECKED_ROLES.includes(role)) continue;\n // Only flag projects whose production source was actually scanned. An all-test project\n // (e.g. an e2e harness whose only files are *.spec.ts) is never observed, so we can't\n // conclude its api-lib dependency is unused.\n if (!scan.scannedProjects.has(projectName)) continue;\n\n const entry = graph[projectName];\n for (const dep of entry.dependsOn) {\n if (!scan.apiLibProjects.has(dep)) continue;\n if (entry.apiRelations && entry.apiRelations[dep]) continue;\n violations.push({\n project: projectName,\n role,\n apiLib: dep,\n apis: apisOwnedBy(scan, dep),\n unresolved: scan.unresolvedApiCalls.filter((c: UnresolvedApiCall) => c.project === projectName),\n });\n }\n }\n return violations;\n}\n\n/**\n * The project DOES wire this contract up — we just couldn't see it, because the import resolved\n * to a decorator-erased declaration file. Report THAT, not a list of dead ends: telling a dev to\n * register a controller they already registered, or to delete a load-bearing dependency, sends\n * them chasing ghosts and gets the whole validator turned off.\n */\n// webpieces-disable no-function-outside-class -- pure formatter, matches the validator-lib style\nfunction describeBlindScan(violation: UnclassifiedApiDep): string {\n const lines = [\n ` ❌ '${violation.project}' (role:${violation.role}) depends on api-lib '${violation.apiLib}' and its ` +\n `wiring IS present in source, but the contract could not be read:`,\n ];\n for (const call of violation.unresolved) {\n lines.push(\n ` • ${call.api} at ${call.at} resolved to ${call.declaredIn} (decorators erased in .d.ts).`,\n );\n }\n lines.push(\n ` This is a CONFIG gap, not a wiring gap. Do NOT add a controller and do NOT remove the dependency.`,\n ` Fix: add a tsconfig.base.json 'paths' entry for '${violation.apiLib}' → its src/index.ts,`,\n ` so the import resolves to source instead of dist/.`,\n );\n return lines.join('\\n');\n}\n\n/** Human-readable, fix-oriented report for one unclassified dependency. */\n// webpieces-disable no-function-outside-class -- pure formatter, matches the validator-lib style\nexport function describeUnclassifiedApiDep(violation: UnclassifiedApiDep): string {\n if (violation.unresolved.length > 0) return describeBlindScan(violation);\n const apiHint = violation.apis.length > 0 ? violation.apis.join(', ') : 'the API';\n const theApi = violation.apis[0] ?? 'TheApi';\n const lines = [\n ` ❌ '${violation.project}' (role:${violation.role}) depends on api-lib '${violation.apiLib}' ` +\n `but neither IMPLEMENTS nor USES any of its APIs (${apiHint}).`,\n ` Do ONE of:`,\n ` 1. USE it as a client: inject ClientHttpFactory (@webpieces/http-client-node) or ` +\n `ClientHttpBrowserFactory (@webpieces/http-client-browser) and call ` +\n `factory.createRpcClient(${theApi}, config); for a @PubSub api use ` +\n `ClientCloudTasksFactory.createPubSubClient(${theApi}, config).`,\n ` 2. IMPLEMENT it: add a controller and register it — ` +\n `apiFactory.addRoutes(${theApi}, TheController).`,\n ` 3. If the dependency is unused, remove '${violation.apiLib}' from '${violation.project}'.`,\n ];\n return lines.join('\\n');\n}\n"]}
1
+ {"version":3,"file":"api-relations-validator.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/api-usage/api-relations-validator.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * API Relations Validator\n *\n * Enforces that the dependency graph is TRUTHFUL: a runnable project (role server\n * or client) that compiles against an api-lib must actually IMPLEMENT (serve) or\n * USE (call) at least one of its APIs. A dependency that is neither is almost\n * always a mistake — a forgotten client wiring, a dead import, or a controller\n * that was never registered — and it would draw an unexplained edge in the graph.\n *\n * The check reuses the same source scan that produces `apiRelations`, so \"does P\n * relate to api-lib D\" is answered by real code, never a declaration.\n */\n\nimport type { EnhancedGraph } from '../graph-sorter';\nimport { ProjectInfo } from '../project-info';\nimport { resolveRole } from '../role-resolver';\nimport { ApiScanResult } from './api-scanner';\n// UnresolvedApiCall moved beside its sibling diagnostic DTOs when api-scanner.ts reached its limit.\nimport { UnresolvedApiCall } from './api-relations';\n\n/** Roles that must justify every api-lib dependency (top-level runnables). */\nconst CHECKED_ROLES: ReadonlyArray<string> = ['server', 'client'];\n\n/** One `role:server`/`role:client` project that depends on an api-lib it neither implements nor uses. */\nexport interface UnclassifiedApiDep {\n project: string;\n role: string;\n apiLib: string;\n /** The API contract class names that api-lib exports (for the fix hint). */\n apis: string[];\n /**\n * Contracts this project DOES name at a call site but which never resolved to decorated\n * source. When non-empty the wiring almost certainly exists and the scan is blind — advice\n * to \"add a controller\" or \"remove the dependency\" would be actively wrong.\n */\n unresolved: UnresolvedApiCall[];\n}\n\nexport class ApiOwnerIdentity {\n constructor(\n public readonly api: string,\n public readonly owner: string,\n ) {}\n}\n\nexport class ApiRelationEvidence {\n constructor(\n public readonly scannedProjects: Set<string>,\n public readonly apiLibProjects: Set<string>,\n public readonly unresolvedApiCalls: UnresolvedApiCall[],\n public readonly apiIndex: ReadonlyMap<string, ApiOwnerIdentity>,\n ) {}\n}\n\n/** The API class names owned by `apiLib`, sorted (for a stable fix hint). */\n// webpieces-disable no-function-outside-class -- pure lookup helper, matches the validator-lib style\nfunction apisOwnedBy(scan: ApiRelationEvidence, apiLib: string): string[] {\n const names: string[] = [];\n for (const info of scan.apiIndex.values()) {\n if (info.owner === apiLib) names.push(info.api);\n }\n return names.sort();\n}\n\n/**\n * Every server/client → api-lib edge for which the scan found NO implements and\n * NO uses. `graph` must already carry `apiRelations` (call scanAndAttachApiRelations first).\n */\n// webpieces-disable no-function-outside-class -- module entry point, mirrors findUnclassified-style validators\nexport function findUnclassifiedApiDeps(\n graph: EnhancedGraph,\n projectInfos: Map<string, ProjectInfo>,\n scan: ApiRelationEvidence,\n): UnclassifiedApiDep[] {\n const violations: UnclassifiedApiDep[] = [];\n for (const projectName of Object.keys(graph)) {\n const info = projectInfos.get(projectName);\n if (!info) continue;\n const role = resolveRole(info).role;\n if (role === null || !CHECKED_ROLES.includes(role)) continue;\n // Only flag projects whose production source was actually scanned. An all-test project\n // (e.g. an e2e harness whose only files are *.spec.ts) is never observed, so we can't\n // conclude its api-lib dependency is unused.\n if (!scan.scannedProjects.has(projectName)) continue;\n\n const entry = graph[projectName];\n for (const dep of entry.dependsOn) {\n if (!scan.apiLibProjects.has(dep)) continue;\n if (entry.apiRelations && entry.apiRelations[dep]) continue;\n violations.push({\n project: projectName,\n role,\n apiLib: dep,\n apis: apisOwnedBy(scan, dep),\n unresolved: scan.unresolvedApiCalls.filter(\n (c: UnresolvedApiCall) => c.project === projectName,\n ),\n });\n }\n }\n return violations;\n}\n\n/**\n * The project DOES wire this contract up — we just couldn't see it, because the import resolved\n * to a decorator-erased declaration file. Report THAT, not a list of dead ends: telling a dev to\n * register a controller they already registered, or to delete a load-bearing dependency, sends\n * them chasing ghosts and gets the whole validator turned off.\n */\n// webpieces-disable no-function-outside-class -- pure formatter, matches the validator-lib style\nfunction describeBlindScan(violation: UnclassifiedApiDep): string {\n const lines = [\n ` ❌ '${violation.project}' (role:${violation.role}) depends on api-lib '${violation.apiLib}' and its ` +\n `wiring IS present in source, but the contract could not be read:`,\n ];\n for (const call of violation.unresolved) {\n lines.push(\n ` • ${call.api} at ${call.at} resolved to ${call.declaredIn} (decorators erased in .d.ts).`,\n );\n }\n lines.push(\n ` This is a CONFIG gap, not a wiring gap. Do NOT add a controller and do NOT remove the dependency.`,\n ` Fix: add a tsconfig.base.json 'paths' entry for '${violation.apiLib}' → its src/index.ts,`,\n ` so the import resolves to source instead of dist/.`,\n );\n return lines.join('\\n');\n}\n\n/** Human-readable, fix-oriented report for one unclassified dependency. */\n// webpieces-disable no-function-outside-class -- pure formatter, matches the validator-lib style\nexport function describeUnclassifiedApiDep(violation: UnclassifiedApiDep): string {\n if (violation.unresolved.length > 0) return describeBlindScan(violation);\n const apiHint = violation.apis.length > 0 ? violation.apis.join(', ') : 'the API';\n const theApi = violation.apis[0] ?? 'TheApi';\n const lines = [\n ` ❌ '${violation.project}' (role:${violation.role}) depends on api-lib '${violation.apiLib}' ` +\n `but neither IMPLEMENTS nor USES any of its APIs (${apiHint}).`,\n ` Do ONE of:`,\n ` 1. USE it as a client: inject ClientHttpFactory (@webpieces/http-client-node) or ` +\n `ClientHttpBrowserFactory (@webpieces/http-client-browser) and call ` +\n `factory.createRpcClient(${theApi}, config); for a @PubSub api use ` +\n `ClientCloudTasksFactory.createPubSubClient(${theApi}, config).`,\n ` 2. IMPLEMENT it: add a controller and register it — ` +\n `apiFactory.addRoutes(${theApi}, TheController).`,\n ` 3. If the dependency is unused, remove '${violation.apiLib}' from '${violation.project}'.`,\n ];\n return lines.join('\\n');\n}\n"]}
@@ -78,6 +78,9 @@ export interface ApiRef {
78
78
  * falls back to the old fan-out and says so out loud.
79
79
  */
80
80
  targetService?: string;
81
+ /** Qualified selected library export that contributed this relationship. */
82
+ declaredVia?: string;
83
+ conditional?: string;
81
84
  /**
82
85
  * ONLY on a `pubsub` uses ref. True means "this producer was attributed to EVERY cloudtasks
83
86
  * method of the contract, not to the methods it actually enqueues".