Commit Graph

318 Commits

Author SHA1 Message Date
Adriano e8a8aeed66 fix(files): un Maker non cancella statistics/reports/logo, GET ristretto (#21)
DELETE /api/files/{path} non aveva nessun legame con ricetta/proprietario:
un Maker poteva cancellare qualunque file sotto uploads, compresi i CSV di
statistica di produzioni altrui. Ora: statistics/, reports/, logos/ sono
403 sempre (il logo si cambia da PUT /api/settings/logo); un file di
ricetta (task.file_path, subtask.image_path, recipe.image_path) e' 409 se
referenziato da una versione non corrente o che ha gia' misurazioni - un
file non referenziato resta cancellabile. GET di statistics/* e reports/*
richiede Metrologist, Supervisor o admin (prima bastava essere autenticati).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 22:36:49 +02:00
Adriano 20957e517d fix(vision): NaN/inf/bool di uno strumento non diventano più una quota (#16)
runner.run_graph esclude bool e NaN/inf dagli output numerici, con il
motivo in RunOutcome.invalid_outputs. vision_service tratta una quota
mappata con valore None/assente/non finito come "non prodotta" (422),
invece di arrivare a float(None) e sollevare TypeError.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 22:35:53 +02:00
Adriano 10261b66f0 deploy(compose): volume uploads in sola lettura nel container vision (#38)
Il worker di visione legge le immagini di riferimento già caricate dal
server, non le scrive. Montato :ro su entrambi i compose (prod e dev).
GIF/WebP animati nelle miniature: MANCA, decisione di prodotto.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 22:35:44 +02:00
Adriano 7a21ec4e44 deploy(traefik): label Traefik v3, dominio da APP_DOMAIN, middleware anche su tmflow-api (#38)
Punto 11: rimossi SSLRedirect e browserXSSFilter (non esistono più in
Traefik v3), aggiunto frameDeny; rule di entrambi i router da
${APP_DOMAIN:?manca APP_DOMAIN}, non più il dominio cablato. Middleware
tmflow-headers ora applicato anche al router API, non solo al web.

Resto del punto 10: rimosso il PathPrefix(`/uploads/`) dalla rule
dell'API — stesso motivo della location nginx già tolta: nessun mount,
i file passano da /api/files.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 22:35:25 +02:00
Adriano e91ef739af fix(rate-limit): secchio login esteso a credenziali supervisore e setup (#8)
Il secchio "login" del rate limit valeva solo per /api/auth/login. Un
capoturno può ora essere attaccato a forza bruta anche via
/api/measurements/{id}/authorise e /api/production-runs/{id}/pause|
resume|close, senza mai toccare il bucket più permissivo "general".
_is_login_bucket_request confronta il path con un'espressione regolare,
non con ==, perché l'id in mezzo varia per richiesta.

Include anche la parte di #9 che riguarda questo stesso file: i path
sotto /api/setup/ entrano nello stesso secchio, indipendentemente dal
metodo (GET /api/setup/status accetta ora una password opzionale).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 22:34:42 +02:00
Adriano 1921bc3302 deploy(nginx): location statiche limitata a /static/, header ripetuti, rimossa /uploads/ morta (#38)
La regex delle estensioni statiche ora richiede il prefisso /static/, non
qualunque path con quell'estensione. Gli header di sicurezza del blocco
server non si ereditano in un location che ha un proprio add_header
(comportamento nginx): ripetuti lì. Rimossa location /uploads/: verificato
che il backend non la monta, i file passano da /api/files (files.py).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 22:34:14 +02:00
Adriano 67392a946c fix(vision): worker down o in timeout non esce più come 500 nudo (#14)
_call_worker distingue httpx.TimeoutException (504) dal resto dei
TransportError/HTTPError (502, RemoteProtocolError incluso). Un 422 del
worker si propaga come 422 con il motivo del worker, non più 502 generico.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 22:34:06 +02:00
Adriano a330f2b340 fix(rate-limit): non fidarsi del primo hop di X-Forwarded-For (#7)
_client_ip usava il primo elemento di XFF, impostabile dal client. Ora
usa l'elemento a -trusted_proxy_count (nuova impostazione, default 1,
env TRUSTED_PROXY_COUNT), quello aggiunto dal proxy fidato più esterno.
Con trusted_proxy_count=0 XFF e X-Real-IP sono ignorati.

Aggiunta anche web_concurrency (env WEB_CONCURRENCY, default 1): i
bucket del rate limit sono per processo, con --workers N il limite
configurato viene diviso per N (arrotondato per eccesso, minimo 1) in
_effective_limit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 22:34:02 +02:00
Adriano 1fee29358f fix(auth): 401 dal backend svuota la sessione e segnala session_expired (#25)
api_client._handle_response: su 401 pulisce la sessione Flask e marca
g.session_expired. Un solo after_request (app.py) trasforma le pagine HTML
rimaste in piedi con un errore generico in un redirect al login con flash;
le route JSON/AJAX che già inoltrano il dict di api_client (measure.py,
statistics.py) ricevono session_expired nel corpo insieme al 401.
Backend (riuso della key, bcrypt a tempo costante) resta al lotto A2.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 22:33:40 +02:00
Adriano ac9bc14f5d deploy(compose): healthcheck su server e client, depends_on condizionati (#38)
server: healthcheck su /api/health. client: healthcheck su /auth/login
(pagina di login, raggiungibile senza sessione). depends_on passa a
condition: service_healthy dove dipende da server/client (client->server,
nginx->server+client in dev). Aggiunge WEB_CONCURRENCY=4 e
TRUSTED_PROXY_COUNT=1 nell'environment del server (li legge il backend).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 22:33:32 +02:00
Adriano 0dce2299d0 fix(csv): neutralizza injection di formule nei campi testuali export (#22)
lot_number, serial_number (export manuale e di produzione), station.code,
recipe.code, subtask.description finivano nel CSV cosi' come arrivavano:
un lotto '=HYPERLINK(...)' apriva un link vivo in Excel/LibreOffice. Nuova
neutralise_csv_cell() in production_export_service.py, importata da
measurements.py: se il campo (spazi iniziali esclusi) comincia con =, +, -,
@, TAB o CR antepone un apice, cosi' resta testo letterale. Applicata solo
ai campi testuali, mai ai numeri.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 22:33:32 +02:00
Adriano d1833ba203 deploy(docker): utente non root e versioni base pinnate (#38)
Punto 1: useradd -u 10001 app, chown /app (+ /app/uploads sul server),
USER app in tutti e tre i Dockerfile. Stesso UID sul server e sul worker
di visione, che condividono il volume uploads in sola lettura (punto 13).

Punto 2: python:3.11.16-slim (server, client), python:3.13.15-slim
(vision), ghcr.io/astral-sh/uv:0.10.9 (versione locale di `uv --version`).
Dockerfile.frontend: TailwindCSS ora si compila in uno stage separato
dall'immagine ufficiale node:20.20.2-bookworm-slim, non più con
`curl | bash` su nodesource nell'immagine finale.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 22:32:18 +02:00
Adriano 925ed196dd fix(csrf): token valido quanto la sessione, errorhandler JSON per fetch (#12)
WTF_CSRF_TIME_LIMIT=None: la pagina di misura resta aperta ore, il token a
1h scadeva a metà turno. Aggiunto errorhandler(CSRFError) in app.py: risposte
JSON/fetch (Accept, Content-Type, X-Requested-With) ricevono 400 JSON con
csrf_expired, le altre un flash + redirect. Lato B1 (task_execute.html) resta
da gestire client-side, fuori da questo commit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 22:30:39 +02:00
Adriano 48089bcba6 chore(repo): rimuove src/backend_temp/requirements.txt (#38)
Residuo pre-monorepo, dipendenze già in pyproject.toml (extra server).
Nessun riferimento altrove nel repo.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 22:30:20 +02:00
Adriano a6d555d07a deploy(gitignore): uploads/* con eccezione .gitkeep, non più l'elenco (#38)
Copre anche le sottocartelle create a runtime (vision reference images,
cartelle per ricetta/versione), non solo le quattro elencate a mano.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 22:30:13 +02:00
Adriano ea9da34899 fix(settings): valida csv_delimiter/decimal_separator/auto_logout_minutes (#23)
PUT /api/settings/ non validava nulla: un csv_delimiter vuoto o di piu'
caratteri arrivava intatto nel DB e faceva fallire csv.writer con TypeError
dentro close_run (500, run mai chiuso). Aggiunta validazione per chiave nota
(csv_delimiter 1 carattere fra , ; TAB |, csv_decimal_separator ',' o '.',
auto_logout_minutes vuoto o intero 1-480, company_name/company_logo_path
non vuoti) -> 422 sui valori fuori dominio. Chiavi non note passano senza
validazione (store generico).

Difesa in profondita' in production_export_service._csv_format: un
csv_delimiter gia' sporco nel DB (scritto prima di questa validazione) non
fa piu' fallire close_run, usa ';' e logga un WARNING.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 22:29:35 +02:00
Adriano 51f3fe32d4 fix(xss): dati query in x-data passano da tojson_attr, non interpolazione JS (#10)
select_recipe.html e recipe_list.html interpolavano recipe/lot/serial/search
dentro un letterale JS con apici singoli: un valore con `'` chiudeva la
stringa e iniettava chiavi nell'oggetto x-data (CSP con unsafe-eval).
Anche recipe_list.html:222 (toLocaleDateString) aveva lo stesso schema su
current_language. Fix: |tojson_attr ovunque.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 22:29:05 +02:00
Adriano 920ab2fb73 deploy(compose): segreti obbligatori in produzione, senza default (#33)
docker-compose.yml: DB_ROOT_PASSWORD, DB_PASSWORD, SERVER_SECRET_KEY,
CLIENT_SECRET_KEY senza default, `config` si rifiuta se mancano.
docker-compose.dev.yml resta con i default (sviluppo). .env.example
documenta l'obbligo in produzione.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 22:28:50 +02:00
Adriano 7249cb8a8c fix(spc): Cp/Cpk usa sigma within (MRbar/1.128), Pp/Ppk la stdev campionaria (#19)
Prima Cp/Cpk usava stdev campionaria e Pp/Ppk la population stdev: stesso
indice due volte, proporzionale. Ora Cp/Cpk (capability a breve termine)
usa sigma_within dalla moving range dei valori consecutivi in ordine
cronologico; Pp/Ppk (performance complessiva) resta sulla stdev campionaria
(n-1). MRbar == 0 -> Cp/Cpk None. Stessa sigma_within per UCL/LCL della
carta di controllo a valori individuali (mean +/- 2.66*MRbar), non piu la
stdev globale che allargava i limiti e nascondeva la deriva.

Aggiunto campo opzionale std_dev_within a CapabilityData.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 22:27:06 +02:00
Adriano 8cc5e8266a docs: il capitolo 3 dichiara che il buco e' colmato, e dove
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JyL1sTGpFxx7Qw3xP58eoZ
2026-09-14 12:46:41 +02:00
Adriano 665d25e574 docs: la V1 e' fatta, e due cose che la specifica dava per buone non reggevano
Il ponte e' in VisionSuite (6f5990c, issue #2 chiusa). Corretti due punti di
questo documento: PyTorch in vs-dxf non c'e' piu' da L2, quindi quel lavoro
non esisteva; e la posa iniziale non serve, misurato — l'ICP converge da solo
fino a 45 gradi. Aggiunta la trappola dell'angolo ambiguo sui pezzi
simmetrici, che e' esattamente il caso dei profilati di Trafilo, e il limite
degli ingressi di scansione non collegabili.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JyL1sTGpFxx7Qw3xP58eoZ
2026-09-14 12:45:59 +02:00
Adriano 401457d21f docs: in fiera ci va anche la V2, e il grafo si compone con un editor a nodi
Decisione A chiusa il 14/09, e revoca la revoca del 09/08: allora il
committente era Trafilo e voleva misurare profilati, adesso il modulo e
un investimento e allo stand guarda chi deve immaginarci il proprio pezzo.
Il taglio per la fiera arriva fino alla misura vera; expected_json resta
dopo. La V3, che e cio che Trafilo ha pagato, scala indietro.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JyL1sTGpFxx7Qw3xP58eoZ
2026-09-14 10:29:26 +02:00
Adriano 4adad9ab9b docs: il modulo di visione è un investimento Tielogic, non una voce di commessa
Decisione F chiusa da Adriano il 14/09: non lo paga nessuno, serve a
prendere lavori futuri. Ne discende che l'unica scadenza del modulo è la
fiera, che Tràfilo va servito col perimetro venduto, e che le ore vanno
separate per non falsare il margine della commessa.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JyL1sTGpFxx7Qw3xP58eoZ
2026-09-14 09:59:51 +02:00
Adriano f930fd98f0 docs: stato verificato della visione su V3.0.0 e specifica di cosa manca
Verificato sul codice, non sui piani: le fondamenta del 16-17/08 ci sono
tutte, ma pattern matching e DXF compare non sono eseguibili dal grafo —
vs-task registra dieci strumenti, tutti geometrici, e vs-dxf non è
nemmeno negli extra. Il ponte va costruito in VisionSuite.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JyL1sTGpFxx7Qw3xP58eoZ
2026-09-14 09:18:11 +02:00
Adriano 39a6f62089 fix(test): prima parte vuol dire tracciato, non fuori da vendor
Il controllo sui riferimenti esterni scandiva anche i file che git non
conosce. Una copia di comodo di una libreria lasciata in static/js/ --
fabric-debug.js -- sembrava codice nostro e faceva fallire la prova con gli
URL nei propri commenti, ma non sta nel repository e in reparto non ci
arriva mai. Su un checkout pulito la prova passava: falliva solo su chi
aveva quel file, cioe' proprio su chi stava lavorando.

Il filtro ora e' la tracciabilita', non il percorso. Non i pattern di
.gitignore, che non dicono nulla su un file gia' tracciato. Se git non
risponde si scandisce tutto come prima: una prova-guardia che ammutolisce
quando perde l'appoggio e' peggio di una che grida al lupo.

Verificato che morda ancora: un fetch verso un CDN in un file tracciato la
fa fallire.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014BBnuACZSCJqXrMYC3LUMU
2026-08-17 11:02:40 +02:00
Adriano f20b92bafb test(vision): la suite non lascia piu' immagini nel repository
Il file di test dei limiti di upload scriveva un jpeg vero in
uploads/vision/reference/ a ogni esecuzione: gli mancava la fixture di
dirottamento che test_vision_reference.py ha gia'.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014BBnuACZSCJqXrMYC3LUMU
2026-08-16 20:28:35 +02:00
Adriano 6bfe3a5d1a docs(spec): registra le correzioni imposte dall'esecuzione
Il gate del fuori tolleranza vale per l'acquisizione e non per la quota:
la regola era pensata per la misura sequenziale e su un evento simultaneo
produceva un blocco permanente.

Piu' il bi-ambiente Python, la versione del motore timbrata al build, la
motivazione della divisione degli extra corretta, e due cose promesse e non
mantenute -- expected_json e image_path -- dichiarate come consegne del
piano 1b invece che lasciate come intenzioni.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014BBnuACZSCJqXrMYC3LUMU
2026-08-16 20:00:10 +02:00
Adriano 5717b3e7aa docs(readme): aggiorna il conteggio dei test, con l'ambiente in cui vale
Il README dichiarava ancora 360 pass, 0 fail - numero superato dal
lavoro su questo branch. Aggiornato a 390 pass, 1 fail, 4 skip, con
l'ambiente in cui vale (Python 3.11, uv run pytest, senza l'extra
vision) esplicitato accanto: un conteggio senza il suo ambiente non
dice nulla, regola che il progetto applica già alla sezione del worker
di visione poco sopra. Il fallimento riportato è preesistente e
indipendente da questo lavoro (test_offline.py, copia locale di
Fabric.js non tracciata da git).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014BBnuACZSCJqXrMYC3LUMU
2026-08-16 19:49:21 +02:00
Adriano 85d7976f4e docs(vision): commenta perché camera resta fuori dal pattern client
MeasurementCreate.input_method accetta solo usb_caliper|manual anche se
l'enum ORM e il database permettono ormai camera. È voluto: una misura
da camera deve nascere in vision_service.execute_task, mai da un client
che posta JSON direttamente - non c'è dietro né immagine, né grafo, né
engine_version. Commento aggiunto perché non venga "corretto" tornando
alla lettera dell'enum.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014BBnuACZSCJqXrMYC3LUMU
2026-08-16 19:49:21 +02:00
Adriano a72e3f11af test(vision): chiude il punto cieco del confine, "Acquire" era un fantasma
FORBIDDEN confrontava solo il primo segmento del path importato: "from
src.vision.runner import ..." dava radice "src", mai vietata (il
backend importa se stesso di continuo), quindi il test non l'avrebbe
mai vista. Confermato manualmente: la logica vecchia su quell'import
restituisce un insieme vuoto di violazioni. Ora si confrontano i path
puntati per intero contro "src.vision"/"src.vision_worker" come
prefissi, non solo la prima radice.

"Acquire" in FORBIDDEN era un nome di classe/SDK (Balluff "mvIMPACT
Acquire"), mai una radice di import: non poteva mai far scattare nulla.
vs-camera importa sotto lo stesso namespace visionsuite di vs-core
(vendor/visionsuite/packages/vs-camera/pyproject.toml: "il codice si
importa come visionsuite.camera...."), già coperto da "visionsuite":
non c'era una radice separata da aggiungere, quindi è stato tolto.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014BBnuACZSCJqXrMYC3LUMU
2026-08-16 19:45:00 +02:00
Adriano 46566ebfa5 fix(vision): build fallisce senza VISION_ENGINE_VERSION, healthcheck vero
Un build senza la variabile produceva un'immagine che partiva e
rispondeva "healthy" pur fallendo ogni richiesta, /health incluso: non
c'era un healthcheck a dirlo. Tre correzioni: RUN test -n
"$VISION_ENGINE_VERSION" in Dockerfile.vision dopo l'ARG, fallisce
subito con il comando da lanciare; healthcheck su /health nel servizio
vision di entrambi i compose; VISION_ENGINE_VERSION documentata in
.env.example accanto a VISION_WORKER_URL, che già c'era.

Verificato con docker build reale: senza la variabile fallisce al passo
del test con il messaggio atteso; con la variabile impostata l'immagine
si costruisce, il container parte, e python3 -c
"urllib.request.urlopen('http://localhost:8100/health')" - lo stesso
comando usato nell'healthcheck - risponde 200 con l'engine_version
giusta.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014BBnuACZSCJqXrMYC3LUMU
2026-08-16 19:43:20 +02:00
Adriano 0bd95367bb fix(vision): limite di tipo e dimensione sui tre endpoint di upload
/execute, /preview e /reference-images leggevano l'immagine senza
limite di tipo o dimensione, e /reference-images scriveva su disco con
estensione .png fissa qualunque fosse il tipo reale. Riusa la
convenzione già esistente in files.py (ALLOWED_IMAGE_TYPES,
validate_file_size) invece di inventarne una seconda: tipo controllato
dall'header prima di leggere, dimensione controllata sul corpo letto,
un PDF non è qualcosa che un grafo di visione può misurare.
save_reference_image ora scrive con l'estensione del tipo reale
dell'upload.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014BBnuACZSCJqXrMYC3LUMU
2026-08-16 19:41:14 +02:00
Adriano 38ef0587f5 fix(vision): il guasto del worker resta suo, il gate anticipa il worker
I2: _call_worker lasciava propagare httpx.ConnectError e ReadTimeout
(worker fermo o job oltre i 120s), senza handler in main.py: l'operatore
vedeva un 500 nudo. Ora ConnectError->502, ReadTimeout->504, ciascuno con
un messaggio che nomina il guasto. La forma della risposta è validata
alla frontiera: outputs/failures/engine_version mancanti o un body non-
dict non fanno più KeyError, rispondono 502 "malformed".

M3: il gate del fuori tolleranza (pending_authorisation) girava dopo
_call_worker: un operatore bloccato bruciava un'intera esecuzione di
visione prima di ricevere il 409. Spostato prima della chiamata al
worker; le quote necessarie erano già caricate prima, nessun'altra query
serviva.

Copertura: test_vision_worker_transport.py (nuovo) per i quattro casi di
guasto/malformazione; test_vision_execute.py aggiunge
test_a_pending_fail_blocks_before_the_worker_is_called, rossa prima dello
spostamento del gate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014BBnuACZSCJqXrMYC3LUMU
2026-08-16 19:38:56 +02:00
Adriano 533edc5be8 fix(vision): il grafo di visione sopravvive a copy-on-write e creazione
_copy_tasks_to_version copiava una lista esplicita di campi che ometteva
vision_json e vision_output: la prima modifica a una ricetta con misure
produceva una versione nuova con i task camera senza grafo. Stessa
famiglia di bug nei percorsi di creazione (POST /api/recipes/{id}/tasks,
POST /api/tasks/{id}/subtasks): i campi erano dichiarati negli schemi ma
mai passati ai costruttori ORM, quindi l'API rispondeva 200 senza
salvare nulla. TDD: test_vision_copy_on_write.py fallisce prima del fix,
passa dopo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014BBnuACZSCJqXrMYC3LUMU
2026-08-16 19:36:18 +02:00
Adriano 3870cc3b0f feat(vision): immagini di riferimento, per comporre senza camera
VisionReferenceImage (migration 013, chained after 012_vision_results):
task_id, path, station_id, device_code, calibration_snapshot,
engine_version, note, expected_json, acquired_at. station_id/device_code/
calibration_snapshot/engine_version/expected_json stay nullable - nessun
endpoint di questo task li popola ancora, arrivano coi Piani 1b/2/4.

save_reference_image salva sotto settings.upload_path (non upload_dir,
come production_export_service). preview esegue il grafo e non salva
niente - comporre non e' misurare, lo asserisce il test.

POST /api/vision/reference-images e /api/vision/preview su require_maker
(non require_measurement_tec di execute): comporre e provare sono lavoro
del Maker, non misure.

Suite intera su Python 3.11.15 locale (uv run pytest -q, SQLite
in-memory per il backend): 1 failed, 373 passed, 4 skipped in 82.35s.
Il fallimento e' quello preesistente e non correlato di
test_no_first_party_script_calls_out (fabric-debug.js locale,
git-ignored) - 371 passed prima di questo task, +2 qui.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014BBnuACZSCJqXrMYC3LUMU
2026-08-16 19:11:47 +02:00
Adriano 398af016f8 test(vision): il confine fra server e VisionSuite è sorvegliato 2026-08-16 19:03:16 +02:00
Adriano b86eeada86 fix(vision): il gate del fuori tolleranza vale per l'acquisizione, non per la quota
Un'acquisizione camera produce N quote nello stesso istante: non c'e "andare
avanti" fra l'una e l'altra, quindi il gate sequenziale di save_measurement
non deve scattare dentro il lotto. execute_task valuta pending_authorisation
una sola volta per l'intera acquisizione (bloccando solo se il fail pendente
non appartiene alle quote di questo task) e passa enforce_tolerance_gate=False
a ogni save_measurement del lotto. Query delle quote ordinata per
marker_number, cosi l'esito non dipende dall'ordine di ritorno di SQLite/MySQL.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014BBnuACZSCJqXrMYC3LUMU
2026-08-16 18:58:46 +02:00
Adriano ddf7788d77 feat(vision): il server esegue tramite worker e salva misure vere
Aggiunge POST /api/vision/execute: route l'immagine al worker di visione,
mappa le uscite del grafo sulle quote e le salva con save_measurement -
stesso verdetto, stesso gate del fuori tolleranza di ogni altra misura.
Nuova tabella vision_results (una riga per acquisizione, non per quota) e
input_method 'camera' su measurements.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014BBnuACZSCJqXrMYC3LUMU
2026-08-16 18:40:12 +02:00
Adriano 05f2937611 fix(vision): dichiara le dipendenze del worker, pin numerico HTTP vs runner
Ruling R9: separa l'extra vision (runner: 4 pacchetti VisionSuite + numpy)
da vision-worker (fastapi, uvicorn, pillow, python-multipart, sopra vision).
Prima il worker risolveva solo perche' vs-pm2d le lista per conto suo; un
domani lo stub di stazione dovra' incorporare il runner senza trascinarsi
dietro un server web che non gli serve.

Aggiorna Dockerfile.vision e README.md al nuovo extra; il test del worker ora
pinna anche il valore numerico del diametro (non solo la chiave), agganciato
alla stessa tolleranza del test in-process del runner.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014BBnuACZSCJqXrMYC3LUMU
2026-08-16 18:28:06 +02:00
Adriano 71da162e1f feat(vision): il worker che espone il runner, versione stampata al build
Container FastAPI separato (Dockerfile.vision, python:3.13-slim) che
espone run_graph/engine_version del Task 2 via POST /run e GET /health,
cosi' l'immagine del server principale non importa mai VisionSuite.

engine_version() ora legge VISION_ENGINE_VERSION se impostata, altrimenti
ricade su git rev-parse nel checkout di sviluppo, e non inventa mai un
valore: senza nessuna delle due solleva un errore esplicito. Nel container
il fallback a git non puo' funzionare (.git del submodule punta fuori dal
build context), quindi Dockerfile.vision prende il commit come build arg
e lo fissa in ambiente; i compose file lo passano da VISION_ENGINE_VERSION.

Nessuna porta pubblicata e nessuna label Traefik sul servizio vision: e'
raggiungibile solo dal server, su tmflow-net.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014BBnuACZSCJqXrMYC3LUMU
2026-08-16 18:12:04 +02:00
Adriano 9387e7c306 fix(vision): traduce in inglese docstring e commenti di test_runner.py
R7: i commenti trascritti verbatim dal brief erano in italiano, contro il
vincolo globale "comments in English". Solo prosa toccata - nomi dei test,
assert, valori, struttura invariati. runner.py verificato: gia' in inglese,
nessuna modifica.
2026-08-16 17:52:56 +02:00
Adriano 0a3d1f5222 fix(vision): aggancia i test alla suite di default, skip pulito su 3.11
testpaths include src/vision/tests: prima uv run pytest (365 test) non ci
passava mai. Niente conftest.py: un pytest.importorskip a livello di modulo
in conftest.py rompe l'intera sessione, perche' Skipped eredita da
BaseException e _importconftest cattura solo Exception (verificato con
traceback, provato sia su src/vision/tests sia su src/vision come radice).
La guardia sta invece in cima a test_runner.py, prima di numpy (assente
anch'esso su 3.11) - idioma standard di pytest, cattura pulita a livello di
collection. README aggiornato con il comando reale per farli girare.
2026-08-16 17:39:57 +02:00
Adriano c5f3366dd1 feat(vision): il runner, unica implementazione dell'esecuzione
Aggiunge il sottomodulo vendor/visionsuite (pin f095fcc), src/vision/runner.py
(run_graph/engine_version, pura libreria: niente HTTP, niente DB) e i test.

Deviazione dal brief: vs-task/vs-measure/vs-pm2d dichiarano
requires-python>=3.13 a monte, mentre il monorepo resta a 3.11. L'extra
`vision` porta marker `python_version>='3.13'` cosi' la sync di base
(server+client+dev, 3.11) resta intatta; per lavorare sulla vision serve
`uv sync --extra vision --extra dev --python 3.13`. Ricade sul Task 3: il
worker (Dockerfile.vision) non puo' partire da python:3.11-slim come previsto
dal suo brief, deve usare 3.13.
2026-08-16 17:28:09 +02:00
Adriano 9546ded1e8 fix(vision): allinea la label del task_type al valore corretto dxf_compare 2026-08-16 17:13:26 +02:00
Adriano b578ac9e1b feat(vision): il grafo vive sul task, la quota nomina la sua uscita 2026-08-16 17:10:20 +02:00
Adriano cb0fab8c92 chore: ignora lo scratch di subagent-driven-development
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014BBnuACZSCJqXrMYC3LUMU
2026-08-16 17:01:43 +02:00
Adriano 767bede43b docs(plan): fondamenta della visione lato server
Sei task TDD: grafo sul task, runner condiviso, worker separato,
esecuzione con salvataggio misure, prova del confine server/VisionSuite,
immagini di riferimento.

Primo dei quattro piani. Si collauda interamente via API, senza camera.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014BBnuACZSCJqXrMYC3LUMU
2026-08-16 16:42:25 +02:00
Adriano 245975791f docs(spec): design dell'integrazione VisionSuite
Otto decisioni dal brainstorming, con il motivo di ognuna: motore su
entrambi i lati con versione in lockstep, agente sottile, server che non
importa mai VisionSuite, device a livello PC, esecuzione decisa dalla
stazione, verdetto tenuto fuori dal grafo, sorgente immagine astratta.

Modello dati, API, errori e le quattro domande che restano aperte.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014BBnuACZSCJqXrMYC3LUMU
2026-08-16 16:29:05 +02:00
Adriano Dal Pastro e0f32fe8a9 docs: la scaletta di collaudo, in ordine e con i numeri già scelti
Tredici punti su quindici sono in esercizio e nessuno li ha percorsi. Finora il
collaudo era «provare le ricette COLLAUDO-A e B», che è un invito a scoprire da
soli in che ordine e con quali valori — cioè a perdere mezza giornata prima di
misurare qualcosa di utile.

Ordinata perché ogni prova prepari la successiva, e perché le due che vanno fatte
adesso vengano prima: il layout, che si vede solo da un monitor largo, e le
richieste di rete, ora che la Content-Security-Policy è appena entrata in vigore.

Due dettagli verificati sul codice prima di scriverla, perché mandavano a caccia
di pulsanti inesistenti: il timer non parte finché non si preme «Avvio
Produzione», che compare solo dopo il primo ciclo confermato — il primo pezzo è
attrezzaggio, non produzione; e una quota già presa si rimisura cliccandola in
alto, che è la via d'uscita dal blocco fuori tolleranza senza chiamare nessuno.

Detto anche cosa la scaletta non copre, incluso il fatto che su una VPS pubblica
staccare la rete non prova niente: si guarda dove vanno le richieste, e il giro
vero si fa in reparto.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 22:08:40 +00:00
Adriano Dal Pastro 2101b9b2c9 docs: porta stato e roadmap ai fatti del 28/07
Erano fermi allo snapshot V2.0.0 di fine aprile: parlavano di Fasi rev04 da
iniziare, di quattro test rotti e di uno stack che scaricava le librerie da CDN.
Niente di tutto questo è più vero, e un documento di stato sbagliato è peggio di
un documento di stato assente — qualcuno ci si fida.

STATO_PROGETTO: i quindici punti con il loro stato e dove vivono nel codice, cosa
è entrato in V3.0.0 raggruppato per tema, le dieci migrazioni, la suite a 360
pass. In fondo la sezione che conta di più, «cosa non è stato provato»: tredici
punti sono in esercizio e nessuno li ha percorsi su un tablet, il punto 14 non è
mai stato riprodotto su un dispositivo, il punto 12 non è mai stato provato a rete
staccata. Serve a non confondere «i test passano» con «funziona in reparto».

ROADMAP: riscritta intorno a quello che resta e in che ordine — il collaudo prima
di tutto, perché è l'unico lavoro che non aspetta risposte da nessuno, con la
tabella di cosa guardare e come si vede che è giusto. Poi il punto 4 (due o tre
giorni, a decisioni chiuse), l'innesto GAIA e l'installazione di settembre.

Le decisioni aperte sono ora le D-1…D-9 del documento del 28/07, non più le
D-0.x di aprile: la corrispondenza è scritta, così chi torna sui vecchi documenti
non si perde. Stessa cosa per le sette Fasi rev04, con dove è finita ciascuna:
due assorbite, una sostituita, una ridimensionata dal fatto che una rete isolata
rende l'aggiornamento automatico privo di senso.

Lo snapshot V2.0.0 è conservato in docs/archive/ invece di essere sovrascritto.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 22:03:40 +00:00