@onlineapps/conn-orch-validator 12.1.1 → 13.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.
- package/CHANGELOG.md +607 -0
- package/README.md +126 -19
- package/manifests/biz-service.manifest.json +15 -2
- package/manifests/library.manifest.json +4 -4
- package/package.json +11 -3
- package/src/CookbookTestRunner.js +275 -105
- package/src/CookbookTestUtils.js +79 -68
- package/src/ServiceReadinessValidator.js +42 -52
- package/src/ValidationOrchestrator.js +65 -44
- package/src/cli/biz-ci-gate.js +2 -2
- package/src/cli/oa-sync-template.js +97 -47
- package/src/cli/oa-validate.js +44 -10
- package/src/helpers/README.md +6 -6
- package/src/helpers/createServiceReadinessTests.js +87 -33
- package/src/index.js +14 -5
- package/src/lint/scripts/lintScripts.js +11 -4
- package/src/manifest/checks/libraryContext.js +6 -3
- package/src/manifest/checks/libraryDocs.js +174 -4
- package/src/manifest/checks/libraryTests.js +200 -19
- package/src/manifest/checks/scriptHeaders.js +6 -13
- package/src/manifest/checks/serviceConfig.js +36 -16
- package/src/manifest/checks/serviceConnectors.js +180 -2
- package/src/manifest/checks/serviceDb.js +0 -3
- package/src/manifest/checks/serviceScripts.js +3 -20
- package/src/manifest/runManifest.js +90 -13
- package/src/manifest/workspaceRoot.js +133 -4
- package/src/mocks/MockMQClient.js +2 -2
- package/src/sync/docsRegion.js +2 -2
- package/src/sync/readmeFile.js +30 -0
- package/src/sync/readmeLocation.js +2 -12
- package/src/sync/readmePointer.js +10 -4
- package/src/sync/serviceTemplate.js +9 -11
- package/src/sync/sharedEnv.js +59 -3
- package/src/sync/uniformFiles.js +81 -8
- package/src/utils/bizCiGateContract.js +2 -2
- package/src/utils/connectorContract.js +54 -2
- package/src/utils/cookbookFormat.js +25 -115
- package/src/utils/dbAccountGrants.js +5 -3
- package/src/utils/deployContract.js +153 -28
- package/src/utils/envContract.js +2 -2
- package/src/utils/handlerRef.js +8 -10
- package/src/utils/integrationRun.js +1 -1
- package/src/utils/operationsDocumentRules.js +242 -0
- package/src/utils/operationsRules.js +157 -0
- package/src/utils/resolveHeaders.js +12 -1
- package/src/utils/setupDatabase.js +1 -1
- package/src/utils/stepFailure.js +3 -3
- package/src/utils/stepReferences.js +28 -87
- package/src/utils/throwawaySchema.js +1 -1
- package/src/utils/yamlTopLevel.js +105 -0
- package/src/validators/ServiceStructureValidator.js +67 -152
- package/templates/business-service/.gitlab-ci.yml +203 -37
- package/templates/business-service/README.md +7 -4
- package/templates/business-service/config/env-templates/shared.env +1 -0
- package/templates/business-service/docs/80-setup/INSTALL.md +31 -3
- package/templates/business-service/docs/80-setup/PLATFORM_MATRIX.md +5 -2
- package/templates/business-service/src/config/index.js +15 -0
- package/TESTING_STRATEGY.md +0 -92
- package/jest.config.js +0 -37
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Which top-level key of a YAML document owns a line — the ONE definition of
|
|
5
|
+
* that question in this package.
|
|
6
|
+
*
|
|
7
|
+
* Two modules ask it, for two different purposes, and both used to answer it
|
|
8
|
+
* themselves: `src/sync/serviceTemplate.js` (which keys the platform block
|
|
9
|
+
* declares, and whose spans the sync replaces) and `src/utils/deployContract.js`
|
|
10
|
+
* (which JOB a `git reset --hard` sits in, so R2 can refuse a guard standing in
|
|
11
|
+
* another one). Two regexes for one fact, with two tolerances and no note that
|
|
12
|
+
* they differed: `.claude/rules/change-discipline.md` § One rail per concern.
|
|
13
|
+
*
|
|
14
|
+
* Column 0 is the whole rule, and it is enough for the files this reads: a
|
|
15
|
+
* `.gitlab-ci.yml` job is a top-level key and everything it owns is indented
|
|
16
|
+
* under it. Text in, positions out — nothing is re-serialized, so a comment and
|
|
17
|
+
* a quoting style survive (`.claude/rules/automation-gates.md` §1 requirement 3).
|
|
18
|
+
*
|
|
19
|
+
* The two readings differ in ONE thing, and it is a named predicate over this
|
|
20
|
+
* one regex rather than a second regex:
|
|
21
|
+
*
|
|
22
|
+
* `topLevelKeys` every key, hidden ones included. A hidden key
|
|
23
|
+
* (`.oa-uniform:`) is a declaration GitLab never runs
|
|
24
|
+
* as a job, but it is still a key, and a reader
|
|
25
|
+
* asking "which key is this line under" has to place
|
|
26
|
+
* it somewhere truthful.
|
|
27
|
+
* `rewritableTopLevelKeys` hidden keys dropped. This is the reading a
|
|
28
|
+
* GENERATOR may act on: `replaceTopLevelKeys`
|
|
29
|
+
* DELETES the spans it is given, and a hidden key a
|
|
30
|
+
* service wrote itself is not the platform's to
|
|
31
|
+
* delete (tests/unit/manifestCiBlock.test.js, G-CI
|
|
32
|
+
* over a pipeline that predates the markers).
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A top-level mapping key: a line that starts in column 0 and ends its key with
|
|
37
|
+
* a colon. The leading dot is optional, because a hidden key is a key.
|
|
38
|
+
*
|
|
39
|
+
* The key's characters are what excludes the lines that are not keys: a list
|
|
40
|
+
* item (`- if: …`), a comment (`# build:`), an indented key, and anything with a
|
|
41
|
+
* space in it cannot match, so no `.gitlab-ci.yml` construct is mistaken for one.
|
|
42
|
+
*/
|
|
43
|
+
const TOP_LEVEL_KEY = /^(\.?[A-Za-z_][A-Za-z0-9_.-]*):/gm;
|
|
44
|
+
|
|
45
|
+
/** What `topLevelKeyAt` answers for text that precedes the first key. */
|
|
46
|
+
const NO_TOP_LEVEL_KEY = '(before the first key)';
|
|
47
|
+
|
|
48
|
+
/** True for a hidden key — GitLab runs nothing whose name starts with a dot. */
|
|
49
|
+
function isHiddenKey(name) {
|
|
50
|
+
return name.startsWith('.');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Every top-level key of the document, in file order.
|
|
55
|
+
*
|
|
56
|
+
* @param {string} text the document
|
|
57
|
+
* @returns {Array<{name: string, at: number, line: number}>} `at` is the offset
|
|
58
|
+
* of the key in `text`, `line` its 0-based index in `text.split('\n')`.
|
|
59
|
+
*/
|
|
60
|
+
function topLevelKeys(text) {
|
|
61
|
+
if (typeof text !== 'string') {
|
|
62
|
+
throw new Error('[YamlTopLevel] Missing document text - Expected the YAML as a string. '
|
|
63
|
+
+ 'Fix: pass the file contents, not a path and not an array of lines.');
|
|
64
|
+
}
|
|
65
|
+
const keys = [];
|
|
66
|
+
TOP_LEVEL_KEY.lastIndex = 0;
|
|
67
|
+
let scanned = 0;
|
|
68
|
+
let line = 0;
|
|
69
|
+
let hit;
|
|
70
|
+
while ((hit = TOP_LEVEL_KEY.exec(text)) !== null) {
|
|
71
|
+
for (let i = scanned; i < hit.index; i += 1) if (text[i] === '\n') line += 1;
|
|
72
|
+
scanned = hit.index;
|
|
73
|
+
keys.push({ name: hit[1], at: hit.index, line });
|
|
74
|
+
}
|
|
75
|
+
return keys;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* The top-level keys a generator may rewrite: every key except the hidden ones.
|
|
80
|
+
*
|
|
81
|
+
* @param {string} text the document
|
|
82
|
+
* @returns {Array<{name: string, at: number, line: number}>}
|
|
83
|
+
*/
|
|
84
|
+
function rewritableTopLevelKeys(text) {
|
|
85
|
+
return topLevelKeys(text).filter((key) => !isHiddenKey(key.name));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* The key an offset sits under — for a command in a `.gitlab-ci.yml`, the job
|
|
90
|
+
* whose shell runs it.
|
|
91
|
+
*
|
|
92
|
+
* @param {Array<{name: string, at: number}>} keys from `topLevelKeys`
|
|
93
|
+
* @param {number} offset an offset into the same document
|
|
94
|
+
* @returns {string} the key's name, or `NO_TOP_LEVEL_KEY`
|
|
95
|
+
*/
|
|
96
|
+
function topLevelKeyAt(keys, offset) {
|
|
97
|
+
let name = NO_TOP_LEVEL_KEY;
|
|
98
|
+
for (const key of keys) {
|
|
99
|
+
if (key.at > offset) break;
|
|
100
|
+
name = key.name;
|
|
101
|
+
}
|
|
102
|
+
return name;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
module.exports = { topLevelKeys, rewritableTopLevelKeys, topLevelKeyAt, NO_TOP_LEVEL_KEY, isHiddenKey };
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
|
|
18
18
|
const fs = require('fs');
|
|
19
19
|
const path = require('path');
|
|
20
|
-
const {
|
|
20
|
+
const { operationsRuleRecords } = require('../utils/operationsRules');
|
|
21
|
+
const { operationsDocumentFindings, documentFindingRecord } = require('../utils/operationsDocumentRules');
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* Read a configuration file from the ONE place a service may keep it:
|
|
@@ -130,7 +131,7 @@ function readFile(root, rel) {
|
|
|
130
131
|
* The body is `[^{}]*?`, NOT `[\s\S]*?`: a lazy any-character body starts at the
|
|
131
132
|
* nearest preceding `const {` and swallows whole lines to reach the right
|
|
132
133
|
* `require`, which dropped the first imported name. Measured on
|
|
133
|
-
* `api_biz/meta/src/handlers/persons.js
|
|
134
|
+
* the imports of `api_biz/meta/src/handlers/persons.js`, where an unrelated `models`
|
|
134
135
|
* destructuring sits directly above the service-common one.
|
|
135
136
|
*
|
|
136
137
|
* @returns {string[]}
|
|
@@ -279,7 +280,7 @@ const STANDARD_LEVELS = [
|
|
|
279
280
|
// infrastructureGate, health, validation, mq, registry, monitoring, state,
|
|
280
281
|
// secrets and heartbeat — not tenantContext; `createTenantContextMiddleware`
|
|
281
282
|
// was deleted on 2026-09-05 together with the runtime-defaults key
|
|
282
|
-
// (docs/governance/confirmations/wrapper-tenant-middleware.md). A
|
|
283
|
+
// (api/docs/governance/confirmations/wrapper-tenant-middleware.md). A
|
|
283
284
|
// level enforcing a dead declaration is the false guarantee automation-gates.md
|
|
284
285
|
// §5 names, so the level is GONE rather than emptied — an empty level would
|
|
285
286
|
// pass for every service while asserting nothing.
|
|
@@ -423,7 +424,7 @@ class ServiceStructureValidator {
|
|
|
423
424
|
? `Standard ${nextLevel.level} (${nextLevel.name}): ${check.message}`
|
|
424
425
|
: `Standard ${nextLevel.level} (${nextLevel.name}): missing ${check.message}`,
|
|
425
426
|
fix: check.fix
|
|
426
|
-
|| `Implement ${check.message} to reach standard ${nextLevel.level}. See docs/biz/60-templates/service-template.md`
|
|
427
|
+
|| `Implement ${check.message} to reach standard ${nextLevel.level}. See api/docs/biz/60-templates/service-template.md`
|
|
427
428
|
});
|
|
428
429
|
}
|
|
429
430
|
}
|
|
@@ -510,7 +511,7 @@ class ServiceStructureValidator {
|
|
|
510
511
|
type: 'MISSING_CONFIG',
|
|
511
512
|
path: 'config/service/config.json',
|
|
512
513
|
message: 'Service configuration missing: config/service/config.json',
|
|
513
|
-
fix: 'Create config.json with service metadata. See: /docs/biz/60-templates/service-template.md'
|
|
514
|
+
fix: 'Create config.json with service metadata. See: api/docs/biz/60-templates/service-template.md'
|
|
514
515
|
});
|
|
515
516
|
} else {
|
|
516
517
|
try {
|
|
@@ -533,7 +534,7 @@ class ServiceStructureValidator {
|
|
|
533
534
|
type: 'MISSING_OPERATIONS',
|
|
534
535
|
path: 'config/service/operations.json',
|
|
535
536
|
message: 'Operations specification missing: config/service/operations.json',
|
|
536
|
-
fix: 'Create operations.json. See: /docs/biz/30-operations/schema-v3.md'
|
|
537
|
+
fix: 'Create operations.json. See: api/docs/biz/30-operations/schema-v3.md'
|
|
537
538
|
});
|
|
538
539
|
} else {
|
|
539
540
|
try {
|
|
@@ -552,40 +553,42 @@ class ServiceStructureValidator {
|
|
|
552
553
|
}
|
|
553
554
|
|
|
554
555
|
/**
|
|
555
|
-
*
|
|
556
|
-
*
|
|
556
|
+
* What step 1 still says about `config.json`: nothing about WHICH KEYS it
|
|
557
|
+
* carries. That question has one owner, the manifest row `C-SERVICE`, and
|
|
558
|
+
* this step is the structural half — the file is there, it reads, it parses.
|
|
559
|
+
*
|
|
560
|
+
* Until d.726 a `requiredFields = ['service.name']` lived here, with a
|
|
561
|
+
* message and a fix of its own, beside a row that demands the same key and
|
|
562
|
+
* two more. The four questions the removal has to answer
|
|
563
|
+
* (`.claude/rules/change-discipline.md` § Removing something removes its
|
|
564
|
+
* declaration):
|
|
565
|
+
*
|
|
566
|
+
* how it came to be — step 1 predates the manifest. When it was written it
|
|
567
|
+
* WAS the only reader of `config.json`, so the key list had to live
|
|
568
|
+
* somewhere and this was the only somewhere there was.
|
|
569
|
+
*
|
|
570
|
+
* which part of the concept carried it — service shape, and that part still
|
|
571
|
+
* stands. What moved is where it is declared: confirmation
|
|
572
|
+
* `biz-service-manifest` §2 makes the manifest the place a rule of service
|
|
573
|
+
* shape is written, with an id, an owner, a severity, a fix and a doc
|
|
574
|
+
* pointer. d.465 already moved boot step 2 onto it.
|
|
575
|
+
*
|
|
576
|
+
* why there were two — nobody removed the first when the second arrived.
|
|
577
|
+
*
|
|
578
|
+
* is the replacement MORE conceptual — yes, and measurably so. `C-SERVICE`
|
|
579
|
+
* demands three keys, not one, and each because grep found the reader that
|
|
580
|
+
* fails without it (`manifest/checks/serviceConfig.js`,
|
|
581
|
+
* REQUIRED_SERVICE_KEYS). The list here had one key and no reader named.
|
|
582
|
+
*
|
|
583
|
+
* And the two rails did not merely overlap: measured d.726, step 1 failing on
|
|
584
|
+
* `name` FAIL-FASTED the orchestrator, so step 2 never ran and a missing
|
|
585
|
+
* `workspaceScoped` — which the wrapper refuses to boot without — was not
|
|
586
|
+
* reported at all until `name` was fixed. The narrower rail was hiding the
|
|
587
|
+
* conceptual one.
|
|
588
|
+
*
|
|
589
|
+
* Note: service.version is read from package.json (Single Source of Truth).
|
|
557
590
|
*/
|
|
558
591
|
validateConfigStructure(config) {
|
|
559
|
-
// Required fields (version comes from package.json, not config.json).
|
|
560
|
-
//
|
|
561
|
-
// `service.port` was required here until 2026-08-30. ADR 0005 removed the
|
|
562
|
-
// HTTP surface from biz containers, so nothing listens and the number has
|
|
563
|
-
// no consumer — requiring it forced every repo to keep a declaration that
|
|
564
|
-
// means nothing, which is what `change-discipline.md` § "Removing something
|
|
565
|
-
// removes its declaration" exists to prevent. Absence is now correct;
|
|
566
|
-
// presence is reported below as a warning, never as a failure.
|
|
567
|
-
const requiredFields = [
|
|
568
|
-
'service.name'
|
|
569
|
-
];
|
|
570
|
-
|
|
571
|
-
for (const field of requiredFields) {
|
|
572
|
-
const parts = field.split('.');
|
|
573
|
-
let value = config;
|
|
574
|
-
for (const part of parts) {
|
|
575
|
-
value = value?.[part];
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
if (!value) {
|
|
579
|
-
this.errors.push({
|
|
580
|
-
type: 'MISSING_CONFIG_FIELD',
|
|
581
|
-
path: 'config/service/config.json',
|
|
582
|
-
field: field,
|
|
583
|
-
message: `Required field missing in config.json: ${field}`,
|
|
584
|
-
fix: `Add "${field}" to config.json`
|
|
585
|
-
});
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
|
|
589
592
|
// A port that is still declared is a stale declaration, not a failure. Say
|
|
590
593
|
// so out loud: `automation-gates.md` §5 — a check that stays silent about a
|
|
591
594
|
// thing it can see is a false guarantee, and the eight services measured in
|
|
@@ -638,118 +641,19 @@ class ServiceStructureValidator {
|
|
|
638
641
|
/**
|
|
639
642
|
* Validate operations.json structure (v3 — handler registry dispatch).
|
|
640
643
|
*
|
|
644
|
+
* Nothing about `operations.json` is decided in this method. The rules about
|
|
645
|
+
* the DOCUMENT (`schema_version`, an operations map with nothing in it) are
|
|
646
|
+
* `src/utils/operationsDocumentRules.js`; the rules about each OPERATION are
|
|
647
|
+
* `src/utils/operationsRules.js`, which asks
|
|
648
|
+
* `@onlineapps/service-validator-core` — the mirror of what the Registry
|
|
649
|
+
* refuses a registration on. Both used to be restated here, and both said
|
|
650
|
+
* something different from what the other rails said (d.465, d.465b).
|
|
651
|
+
*
|
|
641
652
|
* @see api/docs/biz/30-operations/schema-v3.md § File shape
|
|
642
653
|
*/
|
|
643
654
|
validateOperationsStructure(operations) {
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
type: 'INVALID_OPERATIONS_STRUCTURE',
|
|
647
|
-
path: 'config/service/operations.json',
|
|
648
|
-
message: 'operations.json must have "operations" key',
|
|
649
|
-
fix: 'Wrap operations in {"operations": {...}} in config/service/operations.json'
|
|
650
|
-
});
|
|
651
|
-
return;
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
const ops = operations.operations;
|
|
655
|
-
if (typeof ops !== 'object' || Array.isArray(ops)) {
|
|
656
|
-
this.errors.push({
|
|
657
|
-
type: 'INVALID_OPERATIONS_TYPE',
|
|
658
|
-
path: 'config/service/operations.json',
|
|
659
|
-
message: 'operations must be an object',
|
|
660
|
-
fix: 'operations should be key-value pairs: {"operation-name": {...}}'
|
|
661
|
-
});
|
|
662
|
-
return;
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
if (operations.schema_version && operations.schema_version !== '3.0') {
|
|
666
|
-
this.warnings.push({
|
|
667
|
-
type: 'SCHEMA_VERSION_MISMATCH',
|
|
668
|
-
path: 'config/service/operations.json',
|
|
669
|
-
field: 'schema_version',
|
|
670
|
-
value: operations.schema_version,
|
|
671
|
-
message: `operations.json schema_version is "${operations.schema_version}" — expected "3.0"`,
|
|
672
|
-
fix: 'Set schema_version to "3.0" in config/service/operations.json'
|
|
673
|
-
});
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
if (Object.keys(ops).length === 0) {
|
|
677
|
-
this.warnings.push({
|
|
678
|
-
type: 'NO_OPERATIONS',
|
|
679
|
-
path: 'config/service/operations.json',
|
|
680
|
-
message: 'No operations defined',
|
|
681
|
-
fix: 'Add at least one operation to operations.json'
|
|
682
|
-
});
|
|
683
|
-
return;
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
for (const [operationName, operationSpec] of Object.entries(ops)) {
|
|
687
|
-
this.validateOperation(operationName, operationSpec);
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
/**
|
|
692
|
-
* Validate single operation structure (v3).
|
|
693
|
-
* Required: handler, bundle_scope. Forbidden (v2): endpoint, method, path.
|
|
694
|
-
*
|
|
695
|
-
* @see api/docs/biz/30-operations/schema-v3.md § Per-operation keys
|
|
696
|
-
*/
|
|
697
|
-
validateOperation(name, spec) {
|
|
698
|
-
const requiredFields = ['handler', 'bundle_scope'];
|
|
699
|
-
|
|
700
|
-
for (const field of requiredFields) {
|
|
701
|
-
if (!spec[field]) {
|
|
702
|
-
this.errors.push({
|
|
703
|
-
type: 'MISSING_OPERATION_FIELD',
|
|
704
|
-
path: 'config/service/operations.json',
|
|
705
|
-
operation: name,
|
|
706
|
-
field,
|
|
707
|
-
message: `Operation "${name}" missing required field: ${field}`,
|
|
708
|
-
fix: `Add "${field}" to operation "${name}" in config/service/operations.json`
|
|
709
|
-
});
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
if (spec.handler && !HANDLER_REF_PATTERN.test(spec.handler)) {
|
|
714
|
-
this.errors.push({
|
|
715
|
-
type: 'INVALID_HANDLER_REF',
|
|
716
|
-
path: 'config/service/operations.json',
|
|
717
|
-
operation: name,
|
|
718
|
-
field: 'handler',
|
|
719
|
-
value: spec.handler,
|
|
720
|
-
message: `Handler must be in form 'handlers/<path>#<exportName>': ${spec.handler}`,
|
|
721
|
-
fix: `Change handler to form 'handlers/v3/<file>#<exportName>'`
|
|
722
|
-
});
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
const validScopes = ['platform', 'tenant', 'workspace'];
|
|
726
|
-
if (spec.bundle_scope && !validScopes.includes(spec.bundle_scope)) {
|
|
727
|
-
this.errors.push({
|
|
728
|
-
type: 'INVALID_BUNDLE_SCOPE',
|
|
729
|
-
path: 'config/service/operations.json',
|
|
730
|
-
operation: name,
|
|
731
|
-
field: 'bundle_scope',
|
|
732
|
-
value: spec.bundle_scope,
|
|
733
|
-
message: `Invalid bundle_scope: ${spec.bundle_scope}`,
|
|
734
|
-
fix: `Use one of: ${validScopes.join(', ')}`
|
|
735
|
-
});
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
// Reject v2 HTTP-routing fields (clean break per ARCHITECTURE_PRINCIPLES.md §11).
|
|
739
|
-
const forbiddenV2Fields = ['endpoint', 'method', 'path'];
|
|
740
|
-
for (const forbidden of forbiddenV2Fields) {
|
|
741
|
-
if (forbidden in spec) {
|
|
742
|
-
this.errors.push({
|
|
743
|
-
type: 'V2_FIELD_PRESENT',
|
|
744
|
-
path: 'config/service/operations.json',
|
|
745
|
-
operation: name,
|
|
746
|
-
field: forbidden,
|
|
747
|
-
value: spec[forbidden],
|
|
748
|
-
message: `Operation "${name}" has retired v2 field "${forbidden}" — not allowed in v3 schema`,
|
|
749
|
-
fix: `Remove "${forbidden}" from operation "${name}" in config/service/operations.json — v3 dispatches via the handler registry, not by URL`
|
|
750
|
-
});
|
|
751
|
-
}
|
|
752
|
-
}
|
|
655
|
+
this.errors.push(...operationsDocumentFindings(operations).map(documentFindingRecord));
|
|
656
|
+
this.errors.push(...operationsRuleRecords(operations && operations.operations).records);
|
|
753
657
|
}
|
|
754
658
|
|
|
755
659
|
/**
|
|
@@ -791,11 +695,22 @@ class ServiceStructureValidator {
|
|
|
791
695
|
}
|
|
792
696
|
}
|
|
793
697
|
|
|
794
|
-
//
|
|
698
|
+
// The @onlineapps packages a biz service cannot be without: the wrapper it
|
|
699
|
+
// boots through, and this validator, which its own gate runs.
|
|
700
|
+
//
|
|
701
|
+
// `@onlineapps/service-common` was a third entry and is not one any more.
|
|
702
|
+
// The v1.2 block at the top of this file records the decision it
|
|
703
|
+
// contradicted: `dep_service_common` and `business_error_usage` were
|
|
704
|
+
// RETIRED because the error hierarchy moved to the wrapper — so this file
|
|
705
|
+
// refused an import from that package up there and advised installing it
|
|
706
|
+
// down here. It stays a live package, which makes it a dependency a
|
|
707
|
+
// service adds when it needs what that package exports, never a
|
|
708
|
+
// recommendation for every service. See api/docs/biz/RETIRED-VOCABULARY.md,
|
|
709
|
+
// and tests/unit/serviceStructureRecommendedDeps.test.js for what was
|
|
710
|
+
// measured.
|
|
795
711
|
const requiredDeps = [
|
|
796
712
|
'@onlineapps/service-wrapper',
|
|
797
|
-
'@onlineapps/conn-orch-validator'
|
|
798
|
-
'@onlineapps/service-common'
|
|
713
|
+
'@onlineapps/conn-orch-validator'
|
|
799
714
|
];
|
|
800
715
|
|
|
801
716
|
for (const dep of requiredDeps) {
|
|
@@ -844,7 +759,7 @@ class ServiceStructureValidator {
|
|
|
844
759
|
type: 'MISSING_HANDLERS',
|
|
845
760
|
path: 'src/handlers',
|
|
846
761
|
message: 'Handlers directory missing: src/handlers/',
|
|
847
|
-
fix: 'Create src/handlers/ with at least one v3 handler module. See: /docs/biz/60-templates/service-template.md'
|
|
762
|
+
fix: 'Create src/handlers/ with at least one v3 handler module. See: api/docs/biz/60-templates/service-template.md'
|
|
848
763
|
});
|
|
849
764
|
} else {
|
|
850
765
|
this.info.push('✓ Found src/handlers/');
|
|
@@ -860,7 +775,7 @@ class ServiceStructureValidator {
|
|
|
860
775
|
type: 'LEGACY_APP_JS',
|
|
861
776
|
path: 'src/app.js',
|
|
862
777
|
message: 'src/app.js is dead code post-ADR 0005 (bootstrap no longer reads it)',
|
|
863
|
-
fix: 'Delete src/app.js; move any residual middleware into the wrapper adapter. See: /docs/biz/80-decisions/0005-no-http-in-biz-containers.md'
|
|
778
|
+
fix: 'Delete src/app.js; move any residual middleware into the wrapper adapter. See: api/docs/biz/80-decisions/0005-no-http-in-biz-containers.md'
|
|
864
779
|
});
|
|
865
780
|
}
|
|
866
781
|
|