The hub can't reach the CPE network, so wireless signal now flows through a
new read-only F endpoint that CAN (F sits on that network + already has the
airOS code + device creds):
- services/legacy-bridge/ops_airos.php — token-gated (X-Ops-Token, same secret
as ops_reassign.php), SSRF-safe (only curls a mgmt IP present in F's `device`
table). Resolves device by serial/mac/ip, logs into the CPE (airOS >=8.5
/api/auth or <8.5 /login.cgi), reads status.cgi, returns normalized signal /
signal-AP / CCQ / TX-RX rate / airMAX capacity / model / uptime. v8 gets full
data; v6 falls back to top-level wireless.signal; CCQ clamped to 0-100.
- hub: airosSignal now calls the F bridge FIRST (OPS_LEGACY_URL host +
/app/data/ops_legacy.token), n8n webhook demoted to post-migration fallback.
Shared normalizeAiros() for both paths.
Verified end-to-end (hub -> F -> CPE): NanoBeam 5AC signal -53 / airMAX
140/143 Mbps; v6 NanoStation signal -47; offline CPE -> clean {ok:false}.
Auth reject + SSRF guard verified. docs updated (F bridge primary).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Hub (legacy-dispatch-sync.js):
- publishPreview() + GET /dispatch/legacy-sync/publish-preview: apercu unifie
OPS->F (assignation + fermeture + horaire) + avertissements tech non mappe
= la liste des changements avant du bouton. Lecture seule.
- publishJob() + GET|POST /dispatch/legacy-sync/publish-job: apply par job
(GET=plan dry-run, POST=applique) via ops_reassign.php.
- staleTickets() + GET /dispatch/legacy-sync/stale: tickets ouverts sans
activite >=N j (natif, remplace le courriel stale de F); scope buckets/maxAge en SQL.
- staleNudge() + GET|POST /stale-nudge + digest planifie opt-in
(LEGACY_STALE_NUDGE), anti-spam persiste, nudge via alertWebhook.
Bridge F (ops_reassign.php):
- action schedule: write-back due_date/due_time (horaire OPS->F).
Verifie en prod (lecture seule + dry-run; aucune ecriture F reelle tiree).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le hub deploye (/opt/targo-hub) avait derive du depot (deploiements scp
directs non commites). Rapproche la branche de la realite deployee AVANT
d empiler le nouveau travail:
- legacy-dispatch-sync.js: liens natifs source_issue/depends_on/parent_incident
+ backfills (deployes, jamais commites)
- ops_reassign.php: action post (note/reponse au fil, deployee)
Aucun secret (creds via ops_secret.php). Fichiers = copie exacte du deploye.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Hub (lib/roster.js, vision.js, legacy-dispatch-sync.js, server.js) + Ops + pont legacy.
- Capacité par jour AM/PM (Soir = réserve garde/urgence, jamais offerte) calculée
client-side sur les techs visibles -> suit le filtre de compétence.
- Modèle de durée ADDITIF (caractéristiques, tableur inline) + auto-détection
DÉTERMINISTE par mots-clés (sans IA permanente) ; est_min branché sur capacité + pool.
- Capture terrain passive : endpoints publics /field (job/tech/checkpoint/ts/photo/
device/vision), tokens HMAC signés sans PII ; dérive actual_start/end. UI hébergée
public/field-app.html (liste/carte Mapbox/Street View/photo/scan MLKit->Gemini).
- Chrono job (start/finish), repositionnement carte (set-location), vue satellite,
Street View clic-droit, année devant les dates dues groupées.
- Sync techniciens : rapport de réconciliation 3 systèmes (staff legacy / Dispatch
Technician / groupe Authentik), application MANUELLE, zéro écriture Authentik (+11 fiches).
- vision.js : extractEquipment() réutilisable (marque/modèle/série/MAC/codes-barres).
- Pont legacy (ops_reassign.php) : désassignation reflétée, fermeture ticket, retour
au pool ; notification courriel à l'assignation.
Déployé sur le hub ; ce commit aligne le repo sur l'état en production.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>