@onlineapps/conn-orch-validator 7.0.0 → 8.1.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 +2582 -2
- package/README.md +1038 -4
- package/docs/DESIGN.md +3 -1
- package/manifests/biz-service.manifest.json +658 -0
- package/manifests/library.manifest.json +324 -0
- package/package.json +12 -6
- package/src/CookbookTestRunner.js +408 -101
- package/src/CookbookTestUtils.js +7 -8
- package/src/ServiceReadinessValidator.js +10 -35
- package/src/ValidationOrchestrator.js +219 -71
- package/src/cli/biz-ci-gate.js +176 -33
- package/src/cli/oa-lint-scripts.js +221 -0
- package/src/cli/oa-sync-template.js +1020 -0
- package/src/cli/oa-validate.js +474 -0
- package/src/helpers/README.md +2 -1
- package/src/helpers/createServiceReadinessTests.js +60 -4
- package/src/index.js +33 -3
- package/src/lint/scripts/lintScripts.js +298 -0
- package/src/manifest/checks/composeRunnerBlock.js +222 -0
- package/src/manifest/checks/composeShape.js +165 -0
- package/src/manifest/checks/contractBridge.js +181 -0
- package/src/manifest/checks/discoveryOrphan.js +50 -0
- package/src/manifest/checks/docsLintBridge.js +553 -0
- package/src/manifest/checks/fileAbsent.js +35 -0
- package/src/manifest/checks/gitTracked.js +204 -0
- package/src/manifest/checks/index.js +111 -0
- package/src/manifest/checks/libraryContext.js +226 -0
- package/src/manifest/checks/libraryDocs.js +75 -0
- package/src/manifest/checks/libraryPackage.js +272 -0
- package/src/manifest/checks/librarySource.js +274 -0
- package/src/manifest/checks/libraryTests.js +121 -0
- package/src/manifest/checks/libraryWorkspace.js +293 -0
- package/src/manifest/checks/readmeRegion.js +135 -0
- package/src/manifest/checks/scriptHeaders.js +79 -0
- package/src/manifest/checks/serviceConfig.js +390 -0
- package/src/manifest/checks/serviceConnectors.js +81 -0
- package/src/manifest/checks/serviceDb.js +388 -0
- package/src/manifest/checks/serviceFiles.js +754 -0
- package/src/manifest/checks/serviceIdentityRows.js +351 -0
- package/src/manifest/checks/serviceRuntime.js +295 -0
- package/src/manifest/checks/serviceScripts.js +213 -0
- package/src/manifest/deployabilitySignal.js +121 -0
- package/src/manifest/discovery.js +386 -0
- package/src/manifest/loadManifest.js +62 -0
- package/src/manifest/manifestShape.js +446 -0
- package/src/manifest/report.js +245 -0
- package/src/manifest/runManifest.js +449 -0
- package/src/manifest/serviceIdentity.js +140 -0
- package/src/manifest/walk.js +74 -0
- package/src/manifest/workspaceRoot.js +242 -0
- package/src/mocks/MockMQClient.js +13 -30
- package/src/mocks/MockRegistry.js +4 -2
- package/src/mocks/MockStorage.js +4 -2
- package/src/sync/docsRegion.js +463 -0
- package/src/sync/generatedRegion.js +228 -0
- package/src/sync/readmeLocation.js +182 -0
- package/src/sync/readmePointer.js +477 -0
- package/src/sync/serviceTemplate.js +583 -0
- package/src/sync/sharedEnv.js +162 -0
- package/src/sync/uniformFiles.js +474 -0
- package/src/utils/bizCiGateContract.js +131 -7
- package/src/utils/connectorContract.js +97 -7
- package/src/utils/cookbookFormat.js +81 -40
- package/src/utils/deployContract.js +140 -9
- package/src/utils/envContract.js +57 -1
- package/src/utils/handlerRef.js +181 -0
- package/src/utils/installContract.js +287 -41
- package/src/utils/libCompat.js +29 -7
- package/src/utils/migrationOrder.js +163 -0
- package/src/utils/preValidation.js +20 -7
- package/src/utils/setupDatabase.js +194 -13
- package/src/utils/testCoverageContract.js +539 -0
- package/src/utils/testNamespace.js +247 -23
- package/src/utils/throwawaySchema.js +207 -0
- package/src/validators/ServiceStructureValidator.js +2 -1
- package/templates/business-service/.dockerignore +42 -0
- package/templates/business-service/.gitlab-ci.yml +409 -0
- package/templates/business-service/Dockerfile +27 -0
- package/templates/business-service/README.md +213 -0
- package/templates/business-service/config/biz-docs-lint.tree.json +10 -0
- package/templates/business-service/config/env-templates/__SERVICE_NAME__.env +22 -0
- package/templates/business-service/config/env-templates/shared.env +65 -0
- package/templates/business-service/config/service/config.json +14 -0
- package/templates/business-service/config/service/integration-contract.json +12 -0
- package/templates/business-service/config/service/operations.json +41 -0
- package/templates/business-service/docker-compose.production.yml +60 -0
- package/templates/business-service/docker-compose.yml +93 -0
- package/templates/business-service/docs/80-setup/INSTALL.md +123 -0
- package/templates/business-service/docs/80-setup/PLATFORM_MATRIX.md +65 -0
- package/templates/business-service/docs/80-setup/README.md +18 -0
- package/templates/business-service/docs/80-setup/VALIDATION.md +78 -0
- package/templates/business-service/docs/README.md +18 -0
- package/templates/business-service/gitignore +42 -0
- package/templates/business-service/index.js +10 -0
- package/templates/business-service/init.sh +54 -0
- package/templates/business-service/jest.config.js +6 -0
- package/templates/business-service/package.json.template +31 -0
- package/templates/business-service/scripts/verify-deploy-uniform.sh +180 -0
- package/templates/business-service/src/handlers/v3/echo.js +39 -0
- package/templates/business-service/tests/cookbooks/echo.json +36 -0
- package/templates/business-service/tests/unit/handler.test.js +78 -0
- package/src/WorkflowTestRunner.js +0 -402
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The bridge from a manifest row to a checker this package ALREADY runs.
|
|
5
|
+
*
|
|
6
|
+
* Confirmation `biz-service-manifest` 004 point 4: a rule that exists is cited,
|
|
7
|
+
* never restated. So none of the checks below decides anything — each one calls
|
|
8
|
+
* the module that owns the rule, keeps the violations of the requirement its row
|
|
9
|
+
* names, and maps them into the two columns a finding adds (`where`, `what`).
|
|
10
|
+
* The row's `severity`, `owner`, `fix` and `doc` are the row's own, and
|
|
11
|
+
* `runManifest` puts them on the table.
|
|
12
|
+
*
|
|
13
|
+
* Three checkers, one shape (lead decision `api/shared/TODO.md` §0.2b-8 point 3):
|
|
14
|
+
*
|
|
15
|
+
* `deploy-contract` → utils/deployContract.js (R1, R4, … per row)
|
|
16
|
+
* `install-contract` → utils/installContract.js (the ids it exports)
|
|
17
|
+
* `env-contract` → utils/envContract.js (completeness; it has no requirement ids)
|
|
18
|
+
*
|
|
19
|
+
* Why a row must name its requirement rather than take the whole result: a
|
|
20
|
+
* checker raises several unrelated rules at once, and a table row is one rule
|
|
21
|
+
* with one owner and one fix. R2 (the deploy sequence) and R8 (tenant identity)
|
|
22
|
+
* are not rows of this uniform, and a row that swallowed them would report a
|
|
23
|
+
* finding no `fix` sentence of its own answers.
|
|
24
|
+
*
|
|
25
|
+
* @see api/docs/governance/confirmations/biz-service-manifest.md § Confirmation 20260909-biz-service-manifest-004
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
const path = require('path');
|
|
29
|
+
|
|
30
|
+
const { verifyDeployContract, DEPLOY_CONTRACT_REQUIREMENTS } = require('../../utils/deployContract');
|
|
31
|
+
const { verifyInstallContract, INSTALL_CONTRACT_REQUIREMENTS } = require('../../utils/installContract');
|
|
32
|
+
const { verifyEnvCompleteness } = require('../../utils/envContract');
|
|
33
|
+
const { loadAndValidateIntegrationContract } = require('../../utils/bizCiGateContract');
|
|
34
|
+
|
|
35
|
+
/** Where every service declares what it integrates with; three of these rows read it. */
|
|
36
|
+
const CONTRACT_PATH = 'config/service/integration-contract.json';
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* One violation as one table cell: the first line, without the `Fix:` sentence
|
|
40
|
+
* the row carries in its own column.
|
|
41
|
+
*
|
|
42
|
+
* @param {string} requirement the requirement id, kept so the table names it
|
|
43
|
+
* @param {string} message the checker's message, as it writes it
|
|
44
|
+
* @returns {string}
|
|
45
|
+
*/
|
|
46
|
+
function summarise(requirement, message) {
|
|
47
|
+
const firstLine = String(message).split('\n')[0];
|
|
48
|
+
const withoutFix = firstLine.split(' Fix:')[0];
|
|
49
|
+
return `${requirement}: ${withoutFix.trim()}`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Refuse a row whose requirement the checker does not raise. Silence would be
|
|
54
|
+
* indistinguishable from a clean repository, which is the failure mode this
|
|
55
|
+
* whole subject exists to end.
|
|
56
|
+
*
|
|
57
|
+
* @param {object} row the manifest row
|
|
58
|
+
* @param {ReadonlyArray<string>} known the ids the checker raises
|
|
59
|
+
* @param {string} module the module that owns them, for the message
|
|
60
|
+
*/
|
|
61
|
+
function assertKnownRequirement(row, known, module) {
|
|
62
|
+
if (known.includes(row.requirement)) return;
|
|
63
|
+
throw new Error(`[ManifestBridge] Row ${row.id} names requirement "${row.requirement}", which `
|
|
64
|
+
+ `${module} does not raise - Expected one of ${known.join(', ')}. `
|
|
65
|
+
+ 'Fix: correct the row, or add the requirement to that module first; a row that cites a '
|
|
66
|
+
+ 'requirement nobody raises reports nothing and reads as a pass.');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The service's normalized integration contract, or the reason it is unusable.
|
|
71
|
+
*
|
|
72
|
+
* A row that needs the contract and cannot read it says THAT — it never returns
|
|
73
|
+
* an empty list, which a reader would take for a clean repository. Row
|
|
74
|
+
* `C-CONTRACT` names the missing file with its own fix; this second sentence is
|
|
75
|
+
* about the requirement that could not be decided because of it.
|
|
76
|
+
*
|
|
77
|
+
* @param {string} serviceRoot repository root
|
|
78
|
+
* @returns {{ contract: object }|{ problem: string }}
|
|
79
|
+
*/
|
|
80
|
+
function contractOf(serviceRoot) {
|
|
81
|
+
try {
|
|
82
|
+
return { contract: loadAndValidateIntegrationContract(serviceRoot).contract };
|
|
83
|
+
} catch (error) {
|
|
84
|
+
return { problem: String(error.message).split('\n')[0].trim() };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @param {object} row the manifest row
|
|
90
|
+
* @param {string} problem why the contract could not be read
|
|
91
|
+
* @returns {Array<{where: string, what: string}>}
|
|
92
|
+
*/
|
|
93
|
+
const contractUnreadable = (row, problem) => [{
|
|
94
|
+
where: CONTRACT_PATH,
|
|
95
|
+
what: `${row.requirement} could not be decided — the integration contract is unreadable: ${problem}`
|
|
96
|
+
}];
|
|
97
|
+
|
|
98
|
+
const deployContract = Object.freeze({
|
|
99
|
+
scope: 'service',
|
|
100
|
+
requires: Object.freeze(['requirement', 'path']),
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* @param {{ row: object, serviceRoot: string }} params
|
|
104
|
+
* @returns {Array<{where: string, what: string}>}
|
|
105
|
+
*/
|
|
106
|
+
run({ row, serviceRoot }) {
|
|
107
|
+
assertKnownRequirement(row, DEPLOY_CONTRACT_REQUIREMENTS, 'utils/deployContract.js');
|
|
108
|
+
|
|
109
|
+
return verifyDeployContract(serviceRoot).violations
|
|
110
|
+
.filter((violation) => violation.requirement === row.requirement)
|
|
111
|
+
.map((violation) => ({
|
|
112
|
+
where: row.path,
|
|
113
|
+
what: summarise(violation.requirement, violation.message)
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const installContract = Object.freeze({
|
|
119
|
+
scope: 'service',
|
|
120
|
+
requires: Object.freeze(['requirement', 'path']),
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* @param {{ row: object, serviceRoot: string }} params
|
|
124
|
+
* @returns {Array<{where: string, what: string}>}
|
|
125
|
+
*/
|
|
126
|
+
run({ row, serviceRoot }) {
|
|
127
|
+
assertKnownRequirement(row, INSTALL_CONTRACT_REQUIREMENTS, 'utils/installContract.js');
|
|
128
|
+
|
|
129
|
+
const loaded = contractOf(serviceRoot);
|
|
130
|
+
if (loaded.problem !== undefined) return contractUnreadable(row, loaded.problem);
|
|
131
|
+
|
|
132
|
+
// Whether the SQL half applies at all is the contract's own decision, which
|
|
133
|
+
// is why the database block is handed over rather than derived here
|
|
134
|
+
// (installContract.js header: one fact, one owner).
|
|
135
|
+
return verifyInstallContract(serviceRoot, loaded.contract.database).violations
|
|
136
|
+
.filter((violation) => violation.requirement === row.requirement)
|
|
137
|
+
.map((violation) => ({
|
|
138
|
+
where: row.path,
|
|
139
|
+
what: summarise(violation.requirement, violation.message)
|
|
140
|
+
}));
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const envContract = Object.freeze({
|
|
145
|
+
scope: 'service',
|
|
146
|
+
requires: Object.freeze([]),
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Completeness (F16): every environment name the repository visibly reads is
|
|
150
|
+
* declared in the contract's `env` block or covered by the platform env. The
|
|
151
|
+
* rule, the scan and its blind spots are `utils/envContract.js`; this row
|
|
152
|
+
* places its violations on the table.
|
|
153
|
+
*
|
|
154
|
+
* @param {{ row: object, serviceRoot: string }} params
|
|
155
|
+
* @returns {Array<{where: string, what: string}>}
|
|
156
|
+
*/
|
|
157
|
+
run({ row, serviceRoot }) {
|
|
158
|
+
const loaded = contractOf(serviceRoot);
|
|
159
|
+
if (loaded.problem !== undefined) {
|
|
160
|
+
return [{
|
|
161
|
+
where: CONTRACT_PATH,
|
|
162
|
+
what: `the environment contract could not be decided — the integration contract is unreadable: ${loaded.problem}`
|
|
163
|
+
}];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return verifyEnvCompleteness({ serviceRoot: path.resolve(serviceRoot), contract: loaded.contract }).violations
|
|
167
|
+
.map((violation) => ({
|
|
168
|
+
where: violation.sources[0],
|
|
169
|
+
what: `${violation.name} is read here, and the contract neither declares it `
|
|
170
|
+
+ 'nor is it covered by the platform env'
|
|
171
|
+
}));
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
module.exports = {
|
|
176
|
+
checks: [
|
|
177
|
+
{ name: 'deploy-contract', check: deployContract },
|
|
178
|
+
{ name: 'install-contract', check: installContract },
|
|
179
|
+
{ name: 'env-contract', check: envContract }
|
|
180
|
+
]
|
|
181
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `discovery-orphan` — everything the discovery pattern's container holds is a
|
|
5
|
+
* bearer of some uniform, and its SSOT knows it.
|
|
6
|
+
*
|
|
7
|
+
* Completeness is discovery (confirmation biz-service-manifest 005 point 1): no
|
|
8
|
+
* declaration is read, because a bearer's uniform is its location. What the
|
|
9
|
+
* check compares is the disk against the file that owns the names — resolved at
|
|
10
|
+
* run time, never copied into the manifest (004 point 2).
|
|
11
|
+
*
|
|
12
|
+
* The check is workspace-scoped: inside a service container the SSOT is not
|
|
13
|
+
* present, and the row is then reported NOT RUN rather than passed. The same is
|
|
14
|
+
* true one level down, in a checkout that carries `api/` and not `api_biz/`: an
|
|
15
|
+
* absent container holds no directory, and "every directory is declared" would
|
|
16
|
+
* be true of nothing (`automation-gates.md` §5), so the row declares the
|
|
17
|
+
* container it walks and the runner stops it before it looks.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const { discoverBearers, rootOfPattern } = require('../discovery');
|
|
21
|
+
|
|
22
|
+
const check = Object.freeze({
|
|
23
|
+
scope: 'workspace',
|
|
24
|
+
requires: Object.freeze([]),
|
|
25
|
+
|
|
26
|
+
/** The directory the pattern walks; absent, the answer would be an empty disk. */
|
|
27
|
+
requiresSiblings({ block }) {
|
|
28
|
+
return [rootOfPattern(block.pattern)];
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
/** What the run prints instead of a verdict when the workspace is unreachable. */
|
|
32
|
+
describeNotRun({ block }) {
|
|
33
|
+
const owner = block && block.from ? block.from.path : 'the referenced SSOT';
|
|
34
|
+
return `the workspace root is not reachable, so ${owner} cannot be read`;
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {{ block: object, workspaceRoot: string }} params
|
|
39
|
+
* @returns {Array<{where: string, what: string}>}
|
|
40
|
+
*/
|
|
41
|
+
run({ block, workspaceRoot }) {
|
|
42
|
+
const { orphans } = discoverBearers({ block, workspaceRoot });
|
|
43
|
+
return orphans.map((orphan) => ({
|
|
44
|
+
where: orphan.relativeDir,
|
|
45
|
+
what: `wears no uniform — ${orphan.reason}`
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
module.exports = { name: 'discovery-orphan', check };
|