# Module Interactions > The cross-cutting "who talks to whom" reference for the Gigafibre FSM > codebase. Every feature doc links here for dependency questions instead of > duplicating a matrix of its own. > > Sibling docs: [overview.md](overview.md) (containers, networks, Traefik) · > [data-model.md](data-model.md) (ERPNext doctypes). --- ## 1. Purpose The codebase is split into a handful of frontends (`apps/`), a Node.js API gateway (`services/targo-hub/`), a couple of side-car services, and a long tail of external integrations (ERPNext, Stripe, Twilio, GenieACS, etc.). Any given feature crosses three or four of these layers, which means the interesting question is rarely "what does module X do" but "when module X does its job, who does it call, and who reacts to the side-effect?". This document is the single authoritative answer to that question. Feature docs in [../features/](../features/README.md) describe a capability end-to-end and link back here for the cross-module picture. If you are adding a new module or moving an existing call, update this file first. --- ## 2. Module catalog ### 2.1 Apps (frontends) | Module | Host / Route | Owns | Feature doc | |---|---|---|---| | `apps/ops` | `erp.gigafibre.ca/ops/#/*` | Staff PWA — Dispatch, Clients, Tickets, Équipe, Rapports, OCR, Téléphonie, Agent Flows, Réseau, Settings. Also serves the tech mobile tree `/j/*` (same bundle, different layout). Routes registered in [`apps/ops/src/router/index.js`](../../apps/ops/src/router/index.js). | [billing-payments.md](../features/billing-payments.md) · [cpe-management.md](../features/cpe-management.md) · [vision-ocr.md](../features/vision-ocr.md) · [flow-editor.md](../features/flow-editor.md) | | `apps/client` | `portal.gigafibre.ca/#/*` | Customer portal PWA — Dashboard, Invoices, Tickets, Messages, Account, Catalog/Cart, payment return landings. Hash router, routes in [`apps/client/src/router/index.js`](../../apps/client/src/router/index.js). Plan A: passwordless magic-link JWT, no Authentik. | [billing-payments.md §4](../features/billing-payments.md) | | `apps/field` | `/field/` (legacy) | Original Quasar tech app. Being retired in favour of `apps/ops` `/j/*` + SMS magic-link. See §8. | — | | `apps/website` | `www.gigafibre.ca` | React + Vite marketing site. Lead-capture form posts to targo-hub `/api/checkout` / `/api/order`. Built from [`apps/website/`](../../apps/website/). | — | | `apps/portal` | (Traefik only) | Not a runtime. Holds [`traefik-client-portal.yml`](../../apps/portal/traefik-client-portal.yml) which permanently redirects the legacy host `client.gigafibre.ca` → `portal.gigafibre.ca`, so stale SMS and bookmarks keep working. | — | ### 2.2 Services (backends we own) | Module | Host / Port | Owns | Notes | |---|---|---|---| | `services/targo-hub` | `msg.gigafibre.ca:3300` | API gateway / monolith. Routes registered in [`services/targo-hub/server.js`](../../services/targo-hub/server.js). Every external integration (Twilio, Stripe, Mailjet, Gemini, GenieACS, Oktopus, Traccar) is reached *through* the hub — frontends never hit those directly. | Lib catalog below. | | `services/modem-bridge` | `:3301` (internal) | Playwright + headless Chromium driving TP-Link XX230v web UI to read encrypted TR-181 parameters the vendor exposes only via client-side JS. Token auth, internal network only. | [`services/modem-bridge/server.js`](../../services/modem-bridge/server.js) | | `services/docuseal` | `sign.gigafibre.ca` | DocuSeal container for commercial-contract e-signature. Residential contracts use a JWT-based acceptance flow (see `lib/acceptance.js` + `lib/contracts.js`) — DocuSeal is the commercial track. | — | | `services/legacy-db` | `10.100.80.100:3307` | Read-only MariaDB bridge into the old PHP `gestionclient` database. Used by migration scripts and a handful of lookup queries in `lib/auth.js`. | [`services/legacy-db/docker-compose.yml`](../../services/legacy-db/docker-compose.yml) | #### targo-hub library map (`services/targo-hub/lib/`) One file per capability; the router in `server.js` dispatches by path prefix. | File | Mount point | Responsibility | |---|---|---| | `acceptance.js` | `/accept*` | Residential contract acceptance tokens — JWT sign + signed-blob storage on Quotation. | | `address-search.js` | (lib) | Calls the Supabase `rddrjzptzhypltuzmere` RQA address database for autocomplete. | | `agent.js` | `/agent/*` | LLM agent runtime + tool-use dispatch (reads `agent-tools.json`). | | `ai.js` | `/ai/*` | Internal AI decision-making powered by Gemini Flash (classification, summaries, suggestions). | | `auth.js` | `/auth/*` | Staff auth bridge — reads Authentik headers, optionally checks legacy `gestionclient` MySQL for migration. | | `checkout.js` | `/api/catalog`, `/api/checkout`, `/api/accept-for-client`, `/api/order`, `/api/address`, `/api/otp` | Onboarding wizard: catalog → cart → OTP → order. Creates ERPNext Lead/Quotation/Customer. | | `config.js` | (lib) | Environment variable accessor with typed defaults. | | `contracts.js` | `/contract*` | Signed-contract lifecycle — residential JWT and commercial DocuSeal. | | `conversation.js` | `/conversations*` | Threaded inbox: SMS, email, web chat → ERPNext Communication / HD Ticket. | | `device-extractors.js` | (lib) | Pure functions to extract WAN/LAN/WiFi/optical facts from GenieACS device JSON. | | `device-hosts.js` | (lib) | `Device.Hosts.Host.{n}.*` TR-181 host list parser. | | `devices.js` | `/devices`, `/acs/*` | GenieACS NBI proxy — device lookup, presets, tasks, diagnostic summary. | | `dispatch.js` | `/dispatch*` | Dispatch Job assignment scoring, tech-to-job matching. | | `email-templates.js` | (lib) | HTML templates for OTP, magic-link, invoice email. | | `email.js` | (lib) | SMTP transport (Mailjet `in-v3.mailjet.com` per `config.js`). | | `flow-api.js` | `/flow/start`, `/flow/advance`, `/flow/complete`, `/flow/event`, `/flow/runs/*` | HTTP endpoints for the flow runtime. | | `flow-runtime.js` | (lib) | Execution engine for Flow Templates — `dispatchEvent()` is how modules fire triggers. | | `flow-templates.js` | `/flow/templates*` | CRUD for Flow Template doctype. | | `helpers.js` | (lib) | HTTP client, ERPNext REST wrapper (`erpFetch`, `erpRequest`), JSON response helper. | | `ical.js` | `/dispatch/ical-token/:tech`, `/dispatch/calendar/:tech.ics` | Signed-token iCal feed — lets techs subscribe their jobs in Apple/Google Calendar. | | `magic-link.js` | `/magic-link*` | JWT sign/verify + one-time magic-link issuance. | | `modem-bridge.js` | `/modem*` | Thin client for the modem-bridge sidecar. | | `network-intel.js` | `/network/*` | InfluxDB + Grafana log analysis → Gemini summarization. | | `oktopus-mqtt.js` | (worker) | MQTT subscriber to the Oktopus broker — compensates for their broken events-controller hook. | | `oktopus.js` | `/oktopus/*` | Oktopus CE REST API — TR-369/USP device preauth and provisioning. | | `olt-snmp.js` | `/olt*` | SNMP poller for ONU status / optical power on the OLTs. | | `otp.js` | (lib) | One-time-password issuance + email/SMS delivery (used by checkout + portal). | | `outage-monitor.js` | `/webhook/kuma` | Uptime-Kuma webhook ingest; cross-references OLT poller to synthesize mass-outage alerts. | | `payments.js` | `/payments*`, `/webhook/stripe` | Stripe Checkout session creation, customer portal links, webhook verification, Payment Entry reconciliation, PPA cron. Fires `on_payment_received` flow trigger. | | `pbx.js` | `/webhook/3cx/call-event` | 3CX call-event webhook → ERPNext Communication. | | `portal-auth.js` | `/portal/*` | Passwordless magic-link endpoint `POST /portal/request-link` — rate-limited, anti-enumeration. | | `project-templates.js` | (lib) | Hard-coded job-template catalogue (fibre_install, etc.) for dispatch. | | `provision.js` | `/provision/*` | Device provisioning orchestrator — GenieACS preset push + Oktopus preauth. | | `referral.js` | `/api/referral/*` | Referral credit validation + application, backing the wizard. | | `reports.js` | `/reports*` | GL / revenue / tax / A/R reports, Frappe PostgreSQL queries. | | `sse.js` | `/sse`, `/broadcast` | Server-Sent Events fan-out. Topics: `customer:*`, `conversations`, `dispatch`, etc. | | `tech-absence-sms.js` | (worker) | Auto-notify customers when their tech is absent. | | `tech-mobile.js` | `/t/*` | Lightweight mobile tech page (predecessor of `/j/*`). | | `telephony.js` | `/telephony/*` | Routr / identity SIP pool management. | | `traccar.js` | `/traccar*` | Traccar REST API proxy (GPS fleet tracker). | | `twilio.js` | `/send/sms`, `/webhook/twilio/sms-*`, `/voice/*` | Twilio SMS send + inbound webhook + Voice tokens + TwiML. | | `vision.js` | `/vision/barcodes`, `/vision/equipment`, `/vision/invoice` | Gemini 2.5 Flash vision OCR endpoints. | | `voice-agent.js` | `/voice/inbound`, `/voice/gather`, `/voice/connect-agent`, `/voice/ws` | Twilio IVR + agent runtime with Media Streams WebSocket. | ### 2.3 External integrations | Provider | Used from | Channel | Purpose | |---|---|---|---| | ERPNext v16 / Frappe | hub (`helpers.erpFetch`), ops (nginx proxy), client (via hub) | REST `/api/resource`, `/api/method` | Source of truth for Customer, Subscription, Invoice, Ticket, Dispatch Job, etc. | | Authentik SSO | Traefik ForwardAuth middleware | Header injection | Guards staff surfaces (`/ops/`, `n8n.`, `hub.`). | | Twilio | `lib/twilio.js`, `lib/voice-agent.js` | SMS / Voice (REST + webhook + Media Streams WS) | SMS magic-links, OTP, IVR, Voice tokens. | | Stripe | `lib/payments.js` | REST + webhook (`Stripe-Signature`) | Checkout sessions, Billing Portal, card-on-file, Klarna BNPL. | | Mailjet | `lib/email.js` (SMTP `in-v3.mailjet.com`) | SMTP | Transactional email (OTP, invoices, magic-links). | | GenieACS | `lib/devices.js`, `helpers.nbiRequest` | NBI REST / TR-069 | Modem provisioning + diagnostics (legacy CPE fleet). | | Oktopus CE | `lib/oktopus.js`, `lib/oktopus-mqtt.js` | REST + MQTT (TR-369/USP) | New CPE fleet. | | Traccar | `lib/traccar.js` | REST + WebSocket | GPS breadcrumbs for field techs. | | Gemini 2.5 Flash | `lib/vision.js`, `lib/ai.js` | REST (`generativelanguage.googleapis.com`) | Vision OCR + internal AI. | | n8n | (via Authentik header proxy) | REST | Workflow automation — long-running batch jobs. | | DocuSeal | `sign.gigafibre.ca` | REST + webhook | Commercial contract e-signature. | --- ## 3. Interaction matrix Row = caller, column = callee. Cell = primary channel (`REST`, `SSE`, `WS`, `MQTT`, `SMS`, `SMTP`, `TR-069`, `TR-369`, `Webhook`, `JWT`, `ForwardAuth`, iCal). Empty = no direct interaction (use the hub as a relay). | caller ↓ / callee → | ops | client | field | hub | ERPNext | GenieACS | Oktopus | Traccar | Twilio | Stripe | Gemini | Authentik | DocuSeal | |---|---|---|---|---|---|---|---|---|---|---|---|---|---| | **ops** | — | — | — | REST + SSE | REST (nginx proxy w/ token) | — (via hub) | — (via hub) | WS (via hub proxy) | — (via hub) | — (via hub) | — (via hub `/vision`) | ForwardAuth | — | | **client** | — | — | — | REST + SSE | — (via hub) | — | — | — | — (via hub) | — (via hub `checkout-link`) | — (via hub) | JWT (magic-link, not Authentik) | — | | **field** | — | — | — | REST | REST (legacy, token) | — | — | — | — | — | — | — | — | | **website** | — | — | — | REST (`/api/checkout`, `/api/order`) | — (via hub) | — | — | — | — | — | — | — | — | | **hub** | SSE | SSE | — | — | REST (`erpFetch`, `Authorization: token ...`) | REST (NBI `nbiRequest`) | REST + MQTT | REST + WS | SMS REST + Webhook | REST + Webhook (`Stripe-Signature`) | REST | — | REST + Webhook | | **modem-bridge** | — | — | — | — (hub calls it, `:3301`) | — | — | — | — | — | — | — | — | — | | **ERPNext** | — | — | — | — (fire-and-forget HTTP to hub `/flow/*` from server scripts) | — | — | — | — | — | — | — | — | — | | **Traefik** | Forwards | Forwards | Forwards | Forwards | Forwards | — | — | — | — | — | — | ForwardAuth | — | | **Twilio** | — | — | — | Webhook (`/webhook/twilio/*`) | — | — | — | — | — | — | — | — | — | | **Stripe** | — | — | — | Webhook (`/webhook/stripe`) | — | — | — | — | — | — | — | — | — | | **DocuSeal**| — | — | — | Webhook (`/contract/*`) | — | — | — | — | — | — | — | — | — | --- ## 4. Canonical flows Six flows cover 90 % of the interactions. Each is grounded in the hub lib file that drives it. ### 4.1 New customer onboarding ```text apps/website (React lead form) │ POST /api/order (lib/checkout.js) ▼ targo-hub ──► address-search.js ──► Supabase RQA ──► otp.js ──► Twilio SMS + Mailjet SMTP ──► erpFetch ──► ERPNext (Lead → Quotation → Customer) ──► payments.js ──► Stripe Checkout session │ │ user pays ▼ Stripe ──webhook──► hub /webhook/stripe (lib/payments.js, Stripe-Signature verify) ├─► ERPNext Payment Entry ├─► flow-runtime.dispatchEvent('on_payment_received') │ ├─► contracts.js (DocuSeal or JWT accept) │ └─► dispatch.js (create Dispatch Job) └─► sse.broadcast('customer:', ...) ``` Feature doc: [billing-payments.md](../features/billing-payments.md). ### 4.2 Tech dispatches a job ```text apps/ops /dispatch (DispatchPage.vue) │ drag-drop assignment, POST /dispatch/assign (lib/dispatch.js) ▼ hub ──► ERPNext Dispatch Job (scoring weights in dispatch.js) ──► twilio.sendSms → customer cell: "/j/" │ ▼ Tech opens /j/ on phone (apps/ops/src/modules/tech/*) │ JWT verified by magic-link.js │ GPS check-in → traccar.js → Traccar REST → WS breadcrumbs back to ops │ Scan device barcode → useScanner → /vision/barcodes → Gemini │ Mark complete → flow-runtime.dispatchEvent('on_job_complete') ▼ ops SSE topic `dispatch` refreshes the Kanban ``` Feature docs: [cpe-management.md](../features/cpe-management.md) (device scan) · [vision-ocr.md](../features/vision-ocr.md) (OCR pipeline) · [flow-editor.md](../features/flow-editor.md) (post-job trigger). ### 4.3 Customer pays an invoice from the portal ```text apps/client /#/invoices/INV-0000123 (InvoiceDetailPage.vue) │ click "Payer maintenant" │ POST /billing/checkout-link (lib/payments.js) ▼ hub ──► Stripe Checkout session (mode=payment, client_reference_id=INV-…) │ browser redirects to Stripe, user pays ▼ Stripe ──webhook──► hub /webhook/stripe ├─► ERPNext Payment Entry ├─► flow-runtime.dispatchEvent('on_payment_received') └─► sse.broadcast('customer:', 'invoice.paid', …) ▼ Both apps/ops (cust detail) and apps/client (dashboard) update live over SSE. ``` ### 4.4 Customer requests support ```text apps/client /#/messages (MessagesPage.vue) │ POST /conversations (lib/conversation.js) ▼ hub ──► ERPNext HD Ticket (erpFetch) ──► sse.broadcast('conversations', …) ▼ apps/ops /tickets (TicketsPage.vue) — live refresh │ staff replies → conversation.handle → ERPNext Communication ▼ hub ──► twilio.sendSms (if customer channel = SMS) or email.js (if email) ``` ### 4.5 Modem diagnostic from ops ```text apps/ops /network (NetworkPage.vue) │ GET /devices/lookup?serial=… (lib/devices.js) ▼ hub ──► GenieACS NBI (helpers.nbiRequest) │ if TP-Link XX230v → ask modem-bridge :3301 for encrypted params ▼ modem-bridge ──► Playwright headless Chromium ──► modem web UI (172.17.x.x:443) ──► decrypted TR-181 JSON back to hub ▼ hub ──► device-extractors.summarizeDevice → consolidated JSON ▼ ops UI renders WAN / LAN / WiFi / Optical; SSE topic `customer:` updates any other ops tab watching the same client. ``` Feature doc: [cpe-management.md](../features/cpe-management.md). ### 4.6 Passwordless login (customer) ```text apps/client /#/login (LoginPage.vue) │ POST /portal/request-link (lib/portal-auth.js) │ rate-limited, anti-enumeration (constant-time response) ▼ hub ──► otp.js / magic-link.js (JWT HS256, 24h exp) ──► twilio.sendSms ("Votre lien: https://portal.gigafibre.ca/#/?token=…") ──► email.js (SMTP Mailjet) ▼ customer clicks link → apps/client router guard hydrates store from token → store.customerId set → dashboard loads ``` --- ## 5. Authentication matrix | Surface | Mechanism | Source of trust | Notes | |---|---|---|---| | Staff apps (ops, n8n, hub UI) | Authentik ForwardAuth via Traefik `authentik-client@file` middleware | `id.gigafibre.ca` session cookie | Headers `X-Authentik-Email`, `X-Authentik-Groups` injected into upstream. | | Customer portal (`apps/client`) | 24 h HS256 JWT (magic-link) | `lib/magic-link.js` secret | No Authentik; no password form reachable (Traefik redirects legacy `/login`). | | Tech mobile (`/j/*` inside `apps/ops`) | 24 h HS256 JWT sent by SMS | Same secret | Same JWT primitive as portal. | | hub → ERPNext | `Authorization: token :` (Frappe API token) | ERPNext Administrator token — never regenerate ("Generate Keys" in ERPNext UI breaks the hub) | Used by `helpers.erpFetch`. | | ops → ERPNext (frontend) | nginx same-origin reverse proxy injects the same token server-side | Token stays server-side; browser never sees it | See `apps/ops/infra/`. | | Webhooks | Per-provider signature verification | `Stripe-Signature`, `X-Twilio-Signature`, DocuSeal shared secret | Rejected synchronously before any side-effect. | | `modem-bridge` | Shared bearer token | env var on both hub + bridge containers | Internal network only, not exposed via Traefik. | | Traccar | Bearer token preferred (fall-back Basic) | env var on hub | See `lib/traccar.js` header note. | --- ## 6. Data ownership Source of truth per entity. "Hub cache" means the hub keeps a read-through copy; the authoritative write goes to the listed owner. | Entity | Source of truth | Hub role | Frontend read path | |---|---|---|---| | Customer | ERPNext `Customer` | thin pass-through (`erpFetch`) | ops: direct via nginx proxy · client: via hub | | Address (Service Location) | ERPNext `Service Location` | pass-through; `address-search.js` also calls Supabase RQA for autocomplete | — | | Subscription | ERPNext `Subscription` | pass-through | — | | Invoice | ERPNext `Sales Invoice` | pass-through + `/vision/invoice` OCR | — | | Payment | ERPNext `Payment Entry` | written from Stripe webhook in `lib/payments.js` | — | | Ticket | ERPNext `HD Ticket` | written from `lib/conversation.js` | — | | Dispatch Job | ERPNext `Dispatch Job` (custom doctype) | written from `lib/dispatch.js` | ops SSE topic `dispatch` | | Technician | ERPNext `Technician` (custom) | pass-through | — | | Service Equipment | ERPNext `Service Equipment` (custom) | pass-through | — | | Tag | ERPNext `Tag` | pass-through | — | | Flow Template | ERPNext `Flow Template` (custom) | CRUD in `lib/flow-templates.js` | ops `/agent-flows` | | Flow Run | ERPNext `Flow Run` (custom) | written from `lib/flow-runtime.js` | ops `/agent-flows` | | **Exceptions** | | | | | Live modem state (WAN IP, SNR, host list) | GenieACS / Oktopus | hub never persists — always fetched live | — | | GPS breadcrumbs | Traccar | hub proxies WS + REST | ops `/dispatch` map | | JWT sessions | hub process memory + browser `localStorage` | — | no server-side session store | --- ## 7. Real-time channels | Channel | Transport | Producer | Consumer | Topic / payload | |---|---|---|---|---| | Ops / portal live updates | SSE | `lib/sse.js` | `apps/ops`, `apps/client` | `customer:`, `conversations`, `dispatch`, ad-hoc | | TR-369 device events | MQTT | Oktopus broker | `lib/oktopus-mqtt.js` worker | device-online, heartbeat | | GPS breadcrumbs | WebSocket | Traccar | `lib/traccar.js` proxy → ops dispatch | `deviceId`, `latitude`, `longitude`, `speed` | | Twilio Media Streams | WebSocket upgrade on hub `/voice/ws` | Twilio | `lib/voice-agent.js` | raw audio frames for IVR agent | | SSE keep-alive | server-pushed `: ping` | `lib/sse.js` 25 s interval | browsers | heartbeat | --- ## 8. Retired / in-retirement Do not build new features on these. Linked target is where the replacement lives. | Retiring | Replacement | Status | Notes | |---|---|---|---| | `auth.targo.ca` | `id.gigafibre.ca` (Authentik) | active migration | ForwardAuth middleware already on the new host. | | `apps/field` | `apps/ops` `/j/*` tree | in retirement | Same bundle, different layout — removes the separate deploy surface. | | `dispatch-app` (legacy standalone) | `apps/ops` `/dispatch` | retired | Feature parity reached; decommission pending DNS cleanup. | | `client.gigafibre.ca` | `portal.gigafibre.ca` | retired | Traefik permanent-redirect via [`apps/portal/traefik-client-portal.yml`](../../apps/portal/traefik-client-portal.yml). | | Frappe `/login` form for customers | `/portal/request-link` magic-link | retired | Legacy MD5 hashes not migrated — customers forced through the new flow. | | Ollama local `llama3.2-vision` | `lib/vision.js` → Gemini 2.5 Flash | retired | Ops VM has no GPU. | --- Back to [docs/README.md](../README.md).