@onlineapps/conn-orch-validator 8.0.0 → 9.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 +63 -0
- package/package.json +3 -3
- package/templates/business-service/.gitlab-ci.yml +144 -24
- package/templates/business-service/config/env-templates/__SERVICE_NAME__.env +18 -0
- package/templates/business-service/config/env-templates/shared.env +1 -1
- package/templates/business-service/docs/80-setup/INSTALL.md +29 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,69 @@ All notable changes to this package. Follows [Keep a Changelog](https://keepacha
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [9.0.0] — 2026-09-15
|
|
8
|
+
|
|
9
|
+
### Removed — šablona už nenese job `verify-installation-contract` (d.470)
|
|
10
|
+
|
|
11
|
+
Blok `oa-ci v1` v `templates/business-service/.gitlab-ci.yml` deklaroval job, který pouštěl
|
|
12
|
+
`sh scripts/verify-installation-docs-sql-contract.sh`. Šablona ten skript **nikdy nenesla**
|
|
13
|
+
(`templates/business-service/scripts/` = jen `verify-deploy-uniform.sh`) — osm biz repozitářů mělo
|
|
14
|
+
každý vlastní kopii a mapa vydání d.320 ruší všech osm. Job v bloku tedy sliboval kontrolu, kterou
|
|
15
|
+
nově nascaffoldovaná služba nemůže provést (`automation-gates.md` §5).
|
|
16
|
+
|
|
17
|
+
Norma se nikam neztratila, jen má jednu kolej místo dvou: `install-contract` je ve validatoru
|
|
18
|
+
(`src/utils/installContract.js`) a manifest ji vymáhá řádky `G-SETUP`, `D-DB-PACKAGE` a
|
|
19
|
+
`D-DB-HEADERS`, které běží v uniformě před SSH krokem téhož deploy jobu (konfirmace
|
|
20
|
+
`biz-service-manifest` 008) — a jako podpříkaz `biz-ci-gate verify-install-contract`.
|
|
21
|
+
|
|
22
|
+
### Changed — test job šablony nedělá `REGISTRY_URL` (d.470)
|
|
23
|
+
|
|
24
|
+
`test:` (mimo značky, výchozí job nové služby) deklaroval `REGISTRY_URL: "http://localhost:33100"`.
|
|
25
|
+
Nic, co ten job spouští, ho nečte: `test:ci` je `jest`, `jest.config.js` matchuje
|
|
26
|
+
`**/tests/**/*.test.js` a jediný test šablony požaduje `src/handlers/v3/echo.js`. Klíč byl HTTP
|
|
27
|
+
adresou registru pro `getService()`; konfirmace `biz-discovery-redis` 001 přesunula čtení na Redis
|
|
28
|
+
projekci a `725b25c2` HTTP volání odstranil. Čtyři otázky před rušením deklarace jsou zodpovězeny
|
|
29
|
+
v `api/docs/governance/confirmations/declaration-removal.md` 001. Deklarace klíče v
|
|
30
|
+
`config/env-templates/shared.env` se netýká — tu vlastní `api/config/shared-env.json`.
|
|
31
|
+
|
|
32
|
+
### Changed — deploy job migruje jen tam, kde fáze 1 proběhla (d.477)
|
|
33
|
+
|
|
34
|
+
Krok migrací dělal `touch "$TRACKER"` a poté aplikoval vše, co tracker neznal — na boxu, kde
|
|
35
|
+
fáze 1 nikdy neběžela, by tak deploy aplikoval **celou řadu sám**. Konfirmace
|
|
36
|
+
`api/docs/governance/confirmations/db-migrations-first-deploy.md` 001 dělí práci na dvě fáze a
|
|
37
|
+
první z nich (schéma, účet a první běh téhož běžce ručně podle runbooku) nechává člověku.
|
|
38
|
+
|
|
39
|
+
Tracker `config/runtime/applied-migrations-<schema>.txt` je proto od teď **precondition**, ne
|
|
40
|
+
výstup: chybí-li, job končí fail-fast hláškou `[deploy] Migrations tracker <cesta> missing -
|
|
41
|
+
phase 1 … has not run on this box` a jmenuje runbook `api/docs/setup/INSTALL.md`. Žádný `touch`,
|
|
42
|
+
žádné `mkdir` — krok nezakládá nic, stejně jako nezakládá schéma ani účet. Prázdný tracker
|
|
43
|
+
zůstává platným stavem (fáze 1 bez přenesených souborů). `docs/80-setup/INSTALL.md` šablony
|
|
44
|
+
§ Migrations at deploy time to říká třetí odrážkou.
|
|
45
|
+
|
|
46
|
+
## [8.1.0] — 2026-09-15
|
|
47
|
+
|
|
48
|
+
### Changed — deploy job šablony aplikuje migrace DB mezi `pull` a `up` (d.421)
|
|
49
|
+
|
|
50
|
+
Blok `oa-ci v1` v `templates/business-service/.gitlab-ci.yml` (řádek `G-CI`) má nový krok: po
|
|
51
|
+
`compose pull` a **před** `compose up` pustí na biz boxu běžec `api/scripts/lib/mariadb-migrations.sh`
|
|
52
|
+
z klonu api, který si krok uniformy vedle checkoutu stejně dělá — fáze 2 rozhodnutí vlastníka
|
|
53
|
+
`api/docs/governance/confirmations/db-migrations-first-deploy.md` 001. Pořadí je ta pojistka:
|
|
54
|
+
nový obraz je stažený, starý kontejner ještě obsluhuje, takže selhání migrace zastaví nasazení
|
|
55
|
+
s běžící předchozí verzí. Běžec se **nese**, nekopíruje: proud, kterým ssh krmí `bash -s`, je řádek
|
|
56
|
+
hesla, volby shellu, běžec z klonu api a teprve pak tělo skriptu — druhá implementace aplikátoru
|
|
57
|
+
nevzniká nikde (`change-discipline.md` § One rail per concern).
|
|
58
|
+
|
|
59
|
+
Co o schématu rozhoduje: `config/service/integration-contract.json`. Služba bez bloku `database`
|
|
60
|
+
vypíše `NOT APPLICABLE (no database block)` a nasazuje se dál; služba s blokem dostane
|
|
61
|
+
`database.schema` migrované z adresáře, který jmenuje `database.migrations`, s trackerem
|
|
62
|
+
`config/runtime/applied-migrations-<schema>.txt` ve vlastním checkoutu na boxu (tentýž plochý formát
|
|
63
|
+
jako fáze 1). Účet je účet služby (`MARIADB_MIGRATION_USER`/`_PASSWORD` z env souborů na boxu, nově
|
|
64
|
+
deklarované v `config/env-templates/__SERVICE_NAME__.env`), nikdy root, a krok **nic nezakládá**:
|
|
65
|
+
chybí-li klient `mysql`, klíč, env soubor nebo nemá-li účet na schéma právo, job zčervená s hláškou
|
|
66
|
+
na runbook `api/docs/setup/INSTALL.md`.
|
|
67
|
+
|
|
68
|
+
## [8.0.0] — 2026-09-14
|
|
69
|
+
|
|
7
70
|
### Added — zahazovací testovací schéma je export balíčku (d.407)
|
|
8
71
|
|
|
9
72
|
`createThrowawaySchema` (`src/utils/throwawaySchema.js`, na indexu balíčku) staví schéma, které
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onlineapps/conn-orch-validator",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "9.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,8 +28,8 @@
|
|
|
28
28
|
"author": "OnlineApps",
|
|
29
29
|
"license": "PROPRIETARY",
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@onlineapps/logger-contract": "
|
|
32
|
-
"@onlineapps/service-validator-core": "2.0.
|
|
31
|
+
"@onlineapps/logger-contract": "2.0.0",
|
|
32
|
+
"@onlineapps/service-validator-core": "2.0.1"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"jest": "^29.5.0"
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
# Written by @onlineapps/conn-orch-validator (row G-CI of
|
|
3
3
|
# manifests/biz-service.manifest.json). The PLATFORM half of this pipeline: which
|
|
4
4
|
# pipelines run at all, how a service is built, how its image is identified (R5),
|
|
5
|
-
# how the digest reaches the deploy (R1/R2),
|
|
6
|
-
# the
|
|
7
|
-
# (confirmation
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
5
|
+
# how the digest reaches the deploy (R1/R2), the uniform gate that runs before
|
|
6
|
+
# the SSH step (confirmation biz-service-manifest 008) and the post-deploy gate
|
|
7
|
+
# that runs after it (confirmation deploy-gate-targets 003). The installation
|
|
8
|
+
# contract is checked by that uniform gate, in the manifest rows G-SETUP,
|
|
9
|
+
# D-DB-PACKAGE and D-DB-HEADERS, and no longer by a job of its own (d.470).
|
|
10
|
+
# npx oa-sync-template .gitlab-ci.yml --target . rewrites everything between
|
|
11
|
+
# these two markers and nothing outside them.
|
|
11
12
|
#
|
|
12
13
|
# Outside them is this repository's own: its test job - which database, which
|
|
13
14
|
# ci:gate:* steps and which artefacts its integration needs is a fact about the
|
|
@@ -32,16 +33,6 @@ variables:
|
|
|
32
33
|
IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
|
|
33
34
|
IMAGE_LATEST: $CI_REGISTRY_IMAGE:latest
|
|
34
35
|
|
|
35
|
-
verify-installation-contract:
|
|
36
|
-
stage: test
|
|
37
|
-
image: alpine:latest
|
|
38
|
-
script:
|
|
39
|
-
- sh scripts/verify-installation-docs-sql-contract.sh
|
|
40
|
-
rules:
|
|
41
|
-
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
42
|
-
- if: $CI_COMMIT_BRANCH == "main"
|
|
43
|
-
- if: $CI_COMMIT_BRANCH == "production"
|
|
44
|
-
|
|
45
36
|
build:
|
|
46
37
|
stage: build
|
|
47
38
|
image: docker:24
|
|
@@ -198,6 +189,39 @@ deploy-production:
|
|
|
198
189
|
exit 1
|
|
199
190
|
fi
|
|
200
191
|
- echo "Deploying $BIZ_IMAGE_DIGEST to $DEPLOY_HOST ($CI_ENVIRONMENT_URL)"
|
|
192
|
+
# The read-only api clone the uniform step already made beside this checkout.
|
|
193
|
+
# Derived from CI_PROJECT_DIR rather than spelled out a second time: the
|
|
194
|
+
# layout is declared once, in GIT_CLONE_PATH, so the two cannot drift apart.
|
|
195
|
+
# TWO steps read it: the migration runner streamed to the box below, and the
|
|
196
|
+
# post-deploy gate at the end of this job.
|
|
197
|
+
- API_CHECKOUT="$(dirname "$(dirname "$CI_PROJECT_DIR")")/api"
|
|
198
|
+
# WHICH SCHEMA, IF ANY (confirmation db-migrations-first-deploy 001, phase 2).
|
|
199
|
+
# Read from the one declaration there is - this service's own installation
|
|
200
|
+
# contract - and read HERE, on the runner: the contract is this repository's,
|
|
201
|
+
# jq is in this image, and the box is not asked to carry a JSON parser for two
|
|
202
|
+
# values. A service that declares no `database` block has no schema to
|
|
203
|
+
# migrate, and the step below says that out loud rather than passing over it
|
|
204
|
+
# in silence (automation-gates.md 5: a step that checked nothing must never
|
|
205
|
+
# look like one that checked).
|
|
206
|
+
- |
|
|
207
|
+
CONTRACT="$CI_PROJECT_DIR/config/service/integration-contract.json"
|
|
208
|
+
if [ ! -f "$CONTRACT" ]; then
|
|
209
|
+
echo "[deploy] FATAL: no installation contract at $CONTRACT - this deploy cannot tell whether the service has a schema to migrate, and guessing is not an option. Fix: commit config/service/integration-contract.json (the uniform manifest requires it of every biz repository)." >&2
|
|
210
|
+
exit 1
|
|
211
|
+
fi
|
|
212
|
+
BIZ_DB_SCHEMA="$(jq -r '.database.schema // empty' "$CONTRACT")"
|
|
213
|
+
BIZ_DB_MIGRATIONS="$(jq -r '.database.migrations // empty' "$CONTRACT")"
|
|
214
|
+
if [ -n "$BIZ_DB_SCHEMA" ] && [ -z "$BIZ_DB_MIGRATIONS" ]; then
|
|
215
|
+
echo "[deploy] FATAL: $CONTRACT declares database.schema ($BIZ_DB_SCHEMA) and no database.migrations - the deploy would not know which files to apply. Fix: declare the glob of the directory that holds them (api/docs/biz/70-contracts/database-contract.md)." >&2
|
|
216
|
+
exit 1
|
|
217
|
+
fi
|
|
218
|
+
if [ -n "$BIZ_DB_MIGRATIONS" ]; then
|
|
219
|
+
# The runner applies a DIRECTORY and does not descend into it; the
|
|
220
|
+
# contract declares the glob (migrations/*.sql, migrations/BASELINE/*.sql),
|
|
221
|
+
# so the directory is its dirname - the same reading the runbook gives it
|
|
222
|
+
# (api/docs/setup/INSTALL.md, applying a business schema).
|
|
223
|
+
BIZ_DB_MIGRATIONS="$(dirname "$BIZ_DB_MIGRATIONS")"
|
|
224
|
+
fi
|
|
201
225
|
# DEPLOY_PATH / DEPLOY_HOST / DEPLOY_USER are CI variables: the box decides
|
|
202
226
|
# where its checkout lives, and no path is baked into this file.
|
|
203
227
|
#
|
|
@@ -208,18 +232,41 @@ deploy-production:
|
|
|
208
232
|
# first and the script after it: the remote reads that one line, exports it,
|
|
209
233
|
# and hands the rest of the stream to `bash -s` - ssh's stdin is a socket
|
|
210
234
|
# there, so `read` consumes exactly the line and no more.
|
|
235
|
+
#
|
|
236
|
+
# The shell the remote runs is ASSEMBLED from three parts, in this order: the
|
|
237
|
+
# password line, the shell options, and then the platform's own MariaDB
|
|
238
|
+
# migration runner followed by the body below. The runner is carried rather
|
|
239
|
+
# than copied - it is api/scripts/lib/mariadb-migrations.sh out of the clone
|
|
240
|
+
# the uniform step made, so the box applies the same shell the runbook applies
|
|
241
|
+
# by hand in phase 1, and no second implementation of it exists anywhere
|
|
242
|
+
# (change-discipline.md - One rail per concern). The options are set once, for
|
|
243
|
+
# the whole assembled stream, which is why they are no longer the first line
|
|
244
|
+
# of the body.
|
|
211
245
|
- |
|
|
212
246
|
{
|
|
213
247
|
printf '%s\n' "$CI_REGISTRY_PASSWORD"
|
|
248
|
+
printf '%s\n' "set -euo pipefail"
|
|
249
|
+
cat "$API_CHECKOUT/scripts/lib/mariadb-migrations.sh"
|
|
214
250
|
cat
|
|
215
|
-
} <<'REMOTE' | ssh $DEPLOY_USER@$DEPLOY_HOST "IFS= read -r REGISTRY_PASSWORD; export REGISTRY_PASSWORD; bash -s -- '$DEPLOY_PATH' '$CI_REGISTRY' '$CI_REGISTRY_USER' '$CI_PROJECT_PATH_SLUG' '$BIZ_IMAGE_DIGEST' '$CI_PROJECT_PATH'"
|
|
216
|
-
set -euo pipefail
|
|
251
|
+
} <<'REMOTE' | ssh $DEPLOY_USER@$DEPLOY_HOST "IFS= read -r REGISTRY_PASSWORD; export REGISTRY_PASSWORD; bash -s -- '$DEPLOY_PATH' '$CI_REGISTRY' '$CI_REGISTRY_USER' '$CI_PROJECT_PATH_SLUG' '$BIZ_IMAGE_DIGEST' '$CI_PROJECT_PATH' '$BIZ_DB_SCHEMA' '$BIZ_DB_MIGRATIONS'"
|
|
217
252
|
DEPLOY_PATH="$1"
|
|
218
253
|
REGISTRY="$2"
|
|
219
254
|
REGISTRY_USER="$3"
|
|
220
255
|
PROJECT_SLUG="$4"
|
|
221
256
|
export BIZ_IMAGE_DIGEST="$5"
|
|
222
257
|
PROJECT_PATH="$6"
|
|
258
|
+
# Empty when the installation contract declares no `database` block - the
|
|
259
|
+
# only thing that turns the migration step below on or off.
|
|
260
|
+
DB_SCHEMA="$7"
|
|
261
|
+
DB_MIGRATIONS_DIR="$8"
|
|
262
|
+
# This service's own env file on the box, the per-machine half of the two
|
|
263
|
+
# the compose file names (../shared.env is the host's). It carries the
|
|
264
|
+
# migration account - the SAME account the service connects as.
|
|
265
|
+
SERVICE_ENV="config/env-active/__SERVICE_NAME__.env"
|
|
266
|
+
# The host-level env file of the biz box, one per box and referenced rather
|
|
267
|
+
# than copied (server-topology 004) - the same path docker-compose.production.yml
|
|
268
|
+
# names.
|
|
269
|
+
HOST_ENV="../shared.env"
|
|
223
270
|
# REGISTRY_PASSWORD arrives in the environment, from the first line of this
|
|
224
271
|
# script's own stdin. Empty means the runner had no CI_REGISTRY_PASSWORD:
|
|
225
272
|
# the pull would fail later with an authentication error that names nothing.
|
|
@@ -247,6 +294,83 @@ deploy-production:
|
|
|
247
294
|
echo "$REGISTRY_PASSWORD" | docker --config "$DOCKER_CONFIG_DIR" login -u "$REGISTRY_USER" --password-stdin "$REGISTRY"
|
|
248
295
|
echo "[deploy] pulling $BIZ_IMAGE_DIGEST"
|
|
249
296
|
docker --config "$DOCKER_CONFIG_DIR" compose -f docker-compose.production.yml pull
|
|
297
|
+
|
|
298
|
+
# ─── Migrations: after the pull, before the switch ───────────────
|
|
299
|
+
# Owner decision api/docs/governance/confirmations/db-migrations-first-deploy.md
|
|
300
|
+
# 001, phase 2. The ordering IS the safety: at this point the new image is
|
|
301
|
+
# on the box and the OLD container is still serving, so a migration that
|
|
302
|
+
# fails stops the deploy with the previous version running and the new one
|
|
303
|
+
# never started. Applying after the switch would mean the new code meets a
|
|
304
|
+
# schema it does not have; applying before the pull would mean a schema
|
|
305
|
+
# change for an image that might not even be pullable.
|
|
306
|
+
#
|
|
307
|
+
# The account is the SERVICE's, never root: its grants cover its own schema
|
|
308
|
+
# and nothing else, so a migration that reaches sideways into a neighbouring
|
|
309
|
+
# database is refused by the server rather than politely applied
|
|
310
|
+
# (db-accounts-per-service 001). This step CREATES NOTHING - schema and
|
|
311
|
+
# account are an operator's step in the runbook, phase 1, and a deploy that
|
|
312
|
+
# provisioned what it found missing would both grant itself those privileges
|
|
313
|
+
# and hide a wrongly provisioned box.
|
|
314
|
+
if [ -z "$DB_SCHEMA" ]; then
|
|
315
|
+
echo "[deploy] migrations NOT APPLICABLE (no database block) - config/service/integration-contract.json declares no database, so this service has no schema to migrate."
|
|
316
|
+
else
|
|
317
|
+
# The two env files the compose names, in the order it names them: the
|
|
318
|
+
# host-level one first, this service's own second, so the same value wins
|
|
319
|
+
# here that wins inside the container. Reading only one of them would
|
|
320
|
+
# make this step disagree with the service it migrates for, depending on
|
|
321
|
+
# which file the box happens to carry DB_HOST in.
|
|
322
|
+
for env_file in "$HOST_ENV" "$SERVICE_ENV"; do
|
|
323
|
+
if [ ! -f "$env_file" ]; then
|
|
324
|
+
echo "[deploy] Missing env file - $env_file does not exist on this box, and docker-compose.production.yml names it: between the two of them they carry the endpoint and the migration account of $DB_SCHEMA. Fix: place it on the box (both are per-machine and never committed), then re-run this deploy." >&2
|
|
325
|
+
exit 1
|
|
326
|
+
fi
|
|
327
|
+
set -a
|
|
328
|
+
. "$env_file"
|
|
329
|
+
set +a
|
|
330
|
+
done
|
|
331
|
+
missing=""
|
|
332
|
+
[ -n "${MARIADB_MIGRATION_USER:-}" ] || missing="$missing MARIADB_MIGRATION_USER"
|
|
333
|
+
[ -n "${MARIADB_MIGRATION_PASSWORD:-}" ] || missing="$missing MARIADB_MIGRATION_PASSWORD"
|
|
334
|
+
[ -n "${DB_HOST:-}" ] || missing="$missing DB_HOST"
|
|
335
|
+
[ -n "${DB_PORT:-}" ] || missing="$missing DB_PORT"
|
|
336
|
+
if [ -n "$missing" ]; then
|
|
337
|
+
echo "[deploy] Missing environment variable -$missing in $DEPLOY_PATH/$SERVICE_ENV. Expected: the migration account of $DB_SCHEMA (the same account as DB_USER, granted on that schema only) and the endpoint it is reached at. Fix: add the keys to that file on this box; config/env-templates/ declares each of them with the reason it exists." >&2
|
|
338
|
+
exit 1
|
|
339
|
+
fi
|
|
340
|
+
# The precondition confirmation 001 demands before any migration: the
|
|
341
|
+
# account opens the schema, or this deploy stops and names the runbook.
|
|
342
|
+
if ! mariadb_migrations_can_open_over_tcp "$DB_HOST" "$DB_PORT" "$DB_SCHEMA"; then
|
|
343
|
+
echo "[deploy] Schema or account missing - '$MARIADB_MIGRATION_USER' cannot open '$DB_SCHEMA' at $DB_HOST:$DB_PORT. Expected: the schema and the account, with grants on ${DB_SCHEMA}.* and nothing else, provisioned once by an operator. Fix: run the business-schema step of api/docs/setup/INSTALL.md on the infra box, then re-run this deploy. This step never creates either: provisioning is a decision, not a deploy side effect." >&2
|
|
344
|
+
exit 1
|
|
345
|
+
fi
|
|
346
|
+
# The tracker is the caller's state and it lives in the checkout on this
|
|
347
|
+
# box, under the gitignored config/runtime/ - same file name and same flat
|
|
348
|
+
# format the runbook writes in phase 1, so a schema already carried
|
|
349
|
+
# forward is skipped rather than applied twice (confirmation 001:
|
|
350
|
+
# "fáze 2 nesmí změnit sémantiku trackeru").
|
|
351
|
+
#
|
|
352
|
+
# Its EXISTENCE is the precondition, and the step never creates it.
|
|
353
|
+
# Confirmation 001 splits the work in two: phase 1 - schema, account and
|
|
354
|
+
# the first run of this same runner - is an operator's step in the
|
|
355
|
+
# runbook, and phase 2 is this job carrying that state forward. On a box
|
|
356
|
+
# phase 1 never touched there is no tracker, and a job that created one
|
|
357
|
+
# would apply the WHOLE series unattended on the first deploy, which is
|
|
358
|
+
# precisely the phase the owner kept manual. So a missing tracker stops
|
|
359
|
+
# the deploy and names the runbook, exactly as a missing schema does.
|
|
360
|
+
TRACKER="config/runtime/applied-migrations-${DB_SCHEMA}.txt"
|
|
361
|
+
if [ ! -f "$TRACKER" ]; then
|
|
362
|
+
echo "[deploy] Migrations tracker $DEPLOY_PATH/$TRACKER missing - phase 1 (the migrations of $DB_SCHEMA applied by hand with the same runner) has not run on this box. Expected: the flat tracker phase 1 leaves behind, carried forward by this deploy. Fix: run the business-schema migrations step of api/docs/setup/INSTALL.md for $DB_SCHEMA, then re-run this deploy. This step never creates the tracker: a deploy that did would apply the whole series unattended on a box nobody had provisioned." >&2
|
|
363
|
+
exit 1
|
|
364
|
+
fi
|
|
365
|
+
apply_mariadb_migrations_over_tcp "$DB_MIGRATIONS_DIR" "$TRACKER" "$DB_HOST" "$DB_PORT" "$DB_SCHEMA"
|
|
366
|
+
# The runner logs each file it applies; what this line adds is the state
|
|
367
|
+
# that outlives the deploy. The runner's own counter of files it passed
|
|
368
|
+
# over is deliberately not echoed: no word that reads as a bypass belongs
|
|
369
|
+
# anywhere in a deploy job, and the gate that keeps this job free of one
|
|
370
|
+
# judges by the word, not by the intent.
|
|
371
|
+
echo "[deploy] migrations $DB_SCHEMA: $MARIADB_MIGRATIONS_APPLIED applied now, $(wc -l < "$TRACKER" | tr -d " ") recorded in $DEPLOY_PATH/$TRACKER"
|
|
372
|
+
fi
|
|
373
|
+
|
|
250
374
|
docker --config "$DOCKER_CONFIG_DIR" compose -f docker-compose.production.yml up -d
|
|
251
375
|
REMOTE
|
|
252
376
|
# The deploy is not finished until the platform's own post-deploy gate says
|
|
@@ -257,11 +381,8 @@ deploy-production:
|
|
|
257
381
|
# runs in remote mode — same script, same contract, same verdicts as a local
|
|
258
382
|
# run, only reading over ssh.
|
|
259
383
|
- |
|
|
260
|
-
# The gate runs out of the read-only api clone the
|
|
261
|
-
#
|
|
262
|
-
# spelled out a second time: the layout is declared once, in
|
|
263
|
-
# GIT_CLONE_PATH, so the two cannot drift apart.
|
|
264
|
-
API_CHECKOUT="$(dirname "$(dirname "$CI_PROJECT_DIR")")/api"
|
|
384
|
+
# The gate runs out of the same read-only api clone the migration runner
|
|
385
|
+
# was streamed from; API_CHECKOUT is derived once, above.
|
|
265
386
|
bash "$API_CHECKOUT/scripts/run-post-deploy-gate.sh" \
|
|
266
387
|
--target "$DEPLOY_GATE_TARGETS" \
|
|
267
388
|
--biz-host "$DEPLOY_USER@$DEPLOY_HOST" \
|
|
@@ -279,7 +400,6 @@ test:
|
|
|
279
400
|
image: node:24-alpine
|
|
280
401
|
variables:
|
|
281
402
|
RABBITMQ_URL: "amqp://localhost:5672"
|
|
282
|
-
REGISTRY_URL: "http://localhost:33100"
|
|
283
403
|
NODE_ENV: "test"
|
|
284
404
|
script:
|
|
285
405
|
- npm ci
|
|
@@ -2,3 +2,21 @@
|
|
|
2
2
|
# Copy to config/env-active/__SERVICE_NAME__.env and adjust values.
|
|
3
3
|
|
|
4
4
|
SERVICE_NAME=__SERVICE_NAME__
|
|
5
|
+
|
|
6
|
+
# Schema migrations at deploy time. The deploy job (block `oa-ci v1` of
|
|
7
|
+
# .gitlab-ci.yml) runs api/scripts/lib/mariadb-migrations.sh between `compose
|
|
8
|
+
# pull` and `compose up`, and that runner REQUIRES both names - fail-fast, no
|
|
9
|
+
# default value (owner decision
|
|
10
|
+
# api/docs/governance/confirmations/db-migrations-first-deploy.md 001, phase 2).
|
|
11
|
+
# The account is the SAME one DB_USER names: its grants cover this service's
|
|
12
|
+
# schema and nothing else, so a migration that reached into a neighbouring
|
|
13
|
+
# database is refused by the server instead of quietly applied
|
|
14
|
+
# (db-accounts-per-service 001). Pattern: api/config/env-templates/auth.env.
|
|
15
|
+
#
|
|
16
|
+
# Commented out because this scaffold has no database: its
|
|
17
|
+
# config/service/integration-contract.json declares no `database` block, the
|
|
18
|
+
# deploy step prints NOT APPLICABLE and reads neither name. A service that
|
|
19
|
+
# declares that block uncomments both here and fills the values in
|
|
20
|
+
# config/env-active/ only - per machine, never in git.
|
|
21
|
+
# MARIADB_MIGRATION_USER=
|
|
22
|
+
# MARIADB_MIGRATION_PASSWORD=
|
|
@@ -40,7 +40,7 @@ MINIO_ACCESS_KEY=CHANGE_ME
|
|
|
40
40
|
# The secret of that same object-store account; shares the compose-interpolation note of MINIO_ACCESS_KEY.
|
|
41
41
|
MINIO_SECRET_KEY=CHANGE_ME
|
|
42
42
|
|
|
43
|
-
# How often (ms) a business service publishes its MQ heartbeat, and the ONE source of truth for how fresh that signal has to be: every staleness threshold is a multiple of this cadence (BIZ_HEARTBEAT_STALE_FACTOR), never a second duration set by hand - owner decision docs/governance/confirmations/biz-health-freshness.md 001, which supersedes the absolute INFRASTRUCTURE_HEALTH_BUSINESS_STALE_AFTER of batch 111a. It is shared because the publisher and the judge are different processes: the registry derives its monitor and janitor thresholds from it today, and @onlineapps/service-wrapper
|
|
43
|
+
# How often (ms) a business service publishes its MQ heartbeat, and the ONE source of truth for how fresh that signal has to be: every staleness threshold is a multiple of this cadence (BIZ_HEARTBEAT_STALE_FACTOR), never a second duration set by hand - owner decision docs/governance/confirmations/biz-health-freshness.md 001, which supersedes the absolute INFRASTRUCTURE_HEALTH_BUSINESS_STALE_AFTER of batch 111a. It is shared because the publisher and the judge are different processes: the registry derives its monitor and janitor thresholds from it today, and @onlineapps/service-wrapper reads it as the only cadence there is (the wrapper.registry.heartbeatInterval config key is retired and refused by name), so a cadence change cannot reach one side only.
|
|
44
44
|
BIZ_HEARTBEAT_INTERVAL_MS=30000
|
|
45
45
|
|
|
46
46
|
# How many heartbeat cadences a business service may stay silent before it is judged stale; the only knob on top of BIZ_HEARTBEAT_INTERVAL_MS, and the reason no threshold is written as a duration anywhere (owner decision docs/governance/confirmations/biz-health-freshness.md 001: the factor is config, not doctrine - changing it is an ordinary config change). May be fractional; the derived threshold is whole milliseconds.
|
|
@@ -94,6 +94,35 @@ CI publishes that digest from the build stage and the deploy job refuses to run
|
|
|
94
94
|
without it (requirement R1). A first deploy also clones the checkout on the box
|
|
95
95
|
if it is missing, so onboarding a new service needs no manual step there.
|
|
96
96
|
|
|
97
|
+
### Migrations at deploy time
|
|
98
|
+
|
|
99
|
+
Between that `pull` and that `up` the deploy job applies this service's pending
|
|
100
|
+
MariaDB migrations — the ordering is the safety: the new image is already on the
|
|
101
|
+
box, the previous container is still serving, and a migration that fails stops
|
|
102
|
+
the deploy before the switch. Three facts decide what happens:
|
|
103
|
+
|
|
104
|
+
- `config/service/integration-contract.json` → `database`. This scaffold declares
|
|
105
|
+
no such block, so the step prints `NOT APPLICABLE (no database block)` and the
|
|
106
|
+
deploy carries on. A service that declares one has its `database.schema`
|
|
107
|
+
migrated from the directory `database.migrations` names.
|
|
108
|
+
- `config/env-active/__SERVICE_NAME__.env` → `MARIADB_MIGRATION_USER` and
|
|
109
|
+
`MARIADB_MIGRATION_PASSWORD`, the service's own account. The step creates
|
|
110
|
+
neither the schema nor the account and refuses the deploy when that account
|
|
111
|
+
cannot open the schema, naming the runbook instead.
|
|
112
|
+
- `config/runtime/applied-migrations-<schema>.txt` on the box — the tracker.
|
|
113
|
+
**It must already be there**, and this job never creates it. The file is what
|
|
114
|
+
phase 1 leaves behind: the operator who created the schema and the account also
|
|
115
|
+
ran the first pass of the same runner by hand, from the runbook. A deploy onto a
|
|
116
|
+
box phase 1 never touched therefore stops with `Migrations tracker … missing`
|
|
117
|
+
rather than applying the whole series unattended.
|
|
118
|
+
|
|
119
|
+
Who creates the schema, the account and that first tracker — with which grants and
|
|
120
|
+
which collation — is the runbook's step and is not repeated here:
|
|
121
|
+
`api/docs/setup/INSTALL.md`, the business-schema step. The decision behind both
|
|
122
|
+
halves — two phases, the service account as the isolation guard, one runner and
|
|
123
|
+
one tracker across them — is owner confirmation
|
|
124
|
+
`api/docs/governance/confirmations/db-migrations-first-deploy.md` 001.
|
|
125
|
+
|
|
97
126
|
## Status
|
|
98
127
|
|
|
99
128
|
Current. One of the three files the installation contract requires by name —
|