Skip to content

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:


1. Foundational decisions ​

#DecisionChoiceRationale
D1Relationship 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.
D2Primary backendOne Go (Gin) API for the whole relational/CRUD/governance/GACx/validation domain.Single API surface; Go's concurrency suits sync + SSE streaming.
D3Async workerPython (Celery + FastAPI) scoped only to document ingestion + embeddings.Python's libraries (PyMuPDF, chunking) earn their place there and nowhere else.
D4Authorization modelRole-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.
D5Auth providerSupabase 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.
D6Vector storepgvector (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.
D7GACxInternal 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.
D8Realtime/notificationsSupabase Realtime (Postgres change feed).Broadcasts cross-user (e.g. MP alerted on member registration) without extra infra.
D9v1 scopeMVP core first, then phase (see Β§10).Shortest path to a real, usable backend; AI/GACx-movement/pulse/OKR/voting follow.
D10HostingAngular β†’ 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.
D11FrontendLatest 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.
D12AI modelsHaiku 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.
D13API documentationOpenAPI 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.
D14Document β†’ ingestion formatConvert 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_messages schema β†’ 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 with rate/duration/assignee/scope/status and 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| CL

Request flows ​

  1. Login β€” Angular β†’ Google OIDC β†’ Supabase Auth β†’ JWT (with role) β†’ Go API role-guard.
  2. CRUD β€” Angular β†’ Go API crud[T] β†’ Supabase Postgres (MP/Admin: all rows Β· Member: own).
  3. Ask (RAG) β€” Angular β†’ Go /mandates/:id/ask β†’ embed (Voyage) β†’ pgvector top-k β†’ context + live tasks/OKRs/pulse β†’ Claude β†’ SSE β†’ browser-only render.
  4. Ingest β€” upload β†’ Go (202 Accepted) β†’ Upstash queue β†’ Python worker β†’ convert (PDF/Word/Excel β†’ Markdown) β†’ chunk β†’ embed β†’ pgvector β†’ mark ready.
  5. GACx β€” task transitions to Passed β†’ Go gacx β†’ double-entry posting (Supabase).
  6. 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-guard middleware enforces membership_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 heartbeat

Python 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 init runs 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 name claim 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:

RoleModel IDInput $/MTokOutput $/MTokContext
Routing / summariesclaude-haiku-4-51.005.00200K
Default Q&Aclaude-sonnet-4-63.0015.001M
Heavy analysisclaude-opus-4-85.0025.001M

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.css at the app root; DaisyUI + Tailwind via PostCSS (drop the CDN injection from app-shell.js). DaisyUI component classes used straight in templates.
  • Schema-driven UI: admin.js SCHEMA β†’ typed models + a generic CrudService<T> + schema-driven table/form components.
  • State: signals; onclick="" β†’ (click); async via httpResource() / 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 to afterNextRender.
  • 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_reward as 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 SettlementProvider interface).

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; httptest for 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.toml per 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.