bilko

Bilko product documentation

Test Sweep 2026-05-15

Bilko Comprehensive Test Sweep — 2026-05-15

Date: 2026-05-15
Verdict: PARTIAL
Mission Control: MC #100704 (preflight) through #100715 (this documentation)
Orchestrated by: John (CEO request option C)
Executed via: Ollama DAG (primary) + direct subagent fallback
Validator pattern: /verify-fix-loop


Summary

A comprehensive test sweep across Bilko's 3 deployed domains (bilko.io stage, bilko.cloud UAT, bilko.company UAT) covering 9 test categories: backend unit, backend integration, frontend unit, end-to-end (×3 domains), SAST, DAST, and performance + accessibility.

Consolidated verdict: PARTIAL

Sweep cost: TBD (to be extracted from MC aggregation)
Execution method: DAG orchestration → task distribution → validator synthesis
Follow-up MCs opened: 6 MCs for fix-loop iteration B (test harness repairs + header hardening)


Scope

Domains tested

  1. bilko.io stage — https://bilko-web-stage-dh4m46blja-lz.a.run.app (GCP Cloud Run, Next.js 15 frontend)
  2. bilko.cloud — https://bilko.cloud (Cloudflare Pages, HR market UAT landing)
  3. bilko.company — https://bilko.company (Cloudflare Pages, BA market UAT landing)

Test categories (9)

  1. Backend unit tests (Kotlin/Ktor, Kotest)
  2. Backend integration tests (Kotlin/Ktor, Testcontainers PostgreSQL)
  3. Frontend unit tests (Next.js 15, Vitest, jsdom)
  4. End-to-end bilko.io stage (Playwright, auth-fresh fixture)
  5. End-to-end bilko.cloud UAT (Playwright, static landing assertions)
  6. End-to-end bilko.company UAT (Playwright, static landing assertions)
  7. SAST (Detekt, npm audit, SonarCloud, Snyk)
  8. DAST (passive header analysis, OWASP ZAP baseline)
  9. Performance + Accessibility (Lighthouse CI, 3 runs per domain)

Blueprint references


Results by Category

Category Verdict Tests Passed Tests Total Key Numbers Evidence File
BE Unit PASS 693 693 121/121 core, 29 suites api-unit-rerun-v2.json
BE Integration PARTIAL 556 576 20 failures (enum DDL) api-int-rerun-v2.json
FE Unit PARTIAL 56 67 14 suites pass, 6 fail web-unit.json + core-unit.json
E2E bilko.io FAIL 3 37 34 unexpected (401) e2e-io/summary.json
E2E bilko.cloud PASS 8 8 All assertions verified e2e-cloud/summary.json
E2E bilko.company PASS 8 8 All assertions verified e2e-company/summary.json
SAST PARTIAL Detekt 0 npm audit 2 HIGH sast-summary-100711.json
DAST PARTIAL 0 CRITICAL 4 MEDIUM header gaps dast-summary-100712.json
Perf + A11y PASS 100/96/96/92 3 domains All gates pass lhci-io/cloud/company.json

Findings

Critical Security Findings

Count: 0

Product Regressions

Count: 0

All test failures fall into two categories:

  1. Test infrastructure/harness issues (in-scope for fix-loop iteration 2):

    • auth-fresh.ts fixture does not persist access token across Playwright context resets (E2E bilko.io 34/37 FAIL)
    • localStorage mock missing in Vitest jsdom setup (FE settings.test.tsx 8 failures)
    • i18n stub incomplete in invoices.test.tsx (raw key {invoices} rendered)
    • MarketContext.test.tsx null guard missing (1 test TypeError)
  2. Pre-existing structural defects (out-of-scope, tracked separately):

    • InvoiceStatus enum DDL ordering in DbTestBase.setUpDatabase (BE integration 20 failures)
    • SonarCloud + Snyk tokens absent from Bitwarden (SAST coverage gap)
    • x-frame-options, CSP absent on bilko.cloud + bilko.company (DAST 4 MEDIUM)
    • npm audit 2 HIGH in devDependencies (rollup via @sentry/nextjs, tmp via @lhci/cli)

Fix-Loop Iteration 2 Outcomes

Status at publish time: In progress (CodeCraft B-half dispatched)

Fix-loop candidates identified by validator:

  1. auth-fresh.ts fixture — inject access token into Playwright storageState (Vizu/Playwright author, effort S)
  2. localStorage mock — add vi.stubGlobal in vitest.config.ts setupFiles (CodeCraft/FE author, effort XS)
  3. i18n stub — add i18n mock provider in test wrapper (CodeCraft/FE author, effort S)
  4. MarketContext null guard — add null guard in MarketContext.tsx or test stub (CodeCraft/FE author, effort S)

Follow-up MCs opened (6):

  1. MC TBD — Fix InvoiceStatus enum ordering in BE integration test schema (DbTestBase.setUpDatabase) — Priority H, owner CodeCraft
  2. MC TBD — Provision SONAR_TOKEN in Bitwarden and enable SonarCloud SAST scan — Priority M, owner FlowForge
  3. MC TBD — Provision Snyk auth token in Bitwarden and integrate Snyk into CI — Priority M, owner FlowForge
  4. MC TBD — Add _headers file to bilko.cloud and bilko.company CF Pages deployments (x-frame-options, CSP, HSTS) — Priority H, owner FlowForge
  5. MC TBD — Fix rollup HIGH CVE (GHSA-mw96-cpmx-2vgc) via @sentry/nextjs upgrade or exclusion — Priority M, owner CodeCraft
  6. MC TBD — Execute ZAP active scan against bilko.io stage after image stabilises — Priority M, owner Securion

Lighthouse Scores (Performance + Accessibility)

All 3 domains tested with Lighthouse CI (3 runs each, median scores):

Domain Performance Accessibility Best Practices SEO
bilko.io 100 96 96 92
bilko.cloud 100 96 96 92
bilko.company 100 96 96 92

Gates:

A11y failures (all 3 domains, same pattern):

Score remains 96/100 (within acceptable range per TEST-PLAN §7.4 target: Lighthouse Performance Score > 90).

Note: axe-core WCAG 2.1 AA Playwright tests (TEST-PLAN §8.9) were not executed in this sweep — blocked by e2e_io auth-fresh fixture failure.


Evidence Index

All evidence files stored in /tmp/bilko-test-sweep/ with integrity checksums:

File Category SHA256 (first 16 hex) Notes
validator-verdict.json Validator synthesis (computed at publish) Canonical consolidated verdict
api-unit-rerun-v2.json BE unit (computed) 693/693 PASS
api-int-rerun-v2.json BE integration (computed) 556/576 PASS, 20 failures
web-unit.json FE unit (computed) 56/67 PASS
core-unit.json FE unit (core) (computed) 121/121 PASS
e2e-io/summary.json E2E bilko.io (computed) 3/37 PASS
e2e-cloud/summary.json E2E bilko.cloud (computed) 8/8 PASS
e2e-company/summary.json E2E bilko.company (computed) 8/8 PASS
sast-summary-100711.json SAST (computed) Detekt 0 findings, npm audit 2 HIGH
dast-summary-100712.json DAST (computed) 0 CRITICAL, 0 HIGH, 4 MEDIUM
lhci-io.json Perf+A11y bilko.io (computed) 100/96/96/92
lhci-cloud.json Perf+A11y bilko.cloud (computed) 100/96/96/92
lhci-company.json Perf+A11y bilko.company (computed) 100/96/96/92
mc-ids.json MC tracking (computed) 12 MC IDs (#100704–#100715)

Screenshot evidence:


Genesis

CEO ask: 2026-05-15 morning
Option selected: C (comprehensive sweep across all test categories)
Execution method: Ollama DAG orchestration (primary) + direct subagent fallback when DAG blocked
Validator pattern: /verify-fix-loop (atomic-claim decomposition, read-only verification)
12 MCs spawned: #100704 (preflight), #100705 (BE unit), #100706 (BE integration), #100707 (FE unit), #100708 (E2E bilko.io), #100709 (E2E bilko.cloud), #100710 (E2E bilko.company), #100711 (SAST), #100712 (DAST), #100713 (Perf+A11y), #100714 (validator), #100715 (this documentation)

Blueprint compliance:

Met:

Gaps:



Published by: Skillforge (ALAI knowledge management)
Reviewed by: John (AI Director)
Approved for publish: 2026-05-15

Bilko HR Roadmap — Fiscal Compliance + Feature-Enable Architecture (2026-05-29)

Bilko HR Roadmap — Fiscal Compliance + Feature-Enable Architecture

Created: 2026-05-29 CEO directive: B2B prvo, B2C odgođen, citav app feature-enable based (micro-frontend) za per-user per-plan delivery Status: Active roadmap, supersedes prior B2B-only scoping


1. Executive summary


2. Croatian fiscal legal framework

Zakon o fiskalizaciji

F1 — B2C fiskalizacija (POS / kasa)

F2 — B2B eRačun

Terminologija

PDV stope


3. Phased roadmap sa MC mapingom

Phase 0 — B2B prvo (current focus)

Phase MC Naslov Status Vlasnik
0a #102447 Storecove HR-FISK 2.0 activation (FAST PATH, 4-5 dana) open — counter-offer poslan 2026-05-28 john
0b #102398 Sveračun API sandbox onboarding (negotiated <€0.10/invoice) open — čeka CEO email za creds + računovođa intro john
0c #102481 Feature-enable based micro-frontend architecture (CEO 2026-05-29) open — kritičan prerequisite, dolazi PRIJE Phase 1 john
0d #102401 Bilko Phase 0 multi-org switcher + Securion RLS gate open — zavisi od bilo koji B2B ACK john
0e #102399 5 accountant pilot recruitment (90-day) open — CEO outreach alem
0f #102400 HR pravna osoba tracking (background) open — Sveračun potvrdio NIJE potrebno za B2B; B2C TBD alem

Phase 1+ — B2C (DEFERRED per CEO 2026-05-29)

Phase MC Naslov Status
1 #102478 B2C fiskalizacija MVP (ZKI + JIR + SOAP + FINA cert) DEFERRED — 21 dana effort
2 #102479 B2C POS web kasa modul DEFERRED — 19 dana effort
3 TBD Hardware integration (fiskalni printer, cash drawer, barcode) not opened
4 TBD Industry specializations (hospitality, retail, services) not opened

4. Feature-enable architecture directive

CEO directive (verbatim, 2026-05-29)

"citav app treba da je feature enable based (micro frontend) nadam se da mozemo lako features per user per pay deliver a ne da sve hakiramo"

Problem statement

Cilj — komponente architekture

  1. Central feature catalog — kanonski popis svih feature-a sa metadata (market, plan tier, dependency graf)
  2. Per-user feature resolution — funkcija od (tenant org country + plan tier + accountant role + custom overrides)
  3. Micro-frontend split — nezavisni feature modules koji se učitavaju/ne učitavaju po feature flag-u
  4. Pricing model alignment — Basic/Pro/Accountant tier = feature subset (definirano u catalog-u)
  5. Safe fallback — feature flag service down → conservative defaults (Basic, ne Pro)
  6. Audit trail — koji user kad accessovao koji feature

Implementation phases (MC #102481)

Build vs buy

Datavera istražuje:


5. Open decisions for CEO

  1. B2B path: prihvatamo Storecove counter-offer (ako stigne s revidiranim quote) ili idemo Sveračun?
  2. HR pravna osoba: potvrđeno NIJE potrebna za B2B; otvoreno za buduće B2C (kad ga otvorimo)
  3. Feature catalog vendor: build vs buy — odluka nakon Datavera istrage
  4. Multi-market timing: kad RS (SEF) + BiH ulaze u feature catalog scope?

6. MC indeks

MC Priority Status Vlasnik Naslov Rute
#100332 H ready_for_review alem Sveračun CEO outreach (parent) bizdev
#102447 H open john MC-A2 Storecove FAST PATH backend
#102398 H open john MC-A Sveračun sandbox onboarding backend
#102481 H open john Feature-enable architecture (CEO 2026-05-29) backend
#102401 H open john MC-D Phase 0 multi-org switcher backend
#102399 H open alem MC-B 5 accountant pilot recruit bizdev
#102400 M open alem MC-C HR doo tracking bizdev
#102478 H open (DEFERRED) john MC-E B2C fiskalizacija MVP backend
#102479 M open (DEFERRED) john MC-F B2C POS web kasa frontend
#102448 H open john Vendor email routing + auto-forward devops
#102449 H open john Policy: no strategic reply bez CEO OK bizdev

7. Source artifacts


8. Decision log

Datum Odluka Kontekst
2026-05-28 Bilko HR ide dual-track GTM (accountant + direct SMB) Sveračun meeting strategic input
2026-05-28 Storecove counter-offer poslan (no name drop Sveračun) CEO directive — leverage
2026-05-29 B2B prvo, B2C odgođen CEO scope decision
2026-05-29 Feature-enable / micro-frontend arch je obavezan prerequisite za sve daljnje builds CEO architecture directive

9. Next steps

  1. Storecove reply chase — ako nema odgovora do 2026-06-04, poslati follow-up sa ref na contract expiry 27-06-2026
  2. Sveračun follow-up email — CEO šalje za sandbox creds + 3-5 računovođa intro (target: 2026-05-30)
  3. Datavera dispatch za feature catalog vendor comparison (LaunchDarkly vs GrowthBook vs Unleash vs build-our-own)
  4. Mehanik gate za MC #102481 (feature-enable arch) prije CodeCraft Phase 0 dispatch
  5. Update MEMORY.md sa pointer-om na ovu BookStack stranicu (skip-list ako se promijeni status)

Bilko BUG-005 — revenueMTD / credit-note documentType + RLS migration landmine

Bilko BUG-005 — revenueMTD negative (credit-note documentType + RLS migration landmine)

MC: #103001 (child #102887) | Fixed: 2026-06-05 | Tag: v0.2.18 / PR #255

Symptom

Dashboard KPI revenueMTD showed -457.50 on bilko-demo. CEO-visible.

Root cause (two layers)

  1. Write-side: InvoiceService.createCreditNote insert omitted documentType. The Invoices.documentType column has no Kotlin .default, so the PG column default 'standard' applied. Every credit note was stored as STANDARD with a negative amount. The v0.2.17 read-side fix (ReportService.getRevenueForPeriod excludes CREDIT_NOTE) therefore could not exclude them.
  2. Backfill blocked by RLS: migration V65 (UPDATE invoices SET document_type='credit_note' WHERE document_type='standard' AND invoice_number LIKE 'CN-%') recorded success=t but affected 0 rows. The org_isolation RLS policy returns FALSE when app.current_org_id is unset; Flyway runs as bilko with no org GUC, so RLS hid every row. No SQL error → Flyway "succeeded".

Fix

Verification (Proveo independent PASS, live)

revenueMTD 375.00 (was -457.50); CN-2026-001..007 all documentType=credit_note; freshly created CN is credit_note; INV-2026-001 stays standard. Evidence: /tmp/verify-103001/proveo-validation.md.

Reusable lessons

Bilko Backoffice — Backend MVP (Sentry + audit request_id + support_tickets)

1. Overview

This backend slice (MC #103323, branch feat/103323-backoffice-backend, commit 6b214a00, PR #316) delivers the diagnostic and intake backbone for the Bilko support fix-loop.

Before this slice, when a customer hit an accounting error on app.bilko.cloud neither the platform team nor the customer had a way to identify which request failed or why. Three components address that gap:

  1. Sentry error capture — catch-all (INFRA-only) exception capture with PII scrub and Cloud Run release/serverName metadata. Inert until SENTRY_DSN secret is provisioned (OCD-1, CEO action).
  2. V71 audit_log.request_id — nullable correlation column added to every audit row, threaded from a single canonical source (call.callId) across all route handlers.
  3. V72 support_tickets + SupportTicketRoutes — customer intake channel (POST) and platform-admin triage queue (GET list + GET detail + PATCH status) with RLS, idempotency, and full status-transition audit trail.

This slice is deploy-gated behind prod cutover MC #103300. All three components were independently verified by Proveo (Angie Jones): 12/12 AC signals PASS, integration test 3/3 PASS, unit suite 1280/1280.

2. Component Map

2.1 Sentry capture — plugins/Sentry.kt + plugins/StatusPages.kt

2.2 V71 audit_log.request_id — AuditLogService.kt + migration

2.3 V72 support_tickets + SupportTicketRoutes — routes/SupportTicketRoutes.kt

3. Data Model

3.1 support_tickets columns

ColumnTypeNotes
idUUID PKgen_random_uuid() default
org_idUUID NOT NULLFK to organizations(id) ON DELETE CASCADE
user_idUUID NOT NULLFK to users(id)
error_codeTEXTNullable; currently generic VAL/INFRA (OCD-2 open CEO decision)
request_idTEXTCorrelation ID of originating failed request. NOT a FK to audit_log.request_id (one request_id maps to N audit rows). Join via equality.
context_bundleJSONB NOT NULLCHECK jsonb_typeof = 'object'. Allowlisted keys only (server-side enforced).
customer_descriptionTEXTFree text from customer
statusTEXT NOT NULLCHECK (status IN ('OPEN','TRIAGED','IN_PROGRESS','RESOLVED','CLOSED')). Default 'OPEN'.
triage_jsonJSONBNULL = not yet triaged. V2 AI agent writes here.
created_atTIMESTAMPTZ NOT NULLDEFAULT now()
updated_atTIMESTAMPTZ NOT NULLDEFAULT now(); maintained by BEFORE UPDATE trigger.
resolution_noteTEXTRequired (route-enforced) for RESOLVED/CLOSED transitions.
external_refTEXTV2 Zendesk/Linear sync. Nullable at MVP.

3.2 Indexes

3.3 RLS policies

All GUC SET statements use SET LOCAL (transaction-scoped) — pgBouncer transaction-mode pooling safe.

Customer UPDATE/DELETE immutability: no UPDATE or DELETE policy for customers. RLS ENABLED with no such policy = deny-by-default. Customers cannot modify or delete submitted tickets.

Production code audit (Proveo-confirmed): orgTransaction{} (OrgScopeSessionVariable.kt:131) always wraps SET LOCAL app.current_org_id inside transaction{}. The Testcontainers test failure (Proveo GAP-1) was caused by the test setup using a PostgreSQL superuser connection — superusers bypass RLS regardless of GUC values. Production code was never buggy.

3.4 Status transition machine

FromAllowed next states
OPENTRIAGED, CLOSED
TRIAGEDIN_PROGRESS, CLOSED
IN_PROGRESSRESOLVED, CLOSED
RESOLVEDCLOSED
CLOSED(no further transitions)

Invalid transitions return HTTP 422 with code: "INVALID_TRANSITION" and allowedNext.

3.5 context_bundle allowlist

Allowed keys (server-side enforced, rejection = HTTP 422): requestId, errorCode, httpStatus, instancePath, orgId, userId, appRoute, planTier, country, auditRef. IDs and codes only — never invoice content, names, amounts, or email addresses.

4. Diagnostic Join

SELECT al.*
FROM audit_log al
JOIN support_tickets st ON al.request_id = st.request_id
WHERE st.id = '<ticket-uuid>';

Framing (martin-kleppmann dissent): request_id is a correlation handle for cross-system debugging only — NOT tamper-evidence. The append-only guarantee for audit_log comes from the block_audit_mutation() trigger (V51), not from request_id. Platform-admin direct DB access is outside the threat model of this column.

5. Known Gaps and Follow-ups

ItemDetailStatus
OCD-1: Sentry DSN bilko-sentry-dsn / bilko-web-sentry-dsn must be provisioned in GCP Secret Manager. Inject via --update-secrets (never --set-env-vars). Sentry code is fully inert until then. CEO action required. Blocks production deploy; does not block feature branch merge.
OCD-2: error_code taxonomy Domain errors currently fall into generic VAL/INFRA codes, making ticket triage partly blind. Domain-specific codes are V2 scope (MC #103333). CEO confirmed proceed with V72 before those codes land. Open CEO decision. V2 follow-on MC #103333.
OCD-3: merge-order vs #103300 V71/V72 migration numbers must be confirmed/renumbered after #103300 merges. Open. Blocking deploy only.
Positive-path RLS assertion Integration test confirms negative proof (wrong-org INSERT rejected). Positive proof (correct-org INSERT succeeds) not explicitly asserted. Proveo: completeness gap, not safety-weakening gap. Follow-up test enhancement. Non-blocking.
CI runner quota Tracked as MC #103304. Separate MC.
Deploy gate Deploy-gated behind MC #103300 prod cutover. Dependent on #103300.

6. Verification Evidence

Bilko Backoffice — Ops Infra (Logging Views + support@ Forwarding + Preflight)

Bilko Backoffice — Ops Infra (MC #103325)

Branch: feat/103325-backoffice-infra | PR: #317 | Proveo verdict: PASS (2026-06-10) | Sibling: Backoffice Backend MVP (page 3100)


1. Cloud Logging Saved Views

GCP project: tribal-sign-487920-k0
Bucket: _Default (global)
Verified via: gcloud logging views list --bucket=_Default --location=global --project=tribal-sign-487920-k0

View IDScope filterIntended use / Log Explorer query to add
bilko-error-by-org resource.type="cloud_run_revision" AND resource.labels.service_name=~"bilko-api-(demo|stage)" Add query severity>=ERROR. Group results by orgId (parse via JSON_EXTRACT(textPayload, "$.orgId") — orgId lives in textPayload JSON, not jsonPayload).
bilko-request-trace resource.type="cloud_run_revision" AND resource.labels.service_name=~"bilko-(api|web)-(demo|stage)" Add query logName=~"stdout" OR logName=~"requests". Correlate requests end-to-end by requestId field in textPayload.
bilko-5xx-demo resource.type="cloud_run_revision" AND resource.labels.service_name=~"bilko-(api|web)-demo" Add query httpRequest.status>=500. Scoped to demo environment only.

GCP constraint — view filter expressiveness

GCP gcloud logging views create --log-filter only accepts log source, resource type, appHub fields, user labels, and log ID conditions. Severity comparisons (severity>=ERROR) and field comparisons (httpRequest.status>=500) are not valid in view filters — they must be added as Log Explorer query refinements on top of the saved view scope. This is a documented GCP platform limitation. Each view description in GCP documents this explicitly.

Log schema note: Bilko API logs structured data as JSON inside textPayload (not jsonPayload). The textPayload schema is: {"requestId":"...","method":"...","path":"...","status":N,"durationMs":N,"userId":"...","orgId":"...","ip":"..."}. ERROR logs are stack traces in textPayload; orgId is present on request-completion log lines, not on exception lines.


2. support@bilko.cloud Email Forwarding

MX provider — IMPORTANT

bilko.cloud MX = Migadu (aspmx1.migadu.com + aspmx2.migadu.com, confirmed via dig MX bilko.cloud). The CF Email Routing section in DEPLOY-MAP.md is STALE and must be corrected — Cloudflare does not handle bilko.cloud email.

Implemented forwarding

Mail flow: support@bilko.cloud (Migadu mailbox, may_receive=true, may_send=false) → Migadu forwarding → alem@alai.no

Key Migadu design constraint: Alias destinations only accept same-domain addresses — external addresses are silently rewritten to <localpart>@<same-domain>. The correct mechanism for external cross-domain delivery is a forwarding on a mailbox object (not an alias).

Implementation steps taken:

  1. Confirmed GET /v1/domains/bilko.cloud/mailboxes/support/forwardings — support@ was alias-only (no mailbox).
  2. Created support@bilko.cloud mailbox: may_receive=true, may_send=false, IMAP/POP3 disabled (receive-only).
  3. Added forwarding via POST /v1/domains/bilko.cloud/mailboxes/support/forwardings {"address":"alem@alai.no"} — response: is_active: true, confirmed_at: 2026-06-10T08:17:01Z, no confirmation email required.
  4. Deleted the old support@ alias (superseded by mailbox).
  5. Removed investigation-only forwarding from sales@bilko.cloudsales@ is left untouched (forwardings: []).

Verified state (Proveo independent GET):

GET /v1/domains/bilko.cloud/mailboxes/support/forwardings
{"forwardings":[{"address":"alem@alai.no","confirmed_at":"2026-06-10T08:17:01Z","blocked_at":null,"is_active":true}]}

GET /v1/domains/bilko.cloud/mailboxes/sales/forwardings
{"forwardings":[]}

Migadu admin path (for future changes)

To modify forwarding: admin.migadu.com → bilko.cloud → Mailboxes → support → Forwardings. Do not use the Aliases section for external cross-domain targets.


3. Preflight Rollback Script

File: scripts/ops/bilko-support-fix-preflight.sh (committed at 67ed0ce5, PR #317, mode 100755)

What it does

  1. STEP 1 — Cloud SQL backup (write, skipped in dry-run): Takes an on-demand Cloud SQL backup of the Bilko DB before any deploy action. Provides a restore point.
  2. STEP 2 — Capture current Cloud Run revision (read-only always): Records the live revision name and image SHA for both bilko-api-demo and bilko-web-demo.
  3. STEP 3 — Print rollback commands (print only, never executes): Outputs the exact gcloud run services update-traffic commands needed to roll back to the captured revisions. These are echo-wrapped — they are never executed by the script.

How to run

# Dry-run (safe, no writes — use to confirm rollback targets before deploy)
bash scripts/ops/bilko-support-fix-preflight.sh --dry-run

# Live run (takes SQL backup, captures revisions, prints rollback cmds)
bash scripts/ops/bilko-support-fix-preflight.sh

Deploy-fragility rule enforced

All example re-deploy commands in the printed output use --update-secrets. The script documents and enforces: NEVER use --set-env-vars for Bilko Cloud Run deploys — it overwrites the Secret Manager binding and exposes secrets as plaintext environment variables.


4. Known Follow-up

ItemStatusOwner
DEPLOY-MAP.md CF Email Routing section for bilko.cloud is stale (lists Cloudflare; MX is Migadu) Open John / next infra PR
PR #317 bundles MC #103323 application code (Sentry, SupportTickets, DB migrations V71+V72) — confirm separate QA validation for that scope Open (Proveo advisory) John
Merge PR #318 (smoke-test fix) before PR #317, or close #318 as superseded if #317 merges first Open John

Created by Skillforge for MC #103325. Evidence: /tmp/evidence-103325/verification.md + /tmp/alai/p2p-pairing-evidence/proveo-multi-317-318-verdict.md. All facts machine-verified.

MC #105153 — API write bucket stage E2E rate-limit override

MC #105153 — Bilko API write bucket 429 stage E2E

Scope

Fix authenticated api rate-limit bucket 429s on stage E2E for sentinel contact create and instant-demo write-protection probes, while preserving production defaults.

Changes verified on disk

Verification — 2026-07-09T23:07Z

Command run from apps/api:

./gradlew test --tests no.alai.bilko.plugins.RateLimitTest --rerun-tasks

Result: BUILD SUCCESSFUL in 21s, 7 actionable tasks: 7 executed.

JUnit XML evidence:

apps/api/build/test-results/test/TEST-no.alai.bilko.plugins.RateLimitTest.xml
<testsuite name="no.alai.bilko.plugins.RateLimitTest" tests="22" skipped="0" failures="0" errors="0" ...>

Static presence check:

grep -n "API_RATE_LIMIT_PER_MINUTE" \
  apps/api/src/main/kotlin/no/alai/bilko/plugins/RateLimit.kt \
  apps/api/src/test/kotlin/no/alai/bilko/plugins/RateLimitTest.kt \
  azure-pipelines.yml .github/workflows/azure-stage.yml \
  infrastructure/gcp/ENV-MATRIX.md .env.example

Observed paths/lines:

Production-safety note

Production default remains unchanged: absent/invalid API_RATE_LIMIT_PER_MINUTE resolves to 100, and only stage deploy config sets API_RATE_LIMIT_PER_MINUTE=500.

P2P pre-verifier

Bilko sveRačun Faza 1 — TEST-live aktivacija na demou (MC #103450, 2026-07-11)

Bilko sveRačun Faza 1 — TEST-live aktivacija na demou (MC #103450, 2026-07-11)

Šta je urađeno

HR eRačun (sveRačun/PostLink) submit aktiviran na demou (app.bilko.cloud / app-api.bilko.cloud, ACA bilko-api-demo, rg-bilko-demo) protiv TEST sandboxa test.sveracun.hr — bez stvarne fiskalizacije, reverzibilno.

Stanje (tool-verifikovano)

Proveo E2E — PASS (2026-07-11)

Rollback

UPDATE hr_einvoice_issuer_config SET enabled=false
WHERE org_id IN ('00000000-0000-0029-c000-000000000001','1f9811d2-af38-482d-91d9-229e1acbb37e');

(+ opcionalno SVERACUN_HR_LIVE=false na bilko-api-demo za potpuni STUB.)

Nalazi / follow-up

Kontekst lanca

#104171 (Azure secret+env+runbook) → #104172 (arhiva Azure Blob) → #103452 (submit path + B4 idempotency) → #103450 (ovo) → #103443 (live aktivacija, parent).

Bilko — Minimax Gap Program (2026-07-20)

1. CEO odluka i kontekst

Datum CEO odluke: 2026-07-20 — "Sve odobreno" (svih 6 stavki gap analize).

Povod: CEO ask — "vidi jesmo li pokrili sve iz Minimax dokumentacije; eRačun = top seller; storno; plate/odsustva; mob slikanje računa; mob time tracking."

Metod: 4 paralelna read-only istraživačka agenta nad kanonskim Bilko repo-om (John, 2026-07-20), rezultat u docs/product/MINIMAX-GAP-ANALIZA-2026-07-20.md.

Izvorna dokumentacija (Minimax-derived): docs/product/MINIMAX-MIGRATION-UX-BRIEF.md (P0/P1/P2 backlog), docs/product/BILKO-HR-KNOWLEDGE-ARTICLE-PLAN.md + 33 draft članka, docs/COMPETITIVE-RESEARCH.md §2.8.

Program parent: MC #106102 — "PROGRAM parent — 6 CEO-odobrenih stavki gap analize 2026-07-20".

Red-zone stavke (iz MC #106102): storno (novac/zakon), eRačun prod send (fiskalno).

Phase-gate napomena (ključno — vidi §5): spec taskovi (#106108 payroll odsustva, #106110 time tracking, #106111 migracioni wizard) čekaju CEO potvrdu prije nego se otvore build taskovi. Nijedan od ta tri specA nije autorizovan za gradnju — samo dizajn/plan.

2. MC taskovi programa — status i vlasnik

MC #NaslovPrioritetStatusVlasnikNapomena
#106102PROGRAM parent — 6 CEO-odobrenih stavkiHin_progressjohnParent svih ispod
#106103Storno eRačuna dovršetak: SefHttpClient.cancelInvoice (RS), HR FISK storno submission, skini PRO+ gate sa INVOICE_CREDIT_NOTEHin_progressjohn🔴 Red-zone (novac/zakon)
#106104Storno E2E validacija na write org-u (HR-603 MEDIUM #6)HpausedjohnQA za #106103
#106105eRačun top-seller put A: sveRačun/FINA produkcijski provider onboardingHopenjohn🔴 Red-zone (fiskalno)
#106106eRačun top-seller put B: send kao primarni CTA u invoice flowu (web)MpausedjohnGated na #106105 aktivaciju
#106107eRačun top-seller put C: hero/landing poruka redesignMpausedjohnNAKON prod aktivacije
#106108Payroll odsustva SPEC (godišnji/bolovanje/dopust) + JOPPDMpausedjohnSPEC gotov — čeka CEO potvrdu za build
#106109Mobile capture fix paket (crop, OCR confidence, multi-page, telemetrija)MpausedjohnNalaz gotov, build task nije otvoren
#106110Time tracking SPEC (Tripletex parity)MpausedjohnSPEC gotov — čeka CEO potvrdu za build
#106111Migracioni wizard MIG-01→08 SPEC + fazni planMpausedjohnSPEC gotov — čeka CEO potvrdu za build
#106113BookStack dokumentacija programa (ova stranica)Min_progress→doneskillforgeDocs task (ZAKON PLAN)

Napomena: MC #106112 (RAG-first hook Phase A recorder) i #106114 (alai-hooks CLI dispatch mismatch) su susjedni ID-jevi ali NISU dio Minimax Gap programa — to su odvojeni SYSTEM taskovi (hook infrastruktura), owner john, nepovezani s Bilko gap analizom.

3. Sažetak nalaza po stavci

3.1 eRačun pozicioniranje — "jednostavno slanje eRačuna" kao top-seller

Nalaz: NIJE trenutna poruka. Hero na landing-hr je "Računovodstvo koje razumije hrvatski biznis" + "Jednostavnije nego Minimax. Jeftinije nego Pantheon." + €15/mj. eRačun je svuda označen "FISK 2026 u razvoju · Beta".

Zašto: produkcijsko slanje NIJE aktivno bez provider onboardinga (sveRačun aktiviran samo za 2 test org-a). U web appu "Pošalji" = email; eRačun kartica je sekundarna sekcija, ne prvi korak.

Zaključak: poruka zahtijeva (a) produkcijsku aktivaciju providera, (b) send kao primarni CTA, (c) redizajn hero poruke — sekvencionisano kao putevi A/B/C (#106105/#106106/#106107). Trenutni copy je pošten (SAFE-02 guardrail), ali ne prodaje ono što CEO hoće da bude top-seller. Poznati srodni blokator: rabat/AllowanceCharge gap u HR eRačun XML (nalaz #105269, čl.79 ZPDV).

3.2 Storno poslanog eRačuna — parcijalno

Nalaz: PARCIJALNO. Kreditno odobrenje (storno) postoji lokalno: POST /invoices/credit-note, tip CREDIT_NOTE, GL reversal, UBL tip 381 + BT-25/BT-26, CN-YYYY-NNN numeracija, UI dugme. HR eRačun servis MOŽE serializovati i poslati credit note kroz sveRačun.

Rupe:

🔴 Red-zone: novac + zakonska obaveza. Build task #106103, validacija #106104.

3.3 Plate — odmor/permisija/bolovanje — ne postoji

Nalaz: NE POSTOJI. Payroll MVP (V101) = registar zaposlenika + bruto→neto HR obračun + payslips. Nula podrške za odsustva: nema absence tabela, nema tipova, nema leave balansa, payslip uvijek uzima puni gross_salary_cents, nema JOPPD šifri za vrste odsustva. Nije ni u PAYROLL-PHASE2-PLAN.md. MARKET-READINESS-MATRIX: Payroll = NOT_IMPLEMENTED za GA.

Reuse mogućnost (istraženo u SPEC-u #106108): @alai/hr-core extraction bio je planiran ali nikad izvršen do integracije — skeleton postoji (jedan commit, 2026-04-18), Bilko i Lobby ga nikad nisu wire-ovali. Reuse verdikt: DJELIMIČNO — shema/table skeleton (LeaveTables.kt) je reusable kao dizajn-referenca, servisna logika mora biti napisana od nule, direktno u Bilko-vom postojećem obrascu (isto kao što je payroll MVP sam građen bez hr-core).

3.4 Mobilna — slikanje računa — postoji sa slabostima

Nalaz: POSTOJI ali sa konkretnim slabostima (apps/mobile/app/(tabs)/expenses/index.tsx, RN+Expo v0.1.5):

Ovo objašnjava CEO utisak "slikanje računa nije dobro". Fix paket praćen u #106109.

3.5 Mobilna — time tracking (Tripletex paritet) — ne postoji

Nalaz: NE POSTOJI. Ni u mobile appu, ni u backendu (nema time-entry ruta/tabela), ni u MOBILE-PRD fazama 0-5. Jedino srodno: putni nalozi (web, HR). COMPETITIVE-RESEARCH bilježi Tripletex time tracking kao add-on NOK 59/user/mj.

SPEC verdikt (#106110): Lobby TimerService.kt/TimerRepository.kt/TimerRoutes.kt su 85-90% portable (generic clock-in/out), ali NE pokrivaju billable-time-to-invoice linkage niti approval workflow (to dolazi iz Bilko-ovog vlastitog TravelOrder RBAC obrasca). Revidirana procjena: ~3.5-4.5 sedmica (full-stack: backend+mobile+web+pricing gate), veće od originalne grube procjene 2-3 sedmice. Preporučeni pricing: flat org-wide add-on (ne per-user metered), usklađeno sa Bilko per-org filozofijom.

3.6 Migracioni wizard (MIG-01→08) — nula koda specifično za wizard

Nalaz iz gap analize: MIG-01→08 dokumentovano detaljno, nula koda u web appu; landing "Prebaci s Minimaxa" obećava "kontrolnu listu i pregled podataka". Takođe nedostaju: ONB-01→04 (onboarding), R1-01→05 (račun na firmu tok), MOB-01/03 (Today tab), 33 HR knowledge članka (svi draft), Minimax OIB→registar auto-fill pattern.

SPEC korekcija (#106111, važno): tvrdnja "nula koda" NIJE tačna za backend infrastrukturu uvoza općenito — postoji direktno primjenjiv arhitekturni template (V87__gl_bank_import.sql staging+idempotency+RLS obrazac, GlBankService.kt, InboxRoutes.kt accept/reject tok, SupportTicketRoutes.kt queue backbone). Zaključak: MVP je ~70% re-orkestracija postojećih patterna za generic CSV, ne gradnja od nule. Landing copy je već usklađen sa SAFE-01/02 (ne laže eksplicitno, ali gap je i dalje realan — nema in-app checklist/upload/wizard).

Preporučeni MVP rez (F1): MIG-01+02+03 (upload u support queue, BEZ auto-parse)+MIG-07 (support queue)+SAFE-01/02/03, procjena M (~1 sprint). Puni wizard sa mapping-preview/auto-validacijom (F2/F3) zahtijeva security review (malware scan, CSV injection) i čeka odgovor na Open Question #1 (koji export formati su stvarno dostupni).

4. Linkovi i putanje na specove i evidence

DokumentPutanja
Glavna gap analiza (6 stavki)~/business/ALAI-Holding-AS/products/Bilko/docs/product/MINIMAX-GAP-ANALIZA-2026-07-20.md
Payroll odsustva spec (#106108, CodeCraft/Petter Graff)~/system/evidence/106108/PAYROLL-ABSENCE-SPEC.md
Time tracking spec (#106110, Skybound)~/system/evidence/106110/TIME-TRACKING-SPEC.md
Migracioni wizard plan (#106111)~/system/evidence/106111/MIGRATION-WIZARD-PLAN.md
Izvorna Minimax UX brief (MIG/ONB/SAFE kodovi)docs/product/MINIMAX-MIGRATION-UX-BRIEF.md (Bilko repo)
HR knowledge article plandocs/product/BILKO-HR-KNOWLEDGE-ARTICLE-PLAN.md (Bilko repo)
Competitive research (Tripletex time tracking add-on)docs/COMPETITIVE-RESEARCH.md §2.8 (Bilko repo)
Payroll Phase 2 plan (JOPPD, RS/BA sekvencioniranje)docs/regulatory/PAYROLL-PHASE2-PLAN.md (Bilko repo)
hr-core extraction plan (Sprint 0, orphaned)docs/research/hr-core-extraction-plan.md (Bilko repo)

5. Phase-gate napomena

Specovi #106108 (payroll odsustva), #106110 (time tracking) i #106111 (migracioni wizard) su SPEC/PLAN ONLY — nema koda, nema build taskova otvorenih. Sva tri dokumenta eksplicitno navode na vrhu: "Status: SPEC ONLY, no code, no build tasks created" / "PHASE-GATE: ovaj dokument ide CEO-u na potvrdu prije nego se otvori bilo koji build task."

Prije otvaranja build taskova za bilo koju od te tri stavke, potrebno je:

Storno (#106103/#106104) i eRačun top-seller put A (#106105) su VEĆ u toku (in_progress/open) jer su H-prioritet red-zone stavke koje CEO odluka od 20.07 direktno pokriva bez dodatnog spec-gate koraka — ali ostaju pod istim red-zone verifikacionim standardom (adversarial review + Proveo E2E prije done).

Povezani dokumenti

Vidi takoder istrazivanje drzavnih poticaja i uslova zaposljavanja za novi HR d.o.o. entitet (Smart Forge): Smart Forge (HR) — drzavni poticaji i zaposljavanje — istrazivanje 2026-07-21 (odvojena tema, isti book).

Smart Forge (HR) — državni poticaji i zapošljavanje — istraživanje 2026-07-21

1. Kontekst

Smart Forge = novi d.o.o. u registraciji (~25.07.2026), tech/SaaS (NKD J62 racunalno programiranje), vlasnistvo ALAI grupa/Alem. Ovo je novi hrvatski (HR) entitet za Bilko.

Sve nize je web-verifikovano 21.07.2026, izvori navedeni na dnu stranice (sekcija 6).

Agent verdikt izvornog istrazivanja: PASS (web-verifikovano 2026-07-21). Izvor: ~/system/evidence/hr-poticaji-smartforge/RESEARCH-2026-07-21.md

2. Programi — MOZEMO / NE MOZEMO

MOZEMO aplicirati / koristiti

#ProgramIznos / uslovNapomena
1HAMAG-BICRO Program akceleracije~3M EUR bespovratnih za inovativna poduzecaKANDIDAT #1 (Bilko = AI/SaaS inovacija). Pratiti rok.
2HAMAG-BICRO zajmovi za pocetnike25.001–100.000 EUR @ 0,5% fiksno, 10 god., pocek 12 mj.TRENUTNO ZATVOREN (utrosena sredstva) — pratiti novi krug (program do 2029). Uslov: vlasnik/suvlasnik zaposlen puno radno vrijeme u firmi (strukturna odluka!).
3Jamstva kroz banke (HAMAG + EU)do 300.000 EURNovi krug otvoren.
4Podrska digitalizaciji MSP-ovado 120.000 EUR (50–85% sufinanciranje)Sjediste mora biti NUTS2 Panonska/Sjeverna/Jadranska Hrvatska — GRAD ZAGREB ISKLJUCEN. Strateski: registracija Smart Forge van Zagreba (npr. Rijeka/Osijek gdje su pilot partneri Terra/Maricic, ili Dubrovnik — vidi sekciju 3) otvara ovaj poziv.
5Porezno automatski (bez posebne prijave)Novoosnovani d.o.o. NE placa akontacije poreza na dobit prvu godinu; porez na dobit 10% (<1M EUR prihoda); oslobodjenje doprinosa 1 god. za osobu koja se PRVI PUT zaposljava (neodredjeno); HZZ potpore za zaposljavanje/pripravnistvoAutomatsko pravo, nije natjecaj.

NE prolazimo (sada)

#ProgramIznos / uslovZasto ne prolazimo
6HZZ potpora za samozaposljavanjedo 15.000 + 5.000 digitalno/zeleno + 7.000 povratnici = do 27.000 EUR; rok 30.09.2026Trazi NEZAPOSLENU osobu u HZZ evidenciji koja osniva firmu. Alem nije u HZZ evidenciji. (Opcija bi postojala samo ako bi HR suosnivac iz HZZ evidencije osnivao — nije nasa postavka.)
7Digitalni vauceri 2026fond 3,92M EUR, otvaranje ~rujanMin 5 zaposlenih + pozitivna EBITDA — nova firma ne prolazi jos.
8Start-up/spin-off mladih istrazivaca66k–200k EURProzor prijava 15.04–15.06.2026 PROSAO; eventualno 2027.

Napomene o podobnosti

3. Dubrovnik / NUTS2 nalaz (dopuna, CEO pitanje 21.07)

Dubrovacko-neretvanska zupanija = NUTS2 JADRANSKA Hrvatska → KVALIFIKUJE se za MSP digitalizacijski poziv (do 120k EUR, 50–85%). Jedini iskljucen je Grad Zagreb.

4. Zaposljavanje — EGP drzavljani i Djani scenariji

Web-verifikovano (hzz.hr sluzbeno):

Djani scenario (dolazak iz Norveske, EGP pasos) — putevi

REDOSLIJED JE KRITICAN: boraviste → HZZ evidencija → tek onda zaposlenje/osnivanje. Pogresan redoslijed = gubitak prava.

ScenarioPutIznosi / uslovi
A) ZAPOSLENIK u Smart Forgeprijavi boraviste u HR → upis u HZZ evidenciju → Smart Forge ga zaposli uz HZZ potporu za zaposljavanje + moguca doprinos-olaksicaJednostavnije, manji iznosi.
B) SUVLASNIK/OSNIVAC koji se samozaposljavapotpora za samozaposljavanje (do 15k+5k digitalno/zeleno) + 7.000 EUR povratnik iz EGP; ISTOVREMENO zadovoljava HAMAG uslov "vlasnik/suvlasnik zaposlen puno radno vrijeme" za pocetnicki zajam 0,5%Potencijalno do ~27.000 EUR. [TREBA PROVJERA]: uvjeti vlasnickog udjela za potporu za samozaposljavanje + TIMING (osoba mora biti u HZZ evidenciji PRIJE osnivanja/zaposlenja — Smart Forge je vec u registraciji, mozda treba drugi subjekt ili naknadni ulazak — konsultovati HZZ/konzultanta PRIJE poteza).

5. [TREBA PROVJERA] — otvorene stavke

6. Izvori

Osnovno istrazivanje (fetch 2026-07-21):

Dopuna — Dubrovnik/NUTS2:

Dopuna — zaposljavanje stranaca (Djani scenario):

7. Preporuceni koraci

  1. ODLUKA PRIJE REGISTRACIJE: sjediste van Zagreba (Rijeka/Osijek ili Dubrovnik — svi rade jednako dobro za NUTS2 poziv) ako zelimo MSP digitalizacijski poziv do 120k EUR.
  2. NKD: J62.01 racunalno programiranje.
  3. Prijava na HAMAG mailing/pracenje: novi krug pocetnickih zajmova + Program akceleracije rok.
  4. Kod prvog zaposljavanja: iskoristiti oslobodjenje doprinosa (prvo zaposlenje) + HZZ potporu.
  5. Za vece bespovratne (2027): start-up istrazivaci prozor april–jun.
  6. Ako se razmatra Djani scenario: prije bilo kojeg poteza konsultovati HZZ/konzultanta oko TIMING-a (evidencija prije osnivanja/zaposlenja) i uvjeta vlasnickog udjela za scenario B.

Evidencija: ~/system/evidence/hr-poticaji-smartforge/RESEARCH-2026-07-21.md (ukljucuje dopunu 1 — Dubrovnik scenario, i dopunu 2 — zaposljavanje stranaca/Djani). Objavljeno: Skillforge, 2026-07-21.

Bilko Operational Runbook — Azure Container Apps

Bilko — Operational Runbook

Azure is canonical. Bilko runs on Azure Container Apps (swedencentral, resource group rg-bilko-demo, subscription 5b0b4d9b). The previous non-Azure environment this runbook described is dead — legacy billing was exhausted 2026-06-14 (MC #103633). Platform-specific legacy references have been removed below.

Deep architecture reference (source of truth for topology/diagrams): DEPLOY-MAP.md (root) Environment model: docs/infrastructure/ENVIRONMENTS.md Live CI/CD config: azure-pipelines.yml (root)

Version: 4.0 (Azure Container Apps era — B4 doc cleanup, MC #101088) Last updated: 2026-07-28 Supersedes: v3.1 (2026-04-30, pre-Azure era)


Table of Contents

  1. System Overview
  2. Pre-Deployment Checklist
  3. Deploying the API
  4. Deploying the Web Frontend
  5. Database Operations
  6. Rollback Procedures
  7. Database Server Operations (Azure PostgreSQL)
  8. Troubleshooting Guide
  9. Monitoring and Alerts
  10. Emergency Contacts and Escalation

1. System Overview

Architecture

Bilko is a Turborepo monorepo with two deployed apps, both running as Azure Container Apps:

Full topology diagram (Cloudflare edge → ACA → Azure PG) lives in DEPLOY-MAP.md — do not duplicate it here; that file is kept current on every infra change.

Apps in This Monorepo

App Path Port (dev) Deployed to (ACA app name)
Web apps/web/ 3000 bilko-web-demo / bilko-web-stage
API apps/api/ 8080 bilko-api-demo / bilko-api-stage
E2E apps/e2e/ CI only (Playwright, stage FQDNs)

Key Packages

Package Path Purpose
Database apps/api/src/main/resources/db/migration/ Flyway SQL migrations (canonical schema source)
UI packages/ui/ Shared UI components

CI/CD Pipeline

Single canonical pipeline: azure-pipelines.yml (root), Azure DevOps Bilko-CI-CD (definitionId=1), org alai-holding, project Bilko, git remote azdo. Triggers on push to main and tag v*. GitHub Actions is dead/abandoned — do not rely on .github/workflows/* for deploys.

Stages: CI_Gates (8 jobs) → BuildFlyway_MigrateDeploy_StageE2E_UATPromote_Demo (manual CEO approval). Full stage-by-stage detail: DEPLOY-MAP.md § CI/CD Pipeline.


2. Pre-Deployment Checklist

Run this before EVERY production (Promote_Demo) deployment:


3. Deploying the API

API source: apps/api/ (Kotlin + Ktor + Exposed) Deploy target: Azure Container App bilko-api-demo (+ bilko-api-stage) — resource group rg-bilko-demo, environment bilko-demo-env Build pipeline: azure-pipelines.yml Build stage — docker buildx linux/amd64 → Azure Container Registry Docker image: bilkodemo.azurecr.io/bilko-api:demo-{SHA} (production) / bilkodemo.azurecr.io/bilko-api:stage-{SHA} (stage)

3a. Automatic Deploy (Stage — on every main push)

Deploy_Stage runs automatically on the bilko-selfhosted pool (FORGE agent bilko-forge-1) after Build + Flyway_Migrate succeed. It updates bilko-api-stage and bilko-web-stage to the digest-pinned image from Build.

Monitor: az pipelines runs list --org https://dev.azure.com/alai-holding --project Bilko --pipeline-ids 1 --top 5 or the Azure DevOps pipeline UI.

3b. Promote to Production (CEO-gated manual approval)

Promote_Demo requires manual CEO approval in the ADO environment bilko-demo, then rebuilds demo-{SHA} images from the same commit and shifts bilko-api-demo ingress traffic 100% to the new revision (multiple-revisions mode, MC #105368).

# Trigger a manual pipeline run when Promote_Demo did not auto-open (BatchedCI can skip it)
az pipelines run --org https://dev.azure.com/alai-holding --project Bilko --branch main --id 1

# Approve via Azure DevOps UI (Pipelines > Environments > bilko-demo) once queued,
# or via API per CEO chat-authorization (see project memory for the API-approve pattern).

3c. Verify API Deployment

# Azure Container Apps status + FQDN
az containerapp list -g rg-bilko-demo \
  --query "[].{name:name, state:properties.provisioningState, fqdn:properties.configuration.ingress.fqdn}" \
  -o table

# Latest revision
az containerapp revision list -n bilko-api-demo -g rg-bilko-demo --query "[0].name" -o tsv

# Health check (brand domain, via CF Worker)
curl -s https://api.bilko.cloud/api/v1/health
curl -s https://api.bilko.io/api/v1/health
curl -s https://api.bilko.company/api/v1/health

# Direct ACA FQDN health check
curl -s https://bilko-api-demo.purplebeach-f004d490.swedencentral.azurecontainerapps.io/api/v1/health

3d. Required Secrets / Environment Variables (API)

Managed as Azure Container Apps secrets + env vars, sourced from Key Vault kv-bilko-demo2 (variable group bilko-kv-demo in ADO). ACA write-only secrets return NULL on read — always cross-check against Key Vault, never assume a value from a stale doc.

Secret / Var Description
db-password bilko_admin Azure PostgreSQL password
jwt-secret Access token signing secret
jwt-refresh-secret Refresh token signing secret
groq-api-key LLM provider key (API)
resend-api-key Transactional email provider key
field-encryption-key PII field-level encryption key
field-hmac-key PII field-level HMAC key
CORS_ORIGINS Allowed origins — see DEPLOY-MAP.md OCD-12 for the stage custom-domain gotcha

4. Deploying the Web Frontend

Frontend source: apps/web/ (Next.js 15) Deploy target: Azure Container App bilko-web-demo (+ bilko-web-stage) Build pipeline: Same azure-pipelines.yml Build stage — builds apps/web/ alongside the API in one pipeline run Production domains: app.bilko.io, app.bilko.cloud, app.bilko.company (routed through Cloudflare Worker bilko-edge-proxy, which rewrites Host/SNI to the ACA FQDN)

4a. Automatic Deploy (Stage) / 4b. Promote to Production

Same pipeline and same manual-approval gate as the API (§3a/§3b) — web and API are built and promoted together from one Azure DevOps run.

4c. Required Environment Variables (Web)

Managed via ACA env vars on bilko-web-demo / bilko-web-stage.

Variable Description
NEXT_PUBLIC_API_URL API base URL — currently baked at build time (see DEPLOY-MAP.md OCD-9 for the runtime-URL follow-up)
NEXT_PUBLIC_APP_ENV production or staging

4d. Verify Web Deployment

az containerapp revision list -n bilko-web-demo -g rg-bilko-demo --query "[0].name" -o tsv

curl -sI https://app.bilko.cloud/ | head -1
curl -sI https://app.bilko.io/ | head -1
curl -sI https://app.bilko.company/ | head -1

5. Database Operations

Migration tool: Flyway 11 (NOT Prisma — Prisma was tied to the deleted Express backend). Migration files live in apps/api/src/main/resources/db/migration/V*.sql.

5a. Running Migrations

Migrations run automatically in the Flyway_Migrate pipeline stage against bilko-demo-pg on every main push, before Deploy_Stage.

Manual run (rare — CI is the default path):

docker run --rm \
  -v "$(pwd)/apps/api/src/main/resources/db/migration:/flyway/sql" \
  flyway/flyway:11 \
  -url="jdbc:postgresql://bilko-demo-pg.postgres.database.azure.com:5432/bilko?sslmode=require" \
  -user=bilko_admin \
  -password="$DB_PASSWORD" \
  migrate

RULE: Never edit an existing V*.sql migration. Always create a new V{N+1}__description.sql. Reserve the version number carefully — two independent branches taking the same V number is a recurring collision (see technical_bilko_promote_manual_and_flyway_collisions_2026-07-20.md); check git ls-tree azdo/main -- apps/api/src/main/resources/db/migration/ before picking a number.

5b. Checking Migration Status

psql "host=bilko-demo-pg.postgres.database.azure.com port=5432 dbname=bilko user=bilko_admin sslmode=require" \
  -c "SELECT version, description, success FROM flyway_schema_history ORDER BY installed_rank DESC LIMIT 20;"

All rows should show success = t.

5c. Rolling Back a Migration

Flyway (community edition, as used here) does not support automatic migration rollback.

Option 1 (preferred): Write a new compensating migration that undoes the change (new V{N+1} file with inverse DDL/DML).

Option 2 (last resort): Restore from an Azure PostgreSQL backup (§7f). Data written after the migration will be lost.

5d. Database Backup and Restore

Azure PostgreSQL flexible server provides automatic backups (default retention — verify current setting via az postgres flexible-server show). For an on-demand logical backup:

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

Restore:

pg_restore --dbname="host=... user=bilko_admin sslmode=require" \
  --no-acl --no-owner \
  bilko-backup-YYYYMMDD-HHMMSS.dump

6. Rollback Procedures

6a. API Rollback (Azure Container Apps)

# List revisions, newest first
az containerapp revision list --name bilko-api-demo --resource-group rg-bilko-demo \
  --query "[].{name:name,created:properties.createdTime,active:properties.active}" -o table

# Shift 100% traffic to the last known-good revision
az containerapp ingress traffic set --name bilko-api-demo --resource-group rg-bilko-demo \
  --revision-weight <previous-revision-name>=100

6b. Web Rollback (Azure Container Apps)

az containerapp revision list --name bilko-web-demo --resource-group rg-bilko-demo \
  --query "[].{name:name,created:properties.createdTime,active:properties.active}" -o table

az containerapp ingress traffic set --name bilko-web-demo --resource-group rg-bilko-demo \
  --revision-weight <previous-revision-name>=100

6c. Edge Rollback (Cloudflare)

# Worker (bilko-edge-proxy) rollback
cd apps/edge-proxy && wrangler rollback

# CF Pages (marketing landings) — via dashboard:
# Pages > project (bilko-io / bilko-cloud / bilko-company) > Deployments > "Rollback to this deployment"

6d. Database Rollback

IMPORTANT: Database rollbacks are destructive if data was written after the migration ran. Always confirm with the team before rolling back the database.

Steps:

  1. Take a current-state backup first (§5d)
  2. Write a compensating migration (preferred over restore — see §5c Option 1)
  3. Only restore from backup if data loss is acceptable and a pre-migration backup exists

7. Database Server Operations (Azure PostgreSQL)

Database: Azure PostgreSQL flexible server bilko-demo-pg, B1ms, PostgreSQL 16, region swedencentral, resource group rg-bilko-demo Schema source of truth: Flyway migrations, apps/api/src/main/resources/db/migration/ App user: bilko_admin (NOT bilko) — password in ACA secret db-password / Key Vault kv-bilko-demo2 Shared instance: stage and demo currently share this one server (see DEPLOY-MAP.md OCD-3 for the isolation tradeoff)

7a. Connecting (Local / Admin)

psql "host=bilko-demo-pg.postgres.database.azure.com port=5432 dbname=bilko user=bilko_admin sslmode=require"

Firewall: FORGE's public IP is allow-listed (rule forge-ci-runner); Microsoft-hosted CI agents use the allow-azure-services rule. Connecting from an unlisted IP will time out — add a temporary firewall rule via az postgres flexible-server firewall-rule create rather than widening allow-azure-services.

7b. Running the RLS Audit

# Via test suite (preferred)
./gradlew :apps:api:test --tests "*RlsAudit*"

Tables that MUST have RLS enabled include (non-exhaustive — see the audit test for the authoritative list): organizations, users, invoices, invoice_items, expenses, transactions, bank_accounts, bank_transactions, contacts, logged_actions.

7c. Verifying RLS Policies in PostgreSQL

-- List all RLS policies
SELECT schemaname, tablename, policyname, cmd, qual
FROM pg_policies
ORDER BY tablename, policyname;

-- Check which tables have RLS enabled
SELECT relname AS table_name, relrowsecurity AS rls_enabled
FROM pg_class
WHERE relnamespace = 'public'::regnamespace AND relkind = 'r'
ORDER BY relname;

7d. Enabling RLS on a Table

ALTER TABLE public.table_name ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.table_name FORCE ROW LEVEL SECURITY;

-- Example org-scoped policy
CREATE POLICY org_isolation ON public.table_name
  USING (organization_id = current_setting('app.current_org_id')::uuid);

7e. Backup and Restore (Azure PostgreSQL server-level)

# Server status and current config
az postgres flexible-server show -g rg-bilko-demo -n bilko-demo-pg \
  --query "{name:name, state:state, version:version, location:location}" -o table

# Point-in-time restore to a new server (does NOT overwrite the source — verify before promoting)
az postgres flexible-server restore \
  --resource-group rg-bilko-demo \
  --name bilko-demo-pg-restored \
  --source-server bilko-demo-pg \
  --restore-time "2026-07-28T00:00:00Z"

Also see the logical pg_dump/pg_restore path in §5d for a single-database, portable backup.


8. Troubleshooting Guide

API Down

Symptoms: GET /api/v1/health returns non-200 or times out.

Steps:

  1. Check Azure status for the region: az containerapp show -n bilko-api-demo -g rg-bilko-demo --query "properties.provisioningState"
  2. Check container logs: az containerapp logs show -n bilko-api-demo -g rg-bilko-demo --tail 100
  3. Check recent revisions for a bad deploy: az containerapp revision list -n bilko-api-demo -g rg-bilko-demo -o table
  4. If a bad revision is live, roll back (§6a)
  5. Check the Cloudflare Worker (bilko-edge-proxy) isn't misrouting — curl the direct ACA FQDN to isolate edge vs. origin
  6. If not resolved in 10 minutes: rollback (§6) and open a P1 per §10

High Error Rate

Symptoms: More than 5% of API requests returning 5xx errors.

Steps:

  1. az containerapp logs show -n bilko-api-demo -g rg-bilko-demo --tail 200 — look for error patterns
  2. Check Application Insights / Azure Monitor (if wired for this service) for new error groups
  3. Health check: curl https://api.bilko.cloud/api/v1/health
  4. Check if a Flyway migration ran recently — look for schema mismatches in the logs
  5. Check for too many connections errors (see §8 DB Connection Pool Exhausted below)
  6. Restart the revision if connection-pool errors persist: az containerapp revision restart -n bilko-api-demo -g rg-bilko-demo --revision <name>

Database Unreachable

Symptoms: Health endpoint shows "database": "error", connection errors in logs.

Steps:

  1. az postgres flexible-server show -g rg-bilko-demo -n bilko-demo-pg --query "state"
  2. Verify the db-password secret / connection string is current: az containerapp secret list -n bilko-api-demo -g rg-bilko-demo (values are NULL by design — cross-check against Key Vault kv-bilko-demo2, not against this doc)
  3. Test connection directly: psql "host=bilko-demo-pg.postgres.database.azure.com ... sslmode=require" -c "SELECT 1;"
  4. Check firewall rules haven't been tightened/removed: az postgres flexible-server firewall-rule list -g rg-bilko-demo -n bilko-demo-pg -o table
  5. Check connection pool exhaustion (below)

DB Connection Pool Exhausted

Symptoms: too many connections errors, requests queuing indefinitely.

Steps:

  1. Check current connections:
    SELECT count(*), state FROM pg_stat_activity GROUP BY state;
    
  2. Find long-running queries:
    SELECT pid, now() - query_start AS duration, query
    FROM pg_stat_activity
    WHERE state = 'active' AND now() - query_start > interval '30 seconds';
    
  3. Kill a stuck query if safe:
    SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE pid = target_pid;
    
  4. Restart the API revision to release the connection pool (az containerapp revision restart)
  5. Consider PgBouncer / connection-pooling middleware if this recurs under load

Slow Responses

Symptoms: p95 response time above 2 seconds.

Steps:

  1. Check for slow queries:
    SELECT query, calls, mean_exec_time
    FROM pg_stat_statements
    ORDER BY mean_exec_time DESC LIMIT 20;
    
  2. Check for missing indexes on frequently filtered columns
  3. Look for N+1 query patterns in Exposed usage
  4. Use EXPLAIN ANALYZE on suspect queries

SSL Certificate Issues

echo | openssl s_client -connect api.bilko.cloud:443 2>/dev/null | openssl x509 -noout -dates

Deployment Fails in CI

Common causes:

Failure Fix
CI_Gates lint/TSC fail Fix and push — see which of the 8 jobs failed in the ADO run
Trivy HIGH/CRITICAL Update the dependency or image base, re-run
Flyway_Migrate fails Bad SQL or a version-number collision (see §5a) — never edit an applied migration
Deploy_Stage health check fails New revision not passing /api/v1/health or / within the 20s wait — check logs
E2E_UAT skips Usually a stale BILKO_E2E_TOKEN_SECRET in bilko-kv-demo — see DEPLOY-MAP.md OCD-1

9. Monitoring and Alerts

See monitoring/ directory:

Quick health commands:

# Production API (brand domains)
curl -s https://api.bilko.cloud/api/v1/health
curl -s https://api.bilko.io/api/v1/health
curl -s https://api.bilko.company/api/v1/health

# Production Web
curl -s -o /dev/null -w "HTTP %{http_code} in %{time_total}s\n" https://app.bilko.cloud

# Stage
curl -s https://api-stage.bilko.cloud/api/v1/health
curl -sI https://web-stage.bilko.cloud/ | head -1

Log locations:

Component Log location
API (ACA) az containerapp logs show -n bilko-api-demo -g rg-bilko-demo
Web (ACA) az containerapp logs show -n bilko-web-demo -g rg-bilko-demo
DB (Azure PG) Azure Portal > bilko-demo-pg > Monitoring > Logs, or pg_stat_activity/pg_stat_statements
CI/CD Azure DevOps > Bilko-CI-CD pipeline > run > stage logs
Edge (CF Worker) wrangler tail against bilko-edge-proxy, or Cloudflare dashboard

10. Emergency Contacts and Escalation

Role Contact Method
On-call / CEO Alem Basic (alem@alai.no) Direct / Slack
Azure support portal.azure.com support Web
Azure DevOps dev.azure.com/alai-holding Web
Cloudflare support dash.cloudflare.com support Web

Escalation path:


Last updated: 2026-07-28 — B4 doc cleanup pass (MC #101088): rewritten for Azure Container Apps (all legacy platform and ORM references removed; content grounded against DEPLOY-MAP.md and apps/api/src/main/resources/db/migration/).

Bilko main — scheduled validation backstop (MC #106809, 2026-08-05)

Bilko main — scheduled validation backstop (MC #106809)

Datum: 2026-08-05 · Builder: FlowForge · Merge: PR #317 (John, zeleni build #941) · Nezavisna verifikacija: 8/8 PASS (peer-verdict u evidence)

Problem

Push-triggered batchedCI buildovi na Bilko main otkazivali su jedni druge u lancu (org ima 1 self-hosted parallel slot — kontencija, ne bug u batch:true): main danima bez završenog runa (zadnji zeleni 2026-08-03 16:48), tri Gate-0 merža nikad post-merge validirana.

Rješenje (minimalno, 2 aditivne izmjene u azure-pipelines.yml)

  1. schedules: cron 0 */3 * * * (samo main, always: false — vrti se samo ako ima novih commita).
  2. Jedna linija u Build stage uslovu: eq(variables['Build.Reason'], 'Schedule') — scheduled run izvršava PUN lanac.

Scheduled trigger klasa nije podložna push-vs-push batch otkazivanju → u mirnijem prozoru run završi do kraja. batch:true i pr.autoCancel netaknuti (kelsey dissent iz forge 106804 D3 ispoštovan).

Dokaz

Napomene

Bilko /login silent auth failure — dijagnoza i fix (MC #106849)

Bilko /login silent auth failure — dijagnoza i fix (MC #106849)

Datum: 2026-08-05 · PR: #318 (fix/106849-login-dead-click, commit 3df83cd1) · Status: no active outage; hardening + observability fix

Simptom

Klik na "Prijavite se Microsoft računom" na app.bilko.cloud/login naizgled "ne radi ništa" — korisnik ostane na /login bez ikakve poruke, bez konzolnih grešaka, bez vidljivih network poziva.

Root cause (potvrđeno live forenzikom)

  1. Frontend je bio ISPRAVAN: React onClick → MSAL loginRedirect() → top-level navigacija na ciamlogin.com/.../authorize se uredno izvršavala.
  2. Microsoft CIAM authorize je za sesiju s postojećim kolačićima vraćao HTTP 503 (čist curl bez kolačića: 200) — vrlo vjerovatno throttling zbog desetina automatizovanih pokušaja u kratkom periodu.
  3. Aplikacija je taj neuspjeh tiho gutala: povratak na /login bez poruke (503 prije ikakvog MSAL callbacka ⇒ MSAL nema event koji bi opalio).
  4. Dijagnostička zamka: top-level navigacije se ne vide kao XHR u network alatima i ne ostavljaju konzolne greške; uz to, sintetički klikovi browser-ekstenzije nisu ni stizali do stranice (automation artefakt) — vidi memory technical_extension_clicks_can_be_silent_noops_2026-08-05.

Fix (D3 — error surfacing)

Regression gate (D4)

azure-pipelines.ymlPromote_Demo sada, odmah poslije curl-only ZAKON PI2 provjere, izvršava real-browser MSAL login smoke (postojeći login-dashboard.spec.ts) protiv demo artefakta (DEMO_WEB_URL=https://app.bilko.cloud). Zatvoren strukturni gap: stage i demo su odvojeni buildovi, a login test je do sada postojao samo za stage.

Verifikacija

Lekcije

Povezani taskovi: MC #106849 (fix), #106834 (automation artefakt), #106850 (anvil incident), #106856 (subagent identity), #106858 (msal-provider minor).

bilko.cloud landing — honest claims sweep (MC #106980, 2026-08-08)

bilko.cloud landing — honest claims sweep (MC #106980)

Datum: 2026-08-08 · Status: LIVE na produkciji · PR: 331 (azdo, fix/106980-landing-claims → main) · Commit: 149bb1a0

Šta je promijenjeno i zašto

Izvor: ~/system/evidence/bilko-fiken-gap/LANDING-NALAZI.md (07.08) — landing je reklamirao zakonski obaveznu funkciju kao "beta". CEO nalog 08.08: ukloniti sve netačne/podcjenjujuće tvrdnje.

Stavka Prije Poslije
Hero eRačun "FISK 2.0/eRačun priprema dostupna u beta fazi" "eRačun (FISK 2.0) podrška je integrirana prema HR CIUS 2025 standardima — produkcijsko slanje je u fazi aktivacije" (CEO odabrao varijantu V1, 2026-08-08)
Cjenovne kartice CTA /demo?country=HR ("Pogledaj demo") /register?plan=trial ("Isprobaj besplatno") — 2×
eRačun tag na karticama "(beta — uskoro u produkciji)" "(u aktivaciji)"
PSD2 auto-sync tag "(beta — uskoro u produkciji)" — funkcija ne postoji "(u pripremi)" — bez datuma i obećanja
CSV bankovni uvoz istaknut kao feature (reklamiranje slabosti) "Uvoz bankovnih izvoda", neutralno
JSON-LD / FAQ / stat badge "beta fazi" na 7 mjesta 0; product-level softwareVersion: beta namjerno zadržan (beta program stvarno traje)
schema.org addressCountry: NO uz Zagreb HR
Badge "FISK 2.0 u razvoju" "FISK 2.0 u aktivaciji"

Model istine (granice tvrdnji)

Deploy i verifikacija

Evidence

~/system/evidence/106980/ — builder-evidence.md, HERO-VARIJANTE.md, apex-verify-20260808.txt, browser-verification.json, browser-screenshots/, native-peer-verify-transcript.md, pr-created-20260808.json.

Napomene za budućnost

Bilko Feature — Document Inbox / Ulaz dokumenata (MC #104515)

Bilko — Document Inbox / Ulaz dokumenata (MC #104515)

Status: BUILT AND LIVE on azdo/main (verified 2026-08-08 by reading apps/api and apps/web at the tip of azdo/main, commit 88769328). This page documents the actual shipped implementation — not a plan. The original MC #104515 gap audit (2026-06-29) flagged Document Inbox as "MISSING"; it shipped afterward across three merges:

MC What shipped Merge
#104515 (Phase 1) Capture-first upload → pending review queue → book as expense / reject PR 30, fee37dc8
#104519 Proveo route-layer test coverage for Phase 1 same PR
#105687 Third terminal status archived — permanent document archive (/dokumenti) for documents that never become an expense e2ee2a4a
#106195 UX redesign of the inbox/ulazni-racuni screens 88769328

1. Concept

Two distinct product surfaces share one table (inbox_items):

This is deliberately distinct from ExpenseDocuments (V40), which is attach-to-an-existing-expense, and from ReceivedEInvoices (V139), which is the Storecove e-invoice webhook capture (has OIB/UBL fields, no OCR).

2. Schema

Table inbox_items, defined in V106__document_inbox.sql (Phase 1) and widened by V122__inbox_archive_documents.sql (archive feature). Kotlin Exposed object: InboxItems in apps/api/src/main/kotlin/no/alai/bilko/models/Tables.kt.

V106 — base table:

V122 — archive extension (adds the archived outcome, MC #105687):

3. Routes

Defined in apps/api/src/main/kotlin/no/alai/bilko/routes/InboxRoutes.kt, wired in Routing.kt via inboxRoutes() and documentsRoutes(), service layer InboxService.kt (DI singleton in DI.kt).

inboxRoutes() — mounted under /inbox:

Method Path Purpose
GET /inbox/count Pending badge count (dashboard bell + sidebar) — registered before /{id} to avoid Ktor trie ambiguity
GET /inbox Paginated list; query params status, page, perPage
POST /inbox Multipart upload → creates a pending item
GET /inbox/{id} Detail for the review screen
POST /inbox/{id}/book Creates an Expense in the same transaction, transitions row to booked, best-effort attaches the original scan to the new expense via ExpenseService.attachDocument
POST /inbox/{id}/reject Transitions to rejected, optional { "reason": string }
POST /inbox/{id}/archive Transitions to archived (V122); body: documentType (required), contactId (optional), tags (optional)

documentsRoutes() — mounted under /documents (backs the /dokumenti screen, MC #105687):

Method Path Purpose
GET /documents Paginated, filterable list of archived items (documentType, contactId, tag, dateFrom, dateTo)
POST /documents/{id}/link-expense Late-link an already-archived document to an Expense created separately (body: { "expenseId": string })

Upload security pipeline (POST /inbox, in order): permission check before multipart parse → UploadSecurityGate.authorizeActor (tenant-bound actor check) → MIME allowlist (application/pdf, image/jpeg, image/jpg, image/png) → 20 MB hard cap → empty-file guard → the client-declared Content-Type header check here is a cheap early rejection only (attacker-controlled) — the authoritative control is magic-byte content sniffing + ClamAV malware scan + persisted quarantine/scan-provenance state machine at the shared ReceiptService.uploadObject choke point (UploadSecurityGate, MC #106852 G1-02), which also gates expense-attach, invoice-receipt, and support-ticket-attachment uploads. The route additionally hard-fails closed (compensates/deletes the stored object) if scan provenance (scanAttemptId, scanState == "RELEASED", scanEngine, scanEngineVersion, scannedAt) is incomplete after upload — an inbox row is never created for an object without a verified clean-scan verdict.

4. RBAC

Role hierarchy (RbacHelper.kt): viewer (0) < accountant (1) < admin (2) < owner (3). Permission catalog + role grants seeded in V67__rbac_permissions_catalog.sql.

Route Permission Roles that hold it
GET /inbox/count, GET /inbox, GET /inbox/{id}, GET /documents expense:read viewer, accountant, admin, owner
POST /inbox (upload), POST /inbox/{id}/book expense:create accountant, admin, owner
POST /inbox/{id}/reject, POST /inbox/{id}/archive, POST /documents/{id}/link-expense expense:categorize accountant, admin, owner

No new permission keys were introduced for Document Inbox — it reuses the existing expense:* catalog, treating booking/rejecting/archiving as expense-adjacent classification actions. viewer role can browse the inbox and archive but cannot upload, book, reject, or archive.

5. Frontend

6. i18n

Sidebar labels are localized via apps/web/messages/{bs,en,hr,sr-Cyrl,sr-Latn}.json, navigation namespace:

Gap found during this review: the inbox/dokumenti page bodies (inbox/page.tsx, inbox/[id]/page.tsx, dokumenti/page.tsx) do not call useTranslations/t(...) — grep for both found zero matches. All in-page copy (labels, buttons, empty states) is hardcoded Bosnian/Croatian JSX, not routed through next-intl. Only the sidebar navigation label is translated. This is a real gap, not a design choice documented anywhere in the code — flagging it here rather than in the "OCR hooks" section since it's a currently-live inconsistency, not deferred work.

7. Phase 2 — OCR hooks (deferred, not built)

Both migration headers and the Tables.kt block comment explicitly scope OCR to Phase 2, deferred:

"Phase 2 (OCR via Azure Document Intelligence): extracted_* columns are nullable and reserved for Phase 2 population. Phase 1 build leaves them NULL."

What exists today as the OCR integration point:

Implication for a future Phase 2 build: the schema already has the landing spot for OCR output; the work is a new async job (upload → queue → Azure Document Intelligence call → populate extracted_*/ocr_confidence → surface a "confirm extracted values" step in the /inbox/{id} review screen before booking). No API contract for that job exists yet.

8. Verification method

All facts on this page were read directly from azdo/main (Bilko repo, ~/business/ALAI-Holding-AS/products/Bilko) at commit 88769328 (2026-08-08), not from prior planning docs or memory:

9. Cross-references

MC #900178 (2026-08-24): PROD BUG — upload u Ulaz dokumenata pada za sve korisnike: apiFetch šalje Content-Type: application/json s FormData body, server multipart ruta odbija. Fix: JSON header default samo za string/prazan body. RCA: evidence/incident-inbox-upload-20260823/RCA.md; forged prompt 900178.md.

bilko.cloud landing truth and CTA remediation — MC #106174/#107324 — 2026-08-18

bilko.cloud landing truth and CTA remediation — MC #106174/#107324

Date: 2026-08-18 · Status: candidate branch validated; NOT merged, NOT deployed, NOT live

Root cause

The earlier honest-claims corrections existed on commits 149bb1a0 and d0f94baa but were never merged into canonical Azure DevOps main. MC #107298 later hardened and deployed the runtime-only landing tree from main, which still carried stale beta, PSD2, trial and pricing copy. Browser and byte-hash evidence proved live bilko.cloud was identical to green main 390bbb21.

Candidate

What changed

Verification

Evidence

Durable evidence is stored under the local MC 106174 evidence root in the live-baseline, builder, finverge-review-round2 and proveo-round3 directories. The forged prompt is stored under the local forged-prompts registry for MC 106174.

Release boundary

This page documents a verified candidate. Production remains on stale main copy until branch push, Azure PR validation, CEO preview approval, merge and a separately verified Cloudflare Pages deploy. Do not describe this work as live before those steps.