@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.
Files changed (56) hide show
  1. package/CHANGELOG.md +597 -0
  2. package/README.md +126 -19
  3. package/manifests/biz-service.manifest.json +15 -2
  4. package/manifests/library.manifest.json +4 -4
  5. package/package.json +11 -3
  6. package/src/CookbookTestRunner.js +275 -105
  7. package/src/CookbookTestUtils.js +79 -68
  8. package/src/ServiceReadinessValidator.js +42 -52
  9. package/src/ValidationOrchestrator.js +65 -44
  10. package/src/cli/biz-ci-gate.js +2 -2
  11. package/src/cli/oa-sync-template.js +97 -47
  12. package/src/cli/oa-validate.js +44 -10
  13. package/src/helpers/README.md +6 -6
  14. package/src/helpers/createServiceReadinessTests.js +87 -33
  15. package/src/index.js +14 -5
  16. package/src/lint/scripts/lintScripts.js +11 -4
  17. package/src/manifest/checks/libraryContext.js +6 -3
  18. package/src/manifest/checks/libraryDocs.js +174 -4
  19. package/src/manifest/checks/libraryTests.js +200 -19
  20. package/src/manifest/checks/scriptHeaders.js +6 -13
  21. package/src/manifest/checks/serviceConfig.js +36 -16
  22. package/src/manifest/checks/serviceConnectors.js +180 -2
  23. package/src/manifest/checks/serviceDb.js +0 -3
  24. package/src/manifest/checks/serviceScripts.js +3 -20
  25. package/src/manifest/runManifest.js +90 -13
  26. package/src/manifest/workspaceRoot.js +133 -4
  27. package/src/mocks/MockMQClient.js +2 -2
  28. package/src/sync/docsRegion.js +2 -2
  29. package/src/sync/readmeFile.js +30 -0
  30. package/src/sync/readmeLocation.js +2 -12
  31. package/src/sync/readmePointer.js +10 -4
  32. package/src/sync/serviceTemplate.js +9 -11
  33. package/src/sync/sharedEnv.js +59 -3
  34. package/src/sync/uniformFiles.js +81 -8
  35. package/src/utils/bizCiGateContract.js +2 -2
  36. package/src/utils/connectorContract.js +54 -2
  37. package/src/utils/cookbookFormat.js +25 -115
  38. package/src/utils/dbAccountGrants.js +5 -3
  39. package/src/utils/deployContract.js +153 -28
  40. package/src/utils/envContract.js +2 -2
  41. package/src/utils/handlerRef.js +8 -10
  42. package/src/utils/integrationRun.js +1 -1
  43. package/src/utils/operationsDocumentRules.js +242 -0
  44. package/src/utils/operationsRules.js +157 -0
  45. package/src/utils/resolveHeaders.js +12 -1
  46. package/src/utils/setupDatabase.js +1 -1
  47. package/src/utils/stepFailure.js +3 -3
  48. package/src/utils/stepReferences.js +28 -87
  49. package/src/utils/throwawaySchema.js +1 -1
  50. package/src/utils/yamlTopLevel.js +105 -0
  51. package/src/validators/ServiceStructureValidator.js +67 -152
  52. package/templates/business-service/README.md +3 -2
  53. package/templates/business-service/config/env-templates/shared.env +1 -0
  54. package/templates/business-service/src/config/index.js +15 -0
  55. package/TESTING_STRATEGY.md +0 -92
  56. package/jest.config.js +0 -37
package/README.md CHANGED
@@ -195,14 +195,17 @@ What Tier-1 adds is the SCOPE, and only the scope
195
195
  A literal `{{…}}` is the right outcome for a value that is missing at that
196
196
  moment — a step that has not run yet, or one that failed. It is the wrong
197
197
  outcome for an expression no run could ever resolve, because then a green Tier-1
198
- certifies a recipe production does not accept. Two such shapes are refused
199
- BEFORE any step of the cookbook runs, each with its own `Fix:`:
198
+ certifies a recipe production does not accept. These shapes are refused
199
+ BEFORE any step of the cookbook runs, each with its own `Fix:`. The helper call
200
+ is the runner's own refusal; the step references are
201
+ `@onlineapps/cookbook-core` `validateCookbook`'s, which the runner runs first and
202
+ holds no copy of (d.980):
200
203
 
201
204
  | Written in a step's `input` | Tier-1 | Production |
202
205
  |---|---|---|
203
206
  | `{{webalizeString(…)}}`, `{{string2file(…)}}` — any `name(…)` | refused: `Step <id> input calls the template helper "<name>"` | the orchestrator evaluates it through its helper registry, and throws on a name that registry does not carry |
204
- | `{{steps.<id>.…}}` naming no step THIS cookbook defines (`{{steps.0.…}}` included — `0` is a name no recipe declares) | refused: `Step <id> references an undefined step` | 400 `Invalid cookbook references` at submission |
205
- | `depends_on` naming no step this cookbook defines | refused: `Step <id> depends on an undefined step` | — |
207
+ | `{{steps.<id>.…}}` naming no step THIS cookbook defines (`{{steps.0.…}}` included — `0` is a name no recipe declares) | refused (cookbook-core): `Step references that cannot resolve at run time - <id>.input: … - unknown step '<x>'` (positional: `… never by position`) | 400 `Invalid cookbook references` at submission |
208
+ | `depends_on` naming no step this cookbook defines | refused (cookbook-core): `Step '<id>' depends on non-existent step_id '<x>'` | — |
206
209
 
207
210
  The helper registry is absent from the runner on purpose: a helper reaches its
208
211
  own runtime (`@onlineapps/content-resolver`, files, storage), which is not what a
@@ -277,15 +280,18 @@ schema name is refused rather than escaped — no platform name carries one.
277
280
 
278
281
  ### Running the live-database suites
279
282
 
280
- Two suites of this package measure what a **real** MariaDB does with the SQL the
281
- package builds — `tests/unit/setupDatabaseLive.integration.test.js` (what
282
- `ci:gate:setup` leaves in the schema) and `tests/unit/dbCiAccount.integration.test.js`
283
- (whether the grants above are enough, and still not too much). They are part of
284
- `npm run test:unit`, and in CI they have a job with a database of its own,
285
- `test-validator-db` (owner decision
283
+ The live-database suites of this package measure what a **real** MariaDB does
284
+ with the SQL the package builds: what `ci:gate:setup` leaves in the schema,
285
+ whether the account `setup-db-account` creates is granted enough — and still not
286
+ too much — and how far the production grant of that account reaches. They are
287
+ the suites that require `tests/helpers/liveDatabase.js`, and the list of them is
288
+ the `script` of the job `test-validator-db` in `api/.gitlab-ci.yml`; this
289
+ sentence does not keep a second copy of it. They are part of
290
+ `npm run test:unit`, and in CI that job gives them a database of its own (owner
291
+ decision
286
292
  [`ci-validator-db-job` 001](../../../docs/governance/confirmations/ci-validator-db-job.md)).
287
293
 
288
- Neither suite looks for a database. It is **declared**, under the names this
294
+ No such suite looks for a database. It is **declared**, under the names this
289
295
  package's own CLI reads:
290
296
 
291
297
  | Variable | What it is |
@@ -310,9 +316,14 @@ DB_HOST=gen_mariadb10.5 DB_PORT=3306 \
310
316
  CI_DB_ROOT_USER=root \
311
317
  CI_DB_ROOT_PASSWORD="$(grep '^MARIADB_ROOT_PASSWORD=' ../../../config/env-active/gen-db.env | cut -d= -f2-)" \
312
318
  DB_DOCKER_NETWORK=gendb-network \
313
- npx jest tests/unit/setupDatabaseLive.integration.test.js tests/unit/dbCiAccount.integration.test.js
319
+ npx jest $(grep -rl 'helpers/liveDatabase' tests/unit)
314
320
  ```
315
321
 
322
+ Which suites that selects is not typed here either: it is the requirement of the
323
+ helper, so a suite that joins them is run by this block unedited, and
324
+ `npx jest --listTests $(grep -rl 'helpers/liveDatabase' tests/unit)` says which
325
+ files the next run will take before it takes them.
326
+
316
327
  The run prints which server it measured, or why it measured none — that line is
317
328
  what tells a green suite that looked from a green suite that did not.
318
329
 
@@ -735,7 +746,7 @@ the three already had an owner, so no second notation was introduced for them
735
746
 
736
747
  | file | row | decided by |
737
748
  |---|---|---|
738
- | `operations.json` | `C-OPS` | `service-validator-core` `validateOperationsSchema` — the mirror of the fifteen rules the Registry applies on registration |
749
+ | `operations.json` | `C-OPS` | two definitions, neither of them in this row. What each OPERATION must be: `service-validator-core` `validateOperationsSchema` — the mirror of the fifteen rules the Registry applies on registration, reached through `src/utils/operationsRules.js`. What the DOCUMENT must be (`schema_version: "3.0"`, at least one operation declared, and — for the two rails that generate a cookbook from it — `input`/`output` declared with `description` warned about): `src/utils/operationsDocumentRules.js`, which is deliberately NOT in the core — the Registry receives `doc.operations` alone, never the file, and generates no cookbook, so it has a rule about none of them. The row asks the first two; boot step 4 and the readiness score ask all three. Until d.465 this row said the first half and did not do it, while three other rails each restated a different subset of the rules from memory; until d.465b an empty operations map had four answers across those rails (error · warning · silence · finding) and a missing `schema_version` had none |
739
750
  | `integration-contract.json` | `C-CONTRACT` | `utils/bizCiGateContract.js` — the validator the CI gate already runs over this document, so the gate and the table can never disagree about it |
740
751
  | `config.json` | `C-SERVICE` | `src/manifest/checks/serviceConfig.js`, which had no owner before |
741
752
 
@@ -771,6 +782,39 @@ once, in `DEAD_KEYS` (`src/manifest/checks/serviceConfig.js`), and the manifest
771
782
  `why` is held equal to that list by its own test — so this paragraph names the class,
772
783
  never the count. The fix is always the same sentence — delete the key.
773
784
 
785
+ **`C-CI-CONNECTOR-ENV`** asks the other half of the connector question. `C-CONNECTORS`
786
+ above compares two declarations with each other; this row compares them with the ONE
787
+ environment they are exercised in every day. Measured 2026-09-18: three `emailer` tests
788
+ failed in CI on `[RuntimeConfig] Missing environment variable - MINIO_USE_SSL`, a name no
789
+ line of the service reads — `@onlineapps/conn-base-storage` resolves it, `required: true`,
790
+ with no default — so `ci:gate:contract` could not see it (`node_modules` is deliberately
791
+ outside its scan, § Environment contract) and `ci:gate:env` emits six `OA_CI_*` names and
792
+ measures nothing. The same suite passes locally, because the one-shot runner loads
793
+ `config/env-templates/shared.env` through `env_file` and that generated copy carries the
794
+ key; the job's `variables:` is the SECOND delivery of the same set, copied by hand in eight
795
+ repositories, and nothing compared it with the first.
796
+
797
+ The row is keyed by the service's own `requiredConnectors` and by the names the connector's
798
+ library resolves **without a default** — `CONNECTORS` in `src/utils/connectorContract.js`,
799
+ measured against that library's schema, so nothing is copied into the manifest. It is
800
+ deliberately NOT keyed by `required: true` in a library schema: the resolver's priority is
801
+ explicit config → env → default, so that flag is conditional, and four families satisfy it
802
+ while being no gap at all (`REDIS_HOST`/`REDIS_PORT`, which the wrapper passes explicitly;
803
+ `SERVICE_NAME` in `infrastructure-tools`, on the infra path;
804
+ `INFRA_REPORT_SMTP_REQUIRE_TLS`, read only for a fallback transport;
805
+ `LOG_MAX_SIZE_BYTES`/`LOG_MAX_FILES`, delegated to the file sink). A gate on that flag would
806
+ have reported findings nobody could fix on its first day (`automation-gates.md` §3).
807
+
808
+ Like `D-DB-CI-ACCOUNT` it reads only OUTSIDE the `oa-ci v1` block, which is `G-CI`'s, and
809
+ only the `variables:` of the job the row names — the MinIO sidecar's own `variables:`, two
810
+ levels deeper, is that container's environment and not the job's. It asks WHICH names that
811
+ job sets and deliberately not WHETHER a repository runs one, because "add a test job" is
812
+ another fix and one row with two fixes is two mechanisms under one name. It cannot see
813
+ GitLab's project-level variables, which live outside the repository by design, so it
814
+ measures only names with a platform value in `api/config/shared-env.json` — never a secret.
815
+ Owner decision
816
+ [`biz-service-manifest`](../../../docs/governance/confirmations/biz-service-manifest.md) 013.
817
+
774
818
  `service.url` is the one entry carrying a **date**: it is dead from wrapper **8.0.0**,
775
819
  not today. The published 7.0.0 still throws
776
820
  `Missing configuration - service.url is required`, so the key travels WITH the pin, which
@@ -1003,12 +1047,18 @@ becomes an artefact and therefore never reaches a service.
1003
1047
  npx oa-validate --library ../../logger-contract # one package
1004
1048
  npx oa-validate --library --all # every package the uniform finds
1005
1049
  npx oa-validate --library --all --workspace /path/to/workspace --json
1050
+ npx oa-validate --library ../../logger-contract --as-version 1.2.4 # judged as the version being published
1006
1051
  ```
1007
1052
 
1008
1053
  One table (`id · category · where · what · fix`), the `doc` pointer of every reported
1009
1054
  row, exit `0` / `1`; `2` when the run could not start. `--library` with neither a
1010
1055
  package directory nor `--all` is a usage error — nothing is guessed.
1011
1056
 
1057
+ `--as-version <X.Y.Z>` (one package only) is the question a publish dry run asks: the
1058
+ rows that judge a version — `L-CHANGELOG` — judge the version about to be written, not
1059
+ the one `package.json` still carries, and the finding names both. Without it the run
1060
+ audits the tree as it stands (d.1040).
1061
+
1012
1062
  **Who wears the uniform** is discovery, not a list: `api/shared/**/package.json` minus
1013
1063
  `**/node_modules/**` and `**/tests/fixtures/**`, bound to `api/config/libraries.json`.
1014
1064
  A package the SSOT does not know, and a name in the SSOT with no package, are both
@@ -1048,7 +1098,14 @@ the raw file text, so a module documenting the duty it obeys — "this module re
1048
1098
 
1049
1099
  `L-PACK-TESTS` measures the **effect**, not the file: eight packages keep `tests/` out of
1050
1100
  the tarball with a `files` allowlist and no `.npmignore` at all, and a row checking for
1051
- the file would report eight packages that are already right.
1101
+ the file would report eight packages that are already right. An allowlist entry reaches
1102
+ `tests/` when its first segment matches the name — `tests`, `*`, or `**` — so
1103
+ `["**/*.js"]` is a finding too. The row is about the package's own tier at its root: a
1104
+ `tests/` under `templates/**` is template content (this package ships
1105
+ `templates/business-service/tests/**` for `oa-sync-template --new`) and is never a finding.
1106
+ An ignore file is read the way npm walks it, negations included: `/tests/` followed by
1107
+ `!tests/**` ships the tier and is a finding naming the `!` line (d.1006); the reproduction is
1108
+ held against `npm pack --dry-run --json` by `libraryPackTests.npmPack.integration.test.js`.
1052
1109
 
1053
1110
  **What is deliberately NOT a row** is written down as such, in the manifest's `guidance`
1054
1111
  block, with the reason and the decision it rests on — a duty nothing can decide is
@@ -1221,12 +1278,41 @@ Rewrites the files the manifest declares in the classes `identical`, `generated`
1221
1278
  at — one definition, read twice. With no paths it plans every such row. Files of the `own`
1222
1279
  class — `src/**`, `tests/**`, the content of `docs/**` — are never written.
1223
1280
 
1281
+ **One run is the whole sync of a service.** "Bring this repository into line with
1282
+ everything the uniform generates" is `npx oa-sync-template --target .`, and nothing else:
1283
+ every generated row of the service uniform is a line of that run, `README.md` (row
1284
+ `G-README`) and `config/env-templates/shared.env` (row `G-SHARED-ENV`) included. What is
1285
+ NOT in it is `template` and `docs-region`, because their bearer is not a service —
1286
+ `api/templates/business-service` and the `api/docs/**` tree are other directories with
1287
+ other owners.
1288
+
1289
+ Until d.710 the run's row set was the manifest's `files` section, and `G-SHARED-ENV` is
1290
+ filed under `config`, where the manifest reports about a service's configuration. So
1291
+ `oa-validate` measured `shared.env` and the sync wrote nothing: a repository could finish
1292
+ the sync and be reported `NOT DEPLOYABLE` by the very manifest the sync reads (measured
1293
+ 2026-09-20 over a copy of `api_biz/hello` — `NOT DEPLOYABLE — 1 finding(s)` immediately
1294
+ after a full bare run). Instruction 3 of the library cascade asked eight repositories to
1295
+ "sync every row", which was in fact four commands each of them assembled for itself, and
1296
+ BIZ-ingest escalated on 2026-09-18 that the bare run does not carry that row. A row now
1297
+ says what it IS — `"class": "generated"` — wherever the manifest reports it from, and the
1298
+ sync reads that declaration rather than a section's name. A row joins by SAYING so: `own`
1299
+ and `forbidden` cannot drift in, and neither can a future row of any other section.
1300
+
1301
+ `G-SHARED-ENV` renders through `src/sync/sharedEnv.js`, the module that owns the shared
1302
+ key set, in the same expression its own CHECK evaluates — so the bare run, the `shared-env`
1303
+ subcommand and `oa-validate` cannot produce three opinions of one file. The subcommand
1304
+ stays, and not as a convenience: `api/` itself and the business-service template carry
1305
+ `shared.env` too, and neither is a service root the bare run could be pointed at.
1306
+
1224
1307
  **No workspace is required of the run itself** — only of the ROWS that read a workspace
1225
1308
  file, and since d.229 most of them read a file this package carries. The predicate is the
1226
1309
  one the manifest run uses (`rowNeedsWorkspace`), so the `fix` command of `F-INIT`,
1227
1310
  `F-JEST` and `F-RUNNER` can be typed where their finding is now raised: inside the service
1228
1311
  image. A row that does need one is printed `NOT RUN` by name with the `--workspace` fix,
1229
- never skipped in silence.
1312
+ never skipped in silence. Inside a service container that is exactly one line, and it names
1313
+ the row and the file: `NOT RUN G-SHARED-ENV config/env-templates/shared.env — the workspace
1314
+ root is not reachable, so api/config/shared-env.json cannot be read. Fix: …` — the shared
1315
+ key set has a platform file for an owner, so no pinned copy can answer for it.
1230
1316
 
1231
1317
  A row the manifest does not make renderable is **not a row of this run**. Today that is
1232
1318
  `G-PROD-IMAGE` and `G-SETUP`: they declare no `from:` reference, so nothing says what
@@ -1241,8 +1327,11 @@ choosing a position among somebody's own install steps; an installation document
1241
1327
  section, where writing one would mean rewriting somebody's prose — is printed `BLOCKED`
1242
1328
  and exits 1.
1243
1329
 
1244
- Three rows need more than a splice, and the module that owns their CHECK renders them, so
1245
- the generator cannot produce a file its own check rejects:
1330
+ Where a row needs more than a splice, the module that owns its CHECK renders it, so the
1331
+ generator cannot produce a file its own check rejects (`src/sync/uniformFiles.js` §
1332
+ `RENDERERS` is the whole list; a count written here would be a hand-maintained number,
1333
+ which `doc-code-binding.md` §1 forbids — it was wrong by one before d.710 and by two
1334
+ after):
1246
1335
 
1247
1336
  * **`F-RUNNER`** — the `oa-test-runner v1` block is the template's, except for `build`,
1248
1337
  `env_file` and `networks`, which the same row requires to equal the service the runner
@@ -1279,6 +1368,11 @@ the generator cannot produce a file its own check rejects:
1279
1368
  one is never rewritten, and a missing section is `BLOCKED` with the heading named. That
1280
1369
  a document is absent at all stays `G-SETUP`'s finding, through the installation
1281
1370
  contract's `SETUP_DOCS`, so one defect keeps one owner.
1371
+ * **`G-SHARED-ENV`** — `config/env-templates/shared.env`, the whole file, rendered by
1372
+ `src/sync/sharedEnv.js` from the referenced document `api/config/shared-env.json`. The
1373
+ row's `from:` names the owner and this module knows no key, no format and no path to it.
1374
+ It is the one row of the run whose owner is a workspace file, so it is also the one that
1375
+ can report `NOT RUN` in a detached checkout.
1282
1376
 
1283
1377
  `--check` writes nothing and exits 1, naming the first line that differs and what the run
1284
1378
  would put there — both sides, because one of them alone lies about half the runs: an
@@ -1301,7 +1395,7 @@ differs and on a file the packaged template has no source for.
1301
1395
 
1302
1396
  ### `oa-sync-template shared-env`
1303
1397
 
1304
- `config/env-templates/shared.env` is generated, never edited, never edited. The platform env manifest
1398
+ `config/env-templates/shared.env` is generated, never edited. The platform env manifest
1305
1399
  `api/config/shared-env.json` owns the shared key set — name, template value, why the
1306
1400
  platform shares it — and every bearer's copy is rendered from it, so a service can only
1307
1401
  add keys of its own in `service.env`.
@@ -1310,8 +1404,21 @@ add keys of its own in `service.env`.
1310
1404
  npx oa-sync-template shared-env --target <bearer dir> [--workspace <root>] [--check]
1311
1405
  ```
1312
1406
 
1407
+ Neither path in that sentence is written into this command. Since d.710c both come off
1408
+ the manifest row `G-SHARED-ENV`: `row.from` says which file owns the content, `row.path`
1409
+ says where the bearer keeps it, and the command reads the reference the same way every
1410
+ other row is read. It used to build the SSOT's location itself, from the workspace marker,
1411
+ beside the row that declares the same file to `oa-validate` — two declarations of one
1412
+ location, of which only one could be right the day it moved.
1413
+
1313
1414
  `--target` is the bearer: a service root, `api/` itself, or the business-service
1314
- template; `--workspace` is the directory holding `api/` and `api_biz/`, found upwards
1415
+ template. For a SERVICE this is not the only entrance and has not been since d.710: the
1416
+ bare run writes the same file through row `G-SHARED-ENV`, calling this same renderer on
1417
+ the same referenced document, so a service is synced by one command and the two entrances
1418
+ cannot disagree. The subcommand is what the other two bearers have, because neither `api/`
1419
+ nor the template is a service root.
1420
+
1421
+ `--workspace` is the directory holding `api/` and `api_biz/`, found upwards
1315
1422
  from the target when it is not given. `--check` writes nothing and exits 1 with the diff
1316
1423
  when the file on disk is not what the manifest renders; a run that cannot start at all
1317
1424
  exits 2. There is no flag pointing the run at another manifest — the shared key set has
@@ -397,7 +397,7 @@
397
397
  "path": "config/service/operations.json",
398
398
  "severity": "boot",
399
399
  "owner": "BIZ-general",
400
- "why": "the v3 dispatch table; the per-operation rules (handler, bundle_scope, forbidden v2 fields) are step 4 of the boot validation, which this row does not repeat",
400
+ "why": "the v3 dispatch table; the per-operation rules are asked of @onlineapps/service-validator-core, the one definition of what the Registry refuses on registration, so this row and boot step 4 can never disagree about them",
401
401
  "fix": "declare the service's operations in config/service/operations.json",
402
402
  "doc": "api/docs/governance/confirmations/biz-service-manifest.md \u00a72"
403
403
  },
@@ -420,6 +420,18 @@
420
420
  "fix": "make the two declarations agree: set requiredConnectors.<connector> to what the service really needs, or add the wrapper section that configures it / remove the one nothing requires",
421
421
  "doc": "api/docs/governance/confirmations/connector-contract-check.md"
422
422
  },
423
+ {
424
+ "id": "C-CI-CONNECTOR-ENV",
425
+ "check": "ci-connector-env",
426
+ "path": ".gitlab-ci.yml",
427
+ "block": "oa-ci v1",
428
+ "job": "test",
429
+ "severity": "deploy",
430
+ "owner": "BIZ-general",
431
+ "why": "the row above measures whether the two declarations agree; this one measures whether the ONE environment they are exercised in every day carries what they need. Measured 2026-09-18: three emailer tests failed in CI on \"[RuntimeConfig] Missing environment variable - MINIO_USE_SSL\", a name no line of the service reads — @onlineapps/conn-base-storage resolves it, required:true, with no default — so ci:gate:contract could not see it (node_modules is deliberately outside its scan) and ci:gate:env emits six OA_CI_* names and measures nothing. The same suite passes locally, because the one-shot runner loads config/env-templates/shared.env through env_file and that generated copy carries the key; the CI job's variables: is the SECOND delivery of the same set, copied by hand in eight repositories, and nothing compared it with the first. The row is keyed by the service's own requiredConnectors declaration and by the names the connector's library resolves without a default (CONNECTORS in utils/connectorContract.js) — never by required:true in a library schema, which is conditional and holds for four families of keys that are no gap at all. It reads the variables: of the test job OUTSIDE the oa-ci v1 block, which is G-CI's, and asks WHICH names that job sets, deliberately not WHETHER a repository runs one. It cannot see GitLab's project-level variables, so it measures only names with a platform value in api/config/shared-env.json, which belong in the file",
432
+ "fix": "add the missing name to the variables: block of the test job in .gitlab-ci.yml, below the oa-ci v1 block — with the value config/env-templates/shared.env declares for that key where the platform owns one, and with this job's own sidecar value where it does not",
433
+ "doc": "api/docs/governance/confirmations/biz-service-manifest.md"
434
+ },
423
435
  {
424
436
  "id": "C-SERVICE-DEAD",
425
437
  "check": "config-dead-keys",
@@ -441,13 +453,14 @@
441
453
  },
442
454
  {
443
455
  "id": "G-SHARED-ENV",
456
+ "class": "generated",
444
457
  "check": "shared-env-generated",
445
458
  "path": "config/env-templates/shared.env",
446
459
  "from": { "path": "api/config/shared-env.json", "text": true },
447
460
  "severity": "deploy",
448
461
  "owner": "BIZ-general",
449
462
  "why": "the shared key set is one fact with one owner; measured on 2026-09-09, none of the nine copies matched the platform file and one key existed in no copy at all. The owner is api/config/shared-env.json and this row renders from it with the renderer the sync command writes with, so a service cannot be synced and reported drifted in the same hour; a run that cannot REACH the workspace says NOT RUN and names the command, because a render published with this package answers about the day it was published, not about the SSOT; a run that does reach it and finds no api/config/shared-env.json in the api checkout stops instead - a missing SSOT in a workspace the run can read is a finding and never a NOT RUN (automation-gates.md §5), and the message names the checkout to update and the api ref a CI job has to move",
450
- "fix": "npx oa-sync-template shared-env --target .",
463
+ "fix": "npx oa-sync-template --target .",
451
464
  "doc": "api/docs/governance/confirmations/biz-service-manifest.md \u00a718"
452
465
  },
453
466
  {
@@ -174,12 +174,12 @@
174
174
  },
175
175
  {
176
176
  "id": "L-CHANGELOG",
177
- "check": "file-present",
177
+ "check": "library-changelog-entry",
178
178
  "path": "CHANGELOG.md",
179
179
  "severity": "publish",
180
180
  "owner": "BIZ-general",
181
181
  "why": "a published version whose change nobody wrote down is a version nobody can reason about afterwards",
182
- "fix": "add CHANGELOG.md with an entry for the version being published",
182
+ "fix": "write the entry for the version being published — the one in package.json, or the one --as-version names on a publish dry run — under \"## [<version>]\" in CHANGELOG.md — a heading with nothing under it does not count; a pin-only release says which pin moved",
183
183
  "doc": "api/docs/guides/library-publishing-process.md"
184
184
  },
185
185
  {
@@ -187,8 +187,8 @@
187
187
  "check": "library-readme",
188
188
  "severity": "publish",
189
189
  "owner": "BIZ-general",
190
- "why": "a README that cannot say which fact class it owns has no reason to exist, and nobody can tell a live one from a stale one",
191
- "fix": "give README.md the node header - \"> Owns: <the fact class this file is the single owner of>\" and \"> Status: current|draft|archived\"",
190
+ "why": "a README that cannot say which fact class it owns has no reason to exist, nobody can tell a live one from a stale one, and a version copied into it by hand goes stale without anyone noticing",
191
+ "fix": "give README.md the node header - \"> Owns: <the fact class this file is the single owner of>\" and \"> Status: current|draft|archived\" - and delete any hand-written package version from it; package.json owns the version",
192
192
  "doc": "api/docs/standards/INFRA-DOC-STANDARD.md"
193
193
  },
194
194
  {
package/package.json CHANGED
@@ -1,11 +1,17 @@
1
1
  {
2
2
  "name": "@onlineapps/conn-orch-validator",
3
- "version": "12.2.0",
3
+ "version": "13.0.0",
4
4
  "description": "Validation orchestrator for OA Drive microservices - coordinates validation across all layers (base, infra, orch, business)",
5
5
  "oa": {
6
6
  "category": "orchestration"
7
7
  },
8
8
  "main": "src/index.js",
9
+ "exports": {
10
+ ".": "./src/index.js",
11
+ "./manifests/*": "./manifests/*",
12
+ "./templates/*": "./templates/*",
13
+ "./package.json": "./package.json"
14
+ },
9
15
  "bin": {
10
16
  "oa-biz-ci-gate": "src/cli/biz-ci-gate.js",
11
17
  "oa-lint-scripts": "src/cli/oa-lint-scripts.js",
@@ -28,9 +34,11 @@
28
34
  "author": "OnlineApps",
29
35
  "license": "PROPRIETARY",
30
36
  "dependencies": {
31
- "@onlineapps/cookbook-core": "6.0.0",
37
+ "@onlineapps/cookbook-core": "7.0.0",
38
+ "@onlineapps/error-handler-core": "4.0.0",
39
+ "@onlineapps/handler-contract": "1.0.0",
32
40
  "@onlineapps/logger-contract": "2.0.0",
33
- "@onlineapps/service-validator-core": "2.1.0"
41
+ "@onlineapps/service-validator-core": "2.2.0"
34
42
  },
35
43
  "devDependencies": {
36
44
  "jest": "^29.5.0"