[FIX] Epic C / C1 — correzioni post-verifica flotta (1 major + 5 minor)

Flotta di verifica (14 agenti): sicurezza pulita, fedeltà del dato perfetta (codifica
Simon intatta, 0 scostamenti). Corretti i 6 finding confermati:
- MAJOR: PolicyController::ensureFrameworkProcedures ora in transazione + SELECT...FOR UPDATE
  sulla riga org -> no procedure duplicate su fallimento parziale (rollback) o richieste concorrenti.
- MINOR: importer idempotente committato application/cli/seed_framework.php (DDL + upsert da
  nis2_framework_seed.json) -> re-seed riproducibile/versionato (verificato 2 run = 0 duplicati).
- MINOR: allineato '92 requisiti' importanti (dato autoritativo Simon) in help.js + nis2_sources.php
  (era 87, contraddizione interna); coerente '24 requisiti aggiuntivi essenziali' (116-92).
- MINOR (UI): troncamento anteprima valutazione sul RAW prima di esc() (no entita HTML spezzate).
- MINOR (UI a11y/AGID): modale valutazione con focus iniziale, ripristino focus, focus-trap.
Cache-buster ?v=20260623, version 1.18.2, SW nis2-shell-v1.18.2.
NB residuo da verificare: data esatta Determinazione 333017/2025 (mese senza giorno) in nis2_sources.php.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
DevEnv nis2-agile
2026-06-16 18:40:31 +02:00
co-authored by Claude Opus 4.8
parent 1f2d48df7c
commit 45e6c3599c
41 changed files with 278 additions and 130 deletions
+27 -8
View File
@@ -6,7 +6,7 @@
<title>Misure e Requisiti - NIS2 Agile</title>
<!-- Bootstrap Italia v2.18.1 self-hostato (regola: MAI CDN). Caricato PRIMA di style.css. -->
<link rel="stylesheet" href="/vendor/bootstrap-italia/dist/css/bootstrap-italia.min.css">
<link rel="stylesheet" href="/css/style.css?v=20260622">
<link rel="stylesheet" href="/css/style.css?v=20260623">
<style>
.mr-intro { background:#eff6ff; border-left:4px solid var(--primary,#2563eb); padding:14px 18px; border-radius:8px; margin-bottom:14px; font-size:.92rem; line-height:1.6; }
.mr-note { font-size:.82rem; color:#92400e; background:#fffbeb; border:1px solid #fde68a; border-radius:8px; padding:10px 12px; margin:0 0 16px; }
@@ -162,12 +162,12 @@
</div>
</div>
<script src="/js/api.js?v=20260622"></script>
<script src="/js/common.js?v=20260622"></script>
<script src="/js/api.js?v=20260623"></script>
<script src="/js/common.js?v=20260623"></script>
<script src="/vendor/bootstrap-italia/dist/js/bootstrap-italia.bundle.min.js"></script>
<script src="/js/common-bi.js?v=20260618"></script>
<script src="/js/i18n.js?v=20260622"></script>
<script src="/js/help.js?v=20260622"></script>
<script src="/js/i18n.js?v=20260623"></script>
<script src="/js/help.js?v=20260623"></script>
<script>
'use strict';
/*
@@ -301,7 +301,8 @@
html += '<td class="mr-yn ' + (req.applies_essential ? 'y' : 'n') + '">' + (req.applies_essential ? '✓' : '—') + '</td>';
if (MR.has_org) {
const sm = statoMeta(req.stato);
const val = req.valutazione_rischio ? esc(req.valutazione_rischio).slice(0, 90) + (req.valutazione_rischio.length > 90 ? '…' : '') : '';
const _vr = req.valutazione_rischio || '';
const val = _vr ? esc(_vr.slice(0, 90)) + (_vr.length > 90 ? '…' : '') : '';
html += '<td><button type="button" class="sv-badge ' + sm.cls + '" onclick="mrEdit(' + req.id + ')" title="Modifica valutazione">' + esc(sm.label) + '</button>' +
(val ? '<div class="sv-val">' + val + '</div>' : '') + '</td>';
}
@@ -325,9 +326,14 @@
el('mr-eval-val').value = q.valutazione_rischio || '';
el('mr-eval-note').value = q.note || '';
el('mr-eval-err').textContent = '';
MR._lastFocus = document.activeElement;
el('mr-modal').classList.add('open');
setTimeout(function () { const s = el('mr-eval-stato'); if (s) s.focus(); }, 0);
}
function mrCloseEval() {
el('mr-modal').classList.remove('open');
if (MR._lastFocus && typeof MR._lastFocus.focus === 'function') { try { MR._lastFocus.focus(); } catch (e) {} }
}
function mrCloseEval() { el('mr-modal').classList.remove('open'); }
async function mrSaveEval() {
const reqId = parseInt(el('mr-eval-id').value, 10);
const btn = el('mr-eval-save');
@@ -350,7 +356,20 @@
} finally { btn.disabled = false; }
}
document.addEventListener('keydown', function (e) { if (e.key === 'Escape') mrCloseEval(); });
(function () { const ov = el('mr-modal'); if (ov) ov.addEventListener('click', function (e) { if (e.target === ov) mrCloseEval(); }); })();
(function () {
const ov = el('mr-modal'); if (!ov) return;
ov.addEventListener('click', function (e) { if (e.target === ov) mrCloseEval(); });
// focus-trap: Tab/Shift+Tab restano dentro la modale aperta
ov.addEventListener('keydown', function (e) {
if (e.key !== 'Tab' || !ov.classList.contains('open')) return;
const nodes = ov.querySelectorAll('select, textarea, button');
const list = Array.prototype.filter.call(nodes, function (x) { return !x.disabled && x.offsetParent !== null; });
if (!list.length) return;
const first = list[0], last = list[list.length - 1];
if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); }
else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }
});
})();
</script>
</body>
</html>