Appearance
Dispatch β Production Implementation Design β
Status: Draft for review Β· Last updated: 2026-06-15
This document is the agreed production architecture for taking Dispatch β the operating system for an AI-augmented BPO cooperative β from the current design-system reference (design-system/) to a shipping product. It is the input to the implementation plan; it defines intent and architecture, not a task list.
Read alongside:
- design-system/design.md β product + UI spec and the Angular handoff (Β§8). Source of truth for intent.
- design-system/STATUS.md β current state of the reference build.
- docs/implementation/backend-architecture.md β the Contract-Q&A scaffold whose stack we adopt (see Β§1).
1. Foundational decisions β
| # | Decision | Choice | Rationale |
|---|---|---|---|
| D1 | Relationship to docs/implementation/ (Contract-Q&A platform) | Option A β adopt its stack as the Dispatch backend foundation; the document-Q&A pipeline becomes the basis of Dispatch AI. | The scaffold is a proven Go + Python + Supabase + Redis + RAG stack and deploy pattern. It covers only a sliver of Dispatch's domain, so the broader domain is built on the same stack. |
| D2 | Primary backend | One Go (Gin) API for the whole relational/CRUD/governance/GACx/validation domain. | Single API surface; Go's concurrency suits sync + SSE streaming. |
| D3 | Async worker | Python (Celery + FastAPI) scoped only to document ingestion + embeddings. | Python's libraries (PyMuPDF, chunking) earn their place there and nowhere else. |
| D4 | Authorization model | Role-based, not owner-only RLS. | Dispatch members see their own queue; MPs/Admins manage everyone. The scaffold's per-owner RLS is the wrong model and is replaced. |
| D5 | Auth provider | Supabase Auth with Google Workspace (OIDC), hosted-domain restricted. | Provides the JWT the Go API expects, the OIDC name claim for registration prefill (design.md Β§5.4), and the SSO gate. |
| D6 | Vector store | pgvector (in Supabase Postgres), not Pinecone. | One fewer managed dependency; vectors co-located with mandate/document rows; sufficient at co-op scale. A thin vector-store interface keeps Pinecone a drop-in if scale demands. |
| D7 | GACx | Internal double-entry ledger in Postgres, 1:1 USD-pegged unit of account; on-chain settlement deferred behind a SettlementProvider interface. | De-risks launch; the wallet UI already assumes a balances/transactions shape. |
| D8 | Realtime/notifications | Supabase Realtime (Postgres change feed). | Broadcasts cross-user (e.g. MP alerted on member registration) without extra infra. |
| D9 | v1 scope | MVP core first, then phase (see Β§10). | Shortest path to a real, usable backend; AI/GACx-movement/pulse/OKR/voting follow. |
| D10 | Hosting | Angular β Cloudflare (SSG + SSR Worker, CDN/WAF/DNS); Go API + Python worker β Fly.io; Redis β Upstash (managed); DB/auth/storage/realtime β Supabase. | User-chosen split, refined: managed Redis over a self-hosted container; Cloudflare fronts everything. |
| D11 | Frontend | Latest Angular β standalone + signals, zoneless, SSR/SSG β per design.md Β§8. The scaffold's Angular 17 UI is reference only. | The committed target; theme/tokens port unchanged. |
| D12 | AI models | Haiku 4.5 (routing/summaries) Β· Sonnet 4.6 (default Q&A) Β· Opus 4.8 (heavy analysis), with the scaffold's cost-tracking + semantic-cache layer repriced to current rates. | The scaffold pins stale model IDs/pricing (claude-3-5-*, opus-4-6); see Β§8. |
| D13 | API documentation | OpenAPI via swaggo/swag + gin-swagger annotations; Swagger UI served at /swagger. | Standardizes the API contract; the generated spec drives a typed Angular client so FE/BE stay in sync, and CI catches drift between annotations and routes. |
| D14 | Document β ingestion format | Convert PDF / Word / Excel β Markdown (via MarkItDown) as the normalization step before chunking/embedding. | Markdown is a clean, structure-preserving representation for LLM ingestion, and one converter handles all three office/PDF formats. Lands in Phase 2 with Dispatch AI. |
What we adopt vs. correct from the Contract-Q&A scaffold β
- Adopt: Go API + SSE streaming; Python/Celery ingestion; Redis queue; Supabase Postgres + Storage; Voyage AI embeddings; Claude RAG pipeline; the cost-tracking + semantic-cache + model-routing layer; Fly/Upstash deploy pattern.
- Correct: owner-only RLS β role-aware authZ (D4); Pinecone β pgvector (D6); thin
users/mandates/documents/chat_messagesschema β full Dispatch domain (Β§5); Angular 17 reference UI β latest-Angular target (D11); stale Claude model IDs/pricing β current generation (D12). "Mandate" in the scaffold is a generic document folder; in Dispatch it is a client engagement withrate/duration/assignee/scope/statusand derived completion.
2. System topology β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β USERS Member Partners Β· Managing Partners Β· Admins Β· Board β
βββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββββββ
β HTTPS
βββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββ
β CLOUDFLARE β DNS Β· CDN Β· WAF β
β βββββββββββββββββββββββββββββ ββββββββββββββββββββββ β
β β Angular (latest) β β SSG (prerendered) β β
β β standaloneΒ·signalsΒ·zonelessβ β landing/login/reg β β
β β SSR Worker (authed app) β β + static assets β β
β β tokens: styles/bespoke.css β ββββββββββββββββββββββ β
β βββββββββββββββββββββββββββββ β
βββββββββ¬ββββββββββββββββββββββββββββββββ¬ββββββββββββββββββ
REST + SSE β β OIDC redirect
βΌ βΌ
ββββββββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββ
β FLY.io β β GOOGLE WORKSPACE OAuth β
β ββββββββββββββββββββββββββββββ β β (hosted-domain restricted)β
β β GO API (Gin :7070) β β βββββββββββββββ¬βββββββββββββ
β β auth/role-guard β β β JWT + name claim
β β crud[T] (schema engine) β ββββββββββββββββββββββ
β β completion Β· skills/valid β β
β β gacx ledger β β ββββββββββββββββββββββββββββ
β β dispatch-ai (SSE) β βββββββΊβ UPSTASH REDIS β
β β notifications Β· /health β β β job queue Β· rate limit β
β βββββββββββββ¬βββββββββββββββββ β β semantic cache β
β β enqueue ingest β ββββββββββββββββββββββββββββ
β βββββββββββββΌβββββββββββββββββ β
β β PYTHON WORKER (Celery) β β ββββββββββββββββββββββββββββ
β β PyMuPDFβchunkβembedβupsertβ βββββββΊβ VOYAGE AI (embeddings) β
β βββββββββββββ¬βββββββββββββββββ β ββββββββββββββββββββββββββββ
βββββββββββββββΌβββββββββββββββββββ
β SQL / pgvector / storage / realtime ββββββββββββββββββββββββ
βΌ β CLAUDE API β
ββββββββββββββββββββββββββββββββββββββββββββββ dispatch-ai β Haiku 4.5 (routing) β
β SUPABASE ββββββββββββββββΊβ Sonnet 4.6 (default) β
β Postgres + pgvector Β· Storage Β· Auth Β· β SSE stream β Opus 4.8 (heavy) β
β Realtime (change-feed β notifications) β ββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββmermaid
flowchart TB
subgraph CF["Cloudflare β CDN Β· WAF Β· DNS"]
NG["Angular (standaloneΒ·signalsΒ·zoneless)<br/>SSR Worker + SSG prerender<br/>tokens: styles/bespoke.css"]
end
subgraph FLY["Fly.io"]
GO["Go API (Gin :7070)<br/>authΒ·crud[T]Β·completion<br/>skillsΒ·gacxΒ·dispatch-aiΒ·health"]
PY["Python worker (Celery)<br/>extractβchunkβembedβupsert"]
end
RED["Upstash Redis<br/>queue Β· rate limit Β· cache"]
subgraph SB["Supabase"]
PG["Postgres + pgvector"]
ST["Storage"]
AU["Auth"]
RT["Realtime"]
end
GW["Google Workspace OAuth (OIDC)"]
VO["Voyage AI (embeddings)"]
CL["Claude API<br/>Haiku 4.5 Β· Sonnet 4.6 Β· Opus 4.8"]
USERS["Members Β· MPs Β· Admins"] -->|HTTPS| CF
NG -->|REST + SSE| GO
NG -->|OIDC| GW
GW -->|JWT + name| AU
AU --> GO
GO <-->|queue / cache| RED
GO -->|SQL / pgvector| PG
GO -->|publish| RT
RT -->|notifications| NG
PY <--> RED
PY -->|embed| VO
PY -->|vectors| PG
PY --> ST
GO -->|embed query| VO
GO -->|SSE stream| CLRequest flows β
- Login β Angular β Google OIDC β Supabase Auth β JWT (with role) β Go API role-guard.
- CRUD β Angular β Go API
crud[T]β Supabase Postgres (MP/Admin: all rows Β· Member: own). - Ask (RAG) β Angular β Go
/mandates/:id/askβ embed (Voyage) β pgvector top-k β context + live tasks/OKRs/pulse β Claude β SSE β browser-only render. - Ingest β upload β Go (202 Accepted) β Upstash queue β Python worker β convert (PDF/Word/Excel β Markdown) β chunk β embed β pgvector β mark
ready. - GACx β task transitions to
Passedβ Gogacxβ double-entry posting (Supabase). - Notify β registration/event β Supabase Realtime β all MP clients; connection pill polls
/health.
Hosting caveat (SSR placement) β
Angular SSR on Cloudflare runs on the Workers runtime (not full Node), and SSR render-time fetches hop Cloudflare-edge β Fly-region. Mitigate with TransferState + edge caching + a Fly primary region near most members. Fallback if SSR latency or Workers-runtime limits bite: move only the SSR server to Fly beside the API, keeping Cloudflare for SSG/static + CDN. The current Angular-on-Workers adapter status is to be re-verified during implementation.
3. Backend services (Go API) β
The Go API is the single public gateway. Modules:
- auth β verifies the Supabase JWT;
role-guardmiddleware enforcesmembership_role(Member Partner / Managing Partner / Admin). MP-only actions (skill validation) gated server-side. - crud[T] β a generic, typed CRUD engine that mirrors the reference
admin.js SCHEMA: one schema definition drives table columns, create/edit validation, and seed concerns per entity. Adding a field/entity = extend the schema + model. - completion β derives mandate completion live from tasks, weighted by status (Passed 100% Β· Live 60% Β· Progress 50% Β· Queued/Failed 0%). Shared rule, never stored.
- skills/validation β skills catalog, per-user-skill self/validated scores (0.00β10.00, 2 decimals),
coefficient(default 0), and the validation queue. - gacx β the double-entry ledger (see Β§6).
- dispatch-ai β RAG ask endpoint, SSE streaming (see Β§7).
- notifications β writes notifications + publishes via Supabase Realtime.
- health β heartbeat for the connection indicator.
REST surface (mirrors design.md Β§8 routing) β
/organizations CRUD
/mandates CRUD
/mandates/:id/tasks CRUD (tasks scoped to a mandate)
/mandates/:id/documents/upload POST β 202, enqueues ingestion
/mandates/:id/ask POST (SSE) β Dispatch AI
/users CRUD
/users/:id/skills self/validated scores, coefficient
/validation-queue pending skills across all members
/votes CRUD + ballots
/gacx/accounts /gacx/transactions ledger reads + transfers
/notifications list/mark-read
/health connection heartbeatPython worker (narrow) β
Document ingestion only: download from Storage β convert to Markdown (PDF / Word / Excel β MD via MarkItDown) β chunk the Markdown with overlap β embed in batches (Voyage AI) β upsert vectors to pgvector β mark document ready. The Markdown is also retained for citation/preview. At-least-once delivery (Celery task_acks_late), retry with backoff, scale by queue depth.
API documentation (OpenAPI / Swagger) β
The Go API is annotated with swaggo/swag; gin-swagger serves interactive Swagger UI at /swagger/index.html plus the generated swagger.json. The generated OpenAPI spec is the source of the API contract:
- A typed Angular client is generated from the spec (e.g.
openapi-generator/orval) so frontend and backend models stay in sync β no hand-written DTOs. swag initruns in CI; the build fails on drift between annotations and registered routes, keeping the contract honest.- Handler annotations document request/response schemas, auth requirements, and error shapes, standardizing the API surface across the
crud[T]engine and bespoke endpoints (/ask,/gacx/*,/validation-queue).
4. Auth & roles β
- Provider: Supabase Auth, Google Workspace OIDC, restricted to the workspace hosted domain (the SSO gate; the reference simulates this).
- Identity: OIDC
nameclaim pre-fills the registration Profile step (design.md Β§5.4), kept editable. - Token: Go API verifies the Supabase-issued JWT on every protected route; extracts
user_id+membership_role. - AuthZ (role-based, D4):
- Member Partner β sees own dispatch queue, own mandates/tasks, own profile/skills/wallet.
- Managing Partner / Admin β manage all orgs/mandates/tasks/users; run the validation queue; MP-only skill validation.
- Registration β first-run flow writes a pending user with all skills
pending(coefficient 0), lands in the admin Users list, fires the MP notification, populates the validation queue.
5. Domain data model β
All tables carry created_at / updated_at and role-aware policies. Completion is derived, not stored.
ββββββββββββββββ
ββββββββββββ organizationsββββββββββββ
β 1 * ββββββββββββββββ β 1
βΌ βΌ *
βββββββββββββββββ * 1 ββββββββββββββββββββββ 1 * ββββββββββββββββ
β mandates βββββββββββββββ users βββββββββββΊβ user_skills β
β rateΒ·duration β assignee β membership_role β β self_score β
β scopeΒ·status β β functionΒ·org β β validated 0β10β
ββββββββ¬βββββββββ β (Supabase identity)β β coefficient β
1 β * βββββββ¬ββββββββ¬βββββββ β status β
βΌ β β ββββββββ¬ββββββββ
βββββββββββββββββ β β 1 * β *
β tasks β β βΌ βΌ
β statusΒ·due β 1 β ββββββββββββ ββββββββββββββββ
β gacx_reward β completion ββββββ βvote_ballotsβ β skills β
ββββββββ¬βββββββββ (DERIVED) ββββββ¬ββββββ β (catalog) β
β * * β 1 ββββββββββββββββ
βΌ βΌ
βββββββββββββββββ ββββββββββββ ββββββββββββββββββββββββ
β documents β 1 * ββββββββββββ β votes β β pulse_* Β· okrs Β· β
β statusΒ·storageββββββββΊβ chunks β β quorum % β β key_results Β· β
βββββββββββββββββ pgvector vectors ββββββββββββ β notifications β
ββββββββββββββββββββββββ
GACx LEDGER (double-entry, append-only)
ββββββββββββββββ 1 * ββββββββββββββββββββ 1 * ββββββββββββββββ
β gacx_accountsββββββββΊβ gacx_transactionsββββββββΊβ gacx_postingsβ Ξ£ legs = 0
β user/org/treasβ idempotent β debit/credit Β· 1:1 USD peg
ββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββKey fields (from design.md Β§5.3):
- organizations β name, contract, type, mandate count, status.
- mandates β title, org, rate (GACx/h), duration, assignee, status, scope.
- tasks β title, mandate, assignee, due, gacx_reward, status (Queued/Progress/Live/Passed/Failed), updated. A task assigned to a user is referred to as a "dispatch" in product/UI language (the table stays
tasks); the member Dispatch tab is a user's queue of dispatches. - users β name, workspace email (SSO identity), membership_role, function, status, gacx account, skills[].
- user_skills β self_score + validated_score (0.00β10.00, 2 decimals), coefficient (default 0), status (pending/approved/rejected).
- votes / vote_ballots β ref, title, approval %, quorum (voted/eligible), closes, status; one ballot per eligible member.
- documents / chunks β document status + storage path; chunks hold pgvector embeddings + metadata (doc, chunk_index, text, page).
6. GACx ledger β
Double-entry, append-only:
- gacx_accounts β one per user / org / treasury.
- gacx_transactions β idempotent (client-supplied idempotency key), references the business event (e.g. task pass).
- gacx_postings β debit/credit legs; legs of a transaction sum to zero.
Balance = sum of an account's postings (optionally a maintained materialized balance for read speed). 1:1 USD-pegged unit of account. Task β Passed posts the reward. On-chain settlement is deferred behind a SettlementProvider interface (Phase 3) so no ledger rewrites are needed later.
7. Dispatch AI subsystem (Phase 2) β
The Contract-Q&A RAG pipeline becomes Dispatch AI.
Member Angular Go API (dispatch-ai) Voyage pgvector Claude
β question β β β β β
βββββββββββββββΊβ POST /ask (JWT) β β β β
β ββββββββββββββββββββββββΊβ embed(question) ββββββΊβ β β
β β ββββββ vector ββββββββββ€ β β
β β β top-k search βββββββββββββββββββββΊβ β
β β ββββββ chunks βββββββββββββββββββββββ€ β
β β β assemble: chunks + live tasks/OKRs/pulse β
β β β (cache check: Upstash) βββΊ route model β
β β β stream ββββββββββββββββββββββββββββββββββββββΊβ
β β SSE: sources ββββββ token stream (Sonnet 4.6 default) βββββββ€
ββββββββββββββββ€ SSE: tokenβ¦token β record cost + cache (current pricing) β
β (browser- β SSE: done β persist Q&A β
β only render) β β- Ingestion = mandate documents (PDF/Word/Excel) β Markdown (MarkItDown) β chunked β embedded (Voyage) β pgvector, by the Python worker (Β§3).
- Context = retrieved mandate-document chunks (pgvector) plus the member's live structured data (tasks/mandates/OKRs/pulse), assembled into a typed system prompt server-side.
- Models (D12): Haiku 4.5 for summaries/cheap routing Β· Sonnet 4.6 default for Q&A Β· Opus 4.8 for heavy analysis. Prompt-caching on the stable context prefix.
- Cost layer: keep the scaffold's cost-tracking + semantic cache + model router, repriced to current rates (Β§8).
- SSR-safe: the chat renders browser-only (
afterNextRender) β no completion calls during hydration. The Claude API key stays server-side on Fly.
8. Claude models & pricing (current) β
The scaffold pins stale model IDs (claude-3-5-sonnet-20241022, claude-opus-4-6) and matching pricing. Re-pin to current generation:
| Role | Model ID | Input $/MTok | Output $/MTok | Context |
|---|---|---|---|---|
| Routing / summaries | claude-haiku-4-5 | 1.00 | 5.00 | 200K |
| Default Q&A | claude-sonnet-4-6 | 3.00 | 15.00 | 1M |
| Heavy analysis | claude-opus-4-8 | 5.00 | 25.00 | 1M |
The cost-tracking query_metrics / query_cache tables and the model-router keyword logic carry over; only the pricing constants and model IDs change. Use streaming for any large output; prompt-cache the stable prefix.
9. Frontend (Angular) β
- Stack: latest Angular β standalone components, signals, zoneless change detection, SSR + SSG hybrid (design.md Β§8).
- Theme/tokens port unchanged: import
styles.css+bespoke.cssat the app root; DaisyUI + Tailwind via PostCSS (drop the CDN injection fromapp-shell.js). DaisyUI component classes used straight in templates. - Schema-driven UI:
admin.js SCHEMAβ typed models + a genericCrudService<T>+ schema-driven table/form components. - State: signals;
onclick=""β(click); async viahttpResource()/toSignal(). - Rendering: SSG-prerender public routes (landing/login/register entry); SSR the authed app with
provideClientHydration(withEventReplay()). Browser-only concerns (connection indicator, service worker, localStorage cache, Dispatch AI) deferred toafterNextRender. - Mobile template remains a responsive/native design reference, not a literal target.
10. v1 scope & phasing β
PHASE 1 β MVP CORE (go-to-prod) PHASE 2 PHASE 3 (final)
βββββββββββββββββββββββββββββββ ββββββββββββββββββ βββββββββββββββββββββ
βΈ Google Workspace auth + roles βΈ Dispatch AI (RAG) βΈ GACx wallet
βΈ Admin CRUD (orgs/mandates/ βΈ Pulse surveys Β· double-entry ledger
tasks/users) on real backend βΈ OKRs Β· value-movement
βΈ Member app (Dispatch/Mandates/ βΈ Governance voting Β· on-chain settlement
Tasks) (SettlementProvider)
βΈ Skills catalog Β· registration Β· uses: Python worker,
validation queue Voyage, Claude, pgvector, (tasks show gacx_reward
βΈ Completion derivation Upstash cache as display-only until
βΈ Connection indicator Β· PWA this phase)
βΈ CI/CD Β· tests (Go/Py/Angular/e2e)
βββββββββββββββββββββββββββββββ
Foundation built once, all phases:
Cloudflare Β· Fly Β· Upstash Β· Supabase Β· role-aware authZ- Phase 1 (MVP core / go-to-prod): Google Workspace auth + role gating; admin CRUD (orgs/mandates/tasks/users) on the real backend; member app (Dispatch/Mandates/Tasks); skills catalog + registration + validation queue; completion derivation; connection indicator + PWA; CI/CD + tests. Tasks carry
gacx_rewardas a display-only field in this phase (no ledger postings yet). - Phase 2: Dispatch AI (RAG pipeline); Pulse surveys; OKRs; governance voting.
- Phase 3 (final): GACx wallet β the double-entry ledger, value-movement, and on-chain settlement (introducing the
SettlementProviderinterface).
The shared foundation (Cloudflare/Fly/Upstash/Supabase, role-aware authZ) is built once in Phase 1 so later phases add features, not infrastructure. The GACx ledger schema (Β§5, Β§6) lands in the final phase, not the Phase-1 foundation β until then gacx_reward is a plain column displayed on tasks.
11. Testing & verification β
The reference repo has no test runner today; production adds:
- Go API β table-driven unit tests;
httptestfor handlers; integration against Supabase-local / testcontainers. - Python worker β pytest on chunking/embedding with a stubbed Voyage client.
- Angular β unit + signal/component tests; Playwright e2e for critical flows (login β dispatch; registration β MP validation; live vote).
- Shared rules β dedicated tests for completion (weighted task status) and 0.00β10.00 / 2-decimal competence on both Go and Angular sides so surfaces agree.
12. Deployment & CI β
- Cloudflare β Pages/Workers build for Angular (SSG + SSR).
- Fly.io β
fly.tomlper service: Go API (autoscale 1βN), Python worker (always-on, scale by queue depth). - Upstash β managed Redis (queue + rate limit + cache).
- Supabase β Postgres + pgvector + Storage + Auth + Realtime; migrations as SQL under
supabase/migrations. - CI (GitHub Actions) β lint/test/build β deploy. Secrets in Fly/Cloudflare secret stores; Claude + Voyage keys server-side only, never in the browser.
13. Open questions / risks β
- Angular-on-Cloudflare-Workers SSR is the least battle-tested piece β verify the current adapter; SSR-on-Fly fallback (Β§2) is the contingency.
- pgvector at scale β fine for co-op-scale corpora; revisit Pinecone only if query latency degrades (interface keeps this cheap).
- Eligibility/quorum rules for governance voting (Phase 2) need product definition before that phase.
- Pulse anonymity guarantees (Phase 2) need a privacy review before storing responses.
- GACx (final phase) β the wallet, ledger value-movement, and on-chain settlement (chain choice, custody, peg mechanism) are the last workstream; on-chain is a separate, specialized effort out of scope for v1.
Next step: review this spec, then we turn Phase 1 into a step-by-step implementation plan.