@onlineapps/conn-orch-validator 3.3.2 โ†’ 4.0.1

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.
@@ -3,6 +3,7 @@
3
3
 
4
4
  const path = require('path');
5
5
  const fs = require('fs');
6
+ const { spawnSync } = require('child_process');
6
7
  const {
7
8
  DEFAULT_CONTRACT_RELATIVE_PATH,
8
9
  loadAndValidateIntegrationContract,
@@ -14,6 +15,20 @@ const {
14
15
  runConnectorSetup,
15
16
  writeJsonFile,
16
17
  } = require('../utils/bizCiGateContract');
18
+ const {
19
+ DEFAULT_REPORT_DIR_RELATIVE_PATH,
20
+ runIntegrationSuite,
21
+ } = require('../utils/integrationRun');
22
+ const { verifyDeployContract } = require('../utils/deployContract');
23
+ const { verifyInstallContract } = require('../utils/installContract');
24
+ const {
25
+ verifyEnvCompleteness,
26
+ ENV_SCAN_SCOPE,
27
+ ENV_SCAN_BLIND_SPOTS,
28
+ } = require('../utils/envContract');
29
+ const { runPreValidation } = require('../utils/preValidation');
30
+ const { checkLibCompat, loadLibrarySet } = require('../utils/libCompat');
31
+ const { buildSchema } = require('../utils/setupDatabase');
17
32
 
18
33
  function parseArgs(argv) {
19
34
  const parsed = {
@@ -47,10 +62,31 @@ Usage:
47
62
  oa-biz-ci-gate <command> [--service-root <path>] [--contract <path>] [options]
48
63
 
49
64
  Commands:
50
- verify-contract Validate integration-contract.json schema and connector flags.
65
+ verify-contract Validate integration-contract.json schema, connector flags,
66
+ the deploy contract (R1-R7), the installation contract AND
67
+ the env contract.
68
+ verify-env-contract Env contract only: every environment variable the service
69
+ repository visibly reads is declared in the contract's env
70
+ block or covered by a config placeholder / required connector.
71
+ Prints its measurement boundary on every run.
72
+ verify-deploy-contract Deploy contract only (R1-R7): image pin, deploy sequence,
73
+ Node major agreement, no published ports, full commit SHA,
74
+ CI database engine.
75
+ verify-install-contract Installation contract only: the docs/setup package every repo
76
+ carries, plus the BASELINE/SEED SQL tree required of every repo
77
+ whose contract declares a database.
78
+ run-prevalidation Run the service's cookbooks offline against mocked infrastructure
79
+ and write conn-runtime/validation-proof.json. Replaces the
80
+ per-service scripts/run-pre-validation.js.
81
+ verify-lib-compat Compare @onlineapps/* pins against the platform library SSOT (R6).
51
82
  verify-integration-minimum Validate package scripts + fail on empty integration suite.
83
+ run-integration Run the integration suite (one runner process per test file)
84
+ and fail unless the tests actually EXECUTED and passed.
85
+ Counting files on disk cannot see a suite that was
86
+ skipped; this reads the runner's own JSON report.
52
87
  emit-ci-env Emit connector-aware CI environment exports.
53
88
  wait-connectors Wait for required connector TCP endpoints.
89
+ setup-db Build the schema from the declared migrations (contract database block).
54
90
  run-setup Run optional connector setup commands from contract.
55
91
  write-summary Write normalized integration signal artifact JSON.
56
92
 
@@ -59,6 +95,10 @@ Common options:
59
95
  --contract <path> Contract path (absolute or relative to service root).
60
96
  Default: ${DEFAULT_CONTRACT_RELATIVE_PATH}
61
97
 
98
+ verify-lib-compat options:
99
+ --libraries <path|url> Library SSOT source. Default: $LIBRARIES_SSOT_URL
100
+ Auth for an https source: $LIBRARIES_SSOT_TOKEN
101
+
62
102
  emit-ci-env options:
63
103
  --format <shell|dotenv|json> Output format. Default: shell
64
104
  --output <path> Optional output file path
@@ -71,8 +111,10 @@ write-summary options:
71
111
  --output <path> Summary artifact path. Default: <service-root>/ci/integration-signal.json
72
112
  --gate-verdict <pass|fail> Override gate verdict
73
113
  --gate-reason <text> Override gate reason
74
- --unit-executed <number> Executed unit suite count
75
- --integration-executed <number> Executed integration suite count
114
+
115
+ Executed test counts are read from ci/integration-run.json, written by
116
+ run-integration. Without that artifact the summary reports them as unknown โ€”
117
+ it never substitutes a count of files on disk.
76
118
  `);
77
119
  }
78
120
 
@@ -84,11 +126,167 @@ function resolveContractOptions(options) {
84
126
  return {
85
127
  serviceRoot: options['service-root'] || process.cwd(),
86
128
  contractPath: options.contract || undefined,
129
+ // Undefined here means loadLibrarySet falls back to LIBRARIES_SSOT_URL and
130
+ // fails fast if that is unset too โ€” never a silent skip.
131
+ libraries: options.libraries || undefined,
87
132
  };
88
133
  }
89
134
 
90
- function runVerifyContract(options) {
135
+ /**
136
+ * Render a deploy-contract result and signal whether it passed.
137
+ * The rules live in utils/deployContract; this only presents them.
138
+ */
139
+ function reportDeployContract(result) {
140
+ if (result.ok) {
141
+ process.stdout.write(`[BizCiGate] OK deploy-contract (${result.service}) โ€” R1-R7 satisfied\n`);
142
+ return true;
143
+ }
144
+ for (const violation of result.violations) {
145
+ process.stderr.write(`[BizCiGate] FAIL ${result.service} โ€” ${violation.requirement} โ€” ${violation.message}\n`);
146
+ }
147
+ process.stderr.write(`[BizCiGate] FAIL ${result.service} โ€” ${result.violations.length} deploy-contract violation(s)\n`);
148
+ return false;
149
+ }
150
+
151
+ /**
152
+ * Render the env-contract completeness result and signal whether it passed.
153
+ *
154
+ * The scope line is printed on every run, pass or fail. A gate that reports
155
+ * "OK" without saying what it looked at invites the reader to assume it looked
156
+ * at everything; this one measures literal reads in the service repository and
157
+ * says so out loud, including how many dynamic accesses it could not resolve.
158
+ */
159
+ function reportEnvContract(serviceName, contract, result) {
160
+ process.stdout.write(`[BizCiGate] env-contract scope: ${ENV_SCAN_SCOPE}\n`);
161
+ process.stdout.write(`[BizCiGate] env-contract NOT measured: ${ENV_SCAN_BLIND_SPOTS}`
162
+ + ` โ€” ${result.dynamicSites.length} dynamic access site(s) in this repo\n`);
163
+
164
+ if (result.ok) {
165
+ const adoption = contract.env ? '' : ' โ€” no "env" block declared yet (F16 adoption pending)';
166
+ process.stdout.write(`[BizCiGate] OK env-contract (${serviceName}) โ€” ${result.readCount} name(s) read, `
167
+ + `${result.declaredCount} declared, ${result.coveredCount} covered${adoption}\n`);
168
+ return true;
169
+ }
170
+
171
+ for (const violation of result.violations) {
172
+ process.stderr.write(`[BizCiGate] FAIL ${serviceName} โ€” ENV_COMPLETENESS โ€” `
173
+ + `"${violation.name}" is read in ${violation.sources.join(', ')} but is neither declared in `
174
+ + 'config/service/integration-contract.json (env.required / env.optional) nor covered by a '
175
+ + '${...} config placeholder or a required connector.\n'
176
+ + ` Fix: add { "name": "${violation.name}", "why": "<what the service does with it>" } `
177
+ + 'to env.optional (or env.required if the service cannot start without it), or delete the read.\n');
178
+ }
179
+ process.stderr.write(`[BizCiGate] FAIL ${serviceName} โ€” ${result.violations.length} env-contract violation(s)\n`);
180
+ return false;
181
+ }
182
+
183
+ function reportInstallContract(result) {
184
+ if (result.ok) {
185
+ const half = result.databaseChecked ? 'docs + SQL package' : 'docs (no database declared)';
186
+ process.stdout.write(`[BizCiGate] OK install-contract (${result.service}) โ€” ${half}\n`);
187
+ return true;
188
+ }
189
+ for (const violation of result.violations) {
190
+ process.stderr.write(`[BizCiGate] FAIL ${result.service} โ€” ${violation.requirement} โ€” ${violation.message}\n`);
191
+ }
192
+ process.stderr.write(`[BizCiGate] FAIL ${result.service} โ€” ${result.violations.length} install-contract violation(s)\n`);
193
+ return false;
194
+ }
195
+
196
+ /**
197
+ * Build the schema the contract declares. One implementation for every service:
198
+ * the repo declares WHAT, this decides HOW (docs/biz/00-model/uniformity-principle.md).
199
+ */
200
+ function runSetupDb(options) {
91
201
  const contractInfo = loadAndValidateIntegrationContract(options.serviceRoot, options.contractPath);
202
+ const database = contractInfo.contract.database;
203
+
204
+ if (!database) {
205
+ // Skipping is explicit and visible โ€” a service with no database says so by
206
+ // omitting the block, and that is not the same as a failure to find one.
207
+ process.stdout.write('[BizCiGate] OK setup-db โ€” no database declared, nothing to build\n');
208
+ return;
209
+ }
210
+
211
+ const result = buildSchema({
212
+ serviceRoot: contractInfo.serviceRoot,
213
+ database,
214
+ connection: {
215
+ host: process.env.DB_HOST,
216
+ port: Number.parseInt(process.env.DB_PORT || '3306', 10),
217
+ user: process.env.DB_USER,
218
+ password: process.env.DB_PASSWORD ?? '',
219
+ requireTls: process.env.DB_REQUIRE_TLS === '1'
220
+ }
221
+ });
222
+
223
+ process.stdout.write(`[BizCiGate] OK setup-db โ€” ${result.schema}: `
224
+ + `${result.migrationsApplied} migration(s), ${result.seedsApplied} seed(s)\n`);
225
+ }
226
+
227
+ async function runVerifyLibCompat(options) {
228
+ const pkgPath = path.join(options.serviceRoot, 'package.json');
229
+ if (!fs.existsSync(pkgPath)) {
230
+ process.stderr.write(`[BizCiGate] FAIL verify-lib-compat โ€” package.json not found at ${pkgPath}\n`);
231
+ process.exit(1);
232
+ }
233
+
234
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
235
+ const librarySet = await loadLibrarySet(options.libraries);
236
+ const result = checkLibCompat(pkg, librarySet);
237
+
238
+ if (result.notGated.length > 0) {
239
+ process.stdout.write(`[BizCiGate] not gated (no infra service installs these): ${result.notGated.join(', ')}\n`);
240
+ }
241
+ if (!result.ok) {
242
+ for (const violation of result.violations) {
243
+ process.stderr.write(`[BizCiGate] FAIL verify-lib-compat โ€” ${violation.message}\n`);
244
+ }
245
+ process.exit(1);
246
+ }
247
+ process.stdout.write(`[BizCiGate] OK verify-lib-compat โ€” ${result.gated.length} pin(s) match the platform library set\n`);
248
+ }
249
+
250
+ async function runVerifyContract(options) {
251
+ const contractInfo = loadAndValidateIntegrationContract(options.serviceRoot, options.contractPath);
252
+
253
+ // The deploy contract (R1-R7) is part of the same mandatory gate: a service
254
+ // whose compose, deploy sequence, Node major or database engine breaks the
255
+ // platform contract must not reach the build stage. Folding it in here means
256
+ // a version bump activates it โ€” no per-repo CI edit, nothing to forget.
257
+ if (!reportDeployContract(verifyDeployContract(contractInfo.serviceRoot))) {
258
+ process.exit(1);
259
+ }
260
+
261
+ // The installation contract joins the same gate on the same argument: a repo
262
+ // whose setup docs or SQL package are missing cannot be installed from its own
263
+ // instructions, and that must not reach the build stage. Folding it in also
264
+ // retires the eight drifting shell copies โ€” and with them the hand-maintained
265
+ // REPO_HAS_DB literal, since the database block already says whether the SQL
266
+ // half applies.
267
+ if (!reportInstallContract(
268
+ verifyInstallContract(contractInfo.serviceRoot, contractInfo.contract.database)
269
+ )) {
270
+ process.exit(1);
271
+ }
272
+
273
+ // The env contract joins on the same argument once more: a repo that reads a
274
+ // variable nobody declared has no way to tell a missing value from a dead one,
275
+ // and finds out at runtime โ€” biz-emailer read EMAIL_PROVIDER_LIVE_MODE that no
276
+ // template ever set, so live mail was silently off (defect D1).
277
+ if (!reportEnvContract(
278
+ contractInfo.contract.serviceName || path.basename(contractInfo.serviceRoot),
279
+ contractInfo.contract,
280
+ verifyEnvCompleteness({ serviceRoot: contractInfo.serviceRoot, contract: contractInfo.contract })
281
+ )) {
282
+ process.exit(1);
283
+ }
284
+
285
+ // R6 belongs to the same gate for the same reason. It needs LIBRARIES_SSOT_URL;
286
+ // an unconfigured gate fails loudly rather than passing, so the CI variable
287
+ // must exist before any repo pins a version carrying this check.
288
+ await runVerifyLibCompat({ ...options, serviceRoot: contractInfo.serviceRoot });
289
+
92
290
  process.stdout.write(`[BizCiGate] OK verify-contract\n`);
93
291
  process.stdout.write(`${JSON.stringify({
94
292
  serviceRoot: contractInfo.serviceRoot,
@@ -99,6 +297,87 @@ function runVerifyContract(options) {
99
297
  }, null, 2)}\n`);
100
298
  }
101
299
 
300
+
301
+ /**
302
+ * Resolve the service URL from the service's OWN config.
303
+ *
304
+ * ConfigLoader lives in @onlineapps/service-wrapper (L4) and this package is L3,
305
+ * so it is never a dependency here โ€” it is resolved from the service being
306
+ * validated, which is where it legitimately exists.
307
+ */
308
+ function resolveServiceUrl(serviceRoot) {
309
+ let wrapperPath;
310
+ try {
311
+ wrapperPath = require.resolve('@onlineapps/service-wrapper', { paths: [serviceRoot] });
312
+ } catch {
313
+ throw new Error('[PreValidation] Missing dependency - @onlineapps/service-wrapper is not '
314
+ + `installed in ${serviceRoot}. Fix: run npm install in the service repository.`);
315
+ }
316
+
317
+ const { ConfigLoader } = require(wrapperPath);
318
+ const config = ConfigLoader.loadAll({ basePath: serviceRoot, env: process.env });
319
+ return config.service.url;
320
+ }
321
+
322
+ async function runRunPreValidation(options) {
323
+ const outcome = await runPreValidation({
324
+ serviceRoot: path.resolve(options.serviceRoot),
325
+ serviceUrl: resolveServiceUrl(path.resolve(options.serviceRoot)),
326
+ RunnerClass: require('../CookbookTestRunner'),
327
+ ProofGeneratorClass: require('../validators/ValidationProofGenerator'),
328
+ logger: console
329
+ });
330
+
331
+ const { results } = outcome;
332
+ process.stdout.write(`[BizCiGate] cookbooks โ€” total ${results.total}, `
333
+ + `passed ${results.passed}, failed ${results.failed} (${results.duration}ms)\n`);
334
+
335
+ if (!outcome.ok) {
336
+ for (const step of outcome.failedSteps) {
337
+ process.stderr.write(`[BizCiGate] FAIL run-prevalidation โ€” ${step.id}: ${step.error}\n`);
338
+ for (const validationError of step.validationErrors) {
339
+ process.stderr.write(`[BizCiGate] * ${validationError}\n`);
340
+ }
341
+ }
342
+ process.stderr.write(`[BizCiGate] FAIL run-prevalidation โ€” ${results.failed} failed step(s); `
343
+ + 'no validation proof written\n');
344
+ process.exit(1);
345
+ }
346
+
347
+ process.stdout.write(`[BizCiGate] OK run-prevalidation โ€” proof ${outcome.proof.validationProof.slice(0, 16)}... `
348
+ + `-> ${outcome.proofPath}\n`);
349
+ }
350
+
351
+ /** Same checks, runnable on their own โ€” for the cross-repo audit and local use. */
352
+ function runVerifyDeployContract(options) {
353
+ if (!reportDeployContract(verifyDeployContract(options.serviceRoot))) {
354
+ process.exit(1);
355
+ }
356
+ }
357
+
358
+ /** Same checks, runnable on their own. Loads the contract because the database
359
+ * block is what decides whether the SQL half of the contract applies. */
360
+ function runVerifyInstallContract(options) {
361
+ const contractInfo = loadAndValidateIntegrationContract(options.serviceRoot, options.contractPath);
362
+ if (!reportInstallContract(
363
+ verifyInstallContract(contractInfo.serviceRoot, contractInfo.contract.database)
364
+ )) {
365
+ process.exit(1);
366
+ }
367
+ }
368
+
369
+ /** Same check, runnable on its own โ€” for the cross-repo audit and local use. */
370
+ function runVerifyEnvContract(options) {
371
+ const contractInfo = loadAndValidateIntegrationContract(options.serviceRoot, options.contractPath);
372
+ if (!reportEnvContract(
373
+ contractInfo.contract.serviceName || path.basename(contractInfo.serviceRoot),
374
+ contractInfo.contract,
375
+ verifyEnvCompleteness({ serviceRoot: contractInfo.serviceRoot, contract: contractInfo.contract })
376
+ )) {
377
+ process.exit(1);
378
+ }
379
+ }
380
+
102
381
  function runVerifyIntegrationMinimum(options) {
103
382
  const result = verifyIntegrationMinimum(options.serviceRoot, options.contractPath);
104
383
  process.stdout.write(`[BizCiGate] OK verify-integration-minimum\n`);
@@ -111,6 +390,44 @@ function runVerifyIntegrationMinimum(options) {
111
390
  }, null, 2)}\n`);
112
391
  }
113
392
 
393
+ /**
394
+ * The gate's only honest observation point for "did the suite run?".
395
+ *
396
+ * The file-count check runs first as a cheap precondition, then every discovered
397
+ * file gets its own runner process and its own JSON report. Per-file processes
398
+ * are the library's HOW, not a per-repo declaration: biz-invoicing already needed
399
+ * them (its suite was OOM-killed in a single process), and they bound one leaking
400
+ * suite's blast radius for everyone else.
401
+ */
402
+ function runRunIntegration(options) {
403
+ const verified = verifyIntegrationMinimum(options.serviceRoot, options.contractPath);
404
+
405
+ const result = runIntegrationSuite({
406
+ serviceRoot: verified.serviceRoot,
407
+ testFiles: verified.integrationTestFiles,
408
+ jestBin: path.join(verified.serviceRoot, 'node_modules', '.bin', 'jest'),
409
+ jestConfig: path.join(verified.serviceRoot, 'jest.config.js'),
410
+ reportDir: path.join(verified.serviceRoot, DEFAULT_REPORT_DIR_RELATIVE_PATH),
411
+ spawn: spawnSync,
412
+ });
413
+
414
+ const { aggregate, evaluation } = result;
415
+
416
+ if (!evaluation.ok) {
417
+ for (const violation of evaluation.violations) {
418
+ process.stderr.write(`${violation.message}\n`);
419
+ }
420
+ process.stderr.write(`[BizCiGate] FAIL run-integration โ€” ${aggregate.executedTests} test(s) executed, `
421
+ + `${aggregate.notExecutedTests} skipped, ${aggregate.failedTests} failed, `
422
+ + `across ${aggregate.reportCount} runner process(es). Report: ${result.artefactPath}\n`);
423
+ process.exit(1);
424
+ }
425
+
426
+ process.stdout.write(`[BizCiGate] OK run-integration โ€” ${aggregate.executedTests} test(s) executed and passed `
427
+ + `in ${aggregate.totalSuites} suite(s), across ${aggregate.reportCount} runner process(es)\n`);
428
+ process.stdout.write(`[BizCiGate] Wrote integration run artefact to ${result.artefactPath}\n`);
429
+ }
430
+
114
431
  function runEmitCiEnv(options) {
115
432
  const contractInfo = loadAndValidateIntegrationContract(options.serviceRoot, options.contractPath);
116
433
  const env = buildCiEnvironment(contractInfo);
@@ -161,8 +478,6 @@ function runWriteSummary(options) {
161
478
  contractPath: options.contractPath,
162
479
  gateVerdict: options['gate-verdict'],
163
480
  gateReason: options['gate-reason'],
164
- unitExecuted: options['unit-executed'],
165
- integrationExecuted: options['integration-executed'],
166
481
  });
167
482
 
168
483
  const outputPath = options.output
@@ -187,13 +502,42 @@ async function main() {
187
502
 
188
503
  try {
189
504
  if (command === 'verify-contract') {
190
- runVerifyContract(options);
505
+ await runVerifyContract(options);
506
+ return;
507
+ }
508
+
509
+ if (command === 'verify-deploy-contract') {
510
+ runVerifyDeployContract(options);
511
+ return;
512
+ }
513
+
514
+ if (command === 'run-prevalidation') {
515
+ await runRunPreValidation(options);
516
+ return;
517
+ }
518
+
519
+ if (command === 'verify-install-contract') {
520
+ runVerifyInstallContract(options);
521
+ return;
522
+ }
523
+
524
+ if (command === 'verify-env-contract') {
525
+ runVerifyEnvContract(options);
526
+ return;
527
+ }
528
+
529
+ if (command === 'verify-lib-compat') {
530
+ await runVerifyLibCompat(options);
191
531
  return;
192
532
  }
193
533
  if (command === 'verify-integration-minimum') {
194
534
  runVerifyIntegrationMinimum(options);
195
535
  return;
196
536
  }
537
+ if (command === 'run-integration') {
538
+ runRunIntegration(options);
539
+ return;
540
+ }
197
541
  if (command === 'emit-ci-env') {
198
542
  runEmitCiEnv(options);
199
543
  return;
@@ -202,6 +546,11 @@ async function main() {
202
546
  await runWaitConnectors(options);
203
547
  return;
204
548
  }
549
+ if (command === 'setup-db') {
550
+ runSetupDb(options);
551
+ return;
552
+ }
553
+
205
554
  if (command === 'run-setup') {
206
555
  runSetup(options);
207
556
  return;
@@ -13,7 +13,7 @@ Instead of **copying test code** between services, we provide **reusable test he
13
13
 
14
14
  ## Available Helpers
15
15
 
16
- ### 1. createServiceReadinessTests
16
+ ### createServiceReadinessTests
17
17
 
18
18
  **Purpose:** Integration test for service HTTP API readiness
19
19
 
@@ -55,63 +55,11 @@ createServiceReadinessTests(__dirname, {
55
55
 
56
56
  ---
57
57
 
58
- ### 2. createPreValidationTests
59
-
60
- **Purpose:** Tier 1 pre-validation process tests
61
-
62
- **File:** `createPreValidationTests.js`
63
-
64
- **What it does:**
65
- - Validates pre-requisites (cookbooks/, operations.json, scripts)
66
- - Runs cookbook tests with mocked infrastructure
67
- - Validates ValidationProof generation
68
- - Tests proof structure and integrity
69
- - Verifies SHA256 hash correctness
70
- - Tests tamper detection
71
- - Validates dependencies tracking
72
-
73
- **Usage:**
74
- ```javascript
75
- // services/my-service/tests/integration/pre-validation-process.test.js
76
- const { createPreValidationTests } = require('@onlineapps/conn-orch-validator');
77
-
78
- createPreValidationTests(__dirname);
79
- ```
80
-
81
- **Options:**
82
- ```javascript
83
- createPreValidationTests(__dirname, {
84
- timeout: 60000 // Test timeout in ms (default: 60000)
85
- });
86
- ```
87
-
88
- **Test Structure:**
89
- 1. Pre-requisites Check
90
- - cookbooks/ directory exists
91
- - operations.json exists
92
- - Pre-validation script configured
93
- 2. Cookbook Test Execution
94
- - Runs npm run test:cookbooks
95
- - Verifies infrastructure mocking
96
- 3. ValidationProof Generation
97
- - Creates .validation-proof.json
98
- - Validates proof structure
99
- - Checks SHA256 hash
100
- 4. Proof Integrity Verification
101
- - ValidationProofCodec decode
102
- - Hash matching
103
- - Age verification
104
- - Tamper detection
105
- 5. Dependencies Tracking
106
- - Proof includes @onlineapps/* versions
107
-
108
- ---
109
-
110
58
  ## How It Works
111
59
 
112
60
  ### Automatic Structure Validation
113
61
 
114
- Both helpers use `ServiceStructureValidator` to validate service structure BEFORE running tests:
62
+ The helper uses `ServiceStructureValidator` to validate service structure BEFORE running tests:
115
63
 
116
64
  ```
117
65
  ๐Ÿ” Validating service structure...
@@ -156,7 +104,7 @@ If validation fails, clear error messages are shown:
156
104
 
157
105
  ### Zero Configuration
158
106
 
159
- Helpers automatically detect and load:
107
+ The helper automatically detects and loads:
160
108
 
161
109
  ```javascript
162
110
  // Service root (2 levels up from tests/integration/)
@@ -31,6 +31,7 @@ const fs = require('fs');
31
31
  const ServiceReadinessValidator = require('../ServiceReadinessValidator');
32
32
  const MockRegistry = require('../mocks/MockRegistry');
33
33
  const { ServiceStructureValidator } = require('../validators/ServiceStructureValidator');
34
+ const { MIN_COOKBOOK_FORMAT_VERSION } = require('../utils/cookbookFormat');
34
35
 
35
36
  /**
36
37
  * Create service readiness integration test suite
@@ -97,7 +98,10 @@ function createServiceReadinessTests(testsDir, options = {}) {
97
98
  if (includeOptionalChecks) {
98
99
  mockRegistry = new MockRegistry();
99
100
  testCookbook = {
100
- version: '1.0.0',
101
+ // Cookbook FORMAT version โ€” the synthesised cookbook must satisfy the
102
+ // same rule the Tier-1 gate enforces (api/docs/biz/40-cookbooks/format.md
103
+ // ยง Required fields), otherwise this helper rejects its own output.
104
+ version: MIN_COOKBOOK_FORMAT_VERSION,
101
105
  steps: Object.entries(operationsFlat).map(([name, op]) => ({
102
106
  id: `test-${name}`,
103
107
  type: 'task',
package/src/index.js CHANGED
@@ -33,7 +33,6 @@ const ServiceReadinessValidator = require('./ServiceReadinessValidator');
33
33
  const ValidationOrchestrator = require('./ValidationOrchestrator');
34
34
 
35
35
  const { createServiceReadinessTests } = require('./helpers/createServiceReadinessTests');
36
- const { createPreValidationTests } = require('./helpers/createPreValidationTests');
37
36
 
38
37
  module.exports = {
39
38
  get MockMQClient() { return MockMQClient; },
@@ -48,9 +47,13 @@ module.exports = {
48
47
  get ServiceStructureValidator() { return ServiceStructureValidator; },
49
48
  get ValidationOrchestrator() { return ValidationOrchestrator; },
50
49
 
50
+ // The namespace an integration test may write into โ€” read from the platform
51
+ // env, never chosen per test. Enforced by deploy-contract R8.
52
+ get getTestNamespace() { return require('./utils/testNamespace').getTestNamespace; },
53
+
51
54
  get createServiceReadinessTests() { return createServiceReadinessTests; },
52
- get createPreValidationTests() { return createPreValidationTests; },
53
55
  get BizCiGateContract() { return BizCiGateContract; },
56
+ get IntegrationRun() { return require('./utils/integrationRun'); },
54
57
 
55
58
  createMockMQ: () => new MockMQClient(),
56
59
  createMockRegistry: () => new MockRegistry(),