@onlineapps/conn-orch-validator 10.0.0 → 11.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/CHANGELOG.md +173 -0
- package/README.md +254 -10
- package/docs/DESIGN.md +11 -2
- package/manifests/biz-service.manifest.json +28 -1
- package/package.json +2 -1
- package/src/CookbookTestRunner.js +50 -6
- package/src/ValidationOrchestrator.js +244 -58
- package/src/cli/biz-ci-gate.js +163 -1
- package/src/cli/oa-validate.js +63 -1
- package/src/manifest/checks/gitTracked.js +5 -30
- package/src/manifest/checks/serviceDb.js +176 -7
- package/src/manifest/checks/serviceRuntime.js +123 -0
- package/src/manifest/gitCheckout.js +84 -0
- package/src/utils/dbAccountGrants.js +126 -0
- package/src/utils/envContract.js +36 -6
- package/src/utils/envReads.js +102 -0
- package/src/utils/stepReferences.js +278 -0
- package/src/validators/ServiceStructureValidator.js +7 -1
- package/templates/business-service/.gitlab-ci.yml +108 -10
- package/templates/business-service/Dockerfile +49 -16
- package/templates/business-service/README.md +42 -4
- package/templates/business-service/config/env-templates/shared.env +1 -1
- package/templates/business-service/docker-compose.production.yml +9 -0
- package/templates/business-service/docker-compose.yml +17 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,179 @@ All notable changes to this package. Follows [Keep a Changelog](https://keepacha
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [11.0.0] — 2026-09-17
|
|
8
|
+
|
|
9
|
+
### Changed — deploy job aplikuje seedy třídy PRODUCTION_LIKE (d.589, W589)
|
|
10
|
+
|
|
11
|
+
`templates/business-service/.gitlab-ci.yml` (blok `oa-ci v1`): po migracích a před přepnutím se aplikují deklarované
|
|
12
|
+
`database.seeds`, jejichž hlavička nese `Dataset-Class: PRODUCTION_LIKE` (standard `repository-installation-sql-contract.md`
|
|
13
|
+
§4/§5; converter po 10 migracích měl 22 prázdných tabulek). Filtr běží na runneru — box neparsuje JSON ani hlavičky a o třídě
|
|
14
|
+
nerozhoduje; `TEST_ONLY` se nikdy neaplikuje a job to řekne jménem souboru. Seedy se netrackují a jedou při každém nasazení,
|
|
15
|
+
licencí je `-- Idempotency: yes`, její absence i třída mimo slovník odmítnou nasazení před ssh. Devátý poziční argument
|
|
16
|
+
`BIZ_DB_SEEDS`; na boxu `apply_mariadb_seeds_over_tcp <host> <port> <db> <file>...` z běžce neseného z klonu api
|
|
17
|
+
(INFRA `7045a736`), s pojmenovaným odmítnutím mezi předpoklady, když ho klon nenese. Bats `biz-deploy-db-migrations.bats` 24 → 29.
|
|
18
|
+
|
|
19
|
+
### Fixed — box se resetuje na commit, který runner měřil (d.589b, W589)
|
|
20
|
+
|
|
21
|
+
Desátý poziční argument `$CI_COMMIT_SHA`; `git merge-base --is-ancestor` + `git reset --hard "$COMMIT_SHA"` místo
|
|
22
|
+
`origin/production` — push během běhu jobu dosud rozešel to, co runner četl v kontraktu, s tím, co box nasadil.
|
|
23
|
+
|
|
24
|
+
### Fixed — existence `../shared.env` a `config/env-active/<svc>.env` se ověřuje pro každou službu (d.589c, W589)
|
|
25
|
+
|
|
26
|
+
Dosud jen ve větvi s migracemi; služba bez databáze (pdfgen, hello) se o chybějícím souboru dozvěděla až od
|
|
27
|
+
`docker compose`. Kontrola je před větvením s `Fix:` na runbook, který ty dva soubory zakládá
|
|
28
|
+
(`docs/operations/production-box-provisioning.md` § 3).
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
### Added — řádek uniformy `R-USER` (check `process-identity`) (d.588c, W588)
|
|
32
|
+
|
|
33
|
+
Produkční stage `Dockerfile` deklaruje `USER node` a uzel služby v `docker-compose.yml` pinuje `user: "1000:1000"`. Druhý
|
|
34
|
+
platformní fakt vyjmutý ze třídy `own` (prvním je node major, `R-NODE`); běžec drží `F-RUNNER`, produkční compose `G-PROD`
|
|
35
|
+
(kontrolní případy, že `R-USER` mlčí, kde hlásí ten druhý). Zčervená u všech osmi služeb — vždy jedním nálezem o `Dockerfile`
|
|
36
|
+
(dev compose už `user:` má 8/8). `doc` = `docs/biz/00-model/service-shape.md` (vzor `R-PORTS-DEV`); sekce „Process identity"
|
|
37
|
+
v README šablony, generovaná oblast řádků přegenerována.
|
|
38
|
+
|
|
39
|
+
### Fixed — krok, který spadl výjimkou, nechá svůj záznam; nález nese krok (d.602, W602)
|
|
40
|
+
|
|
41
|
+
Wrapper 9.0.x rozhoduje o opakování padlé FÁZE 0.2 podle `result.steps.<krok>.valid`; krok, jehož tělo vyhodilo výjimku, ale
|
|
42
|
+
záznam nezanechal, došel k wrapperu jako „validator spadl" = přechodně, a koupil si šest pokusů (18,5 min bootu, který uspět
|
|
43
|
+
nemůže). `runStep()` je síť pod všemi sedmi kroky: zapíše `{ valid: false, success: false, threw: true, errors: […] }` s tím,
|
|
44
|
+
co krok naměřil, a nález `{ type: 'STEP_THREW', step, message }`; objektové nálezy kroku 1 nesou v `result.errors` `step`
|
|
45
|
+
(kopie). Věty kroků 2–7 zůstávají větami — `describeValidationFinding()` vydaného wrapperu vykreslí objekt jen s `type` i
|
|
46
|
+
`message`; sjednocení na objekty čeká na wrapper, který `errors[].step` čte (pak 11.0.0). `attributeToStep()` odmítne nálezy,
|
|
47
|
+
které nejsou pole, hláškou §5. Krok 5 hlásí `success`, ostatní `valid` — dvě pole pro jeden pojem, zapsáno jako nález.
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
### Changed — BREAKING: šablona biz služby — produkce běží jako uid 1000, běžec má vlastní `conn-runtime` (d.588, W588)
|
|
51
|
+
|
|
52
|
+
`templates/business-service/Dockerfile`: obě stage deklarují `USER node` (uid 1000/gid 1000 v `node:24-alpine`) a zakládají
|
|
53
|
+
`/app/logs` + `/app/conn-runtime` pod tím účtem (`COPY --chown=node:node`). Produkční obraz dosud běžel jako root (změřeno
|
|
54
|
+
`docker run --rm <obraz> id` → `uid=0`), zatímco dev kontejner jako 1000:1000 — asymetrie, kterou nikdo nedeklaroval.
|
|
55
|
+
`docker-compose.production.yml`: `user: "1000:1000"` shodně s dev → řádek `G-PROD` u každé služby zčervená do
|
|
56
|
+
`oa-sync-template docker-compose.production.yml`. Blok `oa-test-runner v1` v `docker-compose.yml` dostal anonymní svazek
|
|
57
|
+
`/app/conn-runtime`, aby proof běžce nepřepisoval proof služby (změřeno converterem: běžec nad `.:/app` zapsal
|
|
58
|
+
`validation-proof.json` běžící instance) → řádek `F-RUNNER` zčervená do `oa-sync-template docker-compose.yml`. Nové sady
|
|
59
|
+
`templateProcessIdentity`, `templateRunnerRuntime`, `templateImageIdentity.integration` (skutečný `docker build` + `docker run`;
|
|
60
|
+
bez démona/checkoutu/registry NOT RUN s důvodem, nikdy červená za síť).
|
|
61
|
+
|
|
62
|
+
### Changed — BREAKING: šablona neměří uniformu při buildu obrazu — verdikt patří CI (d.588b, W588)
|
|
63
|
+
|
|
64
|
+
Řádek `RUN … oa-validate.js .` ve stage `production` (d.215b, `4b5ed9e6`) měřil uniformu nad build kontextem bez `.git`,
|
|
65
|
+
compose a README — u každé reálné služby (`.dockerignore` je správně vyřazuje) `NOT DEPLOYABLE — 10 finding(s)`, všechny
|
|
66
|
+
„absent", a build spadl. Konfirmace 006/2 („information, never a gate"), 010 (`validate-uniform` z každého commitu nad
|
|
67
|
+
checkoutem) a 011 (strom bez git checkoutu = NOT RUN) větu 001 §3.1 překonaly; řádek je pryč i s testem `add-service.bats`
|
|
68
|
+
54, který teď tvrdí opak. `templateImageIdentity` už nepotřebuje srovnat scaffold pinovanou verzí (19 s místo 46 s).
|
|
69
|
+
|
|
70
|
+
### Fixed — fixtury uniformy dosynchronizovány na SSOT sdílené sady (dovětek d.533, W588)
|
|
71
|
+
|
|
72
|
+
14 fixtur `shared.env` a 3 `shared-env.json` se od `f61501cc` lišily jedním klíčem (`JWT_SECRET` `why`/`consumers`);
|
|
73
|
+
`manifestServiceConfig` (`G-SHARED-ENV`) byla proto v HEAD červená. Záměrně rozejitá `service-workspace/api_biz/beta`
|
|
74
|
+
zůstala (vstup případu „not the platform manifest rendered").
|
|
75
|
+
|
|
76
|
+
### Changed — BREAKING: krok 7 mimo git checkout je NOT RUN, ne „NOT DEPLOYABLE" (d.592, W592)
|
|
77
|
+
|
|
78
|
+
Produkční obraz není checkout a nenese compose ani README, takže uniforma měřená tam hlásila 12 nálezů nepřítomnosti a
|
|
79
|
+
služba se registrovala `deployable: false` (BIZ-pdfgen 2026-09-16). Verdikt z toho není přísnost, ale špatná odpověď:
|
|
80
|
+
krok vypíše jednu větu NOT RUN s odkazem na CI job `validate-uniform`, `results.deployable` zůstává `null` (= neměřeno,
|
|
81
|
+
jiné tvrzení než `false`), banner ani `ci/deployability.json` nevznikají (konfirmace `biz-service-manifest` 010, 011).
|
|
82
|
+
Sonda `git ls-files` má jedno místo `src/manifest/gitCheckout.js` (`gitCommand`, `listTrackedFiles`, `isGitCheckout`,
|
|
83
|
+
věta `NOT_A_CHECKOUT`); `X-IGNORED` ji bere odtud. Fixtury kroku 7 jsou nově checkouty přes `makeGitCheckout`.
|
|
84
|
+
|
|
85
|
+
### Added — Tier-1 běžec předává výstup kroku dalším krokům (d.590, W590)
|
|
86
|
+
|
|
87
|
+
`{{steps.<step_id>.output.…}}` v `input` kroku se rozřeší proti tomu, co dřívější kroky téhož receptu vrátily, takže recept
|
|
88
|
+
si fixturu vytvoří, použije a smaže (vlastník: konfirmace `converter-boot-cookbook` 001 — „opravit v knihovně"). Syntaxe
|
|
89
|
+
i průchod zůstávají cookbook formátu a `@onlineapps/cookbook-core` (`resolveReferencesWith`, `resolveReferencePath` —
|
|
90
|
+
táž kolej jako `WorkflowOrchestrator`); běžec přidává jen scope běhu (`src/utils/stepReferences.js`) tvaru `format.md`
|
|
91
|
+
§ Runtime context (`context.steps`): definice kroku + `output` + `_execution`, adresováno `step_id`. Expanduje se jen
|
|
92
|
+
`input`; `output` se zapisuje jen u kroku, který prošel; scope je per cookbook. `CookbookTestRunner.executeStep()` má
|
|
93
|
+
pátý parametr (kontext běhu; samostatný krok dostane prázdný). Po spadlém kroku s `expect` se další kroky nepouští
|
|
94
|
+
(dnešní chování, nově připnuté testem). NOVÁ ZÁVISLOST: `@onlineapps/cookbook-core` (SSOT) — do `dependencies` před vydáním.
|
|
95
|
+
|
|
96
|
+
### Added — Tier-1 běžec odmítá recept, který produkce nepřijme (d.598, W598)
|
|
97
|
+
|
|
98
|
+
Před během: volání template helperu v `input` (`jméno(...)` — Tier-1 registr helperů nemá a `cookbook-template-helpers`
|
|
99
|
+
táhne `content-resolver`, tedy I/O), `{{steps.<id>}}` na krok, který recept nedefinuje (včetně tečkového `{{steps.0}}`;
|
|
100
|
+
gateway odpovídá 400, konfirmace `cookbook-validation-placement` 001 = throw u příjemce jako pojistka), a `depends_on` na
|
|
101
|
+
nedefinovaný krok. Dosud obojí tiše propadlo jako literál. `utils/stepReferences.checkStepInputExpressions()` používá týž
|
|
102
|
+
`resolveReferencesWith()`. Literálem zůstává, co jím podle normy být má (`{{steps[0]}}`, `{{api_input.…}}`, `{{context.…}}`,
|
|
103
|
+
`{{current.…}}`, odkaz na definovaný, ale neběžící/selhavší krok). Dopad na dnešní recepty služeb: 0 ze 43.
|
|
104
|
+
|
|
105
|
+
### Fixed — `MISSING_COOKBOOKS` nese `Fix:` právě jednou (d.599, W598)
|
|
106
|
+
|
|
107
|
+
Jediný ze 17 nálezů `ServiceStructureValidator` měl nápravu i uvnitř `message`; wrapper 9.0.1 složením `message (Fix: fix)`
|
|
108
|
+
ji tiskl dvakrát.
|
|
109
|
+
|
|
110
|
+
### Added — `oa-validate --env-reads [<serviceRoot>]` (d.533c, W533c)
|
|
111
|
+
|
|
112
|
+
Vypíše jména proměnných prostředí, která služba čte: po řádcích, seřazená, bez duplicit, nic jiného (žádný banner, žádný
|
|
113
|
+
verdikt, žádné `ci/deployability.json`), exit 0; bez čitelného `config/service/integration-contract.json` exit 2 a
|
|
114
|
+
`[oa-validate] … Fix: …` na stderr; nekombinuje se s `--json`, `--library`, `--all`, `--workspace`. Odpovídá bráně nasazení
|
|
115
|
+
`api/scripts/validate-env.sh`, která u biz cíle shazovala nasazení na `JWT_SECRET=CHANGE_ME` — klíč, který žádný biz obraz
|
|
116
|
+
nečte (dohoda s INFRA 2026-09-17). `utils/envContract.js` vystavuje `collectDeclaredEnvNames({serviceRoot, contract})` —
|
|
117
|
+
množina, proti které měří řádek `C-ENV-READS` (`env` blok ∪ `${VAR}` z `config/service/*.json` ∪ endpointové proměnné
|
|
118
|
+
vyžadovaných konektorů), vytažená z `verifyEnvCompleteness`, aby druhý konzument nezaložil druhou definici.
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
### Documentation — zabalený `shared.env` nese komentář u `JWT_SECRET` podle SSOT (d.533)
|
|
122
|
+
|
|
123
|
+
Šablona `templates/business-service/config/env-templates/shared.env` je render SSOT `api/config/shared-env.json`; `why` u `JWT_SECRET`
|
|
124
|
+
teď jmenuje čtyři infra čtenáře a říká, že business řetězec klíč nese a nečte (`@onlineapps/service-common` bere tajemství jako vstup).
|
|
125
|
+
Vydání 10.0.0 (22:32) přistálo o čtyři minuty dřív než `f61501cc` — zabalená kopie je o tento komentář pozadu, chování beze změny.
|
|
126
|
+
|
|
127
|
+
### Added — CI pod účtem služby (d.567)
|
|
128
|
+
|
|
129
|
+
Nový příkaz `oa-biz-ci-gate setup-db-account`: založí účet, který služba **deklaruje** (`DB_USER`
|
|
130
|
+
v `config/env-templates/<služba>.env` — týž klíč, který měří řádek `D-DB-ACCOUNT`), a udělí mu granty na
|
|
131
|
+
jeho schéma. Je to **jediný** krok biz pipeline pod rootem databáze a poslední takový: všechno za ním
|
|
132
|
+
se připojuje jako služba, stejně jako produkce. Důvod je konfirmace `db-migrations-first-deploy` 001
|
|
133
|
+
(„migrace nikdy pod rootem"): sedm pipeline dnes aplikuje tutéž migrační sadu pod `DB_USER: "root"`,
|
|
134
|
+
takže CI dokazuje průchod migrací právy, která produkce neuděluje.
|
|
135
|
+
|
|
136
|
+
Věty SQL mají **jednu definici** — `src/utils/dbAccountGrants.js`. Produkční runbook a CI jsou dva
|
|
137
|
+
volající, ne dvě kopie (`db-migrations-first-deploy` 001: „žádný nový generátor SQL, žádná druhá kolej").
|
|
138
|
+
Jediný rozdíl CI proti produkci je grant na zahazovací jmenný prostor `` `<schéma>\_%` `` (staví do něj
|
|
139
|
+
integrační tier, `throwawaySchema.js`); `_` je escapované, takže grant zůstává uvnitř jedné služby —
|
|
140
|
+
neescapovaný je `_` zástupný znak LIKE a `oagen_meta_%` by sáhlo i na `oagen_metadata`.
|
|
141
|
+
`GRANT … ON information_schema.*` v sadě **není**: server ho odmítá i rootu (změřeno na dev
|
|
142
|
+
`gen_mariadb10.5`, MariaDB 10.5.17, `ERROR 1044`), a každý účet ten katalog čte filtrovaně sám.
|
|
143
|
+
|
|
144
|
+
Rootové přihlášení má vlastní jména `CI_DB_ROOT_USER` / `CI_DB_ROOT_PASSWORD` a nebere se z `DB_USER`
|
|
145
|
+
(`architecture-principles.md` §8) — právě to, že ty dvě identity byly jedna hodnota, příkaz končí.
|
|
146
|
+
Heslo účtu je zahazovací hodnota jobu (`DB_PASSWORD`), nikdy `CHANGE_ME` ze šablony: šablona deklaruje
|
|
147
|
+
klíč, ne tajemství. Služba bez bloku `database` hlásí `NOT APPLICABLE` a končí nulou, jako `setup-db`.
|
|
148
|
+
Žádné heslo se na výstup nedostane ani na cestě selhání.
|
|
149
|
+
|
|
150
|
+
Že ty granty **stačí** — a pořád nejsou příliš — není vlastnost textu SQL, ale odpovědi serveru, takže je
|
|
151
|
+
to změřeno proti skutečné MariaDB (dev `gen_mariadb10.5`, 10.5.17): se samotným produkčním grantem server
|
|
152
|
+
stavbu zahazovacího schématu odmítne (`ERROR 1044`), po `setup-db-account` projde, a účet přitom pořád
|
|
153
|
+
nedosáhne na sousední službu — ani na její schéma, ani na zahazovací schéma v jejím jmenném prostoru, ani
|
|
154
|
+
na řádky, ani na její jméno v katalogu. Soused se jmenuje `<ns>ax` k `<ns>a` právě proto, že neescapované
|
|
155
|
+
`_` by ho pustilo dovnitř.
|
|
156
|
+
|
|
157
|
+
Kaskáda: sedm repozitářů s databází přepíše v jobu `test` `DB_USER: "root"` na účet služby a zařadí
|
|
158
|
+
`setup-db-account` před `ci:gate:setup`.
|
|
159
|
+
|
|
160
|
+
### Added — řádek uniformy `D-DB-CI-ACCOUNT` (d.567)
|
|
161
|
+
|
|
162
|
+
`D-DB-ACCOUNT` měří deklaraci (`DB_USER` v šabloně prostředí); nic neměřilo prostředí, ve kterém se ta
|
|
163
|
+
migrační sada každý den skutečně aplikuje. Nový řádek (`severity: deploy`, vlastník BIZ-general) hlásí
|
|
164
|
+
`DB_USER` s hodnotou `root` a `ci:gate:setup`, před kterým neběží `setup-db-account`. Samostatný řádek,
|
|
165
|
+
ne rozšíření `D-DB-ACCOUNT`: jiný soubor, jiná oprava, a jeden řádek se dvěma opravami jsou dva
|
|
166
|
+
mechanismy pod jedním jménem (`automation-gates.md` §1.2). Čte řádky **mimo** blok `oa-ci v1` (ten je
|
|
167
|
+
G-CI), takže nekřísí problém, kvůli kterému byl celosouborový řádek nad `.gitlab-ci.yml` po třech dnech
|
|
168
|
+
stažen.
|
|
169
|
+
|
|
170
|
+
Měří, KDO kroky nad databází pouští, a záměrně ne, ZDA je repozitář pouští: job, který nestaví schéma,
|
|
171
|
+
nemá účet, který by tento krok zakládal. Hranice je napsaná, protože nevyslovená hranice se čte jako
|
|
172
|
+
pokrytí (`automation-gates.md` §5). Obě pravopisné podoby téhož: `DB_USER: "root"` v `variables:` i
|
|
173
|
+
`DB_USER=root` jako předřazené přiřazení na samotném kroku (změřený tvar `api_biz/hello-service`).
|
|
174
|
+
Komentář, který jmenuje `ci:gate:setup`, krokem není (změřený tvar `api_biz/meta`).
|
|
175
|
+
|
|
176
|
+
Změřeno nad všemi osmi repozitáři: sedm hlásí dva nálezy s číslem řádku a proveditelnou opravou,
|
|
177
|
+
`pdfgen` (bez bloku `database`) mlčí z konstrukce. Zrcadla generovaného seznamu řádků (README uniforma
|
|
178
|
+
šablony i všech fixtur, `api/templates/business-service/README.md`) přegenerována v témže commitu.
|
|
179
|
+
|
|
7
180
|
## [10.0.0] — 2026-09-16
|
|
8
181
|
|
|
9
182
|
### Removed — `MockStorage` (d.583)
|
package/README.md
CHANGED
|
@@ -68,6 +68,44 @@ signal saved to `ci/deployability.json`
|
|
|
68
68
|
> The list said "Service Readiness — HTTP API works" until 2026-08-27. That step
|
|
69
69
|
> was removed on 2026-08-22 under ADR 0005 and this README kept describing it.
|
|
70
70
|
|
|
71
|
+
### The shape of a run's result
|
|
72
|
+
|
|
73
|
+
```js
|
|
74
|
+
{
|
|
75
|
+
success, steps: { structure, config, env, operations, cookbooks, connectors, manifest },
|
|
76
|
+
errors: [ … ], warnings: [ … ],
|
|
77
|
+
totalTests, passedTests, failedTests,
|
|
78
|
+
deployable, deployFindings, notRun, durationMs, proof?, fingerprint?
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Every step that was reached has a record in `steps`, and a step that THREW has one
|
|
83
|
+
too** — `{ valid: false, threw: true, errors: [ … ] }`, with whatever it had already
|
|
84
|
+
measured kept beside it. Absence therefore means one thing only: the run stopped before
|
|
85
|
+
that step (it fail-fasts after steps 1 and 3). That distinction is not cosmetic:
|
|
86
|
+
`@onlineapps/service-wrapper` 9.0.x decides whether a failed FÁZE 0.2 may be retried by
|
|
87
|
+
reading `steps.<step>.valid` — `structure`, `config`, `env`, `operations` and `manifest`
|
|
88
|
+
read files alone, so their verdict is permanent, while `cookbooks` and an exception of the
|
|
89
|
+
validator itself keep the retry budget. Until d.602 a step whose body threw left NO record,
|
|
90
|
+
so a structural defect read as "the validator threw" and bought the full six attempts:
|
|
91
|
+
18,5 minutes of a boot that cannot succeed (measured on biz-converter, W591).
|
|
92
|
+
|
|
93
|
+
**`errors` is one flat array of two shapes**, and a reader must handle both:
|
|
94
|
+
|
|
95
|
+
| Shape | Written by | Carries `step` |
|
|
96
|
+
|---|---|---|
|
|
97
|
+
| object `{ type, path?, message, fix?, step }` | step 1, and every step that THREW (`type: 'STEP_THREW'`) | yes |
|
|
98
|
+
| sentence, already in the `[Context] Problem - Expected/Fix` shape | steps 2-7 by their rules | no — see below |
|
|
99
|
+
|
|
100
|
+
The `step` marker is ADDED to the objects, never substituted for what they carried, and the
|
|
101
|
+
sentences keep their shape. That asymmetry is deliberate and measured: the released
|
|
102
|
+
wrapper's `describeValidationFinding()` renders a string verbatim and an object only when
|
|
103
|
+
it carries `type` **and** `message`, so rewriting the sentences into `{ step, message }`
|
|
104
|
+
objects would deliver them to the operator as raw JSON. Unifying on objects is therefore a
|
|
105
|
+
BREAKING change that waits for a wrapper which reads `step` (reported to BIZ-general with
|
|
106
|
+
d.602). A finding's own `errors` array inside `steps.<step>` is the step's answer and is
|
|
107
|
+
left untouched — the marker is on the copy that travels in `errors`.
|
|
108
|
+
|
|
71
109
|
---
|
|
72
110
|
|
|
73
111
|
## Pre-validation runs in-process — and against the real database
|
|
@@ -99,6 +137,140 @@ failure.
|
|
|
99
137
|
|
|
100
138
|
---
|
|
101
139
|
|
|
140
|
+
## A recipe builds its own fixture — `{{steps.<step_id>.output.…}}`
|
|
141
|
+
|
|
142
|
+
A step's `input` may reference what an EARLIER step of the same cookbook
|
|
143
|
+
returned. The runner resolves those references before it calls the handler, so a
|
|
144
|
+
recipe can create the data it needs, use it, and delete it again — instead of
|
|
145
|
+
depending on a seed that exists in one environment and not in another.
|
|
146
|
+
|
|
147
|
+
```json
|
|
148
|
+
{
|
|
149
|
+
"steps": [
|
|
150
|
+
{ "step_id": "create_fixture", "service": "biz-x", "operation": "create-row",
|
|
151
|
+
"input": { "label": "boot-fixture" } },
|
|
152
|
+
{ "step_id": "use_fixture", "service": "biz-x", "operation": "convert",
|
|
153
|
+
"depends_on": ["create_fixture"],
|
|
154
|
+
"input": { "id": "{{steps.create_fixture.output.id}}" } },
|
|
155
|
+
{ "step_id": "delete_fixture", "service": "biz-x", "operation": "delete-row",
|
|
156
|
+
"depends_on": ["create_fixture"],
|
|
157
|
+
"input": { "id": "{{steps.create_fixture.output.id}}" } }
|
|
158
|
+
]
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**The syntax is not this package's.** Which forms exist, that a whole-value
|
|
163
|
+
expression keeps the resolved type while an expression inside text is
|
|
164
|
+
stringified, that a step is addressed by `step_id` and never by position, and
|
|
165
|
+
that an unresolvable reference survives as literal text — all of it is owned by
|
|
166
|
+
[`api/docs/biz/40-cookbooks/variable-references.md`](../../../docs/biz/40-cookbooks/variable-references.md),
|
|
167
|
+
and the traversal is `@onlineapps/cookbook-core`
|
|
168
|
+
(`resolveReferencesWith` + `resolveReferencePath`) — the same two functions
|
|
169
|
+
`WorkflowOrchestrator` calls. There is no second parser here, which is what keeps
|
|
170
|
+
a recipe behaving the same at boot and in a production workflow.
|
|
171
|
+
|
|
172
|
+
What Tier-1 adds is the SCOPE, and only the scope
|
|
173
|
+
(`src/utils/stepReferences.js`):
|
|
174
|
+
|
|
175
|
+
- `input` is the only field expanded. `expect`, `service`, `operation` and
|
|
176
|
+
`depends_on` are read literally — the format names three expansion sites and
|
|
177
|
+
none of them is `expect`
|
|
178
|
+
([variable-references.md](../../../docs/biz/40-cookbooks/variable-references.md)
|
|
179
|
+
§ Limitations).
|
|
180
|
+
- A step entry is spelled the way
|
|
181
|
+
[`format.md`](../../../docs/biz/40-cookbooks/format.md) § Runtime context
|
|
182
|
+
(`context.steps`)
|
|
183
|
+
spells it: the step definition plus `output` and `_execution`, addressed by
|
|
184
|
+
`step_id`.
|
|
185
|
+
- `output` appears on a step that PASSED. A failed step is recorded with its
|
|
186
|
+
status alone, so a reference into it stays literal and the failing input shows
|
|
187
|
+
the reader which step did not deliver.
|
|
188
|
+
- The scope is per COOKBOOK. A run over a directory gives every cookbook its own,
|
|
189
|
+
so two recipes may use the same step names.
|
|
190
|
+
- A Tier-1 run has no workflow input and no delivery, so `{{api_input.…}}`,
|
|
191
|
+
`{{delivery.…}}` and `{{current.…}}` resolve to nothing and stay literal.
|
|
192
|
+
|
|
193
|
+
### What the runner REFUSES, because production does too
|
|
194
|
+
|
|
195
|
+
A literal `{{…}}` is the right outcome for a value that is missing at that
|
|
196
|
+
moment — a step that has not run yet, or one that failed. It is the wrong
|
|
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:`:
|
|
200
|
+
|
|
201
|
+
| Written in a step's `input` | Tier-1 | Production |
|
|
202
|
+
|---|---|---|
|
|
203
|
+
| `{{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` | — |
|
|
206
|
+
|
|
207
|
+
The helper registry is absent from the runner on purpose: a helper reaches its
|
|
208
|
+
own runtime (`@onlineapps/content-resolver`, files, storage), which is not what a
|
|
209
|
+
startup probe may do. A recipe that needs a computed value builds it in an
|
|
210
|
+
earlier step and references that step's output.
|
|
211
|
+
|
|
212
|
+
The reference half is the receiver's side of the owner's 2026-09-02 decision
|
|
213
|
+
([`cookbook-validation-placement.md`](../../../docs/governance/confirmations/cookbook-validation-placement.md)
|
|
214
|
+
001): the gateway answers 400 at submission, and the paths that bypass the
|
|
215
|
+
gateway — a boot run is one — refuse it themselves. What stays literal is
|
|
216
|
+
unchanged: `{{steps[0].…}}` (the bracket form is not a step reference),
|
|
217
|
+
`{{api_input.…}}`, `{{context.…}}`, `{{current.…}}`, and a reference into a
|
|
218
|
+
step that is defined but has not produced anything yet.
|
|
219
|
+
|
|
220
|
+
Why the runner gained this: biz-converter's boot recipe needed a row that only a
|
|
221
|
+
TEST_ONLY seed supplies, so on production Tier-1 answered `NOT_CONVERTIBLE` and
|
|
222
|
+
the container restarted in a loop. The owner's decision was that the recipe stays
|
|
223
|
+
as written and the runner learns what the orchestrator already does —
|
|
224
|
+
`api/docs/governance/confirmations/converter-boot-cookbook.md` 001.
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## The CI database account is the service's own (`setup-db-account`)
|
|
229
|
+
|
|
230
|
+
`db-migrations-first-deploy` 001 says it in one line: **migrace nikdy pod
|
|
231
|
+
rootem.** Production applies a service's migration set as `oagen_<service>`, an
|
|
232
|
+
account granted its own schemas and nothing else. Until d.567 seven biz
|
|
233
|
+
pipelines applied the SAME set under `DB_USER: "root"`, so CI proved the
|
|
234
|
+
migrations under rights no production box grants.
|
|
235
|
+
|
|
236
|
+
`setup-db-account` closes that gap. It runs **first** in `before_script` and is
|
|
237
|
+
the **only** step of the pipeline that uses the database root account:
|
|
238
|
+
|
|
239
|
+
```yaml
|
|
240
|
+
before_script:
|
|
241
|
+
- npx oa-biz-ci-gate setup-db-account # the one root step
|
|
242
|
+
- npm run ci:gate:setup # …and everything after it is the service
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Three facts, three owners, none of them invented by the command:
|
|
246
|
+
|
|
247
|
+
| Fact | Owner |
|
|
248
|
+
|---|---|
|
|
249
|
+
| the schema | `database.schema` of `config/service/integration-contract.json` — what `setup-db` builds |
|
|
250
|
+
| the account | `DB_USER` of `config/env-templates/<service>.env` — the declaration uniform row `D-DB-ACCOUNT` measures |
|
|
251
|
+
| the statements | `src/utils/dbAccountGrants.js` — the same definition the production runbook installs from |
|
|
252
|
+
|
|
253
|
+
The job supplies the credentials, and the two identities are named **separately**
|
|
254
|
+
(`architecture-principles.md` §8 — the whole point is that they stop being one
|
|
255
|
+
value):
|
|
256
|
+
|
|
257
|
+
| Variable | What it is |
|
|
258
|
+
|---|---|
|
|
259
|
+
| `CI_DB_ROOT_USER` / `CI_DB_ROOT_PASSWORD` | the administrative account of the database sidecar, used by this step alone |
|
|
260
|
+
| `DB_USER` | the account every later step connects as; it must equal what the env template declares, and the command refuses the job otherwise |
|
|
261
|
+
| `DB_PASSWORD` | the throwaway password the account is **created with** and connects with — an env template declares the key, never the secret |
|
|
262
|
+
|
|
263
|
+
A service whose contract declares no `database` block reports `NOT APPLICABLE`
|
|
264
|
+
and exits 0, the way `setup-db` does. Neither password is ever printed, on any
|
|
265
|
+
path.
|
|
266
|
+
|
|
267
|
+
**CI gets one grant production does not**: `` `<schema>\_%` ``, the throwaway
|
|
268
|
+
namespace an integration tier builds into (`src/utils/throwawaySchema.js`). The
|
|
269
|
+
`_` is escaped, so the grant stays inside one service — unescaped it is a LIKE
|
|
270
|
+
wildcard, and `oagen_meta_%` would also match `oagen_metadata`.
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
102
274
|
## Environment contract (`env` block)
|
|
103
275
|
|
|
104
276
|
One declaration in `config/service/integration-contract.json`, two consumers:
|
|
@@ -349,6 +521,18 @@ a stateless service is silent — `pdfgen` is the measured case.
|
|
|
349
521
|
(confirmation `db-accounts-per-service` 001: root stops being an operational identity —
|
|
350
522
|
it was the identity of nine containers across eight repositories). A repository that
|
|
351
523
|
declares a database and no `DB_USER` is a finding and not a silence.
|
|
524
|
+
* **`D-DB-CI-ACCOUNT`** — the row above measures the DECLARATION; this one measures the
|
|
525
|
+
one environment where that migration set is applied every day. Measured 2026-09-16:
|
|
526
|
+
seven of eight biz pipelines set `DB_USER: "root"` in their `test` job while declaring
|
|
527
|
+
`oagen_<service>` in the template, so CI proved the set under rights no production box
|
|
528
|
+
grants — the gap `db-migrations-first-deploy` 001 names ("migrace nikdy pod rootem") in
|
|
529
|
+
the only place a migration can be tried before a deploy. It reports that value, and a
|
|
530
|
+
`ci:gate:setup` with no `setup-db-account` step before it. A SEPARATE row rather than a
|
|
531
|
+
wider `D-DB-ACCOUNT`: another file, another fix, and one row with two fixes is two
|
|
532
|
+
mechanisms under one name (`automation-gates.md` §1.2). It reads the lines OUTSIDE the
|
|
533
|
+
`oa-ci v1` block, which is `G-CI`'s, and it asks WHO the database steps run as —
|
|
534
|
+
deliberately not WHETHER a repository runs them, since a job that builds no schema has
|
|
535
|
+
no account for the step to create.
|
|
352
536
|
|
|
353
537
|
One more row about the database sits outside this section on purpose. **`X-DB-CONFIG`**
|
|
354
538
|
is in `files.forbidden`, beside `X-HOOKS` and `X-PREVAL`, and it forbids one path:
|
|
@@ -552,9 +736,9 @@ severity means it never stops one.
|
|
|
552
736
|
### Step 7 and the deployability signal
|
|
553
737
|
|
|
554
738
|
The same check is step **7/7** of the boot validation (`ValidationOrchestrator`), so a
|
|
555
|
-
service measures itself at every start against the manifest
|
|
556
|
-
blocking severities have different consequences, and that
|
|
557
|
-
(confirmation `biz-service-manifest` 001 §4):
|
|
739
|
+
service started from a git checkout measures itself at every start against the manifest
|
|
740
|
+
its pin selected. The two blocking severities have different consequences, and that
|
|
741
|
+
difference is the point (confirmation `biz-service-manifest` 001 §4):
|
|
558
742
|
|
|
559
743
|
| Severity | Boot | `results` | What the operator sees |
|
|
560
744
|
|---|---|---|---|
|
|
@@ -590,8 +774,34 @@ belongs to the uniform (`verdict.incomplete` in the manifest, with `{count}` and
|
|
|
590
774
|
in it), never to the renderer: a library says `publish row(s)` there, and
|
|
591
775
|
`PUBLISHABLE — no findings` where nothing was skipped.
|
|
592
776
|
|
|
593
|
-
|
|
594
|
-
|
|
777
|
+
#### Outside a git checkout the step does not measure (d.592)
|
|
778
|
+
|
|
779
|
+
A production image is not a checkout and carries neither `docker-compose.yml` nor
|
|
780
|
+
`README.md`, so the uniform measured THERE reports absences that say nothing about the
|
|
781
|
+
repository — 12 findings for a healthy service. Owner decision 2026-09-17 (confirmation
|
|
782
|
+
`biz-service-manifest` 011): the step then reports NOT RUN, in one line, and measures
|
|
783
|
+
nothing:
|
|
784
|
+
|
|
785
|
+
```
|
|
786
|
+
[ValidationOrchestrator] NOT RUN manifest conformance — this tree is not a git checkout
|
|
787
|
+
(git ls-files could not answer), so what a clone would receive cannot be read — a container
|
|
788
|
+
and an exported tarball are in exactly this state. Deployability is proven per commit by
|
|
789
|
+
the CI job `validate-uniform` over the checkout (confirmation biz-service-manifest 010).
|
|
790
|
+
```
|
|
791
|
+
|
|
792
|
+
`results.deployable` is then `null`, which is NOT MEASURED — a different statement from
|
|
793
|
+
`false`, which is why the field has three states. No banner, no table, and **no
|
|
794
|
+
`ci/deployability.json`**: the file IS a verdict, and this run reached none. The probe is
|
|
795
|
+
the one `X-IGNORED` already used (`src/manifest/gitCheckout.js`), so the row and the step
|
|
796
|
+
cannot answer the same question differently.
|
|
797
|
+
|
|
798
|
+
What proves deployability is the CI job `validate-uniform`, from every commit, over the
|
|
799
|
+
checkout (confirmation `biz-service-manifest` 010) — the only place that can see what a
|
|
800
|
+
clone receives.
|
|
801
|
+
|
|
802
|
+
The verdict of a run that DID measure is written, in the same step, to
|
|
803
|
+
**`ci/deployability.json`** of the service root, which `deploy-production` reads and
|
|
804
|
+
refuses on a single row. The `oa-validate`
|
|
595
805
|
CLI writes the same file, through the same builder and writer
|
|
596
806
|
(`src/manifest/deployabilitySignal.js`), for the service run it just printed — one file
|
|
597
807
|
shape with two producers, never two shapes:
|
|
@@ -610,10 +820,10 @@ shape with two producers, never two shapes:
|
|
|
610
820
|
```
|
|
611
821
|
|
|
612
822
|
**Two claims, not one.** `deployable` says nothing blocked among the rows that RAN;
|
|
613
|
-
`complete` says no row that CAN block this bearer was skipped.
|
|
614
|
-
|
|
615
|
-
incomplete at once — and a gate reading only
|
|
616
|
-
flagged (`automation-gates.md` §1.5). **The deploy gate takes a signal only when both are
|
|
823
|
+
`complete` says no row that CAN block this bearer was skipped. A run from a checkout that
|
|
824
|
+
cannot reach the workspace root (the `--workspace` that names it) answers the workspace
|
|
825
|
+
rows NOT RUN, so its signal is deployable and incomplete at once — and a gate reading only
|
|
826
|
+
the first was honouring a `--skip` nobody had flagged (`automation-gates.md` §1.5). **The deploy gate takes a signal only when both are
|
|
617
827
|
true**; the complete run is produced from the api checkout with
|
|
618
828
|
`npx oa-validate --workspace <root> <service root>`, and since d.232 that run writes the
|
|
619
829
|
file it is asked for. A service root OUTSIDE the workspace root cannot produce a complete
|
|
@@ -630,7 +840,8 @@ rows that could not look are not passes, and each entry carries the `severity` o
|
|
|
630
840
|
that did not run — which is what decides `complete`.
|
|
631
841
|
|
|
632
842
|
There is no switch that skips the step and none that moves the file — one path,
|
|
633
|
-
unbypassable.
|
|
843
|
+
unbypassable. What decides whether the step measures is not a flag anybody sets but
|
|
844
|
+
whether git can answer in the tree it was handed (above).
|
|
634
845
|
|
|
635
846
|
### Two run modes, three scopes
|
|
636
847
|
|
|
@@ -700,6 +911,39 @@ looked and found nothing", so each row that needs the workspace — `workspace`
|
|
|
700
911
|
alike — is reported `NOT RUN — service root is outside the workspace root <root>` instead,
|
|
701
912
|
in the banner and in the signal alike.
|
|
702
913
|
|
|
914
|
+
### Which names the image reads (`oa-validate --env-reads`)
|
|
915
|
+
|
|
916
|
+
A third mode, and not a verdict at all: it answers ONE question — which environment
|
|
917
|
+
names this service reads — for a deploy gate that must not judge a service's `.env`
|
|
918
|
+
against the platform's whole key set.
|
|
919
|
+
|
|
920
|
+
```bash
|
|
921
|
+
mapfile -t reads < <(npx oa-validate --env-reads "$repo") || exit 1
|
|
922
|
+
for key in "${reads[@]}"; do
|
|
923
|
+
grep -q "^${key}=" "$env_file" || fail "$key is declared and missing"
|
|
924
|
+
done
|
|
925
|
+
```
|
|
926
|
+
|
|
927
|
+
Names on stdout, one per line, sorted, without duplicates, and nothing else: no
|
|
928
|
+
banner, no table, no `ci/deployability.json`. Exit 0 with the list, exit 2 with an
|
|
929
|
+
`[oa-validate] … Fix: …` line on stderr where there is no readable
|
|
930
|
+
`config/service/integration-contract.json` — a gate that cannot get the set must
|
|
931
|
+
stop, never continue with an empty one. The mode combines with no other option
|
|
932
|
+
(`--json`, `--library`, `--all`, `--workspace` are refused), because every other
|
|
933
|
+
mode prints a verdict into the same stdout.
|
|
934
|
+
|
|
935
|
+
The set is the one row `C-ENV-READS` measures the repository against — the contract's
|
|
936
|
+
`env` block, the `${VAR}` placeholders of `config/service/*.json`, and the endpoint
|
|
937
|
+
variables of the connectors the contract declares required — so the gate and the
|
|
938
|
+
uniform cannot disagree about what a service reads. It is deliberately NOT a grep for
|
|
939
|
+
`process.env`: most of the platform's environment is read inside installed libraries,
|
|
940
|
+
where a grep over `src/` sees nothing, and in a gate a set too small means a key
|
|
941
|
+
silently unchecked.
|
|
942
|
+
|
|
943
|
+
The other direction is deliberate too: a name the SOURCE reads that the contract does
|
|
944
|
+
not declare is absent here. That is a blocking finding of `C-ENV-READS`, with its own
|
|
945
|
+
fix — and a gate must not demand a value for a key nobody owns.
|
|
946
|
+
|
|
703
947
|
### Uniforma knihoven (`oa-validate --library`)
|
|
704
948
|
|
|
705
949
|
`manifests/library.manifest.json` is the same concept for the shared libraries, in the
|
package/docs/DESIGN.md
CHANGED
|
@@ -65,7 +65,15 @@ unit test in d.583 (`tests/unit/mockInfrastructureRetired.test.js`).
|
|
|
65
65
|
- `ValidationProofGenerator` — fingerprint + codec helpers
|
|
66
66
|
- `CookbookTestRunner` — cookbook execution probe. The single rail: the second
|
|
67
67
|
one, `WorkflowTestRunner`, was deleted on 2026-09-05 (no consumer anywhere in
|
|
68
|
-
the workspace, and it read the banned `step.id`)
|
|
68
|
+
the workspace, and it read the banned `step.id`). A step's `input` may
|
|
69
|
+
reference an earlier step's output, so a recipe can build, use and remove its
|
|
70
|
+
own fixture instead of depending on a seed one environment has and another does
|
|
71
|
+
not (owner: `../../../docs/governance/confirmations/converter-boot-cookbook.md`
|
|
72
|
+
001). The syntax belongs to the cookbook format
|
|
73
|
+
([variable-references.md](../../../docs/biz/40-cookbooks/variable-references.md))
|
|
74
|
+
and the traversal to `@onlineapps/cookbook-core`, the same rail
|
|
75
|
+
`WorkflowOrchestrator` runs on; `src/utils/stepReferences.js` supplies only the
|
|
76
|
+
run scope, and README § "A recipe builds its own fixture" says what it holds
|
|
69
77
|
- `CookbookTestUtils` — static cookbook-structure validators
|
|
70
78
|
|
|
71
79
|
### Test Suite Helpers
|
|
@@ -93,7 +101,8 @@ unit test in d.583 (`tests/unit/mockInfrastructureRetired.test.js`).
|
|
|
93
101
|
|
|
94
102
|
1. **Service contract** — structure, config files and the v3 operations rules
|
|
95
103
|
2. **Environment contract** — every name declared `env.required` is set
|
|
96
|
-
3. **Workflow capability** — can process cookbooks, in-process,
|
|
104
|
+
3. **Workflow capability** — can process cookbooks, in-process, including a
|
|
105
|
+
recipe whose later steps read what its earlier steps returned
|
|
97
106
|
4. **Connector contract** — the two declarations agree and the environment backs them
|
|
98
107
|
|
|
99
108
|
## What We DO NOT Test
|
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
],
|
|
109
109
|
"own": {
|
|
110
110
|
"concern": "the files of the template whose CONTENT the service decides, and the reason each is not held to the platform's copy",
|
|
111
|
-
"why": "a file added to the template is a decision about who guards it, and this class is the decision written down: no row holds these to the template, because their content IS the service. Dockerfile is the exception that proves the rule - its
|
|
111
|
+
"why": "a file added to the template is a decision about who guards it, and this class is the decision written down: no row holds these to the template, because their content IS the service. Dockerfile is the exception that proves the rule - its TWO platform facts are rows of their own, and everything else in it is this service's build: the node major is R-NODE's row, and WHICH USER the process runs as is R-USER's (added d.588c, after a production image built from the template was measured running as uid 0 while the dev container beside it ran as 1000). What a service BUILDS is its own; what it RUNS AS is the platform's. docs/README.md is the same decision for the map of the documentation tree: which branches a service has is the service's, and what the platform holds the tree to is not that prose but D-LINT, which asks the documentation lint whether the tree is clean. It is the map alone, not docs/ - the three installation documents under docs/80-setup/ ARE held to a skeleton, and a class covering them would exempt what a row requires. The class exists so that the completeness check of finding 18 has an answer other than silence for every file the template carries (automation-gates.md \u00a75)",
|
|
112
112
|
"allowed": [
|
|
113
113
|
"Dockerfile",
|
|
114
114
|
"index.js",
|
|
@@ -497,6 +497,20 @@
|
|
|
497
497
|
"fix": "in docker-compose.yml set the service block's command to [\"node\", \"index.js\"] (the entrypoint init.sh execs it, so node becomes PID 1), then recreate the container and smoke it",
|
|
498
498
|
"doc": "api/docs/governance/confirmations/biz-compose-pid1.md"
|
|
499
499
|
},
|
|
500
|
+
{
|
|
501
|
+
"id": "R-USER",
|
|
502
|
+
"check": "process-identity",
|
|
503
|
+
"image_path": "Dockerfile",
|
|
504
|
+
"image_stage": "production",
|
|
505
|
+
"path": "docker-compose.yml",
|
|
506
|
+
"service_user": "node",
|
|
507
|
+
"service_uid": "1000:1000",
|
|
508
|
+
"severity": "deploy",
|
|
509
|
+
"owner": "BIZ-general",
|
|
510
|
+
"why": "WHICH USER the process is, which is a platform fact and not a service build. Measured 2026-09-17 on a production image built from the platform template: `docker run --rm <image> id` answered uid=0(root) gid=0(root), while the dev container beside it ran as 1000:1000 because docker-compose.yml pins it - nobody had decided that difference, the production stage simply declared no USER, and nothing in the uniform could say so because Dockerfile is a file of the own class. This row is the SECOND platform fact carved out of that class, for the same reason as the first (R-NODE, the node major): what a service BUILDS is its own, what it RUNS AS is the platform. It measures the two places nothing else holds - the production stage of the Dockerfile, which is the stage CI builds and the one the defect lived in, and the SERVICE node of the dev compose. The runner user: sits inside the block F-RUNNER holds byte for byte, and the production compose is the whole-file render of G-PROD, so neither is repeated here (change-discipline.md, One rail per concern). The two spellings are ONE identity, and the row carries both because no machine resolves an account name to a uid without the image: node is uid 1000, gid 1000 in the node images this platform builds on, measured docker run --rm node:24-alpine id node",
|
|
511
|
+
"fix": "in the production stage of Dockerfile put RUN mkdir -p /app/logs /app/conn-runtime && chown -R node:node /app, then USER node before npm ci, and --chown=node:node on both COPY lines; in docker-compose.yml give the service node the line user: \"1000:1000\"",
|
|
512
|
+
"doc": "api/docs/biz/00-model/service-shape.md"
|
|
513
|
+
},
|
|
500
514
|
{
|
|
501
515
|
"id": "R-PORTS-DEV",
|
|
502
516
|
"check": "compose-no-ports",
|
|
@@ -587,6 +601,19 @@
|
|
|
587
601
|
"fix": "set DB_USER in this service's env template to oagen_<shortname>, where <shortname> is what config/service/config.json declares; the account and its grants are created by the operator installing the schema",
|
|
588
602
|
"doc": "api/docs/governance/confirmations/db-accounts-per-service.md"
|
|
589
603
|
},
|
|
604
|
+
{
|
|
605
|
+
"id": "D-DB-CI-ACCOUNT",
|
|
606
|
+
"check": "db-ci-account",
|
|
607
|
+
"path": ".gitlab-ci.yml",
|
|
608
|
+
"block": "oa-ci v1",
|
|
609
|
+
"key": "DB_USER",
|
|
610
|
+
"account": "root",
|
|
611
|
+
"severity": "deploy",
|
|
612
|
+
"owner": "BIZ-general",
|
|
613
|
+
"why": "D-DB-ACCOUNT one row up measures the DECLARATION - the account production installs. Nothing measured the ONE environment where that migration set is applied every day. Measured 2026-09-16 over the eight biz repositories: seven set DB_USER: \"root\" in their test job while declaring oagen_<service> in the env template, so CI proved the set under rights no production box grants - the gap db-migrations-first-deploy 001 names (\"migrace nikdy pod rootem\") in the only place a migration can be tried before a deploy. A separate row and not a wider D-DB-ACCOUNT: another file, another fix, and one row with two fixes is two mechanisms under one name (automation-gates.md §1.2). It reads the lines OUTSIDE the oa-ci v1 block, because that block is the platform's and G-CI compares it byte for byte; reading a region and not the whole file is also what keeps it clear of the defect that had the whole-file row over .gitlab-ci.yml withdrawn after three days. It asks WHO the database steps run as and deliberately not WHETHER a repository runs them: a job that builds no schema has no account for the step to create, and demanding one would be this row inventing a rule (truth-over-agreement.md §6). Silent for a service with no database block by construction - pdfgen is the live case",
|
|
614
|
+
"fix": "in the test job of .gitlab-ci.yml set DB_USER to the account config/env-templates/<service>.env declares, give DB_PASSWORD a throwaway value of the job, name the sidecar's root credential CI_DB_ROOT_USER / CI_DB_ROOT_PASSWORD, and run `npx oa-biz-ci-gate setup-db-account` as the first before_script step, before ci:gate:setup",
|
|
615
|
+
"doc": "api/docs/governance/confirmations/db-accounts-per-service.md"
|
|
616
|
+
},
|
|
590
617
|
{
|
|
591
618
|
"id": "D-DB-HEADERS",
|
|
592
619
|
"check": "install-contract",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onlineapps/conn-orch-validator",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "11.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"
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"author": "OnlineApps",
|
|
29
29
|
"license": "PROPRIETARY",
|
|
30
30
|
"dependencies": {
|
|
31
|
+
"@onlineapps/cookbook-core": "6.0.0",
|
|
31
32
|
"@onlineapps/logger-contract": "2.0.0",
|
|
32
33
|
"@onlineapps/service-validator-core": "2.1.0"
|
|
33
34
|
},
|