Audit strutturale SafeOps: menu, navigazione, ruoli e permessi¶
Data audit: 2026-09-29
Perimetro: checkout PROD /home/safeops/safeops e checkout DEV /home/safeops/safeops-dev.
Metodo: ispezione statica AST/grep, lettura di configurazione e template, lettura di cataloghi FAB e tabelle ACL disponibili. Nessun endpoint è stato modificato, nessuna migration è stata applicata e nessun dato è stato scritto.
1. Executive Summary¶
SafeOps non ha oggi una singola sorgente di verità per la navigazione e l'autorizzazione. La registrazione iniziale delle view è concentrata in app/__init__.py, ma il menu finale è il risultato di più strati: Flask-AppBuilder, menu_service, menu_runtime, policy persistite, monkey patch in bootstrap e classificazione/filtering nei template Jinja. Le ACL cliente/tenant sono invece replicate nei moduli Documentale, Anagrafiche, Formazione, Commerciale, DVR, Ticket e Tecnici.
La superficie è ampia: 349 chiamate add_* individuate staticamente nel bootstrap, 625 ab_view_menu, 1.422 ab_permission_view e 221 nomi di permission distinti nel catalogo PROD letto in sola lettura. Sono presenti 35 ruoli FAB, 62 utenti e 14 profili tenant. Il checkout DEV ha un albero applicativo sostanzialmente analogo ma, come il PROD, contiene modifiche preesistenti non riconducibili a questo audit.
Il rischio principale non è un singolo link errato: è la separazione non documentata tra voce visibile, accesso all'endpoint, accesso ai dati e filtro tenant/cliente. Un add_view_no_menu, un link nascosto o una regola Jinja non costituiscono da soli una protezione dell'endpoint. Prima di qualsiasi restyling o centralizzazione serve una matrice verificata per ruolo, URL/API, tenant e cliente.
2. Baseline analizzata¶
PROD¶
- percorso:
/home/safeops/safeops; - branch:
wip/integration-safeops-20260421; - HEAD:
58230ec9ad7b1a7a5a372c6372230a57c3472dc8; - working tree: dirty, 439 percorsi modificati o non tracciati al momento della rilevazione; non sono stati alterati, ripristinati o messi in stash;
- Python 3.12.6, Flask 3.1.2, Flask-AppBuilder 5.1.0, SQLAlchemy 2.0.36;
- configurazione principale:
config.py, conDATABASE_URLcome sorgente URI e configurazione tenant/module entitlement; - Alembic head disponibile dal repository comune:
0004_dvr_structure_tables; - le statistiche del catalogo FAB riportate in questo documento sono una lettura read-only del database PROD configurato. Nessuna credenziale è inclusa.
DEV¶
- percorso:
/home/safeops/safeops-dev; - branch:
dev/safeops-20260819; - HEAD:
c55d0012821280a4b40f77b4d2f0b735f1334f82; - working tree: dirty, 479 percorsi modificati o non tracciati;
- il checkout DEV contiene gli stessi strati principali di bootstrap, IAM, template e moduli, con divergenze applicative da verificare prima di parlare di allineamento;
- non è stato eseguito alcun collaudo autenticato o query di scrittura sul database DEV. L'assenza di una verifica runtime DEV è un blocker per la fase successiva, non un'indicazione di equivalenza con PROD.
La working tree preesistente rende non affidabile un diff completo PROD↔DEV come prova di allineamento. Un futuro confronto dovrà usare snapshot congelati o commit/artefatti esplicitamente identificati.
3. Architettura attuale¶
Il bootstrap applicativo (app/__init__.py) registra blueprint e centinaia di view/link. La registrazione FAB costruisce un albero iniziale (appbuilder.menu). Successivamente:
app/modules/iam/menu_service.pylegge il menu FAB, policy utente/gruppo/ruolo/tenant e nodi/override strutturali;app/modules/iam/menu_runtime.pyapplica entitlements di modulo e regole speciali per tenant/ruolo;- patch in
app/__init__.pyintercettano redirect, contesto permission/menu e cicli FAB; app/templates/appbuilder/_safeops_menu_tree.htmlriclassifica, nasconde e raggruppa le voci in Jinja;- ogni modulo applica autonomamente permission FAB, ruoli,
tenant_id, cliente e regole di visibilità dati.
Non esiste un registro dichiarativo unico dal quale derivino contemporaneamente menu, permission e ACL. La catena effettiva è quindi:
registrazioni FAB + blueprint
↓
menu_service/menu_runtime + policy DB + patch bootstrap
↓
template Jinja (ulteriore filtering e classificazione)
↓
ACL e permission applicate dalle singole view/API/moduli
4. Mappa completa del menu e della navigazione¶
Punti di costruzione¶
L'AST del bootstrap rileva 349 chiamate add_* in app/__init__.py (tra add_view, add_view_no_menu, add_link, add_separator, add_category); app/views.py mantiene anche una registrazione FAB storica. Sono inoltre presenti nove register_blueprint espliciti per gateway AI, chat, sync/editor documenti, lead, mobile, portale formazione e tutor AI.
Le registrazioni principali sono:
| Area/categoria | Voci e view rappresentative | Metodo/origine | Note |
|---|---|---|---|
| Security / Controllo Accessi / Impostazioni | UserSecuritySettingView, RBAC/IAM, policy, diagnostics, module access |
add_view in app/__init__.py circa 2019–2251 |
area composta da FAB e console IAM parallele |
| Documentale / Import e Sync / Configurazioni | documenti, workflow, template, import, sync e audit | add_view/add_link circa 2366–3010 |
molte view operative sono no_menu e raggiunte da link o URL |
| Operativo / Ticket | ticket, assegnazioni, ODL e tecnici | circa 3012–3285 | registrazioni legacy e nuove convivono |
| Commerciale | clienti, offerte, contratti, preventivi, RAS e servizi | circa 3286–3900 | categorie e nomi ripetuti in più contesti |
| Formazione / Pianificazione | FormazioneDashboardView, CorsoView, Corsi, iscrizioni, discenti, docenti, aule, registro presenze, import, report, scadenze, qualità, WooCommerce |
circa 6377–6730 | numerose view legacy no_menu, link manuali e percorsi /formazione-*//corsoview/* |
| Anagrafiche / Clienti | clienti, aziende, dipendenti, territori | registrazioni distribuite nel bootstrap | accesso e filtro cliente sono anche implementati nelle view |
| Archivi | documenti formazione e archivi speciali | add_link e categorie custom |
categorie condizionali per ruolo/tenant |
Inventario Formazione (tutte le famiglie registrate)¶
Nel blocco formazione risultano, tra le altre, FormazioneDashboardView, DashboardDemo, CalendarDemo, ClientiDemo, CorsoWizard, Corsi, Iscrizioni, CorsistiImport, workspace corsisti, mapping legacy, categorie, libretto, fascicolo, discenti, reportistica, gap, scadenze, tablet, CorsoView, ente formatore, portale discenti, registro presenze, aule, docenti, dipendente-corso, materiale, monitoraggio, iscrizioni WooCommerce, sconti, catalogo, manuale, storico, survey, qualità, audit invii, date/orari ed edizioni. Molte sono registrate con add_view_no_menu e sono esposte tramite link hardcoded o da altre view.
Duplicazioni e fragilità osservate¶
- la stessa capacità è esposta sia come
ModelViewFAB sia come link custom (Corsi/CorsoView, reportistica, materiali, iscrizioni); - esistono categorie e label quasi duplicate (Commerciale, Tecnici, Gestione email, Offerte, Formazione);
- numerosi href sono stringhe manuali (
/formazione-corsi/,/formazione-clienti/,/corsoview/list/, ecc.); - l'ordine delle registrazioni nel bootstrap influenza l'albero e i nomi FAB;
menu_keys.pydeve ripiegare su chiaviunmapped.*quando label/URL non sono riconducibili a una chiave stabile;- il menu policy è basato su label/tenant/subject, quindi una rinomina può scollegare una policy senza errore di tipo;
- il template sidebar aggiunge shortcut amministrativi al di fuori del registro FAB.
5. Mappa permission e autorizzazione¶
Catalogo FAB e dati osservati¶
La lettura read-only PROD ha restituito 35 ruoli FAB, 221 nomi permission distinti, 625 ab_view_menu e 1.422 ab_permission_view. Le PermissionView includono primitive automatiche (can_add, can_delete, can_edit, can_index, can_list, can_show) e numerose azioni custom. menu_access è associato a 337 PermissionView.
Le policy menu persistite contano 140 righe menu_policy, 62 menu_policy_user, nessuna menu_policy_group attiva e un override in menu_structure_override; menu_structure_node è vuota nel catalogo letto. Ciò documenta l'esistenza di più meccanismi, non una loro precedenza unica documentata dall'applicazione.
Primitive e punti di controllo¶
Sono presenti @has_access, @has_access_api, base_permissions, class_permission_name, method_permission_name, is_accessible, permission_name, controlli current_user, controlli di ruolo e controlli tenant/client. menu_service usa la precedenza user → group → role per le policy menu; menu_runtime applica entitlements solo alla visibilità; le view devono quindi ripetere il gate sul percorso reale.
app/modules/iam/policy.py centralizza alcuni helper (current_role_names, is_platform_admin, tenant_context, effective_tenant_key, can_choose_tenant), ma non è l'unico punto di decisione: DVR, SafeCondo, Anagrafiche, Documentale, Formazione, Commerciale, Ticket e Tecnici mantengono policy locali.
6. Ruoli e matrice ruolo → funzione¶
Ruoli presenti¶
I 35 nomi letti comprendono ruoli FAB storici (Admin, Public, GlobalClientManager, ROLE_ADMIN), profili applicativi (ROLE_PROFILE_*), ruoli tecnici/commerciali e template (TEMPLATE_*), oltre a ruoli utente storici. Sono presenti 62 utenti; il report non riporta username o dati personali.
Il profilo tenant è un secondo livello: 14 profili includono admin_tecnico, admin_tenant, amministratore_condominio, cliente, commerciale, gestione, gestore_cliente, operatore_admin, operatore_formazione, operatore_osm, operatore_amministro, readonly, tecnico, utente_condominio. I due profili non di sistema operatore_osm e operatore_amministro risultano nel DB ma non nella lista di default Python osservata: possibile drift storico da verificare.
Matrice comportamentale (baseline da verificare con test)¶
Legenda: S = previsto da ruolo e codice; C = condizionato da tenant/modulo/cliente; N = non previsto; ? = non dimostrabile senza collaudo autenticato per endpoint e dati. La presenza nel menu non equivale all'accesso.
| Funzione | Admin / ROLE_ADMIN | Admin tenant | Tecnico / Admin tecnico | Gestione / Commerciale | Operatore formazione | Amministratore condominio | Cliente / readonly |
|---|---|---|---|---|---|---|---|
| Clienti / Anagrafiche | S | C | C | S | C | C | C |
| Documentale | S | C | C | C | C | C | C |
| RAS / DVR | S | C | S | C | N | C | C |
| Corsi / Formazione | S | C | C | C | S | C | C |
| Preventivi / Commerciale | S | C | N/C | S | N/C | N | C |
| ODL / Interventi | S | C | S | C | N | C | C |
| Calendario | S | C | C | C | C | C | C |
| Chat / portali | S | C | C | C | C | C | C |
| Configurazione moduli | S | S | C | C | C | N | N |
| Amministrazione utenti/RBAC | S | C | N | N/C | N | N | N |
La matrice è una mappa di lavoro, non una certificazione: per ogni cella C/S occorre verificare menu, URL diretto, API, lettura dati e scrittura dati nello stesso tenant e in un tenant diverso.
7. MENU VS PERMESSI¶
La relazione MENU → VIEW → PERMISSION → ROLE → ACL DATI è interrotta in più punti:
add_view_no_menurimuove la voce dal menu ma non costituisce un diniego dell'endpoint;menu_module_state_snapshotdichiara esplicitamente che l'entitlement è display-only e non è un endpoint gate;- Jinja nasconde label in base a ruolo/tenant e riorganizza l'albero dopo che FAB ha già costruito il menu;
- API e blueprint applicano controlli distinti da quelli delle ModelView;
- permission generate automaticamente coesistono con permission custom e label storiche;
- la configurazione
MENU_FILTER_FAIL_OPENpuò restituire il menu completo quando la costruzione filtrata è vuota (menu_service.py, ramo di fallback): questo può esporre navigazione e metadati in caso di errore di configurazione, anche se FAB deve ancora proteggere l'endpoint.
Conclusione: oggi “voce non visibile” e “funzione non accessibile” non sono equivalenti e non devono essere trattate come tali nei test o nel nuovo template.
8. ACL cliente e multi-tenant¶
Il modello combina tenant_id/tenant_key, ruoli FAB, profili tenant, is_platform_admin, entitlements modulo e associazioni utente-cliente. Nel database esistono mapping storici user_cliente/cliente_user (la relazione è rilevata anche nel codice Anagrafiche); non è presente una tabella con un nome unico usato da tutti i moduli.
Le implementazioni si classificano così:
- parzialmente centralizzate: helper in
iam/policy.py,tenant_context, profili ed entitlements; - duplicate:
_visible_*,_effective_tenant_key, set di ruoli e controllicurrent_userripetuti in Anagrafiche, Documentale, Formazione, Commerciale, DVR, Ticket e Tecnici; - divergenti: ogni modulo decide autonomamente se un filtro cliente è obbligatorio, se il platform admin bypassa il filtro e come gestire un tenant mancante;
- legacy: mapping utente-cliente e view
no_menuche usano percorsi propri; - potenzialmente rischiose: filtri applicati dopo la selezione dei dati, policy display-only e fallback fail-open devono essere verificati con dati cross-tenant.
Gli entitlements mostrano che lo stesso modulo può essere attivo in un tenant e disattivo in un altro (ad esempio Formazione attiva in tenant_besant/tenant_retra e disattiva in altri tenant osservati). L'entitlement deve quindi essere testato sia come visibilità sia come diniego server-side.
9. Struttura template e frontend¶
Gerarchia primaria osservata:
FAB base_template / appbuilder/init.html
↓
app/templates/appbuilder/base.html
↓
app/templates/appbuilder/baselayout.html
↓
_safeops_sidebar.html + _safeops_topbar.html + _safeops_topbar_badges.html
↓
_safeops_menu_tree.html
↓
template specifico del modulo
baselayout.html costruisce una shell SafeOps e valuta ruolo, tenant e accesso. _safeops_sidebar.html inserisce shortcut amministrativi hardcoded. _safeops_menu_tree.html contiene macro di rendering ma anche classificazione per parole chiave, liste di label nascoste, condizioni ruolo/tenant e sezionamento commerciale/documentale/tecnico. navbar.html FAB standard rimane nel repository; non è la sola sorgente della navigazione.
CSS/JS SafeOps (in particolare app/static/js/safeops.js) gestiscono ricerca, apertura e toggle del menu. Il JavaScript modifica la presentazione del menu già ricevuto: non deve essere considerato un controllo ACL. Sono presenti riferimenti AdminLTE/Bootstrap e override SafeOps; non è emerso un secondo template AdminLTE3 attivo come fonte unica, ma esistono prove/compatibilità residue da rimuovere solo dopo una mappa di utilizzo.
10. Dipendenze Flask-AppBuilder¶
FAB resta responsabile di SecurityManager, permission automatiche, ViewMenu, PermissionView, ModelView e menu iniziale. SafeOps estende FAB con monkey patch, registrazioni custom, policy DB, entitlements e template. Il bootstrap può anche predisporre tabelle/permission quando DB_AUTO_CREATE è attivo e invoca helper _ensure_*: questo è un side effect di startup che deve essere separato da un futuro registro dichiarativo.
Il codice applicativo dipende sia da nomi FAB generati automaticamente sia da label/URL di presentazione. Questo rende fragile una rinomina o una migrazione di classe: può cambiare il nome della ViewMenu, lasciare PermissionView obsolete o disallineare una MenuPolicy basata su label.
11. Finding e debito tecnico¶
| ID | Severità | Area | Evidenza (file/funzione) | Impatto / dipendenze | Strategia futura (non applicata) |
|---|---|---|---|---|---|
| SEC-001 | CRITICAL | endpoint vs menu | app/__init__.py registrazioni add_view_no_menu; menu_runtime.py display-only |
una funzione può essere raggiunta via URL/API anche se non appare; serve matrice negativa completa | catalogo endpoint con gate server-side verificato |
| ACL-001 | CRITICAL | tenant/cliente | iam/policy.py insieme a policy locali in Anagrafiche, Documentale, Formazione, Commerciale, DVR, Ticket, Tecnici |
rischio di regole divergenti e isolamento non uniforme | servizio ACL comune, migrazione per modulo e test cross-tenant |
| BOOT-001 | HIGH | bootstrap | app/__init__.py, _ensure_*, DB_AUTO_CREATE |
side effect di schema/security all'avvio; refactoring non deterministico | bootstrap read-only in produzione e migration/seed espliciti |
| NAV-001 | HIGH | registro menu | app/__init__.py, circa 349 add_* |
ordine di import, duplicati e dipendenze nascoste | manifest/registry con adapter FAB |
| NAV-002 | HIGH | pipeline menu | menu_service.py, menu_runtime.py, menu_filter.py, patch FAB, template Jinja |
più precedenze e impossibilità di spiegare una voce senza tracing | pipeline unica con decisione backend e audit reason |
| TEMPLATE-001 | HIGH | autorizzazione nel template | _safeops_menu_tree.html, baselayout.html, _safeops_sidebar.html |
logica RBAC/tenant e URL hardcoded duplicano backend | template solo di presentazione |
| SEC-002 | HIGH | errore di filtering | menu_service.py fallback MENU_FILTER_FAIL_OPEN |
errore configurativo può mostrare menu completo | fail-closed con telemetria e test |
| PERM-001 | MEDIUM | catalogo permission | 221 permission name, 1.422 PermissionView, 625 ViewMenu | naming e residui legacy aumentano il costo ACL | inventario canonico e deprecazione controllata |
| POLICY-001 | MEDIUM | policy DB | 140 menu policy, 62 user policy, 0 group policy, 1 override | modello parzialmente usato e basato su label | chiavi stabili e schema di precedenza documentato |
| ROLE-001 | MEDIUM | profili | profili DB operatore_osm/operatore_amministro non nei default Python osservati |
drift tra seed e runtime | registro profili versionato e check di drift |
| LEGACY-001 | MEDIUM | formazione/commerciale | classi e link paralleli, no_menu, path storici |
duplicazione funzionale e refactoring rischioso | migrazione per capability, poi rimozione legacy |
| API-001 | MEDIUM | API/blueprint | blueprint e has_access_api distribuiti |
UI e API possono avere policy differenti | contratto permission comune per UI/API |
| NAMING-001 | LOW | naming/presentazione | label italiane/inglesi, icone e categorie ripetute | confusione operativa e snapshot instabili | dizionario label e navigation key |
12. Responsabilità attuali¶
| Responsabilità | Dove risiede oggi | Valutazione |
|---|---|---|
| Definizione view/menu | soprattutto app/__init__.py, più app/views.py |
centralizzata solo per registrazione, non per policy |
| Permission FAB | SecurityManager/FAB + attributi view/decorator | automatica e custom mescolate |
| Menu policy | menu_service, menu_filter, tabelle menu policy |
duplicata con runtime e template |
| Entitlement modulo | menu_runtime, tenant_module_entitlement |
display-first; gate endpoint distribuito |
| ACL dati | policy centrali + ogni modulo | parzialmente centralizzata e divergente |
| Sidebar/layout | template SafeOps e FAB | responsabilità di autorizzazione impropriamente nel template |
| Registrazione moduli | bootstrap + blueprint | side effect e ordine di import |
13. Architettura target proposta¶
La direzione è compatibile con FAB se FAB resta un adapter, non il registro business principale:
modulo: metadata + capability + navigation + permission contract
↓
Navigation Registry (chiavi stabili)
↓
Permission/ACL service (ruolo + tenant + cliente + endpoint)
↓
adapter FAB / API policy / SafeOps UI
Il registry dovrebbe produrre un albero autorizzato backend-side con key, label, endpoint, capability e reason. FAB può continuare a registrare ModelView e PermissionView durante la transizione; l'adapter assocerà le view legacy a chiavi stabili. Il template riceverà solo la struttura filtrata e non deciderà se un utente è autorizzato. API e view dovranno usare lo stesso permission contract e lo stesso ACL cliente/tenant.
14. Piano di migrazione incrementale¶
| Fase | Obiettivo e componenti | Rischio/prerequisiti | Test e rollback |
|---|---|---|---|
| 0 | congelare baseline DEV/PROD, esportare menu/permission/endpoint | dirty tree e DB non equivalenti | snapshot; rollback = nessuna modifica |
| 1 | normalizzare naming e contratto permission, senza rimuovere legacy | permission obsolete e ruoli storici | catalogo diff, test negativi; revert seed/adapter |
| 2 | introdurre Navigation Registry e adapter FAB in shadow mode | ordine import e duplicati | snapshot menu per ruolo; disattivare adapter |
| 3 | collegare policy menu legacy al registry, mantenendo fallback osservabile | label policy esistenti | confronto old/new, rollback flag |
| 4 | nuovo template SafeOps che renderizza solo l'albero autorizzato | regressioni responsive/accessibilità | test template/visual; flag per tenant |
| 5 | migrare moduli per capability, iniziando da Formazione e Documentale | ACL locali divergenti | matrice UI/API/tenant; rollback per modulo |
| 6 | rimuovere duplicati, no_menu e patch non necessari |
dipendenze non mappate | endpoint legacy e permission orphan; rollback per release |
15. Strategia di test necessaria prima del refactoring¶
Non sono stati implementati test in questa fase. Servono almeno:
- snapshot del menu per ogni ruolo/profilo e per entitlements modulo;
- accesso positivo e negativo a ogni view, URL diretto e endpoint legacy;
- test API separati dalla UI;
- lettura e scrittura nello stesso tenant, diniego cross-tenant e cross-cliente;
- bypass controllato per platform admin e verifica che non diventi bypass globale non previsto;
- test di
Cliente.users/mapping storico e utenti senza cliente; - regressioni FAB su
can_list/show/add/edit/deletee permission custom; - snapshot delle chiavi navigation e test di rinomina label;
- test
MENU_FILTER_FAIL_OPEN/fail-closed e indisponibilità policy; - test entitlements DEV/PROD con configurazioni isolate;
- verifica che bootstrap non crei o alteri schema/security durante un avvio normale;
- test template che accerti che Jinja non possa concedere accesso, ma solo rappresentare una decisione backend.
16. File che presumibilmente dovranno essere modificati in fasi successive¶
Questa lista è indicativa e non autorizza modifiche: app/__init__.py, app/views.py, app/modules/iam/policy.py, menu_service.py, menu_runtime.py, menu_filter.py, menu_keys.py, menu_diagnostics.py, module_access_console.py, app/modules/menu_policy/*, policy/view dei moduli Anagrafiche, Documentale, Formazione, Commerciale, DVR, Ticket e Tecnici, app/templates/appbuilder/baselayout.html, _safeops_sidebar.html, _safeops_menu_tree.html, base.html, index.html, app/static/js/safeops.js, test IAM/ACL/menu e seed/migration solo dopo review separata.
17. Blocker e decisione¶
- working tree DEV e PROD entrambe dirty: non esiste una baseline riproducibile per un allineamento diretto;
- non esiste un registro canonico che leghi capability, menu, permission e ACL;
- cataloghi FAB molto più grandi del set di menu attuale e presenza di residui/duplicati;
- ACL cliente/tenant distribuite e non ancora certificate endpoint per endpoint;
- runtime DEV autenticato e confronto DB DEV↔PROD non eseguiti in questo audit;
- bootstrap con possibili side effect schema/security;
- policy gruppo prevista dal modello ma senza righe attive osservate.
AUDIT_STATUS: COMPLETE_STATIC_AND_READONLY_DB
MENU_MODEL_UNDERSTOOD: PARTIALLY — registrazioni e principali filtri mappati; manca una matrice runtime autenticata per tutte le combinazioni ruolo/tenant.
PERMISSION_MODEL_UNDERSTOOD: PARTIALLY — catalogo FAB e layer custom censiti; non è ancora provato il comportamento effettivo di ogni endpoint.
ACL_MODEL_UNDERSTOOD: PARTIALLY — helper centrali e implementazioni per modulo identificati; divergenze cliente/tenant da certificare.
TEMPLATE_MODEL_UNDERSTOOD: YES_FOR_PRIMARY_SHELL — gerarchia FAB/SafeOps e punti di decisione Jinja identificati.
SAFE_TO_REFACTOR: NO — prima occorrono baseline congelate e test negativi di autorizzazione.
BLOCKERS: working tree DEV/PROD dirty; assenza registry canonico; catalogo permission/menu duplicato; ACL distribuite; runtime DEV e matrice endpoint non verificati; bootstrap side effects.
RECOMMENDED_NEXT_PHASE: congelare snapshot DEV/PROD e produrre export read-only canonico di menu, permission, endpoint e ACL, quindi validarlo con test per ruolo/tenant/cliente.
18. Tracciabilità del task¶
change_summary: creato solo questo report di audit; nessun codice applicativo modificato.
root_cause: non applicabile; attività di analisi strutturale.
files_changed: docs/tecnico/audit/SAFEOPS_MENU_PERMISSION_AUDIT.md
user_impact: nessun cambiamento runtime.
roles_impacted: nessuno.
surfaces_impacted: sola documentazione interna.
behavior_change: nessuno.
permission_change: nessuno.
operator_action_required: revisionare il report e autorizzare separatamente la fase successiva.
application_code_changed: no.
database_schema_changed: no.
migrations_changed: no.
acl_changed: no.
api_contract_changed: no.
targeted_tests: NOT_RUN — audit statico/read-only.
python_tests: NOT_RUN — nessuna modifica applicativa.
javascript_tests: NOT_RUN — nessuna modifica frontend.
runtime_smoke: NOT_RUN — nessun endpoint DEV/PROD attivato.
data_preservation: dati e database non modificati.
helper_impact: no_helper_impact.
helper_content_type: NOT_APPLICABLE.
helper_destination: NOT_APPLICABLE.
release_severity: L0.
helper_ready_for_release: NOT_APPLICABLE.
block_release: true — refactoring non autorizzato e baseline non congelata.
block_reason: review del report, baseline DEV/PROD e matrice ACL/endpoint ancora necessari.
blockers_remaining: vedi sezione 17.
ready_for_dev_test: no.
ready_for_release_candidate: no.
prod_impact: nessuno.
commit_created: no.
tag_created: no.
push_performed: no.
Lavoro fermato come richiesto.