# Deployment Runbooks

Operational runbooks for staging, demo, and production deployments across ALAI products

# Bilko Stage→Demo→Prod Cutover Runbook

# Bilko Stage→Demo→Prod Cutover Runbook

**Document ID:** BILKO-RUNBOOK-001  
**Last Updated:** 2026-08-15  
**Owner:** Skillforge (ALAI Holding AS)  
**Status:** Active — Azure canonical

## Purpose

This runbook documents the promotion path from Bilko `stage` environment to `demo` and eventually `prod`. It incorporates all changes delivered in Rounds 11-27, including massive test coverage expansion, 7+ production bug fixes, RBAC enforcement, permanent CORS configuration, and database migrations V14-V20.

**Context:** Bilko now runs on Azure Container Apps in `rg-bilko-demo`. The apps named `bilko-web-demo` and `bilko-api-demo` are customer-facing production; `app.bilko.cloud` and `api.bilko.cloud` route to them. Stage uses `bilko-web-stage` and `bilko-api-stage`. Both environments currently share the production Azure PostgreSQL server `bilko-demo-pg`, so every database action in this runbook is production-sensitive. *Sources: live read-only Azure query 2026-08-15; BookStack pages 3305 and 3121; `DEPLOY-MAP.md` at azdo/main `3db716d7`.*

## 1. Pre-Cutover Checklist

Complete ALL items before proceeding with cutover:

<table id="bkmrk-item-verification-co"><thead><tr><th>Item</th><th>Verification Command</th><th>Success Criteria</th><th>Status</th></tr></thead><tbody><tr><td>**High/Blocker MCs Resolved**</td><td>`node ~/system/tools/mc.js list --project bilko --priority H,BLOCKER --status open`</td><td>0 open tasks, or all explicitly waived by CEO</td><td>☐</td></tr><tr><td>**Azure DevOps Pipeline Health**</td><td>`az pipelines runs list --org https://dev.azure.com/alai-holding --project Bilko --pipeline-ids 1 --branch main --top 5 --query "[].{id:id,status:status,result:result,sourceBranch:sourceBranch,sourceVersion:sourceVersion,finishTime:finishTime}" -o table`  
*Source: verified read-only `az` query on 2026-08-15; pipeline definition `azure-pipelines.yml` at azdo/main `3db716d7`.*</td><td>Last 5 completed runs on main = SUCCESS, or every failure is explicitly adjudicated before promotion</td><td>☐</td></tr><tr><td>**Coverage Gate**</td><td>`cd apps/api && ./gradlew koverVerify`</td><td>Exit code 0 (current threshold ≥60%)</td><td>☐</td></tr><tr><td>**E2E Regression Tests**</td><td>Open the exact Azure DevOps run for the source commit and inspect `E2E_UAT` plus its published Playwright results.  
`az pipelines runs list --org https://dev.azure.com/alai-holding --project Bilko --pipeline-ids 1 --branch main --top 5 -o table`  
*Source: live `azure-pipelines.yml` stage `E2E_UAT` at azdo/main `3db716d7`.*</td><td>`E2E_UAT` succeeded and the published Playwright results contain no failures for the exact promoted commit</td><td>☐</td></tr><tr><td>**Database Backup**</td><td>`pg_dump "host=bilko-demo-pg.postgres.database.azure.com port=5432 dbname=bilko user=bilko_admin sslmode=require" --format=custom --no-acl --no-owner --file="bilko-backup-$(date +%Y%m%d-%H%M%S).dump"`  
*Source: BookStack page 3305, §5d “Database Backup and Restore”. Supply the password through the approved secure mechanism; never paste it into this page or shell history.*</td><td>Dump exits 0 and `pg_restore --list <dump-file>` exits 0; backup path and checksum recorded in the cutover evidence</td><td>☐</td></tr><tr><td>**CEO Sign-Off**</td><td>MC task comments or Slack approval</td><td>Explicit approval captured with timestamp</td><td>☐</td></tr></tbody></table>

## 2. Database Migration Plan (V14-V20)

Flyway migrations are applied by the `Flyway_Migrate` stage in the canonical Azure DevOps pipeline, after `Build` and before `Deploy_Stage`. This section preserves the migration order and critical dependencies. *Source: `azure-pipelines.yml` at azdo/main `3db716d7`, stage `Flyway_Migrate`.*

### Migration Sequence

<table id="bkmrk-version-description-"><thead><tr><th>Version</th><th>Description</th><th>MC Reference</th><th>Critical Notes</th></tr></thead><tbody><tr><td>**V14**</td><td>Demo + CI test seed</td><td>Round 12.7</td><td>Creates demo@bilko.rs user + test tenant for CI</td></tr><tr><td>**V15**</td><td>Trial fields (plan\_tier, trial\_start, trial\_end)</td><td>MC #100326</td><td>Adds subscription trial mechanic to organizations table</td></tr><tr><td>**V16**</td><td>RLS column widening (country constraint)</td><td>MC #100406</td><td>Expands country VARCHAR to support TaxJurisdiction enum values</td></tr><tr><td>**V17**</td><td>RLS PERMISSIVE policies</td><td>MC #100387</td><td>**REQUIRES:** bilko\_app role exists. V17 includes idempotent `CREATE ROLE IF NOT EXISTS bilko_app` block (Round 25 fix)</td></tr><tr><td>**V18**</td><td>InvoiceStatus enum</td><td>MC #100406 (drugi agent)</td><td>Creates PostgreSQL ENUM for invoice states (DRAFT, SENT, PAID, etc.)</td></tr><tr><td>**V19**</td><td>ExpenseStatus enum</td><td>MC #100406 (drugi agent)</td><td>Creates PostgreSQL ENUM for expense states (DRAFT, APPROVED, PAID, etc.)</td></tr><tr><td>**V20**</td><td>Demo org name fix</td><td>Round 25</td><td>Updates demo organization name to "Bilko Demo Firma" (Croatian/Serbian localization)</td></tr></tbody></table>

### Migration Execution

Migrations are applied by Flyway 11 in the pipeline against `bilko-demo-pg.postgres.database.azure.com:5432/bilko` as `bilko_admin`. The pipeline fails closed if the database contains an applied migration newer than the checked-out source tree. *Source: `azure-pipelines.yml` at azdo/main `3db716d7`, stage `Flyway_Migrate`.*

**Manual Verification After Deployment:**

**Production warning:** `bilko-demo-pg` serves customer-facing production. Use a read-only transaction for verification and do not run ad-hoc DDL/DML.

```bash
# Connect to Azure PostgreSQL Flexible Server
psql "host=bilko-demo-pg.postgres.database.azure.com port=5432 dbname=bilko user=bilko_admin sslmode=require"

# In psql, enter a read-only transaction before checks
BEGIN READ ONLY;

SELECT version, description, success, installed_on
FROM flyway_schema_history
ORDER BY installed_rank DESC
LIMIT 10;

SELECT id, name FROM organizations WHERE name = 'Bilko Demo Firma';
ROLLBACK;

```

*Source: BookStack page 3305, §5b and §7a; database identity re-verified by read-only Azure query on 2026-08-15.*

## 3. Azure Container Apps Image Promotion Sequence

### Stage → Demo (Production) Promotion

**Environment classification:** the resources with `-demo` in their names are production. The canonical pipeline promotes API and Web together from one commit; do not copy a stage tag into production or call `az containerapp update` manually from this runbook.

- **Stage API:** `bilko-api-stage` — `bilkodemo.azurecr.io/bilko-api:stage-{SHA}@sha256:{digest}`
- **Production API:** `bilko-api-demo` — `bilkodemo.azurecr.io/bilko-api:demo-{SHA}@sha256:{digest}`
- **Stage Web:** `bilko-web-stage` — `bilkodemo.azurecr.io/bilko-web:stage-{SHA}@sha256:{digest}`
- **Production Web:** `bilko-web-demo` — `bilkodemo.azurecr.io/bilko-web:demo-{SHA}@sha256:{digest}`

*Sources: live read-only ACA/ACR query 2026-08-15; `DEPLOY-MAP.md` and `azure-pipelines.yml` at azdo/main `3db716d7`.*

### Promotion Commands

#### API Service

```bash
# Queue the canonical pipeline from main. It builds, scans, migrates, deploys stage,
# runs E2E UAT, then waits for the bilko-demo environment approval.
az pipelines run --org https://dev.azure.com/alai-holding --project Bilko --branch main --id 1

# Monitor recent main runs; record the run ID and sourceVersion in cutover evidence.
az pipelines runs list --org https://dev.azure.com/alai-holding --project Bilko   --pipeline-ids 1 --branch main --top 5 -o table

```

Do not approve `Promote_Demo` until `CI_Gates → Build → Flyway_Migrate → Deploy_Stage → E2E_UAT` are green and the pre-cutover checklist is complete. Approval is performed in Azure DevOps under `Pipelines → Environments → bilko-demo`. The approved stage rebuilds `demo-{SHA}` images from the same commit, scans them, deploys digest-pinned images, and shifts `bilko-api-demo` to the new revision.

*Source: BookStack page 3305 §3b and live `azure-pipelines.yml` `Promote_Demo` at azdo/main `3db716d7`.*

#### Web Service

There is no separate Web promotion command. The same approved `Bilko-CI-CD` run builds and deploys `bilko-api-demo` and `bilko-web-demo` from the same source commit. A split API/Web manual promotion would bypass the canonical release contract.

*Source: live `azure-pipelines.yml` `Promote_Demo` deployment job at azdo/main `3db716d7`.*

### Traffic Shift (Gradual Rollout)

**TODO/UNKNOWN — intentional safety stop:** no reviewed gradual 10/90 production rollout command exists in the allowed current sources. The canonical pipeline shifts `bilko-api-demo` 100% to its latest revision; live state shows API in `Multiple` revision mode and Web in `Single` mode. If gradual rollout is required, stop here and obtain a FlowForge-authored, revision-specific procedure. Do not translate the retired traffic-weight commands by analogy.

*Source for current behavior: live `azure-pipelines.yml` `Promote_Demo` and read-only ACA query 2026-08-15.*

## 4. Cloudflare Routing + CORS Verification

### DNS / Routing Configuration

**Customer-facing production URLs:**

- **API:** `https://api.bilko.cloud` → Cloudflare Worker `bilko-edge-proxy` → ACA `bilko-api-demo`
- **Web:** `https://app.bilko.cloud` → Cloudflare Worker `bilko-edge-proxy` → ACA `bilko-web-demo`
- **Stage API:** `https://api-stage.bilko.cloud` → direct ACA custom-domain binding on `bilko-api-stage`
- **Stage Web:** `https://web-stage.bilko.cloud` → direct ACA custom-domain binding on `bilko-web-stage`

The retired `*.alai.no` demo hosts are not release targets and must not be used in health checks or browser tests.

*Sources: BookStack page 3121; `DEPLOY-MAP.md` at azdo/main `3db716d7`; live read-only ACA query 2026-08-15.*

### CORS Configuration

CORS is set by the canonical pipeline, not by a standalone operator update:

- **Stage API:** `https://bilko-web-stage.purplebeach-f004d490.swedencentral.azurecontainerapps.io`, `https://web-stage.bilko.cloud`, and localhost for development.
- **Production API:** `https://app.bilko.cloud`, `https://app.bilko.io`, `https://app.bilko.company`, and localhost for development.

*Source: live `azure-pipelines.yml` `Deploy_Stage` and `Promote_Demo` environment updates at azdo/main `3db716d7`.*

### CORS / Route Verification

```bash
# Public production health (current brand domains)
curl -s https://api.bilko.cloud/api/v1/health
curl -sI https://app.bilko.cloud/ | head -1

# Pipeline evidence: E2E_UAT performs browser-level CSP/CORS-sensitive flows on stage.
az pipelines runs list --org https://dev.azure.com/alai-holding --project Bilko   --pipeline-ids 1 --branch main --top 5 -o table

```

Success requires HTTP 200 on both public routes and a green `E2E_UAT` stage for the exact commit being promoted. The retired password-login preflight is invalid because Bilko is Entra/demo-session based; do not recreate it.

*Sources: BookStack page 3305 §3c/§4d and live `azure-pipelines.yml` E2E/PI2 checks at azdo/main `3db716d7`.*

## 5. Rollback Procedure

### Azure Container Apps Rollback

Live state verified 2026-08-15: `bilko-api-demo` uses `Multiple` revision mode; `bilko-web-demo` uses `Single`. Record the current revision and image before any rollback.

```bash
# API: list revisions and their state
az containerapp revision list --name bilko-api-demo --resource-group rg-bilko-demo   --query "[].{name:name,created:properties.createdTime,active:properties.active}" -o table

# API: after explicit rollback authorization, shift 100% to the reviewed known-good revision
az containerapp ingress traffic set --name bilko-api-demo --resource-group rg-bilko-demo   --revision-weight <previous-revision-name>=100

```

*Source: BookStack page 3305 §6a and `DEPLOY-MAP.md` “Rollback (Azure)”; revision mode re-verified by read-only ACA query 2026-08-15.*

**TODO/UNKNOWN — production Web rollback:** the live Web app is in `Single` revision mode, while one related wiki page still shows a traffic-weight command that is only valid for multiple-revision handling. No approved production Web rollback command consistent with live state was found in the allowed sources. Stop and escalate to FlowForge; do not reuse the API command or the stage-only rollback pipeline.

### Database Rollback

**CRITICAL:** Flyway migrations are forward-only. Prefer a new compensating migration. Do not run ad-hoc policy drops or destructive SQL on `bilko-demo-pg`; it is production and shared with stage.

**Point-in-time restore is a last resort:** Azure PostgreSQL restore creates a new server; it does not overwrite the source. A separate, explicitly authorized application/edge cutover is required afterward.

```bash
az postgres flexible-server restore   --resource-group rg-bilko-demo   --name bilko-demo-pg-restored   --source-server bilko-demo-pg   --restore-time "<verified-UTC-timestamp>"

```

*Source: BookStack page 3305 §5c and §7e. Live server `bilko-demo-pg`, PostgreSQL 16, state `Ready`, backup retention 7 days verified read-only on 2026-08-15.*

## 6. Post-Deploy Smoke Tests

Run immediately after the approved production promotion. Record the pipeline run ID, commit SHA, deployed image digests, and every result.

### Manual Smoke Tests

<table id="bkmrk-test-command%2Faction-"><thead><tr><th>Test</th><th>Command/Action</th><th>Expected Result</th><th>Status</th></tr></thead><tbody><tr><td>**1. Demo Auth Bootstrap**</td><td>`curl -s -o /dev/null -w "%{http_code}\n" "https://bilko-api-demo.purplebeach-f004d490.swedencentral.azurecontainerapps.io/api/v1/auth/demo?country=HR"`</td><td>HTTP 200</td><td>☐</td></tr><tr><td>**2. Real-Browser Login**</td><td>**TODO/UNKNOWN:** no approved standalone authenticated user-info command was found without reintroducing retired password/JWT handling. Confirm the `Real-browser MSAL login smoke (DEMO artifact)` step in the exact `Promote_Demo` run instead.</td><td>Pipeline login smoke succeeds for `app.bilko.cloud`</td><td>☐</td></tr><tr><td>**3. Invoice PDF**</td><td>**TODO/UNKNOWN:** the current allowed sources do not identify a safe production fixture ID and authenticated PDF probe. Do not invent a customer ID or token. Add a sourced probe before treating this row as automated.</td><td>Explicitly waived by CEO or a sourced, tenant-safe probe passes</td><td>☐</td></tr><tr><td>**4. Health Check**</td><td>`curl -s https://api.bilko.cloud/api/v1/health`</td><td>HTTP 200 with the healthy Bilko API response</td><td>☐</td></tr><tr><td>**5. RBAC Enforcement**</td><td>Open the exact Azure DevOps run and confirm `E2E_UAT` plus its published Playwright results succeeded before `Promote_Demo`.</td><td>No failed E2E tests; approval evidence ties to the same source commit</td><td>☐</td></tr></tbody></table>

*Sources: live `azure-pipelines.yml` PI2 and real-browser login gates at azdo/main `3db716d7`; BookStack page 3305 §3c/§4d.*

### Automated E2E Tests (Playwright)

The canonical suite runs in `E2E_UAT` against `api-stage.bilko.cloud` and `web-stage.bilko.cloud`. After promotion, the pipeline reruns `tests/login-dashboard.spec.ts` against `https://app.bilko.cloud`. Do not run the retired `apps/web` command against a dead demo hostname.

```bash
az pipelines runs list --org https://dev.azure.com/alai-holding --project Bilko   --pipeline-ids 1 --branch main --top 5 -o table

```

*Source: live `azure-pipelines.yml` stages `E2E_UAT` and `Promote_Demo` at azdo/main `3db716d7`.*

### Coverage Verification

```bash
# Verify backend coverage threshold is maintained
cd apps/api
./gradlew koverVerify

```

Expected: exit 0. Keep this local quality gate separate from infrastructure promotion.

## 7. Rounds 11-27 Deliverables Manifest

This section provides traceability for all changes delivered in Rounds 11-27.

### Coverage Progression

<table id="bkmrk-round-mc-id-coverage"><thead><tr><th>Round</th><th>MC ID</th><th>Coverage Delta</th><th>Final Threshold</th><th>Test Count</th></tr></thead><tbody><tr><td>Round 16</td><td>\#100336</td><td>Baseline established</td><td>7%</td><td>~50 HTTP integration tests</td></tr><tr><td>Round 17</td><td>\#100336</td><td>+4%</td><td>11%</td><td>+110 service tests (Invoice, Expense, Contact, Report)</td></tr><tr><td>Round 18</td><td>\#100336</td><td>+39%</td><td>50%</td><td>+90 tests (4 streams: Banking, Auth, Stripe, Routes)</td></tr><tr><td>Round 19</td><td>\#100338</td><td>+7%</td><td>57%</td><td>+20 RBAC edge case tests (403 assertions)</td></tr><tr><td>Round 20</td><td>\#100353</td><td>+3%</td><td>60%</td><td>+81 tests (3 streams: RecurringInvoice, Compliance, Archive/Settings/Account)</td></tr><tr><td>Round 21</td><td>\#100359</td><td>Threshold held</td><td>60%</td><td>+75 edge case tests across 17 HttpIntegrationTest files</td></tr><tr><td>Round 22</td><td>\#100382</td><td>Threshold held</td><td>60%</td><td>+33 billing/webhook integration tests</td></tr><tr><td>Round 23</td><td>—</td><td>Threshold held</td><td>60%</td><td>Auth package coverage push (~30 tests, 4 streams)</td></tr><tr><td>Round 24</td><td>\#100396</td><td>Regression suite</td><td>60%</td><td>Failing-by-design specs for CEO Round 12 bugs</td></tr><tr><td>Round 27</td><td>\#100420</td><td>Deep edge coverage</td><td>60%</td><td>+113 deep edge tests across 8 HttpIntegrationTest files</td></tr></tbody></table>

### Production Bug Fixes (Rounds 12-26)

<table id="bkmrk-fix-mc-id-commit-sha"><thead><tr><th>Fix</th><th>MC ID</th><th>Commit SHA</th><th>Description</th></tr></thead><tbody><tr><td>**CORS Permanent**</td><td>\#100297</td><td>3943837</td><td>Historical pre-Azure CORS fix (PR #110); current release configuration is in `azure-pipelines.yml` (see §4)</td></tr><tr><td>**Double Login**</td><td>\#100367</td><td>ef1ed07</td><td>Eliminate double /auth/login call on registration (PR #111)</td></tr><tr><td>**RLS Column Widening**</td><td>\#100406</td><td>660795d</td><td>V16 column widening + V17 line 50 ALTER DATABASE removal</td></tr><tr><td>**bilko\_app Role**</td><td>Round 25</td><td>30d2de4</td><td>V17 idempotent CREATE ROLE IF NOT EXISTS bilko\_app before RLS policies (PR #113)</td></tr><tr><td>**Demo Org Name**</td><td>Round 25</td><td>dbbd80d</td><td>V20 migration ensures demo org name is "Bilko Demo Firma"</td></tr><tr><td>**Wizard Save Draft**</td><td>Round 25</td><td>9319d02</td><td>Step 5 "Save kao nacrt" shows localized toast + redirect</td></tr><tr><td>**Refresh Token**</td><td>Round 26</td><td>b55c8dd</td><td>Eliminate refresh token consumption on login</td></tr><tr><td>**HR L10n Critical**</td><td>\#100355</td><td>e3ede8e</td><td>OIB 11 digits, VAT 25/13/5%, locale routing, register language dynamic (Lexicon audit)</td></tr><tr><td>**Demo API Rebuild**</td><td>\#100400</td><td>9ffe3c4</td><td>Historical pre-Azure demo build path (PR #116); superseded by `Promote_Demo` in `azure-pipelines.yml`</td></tr></tbody></table>

### Feature Deliveries

<table id="bkmrk-feature-mc-id-commit"><thead><tr><th>Feature</th><th>MC ID</th><th>Commit SHA</th><th>Description</th></tr></thead><tbody><tr><td>**RBAC HTTP Layer**</td><td>\#100338</td><td>2a933d8</td><td>Enforce role-check at HTTP layer on all CRUD route handlers (Round 19)</td></tr><tr><td>**RLS Permissive**</td><td>\#100387</td><td>6712fcd</td><td>Phase 2A PERMISSIVE RLS migrations + OrgScope middleware</td></tr><tr><td>**Trial Mechanic**</td><td>\#100326</td><td>d20cb13, e0d200b</td><td>D1+D2+D4 backend + D3 frontend trial countdown banner</td></tr><tr><td>**TaxJurisdiction Expansion**</td><td>\#100386</td><td>e346e9b</td><td>Expand TaxJurisdiction {BA\_FED, BA\_RS} + CountryPlugin interface (ADR-015)</td></tr><tr><td>**ADRs 015-019**</td><td>\#100362</td><td>d17f0ce</td><td>Write ADR-015/016/017/019 per Plan v3 Phase 0</td></tr></tbody></table>

### Infrastructure Changes

<table id="bkmrk-change-mc-id-commit-"><thead><tr><th>Change</th><th>MC ID</th><th>Commit SHA</th><th>Description</th></tr></thead><tbody><tr><td>**GCP Deploy Dormant Fix**</td><td>\#100110</td><td>b1ddecf</td><td>gcp-deploy.yml service name targets bilko-{web,api}-demo (ADR-023 gap closure)</td></tr><tr><td>**V14-V20 Migrations**</td><td>Various</td><td>—</td><td>7 Flyway migrations (demo seed, trial, RLS, enums, org name fix)</td></tr></tbody></table>

## 8. Escalation Contacts

<table id="bkmrk-issue-type-contact-c"><thead><tr><th>Issue Type</th><th>Contact</th><th>Channel</th></tr></thead><tbody><tr><td>**Azure Container Apps Outage**</td><td>Kelsey Hightower (FlowForge)</td><td>MC task assignment via John</td></tr><tr><td>**Database Corruption**</td><td>Bruce Momjian (CodeCraft)</td><td>MC task assignment via John</td></tr><tr><td>**RBAC/Auth Issues**</td><td>Parisa Tabriz (Securion)</td><td>MC task assignment via John</td></tr><tr><td>**Frontend Errors**</td><td>Lee Robinson (CodeCraft)</td><td>MC task assignment via John</td></tr><tr><td>**CEO Approval**</td><td>Alem Basic</td><td>Email: alem@alai.no, Slack: @alem</td></tr></tbody></table>

## 9. Appendix: Key File Locations

<table id="bkmrk-file-path-stage-clou"><thead><tr><th>File</th><th>Path</th></tr></thead><tbody><tr><td>**Canonical CI/CD + promotion**</td><td>`azure-pipelines.yml`</td></tr><tr><td>**Live topology and operator commands**</td><td>`DEPLOY-MAP.md`</td></tr><tr><td>**Stage rollback rehearsal only**</td><td>`azure-pipelines-stage-rollback.yml`</td></tr><tr><td>**Flyway Migrations**</td><td>`apps/api/src/main/resources/db/migration/`</td></tr><tr><td>**Application Config**</td><td>`apps/api/src/main/kotlin/no/alai/bilko/Application.kt`</td></tr><tr><td>**CORS Config**</td><td>`azure-pipelines.yml` (`Deploy_Stage` and `Promote_Demo`)</td></tr><tr><td>**Playwright Tests**</td><td>`apps/e2e/`</td></tr><tr><td>**Kover Config**</td><td>`apps/api/build.gradle.kts`</td></tr></tbody></table>

*Source: `BUILD-BLUEPRINT.md`, `DEPLOY-MAP.md`, and `azure-pipelines.yml` at azdo/main `3db716d7`.*

---

**Document Revision History:**

- **v1.1 (2026-08-15):** MC #107170 — replaced retired deployment, database, registry, hostname, smoke-test, and rollback commands with sourced Azure/ADO procedures; retained explicit safety stops where no current command could be established.
- **v1.0 (2026-05-10):** Initial runbook creation covering Rounds 11-27 deliverables (MC #100424)

# Promote_Demo kapija — zamjena Write_UAT uslova (MC #900135, 2026-08-22)

<!-- ALAI-MC:900135:BEFORE -->

# Promote_Demo kapija — zamjena Write_UAT uslova (MC #900135, 2026-08-22)

**Odluka CEO-a: DA (2026-08-22).** Ovo je zapis odluke, ne prijedlog.

## Problem

`azure-pipelines.yml:2090-2101`, stage `Promote_Demo`:

```
condition: and( succeeded('E2E_UAT'), succeeded('Write_UAT'), or(Manual, refs/tags/v) )
dependsOn: [ Build, Write_UAT ]
```

`Write_UAT` je **ljuska** (MC #900104, mjereno 2026-08-21):

- svih 5 `apps/e2e/tests/write-uat-*.spec.ts` bezuslovno prosljeđuju `createUnavailableWriteUatBootstrapAuthVerifier()` i `createUnavailableWriteUatAllocator()` → `runtimeAvailable:false` → uvijek `fail(...)`;
- stvarni transport postoji u `apps/e2e/tests/helpers/write-uat-stage-runtime.ts`, ali ga **nijedan spec ne koristi** — mrtav kod;
- `/internal/write-uat/v1/*` **ne postoji** u `apps/api` (`git grep` van `apps/e2e` daje samo yml i ovaj ugovor);
- kroz zadnjih 29 buildova koji imaju taj stage: `failed` 3× (1208, 1217, 1234), `skipped` inače, **`succeeded` nijednom**.

Dakle kapija ne štiti ništa — a blokira sve.

## Šta je konkretno blokirala

Ispravan pravni subjekt (SMART FORGE d.o.o.) je na mainu u tri fajla `apps/web`, ali **nije živ** na `app.bilko.cloud` — tamo i dalje piše ALAI Holding AS. To je `business_profile.url` uslov za Stripe prijavu (MC #900044/#900048), dakle kapija je stajala na putu prihodu.

Ručno guranje na demo **nije opcija**: [Bilko Stage→Demo→Prod Cutover Runbook](https://docs.alai.no/books/deployment-runbooks/page/bilko-stagedemoprod-cutover-runbook) izričito zabranjuje kopiranje stage oznake u produkciju i ručnu izmjenu container appa iz runbooka. Jedini put je kroz liniju.

## Izmjena

```
condition: and( succeeded('E2E_UAT'), succeeded('Build'), or(eq(Build.Reason,'Manual'), startsWith(Build.SourceBranch,'refs/tags/v')) )
dependsOn: [ Build, E2E_UAT ]
```

`Write_UAT` **ostaje u liniji i dalje se izvršava** — samo prestaje biti uslov za promote.

## Šta ostaje zaštićeno (namjerno se NE dira)

1. **Ljudsko odobrenje** — `deployment: approve_and_deploy_demo`, `environment: 'bilko-demo'`. Nijedan promote ne ide bez klika.
2. **Digest-pinned deploy** iz `Build` outputa (`API_DIGEST` / `WEB_DIGEST`).
3. **`succeeded('E2E_UAT')`, ne `not(failed(...))`** — fail-open klasa iz MC #107171/#107242 se NE uvodi nazad.
4. Samo `Manual` ili `v*` tag.

## Dokazni minimum prije klika na odobrenje

Nije u liniji nego je Johnov korak, po roadmap pravilu "promote samo s dokazom":

- svjež digest **obje** slike (api + web) i poklapanje s `Build` outputom,
- `E2E_UAT` zelen na tom istom sha,
- **login smoke kroz pregledač** na demo hostu poslije promotea.

## Šta ovo NE rješava

Gradnja prave write-UAT kontrolne ravni ostaje **MC #900104** — po ugovoru `docs/testing/WRITE-UAT-CONTRACT.md` to je 10 blokirajućih preflight kapija, dokazi izolacije i uloga, paket dokaza, quiesce/cleanup/rotacija. To je pojačanje, ne blokator prihoda, i radi se odvojeno.