Sostituito il vecchio monorepo a due cartelle (server/, client/,
requirements.txt, moduli main:app senza namespace) con uv e il layout
attuale (src/backend, src/frontend/flask_app, tre Dockerfile alla radice).
Database Setup: niente più script Python per l'admin — punta alla pagina
/api/setup e ne descrive la disattivazione dopo il primo avvio. Rimossa
la password d'esempio cablata e il comando che stampava database_url
(stampava anche le credenziali).
Docker Deployment: docker compose v2 (non più il trattino), i tre
Dockerfile reali, submodule vendor/visionsuite e VISION_ENGINE_VERSION
obbligatoria alla build. Recommended Architecture/SSL: due percorsi
reali (Traefik con TLS automatico in produzione, Nginx in HTTP semplice
per l'on-premise su LAN), non più un nginx generico con certificato a
mano. Tabella env aggiornata con le variabili introdotte dalla revisione
del 21/09/2026 (APP_DOMAIN, TRUSTED_PROXY_COUNT, WEB_CONCURRENCY,
SESSION_COOKIE_SECURE, VISION_RUN_TIMEOUT_S, VISION_MAX_CONCURRENCY).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
roles e is_admin sono NOT NULL nel DB ma Optional nello schema: {"roles":
null} o {"is_admin": null} passava la validazione Pydantic e falliva al
flush con un 500 non gestito. Ora 422 esplicito. Aggiunta anche la
protezione sull'ultimo admin: un admin non puo' togliersi is_admin da solo
(409) - unico caso raggiungibile via API dato che solo un admin puo'
chiamare l'endpoint, quindi rimuovere il flag a qualcun altro lascia sempre
il chiamante; il controllo sul conteggio resta comunque come difesa in
profondita'.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
--supervisor-password non ha piu' un default committato: se il
capoturno va creato e l'opzione manca, ne genera una casuale
(secrets.token_urlsafe(12)) e la stampa una sola volta.
--conferma-produzione richiesto quando APP_ENV (src.backend.config,
stessa regola di #33) non e' dev/test.
--replace elenca quante Measurement/ProductionRun/ProductionEvent
cancellerebbe e si ferma senza cancellare finche' non si aggiunge --si.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Migrazione 014 (batch_alter_table, nome del vincolo esistente letto con
sa.inspect invece di indovinato) e stessi ondelete nei modelli ORM.
Cancellare un task con esecuzioni o immagini di riferimento non fallisce
più su un vincolo FK MySQL.
MANCA: i file delle immagini di riferimento su disco non vengono
rimossi alla cancellazione del task - tasks.py::delete_task (NON
TOCCARE da questo lotto) cancella con `db.delete(task)` diretto, senza
un punto unico in vision_service da agganciare. `_copy_tasks_to_version`
(recipe_service.py) non copia le immagini di riferimento nel
copy-on-write: nemmeno questo si tocca qui.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
json.JSONDecodeError e' un ValueError: KeyError/JSONDecodeError sulla
risposta del modello finivano nell'except ValueError del router (pensato
per i nostri errori di configurazione, es. API key mancante) e uscivano
come 400 invece di 502. Nuova AIResponseError dedicata in ai_service.py
per contenuto malformato o JSON non valido; recipes.py la traduce in 502,
toccato solo per questo except.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
save_reference_image scrive il file dopo il flush riuscito; il router
lo cancella se il commit successivo fallisce. Aggiunti GET
/api/vision/reference-images?task_id= (lista, Maker) e DELETE
/api/vision/reference-images/{id} (cancella riga e file, Maker).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
action era action=f"{method} {path}" senza limite: un path lungo falliva
l'INSERT (String(100)), inghiottito dal try/except generico, e la riga di
audit spariva in silenzio. Troncata a 100 caratteri. ip_address usava
request.client.host, che dietro Nginx/Flask e' sempre il proxy: ora usa
RateLimitMiddleware._client_ip(request), lo stesso risolutore proxy-aware
del rate limiter (X-Forwarded-For con trusted_proxy_count).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Nuova impostazione app_env (env APP_ENV, default "dev"). All'istanza di
Settings, se app_env non è "dev"/"test" e SERVER_SECRET_KEY o
DB_PASSWORD sono ancora il valore committato, RuntimeError che nomina
la variabile. Dev e test continuano a partire senza .env: il default
"dev" non fa mai scattare la guardia.
Le parti frontend (flask_app/config.py) e deploy (docker-compose.yml)
di questa issue sono degli altri lotti (B2, C2).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
README Quick Start: clone con --recurse-submodules (nota sulla chiave SSH per
git.tielogic.xyz:222), VISION_ENGINE_VERSION valorizzata prima di up --build,
comando già descritto più sotto nel documento e ora portato qui.
CLAUDE.md: titolo del layout V2.0.0 -> V3.0.0, aggiunti src/vision,
src/vision_worker, vendor/visionsuite all'albero; nuova sezione «Visione
(V3.0.0)» con sottomodulo, variabile, worker Python 3.13, comando dei test.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
execute_task rifiuta 422 se il task non è camera_measure, 409 se la
versione non è corrente o la ricetta non è attiva; production_run_id,
quando presente, è validato con measurement_service.validate_run_for_version
(stessa regola di una lettura manuale). Router: lot_number/serial_number
max_length=100, production_run_id gt=0.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
lotNumber/serialNumber erano '{{ lot_number or '' }}': autoescape HTML dentro
uno <script>, non escaping JS/JSON. '&' diventava '&' e finiva salvato nel
DB a ogni misura e a production/start; un lotto con backslash rompeva la
sintassi JS della pagina. |tojson produce un letterale JSON valido sia come JS
che come HTML, come già fa refuseWhileBlocked() più sotto nello stesso file.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
toggleSign() usciva subito a display vuoto: il segno del calibro, che arriva
prima delle cifre, veniva scartato. Ora '-'/'+' impostano il segno in modo
esplicito (non toggle) quando arrivano da tastiera/HID; toggleSign() (bottone
manuale +/-) funziona anche a vuoto.
_trackBurst riconosce l'inizio di una raffica solo dal secondo carattere:
prima ripuliva il display in ritardo, concatenando cifre digitate a mano.
Ora la raffica ripulisce il display fino al proprio inizio quando la pausa
prima di essa è > 300ms. Parte intera oltre 6 cifre: rifiutata con evento
numpad-error invece di essere troncata in silenzio.
Test node in tests/js/test_numpad.js, lanciato da pytest
(tests/test_numpad_js.py, skip se node manca).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
docker-compose.dev.yml (Nginx senza SSL, l'installazione on-premise):
default false. .env.example: default true documentato, con la spiegazione
di quando va a false. La lettura in src/frontend/flask_app/config.py è
del lotto B2, non ancora landata: qui si documenta e si predispone solo
la variabile lato deploy.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
login_user riusa l'api_key esistente invece di rigenerarla ad ogni
login: due postazioni collegate con lo stesso utente restavano
altrimenti disconnesse a vicenda. logout_user diventa no-op sulla key
a meno di all_sessions=True (nuovo parametro, passato da
POST /api/auth/logout?all_sessions=true nel router).
authenticate_user esegue comunque bcrypt.checkpw contro un hash fittizio
quando lo username non esiste, per non rivelare col tempo di risposta
se uno username è registrato.
PUT /api/users/{id}/password azzera l'api_key dell'utente dopo il
cambio, come già fa setup.py:634.
Non introdotto in questa issue: hashing o scadenza della api_key
(annotato come MANCA nel rapporto del lotto).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
update_current_version creava il task di default (nessun task ancora sulla
versione) senza task_type, quindi finiva "note" invece di "drawing" come
fa create_new_version alla riga ~293. Aggiunta la riga mancante.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Decodifica + run_graph girano in asyncio.to_thread sotto asyncio.wait_for
(VISION_RUN_TIMEOUT_S, default 100s, sotto i 120s del backend) -> 504 del
worker allo scadere. /health resta leggero e risponde durante un calcolo
in corso. asyncio.Semaphore(VISION_MAX_CONCURRENCY, default 2): oltre ->
503 "worker busy". vision_service._call_worker inoltra 422/503/504 del
worker con il loro status e motivo invece di appiattirli su 502.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
pyproject.toml allineato al layout V3.0.0 già in uso da README/CLAUDE.md.
uv lock (senza upgrade): cambia solo la riga della versione del progetto,
verificato col diff.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Il controllo ruolo di GET /api/measurements/export/csv ignorava is_admin:
un admin senza MeasurementTec/Metrologist assegnato prendeva 403, a
differenza di ogni altro endpoint che passa da require_role (ruolo o admin).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
kaleido (pio.to_image), WeasyPrint (write_pdf), pdfplumber (extract_text_from_pdf)
e la scrittura del CSV di produzione giravano sincrone dentro handler async:
un PDF pesante o un run grande fermava il worker per tutte le altre richieste.
Ora avvolte in await asyncio.to_thread(...) in report_service.py,
ai_service.py e production_export_service.py.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
_check_password confrontava la password con !=, ora hmac.compare_digest.
Nuova guardia _require_not_initialised: se esiste già un admin,
reset-db, seed, manage-user, change-user-password e init-db rispondono
403 "setup already completed", a meno di settings.setup_allow_after_init
(nuovo, env SETUP_ALLOW_AFTER_INIT, default False). create-admin,
list-users e toggle-user-active restano fuori dalla guardia.
GET /api/setup/status senza password valida restituisce solo
{"initialised": bool}; l'elenco tabelle solo con la password.
Adeguati i test esistenti che seedavano un admin e poi richiamavano un
endpoint ora guardato (test_setup.py, test_station_seed.py: bypassano
la guardia con setup_allow_after_init quando serve testare altro).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Worker: Image.open().convert() in try/except (422 "image is not
decodable"); il grafo deve essere un dict (422 altrimenti); run_graph
distingue costruzione (GraphBuildError -> 422) da esecuzione (500 con
motivo). Router: verifica dei magic bytes (PNG/JPEG/BMP/TIFF/WebP/GIF)
prima di chiamare il worker.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
config.py: guard_default_secret_key, verificata a ogni richiesta (prima di
create_app, TESTING/DEBUG non sono ancora impostati dai fixture di test) —
rifiuta con RuntimeError se CLIENT_SECRET_KEY è rimasto al default committato
fuori da debug/testing. auth.py: session.permanent=True al login (senza,
PERMANENT_SESSION_LIFETIME non aveva mai effetto); /auth/logout solo POST,
con CSRF. Aggiornati i tre punti che lo chiamavano: profile.html e
navbar.html (link -> form POST), auto-logout JS in base.html (fetch POST +
redirect al login).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
_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>
_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>
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>
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>
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>
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>
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>
Residuo pre-monorepo, dipendenze già in pyproject.toml (extra server).
Nessun riferimento altrove nel repo.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
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>
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>
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>
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>
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
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
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
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
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
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
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
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