@onlineapps/conn-orch-validator 6.0.1 → 8.0.0

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 (102) hide show
  1. package/CHANGELOG.md +2591 -2
  2. package/README.md +1075 -7
  3. package/docs/DESIGN.md +3 -1
  4. package/manifests/biz-service.manifest.json +658 -0
  5. package/manifests/library.manifest.json +324 -0
  6. package/package.json +12 -6
  7. package/src/CookbookTestRunner.js +422 -104
  8. package/src/CookbookTestUtils.js +7 -8
  9. package/src/ServiceReadinessValidator.js +78 -42
  10. package/src/ValidationOrchestrator.js +298 -75
  11. package/src/cli/biz-ci-gate.js +176 -33
  12. package/src/cli/oa-lint-scripts.js +221 -0
  13. package/src/cli/oa-sync-template.js +1020 -0
  14. package/src/cli/oa-validate.js +474 -0
  15. package/src/helpers/README.md +12 -2
  16. package/src/helpers/createServiceReadinessTests.js +75 -6
  17. package/src/index.js +33 -3
  18. package/src/lint/scripts/lintScripts.js +298 -0
  19. package/src/manifest/checks/composeRunnerBlock.js +222 -0
  20. package/src/manifest/checks/composeShape.js +165 -0
  21. package/src/manifest/checks/contractBridge.js +181 -0
  22. package/src/manifest/checks/discoveryOrphan.js +50 -0
  23. package/src/manifest/checks/docsLintBridge.js +553 -0
  24. package/src/manifest/checks/fileAbsent.js +35 -0
  25. package/src/manifest/checks/gitTracked.js +204 -0
  26. package/src/manifest/checks/index.js +111 -0
  27. package/src/manifest/checks/libraryContext.js +226 -0
  28. package/src/manifest/checks/libraryDocs.js +75 -0
  29. package/src/manifest/checks/libraryPackage.js +272 -0
  30. package/src/manifest/checks/librarySource.js +274 -0
  31. package/src/manifest/checks/libraryTests.js +121 -0
  32. package/src/manifest/checks/libraryWorkspace.js +293 -0
  33. package/src/manifest/checks/readmeRegion.js +135 -0
  34. package/src/manifest/checks/scriptHeaders.js +79 -0
  35. package/src/manifest/checks/serviceConfig.js +390 -0
  36. package/src/manifest/checks/serviceConnectors.js +81 -0
  37. package/src/manifest/checks/serviceDb.js +388 -0
  38. package/src/manifest/checks/serviceFiles.js +754 -0
  39. package/src/manifest/checks/serviceIdentityRows.js +351 -0
  40. package/src/manifest/checks/serviceRuntime.js +295 -0
  41. package/src/manifest/checks/serviceScripts.js +213 -0
  42. package/src/manifest/deployabilitySignal.js +121 -0
  43. package/src/manifest/discovery.js +386 -0
  44. package/src/manifest/loadManifest.js +62 -0
  45. package/src/manifest/manifestShape.js +446 -0
  46. package/src/manifest/report.js +245 -0
  47. package/src/manifest/runManifest.js +449 -0
  48. package/src/manifest/serviceIdentity.js +140 -0
  49. package/src/manifest/walk.js +74 -0
  50. package/src/manifest/workspaceRoot.js +242 -0
  51. package/src/mocks/MockMQClient.js +13 -30
  52. package/src/mocks/MockRegistry.js +4 -2
  53. package/src/mocks/MockStorage.js +4 -2
  54. package/src/sync/docsRegion.js +463 -0
  55. package/src/sync/generatedRegion.js +228 -0
  56. package/src/sync/readmeLocation.js +182 -0
  57. package/src/sync/readmePointer.js +477 -0
  58. package/src/sync/serviceTemplate.js +583 -0
  59. package/src/sync/sharedEnv.js +162 -0
  60. package/src/sync/uniformFiles.js +474 -0
  61. package/src/utils/bizCiGateContract.js +131 -7
  62. package/src/utils/connectorContract.js +97 -7
  63. package/src/utils/cookbookFormat.js +81 -40
  64. package/src/utils/deployContract.js +213 -13
  65. package/src/utils/envContract.js +57 -1
  66. package/src/utils/handlerRef.js +181 -0
  67. package/src/utils/installContract.js +287 -41
  68. package/src/utils/libCompat.js +29 -7
  69. package/src/utils/migrationOrder.js +163 -0
  70. package/src/utils/preValidation.js +20 -7
  71. package/src/utils/setupDatabase.js +194 -13
  72. package/src/utils/testCoverageContract.js +539 -0
  73. package/src/utils/testNamespace.js +247 -23
  74. package/src/utils/throwawaySchema.js +207 -0
  75. package/src/validators/ServiceStructureValidator.js +21 -20
  76. package/templates/business-service/.dockerignore +42 -0
  77. package/templates/business-service/.gitlab-ci.yml +290 -0
  78. package/templates/business-service/Dockerfile +27 -0
  79. package/templates/business-service/README.md +213 -0
  80. package/templates/business-service/config/biz-docs-lint.tree.json +10 -0
  81. package/templates/business-service/config/env-templates/__SERVICE_NAME__.env +4 -0
  82. package/templates/business-service/config/env-templates/shared.env +65 -0
  83. package/templates/business-service/config/service/config.json +14 -0
  84. package/templates/business-service/config/service/integration-contract.json +12 -0
  85. package/templates/business-service/config/service/operations.json +41 -0
  86. package/templates/business-service/docker-compose.production.yml +60 -0
  87. package/templates/business-service/docker-compose.yml +93 -0
  88. package/templates/business-service/docs/80-setup/INSTALL.md +101 -0
  89. package/templates/business-service/docs/80-setup/PLATFORM_MATRIX.md +65 -0
  90. package/templates/business-service/docs/80-setup/README.md +18 -0
  91. package/templates/business-service/docs/80-setup/VALIDATION.md +78 -0
  92. package/templates/business-service/docs/README.md +18 -0
  93. package/templates/business-service/gitignore +42 -0
  94. package/templates/business-service/index.js +10 -0
  95. package/templates/business-service/init.sh +54 -0
  96. package/templates/business-service/jest.config.js +6 -0
  97. package/templates/business-service/package.json.template +31 -0
  98. package/templates/business-service/scripts/verify-deploy-uniform.sh +180 -0
  99. package/templates/business-service/src/handlers/v3/echo.js +39 -0
  100. package/templates/business-service/tests/cookbooks/echo.json +36 -0
  101. package/templates/business-service/tests/unit/handler.test.js +78 -0
  102. package/src/WorkflowTestRunner.js +0 -402
@@ -3,7 +3,7 @@
3
3
  const {
4
4
  MIN_COOKBOOK_FORMAT_VERSION,
5
5
  checkCookbookFormatVersion,
6
- normalizeCookbookSteps,
6
+ readCookbookSteps,
7
7
  stepIdentityOf
8
8
  } = require('./utils/cookbookFormat');
9
9
 
@@ -85,13 +85,12 @@ class CookbookTestUtils {
85
85
  errors.push(versionProblem);
86
86
  }
87
87
 
88
- // Both documented shapes, through the one owner of that rule. Requiring an
89
- // array here rejected every V2 object-shaped cookbook, and requiring `id`
90
- // rejected every cookbook that follows format.md — measured 2026-08-30,
91
- // that was 100 % of the tree.
88
+ // The one shape the format allows, through the one owner of that rule
89
+ // (`utils/cookbookFormat`), so this entry point and the Tier-1 runner cannot
90
+ // disagree about what a cookbook's steps are.
92
91
  let steps;
93
92
  try {
94
- steps = normalizeCookbookSteps(cookbook.steps);
93
+ steps = readCookbookSteps(cookbook.steps);
95
94
  } catch (error) {
96
95
  // The refusal is the finding. This entry point answers with a list, not
97
96
  // an exception, so the message becomes one more error in that list.
@@ -137,8 +136,8 @@ class CookbookTestUtils {
137
136
  }
138
137
 
139
138
  // Compare steps
140
- const steps1 = normalizeCookbookSteps(cookbook1.steps);
141
- const steps2 = normalizeCookbookSteps(cookbook2.steps);
139
+ const steps1 = readCookbookSteps(cookbook1.steps);
140
+ const steps2 = readCookbookSteps(cookbook2.steps);
142
141
 
143
142
  if (steps1.length !== steps2.length) {
144
143
  differences.push({
@@ -1,10 +1,21 @@
1
1
  'use strict';
2
2
 
3
+ const { assertLogger } = require('@onlineapps/logger-contract');
3
4
  const CookbookTestUtils = require('./CookbookTestUtils');
5
+ const { HANDLER_REF_PATTERN } = require('./utils/handlerRef');
4
6
 
5
- // The logger methods this validator requires — the order is the order the
6
- // error message lists them in.
7
- const LOGGER_METHODS = ['info', 'warn', 'error', 'debug'];
7
+ /**
8
+ * The semver 2.0.0 grammar, verbatim from semver.org's own published regular
9
+ * expression (anchored, no leading `v`, no surrounding whitespace, no leading
10
+ * zeroes in a numeric identifier).
11
+ *
12
+ * Written out rather than pulled from the `semver` package: this is the only
13
+ * version rule in the package, one anchored pattern expresses it completely,
14
+ * and a dependency added for a single `test()` call is a supply chain the gate
15
+ * does not need. If a second version rule ever appears here, that trade changes
16
+ * — and the change is then visible, because it is this comment that has to go.
17
+ */
18
+ const SEMVER_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;
8
19
 
9
20
  /**
10
21
  * ServiceReadinessValidator - Orchestrates complete service validation.
@@ -25,16 +36,9 @@ const LOGGER_METHODS = ['info', 'warn', 'error', 'debug'];
25
36
  * previously spent on `health` fold into `operations` so total stays
26
37
  * at 100.
27
38
  *
28
- * 2026-08-22: the `url` the probe consumed is gone too. It outlived the
29
- * probe by five months as an accepted-and-ignored argument echoed into
30
- * `results.serviceUrl` and printed as a `URL:` line — a report field
31
- * naming an endpoint that does not exist. The only caller that still
32
- * passed one was Tier-1's readiness step, and that step is gone
33
- * (see ValidationOrchestrator: its verdict was a strict function of
34
- * steps 2 and 3). What is left has exactly one consumer:
35
- * `helpers/createServiceReadinessTests`, and through it the
36
- * `tests/bootstrap/` suites of the biz repos — which pass name, version,
37
- * operations, testCookbook and registry, and never passed a url.
39
+ * `validateReadiness(service)` reads `name`, `version`, `operations`,
40
+ * `testCookbook` and `registry`. Those five are what it reads, and it
41
+ * reads no others.
38
42
  *
39
43
  * @see api/docs/biz/30-operations/schema-v3.md
40
44
  * @see /api/docs/biz/40-cookbooks/test-runner-flow.md (input probe contract)
@@ -48,26 +52,11 @@ class ServiceReadinessValidator {
48
52
  // caller that did not print the returned object learned nothing about why a
49
53
  // service was refused. Owner confirmation:
50
54
  // docs/governance/confirmations/connector-logger-contract.md 001.
51
- if (!options.logger) {
52
- throw new Error(
53
- '[ServiceReadinessValidator] Logger is required - Expected: a logger with info/warn/error/debug, '
54
- + 'so the readiness verdict leaves the process. '
55
- + 'Fix: pass options.logger (e.g. the logger your service already built).'
56
- );
57
- }
58
-
59
- const missingLoggerMethods = LOGGER_METHODS.filter(
60
- (method) => typeof options.logger[method] !== 'function'
55
+ this.logger = assertLogger(
56
+ 'ServiceReadinessValidator',
57
+ options.logger,
58
+ 'the readiness verdict leaves the process'
61
59
  );
62
- if (missingLoggerMethods.length > 0) {
63
- throw new Error(
64
- '[ServiceReadinessValidator] Logger is incomplete - Expected: info, warn, error, debug as functions; '
65
- + `missing: ${missingLoggerMethods.join(', ')}. `
66
- + 'Fix: pass a logger implementing all four.'
67
- );
68
- }
69
-
70
- this.logger = options.logger;
71
60
 
72
61
  // Readiness checks: core (80 points) + optional (20 points) = 100 points max
73
62
  // Core checks ALWAYS run, optional checks run if testCookbook/registry provided
@@ -240,7 +229,6 @@ class ServiceReadinessValidator {
240
229
  }
241
230
 
242
231
  const validScopes = ['platform', 'tenant', 'workspace'];
243
- const handlerPattern = /^handlers\/[a-zA-Z0-9_\/-]+#[a-zA-Z_][a-zA-Z0-9_]*$/;
244
232
  const forbiddenV2Fields = ['endpoint', 'method', 'path'];
245
233
 
246
234
  for (const [name, operation] of Object.entries(operations)) {
@@ -249,7 +237,7 @@ class ServiceReadinessValidator {
249
237
  }
250
238
  if (!operation.handler) {
251
239
  errors.push(`Operation '${name}' missing handler (v3 — 'handlers/<path>#<export>')`);
252
- } else if (!handlerPattern.test(operation.handler)) {
240
+ } else if (!HANDLER_REF_PATTERN.test(operation.handler)) {
253
241
  errors.push(`Operation '${name}' has invalid handler ref: '${operation.handler}'`);
254
242
  }
255
243
  if (!operation.bundle_scope) {
@@ -311,16 +299,63 @@ class ServiceReadinessValidator {
311
299
  }
312
300
 
313
301
  /**
314
- * Check registry compatibility
302
+ * Check registry compatibility.
303
+ *
304
+ * `canRegister` used to be `!!(name && version && operations)`. Truthiness is
305
+ * the wrong question about a version: the line below it compares this value
306
+ * against what the registry already holds, and a comparison between two
307
+ * strings that are not versions decides nothing. Measured (BIZ-ingest): every
308
+ * biz `config/service/config.json` declares
309
+ * `"service": { "version": "${npm_package_version}" }`, the placeholder was
310
+ * handed here unexpanded, and — being a non-empty string — it passed. The
311
+ * gate reported `canRegister: true` about a value that is not a version.
312
+ *
313
+ * The grammar is semver 2.0.0, checked here and nowhere else: this is the one
314
+ * place that decides `canRegister`. Callers supply the value (the `version`
315
+ * field of the service package.json, which is what ConfigLoader reads at
316
+ * runtime); they do not re-check it.
315
317
  */
316
318
  async checkRegistryCompatibility(service, registry) {
317
319
  try {
318
- // Check if service can be registered
319
- const canRegister = !!(
320
- service.name &&
321
- service.version &&
322
- service.operations
323
- );
320
+ const errors = [];
321
+
322
+ if (!service.name) {
323
+ errors.push(
324
+ '[ServiceReadinessValidator] Service name is missing - '
325
+ + 'validateReadiness() received no `name`. '
326
+ + 'Expected: the service name, e.g. biz-ingest. '
327
+ + 'Fix: pass it in the object handed to validateReadiness().'
328
+ );
329
+ }
330
+
331
+ if (service.version === undefined || service.version === null) {
332
+ errors.push(
333
+ '[ServiceReadinessValidator] Service version is missing - '
334
+ + 'validateReadiness() received no `version`. '
335
+ + 'Expected: the `version` field of the service package.json, e.g. 1.4.2. '
336
+ + 'Fix: pass it in the object handed to validateReadiness().'
337
+ );
338
+ } else if (!SEMVER_PATTERN.test(service.version)) {
339
+ errors.push(
340
+ '[ServiceReadinessValidator] Service version is not a semantic version - '
341
+ + `received "${service.version}". `
342
+ + 'Expected: MAJOR.MINOR.PATCH per semver 2.0.0, e.g. 1.4.2 or 1.4.2-rc.1. '
343
+ + 'Fix: pass the `version` field of the service package.json — the single source of '
344
+ + 'truth ConfigLoader reads at runtime. An unresolved "${...}" means the value came '
345
+ + 'from config.json instead, where the key is never expanded.'
346
+ );
347
+ }
348
+
349
+ if (!service.operations) {
350
+ errors.push(
351
+ '[ServiceReadinessValidator] Operations are missing - '
352
+ + 'validateReadiness() received no `operations`. '
353
+ + 'Expected: the flat operations object from config/service/operations.json. '
354
+ + 'Fix: pass it in the object handed to validateReadiness().'
355
+ );
356
+ }
357
+
358
+ const canRegister = errors.length === 0;
324
359
 
325
360
  // Check if service operations match registry expectations
326
361
  const registeredService = registry.getService(service.name);
@@ -331,7 +366,8 @@ class ServiceReadinessValidator {
331
366
  passed: canRegister && compatible,
332
367
  canRegister,
333
368
  compatible,
334
- existingVersion: registeredService?.version
369
+ existingVersion: registeredService?.version,
370
+ errors
335
371
  };
336
372
  } catch (error) {
337
373
  return {