@onlineapps/conn-orch-validator 8.0.0 → 8.1.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 +24 -0
- package/package.json +2 -2
- package/templates/business-service/.gitlab-ci.yml +126 -7
- 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 +22 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,30 @@ All notable changes to this package. Follows [Keep a Changelog](https://keepacha
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [8.1.0] — 2026-09-15
|
|
8
|
+
|
|
9
|
+
### Changed — deploy job šablony aplikuje migrace DB mezi `pull` a `up` (d.421)
|
|
10
|
+
|
|
11
|
+
Blok `oa-ci v1` v `templates/business-service/.gitlab-ci.yml` (řádek `G-CI`) má nový krok: po
|
|
12
|
+
`compose pull` a **před** `compose up` pustí na biz boxu běžec `api/scripts/lib/mariadb-migrations.sh`
|
|
13
|
+
z klonu api, který si krok uniformy vedle checkoutu stejně dělá — fáze 2 rozhodnutí vlastníka
|
|
14
|
+
`api/docs/governance/confirmations/db-migrations-first-deploy.md` 001. Pořadí je ta pojistka:
|
|
15
|
+
nový obraz je stažený, starý kontejner ještě obsluhuje, takže selhání migrace zastaví nasazení
|
|
16
|
+
s běžící předchozí verzí. Běžec se **nese**, nekopíruje: proud, kterým ssh krmí `bash -s`, je řádek
|
|
17
|
+
hesla, volby shellu, běžec z klonu api a teprve pak tělo skriptu — druhá implementace aplikátoru
|
|
18
|
+
nevzniká nikde (`change-discipline.md` § One rail per concern).
|
|
19
|
+
|
|
20
|
+
Co o schématu rozhoduje: `config/service/integration-contract.json`. Služba bez bloku `database`
|
|
21
|
+
vypíše `NOT APPLICABLE (no database block)` a nasazuje se dál; služba s blokem dostane
|
|
22
|
+
`database.schema` migrované z adresáře, který jmenuje `database.migrations`, s trackerem
|
|
23
|
+
`config/runtime/applied-migrations-<schema>.txt` ve vlastním checkoutu na boxu (tentýž plochý formát
|
|
24
|
+
jako fáze 1). Účet je účet služby (`MARIADB_MIGRATION_USER`/`_PASSWORD` z env souborů na boxu, nově
|
|
25
|
+
deklarované v `config/env-templates/__SERVICE_NAME__.env`), nikdy root, a krok **nic nezakládá**:
|
|
26
|
+
chybí-li klient `mysql`, klíč, env soubor nebo nemá-li účet na schéma právo, job zčervená s hláškou
|
|
27
|
+
na runbook `api/docs/setup/INSTALL.md`.
|
|
28
|
+
|
|
29
|
+
## [8.0.0] — 2026-09-14
|
|
30
|
+
|
|
7
31
|
### Added — zahazovací testovací schéma je export balíčku (d.407)
|
|
8
32
|
|
|
9
33
|
`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": "8.
|
|
3
|
+
"version": "8.1.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,7 +28,7 @@
|
|
|
28
28
|
"author": "OnlineApps",
|
|
29
29
|
"license": "PROPRIETARY",
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@onlineapps/logger-contract": "1.
|
|
31
|
+
"@onlineapps/logger-contract": "1.3.0",
|
|
32
32
|
"@onlineapps/service-validator-core": "2.0.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
@@ -198,6 +198,39 @@ deploy-production:
|
|
|
198
198
|
exit 1
|
|
199
199
|
fi
|
|
200
200
|
- echo "Deploying $BIZ_IMAGE_DIGEST to $DEPLOY_HOST ($CI_ENVIRONMENT_URL)"
|
|
201
|
+
# The read-only api clone the uniform step already made beside this checkout.
|
|
202
|
+
# Derived from CI_PROJECT_DIR rather than spelled out a second time: the
|
|
203
|
+
# layout is declared once, in GIT_CLONE_PATH, so the two cannot drift apart.
|
|
204
|
+
# TWO steps read it: the migration runner streamed to the box below, and the
|
|
205
|
+
# post-deploy gate at the end of this job.
|
|
206
|
+
- API_CHECKOUT="$(dirname "$(dirname "$CI_PROJECT_DIR")")/api"
|
|
207
|
+
# WHICH SCHEMA, IF ANY (confirmation db-migrations-first-deploy 001, phase 2).
|
|
208
|
+
# Read from the one declaration there is - this service's own installation
|
|
209
|
+
# contract - and read HERE, on the runner: the contract is this repository's,
|
|
210
|
+
# jq is in this image, and the box is not asked to carry a JSON parser for two
|
|
211
|
+
# values. A service that declares no `database` block has no schema to
|
|
212
|
+
# migrate, and the step below says that out loud rather than passing over it
|
|
213
|
+
# in silence (automation-gates.md 5: a step that checked nothing must never
|
|
214
|
+
# look like one that checked).
|
|
215
|
+
- |
|
|
216
|
+
CONTRACT="$CI_PROJECT_DIR/config/service/integration-contract.json"
|
|
217
|
+
if [ ! -f "$CONTRACT" ]; then
|
|
218
|
+
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
|
|
219
|
+
exit 1
|
|
220
|
+
fi
|
|
221
|
+
BIZ_DB_SCHEMA="$(jq -r '.database.schema // empty' "$CONTRACT")"
|
|
222
|
+
BIZ_DB_MIGRATIONS="$(jq -r '.database.migrations // empty' "$CONTRACT")"
|
|
223
|
+
if [ -n "$BIZ_DB_SCHEMA" ] && [ -z "$BIZ_DB_MIGRATIONS" ]; then
|
|
224
|
+
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
|
|
225
|
+
exit 1
|
|
226
|
+
fi
|
|
227
|
+
if [ -n "$BIZ_DB_MIGRATIONS" ]; then
|
|
228
|
+
# The runner applies a DIRECTORY and does not descend into it; the
|
|
229
|
+
# contract declares the glob (migrations/*.sql, migrations/BASELINE/*.sql),
|
|
230
|
+
# so the directory is its dirname - the same reading the runbook gives it
|
|
231
|
+
# (api/docs/setup/INSTALL.md, applying a business schema).
|
|
232
|
+
BIZ_DB_MIGRATIONS="$(dirname "$BIZ_DB_MIGRATIONS")"
|
|
233
|
+
fi
|
|
201
234
|
# DEPLOY_PATH / DEPLOY_HOST / DEPLOY_USER are CI variables: the box decides
|
|
202
235
|
# where its checkout lives, and no path is baked into this file.
|
|
203
236
|
#
|
|
@@ -208,18 +241,41 @@ deploy-production:
|
|
|
208
241
|
# first and the script after it: the remote reads that one line, exports it,
|
|
209
242
|
# and hands the rest of the stream to `bash -s` - ssh's stdin is a socket
|
|
210
243
|
# there, so `read` consumes exactly the line and no more.
|
|
244
|
+
#
|
|
245
|
+
# The shell the remote runs is ASSEMBLED from three parts, in this order: the
|
|
246
|
+
# password line, the shell options, and then the platform's own MariaDB
|
|
247
|
+
# migration runner followed by the body below. The runner is carried rather
|
|
248
|
+
# than copied - it is api/scripts/lib/mariadb-migrations.sh out of the clone
|
|
249
|
+
# the uniform step made, so the box applies the same shell the runbook applies
|
|
250
|
+
# by hand in phase 1, and no second implementation of it exists anywhere
|
|
251
|
+
# (change-discipline.md - One rail per concern). The options are set once, for
|
|
252
|
+
# the whole assembled stream, which is why they are no longer the first line
|
|
253
|
+
# of the body.
|
|
211
254
|
- |
|
|
212
255
|
{
|
|
213
256
|
printf '%s\n' "$CI_REGISTRY_PASSWORD"
|
|
257
|
+
printf '%s\n' "set -euo pipefail"
|
|
258
|
+
cat "$API_CHECKOUT/scripts/lib/mariadb-migrations.sh"
|
|
214
259
|
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
|
|
260
|
+
} <<'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
261
|
DEPLOY_PATH="$1"
|
|
218
262
|
REGISTRY="$2"
|
|
219
263
|
REGISTRY_USER="$3"
|
|
220
264
|
PROJECT_SLUG="$4"
|
|
221
265
|
export BIZ_IMAGE_DIGEST="$5"
|
|
222
266
|
PROJECT_PATH="$6"
|
|
267
|
+
# Empty when the installation contract declares no `database` block - the
|
|
268
|
+
# only thing that turns the migration step below on or off.
|
|
269
|
+
DB_SCHEMA="$7"
|
|
270
|
+
DB_MIGRATIONS_DIR="$8"
|
|
271
|
+
# This service's own env file on the box, the per-machine half of the two
|
|
272
|
+
# the compose file names (../shared.env is the host's). It carries the
|
|
273
|
+
# migration account - the SAME account the service connects as.
|
|
274
|
+
SERVICE_ENV="config/env-active/__SERVICE_NAME__.env"
|
|
275
|
+
# The host-level env file of the biz box, one per box and referenced rather
|
|
276
|
+
# than copied (server-topology 004) - the same path docker-compose.production.yml
|
|
277
|
+
# names.
|
|
278
|
+
HOST_ENV="../shared.env"
|
|
223
279
|
# REGISTRY_PASSWORD arrives in the environment, from the first line of this
|
|
224
280
|
# script's own stdin. Empty means the runner had no CI_REGISTRY_PASSWORD:
|
|
225
281
|
# the pull would fail later with an authentication error that names nothing.
|
|
@@ -247,6 +303,72 @@ deploy-production:
|
|
|
247
303
|
echo "$REGISTRY_PASSWORD" | docker --config "$DOCKER_CONFIG_DIR" login -u "$REGISTRY_USER" --password-stdin "$REGISTRY"
|
|
248
304
|
echo "[deploy] pulling $BIZ_IMAGE_DIGEST"
|
|
249
305
|
docker --config "$DOCKER_CONFIG_DIR" compose -f docker-compose.production.yml pull
|
|
306
|
+
|
|
307
|
+
# ─── Migrations: after the pull, before the switch ───────────────
|
|
308
|
+
# Owner decision api/docs/governance/confirmations/db-migrations-first-deploy.md
|
|
309
|
+
# 001, phase 2. The ordering IS the safety: at this point the new image is
|
|
310
|
+
# on the box and the OLD container is still serving, so a migration that
|
|
311
|
+
# fails stops the deploy with the previous version running and the new one
|
|
312
|
+
# never started. Applying after the switch would mean the new code meets a
|
|
313
|
+
# schema it does not have; applying before the pull would mean a schema
|
|
314
|
+
# change for an image that might not even be pullable.
|
|
315
|
+
#
|
|
316
|
+
# The account is the SERVICE's, never root: its grants cover its own schema
|
|
317
|
+
# and nothing else, so a migration that reaches sideways into a neighbouring
|
|
318
|
+
# database is refused by the server rather than politely applied
|
|
319
|
+
# (db-accounts-per-service 001). This step CREATES NOTHING - schema and
|
|
320
|
+
# account are an operator's step in the runbook, phase 1, and a deploy that
|
|
321
|
+
# provisioned what it found missing would both grant itself those privileges
|
|
322
|
+
# and hide a wrongly provisioned box.
|
|
323
|
+
if [ -z "$DB_SCHEMA" ]; then
|
|
324
|
+
echo "[deploy] migrations NOT APPLICABLE (no database block) - config/service/integration-contract.json declares no database, so this service has no schema to migrate."
|
|
325
|
+
else
|
|
326
|
+
# The two env files the compose names, in the order it names them: the
|
|
327
|
+
# host-level one first, this service's own second, so the same value wins
|
|
328
|
+
# here that wins inside the container. Reading only one of them would
|
|
329
|
+
# make this step disagree with the service it migrates for, depending on
|
|
330
|
+
# which file the box happens to carry DB_HOST in.
|
|
331
|
+
for env_file in "$HOST_ENV" "$SERVICE_ENV"; do
|
|
332
|
+
if [ ! -f "$env_file" ]; then
|
|
333
|
+
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
|
|
334
|
+
exit 1
|
|
335
|
+
fi
|
|
336
|
+
set -a
|
|
337
|
+
. "$env_file"
|
|
338
|
+
set +a
|
|
339
|
+
done
|
|
340
|
+
missing=""
|
|
341
|
+
[ -n "${MARIADB_MIGRATION_USER:-}" ] || missing="$missing MARIADB_MIGRATION_USER"
|
|
342
|
+
[ -n "${MARIADB_MIGRATION_PASSWORD:-}" ] || missing="$missing MARIADB_MIGRATION_PASSWORD"
|
|
343
|
+
[ -n "${DB_HOST:-}" ] || missing="$missing DB_HOST"
|
|
344
|
+
[ -n "${DB_PORT:-}" ] || missing="$missing DB_PORT"
|
|
345
|
+
if [ -n "$missing" ]; then
|
|
346
|
+
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
|
|
347
|
+
exit 1
|
|
348
|
+
fi
|
|
349
|
+
# The precondition confirmation 001 demands before any migration: the
|
|
350
|
+
# account opens the schema, or this deploy stops and names the runbook.
|
|
351
|
+
if ! mariadb_migrations_can_open_over_tcp "$DB_HOST" "$DB_PORT" "$DB_SCHEMA"; then
|
|
352
|
+
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
|
|
353
|
+
exit 1
|
|
354
|
+
fi
|
|
355
|
+
# The tracker is the caller's state and it lives in the checkout on this
|
|
356
|
+
# box, under the gitignored config/runtime/ - same file name and same flat
|
|
357
|
+
# format the runbook writes in phase 1, so a schema already carried
|
|
358
|
+
# forward is skipped rather than applied twice (confirmation 001:
|
|
359
|
+
# "fáze 2 nesmí změnit sémantiku trackeru").
|
|
360
|
+
mkdir -p config/runtime
|
|
361
|
+
TRACKER="config/runtime/applied-migrations-${DB_SCHEMA}.txt"
|
|
362
|
+
touch "$TRACKER"
|
|
363
|
+
apply_mariadb_migrations_over_tcp "$DB_MIGRATIONS_DIR" "$TRACKER" "$DB_HOST" "$DB_PORT" "$DB_SCHEMA"
|
|
364
|
+
# The runner logs each file it applies; what this line adds is the state
|
|
365
|
+
# that outlives the deploy. The runner's own counter of files it passed
|
|
366
|
+
# over is deliberately not echoed: no word that reads as a bypass belongs
|
|
367
|
+
# anywhere in a deploy job, and the gate that keeps this job free of one
|
|
368
|
+
# judges by the word, not by the intent.
|
|
369
|
+
echo "[deploy] migrations $DB_SCHEMA: $MARIADB_MIGRATIONS_APPLIED applied now, $(wc -l < "$TRACKER" | tr -d " ") recorded in $DEPLOY_PATH/$TRACKER"
|
|
370
|
+
fi
|
|
371
|
+
|
|
250
372
|
docker --config "$DOCKER_CONFIG_DIR" compose -f docker-compose.production.yml up -d
|
|
251
373
|
REMOTE
|
|
252
374
|
# The deploy is not finished until the platform's own post-deploy gate says
|
|
@@ -257,11 +379,8 @@ deploy-production:
|
|
|
257
379
|
# runs in remote mode — same script, same contract, same verdicts as a local
|
|
258
380
|
# run, only reading over ssh.
|
|
259
381
|
- |
|
|
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"
|
|
382
|
+
# The gate runs out of the same read-only api clone the migration runner
|
|
383
|
+
# was streamed from; API_CHECKOUT is derived once, above.
|
|
265
384
|
bash "$API_CHECKOUT/scripts/run-post-deploy-gate.sh" \
|
|
266
385
|
--target "$DEPLOY_GATE_TARGETS" \
|
|
267
386
|
--biz-host "$DEPLOY_USER@$DEPLOY_HOST" \
|
|
@@ -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,28 @@ 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. Two 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
|
+
|
|
113
|
+
Who creates them, with which grants and which collation, is the runbook's step
|
|
114
|
+
and is not repeated here: `api/docs/setup/INSTALL.md`, the business-schema step.
|
|
115
|
+
The decision behind both halves — two phases, the service account as the
|
|
116
|
+
isolation guard, one runner and one tracker across them — is owner confirmation
|
|
117
|
+
`api/docs/governance/confirmations/db-migrations-first-deploy.md` 001.
|
|
118
|
+
|
|
97
119
|
## Status
|
|
98
120
|
|
|
99
121
|
Current. One of the three files the installation contract requires by name —
|