UAT Phase 1 Findings

UAT Phase 1 Findings

MC: #10487
Date: 2026-05-02
Stage web: https://bilko-web-stage-dh4m46blja-lz.a.run.app
Stage API: https://bilko-api-stage-dh4m46blja-lz.a.run.app
Verdict: ACCEPT-WITH-FOLLOWUP


Methodology

Three-agent pure UAT discovery on Bilko stage environment (post-Kotlin migration, pre-Express deletion). No build work — observational only.

Team:


Top P0 Findings (Blocking Go-Live)

1. Registration Fails — Dual Bug

Root causes:

Impact: Zero users can register. Product completely inaccessible to new users.

Evidence:

curl -X POST https://bilko-api-stage-dh4m46blja-lz.a.run.app/api/v1/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email":"test@test.ba","password":"test1234","fullName":"Test User","orgName":"Test DOO","country":"BA","baseCurrency":"BAM"}'

→ HTTP 500: {"error":"PSQLException: column \"role\" is of type \"UserRole\" 
   but expression is of type character varying","code":"INTERNAL_ERROR"}

Followup: MC #10494


2. Invoice Email Send — UI Only, No Dispatch

What: Invoice wizard Step 6 shows email compose fields (To, Subject, Message, "Send me a copy"). InvoiceService.sendInvoice() changes status to "sent" but does NOT send email. The emailData state is never passed to API.

Impact: Customer never receives invoice. User believes invoice was sent. Creates chargeback disputes and relationship damage when client claims "nisam dobio nista."

Evidence: apps/api/src/main/kotlin/no/alai/bilko/services/InvoiceService.kt — no email service injection in DI container. sendInvoice() returns success without SMTP call.

Followup: MC #10495 (Sprint 1 P0)


3. Dead UI Buttons — Receipt Scan + Attach

What:

Impact: The mobile expense entry feature — marketed as a selling point — is non-functional. Field users will abandon immediately. RS/BA tax law requires receipt documentation for deductible expenses; without attachment capability, Bilko cannot support compliance.

Evidence:

Followup: MC #10495 (Sprint 1 P0)


4. Bank CSV Format Incompatibility

What: CSV import expects exact format: date,description,amount,reference (ISO 8601 date, comma-separated). Real Bosnian/Serbian banks export semicolon-separated with local date formats: Raiffeisen BA (DD.MM.YYYY), UniCredit RS (local thousand separators), Intesa RS (multi-line headers).

Impact: User exports from Raiffeisen online banking, uploads CSV, gets 0 imported rows. Feature reads as broken. Bank reconciliation is effectively non-functional for Balkan users.

Evidence: apps/api/src/main/kotlin/no/alai/bilko/services/BankingService.kt:303-354 — generic parser, no named bank format presets

Followup: MC #10496 (Sprint 2)


Mobile UX Score: 5/10

Why not lower: Clean UI, readable fonts, BS/SR/HR localization present, adequate color contrast.

Why not higher:


Localization Status

Good news: BS, SR-Latn, SR-Cyrl, HR, EN all have complete translation files. Default sr-Latn. Language switcher (globe icon) present.

Issues:


Friction Summary

Severity Count
P0 (blocks all use) 2
P1 (core workflow broken) 4
P2 (significant friction/trust damage) 6
P3 (polish/consistency) 4

Full list: 16 friction points documented in /tmp/bilko-uat-ux-10487.md


Missing Features (vs SMB Expectations)

  1. No email delivery of invoices (Step 6 UI is cosmetic)
  2. No OCR receipt scanning (button exists, zero capability)
  3. No Bosnian/Serbian bank CSV format support
  4. No SEF integration UI for new users (Settings "Integracije" present but no SEF config screen)
  5. No registration completion flow (blocked by P0 bugs)
  6. No offline/draft persistence (wizard loses data on refresh or connectivity loss)
  7. No payment link/QR code on invoice
  8. No onboarding validation (flow exists, untestable due to P0 block)
  9. No mobile-optimized dashboard (charts overflow/compress at 390px)
  10. No forgot-password email confirmation (shows "Provjerite vas email" but no evidence of dispatch)

Architecture Findings (petter-graff)

Backend Disambiguation: KOTLIN/KTOR

Evidence:

Conclusion: Stage is confirmed Kotlin. Express docs are stale.


User Stories Implementation Matrix

15 stories tracked (not 17 — task brief was imprecise). IDs: US-001..004, US-010..012, US-020, US-030..031, US-040, US-050, US-060..061, US-070.

Summary:

P0 gaps (3):

P1 gaps (4):


Schema Reality (Prisma vs Kotlin Exposed vs Flyway)

Model count drift:

Specific drifts:

Item Prisma Kotlin Exposed Flyway Gap
pausal_rates PausalRate model No Exposed Table (data class only) V4 GAP: Kotlin may use raw SQL
archive_jobs ArchiveJob model Comment: "Round 2" V4 GAP: ArchiveService uses in-memory, DB table unused
sefDocumentId, sefAcceptedAt Present in Prisma Invoice Deferred to "Round 2" in Kotlin Not in Flyway DRIFT: Prisma has fields, Flyway/Kotlin defer
Organization address Absent Comment: "Round 2" Absent Missing from all three

Production Readiness Gaps (12 observational)

  1. No CI pipeline for Kotlin API (Cloud Build deploys web only)
  2. DB public IP, no SSL/IAM (TD-3, MC #10241 blocker)
  3. Compliance endpoints auth-gated (/pausal/rates returns 401 — should be public)
  4. No Prometheus metrics endpoint (Kotlin has no /metrics route)
  5. No automated overdue invoice scheduler
  6. Serbian CoA seeding absent from registration
  7. No SLOs defined or measured
  8. Rollback runbook references Vercel (wrong platform)
  9. Email verification not implemented
  10. SEF stub in production path (legal compliance risk)
  11. X-Powered-By header absent (consistency gap, not security issue)
  12. Invite acceptance endpoint missing

Top 5 Architectural Risks

  1. Split-backend contract drift with zero CI enforcement — Stage Kotlin, prod Express (or nothing). Web pipeline only. Contract drift invisible.
  2. SEF stub in production — Serbian e-invoicing law mandates real SEF submission. Stub invoices = legally non-compliant.
  3. Public IP database, no IAM authpostgres-socket-factory absent, no SSL enforcement (TD-2/TD-3)
  4. No automated schema validation — Prisma/Exposed/Flyway independently maintained; confirmed drifts exist
  5. Compliance public endpoint regression — Pausal calculator returns 401, blocks landing-page GTM feature

AC1 Followup: angie-jones Functional Smoke

Status: INCOMPLETE
Evidence: /tmp/bilko-uat-bugs-10487.json = 2 bytes ({})
Expected: Structured JSON with ≥8 epic entries, each with screenshots + HAR + repro steps

Followup: MC #10500 (re-run with HAR/screenshots for 8 epics AFTER Sprint 0 lands so login works)


References

Source MCs:

Evidence files:

Bilko repo: https://github.com/johnatbasicas/bilko


Revision #2
Created 2026-05-02 12:30:57 UTC by John
Updated 2026-06-07 20:00:57 UTC by John