@onlineapps/conn-orch-validator 12.1.1 → 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 (59) hide show
  1. package/CHANGELOG.md +607 -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/.gitlab-ci.yml +203 -37
  53. package/templates/business-service/README.md +7 -4
  54. package/templates/business-service/config/env-templates/shared.env +1 -0
  55. package/templates/business-service/docs/80-setup/INSTALL.md +31 -3
  56. package/templates/business-service/docs/80-setup/PLATFORM_MATRIX.md +5 -2
  57. package/templates/business-service/src/config/index.js +15 -0
  58. package/TESTING_STRATEGY.md +0 -92
  59. package/jest.config.js +0 -37
package/CHANGELOG.md CHANGED
@@ -4,6 +4,613 @@ All notable changes to this package. Follows [Keep a Changelog](https://keepacha
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [13.0.0] — 2026-09-27
8
+
9
+ pin: `@onlineapps/cookbook-core` 6.0.0 → 7.0.0
10
+ pin: `@onlineapps/error-handler-core` 3.1.0 → 4.0.0
11
+ pin: `@onlineapps/handler-contract` 1.0.0 (nová závislost)
12
+ pin: `@onlineapps/service-validator-core` 2.1.0 → 2.2.0
13
+
14
+ ### Changed — BREAKING — `CookbookTestUtils.createControlFlowStep` vyrábí krok, který schéma V2.1 přijme (d.1060)
15
+
16
+ Generátor dřív vracel tvary, které schéma `@onlineapps/cookbook-core` nezná (`items`, `condition`,
17
+ `cases`/`branches` jako pole, `body` jako objekt), `step_id` s pomlčkou a řídicí krok bez `service`
18
+ (`docs/governance/confirmations/cookbook-execution-owner.md` 002).
19
+
20
+ - `options.service` je povinný; krok ho nese a výchozí vnořené tasky běží v téže službě.
21
+ - Tvary podle schématu: `foreach.iterator` + `body` jako pole, `switch.expression` + `cases` jako objekt,
22
+ `fork_join.branches` jako objekt, nově i typ `steps`; výchozí `step_id` odpovídá `^[a-zA-Z0-9_]+$`.
23
+ - Odmítá se jménem (`[CookbookTestUtils] createControlFlowStep('<typ>') - … Fix: …`): chybějící `service`,
24
+ volba `id` (dřív alias `step_id`) a typ mimo `foreach`, `switch`, `fork_join`, `steps`
25
+ (dřív vrátil napůl postavený krok).
26
+ - Testy: `tests/unit/cookbookStepIdentity.test.js` pouští každý vyrobený krok přes `validateCookbook`
27
+ z `@onlineapps/cookbook-core`; dva staré případy, které tvrdily původní tvar, nahrazeny.
28
+
29
+ ### Added — `oa-validate --library <packageDir> --as-version <X.Y.Z>`: `L-CHANGELOG` soudí verzi, která se vydává (d.1040)
30
+
31
+ Brána F10 (`scripts/ci/lib/prepublishGate.js`) dostává od `publish-library.sh` `--as-version
32
+ <NOVÁ>` a posouvala jí jen svá pravidla; uniforma (G7) běžela bez verze a `L-CHANGELOG` četl
33
+ `version` z `package.json` — tedy STAROU verzi. Suchý běh tak pustil chybějící `## [NOVÁ]`
34
+ (d.1033: `error-handler-core --as-version 4.0.0` PASS bez hlavičky) a balíček, jehož stará verze
35
+ záznam neměla, odmítl kvůli verzi, kterou nikdo nevydával (d.1038, `handler-contract`).
36
+
37
+ - CLI: `--as-version <X.Y.Z[-prerelease]>`, jen s `--library <packageDir>`. Bez hodnoty, ve
38
+ špatném tvaru, s `--all` nebo bez `--library` se odmítá jménem (exit 2, `Fix:`).
39
+ - `runManifest({ …, asVersion })` hodnotu na vstupu ověří (`isTargetVersion`, exportováno — jediný
40
+ vlastník tvaru, CLI se ptá téhož) a předá ji každé kontrole jako `asVersion`.
41
+ - `library-changelog-entry` soudí `asVersion`, je-li dána, jinak `version` z `package.json` —
42
+ dvě legitimní otázky (vydání × audit stromu), ne hodnota a její náhrada. Nález říká, kterou
43
+ soudil: `no entry for the version being published (4.0.0, given by --as-version; package.json
44
+ says 3.1.0)`. Bez `--as-version` je chování i text beze změny.
45
+
46
+ Text opravy řádku `L-CHANGELOG` (`manifests/library.manifest.json`) říkal „the version in
47
+ package.json" — při suchém běhu vydání tedy posílal psát záznam pro verzi, ze které se odchází.
48
+ Nově: „the version being published — the one in package.json, or the one --as-version names on a
49
+ publish dry run" (d.1040b).
50
+
51
+ Zapojení do brány (G7 předá `--as-version`) je mimo tento balíček (`scripts/ci/lib/prepublishGate.js`).
52
+
53
+ ### Changed — BREAKING: Tier-1 `test.timeout`, který není kladné číslo, se odmítá jménem (d.1001)
54
+
55
+ `_dispatchViaHandler` bral limit kroku jako `testConfig.timeout || this.timeout`. Zapsaná
56
+ `0`, `-1` nebo `NaN` tak tiše propadla na výchozí hodnotu běžce a řetězec `"5"` se dostal
57
+ do `setTimeout` jako řetězec — recept říkal jedno, běh dělal druhé
58
+ (`architecture-principles.md` §3). Nově rozhoduje jedno místo, `_stepTimeout(testConfig)`,
59
+ podle kontraktu `docs/biz/40-cookbooks/testing.md` § The `test` block: klíč chybí → výchozí
60
+ hodnota běžce (volba konstruktoru `timeout`); klíč je a není kladné konečné číslo → krok
61
+ selže dřív, než se handler zavolá (`result.request === null`), s hláškou
62
+ `[CookbookTestRunner] test.timeout must be a positive number of milliseconds - got <hodnota>.
63
+ Expected: … Fix: write a positive number (e.g. "timeout": 5000) in the cookbook's test block,
64
+ or drop the key.` Platná hodnota výchozí přebíjí jako dosud.
65
+
66
+ BREAKING pro recept, který nesl `0`, záporné číslo, `NaN`, `Infinity` nebo číslo jako
67
+ řetězec: dřív prošel, nově jeho kroky selžou. Změřeno před změnou nad 56 recepty
68
+ (`api/cookbooks`, `api_biz/*/tests/cookbooks` na `origin/devel`): 21 bez bloku `test`,
69
+ zbylé nesou `timeout` 5000, 10000 nebo 15000 — žádný recept se tím nemění.
70
+
71
+ ### Changed — BREAKING: Tier-1 běžec `correlation_id` a `workflow_id` z kroku nečte (d.1001)
72
+
73
+ Dřív `step.correlation_id || crypto.randomUUID()` a `step.workflow_id || correlationId`.
74
+ Formát kroku (`docs/biz/40-cookbooks/format.md` § Task step, `TaskStep`
75
+ v `cookbook.v2.schema.json`) ani jeden klíč nezná a produkce je z kroku nečte, takže
76
+ identitu běhu vyrábí jen běžec: `correlation_id` = nové `crypto.randomUUID()` pro každý
77
+ krok, `workflow_id` = to samé uuid. Krok, který klíče dál nese, se NEodmítá — `TaskStep`
78
+ drží `additionalProperties: true` záměrně (conf `parked-features` 001) a odmítnutí tady by
79
+ byla druhá kolej proti `@onlineapps/cookbook-core` — klíče se chovají jako každý neznámý
80
+ klíč: nečtou se. BREAKING pro Tier-1: recept, který těmi klíči nastavoval ctx handleru,
81
+ je nově nenastaví. Změřeno: 0 z 56 receptů je na kroku nese.
82
+
83
+ `step.person_id` se NEMĚNÍ a běžec ho čte dál: zda Tier-1 nese jednající osobu a odkud,
84
+ rozhoduje conf `testing-tenant-identity` 001 (koordinátor).
85
+
86
+ Doklad: `tests/unit/CookbookTestRunner.stepTimeoutAndIdentity.test.js` (RED 6 z 12 —
87
+ pět odmítnutí timeoutu a uuid místo hodnoty z kroku; kontrolní: chybějící timeout =
88
+ výchozí běžce, platný ho přebíjí, krok bez klíčů beze změny, `person_id` se čte) a
89
+ `…stepTimeoutAndIdentity.integration.test.js` přes `runCookbook()` s reálnými handlery
90
+ fixture (RED 2 z 3, kontrolní recept `two-steps.json` beze změny). Fixture služba má
91
+ novou operaci `sleep`, podle které je vidět, proti kterému limitu handler běžel.
92
+
93
+ ### Fixed — `L-PACK-TESTS` pozná allowlist glob, který pustí `tests/` do tarballu; `tests/` pod `templates/**` výslovně není tier balíčku; kořenový `jest.config.js` z tarballu pryč (d.1002)
94
+
95
+ - **Glob v `files`:** řádek dřív uznal jen doslovný záznam `tests`. Balíček s `files: ["**/*.js"]`
96
+ nebo `["*"]` přitom `tests/unit/*.test.js` do tarballu zabalí (změřeno `npm pack --dry-run --json`)
97
+ a řádek o tom mlčel. Teď záznam, jehož první segment odpovídá `tests` (`tests`, `*`, `**`, …),
98
+ je nález, který ten záznam jmenuje: `the files allowlist entry "**/*.js" reaches tests — the tier
99
+ would ship to every consumer`. Změnil se i text nálezu pro doslovné `tests` (dřív `the files
100
+ allowlist names tests …`). Glob omezený na kořen (`*.js`) adresář nezasáhne a nálezem není.
101
+ - **Šablona:** řádek měří jen `tests/` v kořeni balíčku, tedy jeho vlastní tier. `tests/` pod
102
+ `templates/**` je obsah šablony, kterou balíček vydává záměrně (`templates/business-service/tests/**`
103
+ pro `oa-sync-template --new`), a nálezem není. Teď to říkají docblock kontroly i README; testy
104
+ tvrdí obě strany.
105
+ - **Tarball validatoru:** `.npmignore` vylučuje kořenový `/jest.config.js`. Spouští jen vlastní sadu
106
+ balíčku a po instalaci ho nic nečte: grep `conn-orch-validator/jest` nad `shared/`, `infra/` a
107
+ `origin/devel` všech checkoutů pod `api_biz/` (10) dal 0. `npm pack --dry-run`: 116 → 115 souborů, ubyl právě
108
+ `jest.config.js`; `templates/business-service/jest.config.js` a obě šablonové `tests/**` zůstávají.
109
+
110
+ ### Fixed — `L-PACK-TESTS` čte negace `.npmignore` / `.gitignore`: `/tests/` a pozdější `!tests/**` je nález (d.1006)
111
+
112
+ Řádek četl z ignore souboru jen řádky, které `tests/` vylučují, a žádnou negaci — takže
113
+ `.npmignore` s `/tests/` a pod ním `!tests/**` prošel jako „vyloučeno", zatímco
114
+ `npm pack --dry-run --json` tier zabalil. Nově řádek přehrává npm procházku
115
+ (`testsShippedBy` v `src/manifest/checks/libraryTests.js`) nad skutečnými soubory
116
+ `tests/`: každý adresář i soubor soudí poslední řádek, který ho trefí; vyloučený adresář
117
+ se znovu otevře, když ho pozdější `!` vzor se `/` může trefit uvnitř (začíná jménem
118
+ adresáře nebo `**`) — vzor bez `/` (`!*.js`) ho neotevře. Nález jmenuje řádek, který tier
119
+ vrátil: `excludes tests/, then re-includes it with "!tests/**" — …`. Pořadí `files` →
120
+ `.npmignore` → `.gitignore` beze změny (`files` vyhrává, `.npmignore` se pak nečte).
121
+
122
+ Bez nové závislosti: `npm-packlist` ani `@npmcli/arborist` balíček nemá (`npm ls` prázdné),
123
+ a volat `npm` z PATH by bránu udělalo závislou na prostředí (`automation-gates.md` §1.1).
124
+ Že reprodukce odpovídá npm, drží `tests/unit/libraryPackTests.npmPack.integration.test.js`:
125
+ 20 tvarů (tabulka `tests/helpers/packTestsShapes.js`, sdílená s unit sadou) změřených
126
+ `npm pack --dry-run --json` (npm 11.12.1); bez `npm` v PATH je sada NOT RUN jménem.
127
+ Nad 29 balíčky `api/shared/**` řádek před i po změně 0 nálezů.
128
+
129
+ Exporty `ignoresTests` a `allowsTests` z `src/manifest/checks/libraryTests.js` odcházejí
130
+ (d.1006b) — čtyři otázky `change-discipline.md` § Removing: (1) vznikly v d.211 jako
131
+ pomocné predikáty řádku a exportovaly se vedle `checks`; (2) nesl je řádek `L-PACK-TESTS`,
132
+ který stojí dál; (3) nečte je nikdo — mimo soubor 0 výskytů v celém `api/` (testy
133
+ včetně) a uvnitř je nahradily `allowlistEntryReachingTests` (d.1002) a `testsShippedBy`
134
+ (d.1006); (4) náhrada je koncepčnější: řádek odpovídá jedním výpočtem nad skutečnými
135
+ soubory, ne predikátem nad textem. Modul exportuje jen `checks`.
136
+
137
+ ### Added — Tier-1 běžec zná epilog receptu: `compensationStep` a scope `global_error_handler._failure` (d.945, conf meditest-manual-trigger 009)
138
+
139
+ Recept smí deklarovat `global_error_handler: { strategy: 'compensate', compensationStep: '<poslední krok>' }`.
140
+ Běžec se teď chová jako engine (`WorkflowOrchestrator`, d.934):
141
+
142
+ - běžný běh k epilogovému kroku nikdy nedojde (šťastná cesta ho nespustí);
143
+ - recept s deklarací skončí u PRVNÍHO kroku s `passed === false` (handler vyhodil, nebo `expect`
144
+ nesplněn) a pak jednou spustí `compensationStep`; ve scope je pád jako
145
+ `{{global_error_handler._failure.<pole>}}` se šesti klíči `step_id`, `service`, `operation`,
146
+ `error_type`, `error_code`, `details`. `error_type` je odpověď klasifikátoru enginu (d.982, viz
147
+ `### Changed` níže), `error_code` a `details` jsou hodnoty vyhozené chyby, jinak `null` — nic se
148
+ nedomýšlí;
149
+ - padlý epilog další epilog nespustí;
150
+ - výsledek `runCookbook` nese aditivní `epilogue: { step_id, status, output? | error? }`, jen když
151
+ epilog běžel; počty kroků ani `steps` nemění;
152
+ - recept BEZ deklarace beze změny — včetně toho, že krok bez `expect` po selhání nechá běžet další kroky.
153
+
154
+ `runCookbook` nově volá `validateCookbook` z `@onlineapps/cookbook-core` (od d.980 PRVNÍ a bez
155
+ kopií v běžci — viz `### Changed` níže). Z formátu přibývá, co běžec sám neznal:
156
+ povinný `type` kroku, vzor `step_id` (`^[a-zA-Z0-9_]+$`), typ `workspace_id` (celé číslo — recept
157
+ s jiným odmítne celý, hláška jmenuje cestu klíče), pravidla epilogu (d.933: tvar deklarace; d.942:
158
+ `_failure` jen v epilogu) a odmítnutí pozičního odkazu `{{steps[0]…}}` (d.951: krok se adresuje
159
+ `step_id`, nikdy pozicí — dosud zůstával doslovným textem).
160
+
161
+ Testy a fixtury podle pravdy formátu: `type: "task"` doplněn do receptů fixtur i receptů psaných
162
+ v testech; `step_id` s pomlčkou přejmenovány na podtržítko (`^[a-zA-Z0-9_]+$`); testy workspace
163
+ a závorkového odkazu tvrdí odmítnutí formátem. Fixtura `v3-test-service` má operaci
164
+ `boom-with-details`.
165
+
166
+ ### Removed — druhá kolej zákazu klíče `id` u kroku (`utils/cookbookFormat.readCookbookSteps`) (d.985)
167
+
168
+ Zákaz klíče `id` u kroku vlastní cookbook-core (d.983: `Forbidden field 'id' at /steps/<n>/id - this key
169
+ must not be declared here …`). Běží PRVNÍ v Tier-1 (`CookbookTestRunner.validateCookbook`, d.980) i v readiness
170
+ cestě (`CookbookTestUtils.validateCookbook`, d.984), takže kopie v `readCookbookSteps` byla v obou cestách
171
+ nedosažitelná. Hláška `[CookbookFormat] step "<id>" carries "id" - Expected: "step_id" only …` zaniká. Krok
172
+ s `id` se odmítá dál, jen slovy cookbook-core; `readCookbookSteps` zůstává pro čtenáře nevalidovaného receptu
173
+ (`compareCookbooks`, `_operationsOf`, `hasExpectClauses`) a odmítá už jen `steps`, které nejsou pole.
174
+ Test `cookbookStepIdentity.test.js` hlídá, že literál staré hlášky v `src/` nevznikne znovu.
175
+
176
+ ### Changed — readiness cesta (`CookbookTestUtils.validateCookbook`) volá `validateCookbook` z `@onlineapps/cookbook-core` PRVNÍ; syntetický recept `createServiceReadinessTests` má `step_id` tvaru `test_<operace>` (d.984)
177
+
178
+ Totéž, co d.980 udělal Tier-1 běžci, teď platí i pro cestu readiness wrapperu
179
+ (`ServiceReadinessValidator.checkCookbookExecution` ← `createServiceReadinessTests`): kopie kontrol
180
+ receptu v `CookbookTestUtils.validateCookbook` jsou pryč a odmítnutí formuluje cookbook-core. Metoda
181
+ dál vrací `{ valid, errors }`; odmítnutí je jediná položka `errors`. Mizí tyto hlášky readiness cesty
182
+ (vpravo podřetězec hlášky cookbook-core, která je nahrazuje):
183
+
184
+ | Mizející hláška readiness cesty | Nově (cookbook-core) |
185
+ |---|---|
186
+ | `Cookbook is required` | `Invalid type for 'root' at /. Expected object, got null.` |
187
+ | `cookbook format version is missing …` | `Missing required field 'version' at /.` |
188
+ | `cookbook format version "<v>" is below the required minimum 2.1.0 …` | `FAIL-FAST: V1 format (1.x.x) is BANNED` / `FAIL-FAST: V2.0 format (2.0.x) is BANNED` |
189
+ | `cookbook format version <v> is not a version string …` | `Invalid format for 'version' at /version. Must match pattern: ^2\.\d+\.\d+$` |
190
+ | `[CookbookFormat] cookbook "steps" is an object, not an array …` | `Invalid type for 'steps' at /steps. Expected array, got object.` |
191
+ | `Steps array is required` | `Missing required field 'steps' at /.` / `must NOT have fewer than 1 items (got 0 items)` |
192
+ | `Step <i>: step_id is required` | `Missing required field 'step_id' at /steps/<i>.` |
193
+ | `Step <i>: type is required` | `Missing required field 'type' at /steps/<i>.` |
194
+ | `Step <i>: service is required for task steps` | `Missing required field 'service' at /steps/<i>.` |
195
+
196
+ Recept, který cookbook-core odmítne, readiness cesta nově odmítne i tehdy, když ho dřív propustila
197
+ (např. `step_id` s pomlčkou, chybějící `operation`). Jiná výjimka než `CookbookValidationError`
198
+ z cookbook-core se nepřevádí na nález; propadne do `checkCookbookExecution`, který ji hlásí jako
199
+ neprošlou kontrolu. Vlastní pravidlo readiness cesta nemá žádné: zákaz klíče `id` u kroku odmítá
200
+ cookbook-core (d.983, `Forbidden field 'id' at /steps/<n>/id`) a kopie v `readCookbookSteps` zanikla
201
+ s d.985 (`### Removed` níže).
202
+
203
+ `createServiceReadinessTests` skládá syntetický recept v nové funkci `buildReadinessCookbook`
204
+ (export modulu `helpers/createServiceReadinessTests`, veřejný export balíčku se nemění). `step_id`
205
+ kroku je `test_<jméno operace>`, kde se každý znak mimo `[a-zA-Z0-9_]` nahradí `_` (dřív
206
+ `test-<jméno>`, což vzor `step_id` formátu porušovalo u každé operace). Když dvě operace dostanou
207
+ stejné `step_id`, pomocník to odmítne hláškou
208
+ `[createServiceReadinessTests] Operations "<a>" and "<b>" map to the same step_id "<id>" - …`. Pro biz
209
+ readiness sady (`tests/bootstrap/service-readiness.test.js`) se mění jen tvar syntetického `step_id`;
210
+ jejich tvrzení je průchod a ten platí (ověřeno nad kopiemi `operations.json` služeb hello, converter,
211
+ ingest, pdfgen).
212
+
213
+ `utils/cookbookFormat.checkCookbookFormatVersion` ztratil posledního čtenáře a zanikl i se svými
214
+ testy; `readCookbookSteps`, `stepIdentityOf` a `MIN_COOKBOOK_FORMAT_VERSION` zůstávají.
215
+
216
+ ### Changed — Tier-1 `_failure.error_type` je string z klasifikátoru enginu, ne `null` (d.982)
217
+
218
+ `cookbook-core` `RunFailure.error_type` je povinný string; běžec psal `null`, takže recept, který
219
+ `_failure` předá dál (ingest `ack-source-delivery` čte `failure.error_type` jako string), by v Tier-1
220
+ padl na schématu. Běžec teď `error_type` získá týmž klasifikátorem jako engine:
221
+ `ErrorClassifier.classify` z `@onlineapps/error-handler-core` — jádro, na které deleguje
222
+ `classifyError` konektoru `@onlineapps/conn-infra-error-handler`, jehož instanci engine dostává.
223
+ Jedna instance na běžce.
224
+
225
+ - vyhozená chyba handleru → co klasifikátor vrátí pro ni (např. `ValidationError` z
226
+ `@onlineapps/handler-contract` → `VALIDATION`, obecný `Error` bez známého kódu a statusu → `UNKNOWN`);
227
+ - nesplněný `expect` bez vyhozené chyby → `classify(null)` = `UNKNOWN`; Tier-1 `expect` v enginu
228
+ protějšek nemá, `error_code` a `details` zůstávají `null`.
229
+
230
+ Nová závislost: `@onlineapps/error-handler-core` (exact pin na verzi, kterou pinuje
231
+ `@onlineapps/conn-infra-error-handler` orchestrátoru — táž třída `ErrorClassifier`).
232
+ Tvar `results.epilogue` a běh receptu bez deklarace epilogu se nemění.
233
+
234
+ ### Changed — Tier-1 běžec nedrží druhou kopii kontrol receptu: `validateCookbook` z `@onlineapps/cookbook-core` běží PRVNÍ, jeho hlášky nahrazují hlášky běžce (d.980)
235
+
236
+ `CookbookTestRunner.validateCookbook()` (a tím `runCookbook`) volá nejdřív `validateCookbook`
237
+ z `@onlineapps/cookbook-core` — tutéž funkci, na které hází přijímající strana. Kopie jeho pravidel
238
+ v běžci jsou pryč (jedna kolej, `change-discipline.md` § One rail per concern). Co recept odmítne,
239
+ se nemění; mění se SLOVA, která biz vlákno čte ve výstupu Tier-1 (záznam `cookbook-load-failure`
240
+ v `results.steps[].error`, řádek chyb kroku 4 validace). Mizí tyto hlášky běžce a nahrazuje je
241
+ hláška cookbook-core (vpravo podřetězec, který se v ní objeví):
242
+
243
+ | Mizející hláška běžce | Nově (cookbook-core) |
244
+ |---|---|
245
+ | `[CookbookTestRunner] Cookbook is not a JSON object …` | `Invalid type for 'root' at /. Expected object, got <typ>.` |
246
+ | `[CookbookTestRunner] cookbook format version is missing …` | `Missing required field 'version' at /.` (prázdný řetězec: `Invalid format for 'version' at /version.`) |
247
+ | `[CookbookTestRunner] cookbook format version "<v>" is not a version string …` | `Invalid format for 'version' at /version. Must match pattern: ^2\.\d+\.\d+$` |
248
+ | `[CookbookTestRunner] cookbook format version "<v>" is below the required minimum 2.1.0 …` | `FAIL-FAST: V1 format (1.x.x) is BANNED` / `FAIL-FAST: V2.0 format (2.0.x) is BANNED` |
249
+ | `[CookbookTestRunner] Cookbook has no steps …` | `Missing required field 'steps' at /.` (`null`: `Invalid type for 'steps' at /steps. Expected array, got null.`) |
250
+ | `[CookbookFormat] cookbook "steps" is an object, not an array …` | `Invalid type for 'steps' at /steps. Expected array, got object.` |
251
+ | `[CookbookTestRunner] Cookbook must have at least one step …` | `Invalid value for 'steps' at /steps - must NOT have fewer than 1 items (got 0 items).` |
252
+ | `[CookbookTestRunner] Step #<n> has no step_id …` | `Missing required field 'step_id' at /steps/<n-1>.` |
253
+ | `[CookbookTestRunner] Step <id> must have service …` | `Missing required field 'service' at /steps/<i>.` (prázdný: `Invalid value for 'service' …`) |
254
+ | `[CookbookTestRunner] Step <id> must have operation …` | `Missing required field 'operation' at /steps/<i>.` |
255
+ | `[CookbookTestRunner] Step <id> references an undefined step …` | `Step references that cannot resolve at run time - <id>.input: {{steps.<x>…}} - unknown step '<x>'.` |
256
+ | `… references an undefined step - "{{steps.0…}}" names "0"` | `… - a step is addressed by its step_id, never by position` |
257
+ | `[CookbookTestRunner] Step <id> depends on an undefined step …` | `Step '<id>' depends on non-existent step_id '<x>'` |
258
+
259
+ Soubor receptu se dál připojuje na konec (`(cookbook file: <cesta>)`); tvar `results`, `epilogue`,
260
+ počty ani `kind` záznamu se nemění.
261
+
262
+ `version` jiného typu než řetězec (např. číslo `2.1`) cookbook-core vlny 3 odmítá pojmenovanou vadou
263
+ `Invalid type for 'version' at /version. Expected string, got number.` (d.983; do té doby padal
264
+ v `detectVersion` na `TypeError`, nahlášeno s d.980).
265
+
266
+ Běžci zůstává jen to, co cookbook-core nedělá: odmítnutí `test.mockInfrastructure` jménem; odmítnutí volání
267
+ helperu `{{name(…)}}` ve vstupu kroku (Tier-1 nemá registr helperů); hodnota `workspace_id`
268
+ (`assertWorkspaceId` při spuštění kroku). `CookbookTestRunner.validateCookbook()` nově odmítá
269
+ i to, co dřív odmítal až `runCookbook` (povinný `type`, vzor `step_id` …), protože cookbook-core
270
+ běží v něm.
271
+
272
+ ### Changed — BREAKING: `parseHandlerRef` čte tvar handler ref z `@onlineapps/service-validator-core`; export `HANDLER_REF_PATTERN` zaniká (d.962, d.465d)
273
+
274
+ `src/utils/handlerRef.js` nedrží vlastní literál tvaru `handlers/<cesta>#<export>`: `parseHandlerRef` testuje
275
+ proti `HANDLER_REF_REGEX` z veřejného vstupu `@onlineapps/service-validator-core` (export z d.720), tedy proti
276
+ TÉMUŽ objektu, podle kterého registr odmítá `operations.json`. Dvě kopie jednoho pravidla se rozejdou v den,
277
+ kdy se jedna opraví (`change-discipline.md` § One rail per concern). Chování se nemění — oba výrazy změřeny nad
278
+ stejnou sadou vzorků (`handlers/a/b#fn`, `handlers/a.b#fn`, `handlers/a#1x`, `handlers//x#f` a dalších), 0 rozdílů.
279
+
280
+ BREAKING: vstup balíčku už nevyváží `HANDLER_REF_PATTERN`. Čtenáři mimo balíček: 0 (změřeno
281
+ `grep -rn --exclude-dir=node_modules` nad `api/shared`, `api/infra`, `api/scripts`, `api/templates`, `api_biz`,
282
+ `fe_adminui`). Kdo tvar potřebuje jako hodnotu, čte `require('@onlineapps/service-validator-core').HANDLER_REF_REGEX`.
283
+
284
+ Vyžaduje `@onlineapps/service-validator-core` s exportem `HANDLER_REF_REGEX` na vstupu balíčku (dnes v jeho
285
+ `[Unreleased]`; vydaná 2.1.0 ho nemá).
286
+
287
+ Stráž: `tests/unit/handlerRefSingleSource.test.js` — pod `src/` není literál `^handlers\/` ani hluboký import
288
+ `@onlineapps/<balíček>/src/…`; `tests/unit/handlerRef.test.js` měří identitu (`jest.spyOn` na exportovaném objektu).
289
+
290
+ ### Fixed — unit sada nepřepisuje `manifests/biz-service.manifest.json` balíčku (d.965)
291
+
292
+ Dvě sondy v `tests/unit/sharedEnvCli.test.js` přepisovaly zabalený manifest a v `finally` ho vracely, takže paralelní
293
+ worker jestu, který ho v tu chvíli četl, dostal `[Manifest] Manifest is not valid JSON` (náhodně červený
294
+ `templateRunnerRuntime`); sondy teď běží nad kopií manifestu v dočasném adresáři a stráž `afterEach` shodí test, který
295
+ zabalený manifest změní (mtime nebo obsah).
296
+
297
+ ### Changed — Tier-1 běžec předává handleru skutečný `OperationContext` (conf handler-contract 001)
298
+
299
+ `CookbookTestRunner._dispatchViaHandler` staví `ctx` jako `new OperationContext({...})` z
300
+ `@onlineapps/handler-contract` místo objektového literálu — tutéž třídu, kterou handler dostává
301
+ v produkci od `@onlineapps/service-wrapper`. Import jde z balíčku kontraktu (L1), ne z wrapperu (L4),
302
+ takže běžec (L3) nemá zpětnou hranu (architecture-principles §7).
303
+
304
+ Co to pro handler znamená pod běžcem:
305
+
306
+ - `ctx.requireActingPerson()` vrací `step.person_id`; krok bez něj skončí 400 `VALIDATION_FAILED`
307
+ (dosud `TypeError: ctx.requireActingPerson is not a function`, 500).
308
+ - `ctx.normalizeDateRange()` funguje jako v produkci (dosud `TypeError`, 500).
309
+ - `ctx` je zmrazený (`Object.isFrozen(ctx) === true`): zápis do slotu hodí v strict mode
310
+ `TypeError`, stejně jako v produkci. Dosud byl literál běžce zapisovatelný.
311
+
312
+ Sada slotů se NEmění (conf handler-contract 001, čtení 4): runner-only `headers` a `service_name`
313
+ zůstávají, `config` ani `scheduler` běžec nestaví. Nová závislost `@onlineapps/handler-contract`
314
+ `1.0.0` (exact).
315
+
316
+ ### Fixed — šablona biz služby nese `src/config/index.js`, ze kterého wrapper čte konfiguraci (d.957)
317
+
318
+ `bootstrap(__dirname)` z `@onlineapps/service-wrapper` čte konfiguraci služby jen z `<serviceRoot>/src/config`
319
+ a `service.env` bere z toho, co tam vrátí `ConfigLoader.loadAll({ basePath, env: process.env })`. Všech osm biz služeb
320
+ ten soubor nese se stejným kódem; `templates/business-service` ho neměl, takže služba vytvořená `oa-sync-template --new`
321
+ (i `scripts/add-service.sh`, který jde touž cestou) by nenabootovala. Šablona teď nese `src/config/index.js` jako
322
+ bajtovou kopii souboru služeb converter, hello a ingest; zrcadlo `api/templates/business-service` ho nese také
323
+ (`tests/unit/templateMirror.test.js`) a README šablony ho uvádí v § Structure. Sada `tests/unit/templateServiceConfig.test.js`
324
+ měří, že vyrenderovaná služba soubor má a že předá loaderu kořen služby a `process.env` a vyveze, co loader vrátil
325
+ (wrapper v ní zastupuje zapsaný stub souseda). Stávající služby se nemění.
326
+
327
+ ### Fixed — `oa-validate --library` nad balíčkem z jiného checkoutu skončí fail-fast, ne falešným nálezem (d.940)
328
+
329
+ Běh jednoho balíčku měří balíček proti api checkoutu, za který kopie validatoru mluví (`workspaceRoot.js`
330
+ § apiCheckoutOf); balíček ze sourozeneckého checkoutu (typicky git worktree vedle `api/`) tak dostal `L-README-REGION`
331
+ s fixem `npx oa-sync-template readme-uniform --all`, který by README přepsal na odkaz do cizího checkoutu, a `where`
332
+ s jménem cizího adresáře. Nově `requirePackageInApiCheckout()` běh zastaví před prvním řádkem hláškou
333
+ `[ManifestWorkspace] <balíček> lies outside the checkout this validator speaks for (<checkout>) - Fix: run oa-validate
334
+ from the checkout that carries the package: node <jeho checkout>/…/oa-validate.js --library <balíček>` (exit 2); leží-li
335
+ balíček v adresáři, nad kterým žádný checkout není, hláška to říká a příkaz nevymýšlí. Balíček uvnitř checkoutu a běh bez
336
+ workspace (`NOT RESOLVED`) se nemění. Sada: `tests/unit/manifestWorkspaceRoot.test.js` § a package in a sibling checkout.
337
+
338
+ ### Changed — `only_with` je vlastnost ŘÁDKU manifestu, ne jedné kontroly (d.838)
339
+
340
+ Podmíněný řádek `only_with: <cesta>` existuje od `S-INT-C`: skript, který ukazuje na sadu,
341
+ kterou repozitář nemá, je deklarace bez obsahu. Vyhodnocovala ho ale skriptová kontrola sama
342
+ (`checks/serviceScripts.js`), takže kterýkoli jiný druh řádku podmínku ignoroval — `only_with`
343
+ nad `config/service/*` by v manifestu stálo a v běhu nerozhodlo nic. Pravidlo viditelné
344
+ v deklaraci a nepřítomné v běhu je ta tichá neúčinnost, kterou `automation-gates.md` §5 počítá
345
+ za vadu.
346
+
347
+ Od téhle změny ho čte engine, který řádky rozděluje na kontroly (`manifest/runManifest.js`,
348
+ `rowApplies`), a čte ho pro VŠECHNY scope — `service`, `workspace` i `bearer` (u nosičů per
349
+ nosič, protože podmínka je o tom, co nese konkrétní repozitář).
350
+
351
+ Chování se nemění: řádek, jehož podmínka neplatí, neodpovídá nic — žádný nález a žádné NOT RUN,
352
+ přesně jako dosud u `S-INT-C`. NOT RUN by znamenalo „nemohl jsem posoudit"; tady není co
353
+ posuzovat, řádek pro takovou službu neplatí. Zachována je i ta část, na které `S-INT-C` stojí:
354
+ adresář, který existuje a je prázdný, podmínku NESPLŇUJE.
355
+
356
+ Žádný nový řádek uniformy tím nevzniká — mechanismus se zobecňuje dopředu, pro podmíněný řádek
357
+ nad `config/service/*`, který přijde s pinem po vlně, jež vydá jeho schéma.
358
+
359
+ ### Changed — BREAKING: veřejnou plochou balíčku je mapa `exports`, ne strom souborů (d.812)
360
+
361
+ `package.json` nese mapu `exports` se čtyřmi klíči: `.` (→ `src/index.js`), `./manifests/*`, `./templates/*`
362
+ a `./package.json`. Každá jiná cesta do balíčku — typicky `require('@onlineapps/conn-orch-validator/src/utils/deployContract.js')`
363
+ — končí chybou `ERR_PACKAGE_PATH_NOT_EXPORTED`; interní moduly (`src/**`, včetně `src/cli/*`) přestávají být importovatelné.
364
+ Mapy `bin` se to netýká: `oa-validate`, `oa-biz-ci-gate`, `oa-lint-scripts` i `oa-sync-template` běží dál. Schopnost, kterou
365
+ jediný změřený konzument bral hlubokou cestou (`api/scripts/verify-biz-deploy-contract.sh`), dostala jméno na vstupním bodě:
366
+ `verifyDeployContract`, `DEPLOY_CONTRACT_REQUIREMENTS`, `DEPLOY_CONTRACT_SCOPE`. Konzument s hlubokým `require` přejde na tyto
367
+ exporty (nebo na `manifests/*` / `templates/*`). Mapu drží `tests/unit/packageExports.test.js`, seznam jmen vstupního bodu
368
+ `tests/unit/indexExports.test.js`.
369
+
370
+ ### Fixed — varování kroku 1 (struktura) se dostanou do souhrnu validace (d.791c)
371
+
372
+ `ValidationOrchestrator` z kroku 1 sbíral jen `errors`; `warnings`, které `ServiceStructureValidator` plní (např.
373
+ `STANDARD_LEVEL_GAP`), tiše zahazoval, zatímco krok 4 je sbíral. Nově jsou v `results.warnings` se `step: structure`.
374
+ Verdikt se nemění — nikdo ho podle počtu varování nepočítá; služba může v souhrnu uvidět varování, které tam dřív nebylo
375
+ (změřeno nad `api_biz/*`: jedno, `STANDARD_LEVEL_GAP` u ingestu).
376
+
377
+ ### Changed — BREAKING: R8 permit pro `require` modulu namespace žádá vykonávaný řádek, ne zmínku (d.782b)
378
+
379
+ `src/utils/deployContract.js`: permit R8 „soubor si vyžádá modul test-namespace“ se měřil nad celým textem souboru, takže
380
+ komentář `// TODO: require('./testNamespace')` soubor z bezpečnostní hranice R8 vyvázal. Nově platí `matchesOnExecutingLine()`
381
+ jako u ostatních permitů; regex má jméno `REQUIRE_NAMESPACE_MODULE`. Integrační test, který require nese jen v komentáři,
382
+ je teď nález R8. Změřeno nad `api_biz/*`: 0 takových souborů, verdikt R8 se u žádné služby nemění.
383
+
384
+ ### Fixed — obě hlášky R8 jmenují obě cesty, které permit uznává (d.778, d.782)
385
+
386
+ Finální hláška R8 i hláška o souboru, který si staví vlastní databázi a nepřesměruje `DB_NAME`, radily jen
387
+ `require` modulu `tests/integration/testNamespace.js`. Nově jmenují obě uznávané cesty: volání `getTestNamespace()` /
388
+ `getForeignTestNamespace()` na vykonávaném řádku, nebo `require()` modulu repozitáře. Permit se nemění, jen text hlášek.
389
+
390
+ ### Fixed — `createServiceReadinessTests`: deklarovaný `timeout` platí pro všechny tři registrované testy (d.764b)
391
+
392
+ Volba `options.timeout` (výchozí 15000) se předávala jen prvnímu ze tří testů; zbylé dva běžely s výchozím rozpočtem jestu
393
+ (5000). Nově ji nesou všechny tři. Neplatná hodnota (včetně `0`, které jest čte jako „bez rozpočtu“) končí fail-fast hláškou
394
+ `[Context] … Fix:`; vynechaná volba nebo `undefined` zůstává výchozí 15000. Volajícím bez voleb se dvěma testům rozpočet
395
+ rozšiřuje z 5000 na 15000.
396
+
397
+ ### Changed — cesta k proofu má v balíčku jednoho vlastníka; proof na disku nepřeskočí nic (d.758)
398
+
399
+ `ValidationOrchestrator` bere cestu `conn-runtime/validation-proof.json` z `PROOF_RELATIVE_PATH` (`src/utils/preValidation.js`)
400
+ místo vlastního literálu. Chování se nemění: nová sada `tests/unit/proofFileIsOutputNotCache.test.js` dokládá hodnotou, že
401
+ starý proof položený na disk běh nepřeskočí a skončí přepsaný otiskem, který běh naměřil.
402
+
403
+ ### Fixed — citace dokumentů v hláškách a komentářích nesou tvar `api/…`; sweep nedoporučuje `service-common` (d.753b, d.753c, d.756, d.756b)
404
+
405
+ Citace začínající `/` (`See: /docs/biz/…`, `@see /api/…`) a citace bez prefixu ve vytištěných `fix:` hláškách
406
+ (`ServiceStructureValidator`, `sharedEnv`) nesou `api/docs/…` — tvar, který se ze workspace otevře a který čte `L008`.
407
+ Sondy v `tests/unit/serviceStructureMessages.test.js` a `tests/unit/readmePointerCli.test.js` měří každou citaci v `src/**`
408
+ (a v komentářích `tests/**`) proti disku. `ServiceStructureValidator.validatePackageJson()` přestal doporučovat
409
+ `@onlineapps/service-common` — týž soubor import retired hierarchie chyb odmítá; odmítnutí zůstává beze změny. Konzument,
410
+ který porovnává text hlášek doslova, uvidí nový tvar cesty. Neexportovaná konstanta `MIGRATIONS_README` (`manifest/checks/serviceDb.js`)
411
+ bez čtenáře smazána — cestu nese řádek `D-DB-README`.
412
+
413
+ ### Changed — jméno README má jednoho vlastníka, `src/sync/readmeFile.js` (d.739, d.739b, d.753)
414
+
415
+ `README_FILE` deklaruje jediný modul `src/sync/readmeFile.js`; `sync/readmePointer.js`, `sync/readmeLocation.js`,
416
+ `manifest/checks/libraryDocs.js` i nápověda a hlášky `oa-sync-template` ho importují. `src/cli/oa-sync-template.js` už
417
+ `README_FILE` nere-exportuje a `sync/readmeLocation.js` ho nevyváží (importéra mimo balíček neměl ani jeden). Vypsaný text
418
+ se nemění (`README_FILE === 'README.md'`).
419
+
420
+ ### Added — `L-README` odmítá ručně psanou verzi balíčku (d.732)
421
+
422
+ Kontrola řádku `L-README` (`manifest/checks/libraryDocs.js`) hlásí v README zápis `Version: X.Y.Z` a samostatné `vX.Y.Z`
423
+ — verzi vlastní `package.json`. Holá trojice bez popisku, konfigurační příklad (`serviceVersion: '1.0.0'`) ani jméno
424
+ závislosti (`lib-v2`) nálezem nejsou. Knihovna s verzí v README dostane od `oa-validate --library` nález.
425
+
426
+ ### Changed — `L-CHANGELOG` měří záznam vydávané verze, ne existenci souboru (d.731b)
427
+
428
+ Řádek běží jako kontrola `library-changelog-entry`: v `CHANGELOG.md` musí existovat sekce pro verzi z `package.json`
429
+ a nést aspoň jeden neprázdný řádek, který není podnadpis. Prerelease `X.Y.Z-rc.N` smí stát pod neprázdným `[Unreleased]`.
430
+ Tvar nadpisu se neřeší (`## [1.0.3] — …` i `## 1.0.0`). Knihovna s prázdnou sekcí vydávané verze je nově NOT PUBLISHABLE.
431
+
432
+ ### Changed — BREAKING: klíče `config.json` vlastní jen řádek `C-SERVICE` (d.726)
433
+
434
+ Krok 1 (`ServiceStructureValidator`) přestal žádat `service.name` vlastním seznamem; drží jen strukturu (soubor existuje,
435
+ čte se, parsuje). Klíče žádá řádek `C-SERVICE` (`manifest/checks/serviceConfig.js`, `REQUIRED_SERVICE_KEYS`). Typ nálezu
436
+ `MISSING_CONFIG_FIELD` zaniká — kdo ho čte, přejde na nález `C-SERVICE`. Služba bez `service.name` už neselže v kroku 1,
437
+ takže běh nově promluví i o dalších chybějících klíčích (např. `workspaceScoped`).
438
+
439
+ ### Changed — `oa-sync-template --target <služba>` uvede do souladu každý generovaný řádek, i `G-SHARED-ENV` (d.710, d.710c)
440
+
441
+ Holý běh psal jen řádky sekce `files`; `config/env-templates/shared.env` (řádek `G-SHARED-ENV` v sekci `config`) nezapsal,
442
+ takže služba po syncu mohla být NOT DEPLOYABLE. Nově běh bere každý řádek, který o sobě deklaruje `"class": "generated"`,
443
+ a bez workspace nad sebou hlásí `NOT RUN G-SHARED-ENV …`. Podpříkaz `shared-env` bere umístění SSOT i cíle z řádku
444
+ `G-SHARED-ENV` (`from`, `path`), ne z vlastních konstant; chybějící SSOT hlásí sdílenou větou `[ManifestDiscovery] Referenced
445
+ file not found …`. `fix` řádku `G-SHARED-ENV` je nově `npx oa-sync-template --target .`. Exporty CLI `MANIFEST_RELATIVE`
446
+ a `SHARED_ENV_RELATIVE` smazány (bez importéra).
447
+
448
+ ### Fixed — R2 čte stráž předka po jobech a komentář symetricky; top-level klíč YAML má jednu definici (d.707, d.707b, d.707c, d.707d)
449
+
450
+ `src/utils/deployContract.js`: stráž `git merge-base --is-ancestor` kryje `git reset --hard <commit>` jen v TÉMŽE top-level
451
+ jobu a na dřívější pozici — stráž v sousedním jobu reset nekryje. Řádek, který začíná komentářem (`#`), neběží: zakomentovaná
452
+ stráž nekryje a zakomentovaný `git pull`, `git merge` či `git reset --hard` se nepočítá (predikát `isCommentLine()`
453
+ s `YAML_COMMENT_OPENERS`). Rozklad na klíče nese nový `src/utils/yamlTopLevel.js` (`topLevelKeys`, `rewritableTopLevelKeys`),
454
+ sdílený s `src/sync/serviceTemplate.js`. Repozitář se stráží jen v jiném jobu nebo v komentáři nově dostane nález R2;
455
+ změřeno nad `api_biz/*` a balenou šablonou: bez nálezu.
456
+
457
+ ### Added — generovaný `shared.env` umí nést značku `# @per-machine` (d.666)
458
+
459
+ `src/sync/sharedEnv.js`: položka `config/shared-env.json` smí nést `perMachine: { reason: "<text>" }`; render pak nad `KEY=`
460
+ napíše `# @per-machine — <reason>`, tvar, který čte `scripts/lib/env-template-markers.sh`. Neplatný tvar (ne objekt, neznámé
461
+ pole, chybějící/prázdný/víceřádkový `reason`) končí hláškou `[SharedEnv] … Fix: …`. Zabalená kopie `shared.env` nese značku
462
+ u `NODE_ENV`.
463
+
464
+ ### Fixed — hláška o chybějící proměnné v hlavičce kroku končí `Fix:` (d.657)
465
+
466
+ `src/utils/resolveHeaders.js`: k dosavadní větě se připojuje `Fix:` — hodnotu nastavit v `env-active/*.env`, nebo
467
+ dát do `headers` kroku kuchařky literál. Dosavadní text stojí před ní doslova; kdy a jakou třídou se hází, se nemění.
468
+
469
+ ### Fixed — `oa-sync-template` čte platformní soubory přes konvenci `api/`, ne přes jméno adresáře (d.653, W653)
470
+
471
+ `shared-env` a `--new` skládaly cestu k `api/config/shared-env.json` (§ loadManifest) a
472
+ `api/config/libraries.json` (§ loadLibraries) prostým `path.join(workspaceRoot, …)`. `api/` v čele takové
473
+ cesty je konvence deklarujícího textu, nikdy jméno adresáře na disku (`src/manifest/workspaceRoot.js`
474
+ hlavička), a GitLab klonuje repozitář pod jménem projektu — oba podpříkazy tedy fungovaly na stroji vývojáře
475
+ a nikde jinde. Změřeno 2026-09-18 v obrazu jobu, jeden strom pod dvěma jmény:
476
+
477
+ infra-mono → [oa-sync-template] Missing platform env manifest - <workspace>/api/config/shared-env.json does not exist.
478
+ api → [oa-sync-template] shared-env is in sync - …/config/env-templates/shared.env
479
+
480
+ Cesta v té větě nepatří žádnému checkoutu toho workspace, takže podle ní nelze jednat
481
+ (`.claude/rules/automation-gates.md` §1 požadavek 4). V CI to shodilo `tests/scripts/shared-env-sync.bats:32,39`
482
+ a — přes `--new`, který čte oba soubory — `tests/scripts/readme-uniform-pointer.bats:248,260,302`. Obě místa
483
+ teď procházejí `resolveWorkspacePath()`, jediným vlastníkem té konvence.
484
+
485
+ ### Fixed — `workspaceAbove()` pozná checkout pod jakýmkoli jménem (d.652, W652)
486
+
487
+ Otázka „ve kterém workspace ten strom leží" se ptala na doslovný `api/config/services.json` nad každým
488
+ předkem, zatímco checkout se jmenuje podle projektu GitLabu. Změřeno 2026-09-18 nad stromem `infra-mono`
489
+ (`tests/scripts/readme-uniform-pointer.bats:241`, job `test-scripts` pipeline 2860587739): `null` pro strom,
490
+ který ležel uvnitř téhož workspace, ze kterého běh vyšel — a `oa-sync-template readme-uniform` pak zapsaný
491
+ soubor označil absolutní cestou, jménem, které na každém stroji znamená něco jiného. Ptá se teď
492
+ `apiCheckoutOf()`, tedy touž jednou kolejí jako tři otázky pod ní; štítek `oa-sync-template` zůstává
493
+ `<jméno checkoutu>/…`, což je jeho vlastní věc a tato změna se jí nedotýká.
494
+
495
+ ### Fixed — `where` nálezu nese konvenci `api/`, ne jméno adresáře checkoutu (d.652, W652)
496
+
497
+ `checks/libraryContext.js` § whereOf skládal `where` odečtením kořene workspace od kořene nesoucího, takže
498
+ hlavou cesty bylo **jméno adresáře** checkoutu. GitLab klonuje repozitář pod jménem projektu, a tentýž nález,
499
+ který lokálně ukazuje na `api/shared/connector/conn-orch-validator/…/deployment.md:3`, tak v CI ukázal na
500
+ `infra-mono/shared/connector/…` — cestu, která neexistuje pod žádným jménem, jež si čtenář může někam vložit
501
+ (`.claude/rules/automation-gates.md` §1 požadavek 4). Změřeno 2026-09-18 v obrazu CI nad `library-workspace`
502
+ přejmenovaným na `infra-mono`: tabulka nesla obě hlavy najednou —
503
+ `infra-mono/shared/lib-bad-core/package.json` vedle `api/shared/connector/conn-orch-validator/README.md`,
504
+ protože prefix už vzal `src/sync/readmeLocation.js` (d.625) a `whereOf` ne.
505
+
506
+ Konvence má teď jednoho vlastníka i pro zpětný směr: `manifest/workspaceRoot.js` § workspaceRelativeOf, inverze
507
+ k `resolveWorkspacePath` a totéž pravidlo, které `scripts/ci/lint-biz-docs.mjs` § workspaceRelativeOf aplikuje
508
+ na své vlastní nálezy. Čtenáři jsou dva a **musí** se shodnout: `whereOf` řetězec píše a `runManifest.js`
509
+ § buildScopeFilter jím filtruje, které workspace-nálezy si běh v režimu služby nechá — dvě hláskování jedné
510
+ cesty by v CI zahodila všechny nálezy o službě, které se běh ptal (`.claude/rules/change-discipline.md`
511
+ § One rail per concern).
512
+
513
+ ### Added — řádek uniformy `C-CI-CONNECTOR-ENV`: job `test` nastavuje klíče deklarovaného konektoru (d.635b, W635b)
514
+
515
+ Nový řádek kategorie `config` (check `ci-connector-env`, severita `deploy`): pro každý
516
+ `requiredConnectors.<c>: true` musí `variables:` jobu `test` v `.gitlab-ci.yml` — **mimo** blok `oa-ci v1`,
517
+ který patří `G-CI` — nastavovat každý klíč z `CONNECTORS[c].env`. Změřeno 2026-09-18: tři testy emaileru padly
518
+ v CI na `[RuntimeConfig] Missing environment variable - MINIO_USE_SSL`, jméno, které nečte žádný řádek služby —
519
+ resolvuje ho `@onlineapps/conn-base-storage` (`required: true`, bez defaultu), takže `ci:gate:contract` ho
520
+ nevidí (`node_modules` je vědomě mimo jeho rozsah) a `ci:gate:env` jen emituje šest `OA_CI_*` a neměří nic.
521
+ Lokálně táž sada projde: běžec čte `config/env-templates/shared.env` přes `env_file`. Blok `variables:` je
522
+ druhé doručení téže množiny, ručně opsané v osmi repech, a nic ho neporovnávalo s prvním.
523
+
524
+ Řádek se klíčuje deklarací služby a množinou, kterou knihovna konektoru resolvuje bez defaultu — ne příznakem
525
+ `required: true` v schématu knihovny, který je podmíněný (priorita explicitní config → env → default) a platí
526
+ i pro čtyři rodiny klíčů, kde mezera není. Čte `variables:` jednoho jmenovaného jobu, ne `variables:` sidecaru
527
+ o dvě úrovně hlouběji, a neptá se, **zda** repozitář job má — to je jiná oprava, tedy jiný řádek.
528
+ Projektové proměnné GitLabu nevidí, proto míří jen na klíče s platformovou hodnotou v `api/config/shared-env.json`.
529
+ Rozhodnutí vlastníka `biz-service-manifest` 013.
530
+
531
+ ### Changed — `CONNECTORS[minio].env` je pět klíčů, které `conn-base-storage` opravdu vyžaduje (d.635b, W635b)
532
+
533
+ Z `MINIO_ENDPOINT`, `MINIO_ACTUAL_HOST` na `MINIO_ENDPOINT`, `MINIO_PORT`, `MINIO_USE_SSL`, `MINIO_ACCESS_KEY`,
534
+ `MINIO_SECRET_KEY` — přesně ty, které `conn-base-storage/src/config.js` značí `required: true` bez defaultu.
535
+ `MINIO_ACTUAL_HOST` mezi nimi není: je to **volitelný** override hostitele proxy (`actualHost`, bez `required`),
536
+ takže by řádek vyžadoval klíč, který knihovna nepotřebuje; služby, které přes proxy jdou, ho deklarují tam, kde
537
+ ho čtou — placeholder `${MINIO_ACTUAL_HOST}` v `config/service/config.json`, což je pokrytí M1. Dopad na dva
538
+ další čtenáře téže množiny: M2 (`envContract.js`) pokrývá u služby s `minio: true` nově všech pět jmen, takže
539
+ `--env-reads` je vydá deploy bráně (všechny osmery `shared.env` je nesou); a krok bootu
540
+ `verifyConnectorContract` se dál ptá „aspoň jedno z nich", ale `MINIO_ACTUAL_HOST` sám o sobě už důkazem není.
541
+
542
+ ### Removed — `checks/scriptHeaders.js` nevyváží ani `SCRIPT_SCOPE` (d.636d, W636)
543
+
544
+ Druhý re-export téhož druhu: vznikl s modulem v d.218 (`c2aa5367`) a čtenáře nikdy neměl — všichni tři
545
+ (`oa-lint-scripts.js`, `lintScripts.js`, `lintScripts.test.js`) ho od začátku berou od vlastníka
546
+ `src/lint/scripts/lintScripts.js`. Most řádku `S-SCRIPTS` teď vyváží jen `checks`.
547
+
548
+ ### Removed — `checks/scriptHeaders.js` nevyváží `API_CHECKOUT` (d.636c, W636)
549
+
550
+ Jméno vzniklo s modulem v d.218 (`c2aa5367`) a čtenáře nikdy nemělo (`git grep -w` v každém commitu, který se
551
+ symbolu dotkl). Konvenci „hlava cesty je `api/`" vlastní od d.636b `workspaceRoot.API_PREFIX`; most řádku
552
+ `S-SCRIPTS` ji čte, ale nevydává pod druhým jménem. Věta NOT RUN se nemění a měří ji `manifestScriptHeaders`.
553
+
554
+ ### Documentation — příklad běhu živých DB sad nevypisuje soubory (d.636c, W636)
555
+
556
+ Vývojářský příkaz v § Running the live-database suites bral dvě sady ze tří; výběr teď dělá požadavek na
557
+ `tests/helpers/liveDatabase.js` (`npx jest $(grep -rl …)`), takže nová sada se přidá bez editace README.
558
+
559
+ ### Changed — prefix `api/` má jednoho vlastníka; čtenáři ho berou symbolem (d.636b, W636)
560
+
561
+ `workspaceRoot.js` vyváží `API_PREFIX`; `lintScripts.js` (dosud vlastní literál `'api/'`), řádek `scripts-lint`
562
+ a `readmeLocation.js` (dosud dvakrát hlava `WORKSPACE_MARKER`) čtou jeho hodnotu. Chování beze změny; sonda nad
563
+ `src/**` v `manifestWorkspaceRoot.test.js` hlídá, že druhé znění prefixu nepřibude.
564
+
565
+ ### Documentation — § Running the live-database suites nedrží vlastní počet sad (d.636a, W636)
566
+
567
+ Věta o živých DB sadách už nejmenuje „dvě“ (od d.620b jsou tři) ani je nevypisuje — seznamem je `script` jobu
568
+ `test-validator-db` v `api/.gitlab-ci.yml` a požadavek na `tests/helpers/liveDatabase.js`.
569
+
570
+ ### Fixed — řádek `oa-sync-template` jmenuje blok, když měřil jen blok (d.610)
571
+
572
+ U blokového řádku (`F-INIT`, `F-RUNNER`, `G-CI`) hlásí generátor `unchanged F-INIT init.sh (block "oa-deps-guard v1")` —
573
+ porovnal jen obsah uvnitř značek, zbytek souboru je vlastnictví služby. Řádek celého souboru závorku nemá. Kdo parsuje
574
+ výstup `oa-sync-template` doslova, uvidí u blokových řádků závorku navíc.
575
+
576
+ ### Changed — BREAKING: pravidla `operations.json` mají jednoho vlastníka (d.465, d.465b, d.465c)
577
+
578
+ Pravidla o JEDNÉ operaci se všude (krok 1, krok 4, readiness skóre, sada `createServiceReadinessTests`, řádek `C-OPS`) ptají
579
+ zrcadla registru `validateOperationsSchema` z `@onlineapps/service-validator-core` přes `src/utils/operationsRules.js` —
580
+ lokální kontrola tedy nově odmítne i `mutates`, `resource_type` a klíč operace, které registr odmítá. Pravidla o DOKUMENTU
581
+ (`src/utils/operationsDocumentRules.js`): prázdná mapa operací je všude error a `schema_version` musí být deklarován a roven
582
+ `"3.0"`; typy nálezů kroku 1 `INVALID_OPERATIONS_STRUCTURE`, `INVALID_OPERATIONS_TYPE`, `NO_OPERATIONS` a
583
+ `SCHEMA_VERSION_MISMATCH` zanikají ve prospěch `INVALID_OPERATIONS_DOCUMENT`. Úplnost deklarace (`input`, `output`,
584
+ `description`) hlásí krok 4 i readiness touž větou (`operationsDeclarationFindings()`). Exportované API se nemění;
585
+ změřeno nad `api_biz/*`: verdikty `oa-validate` před a po shodné.
586
+
587
+ ### Documentation — komentáře `src/**` bez čísel řádků; `setupDurationMs` rozlišuje jednorázovou a opakovanou cenu (d.795b, d.795c, d.981)
588
+
589
+ Jen komentáře a docblocky, chování beze změny. `CookbookTestRunner.js` (u `setupDurationMs`
590
+ na dvou místech) už netvrdí, že čtení `operations.json` i `require()` modulu jsou „a one-off
591
+ cost per process": `require()` Node po prvním kroku podá z registru modulů, ale
592
+ `resolveOperation()` čte `config/service/operations.json` z disku při každém kroku
593
+ (změřeno d.795b: pět volání → pět čtení) (d.795b, d.795c). Čísla řádků v komentářích
594
+ `src/**` (`createServiceReadinessTests.js`, `mocks/MockMQClient.js`, `utils/cookbookFormat.js`,
595
+ `utils/envContract.js`, `validators/ServiceStructureValidator.js`) nahradily symboly (d.981).
596
+
597
+ ### Removed — `TESTING_STRATEGY.md` odstraněn (0 čtenářů) (d.1020)
598
+
599
+ - Dokument nečetl žádný soubor v balíčku ani v repozitáři, a tak zmizel z repozitáře i z tarballu;
600
+ `npm pack --dry-run` ubral právě tento soubor. `docs/DESIGN.md` se balí dál — odkazuje na něj
601
+ balený `src/helpers/README.md`.
602
+
603
+ ## [12.2.0] — 2026-09-26
604
+ ### Changed — šablona biz CI: obraz se staví jen na `main`, produkce nasazuje digest z registru (d.671, d.671b; conf image-promotion 001)
605
+
606
+ Job `build` v `templates/business-service/.gitlab-ci.yml` běží jen na `main` a před stavbou se ptá
607
+ registru, zda tag `:$CI_COMMIT_SHA` už existuje (existuje → nestaví a vypíše digest); tag se nikdy
608
+ nepřepíše. `deploy-production` nebere digest z dotenv artefaktu (`dependencies: []`), ale týmž
609
+ dotazem do registru (`.oa-registry-digest`); když obraz pro commit nenajde, zastaví nasazení
610
+ jmenovanou hláškou a nikdy nestaví náhradu. Fixtury manifestu nesou tentýž blok `oa-ci v1`;
611
+ tvar i tělo dotazu měří `tests/unit/templateImagePromotion.test.js`
612
+ a `tests/unit/templateImagePromotionShell.integration.test.js`.
613
+
7
614
  ## [12.1.1] — 2026-09-25
8
615
 
9
616
  ### Fixed — `verify-deploy-uniform.sh`: rada při checkoutu mimo `api_biz/` vede na cestu, kterou šablona skutečně deklaruje (d.899)