Skip to main content

ADR-020 — Canonical Backend Location (apps/api, Deprecate apps/api-kotlin) + Phase 1 Track A Supersession

ADR-020: Canonical Backend is backend/ — Deprecate apps/api-kotlin/

Status: Accepted Date: 2026-04-28 Author: ALAI, 2026 Related: ADR-009 (superseded), ADR-011, ADR-015, ADR-016, ADR-017, ADR-018, ADR-019

MAJOR PATH UPDATE (2026-04-29): backend/apps/api/ (canonical Kotlin/Ktor location now). apps/api-legacy/.archive/api-legacy/. The deprecation of api-kotlin/ in this ADR was executed in MC #10034 (deleted as apps/api-kotlin-abandoned). See ADR-021.


Context

The Dual-Backend Incident

As of 2026-04-27, the Bilko repository contained two parallel, independent Kotlin/Ktor backends serving identical purposes — an architectural anomaly discovered during forensic audit MC #9892.

Timeline — git-verified (SHA + date + author):

SHA Date Author Event
5f97eff 2026-03-04 John AI Earliest backup commit — backend/ fully present with build.gradle.kts, package no.alai.bilko, Kotlin 2.3.0 / Ktor 3.4.0 / JVM 25
e23ade3 2026-03-19 Makinja Security headers plugin and security audit added to backend/
6b76981 2026-04-10 Makinja CI fixes applied to backend/
6c71a79 2026-04-14 Makinja apps/api-kotlin/ created — "Complete Kotlin/Ktor backend — Auth, Invoices, Clients, Expenses, Health" — package io.bilko, Kotlin 2.1.20 / Ktor 3.1.2 / JVM 21
f66ddec 2026-04-14 Makinja GCP Terraform + CI added (lands in both directories)
ee27c6b 2026-04-15 Makinja apps/api-kotlin/ scaffold finalised — 10 feature modules, 17 source files, titled "migration scaffold"

Result: backend/ was first on 2026-03-04 (6 weeks before apps/api-kotlin/). A generic builder agent (dispatched without Mehanik gate clearance) created apps/api-kotlin/ on 2026-04-14 while backend/ was already the active implementation. From 2026-04-15 onward, apps/api-kotlin/ received no further commits. backend/ continued active development with commits through 2026-04-23.

Background — Why This Happened

CEO decision (2026-03-17, ALAI/CLAUDE.md) mandated migration of all products from Express/TS to Kotlin/Ktor. Migration task MC #5125 ("Bilko backend: Express/TS → Kotlin/Ktor") was created but not formally unblocked. The Phase 1 Track A execution document (docs/bookstack-sync/phase1-track-a-execution.md, lines 241 and 299) designated apps/api-kotlin/ as the FUTURE migration target — explicitly stating:

"Do NOT start Track B until MC #5125 unblocks. All Track B work goes into apps/api-kotlin/."

However, a generic builder agent was dispatched into apps/api-kotlin/ on 2026-04-14 before MC #5125 was formally unblocked and before Mehanik clearance was obtained. This created an unauthorized parallel scaffold while the actual canonical domain implementation continued to grow in backend/.

State at Time of Discovery (MC #9892, 2026-04-27)

backend/:

  • Package: no.alai.bilko
  • Kotlin 2.3.0 / Ktor 3.4.0 / JVM 25
  • 51 .kt source files + 3 test files
  • Full domain: HR-FISK, SEF, EInvoice, AdapterException (14 codes), Koin DI, Redis, Apache PDFBox, Sentry, EmailService, SecurityHeaders, CORS, RateLimit
  • All ADR-015 through ADR-019 reference no.alai.bilko paths inside backend/
  • Last commit: 2026-04-23 (John, active)

apps/api-kotlin/:

  • Package: io.bilko
  • Kotlin 2.1.20 / Ktor 3.1.2 / JVM 21
  • 17 .kt source files + 3 test files
  • Skeleton only: Auth, DB tables, feature route scaffolds
  • Missing: HR-FISK, SEF, EInvoice adapter interface, AdapterException, Koin DI, Redis, PDFBox, Sentry, EmailService, RateLimit, CORS
  • Last commit: 2026-04-15 ee27c6b (Makinja, 13 days stale at discovery)
  • NOT deployed (confirmed by docs/evidence/9386/verification.json line 36 and docs/evidence/9398/verify-cookie-fix.js line 76)
  • NOT referenced in any architecture document

Prior Audit Gap

The preliminary architecture audit (2026-04-27) saw apps/api-kotlin/ in the directory listing and noted: "need to confirm these are indeed empty/removed" — but did not follow through with a find verification. The tool-first discipline (ZAKON NULA) required explicit verification before any assumption about directory contents. The audit concluded without detecting the 17 active Kotlin files, full auth module, and complete Gradle build in apps/api-kotlin/.


Decision

backend/ is the canonical Kotlin/Ktor backend for Bilko.

apps/api-kotlin/ is deprecated and will be archived as of MC #9894.

All present and future development of the Bilko API occurs in backend/. The io.bilko package namespace is abandoned. The no.alai.bilko namespace (established 2026-03-04) is permanent for the Kotlin backend.


Rationale

Three hard facts — all git-verified, zero assumptions:

Fact 1 — Scale disparity (51 vs 17 files). backend/ contains 51 Kotlin source files. apps/api-kotlin/ contains 17. More critically, the qualitative gap is larger than the count suggests: backend/ contains every domain-specific component (fiscal adapters, error registry, DI wiring, PDF generation, rate limiting, Sentry telemetry). apps/api-kotlin/ contains only the routing skeleton.

Fact 2 — All ADR paths reference backend/. ADR-014 through ADR-019 — every architecture decision record written for this product — reference no.alai.bilko paths inside backend/. ADR-019 was explicitly verified against backend/src/main/kotlin/no/alai/bilko/adapter/AdapterException.kt. Zero architecture documents reference io.bilko or apps/api-kotlin/. An ADR is a commitment. Reversing ADR-015 through ADR-019 evidence chains to point at apps/api-kotlin/ would be rework with no technical benefit.

Fact 3 — Version inversion confirms direction of travel. apps/api-kotlin/ is pinned to Kotlin 2.1.20 / Ktor 3.1.2 — the versions specified in the original Phase 1 Track A scaffold spec. backend/ runs Kotlin 2.3.0 / Ktor 3.4.0 / JVM 25 — current as of 2026-04-28. This is not a coincidence: apps/api-kotlin/ was scaffolded once to a fixed spec and never updated. backend/ was actively maintained and upgraded. The version delta tells the entire story: one codebase is alive, the other is frozen at its creation point.


Consequences

For Lane B BLOCKER Tasks (#9852 / #9853 / #9854 / #9855)

All Lane B backend tasks are unblocked against backend/ as the target. No work should be directed to apps/api-kotlin/. Any task whose scope referenced apps/api-kotlin/ or io.bilko must be updated to reference backend/ and no.alai.bilko before execution begins.

For MC #5125 (Bilko Express → Kotlin Migration)

MC #5125 was the formal trigger for the Kotlin migration and the stated prerequisite for any work in apps/api-kotlin/. With this ADR:

  • backend/ fulfills the Kotlin/Ktor migration requirement — the migration is structurally complete at the backend layer.
  • MC #5125 may be closed (DONE) once BUILD-BLUEPRINT.md is updated (MC #9897) to document backend/ as the canonical backend and the Express legacy (apps/api-legacy/) as deprecated.
  • The specific Track A intent ("apps/api-kotlin/ is the migration landing zone") is superseded by this ADR. Track B work proceeds in backend/ directly.

For BUILD-BLUEPRINT.md

BUILD-BLUEPRINT.md §3 currently documents apps/api/ (now apps/api-legacy/) as the backend and makes no mention of either backend/ or apps/api-kotlin/. This is pre-migration documentation. MC #9897 (BUILD-BLUEPRINT update) must:

  1. Replace the backend section to reference backend/ (package no.alai.bilko, Kotlin 2.3.0, Ktor 3.4.0)
  2. Document the directory structure: backend/ lives outside Turborepo workspace (independent Gradle + GCP Cloud Run deploy)
  3. Update build commands to cd backend && ./gradlew run
  4. Mark apps/api-legacy/ as deprecated with a pointer to its decommission timeline

For DEPLOY-MAP.md

DEPLOY-MAP.md currently records bilko-api as "Manual only (Kotlin TBD)". After Dockerfile work (MC #9898), this entry must be updated to reflect: source = backend/, build = Docker multi-stage, deploy target = GCP Cloud Run via gcp-deploy.yml.

Negative Consequences

  1. One-time porting effort. apps/api-kotlin/ contains a more rigorous implementation of refresh token rotation (features/auth/AuthRepository.rotateRefreshToken()). This pattern should be reviewed against backend/ before archiving — MC #9895 covers this comparison.

  2. Track A plan invalidated. The Phase 1 Track A execution document explicitly designated apps/api-kotlin/ as the future target. That plan is now superseded. The document must be annotated with a pointer to this ADR to prevent future agents from acting on stale guidance.

  3. Feature-sliced architecture not adopted. apps/api-kotlin/ used a feature-sliced layout (features/auth/, features/invoices/). backend/ uses a layered layout (routes/, services/, auth/). The architectural pattern debate is resolved in favor of the layered approach by inertia — 51 files are not being reorganized. This is a deliberate trade-off: stability over structural preference.


Lessons Learned

Lesson 1 — Premature Scaffold Incident

The Track A document stated that apps/api-kotlin/ should NOT be built until MC #5125 formally unblocked. A generic builder agent built it anyway (2026-04-14). This is the root cause of the entire incident. The constraint in writing was not sufficient — it required a hard gate (Mehanik) enforcing it programmatically.

Fix: Mehanik pre-dispatch gate (activated 2026-04-25, MC #9274) is the structural remedy. No backend task may be dispatched without Mehanik clearance that checks: task MC ID exists, BUILD-BLUEPRINT.md read, scope ceiling verified, CI green if deploy.

Lesson 2 — "Probably Empty" Hallucination in Audit

The preliminary audit saw apps/api-kotlin/ in the ls output and wrote "need to confirm these are indeed empty/removed" — then concluded without verifying. The correct tool-first discipline (ZAKON NULA) required a find apps/api-kotlin/src -name "*.kt" call before any assumption about directory state. A single verification command would have revealed 17 Kotlin files and flagged the duplicate immediately.

Fix: Any directory flagged as "need to confirm" in an audit is an open obligation, not a closed finding. Audits are not complete until all flagged items are machine-verified. Post-audit review by a second agent (MC #9892 forensic) should be standard for architecture-level audits on active codebases.

Lesson 3 — ZAKON NULA Violation (Tool-First)

John dispatched the backend-dev agent to apps/api-kotlin/ without reading BUILD-BLUEPRINT.md, without running node ~/system/tools/mc.js show 5125, and without querying the existing project structure. Had BUILD-BLUEPRINT.md been read first, it would have been apparent that backend/ was the active implementation and that apps/api-kotlin/ was the designated (but not yet active) future target — a distinction requiring a human (Alem) decision, not an agent initiative.

Fix: ZAKON NULA (CLAUDE.md) is enforced by the Mehanik pre-dispatch hook. The hook requires tool-verified project state before clearing any build dispatch.

Lesson 4 — ZAKON #1 Violation (Specialist Routing)

MC #5125 is a complex backend migration (Express/TS → Kotlin/Ktor, domain logic, multi-market fiscal adapters, DI framework selection). This requires a specialist — CodeCraft (Petter Graff / Hadi Hariri), not a generic builder agent. CLAUDE.md §5 is unambiguous: "Never generic builder/minion. Route to the right company." Generic agents lack the architectural judgment to navigate this class of decision (where does the backend live? which package namespace? which version pins?).

Fix: Complex backend migrations are categorically CodeCraft work. If the specialist routing table in CLAUDE.md is unclear for a given task, the correct action is to ask John, not to default to a generic pool. The Mehanik gate now enforces specialist routing as part of its clearance criteria.


Migration Path

The following tasks (C2–C6, MC #9894–#9898) execute the deprecation and consolidation. All tasks have Mehanik-cleared MC IDs. Sequencing matters: C2 (archive) must complete before C3 (port auth) to avoid confusion about which directory to edit.

C2 — Archive apps/api-kotlin/ (MC #9894)

Owner: CodeCraft | Effort: S (1h)

  1. Rename apps/api-kotlin/ to apps/api-kotlin-abandoned/.
  2. Add README.md at the root of the renamed directory:
    DEPRECATED 2026-04-28 — see ADR-020
    This directory is the abandoned migration scaffold created 2026-04-14 to 2026-04-15.
    The canonical Kotlin backend is /backend/ (no.alai.bilko, Kotlin 2.3.0, Ktor 3.4.0).
    Do not edit this directory. It will be deleted after 2026-05-28.
    
  3. Verify turbo.json and root package.json workspaces do NOT include apps/api-kotlin or apps/api-kotlin-abandoned (Turborepo workspace scope must not resolve against it).
  4. Annotate docs/bookstack-sync/phase1-track-a-execution.md lines 241 and 299 with: [SUPERSEDED by ADR-020 — apps/api-kotlin abandoned, backend/ is canonical].

C3 — Port Auth Improvements to backend/ (MC #9895)

Owner: CodeCraft | Effort: M (4h)

Compare apps/api-kotlin-abandoned/features/auth/AuthRepository.kt (specifically rotateRefreshToken() and the ThreadLocal side-channel pattern) against backend/src/main/kotlin/no/alai/bilko/auth/AuthService.kt. If the abandoned version is more rigorous, port the improvement. Do not port file structure or package names.

Scope: auth only. No feature modules, no table objects, no routing changes.

C4 — Update BUILD-BLUEPRINT.md (MC #9897)

Owner: CodeCraft | Effort: S (2h)

See Consequences section above for mandatory content. In addition, add an explicit architectural note: "backend/ lives outside the Turborepo workspace by design. It is a standalone Gradle project with its own GCP Cloud Run deploy pipeline. Do not move it inside apps/."

C5 — Add Dockerfile to backend/ + Update DEPLOY-MAP.md (MC #9898)

Owner: FlowForge | Effort: M (4h)

  1. Port apps/api-kotlin-abandoned/Dockerfile (JVM 21, multi-stage, non-root user, health check) to backend/Dockerfile. Upgrade base image from JVM 21 to JVM 25 (matching backend/ JVM target).
  2. Verify fat JAR output name: bilko-api.jar (check build.gradle.kts shadowJar config).
  3. Local build validation: docker build -t bilko-api-test ./backend must succeed.
  4. Update DEPLOY-MAP.md bilko-api entry: source = backend/, Dockerfile = backend/Dockerfile, deploy = GCP Cloud Run via gcp-deploy.yml.

C6 — Proveo Verification (MC #9898 gate, Proveo)

Owner: Proveo (Angie Jones) | Effort: S (2h)

Acceptance criteria:

  1. docker build -t bilko-api ./backend exits 0.
  2. docker run --rm -p 8080:8080 bilko-api starts and responds to GET /health with HTTP 200.
  3. apps/api-kotlin/ directory no longer exists in repo root (renamed per C2).
  4. turbo.json workspaces grep returns no match for api-kotlin.
  5. grep -r "io.bilko" backend/ returns no matches (no namespace contamination).

References

  • MC #9892 — Forensic audit: dual Kotlin backend root-cause analysis
  • MC #9894 — C2: Archive apps/api-kotlin/
  • MC #9895 — C3: Port auth improvements to backend/
  • MC #9897 — C4: Update BUILD-BLUEPRINT.md
  • MC #9898 — C5/C6: Dockerfile + DEPLOY-MAP.md + Proveo verify
  • MC #5125 — Bilko backend migration: Express/TS → Kotlin/Ktor (to be closed after MC #9897)
  • ADR-015 — Four-Jurisdiction Plugin Architecture (references no.alai.bilko)
  • ADR-016 — E-Invoice Adapter and UBL 2.1 Canonical Model (references no.alai.bilko)
  • ADR-017 — RLS Multi-Tenancy (references no.alai.bilko)
  • ADR-018 — Market Locale Separation (references no.alai.bilko)
  • ADR-019 — Integration Adapter Registry (explicitly verified against backend/ path)
  • docs/bookstack-sync/phase1-track-a-execution.md — Phase 1 Track A intent document (lines 241, 299 superseded by this ADR)
  • Forensic reports/tmp/bilko-dual-backend-da.md, /tmp/bilko-dual-backend-petter.md

Approval

Accepted: 2026-04-28 Executed by: ALAI, 2026 Execution tasks: MC #9894, #9895, #9897, #9898