@onlineapps/conn-orch-validator 12.2.0 → 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 +597 -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/README.md +3 -2
- package/templates/business-service/config/env-templates/shared.env +1 -0
- 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
|
|
|
@@ -8,7 +8,7 @@ Duty sections that apply:
|
|
|
8
8
|
- `files`: F-INIT, F-JEST, F-RUNNER, F-GITIGNORE, F-DOCKERIGNORE, G-PROD, G-CI, G-PROD-IMAGE, G-README, G-SETUP, G-SETUP-INSTALL, G-SETUP-MATRIX, G-SETUP-VALIDATION, X-IGNORED, X-HOOKS, X-PREVAL, X-DB-CONFIG
|
|
9
9
|
- `scripts`: S-TEST, S-ALL-C, S-INT-C, S-UNIT-C, S-COOKBOOKS, S-COOK-C, S-HOST, S-HOOKS
|
|
10
10
|
- `tooling`: S-SCRIPTS
|
|
11
|
-
- `config`: C-IDENTITY, C-SERVICE, C-OPS, C-CONTRACT, C-CONNECTORS, C-SERVICE-DEAD, C-ENV, G-SHARED-ENV, C-ENV-READS
|
|
11
|
+
- `config`: C-IDENTITY, C-SERVICE, C-OPS, C-CONTRACT, C-CONNECTORS, C-CI-CONNECTOR-ENV, C-SERVICE-DEAD, C-ENV, G-SHARED-ENV, C-ENV-READS
|
|
12
12
|
- `runtime`: R-NODE, R-MEM, R-PID1, R-USER, R-PORTS-DEV, R-PORTS
|
|
13
13
|
- `db`: D-DB-CONSISTENT, D-DB-COLLATION, D-DB-PACKAGE, D-DB-NAMING, D-DB-README, D-DB-ACCOUNT, D-DB-CI-ACCOUNT, D-DB-HEADERS
|
|
14
14
|
- `docs`: C-LINT, D-PORT, D-NPM, D-SCRIPT, D-RETIRED, D-HEADER, D-LINT
|
|
@@ -17,7 +17,7 @@ Paths a duty owns:
|
|
|
17
17
|
|
|
18
18
|
- `.dockerignore`: F-DOCKERIGNORE (from `./node_modules/@onlineapps/conn-orch-validator/templates/business-service/gitignore`)
|
|
19
19
|
- `.gitignore`: F-GITIGNORE (from `./node_modules/@onlineapps/conn-orch-validator/templates/business-service/gitignore`)
|
|
20
|
-
- `.gitlab-ci.yml`: G-CI (from `./node_modules/@onlineapps/conn-orch-validator/templates/business-service/.gitlab-ci.yml`), D-DB-CI-ACCOUNT
|
|
20
|
+
- `.gitlab-ci.yml`: G-CI (from `./node_modules/@onlineapps/conn-orch-validator/templates/business-service/.gitlab-ci.yml`), C-CI-CONNECTOR-ENV, D-DB-CI-ACCOUNT
|
|
21
21
|
- `README.md`: G-README (from `./node_modules/@onlineapps/conn-orch-validator/manifests/biz-service.manifest.json`)
|
|
22
22
|
- `config/biz-docs-lint.tree.json`: C-LINT
|
|
23
23
|
- `config/env-templates`: C-ENV, D-DB-ACCOUNT
|
|
@@ -84,6 +84,7 @@ config/
|
|
|
84
84
|
├── env-templates/ # Versioned env templates
|
|
85
85
|
└── env-active/ # Runtime env (gitignored)
|
|
86
86
|
src/
|
|
87
|
+
├── config/index.js # ConfigLoader.loadAll — the one place bootstrap() reads the configuration
|
|
87
88
|
└── handlers/v3/ # One module per operation; the handler: field points here
|
|
88
89
|
tests/
|
|
89
90
|
├── unit/
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# api/config/shared-env.json, then run `npx oa-sync-template shared-env --target <dir>`.
|
|
3
3
|
|
|
4
4
|
# The runtime environment name every platform library resolves through its own runtime-config schema; without it a library cannot say which environment it is reporting from.
|
|
5
|
+
# @per-machine — the environment a process runs in is a property of the box, not of the platform: development on a developer machine, production on a deployed one, so the template value is the dev value and a deployed box differs from it on purpose
|
|
5
6
|
NODE_ENV=development
|
|
6
7
|
|
|
7
8
|
# The log level an infrastructure service's config/logging.json resolves through ${LOG_LEVEL}; one platform value so a stack does not log at a different level in every service. In the business chain the key is carried, not read: no library in the business chain reads it - @onlineapps/monitoring-core reads no environment variable at all (principle 1) and takes its level from the config the service hands it - so setting it on a business service changes nothing. It reaches every bearer because the shared key set is ONE file whose every copy is byte-identical to the platform template (confirmation biz-service-manifest 003 §18), never because each bearer reads every key.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration loader
|
|
3
|
+
* Centralized configuration management
|
|
4
|
+
*
|
|
5
|
+
* Uses ConfigLoader utility for consistent, traceable config loading
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const { ConfigLoader } = require('@onlineapps/service-wrapper');
|
|
10
|
+
|
|
11
|
+
// Load all configurations using unified loader (explicit basePath + env)
|
|
12
|
+
const basePath = path.join(__dirname, '..', '..');
|
|
13
|
+
const config = ConfigLoader.loadAll({ basePath, env: process.env });
|
|
14
|
+
|
|
15
|
+
module.exports = config;
|
package/TESTING_STRATEGY.md
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
# Integration Testing Strategy Analysis
|
|
2
|
-
|
|
3
|
-
## 📋 Testing Scope Analysis
|
|
4
|
-
|
|
5
|
-
### ✅ DÁVÁ SMYSL - Rozsáhlé E2E testy
|
|
6
|
-
1. **ServiceWrapper E2E testy**
|
|
7
|
-
- Kompletní flow: MQ → Wrapper → HTTP → Response
|
|
8
|
-
- Multi-step workflows
|
|
9
|
-
- Error handling & retry logic
|
|
10
|
-
- Circuit breaker testing
|
|
11
|
-
- Performance & load testing
|
|
12
|
-
|
|
13
|
-
2. **conn-orch-validator package**
|
|
14
|
-
- Orchestruje všechny E2E testy
|
|
15
|
-
- Testuje kompletní workflow scenarios
|
|
16
|
-
- Simuluje reálné use cases
|
|
17
|
-
|
|
18
|
-
3. **Infrastrukturní uzly**
|
|
19
|
-
- RabbitMQ integration (message flow, dead letter queues)
|
|
20
|
-
- Redis integration (caching, session management)
|
|
21
|
-
- Registry integration (service discovery, health checks)
|
|
22
|
-
|
|
23
|
-
### ❓ ZVÁŽIT - Unit testy jednotlivých balíčků
|
|
24
|
-
- Jednotlivé connectors už mají unit testy ✅
|
|
25
|
-
- Integration testy jednotlivých connectors možná redundantní
|
|
26
|
-
- Lepší je testovat je jako součást větších E2E testů
|
|
27
|
-
|
|
28
|
-
## 🎯 Priority Testing Areas
|
|
29
|
-
|
|
30
|
-
### 1. Critical Path Testing
|
|
31
|
-
- **hello-service complete flow** ✅ DONE
|
|
32
|
-
- HTTP server running
|
|
33
|
-
- MQ wrapper listening
|
|
34
|
-
- Message processing working
|
|
35
|
-
|
|
36
|
-
### 2. ServiceWrapper Integration
|
|
37
|
-
- Registry registration/heartbeat
|
|
38
|
-
- MQ message consumption
|
|
39
|
-
- HTTP service invocation
|
|
40
|
-
- Response handling
|
|
41
|
-
- Error scenarios
|
|
42
|
-
|
|
43
|
-
### 3. Workflow Orchestration
|
|
44
|
-
- Multi-step workflows
|
|
45
|
-
- Conditional branching
|
|
46
|
-
- Parallel execution
|
|
47
|
-
- Error recovery
|
|
48
|
-
|
|
49
|
-
### 4. Infrastructure Resilience
|
|
50
|
-
- Service failures & recovery
|
|
51
|
-
- Network partitions
|
|
52
|
-
- Message queue failures
|
|
53
|
-
- Cache misses
|
|
54
|
-
|
|
55
|
-
## 📝 Implementation Progress
|
|
56
|
-
|
|
57
|
-
### Completed
|
|
58
|
-
- ✅ hello-service hybrid architecture implemented
|
|
59
|
-
- ✅ MQ wrapper successfully starts and listens
|
|
60
|
-
- ✅ Fixed all initialization issues:
|
|
61
|
-
- ServiceWrapper consume() parameter order
|
|
62
|
-
- RegistryClient queueManager.init()
|
|
63
|
-
- Redis cache singleton issues
|
|
64
|
-
- LoggerConnector initialization in ServiceWrapper
|
|
65
|
-
- ✅ ServiceWrapper E2E test suite created
|
|
66
|
-
- ✅ Test infrastructure setup (Jest, Express for mock services)
|
|
67
|
-
- ✅ Fixed message parsing from AMQP buffer to JSON
|
|
68
|
-
- ✅ Created test-mq-flow.js for testing message flow (script removed 2026-08-26 — no consumer)
|
|
69
|
-
- ✅ Fixed cookbook validation (added type field to steps)
|
|
70
|
-
|
|
71
|
-
### In Progress
|
|
72
|
-
- 🔧 Verifying complete E2E message flow
|
|
73
|
-
- 🔧 Testing workflow orchestration with real services
|
|
74
|
-
|
|
75
|
-
### TODO
|
|
76
|
-
- [ ] Implement response queue listener for capturing results
|
|
77
|
-
- [ ] Complete E2E test suite with all scenarios
|
|
78
|
-
- [ ] Load testing scenarios
|
|
79
|
-
- [ ] Chaos engineering tests
|
|
80
|
-
- [ ] Performance benchmarks
|
|
81
|
-
|
|
82
|
-
## 🚀 Next Session Actions
|
|
83
|
-
|
|
84
|
-
1. **Complete conn-orch-validator package**
|
|
85
|
-
2. **Run full E2E test suite**
|
|
86
|
-
3. **Document test results**
|
|
87
|
-
4. **Fix any issues found**
|
|
88
|
-
5. **Performance optimization based on test results**
|
|
89
|
-
|
|
90
|
-
---
|
|
91
|
-
*Last updated: 2025-09-26*
|
|
92
|
-
*Session ending at 5hr limit*
|
package/jest.config.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
process.env.TESTING_TENANT_ID = process.env.TESTING_TENANT_ID || '99';
|
|
4
|
-
process.env.TESTING_WORKSPACE_ID = process.env.TESTING_WORKSPACE_ID || '200';
|
|
5
|
-
|
|
6
|
-
module.exports = {
|
|
7
|
-
testEnvironment: 'node',
|
|
8
|
-
coverageDirectory: 'coverage',
|
|
9
|
-
collectCoverageFrom: [
|
|
10
|
-
'src/**/*.js',
|
|
11
|
-
'!src/index.js'
|
|
12
|
-
],
|
|
13
|
-
testMatch: [
|
|
14
|
-
'**/tests/**/*.test.js'
|
|
15
|
-
],
|
|
16
|
-
|
|
17
|
-
// tests/fixtures/manifest/** holds SIMULATED CHECKOUTS that the manifest
|
|
18
|
-
// suites read from disk as data, never require. Jest's haste map indexes every
|
|
19
|
-
// package.json it crawls, so the three fixture workspaces that each carry the
|
|
20
|
-
// service `biz-alpha` read as three modules of one name and the crawl prints
|
|
21
|
-
// "Haste module naming collision" on stdout. The reason the fixtures keep
|
|
22
|
-
// their names, and why the pattern stops at `manifest/` rather than covering
|
|
23
|
-
// tests/fixtures/ whole, is written once — in the repository's jest.config.js,
|
|
24
|
-
// beside the same string. This config owns the same exclusion for its own map,
|
|
25
|
-
// because rootDir here is the package.
|
|
26
|
-
modulePathIgnorePatterns: ['/tests/fixtures/manifest/'],
|
|
27
|
-
coverageThreshold: {
|
|
28
|
-
global: {
|
|
29
|
-
branches: 80,
|
|
30
|
-
functions: 80,
|
|
31
|
-
lines: 80,
|
|
32
|
-
statements: 80
|
|
33
|
-
}
|
|
34
|
-
},
|
|
35
|
-
verbose: true,
|
|
36
|
-
testTimeout: 10000
|
|
37
|
-
};
|