The ops tech module at /ops/#/j/* had drifted from the field app in two ways:
1. Scanner — a prior "restoration" re-added html5-qrcode, but the
design has always been native <input capture="environment"> → Gemini
2.5 Flash via targo-hub /vision/barcodes (up to 3 codes) and
/vision/equipment (structured labels, up to 5). Revert useScanner.js
+ ScanPage.vue + TechScanPage.vue to commit e50ea88 and drop
html5-qrcode from both package.json + lockfiles. No JS barcode
library, no camera stream, no polyfills.
2. Equipment UX — TechJobDetailPage.vue was a 186-line stub missing the
Ajouter bottom-sheet (Scanner / Rechercher / Créer), the debounced
SN-then-MAC search, the 5-field create dialog, Type + Priority
selects on the info card, and the location-detail contact expansion.
Port the full UX from apps/field/src/pages/JobDetailPage.vue (526
lines) into the ops module (458 lines after consolidation).
Rebuilt and deployed both apps. Remote smoke test confirms 0 bundles
reference html5-qrcode and the new TechJobDetailPage.1075b3b8.js chunk
(16.7 KB vs ~5 KB stub) ships the equipment bottom-sheet strings.
Docs:
- docs/features/tech-mobile.md — new. Documents all three delivery
surfaces (legacy SSR /t/{jwt}, transitional apps/field/, unified
/ops/#/j/*), Gemini-native scanner pipeline, equipment UX, magic-link
JWT, cutover plan. Replaces an earlier stub that incorrectly
referenced html5-qrcode.
- docs/features/dispatch.md — new. Dispatch board, scheduling, tags,
travel-time optimization, magic-link SMS, SSE updates.
- docs/features/customer-portal.md — new. Plan A passwordless magic-link
at portal.gigafibre.ca, Stripe self-service, file inventory.
- docs/architecture/module-interactions.md — new. One-page call graph
with sequence diagrams for the hot paths.
- docs/README.md — expanded module index (§2) now lists every deployed
surface with URL + primary doc + primary code locations (was missing
dispatch, tickets, équipe, rapports, telephony, network, agent-flows,
OCR, every customer-portal page). New cross-module edge map in §4.
- docs/features/README.md + docs/architecture/README.md — cross-link
all new docs.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
22 KiB
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 (containers, networks, Traefik) · 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/ 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. |
billing-payments.md · cpe-management.md · vision-ocr.md · 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. Plan A: passwordless magic-link JWT, no Authentik. |
billing-payments.md §4 |
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/portal |
(Traefik only) | Not a runtime. Holds 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. 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/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 |
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
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:<name>', ...)
Feature doc: billing-payments.md.
4.2 Tech dispatches a job
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/<JWT>"
│
▼
Tech opens /j/<JWT> 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 (device scan) · vision-ocr.md (OCR pipeline) · flow-editor.md (post-job trigger).
4.3 Customer pays an invoice from the portal
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:<name>', 'invoice.paid', …)
▼
Both apps/ops (cust detail) and apps/client (dashboard) update live over SSE.
4.4 Customer requests support
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
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:<name>`
updates any other ops tab watching the same client.
Feature doc: cpe-management.md.
4.6 Passwordless login (customer)
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 <key>:<secret> (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:<name>, 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. |
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.