@onlineapps/conn-orch-validator 2.0.33 → 3.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/docs/DESIGN.md CHANGED
@@ -1,134 +1,80 @@
1
- # Connector-Testing Design Document
1
+ # conn-orch-validator Design
2
2
 
3
3
  ## Purpose
4
4
 
5
- The conn-orch-validator package provides a unified testing and validation framework for OA Drive microservices. It serves three critical functions:
5
+ Validation framework for OA Drive microservices. Drives two flows:
6
6
 
7
- 1. **Development Testing** - Mock infrastructure for isolated service testing
8
- 2. **Production Validation** - On-the-fly validation of new services during registration
9
- 3. **Service Readiness** - Complete verification before deployment
7
+ 1. **Pre-validation** (Tier 1) runs during `service-wrapper` startup. Verifies
8
+ structure (config files, package.json), readiness (endpoints respond, health
9
+ works) and produces a signed `validation-proof.json` stored under
10
+ `conn-runtime/`.
11
+ 2. **Readiness checks** — reusable probes (`createServiceReadinessTests`,
12
+ `createPreValidationTests`) consumable from unit/component test suites
13
+ of individual biz services.
10
14
 
11
- ## Key Principles
15
+ The single source of truth for service endpoint metadata is
16
+ [`operations.json`](../../../docs/standards/operations-registry-contract.md).
17
+ OpenAPI iteration (`paths`/`operationId`) is NOT supported — that legacy
18
+ surface was removed together with the now-retired `ServiceValidator` /
19
+ `TestOrchestrator` / `ServiceTestHarness` classes.
12
20
 
13
- ### No Duplication
14
- - Does NOT duplicate tests from individual connectors
15
- - Uses existing connector functionality where available
16
- - Focuses on integration and orchestration
21
+ ## Key Principles
17
22
 
18
- ### Production-Ready
19
- - Same code used in development AND production
20
- - Validates services before they join the ecosystem
21
- - Ensures cookbook compatibility
23
+ - **No duplication** — does not re-test individual connector logic; focuses on
24
+ integration, readiness and proof generation.
25
+ - **Production-ready** the exact same code runs locally, in CI and during
26
+ wrapper startup.
27
+ - **Fail-fast** — missing logger, missing serviceUrl, missing operations.json:
28
+ immediate throw.
22
29
 
23
30
  ## Components
24
31
 
25
- ### 1. Mock Infrastructure (Development)
26
- - `MockMQClient` - In-memory message queue simulation
27
- - `MockRegistry` - Service registry simulation
28
- - `MockStorage` - Object storage simulation
32
+ ### Mock Infrastructure (for unit tests)
33
+
34
+ - `MockMQClient` — in-memory message queue simulation
35
+ - `MockRegistry` service registry simulation
36
+ - `MockStorage` — object storage simulation
29
37
 
30
- ### 2. Validation Tools (Production)
31
- - `ServiceValidator` - OpenAPI compliance and endpoint testing
32
- - `WorkflowTestRunner` - Cookbook execution testing
33
- - `CookbookTestUtils` - Cookbook structure validation
38
+ ### Production Validation
34
39
 
35
- ### 3. Integration Framework
36
- - `ServiceTestHarness` - Complete test environment orchestration
40
+ - `ValidationOrchestrator` 6-step pre-validation pipeline, emits proof
41
+ - `ServiceReadinessValidator` — score-based readiness checks (operations /
42
+ endpoints / health / cookbook / registry)
43
+ - `ServiceStructureValidator` — config layout checks (config/service/*)
44
+ - `ValidationProofGenerator` — fingerprint + codec helpers
45
+ - `CookbookTestRunner` / `WorkflowTestRunner` — cookbook execution probes
46
+ - `CookbookTestUtils` — static cookbook-structure validators
37
47
 
38
- ## Use Cases
48
+ ### Test Suite Helpers
39
49
 
40
- ### Development Workflow
41
- ```javascript
42
- // Developer testing their service
43
- const harness = new ServiceTestHarness({
44
- service: myExpressApp,
45
- serviceName: 'my-service',
46
- openApiSpec: spec,
47
- mockInfrastructure: true // Use mocks
48
- });
50
+ - `createServiceReadinessTests(options)` — Jest suite generator
51
+ - `createPreValidationTests(options)` — Jest suite generator
49
52
 
50
- await harness.start();
51
- // Run tests...
52
- await harness.stop();
53
- ```
53
+ ## Integration Points
54
54
 
55
- ### Production Validation
56
- ```javascript
57
- // Registry validating new service
58
- const validator = new ServiceValidator();
59
- const result = await validator.validateService(
60
- serviceUrl,
61
- openApiSpec
62
- );
63
-
64
- if (!result.valid) {
65
- // Reject registration
66
- }
67
- ```
68
-
69
- ### Service-Wrapper Integration
70
- ```javascript
71
- // Service-wrapper using connector-testing
72
- class ServiceWrapper {
73
- async validateBeforeStart() {
74
- const validator = new ServiceValidator();
75
- const validation = await validator.validateService(
76
- `http://localhost:${this.port}`,
77
- this.openApiSpec
78
- );
79
-
80
- if (!validation.valid) {
81
- throw new Error('Service validation failed');
82
- }
83
- }
84
- }
85
- ```
86
-
87
- ## Testing Strategy
88
-
89
- ### What We Test
90
- 1. **Service Contract** - OpenAPI compliance
91
- 2. **Workflow Capability** - Can process cookbooks
92
- 3. **Infrastructure Integration** - Registry, MQ, Storage connectivity
93
- 4. **Health & Status** - Monitoring endpoints
94
-
95
- ### What We DON'T Test
96
- 1. Individual connector functionality (tested in connector packages)
97
- 2. Business logic correctness (service's responsibility)
98
- 3. Performance metrics (separate concern)
99
-
100
- ## Production Validation Flow
101
-
102
- ```
103
- New Service Registration
104
-
105
- [Registry]
106
-
107
- Uses connector-testing
108
-
109
- 1. Validate OpenAPI
110
- 2. Test all endpoints
111
- 3. Execute test cookbook
112
- 4. Verify health check
113
-
114
- Pass? → Accept : Reject
115
- ```
116
-
117
- ## Integration with Existing System
118
-
119
- ### Uses Existing Connectors
120
- - Leverages real connector implementations where possible
121
- - Only mocks what's necessary for isolation
122
- - Validates against actual connector interfaces
123
-
124
- ### Complements Existing Tests
125
- - Individual connectors have unit tests
126
- - Services have business logic tests
127
- - Connector-testing provides integration validation
128
-
129
- ## Future Enhancements
130
-
131
- 1. **Performance Benchmarking** - Baseline performance requirements
132
- 2. **Security Validation** - Authentication/authorization checks
133
- 3. **Chaos Testing** - Fault injection and recovery
134
- 4. **Compliance Checking** - Regulatory requirement validation
55
+ - `@onlineapps/service-wrapper` instantiates `ValidationOrchestrator` during
56
+ wrapper startup. Proof is cached under `conn-runtime/validation-proof.json`
57
+ (30-day validity, invalidated by config fingerprint change — includes
58
+ operations map, see
59
+ [operations-registry-contract.md §3](../../../docs/standards/operations-registry-contract.md)).
60
+ - Biz-service templates import `createPreValidationTests` /
61
+ `createServiceReadinessTests` from this package for their own test suites.
62
+
63
+ ## What We Test
64
+
65
+ 1. **Service contract** — operations.json structure + endpoint reachability
66
+ 2. **Workflow capability** — can process cookbooks
67
+ 3. **Infrastructure** — registry / MQ / storage connectivity
68
+ 4. **Health** — `/health` returns 200
69
+
70
+ ## What We DO NOT Test
71
+
72
+ 1. Individual connector behavior (tested in connector packages)
73
+ 2. Business logic correctness (service responsibility)
74
+ 3. Performance / load (separate concern)
75
+
76
+ ## Related Documentation
77
+
78
+ - [operations-registry-contract.md](../../../docs/standards/operations-registry-contract.md) — operations.json schema
79
+ - [validation-probe-contract.md](../../../docs/standards/validation-probe-contract.md) — required `example`/`default` fields for probes
80
+ - [validator.md](../../../docs/architecture/validator.md) — architecture overview
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onlineapps/conn-orch-validator",
3
- "version": "2.0.33",
3
+ "version": "3.0.0",
4
4
  "description": "Validation orchestrator for OA Drive microservices - coordinates validation across all layers (base, infra, orch, business)",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -21,7 +21,7 @@
21
21
  "author": "OnlineApps",
22
22
  "license": "PROPRIETARY",
23
23
  "dependencies": {
24
- "@onlineapps/service-validator-core": "1.0.12",
24
+ "@onlineapps/service-validator-core": "1.0.13",
25
25
  "@onlineapps/runtime-config": "1.0.2",
26
26
  "ajv": "^8.12.0",
27
27
  "ajv-formats": "^2.1.1",
@@ -1,29 +1,34 @@
1
1
  'use strict';
2
2
 
3
- const ServiceValidator = require('./ServiceValidator');
4
3
  const CookbookTestUtils = require('./CookbookTestUtils');
5
- const { resolveHeaders } = require('./utils/resolveHeaders');
6
4
 
7
5
  /**
8
- * ServiceReadinessValidator - Orchestrates complete service validation
9
- * Used by service-wrapper to verify service is ready for production
6
+ * ServiceReadinessValidator - Orchestrates complete service validation.
10
7
  *
11
- * IMPORTANT: Only supports operations.json format. OpenAPI is deprecated.
8
+ * Used by service-wrapper to verify a service is ready for production.
9
+ * Works exclusively against the v3 operations.json contract — operations
10
+ * are dispatched via the handler registry (RFC §5.3, §5.9), not HTTP
11
+ * per-operation endpoints. The HTTP loopback check was removed when v3
12
+ * adopted in-process handler invocation.
13
+ *
14
+ * @see /api/docs/architecture/biz-service-invocation-model.md §5.3
15
+ * @see /api/docs/standards/validation-probe-contract.md
12
16
  */
13
17
  class ServiceReadinessValidator {
14
18
  constructor(options = {}) {
15
19
  if (!options.logger || typeof options.logger.warn !== 'function') {
16
20
  throw new Error('[ServiceReadinessValidator] Logger is required — Expected object with warn() method');
17
21
  }
18
- this.validator = new ServiceValidator(options);
19
22
  this.logger = options.logger;
20
23
 
21
24
  // Readiness checks: core (80 points) + optional (20 points) = 100 points max
22
25
  // Core checks ALWAYS run, optional checks run if testCookbook/registry provided
23
26
  // See: /shared/connector/conn-orch-validator/README.md for usage pattern
27
+ // v3: per-op HTTP endpoint probing is retired. Operations are dispatched in-process
28
+ // via the handler registry. The 30-point weight previously spent on `endpoints`
29
+ // is folded into `operations` so the total stays at 100.
24
30
  this.checks = {
25
- operations: { weight: 30, required: true }, // operations.json structure valid
26
- endpoints: { weight: 30, required: true }, // all endpoints respond correctly
31
+ operations: { weight: 60, required: true }, // operations.json v3 structure valid
27
32
  health: { weight: 20, required: true }, // health check works
28
33
  cookbook: { weight: 15, required: false }, // OPTIONAL - cookbook valid (with mocks)
29
34
  registry: { weight: 5, required: false } // OPTIONAL - registry compatible (MockRegistry)
@@ -69,15 +74,8 @@ class ServiceReadinessValidator {
69
74
  results.checks.operations = { passed: false, error: 'Missing operations.json' };
70
75
  }
71
76
 
72
- // 2. Test all declared operations endpoints
73
- if (operations && results.checks.operations?.passed) {
74
- results.checks.endpoints = await this.checkOperationsEndpoints(url, operations);
75
- if (results.checks.endpoints.passed) {
76
- results.score += this.checks.endpoints.weight;
77
- } else if (this.checks.endpoints.required) {
78
- results.errors.push('Endpoint validation failed');
79
- }
80
- }
77
+ // 2. (v3) Per-op HTTP endpoint probing retired — operations are dispatched
78
+ // in-process via the handler registry. No network call per operation.
81
79
 
82
80
  // 3. Verify health check
83
81
  results.checks.health = await this.checkHealth(url + healthEndpoint);
@@ -128,14 +126,15 @@ class ServiceReadinessValidator {
128
126
  }
129
127
 
130
128
  /**
131
- * Check operations.json compliance
129
+ * Check operations.json compliance (v3 — handler registry).
130
+ * Required per operation: description, handler, bundle_scope, input, output.
131
+ * Forbidden (v2): endpoint, method, path.
132
132
  */
133
133
  async checkOperationsCompliance(operations) {
134
134
  try {
135
135
  const errors = [];
136
136
  const warnings = [];
137
137
 
138
- // Validate operations structure
139
138
  if (!operations || typeof operations !== 'object') {
140
139
  return {
141
140
  passed: false,
@@ -151,17 +150,23 @@ class ServiceReadinessValidator {
151
150
  };
152
151
  }
153
152
 
154
- // Validate each operation
153
+ const validScopes = ['platform', 'tenant', 'workspace'];
154
+ const handlerPattern = /^handlers\/[a-zA-Z0-9_\/-]+#[a-zA-Z_][a-zA-Z0-9_]*$/;
155
+ const forbiddenV2Fields = ['endpoint', 'method', 'path'];
156
+
155
157
  for (const [name, operation] of Object.entries(operations)) {
156
- // Required fields
157
158
  if (!operation.description) {
158
- errors.push(`Operation '${name}' missing description`);
159
+ warnings.push(`Operation '${name}' missing description`);
159
160
  }
160
- if (!operation.endpoint) {
161
- errors.push(`Operation '${name}' missing endpoint`);
161
+ if (!operation.handler) {
162
+ errors.push(`Operation '${name}' missing handler (v3 — 'handlers/<path>#<export>')`);
163
+ } else if (!handlerPattern.test(operation.handler)) {
164
+ errors.push(`Operation '${name}' has invalid handler ref: '${operation.handler}'`);
162
165
  }
163
- if (!operation.method) {
164
- errors.push(`Operation '${name}' missing method`);
166
+ if (!operation.bundle_scope) {
167
+ errors.push(`Operation '${name}' missing bundle_scope (platform|tenant|workspace)`);
168
+ } else if (!validScopes.includes(operation.bundle_scope)) {
169
+ errors.push(`Operation '${name}' has invalid bundle_scope: '${operation.bundle_scope}'`);
165
170
  }
166
171
  if (!operation.input) {
167
172
  errors.push(`Operation '${name}' missing input schema`);
@@ -170,15 +175,10 @@ class ServiceReadinessValidator {
170
175
  errors.push(`Operation '${name}' missing output schema`);
171
176
  }
172
177
 
173
- // Validate method
174
- const validMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'];
175
- if (operation.method && !validMethods.includes(operation.method)) {
176
- errors.push(`Operation '${name}' has invalid method: ${operation.method}`);
177
- }
178
-
179
- // Validate endpoint format
180
- if (operation.endpoint && !operation.endpoint.startsWith('/')) {
181
- warnings.push(`Operation '${name}' endpoint should start with /`);
178
+ for (const forbidden of forbiddenV2Fields) {
179
+ if (forbidden in operation) {
180
+ errors.push(`Operation '${name}' has retired v2 field '${forbidden}' — remove per RFC §5.3`);
181
+ }
182
182
  }
183
183
  }
184
184
 
@@ -196,85 +196,6 @@ class ServiceReadinessValidator {
196
196
  }
197
197
  }
198
198
 
199
- /**
200
- * Check all operations endpoints respond correctly
201
- */
202
- async checkOperationsEndpoints(serviceUrl, operations) {
203
- const axios = require('axios');
204
- const results = {
205
- passed: true,
206
- total: Object.keys(operations).length,
207
- successful: 0,
208
- failed: 0,
209
- details: []
210
- };
211
-
212
- for (const [operationName, operation] of Object.entries(operations)) {
213
- try {
214
- const endpoint = operation.endpoint;
215
- const method = operation.method.toLowerCase();
216
- const url = `${serviceUrl}${endpoint}`;
217
-
218
- // Generate test input based on schema
219
- const testInput = this.generateTestInput(operation.input);
220
-
221
- const validationTenantId = process.env.OA_VALIDATION_TENANT_ID;
222
- const validationWorkspaceId = process.env.OA_VALIDATION_WORKSPACE_ID;
223
- if (!validationTenantId || !validationWorkspaceId) {
224
- throw new Error('[ServiceReadinessValidator] Missing required environment variables OA_VALIDATION_TENANT_ID and/or OA_VALIDATION_WORKSPACE_ID');
225
- }
226
- const headers = {
227
- 'x-validation-request': 'true',
228
- 'x-tenant-id': validationTenantId,
229
- 'x-workspace-id': validationWorkspaceId,
230
- ...resolveHeaders(operation.headers)
231
- };
232
-
233
- // Make request
234
- const response = await axios({
235
- method,
236
- url,
237
- data: method !== 'get' ? testInput : undefined,
238
- params: method === 'get' ? testInput : undefined,
239
- headers,
240
- timeout: 5000,
241
- validateStatus: () => true // Accept any status for validation
242
- });
243
-
244
- // Check response: 2xx = success, 4xx = endpoint works but rejected test data (valid)
245
- // Only 5xx (server error) or connection failure = invalid endpoint
246
- const isValid = response.status < 500;
247
-
248
- if (isValid) {
249
- results.successful++;
250
- } else {
251
- results.failed++;
252
- results.passed = false;
253
- }
254
-
255
- results.details.push({
256
- operation: operationName,
257
- endpoint,
258
- method: method.toUpperCase(),
259
- status: response.status,
260
- valid: isValid
261
- });
262
- } catch (error) {
263
- results.failed++;
264
- results.passed = false;
265
- results.details.push({
266
- operation: operationName,
267
- endpoint: operation.endpoint,
268
- method: operation.method,
269
- valid: false,
270
- error: error.message
271
- });
272
- }
273
- }
274
-
275
- return results;
276
- }
277
-
278
199
  /**
279
200
  * Check health endpoint
280
201
  */
@@ -349,45 +270,6 @@ class ServiceReadinessValidator {
349
270
  }
350
271
  }
351
272
 
352
- /**
353
- * Generate test input based on operation input schema.
354
- *
355
- * Resolution order per field: example > default > enum[0]
356
- * If none found for a required field, throws with actionable message.
357
- *
358
- * Contract: every required field in operations.json MUST have 'example' or 'default'.
359
- * See: docs/standards/validation-probe-contract.md
360
- */
361
- generateTestInput(inputSchema) {
362
- const testData = {};
363
-
364
- for (const [fieldName, fieldSpec] of Object.entries(inputSchema)) {
365
- if (!fieldSpec.required) continue;
366
-
367
- const value = this._resolveTestValue(fieldName, fieldSpec);
368
- testData[fieldName] = value;
369
- }
370
-
371
- return testData;
372
- }
373
-
374
- /**
375
- * Resolve a single test value for a required input field.
376
- * Throws if no value can be determined — forces service authors to provide examples.
377
- * @private
378
- */
379
- _resolveTestValue(fieldName, fieldSpec) {
380
- if (fieldSpec.example !== undefined) return fieldSpec.example;
381
- if (fieldSpec.default !== undefined) return fieldSpec.default;
382
- if (Array.isArray(fieldSpec.enum) && fieldSpec.enum.length > 0) return fieldSpec.enum[0];
383
-
384
- throw new Error(
385
- `[ServiceReadinessValidator] No test value for required field '${fieldName}' ` +
386
- `(type: ${fieldSpec.type}) — Add 'example' or 'default' to operations.json input schema. ` +
387
- `See: docs/standards/validation-probe-contract.md`
388
- );
389
- }
390
-
391
273
  /**
392
274
  * Get recommendation based on results
393
275
  */
@@ -329,7 +329,9 @@ class ValidationOrchestrator {
329
329
  }
330
330
 
331
331
  /**
332
- * Step 3: Validate operations compliance
332
+ * Step 3: Validate operations compliance (v3 — handler registry dispatch).
333
+ * Required per operation: handler ('handlers/<path>#<export>'), bundle_scope, input, output.
334
+ * Forbidden (v2): endpoint, method, path.
333
335
  */
334
336
  async validateOperations() {
335
337
  try {
@@ -337,18 +339,21 @@ class ValidationOrchestrator {
337
339
  const operations = JSON.parse(fs.readFileSync(operationsFile, 'utf8'));
338
340
  const errors = [];
339
341
 
340
- // Validate each operation
342
+ const validScopes = ['platform', 'tenant', 'workspace'];
343
+ const handlerPattern = /^handlers\/[a-zA-Z0-9_\/-]+#[a-zA-Z_][a-zA-Z0-9_]*$/;
344
+ const forbiddenV2Fields = ['endpoint', 'method', 'path'];
345
+
341
346
  for (const [opName, opDef] of Object.entries(operations.operations || {})) {
342
- if (!opDef.endpoint) {
343
- errors.push(`Operation ${opName}: missing endpoint`);
344
- } else if (!opDef.endpoint.startsWith('/')) {
345
- errors.push(`Operation ${opName}: endpoint must start with /`);
347
+ if (!opDef.handler) {
348
+ errors.push(`Operation ${opName}: missing handler (v3 — 'handlers/<path>#<export>')`);
349
+ } else if (!handlerPattern.test(opDef.handler)) {
350
+ errors.push(`Operation ${opName}: invalid handler ref '${opDef.handler}' — expected 'handlers/<path>#<exportName>'`);
346
351
  }
347
352
 
348
- if (!opDef.method) {
349
- errors.push(`Operation ${opName}: missing method`);
350
- } else if (!['GET', 'POST', 'PUT', 'DELETE', 'PATCH'].includes(opDef.method)) {
351
- errors.push(`Operation ${opName}: invalid HTTP method`);
353
+ if (!opDef.bundle_scope) {
354
+ errors.push(`Operation ${opName}: missing bundle_scope (platform|tenant|workspace)`);
355
+ } else if (!validScopes.includes(opDef.bundle_scope)) {
356
+ errors.push(`Operation ${opName}: invalid bundle_scope '${opDef.bundle_scope}' — expected one of ${validScopes.join(', ')}`);
352
357
  }
353
358
 
354
359
  if (!opDef.input) {
@@ -357,6 +362,12 @@ class ValidationOrchestrator {
357
362
  if (!opDef.output) {
358
363
  errors.push(`Operation ${opName}: missing output schema`);
359
364
  }
365
+
366
+ for (const forbidden of forbiddenV2Fields) {
367
+ if (forbidden in opDef) {
368
+ errors.push(`Operation ${opName}: retired v2 field '${forbidden}' present — remove per RFC §5.3`);
369
+ }
370
+ }
360
371
  }
361
372
 
362
373
  console.log(`[ValidationOrchestrator] ✓ Operations compliance: ${errors.length === 0 ? 'PASS' : 'FAIL'}`);
@@ -374,6 +385,11 @@ class ValidationOrchestrator {
374
385
 
375
386
  /**
376
387
  * Step 4: Run cookbook tests
388
+ *
389
+ * v3 note: per-operation HTTP dispatch is retired. If operations.json is v3
390
+ * (schema_version "3.0" or any operation declares `handler`), cookbook runs
391
+ * are skipped here with a warning. Handler-registry-based cookbook dispatch
392
+ * is tracked separately (see RFC §5.3, §5.9).
377
393
  */
378
394
  async runCookbookTests() {
379
395
  try {
@@ -390,6 +406,25 @@ class ValidationOrchestrator {
390
406
  };
391
407
  }
392
408
 
409
+ const operationsFile = path.join(this.configPath, 'operations.json');
410
+ let isV3 = false;
411
+ try {
412
+ const ops = JSON.parse(fs.readFileSync(operationsFile, 'utf8'));
413
+ const opValues = Object.values(ops.operations || {});
414
+ isV3 = ops.schema_version === '3.0' || opValues.some(o => o && typeof o === 'object' && 'handler' in o);
415
+ } catch (_) { /* fall through to runner */ }
416
+
417
+ if (isV3) {
418
+ console.warn('[ValidationOrchestrator] Step 4: cookbook HTTP dispatch is v2-only. Skipping for v3 ops schema.');
419
+ return {
420
+ success: true,
421
+ total: 0,
422
+ passed: 0,
423
+ failed: 0,
424
+ warnings: ['Cookbook tests skipped — v3 handler-registry dispatch not yet implemented in CookbookTestRunner']
425
+ };
426
+ }
427
+
393
428
  const result = await this.cookbookRunner.runCookbooks(cookbooksPath);
394
429
 
395
430
  console.log(`[ValidationOrchestrator] ✓ Cookbook tests: ${result.passed}/${result.total} passed`);
package/src/config.js CHANGED
@@ -4,7 +4,7 @@
4
4
  * Runtime configuration schema for @onlineapps/conn-orch-validator.
5
5
  *
6
6
  * Uses @onlineapps/runtime-config for unified priority:
7
- * 1. Explicit config (passed to TestOrchestrator options)
7
+ * 1. Explicit config (passed to ValidationOrchestrator/readiness options)
8
8
  * 2. Environment variable
9
9
  * 3. Module-owned defaults (none for topology)
10
10
  *
@@ -179,14 +179,12 @@ function createServiceReadinessTests(testsDir, options = {}) {
179
179
  expect(result.score).toBe(100);
180
180
  expect(result.checks.health?.passed).toBe(true);
181
181
  expect(result.checks.operations?.passed).toBe(true);
182
- expect(result.checks.endpoints?.passed).toBe(true);
183
182
  expect(result.checks.cookbook?.passed).toBe(true);
184
183
  expect(result.checks.registry?.passed).toBe(true);
185
184
  } else {
186
185
  expect(result.score).toBeGreaterThanOrEqual(80);
187
186
  expect(result.checks.health?.passed).toBe(true);
188
187
  expect(result.checks.operations?.passed).toBe(true);
189
- expect(result.checks.endpoints?.passed).toBe(true);
190
188
  }
191
189
  }, timeout);
192
190
 
@@ -199,7 +197,7 @@ function createServiceReadinessTests(testsDir, options = {}) {
199
197
  expect(data.status).toBe('healthy');
200
198
  });
201
199
 
202
- test('service has valid operations specification', () => {
200
+ test('service has valid operations specification (v3)', () => {
203
201
  expect(operations).toBeDefined();
204
202
  expect(operations.operations || operations).toBeDefined();
205
203
 
@@ -207,12 +205,16 @@ function createServiceReadinessTests(testsDir, options = {}) {
207
205
  expect(typeof operationsFlat).toBe('object');
208
206
  expect(Object.keys(operationsFlat).length).toBeGreaterThan(0);
209
207
 
210
- // Verify operations structure
211
- for (const [operationName, operationSpec] of Object.entries(operationsFlat)) {
212
- expect(operationSpec).toHaveProperty('endpoint');
213
- expect(operationSpec).toHaveProperty('method');
214
- expect(operationSpec.endpoint).toMatch(/^\//);
215
- expect(['GET', 'POST', 'PUT', 'DELETE', 'PATCH']).toContain(operationSpec.method);
208
+ const validScopes = ['platform', 'tenant', 'workspace'];
209
+ const handlerPattern = /^handlers\/[a-zA-Z0-9_/-]+#[a-zA-Z_][a-zA-Z0-9_]*$/;
210
+
211
+ for (const [, operationSpec] of Object.entries(operationsFlat)) {
212
+ expect(operationSpec).toHaveProperty('handler');
213
+ expect(operationSpec).toHaveProperty('bundle_scope');
214
+ expect(operationSpec.handler).toMatch(handlerPattern);
215
+ expect(validScopes).toContain(operationSpec.bundle_scope);
216
+ expect(operationSpec).not.toHaveProperty('endpoint');
217
+ expect(operationSpec).not.toHaveProperty('method');
216
218
  }
217
219
  });
218
220
  });