XS <½d
S ½–1d
M 2–4d
L 1–2w
XL >2w
Each node is a feature area, coloured by status. Red = blocks a complete self-serve product.
flowchart TB classDef done fill:#193b22,color:#7ee787,stroke:#2ea043,stroke-width:1px; classDef wip fill:#3a2f12,color:#e3b341,stroke:#d29922,stroke-width:1px; classDef pending fill:#1f2630,color:#aab6c4,stroke:#6b7787,stroke-width:1px; classDef blocker fill:#3d1715,color:#ff938b,stroke:#e5534b,stroke-width:1.5px; AUTH["🔐 Auth / login
password · OTP · PIN · passkey"]:::done subgraph KEFI["🎉 Kefi — multi-tenant events (KUCY · UBS)"] direction TB K1["Tenancy · signup · onboarding wizard"]:::done K2["Per-tenant branded pages (KUCY/UBS)"]:::done K3["Events · ticketing · GDPR · dashboards"]:::done K4["Phase F — free-tier dynamic publish #159 ✅"]:::done K5["Phase 13 — email lifecycle #184 + IMAP E2E #185 ✅"]:::done K6["Attendee-payment automation #177 ✅ (per-tenant Stripe Checkout + webhook)"]:::done K7["Per-event publish #4 ✅ · custom domains #5 ✅"]:::done K8["Hardening & debt ✅ (#176/#178/#179/#180/#181/#182/#183 + attendee-pay #177)"]:::done end subgraph KAT["📋 Katalogos — online menus"] KA1["Login · menus · variants/modifiers editor ✅ · public-cache SW fix ✅"]:::done KA2["Billing backend-enforcement ✅ (watermark/premium gated vs PaymentService)"]:::done end subgraph ER["📝 Erevna — surveys"] E1["Login methods"]:::done E2["Public flow ✅ · analytics v1+funnel/crosstab ✅ · 12 question types + validation ✅ · embeds ✅"]:::done E3["File-upload type + true drop-off + quotas/invites — pending"]:::wip end subgraph PO["📍 Poueni — find-my-device"] P1["Login · passkey · PIN"]:::done P3["Find My Device COMPLETE: live map #184 + remote-locate #173 + Ring + battery + remote lock + Lost Mode #201 + alarm #202 + SIM-change #203 + last-location #204 + biometric-disarm #205 + remote wipe #199 ✅
(transparent/owner-controlled)"]:::done P4["Collector APK v0.25.0 ✅ — tap-to-pin · WiFi-signal warning · cell fallback + timing-advance · richer cell capture · FMD ring + lock + Lost Mode + alarm + SIM-change + low-battery beacon + biometric theft-protection + remote erase
(on-device QA pending)"]:::done P0["⚠️ Localization accuracy — first real signal ✅ median 2.7 m · WiFi-first + cell fallback + /accuracy WiFi-vs-cell split
(formal <5m gate needs a single-site war-walk; everything now exists to make it easy + measurable)"]:::blocker P6["GDPR erasure/export ✅ (Art.15/17, live)"]:::done P7["Play Console #175 — AAB+listing prepped (account/submit = user)"]:::wip P5["FMD survive-factory-reset #206 (Device-Owner — DESIGN+runbook done, build fresh-device-gated) · cold-start onboarding · GDPR trigger-UI + DPIA · NL-query slice"]:::pending end AUTH --> KEFI & KAT & ER & PO
Completed work over this program (left → right). Everything below shipped to production + E2E-gated.
gantt title Login-methods program + product mapping (2026) dateFormat YYYY-MM-DD axisFormat %b %d section Foundation #166 Realm hardening :done, t166, 2026-05-31, 1d #167 PIN engine + Kefi pilot :done, t167, 2026-06-01, 1d #168 Passkeys / WebAuthn :done, t168, 2026-06-01, 1d section Rollout #169 PIN+passkey → kat/ere/pou :done, t169, 2026-06-02, 1d #170 Reset → revoke devices :done, t170, 2026-06-03, 1d #171 D5 preferred-method :done, t171, 2026-06-04, 1d section Parity #172 OTP → Katalogos + Erevna :done, t172, 2026-06-05, 1d #173 Poueni device-PIN :done, t173, 2026-06-05, 1d #184 Poueni dual-marker live map :done, t184, 2026-06-05, 1d #180 Poueni nightly canary green :done, t180, 2026-06-05, 1d #174 Fix staging signup 503 :done, t174, 2026-06-05, 1d #175 E2E lint debt → 0 :done, t175, 2026-06-05, 1d Kefi interactive-OTP E2E :done, totp, 2026-06-05, 1d #159 Phase F free-tier publish :done, p159, 2026-06-06, 1d #184 Phase 13 email lifecycle :done, p13, 2026-06-06, 1d #181 Endpoint claim audit + guard :done, t181, 2026-06-07, 1d #185 Phase 13 IMAP E2E + seeding :done, t185, 2026-06-07, 1d #177 Attendee Stripe payments :done, t177, 2026-06-08, 1d Erevna public respondent flow :done, erpub, 2026-06-09, 1d Erevna analytics v1 :done, eran, 2026-06-10, 1d Erevna question types + validation :done, erqt, 2026-06-11, 1d Katalogos public-cache fix :done, kcache, 2026-06-11, 1d Katalogos billing enforcement :done, kbill, 2026-06-11, 1d Erevna funnel + crosstab :done, eran2, 2026-06-12, 1d Erevna embeds + ranking/matrix :done, eremb, 2026-06-12, 1d Custom-domains pkg + Kefi (A+B) :done, cdab, 2026-06-12, 1d Katalogos onto pkg backend (C-1) :done, cdc1, 2026-06-13, 1d Katalogos CD RBAC+FE+E2E (C-2..4) :done, cdc2, 2026-06-13, 1d Katalogos CD CORS + staging deploy:done, cdc3, 2026-06-13, 1d Katalogos CD staging ACTIVATED :done, cdc4, 2026-06-13, 1d Katalogos CD LIVE prod (C-5) :done, cdc5, 2026-06-13, 1d Kefi per-event publish #4 :done, kpe, 2026-06-10, 1d Kefi custom domains #5 :done, kcd, 2026-06-10, 1d section Poueni v0.11.0 #187 Device-PIN E2E flake fix :done, t187, 2026-06-07, 1d #163 Walk replay (APK+dashboard) :done, t163, 2026-06-07, 1d APK v0.11.0 released :done, apk11, 2026-06-07, 1d #186/#175 war-walk + Play prep :done, pprep, 2026-06-07, 1d Estimate-vs-actual replay v0.14.0 :done, apk14, 2026-06-10, 1d Tap-to-pin hold-out v0.15.0 :done, apk15, 2026-06-10, 1d section Pending (phone / account) #186 M0 war-walk + <5m eval : pwalk, 2026-06-08, 3d #175 Play Console submit : pplay, 2026-06-08, 3d section Implemented — 2026-06-13 (built, pending commit/deploy) Reuse pwa-sw NPM + erevna migrate :done, rpwa, 2026-06-13, 1d Erevna server-side validation :done, eval, 2026-06-13, 1d Katalogos surface A/B experiments :done, kexp, 2026-06-13, 1d Kefi GTM SEO + analytics + brand :done, kgtm, 2026-06-13, 1d Reuse bff-web-client LIVE prod :done, rbff, 2026-06-14, 1d Reuse tenant-theme-web LIVE prod :done, rttw, 2026-06-14, 1d Reuse orval-preset LIVE prod :done, rorv2, 2026-06-14, 1d section Platform / Product Factory (0-hero) P0 npm publish + retire bridge :done, pf0, 2026-06-14, 1d P1 libraries + kefi adoption (n3) :active, pf1, after pf0, 7d P2 shared platform services : pf2, after pf1, 7d P3 pipeline templatization : pf3, after pf2, 7d P4 mobile / Expo EAS builds : pf4, after pf3, 10d P5 scaffolder CLI (capstone, LAST) : pf5, after pf4, 10d section Planned — product increments Erevna quota + closing-date :done, equota, 2026-06-22, 1d Erevna respondent meta + anonymity :done, emeta, 2026-06-22, 1d Erevna save & resume :done, eresume, 2026-06-22, 1d Erevna email invites + file-upload : einv, 2026-06-23, 4d Katalogos public-viewer SEO/perf : kseo, 2026-06-16, 4d Poueni runbook v0.20 + coverage : pcov, 2026-06-16, 4d
Identity.Keycloak.Authentication ns (KeycloakHttpOptions / KeycloakHttpClientExtensions / MultiRealmJwtBearer.ConfigureMultiRealm / KeycloakClaimsHelper); each service deletes its 3 local Security/ copies, RealmAuthorization*/SuperUser*/IClaimsTransformation stay per-service; ~4,400 unit tests green; per-service staging→auth-E2E→prod; published Identity.Abstractions 1.6.2 to unblock the dep chain; caught+fixed a Tenant CrashLoopBackOff (CentralPackageTransitivePinning vs local-feed Identity.Abstractions mismatch — build passes, runtime FileNotFound 1.6.2). ✅ analytics instrumentation DONE 2026-06-15 — Umami snippet live on erevna.dloizides.com (id 2ecad02e) + katalogos.dloizides.com (id 493b52aa) via each app's +html.tsx head (data-domains-scoped to prod); curl-verified on prod. Kefi Marketing already instrumented (#188); kefi-web organizer is admin-exempt / covered by per-tenant ids. ✅ login-error E2E fixed 2026-06-15 — 2 stale login.spec tests asserted a legacy window.alert dialog; the shared <LoginForm> surfaces errors INLINE (testID auth-login-error) → tests updated; 7/7 login.spec green on prod. ✅ adopt-existing: ContentService→MultiTenancy.EntityFrameworkCore 1.4.1 DONE + LIVE prod 2026-06-15 — deleted local Content.Infrastructure.MultiTenancy (ICurrentTenantService+CurrentTenantService), now consumes the shared MultiTenancy.Abstractions + AddMultiTenancy() (claims via Security.Claims); matches the other 4 backend services; fixed broken superuser detection (real role superUser, local checked superuser/SuperUser); 208 unit tests + content E2E 23 (staging) + 17 (prod) green. ✅ P1 (shared-lib tier + adoption) COMPLETE — only Poueni→Email.Smtp remains, deferred to the parallel Poueni session. ✅ ALL shared pkgs now on npm (tarball bridge retired, P0 done). Deferred (premature): GDPR contract, subscription-gate (n1), Storage.S3, QR. auditmanage.sh scaffold-service <slug> generates a new .NET backend's full pipeline (k8s Service/Deployment/Ingress + standardized Dockerfile + the 9 Tilt resources + a Playwright E2E project + manage.sh registration), parameterized by name/realm/ports, from scripts/templates/service/. Generates files + emits paste-snippets for the shared multi-service files (apis.yml/Tiltfile/playwright.projects.ts/manage.sh) rather than auto-editing them. Validated against a throwaway demo service (YAML parses, Keycloak-before-Jwt order preserved, 9 Tilt resources, 0 leftover placeholders). Follow-ups (optional): marker-based auto-insert, custom-domain RBAC fragment, CI workflow (skipped — no per-service CI in use). P4 ✅ DONE — all 3 Android APKs BUILT + LIVE prod 2026-06-15 — mobile/Expo-EAS Android tier: erevna/katalogos/kefi each EAS-build-ready (app.json android com.dloizides.<slug>+versionCode, eas.json APK profiles) → EAS cloud builds → self-hosted download (erevna.dloizides.com/download + katalogos.dloizides.com/download direct; kefi via bff-kefi proxy at the tenant host). Templatized as manage.sh scaffold-mobile + manage.sh publish-apk; runbook mobile-eas-runbook.md. Fixed 2 native-bundle bugs surfaced by the first build: frontend-devtools main entry pulled Node fs (→1.0.3, eslint moved to /eslint subpath) + kefi @mlc-ai/web-llm browser-only (→webllm.native.ts stub). iOS/Play-Store/push deferred. Next: the Capability Wave C1–C9 (cross-product reuse) → THEN P5 (capstone, composes everything). planui-feedback@1.2.0 (feedback + shared useUi context) · ui-forms · ui-tables · ui-icons · legal-ui · ui-layout · utils@1.1.0 (extended); erevna+katalogos fully migrated. settings-hub deferred (stateful screens — needs app substrate shared). Next epic: C2 comms/push) · C2 comms/push (#213, ✅ COMPLETE/LIVE prod — was the #1 functional gap): push (Expo Push.Expo + VAPID Push.WebPush@1.0.1, web opt-in on erevna+katalogos; mobile activation gated on owner FCM, 2026-06-15) · marketing/bulk email (Marketing.AspNetCore@1.0.0 bulk-send over Maddy SMTP + merge + RFC 8058 HMAC unsubscribe + Loki, /api/v1/marketing/* + erevna Campaigns UI + E2E, 2026-06-16) · delivery webhooks (notification.delivered/campaign.sent wired into the existing dispatcher) · email templating+i18n (Email.Templating@1.0.0 — shared layout + en/el localizer, kefi's EmailLayout now delegates) · digest batcher (NotificationService, built+tested, scheduler off by default) — 2026-06-17. NOT Postal (forward-looking swap). 539 NS tests. · C3 ✅ COMPLETE/LIVE prod 2026-06-17 — generation abstraction (#212): provider-agnostic "generate X" seam (in-browser model · API provider · self-host ComfyUI/Diffusers · build-time sharp). DoD met (2 seams, 2 consumers): image @dloizides/og-image-generator@1.0.0 (ImageRasterizer + sharp-injected SharpRasterizer, consumer = kefi-marketing build-og, byte-identical) + text/copy @dloizides/text-generation@1.0.0 (TextGenerator + ChatCompletionsTextGenerator driving an injected in-browser WebLLM engine OR a remote OpenAI-compatible API + fetch provider + JSON helpers, consumer = kefi-web onboarding "Generate with AI", deployed prod @61ec6a05a0c7). Both zero runtime deps / 100% cov. Forward (optional): game art (Aurora SDXL/ComfyUI) · register audio (music-runtime) · integrate forge/ ·C4 ✅ COMPLETE/LIVE prod 2026-06-17 — games kit (#215). Phase-0 audit corrected the scope (C4-duplication-audit.md): the games share far less copy-paste logic than assumed — only haptics + save-state are defensible same-stack extractions; menu-fw/i18n/input/monetization/achievements all n=1 or divergent or cross-stack → DEFERRED (forward). C4.1: @dloizides/game-input@1.0.0 (player-toggleable Web Vibration wrapper, zero-dep, injectable nav/storage, HAPTIC_PATTERNS vocabulary). C4.2: @dloizides/save-state@1.0.0 (storage port KeyValueStore/memory/default + corruption-safe readJson/writeJson/removeKeys + generic versioned slotted SlotStore<T>). Both zero-dep/isomorphic/100% cov. Consumers: Aurora (haptics re-export shim + progress.ts on the storage port; 617 tests; deployed prod @a251e0eb) + Morphe (new haptics adoption + SaveManager wraps SlotStore; 772 tests; committed — Morphe has no k8s pipeline so undeployed-as-before). NB: @eisaipollis scope was never on npm (music-runtime only vendored via file:) → games pkgs publish under @dloizides. · C5 landing/marketing kit (#216, ✅ COMPLETE/LIVE prod 2026-06-18) — Phase-0 audit corrected scope (C5-duplication-audit.md): the 2 Astro sites share NO copy-pasted code (theme-system/contact-reveal/funnel all n=1, not dups); the real dups are the vanilla static KUCY↔UBS sites (sw.js + a fork bug, ledger-stats, countdown, register→WhatsApp) which have NO build step → vendored vanilla `<script>`, not npm. C5.2 ✅ manage.sh scaffold-landing <slug> — generates a static site shipping the web-app-standards baseline by construction (Umami + SEO/OG/JSON-LD + robots/sitemap + spam-safe JS contact + gzip nginx + k8s/manage.sh wiring); feeds P5. C5.1 ✅ @dloizides/event-landing-kit@1.0.0 (TS, 100% cov, zero-dep IIFE bundles: EventLandingKit page kit [buildWhatsAppUrl · initLedgerStats · initCountdown] + EventSW service-worker core) — vendored into KUCY (cache kucy-v185) + UBS (ubs-v163), both LIVE prod + Chrome-verified; fixes the cross-site SW cache fork bug (cleanup scoped to each site's own prefix); register form stays per-site (KUCY Pro-Video add-on). · C6 mobile shell+OTA (#211, ✅ COMPLETE as-scoped 2026-06-18) — Phase-0 audit corrected scope (C6-duplication-audit.md): no Expo app ships native (erevna/katalogos/kefi are web-first RN-web), OTA is greenfield (n=0), the one shipped native app (Poueni) is Kotlin → a shared Expo shell/OTA runtime has zero consumers. Owner decision: web-first PWA. Delivered C6.1 = enhanced scaffold-mobile (full app.json native baseline: deep-link scheme + Android intentFilters + iOS associatedDomains + splash/icon/adaptiveIcon + optional off-by-default expo-updates/runtimeVersion OTA seam + README OTA/deep-link runbook; personalServerNotes 6765600). Runtime shell + live OTA deferred-for-P5 (revisit if an Expo app ships native); follow-up: kefi-web missing icon/splash assets. · C7 observability/quality (#217, ✅ COMPLETE as-scoped 2026-06-18) — Phase-0 audit corrected scope (C7-duplication-audit.md): C7 was ~80% already shipped — Sentry crash reporting (backends + erevna/katalogos), production OTel/Prometheus/Loki/health/canary/Daily-Report, Cloudflare Turnstile on Kefi signup, Lighthouse + a full GH-Actions CI + static-smoke, and all 3 GDPR export/delete flows (divergent-by-design → Gdpr.Contracts NOT built, the C4/C5/C6 anti-pattern). Delivered the small real-gap polish (all LIVE): kefi-web Sentry+ErrorBoundary (the only web app missing it; erevna/katalogos already wired) · Web Vitals→analytics ×3 (web-only, consent-gated) · axe-core a11y E2E (new a11y-public Playwright project; caught + fixed a real WCAG contrast bug on the kefi hero "live" chip → all 3 green on prod) · per-app Lighthouse (kefi-web real: perf 0.99/a11y 1.00/bp 1.00/seo 0.91). Deferred-forward: flags service · full-text search · A/B generalization · secrets Vault · games/Poueni crash reporting. Follow-up: set SENTRY_DSN in prod (owner). · ⛔C8 app-store dist (#209, needs Play+Apple accts) · ⛔C9 Steam+game-publish (#210, needs Steamworks) · P5 scaffolder (#202, ✅ COMPLETE/validated 2026-06-19 — P5.1+P5.2+P5.3 all shipped) — Phase-0 audit done (P5-phase0-audit.md): premise VALID (synthesis, not extraction); all primitives shipped; gaps = source-gen templates + orchestrator. Sequence P5.1→P5.2→P5.3, emit registration snippets (no auto-edit), honest DoD = no hand-wiring of CODE + a runbook for owner-gated seams. P5.1 ✅ manage.sh scaffold-backend — generates a buildable .NET service SOURCE skeleton wired to the adoption-contract baseline A–M (Clean Arch Core/UseCases/Infra/ServiceDefaults/Web+tests, CPM/net10, sample Widget aggregate+GET/POST+InitialCreate migration+docker-compose; pairs with scaffold-service for the pipeline); validated generate→build 0W/0E→test 7/7, near-zero delta vs ContentService; personalServerNotes 5abcf18. P5.2 ✅ manage.sh scaffold-web — generates a minimal runnable RN-web/PWA app SHELL on the shared @dloizides/* stack (login→dashboard: all config/tooling + _layout providers/Sentry/ErrorBoundary/Web-Vitals + auth login + placeholder protected dashboard; EXCLUDES product screens) + a thin Bff.<Proj> skeleton; validated generate→npm install→lint 0/tsc 0/yagni 0/jest 4-4→expo export web PASS; personalServerNotes 8774cec (75 web + 8 bff templates). P5.3 ✅ manage.sh create-app <slug> — the CAPSTONE orchestrator: COMPOSES the four generators (scaffold-backend/web/mobile/landing) under one command (tiers --backend/--web/--mobile/--landing, default backend+web), BINDS feature toggles by FILLING the P5.3 seam markers in the generated backend (--billing Subscriptions.AspNetCore+ISubscriptionStore stub · --storage Storage.S3+IS3StorageService stub · --custom-domains CustomDomains.AspNetCore+ICustomDomainStore stub · --email Email.Smtp · --gdpr UserDeleted/UserDataExport consumer stubs · --analytics/--theming/--notifications frontend), and emits a consolidated REGISTRATION.md (all chained snippets, no auto-edit per audit #2) + a per-product RUNBOOK.md (only the enabled tiers/toggles' owner-gated seams: realm/DNS/Stripe/S3/email/Turnstile/Umami/EAS). Toggle versions pinned to the nuget.org-available builds (private newer = local-feed only). Validated: create-app demo --backend --web --landing --billing --analytics → backend dotnet build -c Release 0W/0E (TreatWarningsAsErrors+Sonar) + 7/7 tests; ALL backend toggles together (billing+storage+email+custom-domains+gdpr) also build 0W/0E; web shell + valid package.json + REGISTRATION/RUNBOOK complete. Footguns found+fixed: child-namespace store refs must be FQ; stub TODO comments trip Sonar S1135 (use neutral wording); the template seam COMMENT literally contains using Storage.S3;/a fake PackageVersion so guards must anchor ^; Email.Smtp 1.0.0 floors Identity.Abstractions 1.0.1 (NU1109 vs Identity.Keycloak 1.7.0's 1.6.2) + MailKit 4.15.1 (NU1902 → pin 4.17.0). P5 is now COMPLETE — true 0→running across all tiers up to the owner-gated boundary.openmindednewby/AMLService), LIVE prod aml-screening.dloizides.com. Phases 0/1/4/5 LIVE — secrets/OIDC-RP/linter (P0) · tenant onboarding + API keys + screening engine (TP/PM/FP + decision) + direct-per-watchlist ingestion (UN/OFAC/EU/UK ~38k entities, pg_trgm + JaroWinkler) (P1) · per-tenant risk-scoring engine (P4) · per-tenant matching config + phonetic + Wikidata PEP ~6958 (bounded) + stats dashboard (P5). ✅ aml Keycloak realm PROVISIONED + admin OIDC path verified end-to-end on prod 2026-06-18 (3 clients + claims scope; both admin principals → 200 on /v1/admin/stats; built a reusable realm scaffolder + ADDING-A-SERVICE-REALM recipe). ✅ Batch C — per-tenant external-provider opt-in LIVE prod 2026-06-18 (the comprehensive-PEP lever): tenants opt into a commercial provider (OpenSanctions ref) with their OWN credentials, additive "over and above" the local engine — AES-256-GCM-encrypted key at rest, per-tenant resolver, admin API GET/PUT/DELETE /v1/tenants/{id}/providers (key write-only), resilient (a bad tenant key can't break the screen); 176 tests. ✅ Batch D — true ongoing monitoring LIVE prod 2026-06-19 (197 tests): HMAC-signed webhook dispatcher (encrypted secret, screening.completed) + MonitoredSubject registry (monitor:true enrol-on-screen, GET/DELETE /v1/monitoring/subjects) + re-screen-on-list-change sweep emitting monitoring.alert (opt-in Monitoring:Enabled); prod-verified onboard-with-webhook + enrol + list/delete. ✅ Token-based (whole-name) phonetic matching LIVE prod 2026-06-19 — per-token dmetaphone text[] + GIN array-overlap (replaces first-token-only), so a sound-alike surname matches even with a misspelt first name; backfilled 112k names, recall-only (scorer still gates). ✅ Adverse-media AM-1 LIVE (best-effort) prod 2026-06-19 — query-time GDELT news search + keyword/rules → decision Review (opt-in, self-resilient); owner path = GDELT-free now → per-tenant BYO → commercial, keyword/rules → self-hosted NER → LLM (docs/adverse-media.md). AM-1.1 per-name cache LIVE 2026-06-19 (TTL + negative-caching; own DbContext scope — fixed a parallel-provider 500). ⚠️ GDELT free rate-limits a shared cluster IP persistently → GDELT-free is best-effort/demo-grade. ✅ AM-2 per-tenant bring-your-own adverse-media LIVE 2026-06-19 — a tenant configures their OWN GDELT-compatible endpoint+key+keywords (PUT /v1/tenants/{id}/providers/AdverseMedia, reuses Batch C framework, overrides global by name); their own quota = the reliable path. Prod-verified (no-endpoint 400, endpoint-no-key 200, screen 201). ✅ AM-3 relevance precision filter LIVE 2026-06-19 — pluggable IAdverseMediaRelevanceFilter (rules: subject-in-headline + negative-context, score+threshold) drops noisy co-occurrence hits on both providers; a neural NER drops into the seam later (needs in-pod-vs-sidecar infra call). ✅ Per-tenant monthly screening quota LIVE 2026-06-19 — fair-use/billing cap (429 over limit; admin PUT /v1/tenants/{id}/quota, tenant GET /v1/usage; counts API screens only, not monitoring re-screens); prod-verified. ✅ Per-tenant rate limit (screens/minute) LIVE 2026-06-20 — rolling-60s throughput/burst cap alongside the monthly quota (429 + Retry-After; set at onboarding or admin PUT /v1/tenants/{id}/rate-limit; GET /v1/usage reports it); prod-verified (limit 3 → 3×201 then 429). ✅ White-label slice (M4 start) LIVE 2026-06-21 — per-tenant branding (display name / logo / accent), GET /v1/branding + admin PUT /v1/tenants/{id}/branding + at-onboarding + validation; the /playground re-brands from the tenant behind the key (API stays brand-neutral); prod-verified. Remaining M4: admin-dashboard/marketing theming, per-tenant custom domain, UI i18n. ✅ Backend integration tests added 2026-06-21 — AMLService.IntegrationTests (WebApplicationFactory + Testcontainers Postgres; 9 tests: auth/screening/quota+rate-limit 429/usage/admin), closing the gap that the repo had only unit tests + no Playwright coverage. 386 unit + 9 integration green. ✅ Cold-start warmup LIVE 2026-06-19 — readiness gated on a startup DB warmup so K8s won't route the first screen onto a cold pod (fixes the cold-start 500). ✅ Case-management back-office (CM-1/2/3) LIVE 2026-06-19 — review/disposition (open|cleared|confirmed|escalated) + audit note thread + CSV/regulator export + bundled console at /cases/index.html (filter/page table, detail drawer, dispositions, notes); tenant-scoped; Chrome visual-QA'd. ✅ Own IdP (OpenIddict) LIVE prod 2026-06-20 — replaces the Keycloak dependency: machine (client_credentials) + human (authorization_code/PKCE + hosted login UI) auth at aml-identity.dloizides.com; AMLService accepts BOTH Keycloak and the own IdP (dual-authority, zero-downtime); stats dashboard signs in via OIDC. Both issuers prod-verified → 200. ✅ Engine backlog E1/E2/E3/E4/E6 LIVE prod 2026-06-20 (built in parallel by 5 agents on isolated worktrees → integrated + migration-verified [357 tests] → deployed image daa3573f; prod-smoked): E1 bulk search (POST /v1/screenings/bulk, JSON/CSV) · E2 PEP tiering + warning-type/category config · E3 neural-NER seam client (remote provider + flag, default rules; sidecar deploy still pending the in-pod-vs-sidecar call) · E4 comprehensive audit log (GET /v1/audit + CSV) · E6 criminal-records BYO provider type. ✅ E5 nickname/hypocorism matching LIVE prod 2026-06-20 (image 3cc3448f) — query expansion + nickname-aware engine classification; prod-verified ('Joe Kony'→Fail/JOSEPH KONY, exact-match gate + no-FP). The PowerPoint AML-engine spec is now COMPLETE bar E3's optional NER-sidecar deploy (infra call). ✅ Go-to-market surfaces LIVE prod 2026-06-20 (built in parallel by 5 agents → integrated + green-gated [368 tests] → deployed): full product docs (docs/architecture.md · api-reference.md · operations-runbook.md + index); 5-minute onboarding quickstart.md + Postman/.http + bulk-CSV template + sample payloads + onboarding-wizard polish; bundled developer-docs portal at /docs (Scalar rendering the now-prod-exposed OpenAPI at /swagger/v1/swagger.json; fixed the bulk dual-action Swagger 500 + directory-root index serving); AI-agent automation guide + OpenAI/Anthropic tool schemas + a runnable @proovid/aml-mcp-server (MCP tools screen/bulk/get/review, 8/8 tests); and a marketing landing LIVE at aml.dloizides.com (separate static nginx svc + LE TLS, web-app-standards: SEO/sitemap/robots/spam-safe contact). ⚠️ Umami website-id is a placeholder → owner one-liner (create the aml.dloizides.com Umami entry, paste the id, redeploy aml-marketing). ✅ Non-technical "Try it" page + MCP verified 2026-06-20 — /playground (paste API key once → screen a name → visual Pass/Review/Fail + risk + matches; onboarding wizard hands the key over via localStorage + "Test it now" CTA = no-code onboard→test path); MCP server verified end-to-end live (stdio → 4 tools → screen_person→Fail) + npm run smoke; repo hygiene (untracked mcp-server node_modules/dist). ✅ Per-call list selection + engine gaps + async batch LIVE prod 2026-06-22 — built concurrently in 3 isolated git worktrees → merged + migration-reconciled + green (421 unit + 19 integration): per-screen lists override (request.Lists now actually filters matching; playground list-picker) · POST /v1/screenings/{id}/rescreen · GET /v1/cases/{id}/regulator-pack (JSON bundle) · tamper-evident audit hash-chain + GET /v1/audit/verify · first-class async batch (POST /v1/screenings/batch CSV/XLSX → background job → /results download; ClosedXML; 50k cap; xmin-claimed for multi-replica; playground upload now uses it; verified the OpenSanctions test sheet runs). git-secrets sweep = clean (removed a stray 89MB images.tar). ⚠️ NER sidecar built + deployed then ROLLED BACK prod 2026-06-22 — aml-ner (FastAPI + spaCy, /assess→0.9 verified, ran 1/1) but with it running there was no room for an aml-screening Recreate rollover → the API pod went Pending (Insufficient memory, ~6 min outage); deleting aml-ner recovered it immediately. NER on PROD is GATED on a NODE RESIZE (prod node chronically ~94% memory). ✅ NER now LIVE on STAGING 2026-06-22 — the staging cluster (jim@staging, 16 GB node @ ~51%) runs aml-ner 1/1 + its aml-screening wired to it (/assess→0.9 verified in-cluster), so NER is fully validated end-to-end there. PROD aml-screening stays wired AdverseMedia__RelevanceProvider=remote. Image + manifest in ner-service/. ✅ PROD→STAGING NER over PRIVATE WireGuard 2026-06-22 — staging aml-ner exposed via a hostPort bound to hostIP staging (private interface only, NOT public; iptables not available since staging sudo=kubectl-only); prod AdverseMedia__RemoteNerEndpoint=http://staging:8089/; prod→staging HTTP /health verified over the tunnel. Graceful NER-down handling hardened — detailed PII-free log (names the endpoint, classifies timeout / unreachable / HTTP-status / bad-response, states the screen was unaffected) then degrades to rules (442 unit tests, +1 asserting the message). ⚠️ FOOTGUN: the staging hostPort is a LIVE patch (not in the committed manifest, since standalone co-locates NER) — re-patch if staging aml-ner is re-applied, else prod silently falls back to rules. ✅ 4 more features (4 parallel worktree agents) LIVE/ready prod 2026-06-22 (441 unit + 32 integration green): full-text search (GET /v1/screenings/search, Postgres stored tsvector generated col + GIN + ts_rank; migration AddScreeningSearchVector; prod-verified 19 hits ranked) · turnover-threshold monitoring engine (M1) (opt-in: ThresholdLedger/TurnoverEvent, POST /v1/threshold/events rolling-window→auto-rescreen on breach + threshold.breached webhook, GET /v1/threshold/subjects/{ref}, GET/PUT /v1/tenants/{id}/threshold-config; inert until a threshold is set; migration AddThresholdEngine; prod-verified turnover accrues) · UI i18n (M4) (EN/ES/FR vanilla shared/i18n.js + locales across playground/onboarding/stats/cases/docs, key-parity checked; prod-verified assets 200) · TypeScript SDK (M3) @proovid/aml-sdk@0.1.0 (18 typed methods, 33 tests; npm package, not deployed). Two parallel migrations (threshold+search, disjoint) auto-merged the snapshot + integration AutoMigrate proved the chain. ✅ GDPR data-privacy + small-engine batch (2 parallel worktree agents) LIVE prod 2026-06-22 (498 unit + 41 integration green; migrations AddDataPrivacy + AddCaseAttachmentsAndRescreenSchedule auto-merged): GDPR — data-subject rights /v1/privacy/subjects/{ref}/export|erase|restrict|object|rectify (SAR + right-to-erasure that anonymizes-in-place + de-links the subject; monitoring skips restricted/objected), consent capture + versioned copy + withdrawal, per-tenant DataRetentionDays + RetentionPurgeHostedService (anonymize, off by default); prod-verified (export bundle, consent 201, erase→recordsAnonymized:1 + de-link). Engine — POST /v1/cases/bulk, case attachments upload/list/download (10 MB cap, bytea), scheduled re-screen cadence (daily-delta + monthly-full ScheduledRescreenHostedService, off by default), RBAC support+developer_read read-only roles; all prod-verified. ✅ Buildable-polish cluster (2 parallel worktree agents) LIVE prod 2026-06-22 (506 unit + 45 integration): regulator-pack PDF (?format=pdf, MIT PdfSharpCore+MigraDocCore; FOOTGUN — slim Linux container ships NO fonts so PdfSharpCore threw "No Fonts installed" 500 → fixed with an EMBEDDED DejaVu font + an IFontResolver registered UNCONDITIONALLY [the GlobalFontSettings.FontResolver getter is lazily non-null so a null-guard never fires]; prod %PDF 43KB verified) · decision explainability (persist risk factors as jsonb + return on check/get + cases "why this decision" panel) · WCAG 2.1 AA across all bundled UIs (skip-links/landmarks/aria-live/focus-visible/contrast; live axe = 0 violations). ✅ Final buildable polish (2 parallel worktree agents) LIVE prod 2026-06-22 (521 unit + 48 integration): regulator-pack Parquet export (Parquet.Net, ?format=parquet, prod PAR1 3KB) · design-token theming (6 brand tokens secondary/surface/text/font/radius via branding API theme object + shared applyBrandTheme → CSS custom properties, graceful fallback; migration AddBrandTheme) · PT + DE languages (switcher now EN/ES/PT/FR/DE, parity-checked). Screening engine + back-office + GDPR is FEATURE-COMPLETE and the buildable engine/UI backlog is CLOSED. Remaining (backlog aml-backlog.md): only WorldCheck/Dow Jones connectors (contract-gated). Then the owner/ops track — M2 enhanced KYC (out of scope) · M5 compliance/DPIA + pen-test/UAT (owner) · M6 pilot & GA · SLO/WAF/data-locality (ops) · resize prod node (NER first-class on prod). Owner one-time: rotate IdP/provider/mailbox secrets + scrub git history; replace dev Postgres passwords. ROADMAP.md · analysisapp.menuflow.com domain → corrected to katalogos.dloizides.com (curl-verified live); embed routes now noindex + canonical→standalone menu (no duplicate-content); MenuViewedPublic enriched with language+tenantId; public menu images lazy-load on web (real <img loading=lazy>). katalogos-web ca33cf9, lint/tsc/yagni clean + 356 tests; prod rollout green. ✅ duplicate-menu (#250) DONE + LIVE prod 2026-06-22 — POST /TenantMenus/{id}/duplicate clones name+contents+schedule into an inactive '(Copy)' draft (unique-name); menu-list 'Duplicate' action (TenantListItem onDuplicate + MenuDuplicated analytics). ✅ AI paid-gate (#250) DONE + LIVE prod 2026-06-22 — the AI suite was effectively ungated (import 'check' only logged); now all 4 AI endpoints (description/nutrition/import/translate) enforce paid subscription (403 + upgrade prompt) via a shared AiSubscriptionGate. ✅ templates-anywhere RESOLVED (owner 2026-06-22) — covered by starter-templates (create) + duplicate-menu (clone any menu); the named-library was judged redundant, not built. OnlineMenu 1261 + katalogos-web tests green. Katalogos is now feature-complete. Optional later: full per-tenant AI usage quotas (needs PaymentService tiers) · dynamic per-menu sitemap (needs a public-menus list endpoint). strategy · katalogos.md?draft= resume link + prefill. QuestionerService 97aa027/70eb5ee (344 tests), erevna-web 044f8c0/538131c (lint/tsc/yagni clean, 278 tests); staging→prod rollouts green, migrations applied clean, erevna.dloizides.com 200. Email invitations ✅ RESOLVED (owner 2026-06-22) — covered by the existing erevna Campaigns recipient-list email (NotificationService/Maddy); no dedicated survey-invite flow (would duplicate Campaigns). File-upload question type ⏸ DEFERRED (owner 2026-06-22) — needs an anonymous ContentService S3 upload endpoint (public abuse/cost surface); revisit later, possibly token-gated. Erevna is now feature-complete except the deferred file-upload. Beats incumbents on privacy/ownership; behind on distribution. strategy · erevna.mdStripeException→500 on subscriptions (prod's key is the placeholder) → graceful Trial fallback · #235 canary teardown ran per-process + swept the e2ec- tenant users mid-run (401 cascade) → E2E_CANARY_SKIP_TEARDOWN + one final sweep · #236 prod observability: container-metrics (pod label) + Grafana Loki datasource + Loki level label, and correlation-id/TenantId now reach Loki (Logging.Client 1.5.0 console template + cluster-aware queries; onlinemenu-api) · #237 negative-login inline-error assertions · #238 content images served same-origin via the BFF (/content/{id}/download + /public-download, S3 kept private) — fixed unreachable internal-S3 image URLs (a real customer-facing bug) + content-upload ESM import + survey/menu embed CSP (nginx map survives the SPA try_files fallback) · #239B bff-token 429 retry. Verified: menu-content-upload-basic.spec.ts 9/9 in isolation against deployed prod. Full diagnosis: E2E-TRIAGE.md. Remaining = owner data only: #239A-UBS re-publish the kefi UBS LandingConfig to match the standalone (#djs+ambassadors sections); KUCY parity passes (+ a couple known chunk-ordering test flakes).SessionId + session-scoped endpoints + m0-eval.py --session (`poueni-api@e5175df281d6`, 183 tests; app tests green). LIVE prod 2026-06-15 (`poueni f5f397a`); on-device QA pending.no_neighbours) is a coverage problem (sparse + non-co-located + 2 sites mixed), not the model. Settings gained copy + show/hide on the API key (v0.27.0, LIVE prod). Next: a guided calibration wizard + session-scoped eval so one dense walk is collected + measured cleanly. 2026-06-15notification-api@sha256:78d7aaba2947, 447/447 tests).?draft= link (separate SurveyDraft aggregate, kept out of analytics/quota; migration AddSurveyDrafts). "Save & continue later" UI + answer prefill. #246 LIVE prod 2026-06-22.acceptingResponses → respondent "survey closed" state, editor gets "Closing date"/"Response limit" fields. Migration AddTemplateResponseLimits (nullable, no backfill). #244 LIVE prod 2026-06-22 (QuestionerService 328 tests, erevna-web 176 + lint/tsc/yagni clean; staging→prod rollouts green).pinned ground-truth overrides GPS + skips 20 m gate; Live-map tap-to-place; indoor hold-out needs no GPS — LIVE prod 2026-06-10 (on-device QA pending)@93761bf9)/accuracy gains a WiFi-vs-cell split (cell-calibration aid) — LIVE prod 2026-06-11SimMonitor, operator MCC+MNC + state only — never ICCID/IMSI) → stores a DeviceSecurityEvent + emails the owner + a Devices-page alert banner (ack to dismiss). 6h dedupe; 172/172 tests; code-review PASSED. — LIVE prod 2026-06-13 (on-device QA pending)low_battery event with last GPS/ML fix; best-effort ShutdownReceiver on power-off. Shared DeviceSecurityNotifier (store+email+dedupe, refactored from #203); banner shows battery + coords (text, no external map). 177/177 tests; code-review (1 fix; "wrong shutdown action" finding was a verified false positive). — LIVE prod 2026-06-13 (on-device QA pending)BiometricGate, BIOMETRIC_STRONG\|DEVICE_CREDENTIAL); disabling protection is itself gated. MainActivity→FragmentActivity. Best-effort (no-op without an enrolled lock). Android-only; code-review PASSED (3 fixes; "use 2-arg ctor" finding rejected — 1.1.0 has no such overload). — LIVE prod 2026-06-13 (on-device QA pending)POST .../wipe → Device-Admin wipeData(0) (internal only, keeps FRP). Triple-guarded: admin opt-in (disclosed "lock & erase") + dashboard type-to-confirm (ERASE) + handleWipe fresh+once (consume BEFORE wipe, ±3min). 180/180 tests; code-review PASSED. Persistence-after-reset designed (FRP baseline; Device-Owner → #206). FMD suite COMPLETE. — LIVE prod 2026-06-13 (on-device QA pending)knn-v0-cell, ≥50 m, ½ confidence) + carrier name captured per scan; 11 tests, WiFi path byte-identical — LIVE prod 2026-06-10m0-eval.py clusters held-out points into sites (--site-radius-m) + best-dense-site verdict; dashboard /accuracy page does the same over leave-one-out — multi-site data reads cleanly — 2026-06-10Tech-debt + risks surfaced reviewing how Kefi holds together. Tracked as #176–#183. Solid & intentionally untouched: multi-tenancy isolation, BFF auth + cross-realm wall, GDPR erasure/export/retention, Stripe tenant billing, audit trail.
| # | Item | Sev | Size | Why it matters |
|---|---|---|---|---|
| ✅ | S | GDPR Art. 13/7 — ConsentGivenAt + required consent checkbox (web + landings), LIVE 2026-06-06 w/ Phase 13 | ||
| ✅ | XL | per-tenant Stripe Checkout (organizer's own key, AES-GCM at rest) + per-tenant signed-webhook route → idempotent mark-Paid / refund→Cancelled; manual links unchanged — LIVE prod + E2E 2026-06-08 | ||
tenant_id claim | ✅ | S | TenantIsolationGuardMiddleware 403s an authed tenant principal with no tenant_id (super-users/platform-admins exempt) — LIVE 2026-06-07, lifecycle E2E green both clusters | |
| ✅ | M | BlobJson + schemaVersion on all 4 blobs; malformed parse logged, ahead-version warned — LIVE 2026-06-07 | ||
SubscriptionPlanCode | ✅ | S | SubscriptionPlanCodes single source + fail-loud Normalize; webhook store rejects unknown codes — LIVE 2026-06-07 | |
| #181 | Audit kefi-api endpoints for claim enforcement ✅ | MED | S | all 57 endpoints explicit posture; reflection guard test; no gaps — DONE 2026-06-07 |
RoleNames constants (no magic-string roles) | ✅ | XS | Web KefiRoles now aliases Core KefiManagedRoles (single source) + guard test — LIVE 2026-06-07 | |
ProVideoBooking to its event | ✅ | XS | Event FK already existed; added composite FK (AttendeeId, EventId) so dancer/payer links can't span events — LIVE 2026-06-07 |
| Product | Status | Pending to be complete (size) | Detail doc |
|---|---|---|---|
| Cross-cutting · Auth | ✅ complete | — | login-methods-status.md |
| Kefi (KUCY, UBS) | ✅ live; feature-complete | Phase F publish ✅ done (#159); email lifecycle ✅ done (#184, +consent #176, +IMAP E2E #185); attendee-payment automation ✅ done (#177); per-event publish ✅ done (#4 — /t/{slug}/{eventSlug}); custom domains ✅ done (#5 — subdomain CNAME); seams/hardening ✅ ALL (#176–#183) — feature-complete, see table above | roadmap/kefi.md |
| Katalogos | ❓ needs map | login ✅; feature-completeness pass S | roadmap/katalogos.md |
| Erevna | 🚧 live; ~65% | public respondent flow ✅ + analytics v1 ✅ + 10 question types & validation ✅ (06-11); next: embeds + funnel/crosstab analytics | roadmap/erevna.md |
| Poueni | 🚧 live; mapping in progress | 🔴 core accuracy: real hold-out walk done 2026-06-15 — one site 1.08 m (proven where data overlaps); pooled 157 m was a COVERAGE failure (73% no_neighbours, 2 sites mixed), NOT the model. FORMAL <5m gate still needs ONE dense single-site walk (needs a phone) M; guided calibration wizard + session-scoped eval proposed to make that walk un-mis-collectable M–L; positioning is now multi-signal + measurable: tap-to-pin (v0.15.0) ✅, WiFi-signal warning (v0.16.0) ✅, WiFi-first kNN + cell fallback (v0.17.0) ✅, richer cell capture + timing-advance distance prior (v0.18.0) ✅, dashboard /accuracy WiFi-vs-cell split + pinned/carrier ✅ all live; GDPR erasure/export ✅ live; cold-start onboarding wizard M; Play Console (#175) 🚧 prepped (account/submit = user) M; cell calibration (post-walk) S; NL-query LLM slice L; federation aspirational XL | roadmap/poueni.md |
untracked). Depth lives in the linked *-status.md / *-completeness.md docs; this file is the graphical overview & source of truth for "what's left".