Bilko Stage→Demo→Prod Cutover Runbook
Bilko Stage→Demo→Prod Cutover Runbook
Document ID: BILKO-RUNBOOK-001
Last Updated: 2026-05-10
Owner: Skillforge (ALAI Holding AS)
Status: Active
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: As of 2026-05-10, Bilko stage is on revision 00185-fay (image stage-3a4a9e9). Demo environment has only a subset of these changes. Prod does not exist as a separate environment yet and currently uses demo as customer-facing.
1. Pre-Cutover Checklist
Complete ALL items before proceeding with cutover:
| Item | Verification Command | Success Criteria | Status |
|---|---|---|---|
| High/Blocker MCs Resolved | node ~/system/tools/mc.js list --project bilko --priority H,BLOCKER --status open |
0 open tasks, or all explicitly waived by CEO | ☐ |
| Cloud Build Health | gcloud builds list --project=tribal-sign-487920-k0 --limit=5 --filter="source.repoSource.branchName=main" |
Last 5 builds on main = SUCCESS | ☐ |
| Coverage Gate | cd apps/api && ./gradlew koverVerify |
Exit code 0 (current threshold ≥60%) | ☐ |
| E2E Regression Tests | cd apps/web && npm run test:e2e |
Phase A pass rate ≥50% (Round 24 regression specs) | ☐ |
| Database Backup | gcloud sql backups create --instance=bilko-demo-db --project=tribal-sign-487920-k0 |
Backup ID returned + status=SUCCESSFUL | ☐ |
| CEO Sign-Off | MC task comments or Slack approval | Explicit approval captured with timestamp | ☐ |
2. Database Migration Plan (V14-V20)
Flyway migrations are applied automatically when Cloud Run starts. This section documents migration order and critical dependencies.
Migration Sequence
| Version | Description | MC Reference | Critical Notes |
|---|---|---|---|
| V14 | Demo + CI test seed | Round 12.7 | Creates [email protected] user + test tenant for CI |
| V15 | Trial fields (plan_tier, trial_start, trial_end) | MC #100326 | Adds subscription trial mechanic to organizations table |
| V16 | RLS column widening (country constraint) | MC #100406 | Expands country VARCHAR to support TaxJurisdiction enum values |
| V17 | RLS PERMISSIVE policies | MC #100387 | REQUIRES: bilko_app role exists. V17 includes idempotent CREATE ROLE IF NOT EXISTS bilko_app block (Round 25 fix) |
| V18 | InvoiceStatus enum | MC #100406 (drugi agent) | Creates PostgreSQL ENUM for invoice states (DRAFT, SENT, PAID, etc.) |
| V19 | ExpenseStatus enum | MC #100406 (drugi agent) | Creates PostgreSQL ENUM for expense states (DRAFT, APPROVED, PAID, etc.) |
| V20 | Demo org name fix | Round 25 | Updates demo organization name to "Bilko Demo Firma" (Croatian/Serbian localization) |
Migration Execution
Migrations are applied automatically by Flyway during Cloud Run startup via FlywayPlugin in apps/api/src/main/kotlin/no/alai/bilko/Application.kt.
Manual Verification After Deployment:
# Connect to bilko-demo-db
gcloud sql connect bilko-demo-db --user=bilko_admin --project=tribal-sign-487920-k0
# Check Flyway schema history
SELECT version, description, success, installed_on
FROM flyway_schema_history
ORDER BY installed_rank DESC
LIMIT 10;
# Verify V20 demo org name
SELECT id, name FROM organizations WHERE name = 'Bilko Demo Firma';
3. Cloud Run Image Promotion Sequence
Stage → Demo Promotion
Current State (2026-05-10):
- Stage API:
bilko-api-stage— imageeurope-north1-docker.pkg.dev/tribal-sign-487920-k0/bilko/api:stage-3a4a9e9 - Demo API:
bilko-api-demo— image TBD (promote from stage) - Stage Web:
bilko-web-stage— imageeurope-north1-docker.pkg.dev/tribal-sign-487920-k0/bilko/web:stage-<SHA> - Demo Web:
bilko-web-demo— image TBD (promote from stage)
Promotion Commands
API Service
# Get current stage image SHA
STAGE_SHA=$(gcloud run services describe bilko-api-stage \
--region=europe-north1 \
--project=tribal-sign-487920-k0 \
--format='value(spec.template.spec.containers[0].image)' | \
awk -F':' '{print $2}')
echo "Promoting API image: stage-${STAGE_SHA}"
# Promote to demo (use gcloud-write.sh for safety)
bash ~/system/tools/gcloud-write.sh --mc 100424 run services update bilko-api-demo \
--region=europe-north1 \
--project=tribal-sign-487920-k0 \
--image=europe-north1-docker.pkg.dev/tribal-sign-487920-k0/bilko/api:stage-${STAGE_SHA}
Web Service
# Get current stage web image SHA
WEB_SHA=$(gcloud run services describe bilko-web-stage \
--region=europe-north1 \
--project=tribal-sign-487920-k0 \
--format='value(spec.template.spec.containers[0].image)' | \
awk -F':' '{print $2}')
echo "Promoting Web image: stage-${WEB_SHA}"
# Promote to demo
bash ~/system/tools/gcloud-write.sh --mc 100424 run services update bilko-web-demo \
--region=europe-north1 \
--project=tribal-sign-487920-k0 \
--image=europe-north1-docker.pkg.dev/tribal-sign-487920-k0/bilko/web:stage-${WEB_SHA}
Traffic Shift (Gradual Rollout)
If gradual rollout is needed:
# Split traffic 10% new revision, 90% old
bash ~/system/tools/gcloud-write.sh --mc 100424 run services update-traffic bilko-api-demo \
--region=europe-north1 \
--project=tribal-sign-487920-k0 \
--to-revisions=bilko-api-demo-00186-xyz=10,bilko-api-demo-00185-abc=90
# After soak period (30 min), shift to 100%
bash ~/system/tools/gcloud-write.sh --mc 100424 run services update-traffic bilko-api-demo \
--region=europe-north1 \
--project=tribal-sign-487920-k0 \
--to-latest
4. Cloudflare DNS + CORS Verification
DNS Configuration
Demo Environment URLs:
- API:
https://bilko-demo-api.alai.no→ Cloud Runbilko-api-demo - Web:
https://bilko-demo.alai.no→ Cloud Runbilko-web-demo
DNS records managed in Cloudflare dashboard under alai.no zone.
CORS Configuration
Permanent CORS fix delivered in Round 12.8 (PR #110, MC #100297):
CORS_ORIGINS env var is permanently set in cloudbuild-stage.yaml and cloudbuild-demo-api.yaml:
- name: 'gcr.io/cloud-builders/gcloud'
args:
- 'run'
- 'services'
- 'update'
- 'bilko-api-demo'
- '--region=europe-north1'
- '--update-env-vars=CORS_ORIGINS=https://bilko-demo.alai.no,http://localhost:3000'
CORS Preflight Verification
# Test CORS preflight from demo domain
curl -X OPTIONS https://bilko-demo-api.alai.no/auth/login \
-H "Origin: https://bilko-demo.alai.no" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: Content-Type" \
-v
# Expected headers in response:
# Access-Control-Allow-Origin: https://bilko-demo.alai.no
# Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
# Access-Control-Allow-Headers: Content-Type, Authorization
# Access-Control-Allow-Credentials: true
5. Rollback Procedure
Cloud Run Rollback
Cloud Run keeps all previous revisions for 60 days. Rollback shifts traffic to a previous stable revision.
# List recent revisions
gcloud run revisions list \
--service=bilko-api-demo \
--region=europe-north1 \
--project=tribal-sign-487920-k0 \
--limit=10
# Identify previous stable revision (e.g., bilko-api-demo-00185-fay)
PREV_REV="bilko-api-demo-00185-fay"
# Rollback to previous revision
bash ~/system/tools/gcloud-write.sh --mc 100424 run services update-traffic bilko-api-demo \
--region=europe-north1 \
--project=tribal-sign-487920-k0 \
--to-revisions=${PREV_REV}=100
Database Rollback
CRITICAL: Flyway migrations are forward-only. There is NO automatic rollback for schema changes.
If V17+ RLS Policies Cause Issues:
-- Disable RLS policies manually (Cloud SQL console or psql)
DROP POLICY IF EXISTS org_isolation ON invoices;
DROP POLICY IF EXISTS org_isolation ON expenses;
DROP POLICY IF EXISTS org_isolation ON contacts;
DROP POLICY IF EXISTS org_isolation ON transactions;
DROP POLICY IF EXISTS org_isolation ON bank_accounts;
DROP POLICY IF EXISTS org_isolation ON recurring_invoices;
-- Or disable RLS entirely (NOT recommended for prod)
ALTER TABLE invoices DISABLE ROW LEVEL SECURITY;
Database Point-in-Time Recovery (PITR):
# Clone database to a specific point in time (last known good state)
gcloud sql instances clone bilko-demo-db bilko-demo-db-rollback \
--point-in-time="2026-05-10T10:00:00.000Z" \
--project=tribal-sign-487920-k0
# Swap Cloud Run to rollback instance (update DATABASE_URL secret)
# This is a LAST RESORT — requires DNS cutover + application restart
6. Post-Deploy Smoke Tests
Run immediately after cutover to verify core functionality.
Manual Smoke Tests
| Test | Command/Action | Expected Result | Status |
|---|---|---|---|
| 1. Login | curl -X POST https://bilko-demo-api.alai.no/auth/login -H "Content-Type: application/json" -d '{"email":"[email protected]","password":"Demo2026!"}' |
HTTP 200 + JWT token in accessToken field |
☐ |
| 2. User Info | curl https://bilko-demo-api.alai.no/auth/me -H "Authorization: Bearer <JWT>" |
HTTP 200 + user object with org name "Bilko Demo Firma" | ☐ |
| 3. Invoice PDF | curl https://bilko-demo-api.alai.no/invoices/<demo-invoice-id>/pdf -H "Authorization: Bearer <JWT>" > /tmp/invoice.pdf |
HTTP 200 + PDF bytes + PDF contains "Bilko Demo Firma" (V20 regression gate) | ☐ |
| 4. Health Check | curl https://bilko-demo-api.alai.no/api/v1/health |
HTTP 200 + {"status":"healthy"} |
☐ |
| 5. RBAC Enforcement | Login as viewer role, attempt DELETE /invoices/:id | HTTP 403 Forbidden (Round 19 RBAC enforcement) | ☐ |
Automated E2E Tests (Playwright)
# Run Playwright tests against demo environment
cd apps/web
PLAYWRIGHT_BASE_URL=https://bilko-demo.alai.no npm run test:e2e
# Expected: 13/15 PASS
# Known failures (Round 12):
# - session persistence (3 specs)
# - navigation edge cases
# - logout redirect
Coverage Verification
# Verify backend coverage threshold is maintained
cd apps/api
./gradlew koverVerify
# Expected: EXIT 0 (threshold ≥60% per Round 20)
7. Rounds 11-27 Deliverables Manifest
This section provides traceability for all changes delivered in Rounds 11-27.
Coverage Progression
| Round | MC ID | Coverage Delta | Final Threshold | Test Count |
|---|---|---|---|---|
| Round 16 | #100336 | Baseline established | 7% | ~50 HTTP integration tests |
| Round 17 | #100336 | +4% | 11% | +110 service tests (Invoice, Expense, Contact, Report) |
| Round 18 | #100336 | +39% | 50% | +90 tests (4 streams: Banking, Auth, Stripe, Routes) |
| Round 19 | #100338 | +7% | 57% | +20 RBAC edge case tests (403 assertions) |
| Round 20 | #100353 | +3% | 60% | +81 tests (3 streams: RecurringInvoice, Compliance, Archive/Settings/Account) |
| Round 21 | #100359 | Threshold held | 60% | +75 edge case tests across 17 HttpIntegrationTest files |
| Round 22 | #100382 | Threshold held | 60% | +33 billing/webhook integration tests |
| Round 23 | — | Threshold held | 60% | Auth package coverage push (~30 tests, 4 streams) |
| Round 24 | #100396 | Regression suite | 60% | Failing-by-design specs for CEO Round 12 bugs |
| Round 27 | #100420 | Deep edge coverage | 60% | +113 deep edge tests across 8 HttpIntegrationTest files |
Production Bug Fixes (Rounds 12-26)
| Fix | MC ID | Commit SHA | Description |
|---|---|---|---|
| CORS Permanent | #100297 | 3943837 | Permanent CORS_ORIGINS + EMAIL_FROM in cloudbuild-stage.yaml (PR #110) |
| Double Login | #100367 | ef1ed07 | Eliminate double /auth/login call on registration (PR #111) |
| RLS Column Widening | #100406 | 660795d | V16 column widening + V17 line 50 ALTER DATABASE removal |
| bilko_app Role | Round 25 | 30d2de4 | V17 idempotent CREATE ROLE IF NOT EXISTS bilko_app before RLS policies (PR #113) |
| Demo Org Name | Round 25 | dbbd80d | V20 migration ensures demo org name is "Bilko Demo Firma" |
| Wizard Save Draft | Round 25 | 9319d02 | Step 5 "Save kao nacrt" shows localized toast + redirect |
| Refresh Token | Round 26 | b55c8dd | Eliminate refresh token consumption on login |
| HR L10n Critical | #100355 | e3ede8e | OIB 11 digits, VAT 25/13/5%, locale routing, register language dynamic (Lexicon audit) |
| Demo API Rebuild | #100400 | 9ffe3c4 | Add cloudbuild-demo-api.yaml to rebuild bilko-api-demo (PR #116) |
Feature Deliveries
| Feature | MC ID | Commit SHA | Description |
|---|---|---|---|
| RBAC HTTP Layer | #100338 | 2a933d8 | Enforce role-check at HTTP layer on all CRUD route handlers (Round 19) |
| RLS Permissive | #100387 | 6712fcd | Phase 2A PERMISSIVE RLS migrations + OrgScope middleware |
| Trial Mechanic | #100326 | d20cb13, e0d200b | D1+D2+D4 backend + D3 frontend trial countdown banner |
| TaxJurisdiction Expansion | #100386 | e346e9b | Expand TaxJurisdiction {BA_FED, BA_RS} + CountryPlugin interface (ADR-015) |
| ADRs 015-019 | #100362 | d17f0ce | Write ADR-015/016/017/019 per Plan v3 Phase 0 |
Infrastructure Changes
| Change | MC ID | Commit SHA | Description |
|---|---|---|---|
| GCP Deploy Dormant Fix | #100110 | b1ddecf | gcp-deploy.yml service name targets bilko-{web,api}-demo (ADR-023 gap closure) |
| V14-V20 Migrations | Various | — | 7 Flyway migrations (demo seed, trial, RLS, enums, org name fix) |
8. Escalation Contacts
| Issue Type | Contact | Channel |
|---|---|---|
| Cloud Run Outage | Kelsey Hightower (FlowForge) | MC task assignment via John |
| Database Corruption | Bruce Momjian (CodeCraft) | MC task assignment via John |
| RBAC/Auth Issues | Parisa Tabriz (Securion) | MC task assignment via John |
| Frontend Errors | Lee Robinson (CodeCraft) | MC task assignment via John |
| CEO Approval | Alem Basic | Email: [email protected], Slack: @alem |
9. Appendix: Key File Locations
| File | Path |
|---|---|
| Stage Cloud Build | cloudbuild-stage.yaml |
| Demo API Cloud Build | cloudbuild-demo-api.yaml |
| Flyway Migrations | apps/api/src/main/resources/db/migration/ |
| Application Config | apps/api/src/main/kotlin/no/alai/bilko/Application.kt |
| CORS Config | apps/api/src/main/kotlin/no/alai/bilko/plugins/CorsPlugin.kt |
| Playwright Tests | apps/web/tests/ |
| Kover Config | apps/api/build.gradle.kts |
Document Revision History:
- v1.0 (2026-05-10): Initial runbook creation covering Rounds 11-27 deliverables (MC #100424)
No comments to display
No comments to display