Changelog¶
Included directly from the repository CHANGELOG.md
so it never goes stale relative to the single source of truth.
Changelog¶
Formato basato su Keep a Changelog.
[Unreleased]¶
Changed¶
utility/orca.Orca.protect_and_forward: vectorized the entry-shield stage -- the previousfor b in range(B)Python loop called_execute_4_phase_input_shieldonce per row (once per element of the batch), each a separate JAX dispatch. Same dispatch-dominated pattern already found and fixed incore/hybrid_engine.hybrid_shieldfor Armatura. New_execute_4_phase_input_shield_batchprocesses all B rows in one call via a newAdaptiveSignalStabilizer.filter_batch_scenarios_independent(core/engine.py): each row gets its OWN calibration (calibrate_macro_context_independent), not one shared across the whole batch like the existingfilter_batch_scenarios-- rows with different scale/noise no longer contaminate each other's threshold, matching the old loop's per-row-independent behavior exactly. Verified bit-for-bit (float64) against the loop version, including a test that specifically checks one row's huge injected spike does not change another row's output (test/test_orca_vectorized_batch.py). Real measured latency, synthetic B=500/F=10 batch (same shape as the APT29 + AI-shield experiment): median of 10 calls after JIT warmup, 750.6ms -> 2.3ms (~321x).chunk_thresholdbehavior unchanged for the one case this doesn't cover (a single row's own flattened size exceedingchunk_threshold, which falls back to the original per-row/per-chunk loop, untouched). No public API change.
Changed¶
core/hybrid_engine.hybrid_shield: vectorized withjax.vmap-- the previousfor i in range(2, n)Python loop calledcalculate_phi_ab/calculate_vettore_dinamico/evaluate_phi_triggeronce per point. Profiling on a real 500-point security-telemetry series (Sysmon EventID10, APT29) showed 87% of the time in JAX dispatch itself (~2000jnp.arrayconstructions, 3.2Misinstancecalls), not in the actual per-point math -- pure Python-orchestration overhead, not a real compute limit. Verified bit-for-bit against the original loop on that same real series and on synthetic edge cases (isolated spike, sustained step, with/without an external reference, n=0..3): identical output and trigger in every case. Real measured latency on that 500-point series (median of 20 calls after JIT warmup): 292.4ms -> 29.8ms (~9.8x). No public API change;baseline/scale/ipgstill read onlyprocessed(the sanitized raw series), neveroutfrom a previous step, per the no-self-contamination invariant this function has always documented.
[1.1.19] - 2026-09-05¶
Added¶
dynamics/passivity_cbf_controller.py(solve_control_qp): task-space passivity + singularity-avoidance CBF controller (Kurtz, Wensing & Lin 2021, arXiv:2109.13349) for any robotRigidBodyModelcan load. A small QP over joint acceleration, constrained byVdot<=0(passivity) and an exponential CBF keeping the manipulability index above a declared floor. Two-step promotion from Dense-Evolution-Discovery, same asRigidBodyModel: Experiment 61 built the same controller hardcoded to one Kinova Gen3; Experiment 63 replaced the hardcoded calls withRigidBodyModel's API and re-validated on the same three robots (Kinova Gen3 7-DoF, Kinova Gen3 6-DoF, Franka Emika Panda), each driven toward its own true singularity, manipulability held within 0.1-1.8% of the declared floor in every case. Found and fixed a real OSQP-infeasibility bug along the way (soft passivity constraint dropped in favor of the hard CBF one when the solver reports infeasible). New dependency:osqp.- Real per-joint position/velocity limits:
RigidBodyModelnow parses each joint's real<limit>tag (+/-infwhere the URDF declares none);solve_control_qpenforces them as an additional CBF (Kurtz et al.'s own "joint" constraint type), added only when a robot's URDF has a real finite limit somewhere. Real number, Franka Pandajoint4(range[-3.1416, 0.0]) at its bound with velocity driving past it: unconstrained commandqdd=-205.8clamped to the real box[-7.175, -4.999]. dynamics/six_dof_pbc_cbf_controller.py(solve_control_qp): full 6-DoF (position + orientation) generalization ofpassivity_cbf_controller.py, tracking a link's full pose viaRigidBodyModel's newlink_pose/link_spatial_jacobian. Attitude error uses Lee, Leok & McClamroch (2010)'s SO(3) formula instead of an RPY-based one, avoiding gimbal lock. Promoted from Dense-Evolution-Discovery Experiment 65: validated with an exact gravity-compensation check at zero position and orientation error (machine precision), and a real closed-loop run converging a 10cm/30-degree offset to near-zero (position 1e-6 m, orientation 1e-4) over 1000 control ticks; also validated on the same 3-robot bar aspassivity_cbf_controller.py, which surfaced a real second-level OSQP infeasibility (the 6-DoF manipulability measure can be well below the 3-DoF one at the same configuration, making CBF+box jointly infeasible) -- fixed with a third fallback level that drops the box and keeps only the CBF..xacrofile support:RigidBodyModelnow accepts.xacromacro files directly, expanding them via the realxacropackage before parsing. Promoted from Dense-Evolution- Discovery Experiment 66, which found and fixed a real inconsistency in the Franka Panda's own published macros (clvrai/furniture): a hand-attachment link commented out in the arm macro but required by the hand macro. New dependency:xacro.<mimic>joint support: a joint with a<mimic joint="..." multiplier="..." offset="..."/>tag no longer gets its own independent coordinate -- its motion is computed from its master's, chain-ruled onto the master's own column in the hand-built geometric Jacobian. Promoted from Dense-Evolution-Discovery Experiment 67, checked against a real central finite difference oflink_pose(Franka Panda gripper fingers) to under 1e-5.
[1.1.18] - 2026-09-05¶
Added¶
dynamics/urdf_dynamics.py(RigidBodyModel): Euler-Lagrange rigid-body dynamics -- M(q), C(q,qdot)qdot, g(q) -- parsed from any URDF file (stdlib xml.etree, no new dependency), not one hardcoded robot. Newdynamics/subpackage, separate fromutility/, since it needs an actual physical model file and returns torque-level quantities. Two-step promotion from Dense-Evolution-Discovery: Experiment 61 built the same dynamics (JAX autodiff, not hand-derived Christoffel symbols) but hand-transcribed from one Kinova Gen3's URDF -- the reason it wasn't promoted then. Experiment 62 replaced the hardcoded tables with a parser and re-validated on three independent robots (Kinova Gen3 7-DoF, Kinova Gen3 6-DoF, Franka Emika Panda -- different manufacturer, prismatic joints): cross-checked to machine precision against Experiment 61's own numbers, then mass-matrix SPD and energy-conservation (correct 4th-order RK4 convergence) verified fresh on all three.
[1.1.17] - 2026-09-04¶
Added¶
utility/cbf_filter.py(cbf_safety_filter_live): single-control-tick counterpart tocbf_filtered_trajectory-- for a real-time loop reacting to one sensor callback at a time (a realdtper tick) instead of filtering a whole pre-recorded array offline. Promoted after a real live ROS2/Ignition safety loop (Dense-Evolution-Discovery Experiment 58) needed exactly this and had to reconstruct it by hand fromcbf_filtered_trajectory. Reuses the same substep math already validated on SO-101/ALOHA -- not a new validation domain, an ergonomic single-tick wrapper around it. Verified directly: converges exactly to the declared safety boundary and never crosses it (max reached 2.199999999999889 for a 2.2 boundary).utility/trajectory.py(quintic_trajectory): closed-form, minimum-jerk-continuous point-to-point trajectory generator for any number of joints -- fills the gaprate_limiter/cbf_filterdon't cover (neither generates a reference to track). Deliberately scoped down from two real papers proposing much larger URDF/dynamics-aware optimizers (Lozer et al., Robotics and Autonomous Systems; Fried & Paternain, arXiv:2412.07859 -- both read in full before writing any code). Promoted from Dense-Evolution-Discovery after validation on two independent real physical domains (SO-101, ALOHA, 20 real joint excursions): quintic peak velocity always lower than the real recorded peak velocity for the same start/end/duration -- expected, since it is the smoothest possible path, not a bug.utility/kinematic_controller.py(kinematic_tracking_controller): closed-form feedforward-plus-proportional kinematic tracking controller -- turns a reference fromtrajectory.pyinto a velocity command forrate_limiter/cbf_filter. Searched for a real "universal controller" paper first (Wu & Tan 2025 -- best fit, paywalled; Scruggs -- real, needs infinite-dimensional convex Youla optimization; Califano et al. -- real, needs Hamiltonian mechanics; classical PD+gravity-compensation -- needs a real dynamics model, contradicting this stack's single-integrator scope). What ships instead:u = qd_ref + kp*(q_ref - q), with an exact closed-form exponential convergence guarantee, verified numerically. Promoted from Dense-Evolution-Discovery after validation on two independent real physical domains (SO-101, ALOHA, 20 real joint excursions), chained withquintic_trajectory: every real excursion recovers from a real disclosed nonzero initial tracking error and converges.
[1.1.16] - 2026-09-04¶
Fixed¶
dense_armor/__init__.py:__version__was still hardcoded to"1.1.14"after the 1.1.15 release -- it drivesdense_armor_health'sdense_armor_versionfield in the MCP server, so the running package misreported its own version. Real regression found by checkingdense_armor.__version__right after installing 1.1.15 from PyPI, not caught before that release.pyproject.toml'sversionfield was already correct; only the Python-level constant was stale.
[1.1.15] - 2026-09-04¶
Added¶
utility/cbf_filter.py(cbf_safety_filter,cbf_filtered_trajectory): geometric Control Barrier Function safety filter -- never lets an applied command enter a forbidden region, complementingrate_limiter.py's rate-of-change bound. Grounded in Ames et al. (2019), "Control Barrier Functions: Theory and Applications" (2019 ECC, arXiv:1903.11199) -- the same theory SAFER-Splat uses, applied to a known geometric obstacle instead of SAFER-Splat's GPU-bound Gaussian-Splatting perception. Real numerical finding disclosed, not hidden: the CBF's continuous-time guarantee needs discrete sub-stepping (20/sample default) to hold on real commands that can jump substantially between samples. Promoted from Dense-Evolution-Discovery after validation on two independent real physical domains (SO-101 6-DoF 30Hz, ALOHA bimanual 14-DoF 50Hz): 100% invariance from safe starting conditions on both (17/17, 38/38), minimal invasiveness 99.9%+ exact on SO-101 and perfectly exact (0/18444) on ALOHA.utility/rate_limiter.py(rate_limited_follower): causal velocity+acceleration command limiter -- bounds how fast an applied command can physically change instead of classifying whether a deviation is real, after a causal neighbor-consensus classifier approach hit a structural dead end (1.7% win rate vs a trivial moving median, 120 real trials). Grounded in Berscheid & Kroger (2021), "Jerk-limited Real-time Trajectory Generation" (RSS 2021, arXiv:2105.04830) -- causal by construction, verified directly. Promoted from Dense-Evolution-Discovery after validation on two independent real physical domains (SO-101 6-DoF 30Hz, ALOHA bimanual 14-DoF 50Hz): wins the real safety metric (max instantaneous command jump) 400/400 real trials across both domains. Honest limit, not hidden: average tracking fidelity (RMSE vs the true signal) does not reliably beat a trivial moving median -- this is a safety bound, not a signal cleaner.- 3 new stateful MCP tools (
dense_armor_stream_start/_update/_end,dense_armor.mcp_server): a real-time streaming session wrappingMultiChannelStreamingDeviationDetector-- for a live sensor feed (robot joints, IMU axes) where waiting to collect a full array first isn't an option, unlike the existing batchdense_armor_detect_anomaliestool. Onesession_id-keyed detector instance per session, held in server memory (in-process, same architecture as every other tool here -- see server.py's own docstring); a real caller must call_stream_endwhen a real stream ends, sessions are not garbage-collected automatically. Verified end-to-end against real LeRobot robot-arm joint data fed one point at a time through the real MCP call path (mcp.call_tool), not just the underlying Python function directly.
Fixed¶
utility/curvature.py(curvature): added ascaleparameter (default1.0, reproduces the exact previous behavior --Orca's existing call site unaffected). Real finding, checked directly against real SO-101 joint data (Dense-Evolution-Discovery): the unscaled formula saturates to ~1.0 within ~5 raw units of any reference regardless of the caller's physical units, making it a near-binary near/far indicator rather than a graded proximity signal when used in real degrees (elbow_flex, whose real distance to its real physical limit spans 0.1-66.9 degrees in one real episode: correlation with raw distance only 0.235 at the old fixed scale, vs 0.932 with a physically meaningfulscale=15.0). Not a promotion (no second-domain validation attempted -- this fixes an existing internal utility's own parameterization, not a new one).
[1.1.14]¶
Added¶
utility/streaming.py(StreamingDeviationDetector,MultiChannelStreamingDeviationDetector,classify_segments_ multichannel): porta a latenza zero solo la meta' causale diclassify_segments(arbiter.py) -- il flag di deviazione per-punto, non l'etichetta finale spike/regime, che guardaradiuspunti avanti alla fine di una sequenza deviante e resta una domanda batch/offline per design, non una svista. Buffer semplice (deque, O(span)), non una struttura a due heap -- misurato a ~18.6kHz sostenibile per le finestre gia' usate in tutto il progetto (10-100 punti), oltre 180x il tasso reale di un anello di controllo robotico (30-100Hz). Il supporto multi-canale rimuove il bisogno di un ciclo manuale per-canale (giunti di un braccio robotico, assi di un IMU), ogni canale con la propria finestra di riferimento indipendente. Promosso da Dense-Evolution-Discovery (Esperimenti 48-49) dopo validazione su due domini fisici reali indipendenti (braccio robotico teleoperato SO-101, IMU umano reale UCI HAR) -- stessa disciplina di promozione gia' usata perstable_frame_filter.py. Corrispondenza bit-per-bit conclassify_segmentsverificata direttamente, non assunta.utility/cusum.py(one_sided_arl,two_sided_arl,detectability_report): stima pre-flight, teoria classica Reynolds (1975)/Siegmund (1985), di quanti campioni servono per rilevare uno shift dato il rumore locale reale dicusum_detector-- senza dover lanciare un benchmark prima. Promossa da Dense-Evolution-Discovery dopo validazione su due domini fisici reali indipendenti: lidar (7/7 punti reali, latenza sempre sotto la stima) e accelerometro (5 punti reali, risultato genuinamente misto 2/5 -- non forzato a coincidere con il lidar). L'accelerometro ha anche fatto emergere un limite reale della formula grezza a SNR estremo (ARL frazionario, privo di senso fisico sotto 1 campione) -- corretto qui con un floor a 1.0 su entrambi gli ARL.
[1.1.13]¶
Fixed¶
utility/cusum.py(cusum_detector): il defaulth=5.0("classico da manuale", mai validato contro una lunghezza di serie pratica) dava un tasso di falso allarme a livello di STREAM del 100% (adaptive) / 85% (fixed) su una serie stabile di 1000 campioni -- invisibile al test esistente, che controllava solo il tasso di falso allarme per PUNTO (molto più basso e fuorviante). Trovato come sottoprodotto diretto di un porting parallelo dello stesso algoritmo per online-ml/river, dove lo stesso identico problema è emerso per primo. Nuovo defaulth=20.0: 3.5% (adaptive) / 15.5% (fixed) sulla stessa serie di 1000 campioni.fixedresta strutturalmente più esposto (il suo riferimento non si aggiorna mai, quindi una stima di warmup sfortunata non si autocorregge) -- non un difetto di taratura, conseguenza diretta di cosafixedè pensato per fare. Nuovo test di regressione (test_default_h_keeps_stream_level_false_alarm_rate_low_ on_a_practical_length) per non ricadere silenziosamente nello stesso problema. Nessun benchmark preregistrato è stato toccato: tutti e quattro (test_benchmark_v0_runtime_behavioral_drift.py,test_benchmark_v2_1_ablation.py,test_benchmark_v2_1_log_ onesided.py,test_benchmark_v2_agent_runtime.py) passanoh=5.0esplicitamente nel proprioCUSUM_KWcongelato, indipendentemente dal default della funzione.
Added¶
utility/stable_frame_filter.py(velocity_gated_stable_mask): filtro che restringe l'analisi di un segnale ai punti dove un segnale di riferimento COMPANION indica uno stato stabile/lento -- evita che un detector interpreti come anomalia un transitorio spiegato da un confondimento noto e indipendente (es. il comando/leader di un braccio robotico che si muove veloce, causando un lag di tracking normale). Parametroalready_rateper riferimenti gia' di tipo velocita' (es. magnitudine giroscopica) invece che posizione -- trovato necessario da un fallimento reale su un secondo caso, non aggiunto preventivamente. Promosso da Dense-Evolution-Discovery dopo validazione su due domini fisici reali indipendenti (braccio robotico teleoperato SO-101, IMU umano reale) -- stessa disciplina di promozione gia' usata perone_sided_upper_filter.
[1.1.12]¶
Added¶
utility/cusum.py(cusum_detector): canale complementare aclassify_segmentsper il drift lento/sostenuto -- CUSUM a due code (Page 1954), modalita'adaptive(default, finestra causale scorrevole, dichiarata onestamente come variante non identica allo schema originale a riferimento fisso) efixed(lo schema classico). Gestione NaN/Inf esplicita. Nato da un gap reale misurato intest/test_benchmark_v0_runtime_behavioral_drift.py:classify_segmentsrileva un glitch transitorio al 100% (latenza 0) ma solo il 9-27% di un drift graduale nella finestra di transizione.utility/one_sided.py(one_sided_upper_filter): filtro a una coda componibile conclassify_segments/cusum_detector-- per segnali dove solo un AUMENTO e' significativo (latenza, error rate). Riduce i falsi positivi misurati su telemetria reale di un agente Qwen2 1.8B (via Ollama) dal 22.5% al 12.5% (classify_segments) e dal 17.5% al 10.0% (cusum_detector), e il falso rifiuto su un cambio di comportamento legittimo dal 24.0% al 4.0% su entrambi -- al costo reale di una sensibilita' ridotta al drift persistente (non nascosto: veditest/test_benchmark_v2_1_ablation.py). Un log-transform provato in parallelo non ha portato benefici misurabili ed e' stato scartato.
Investigated (percorso di validazione, non solo il risultato finale)¶
- Benchmark sintetico congelato (
test/test_benchmark_v0_runtime_ behavioral_drift.py): protocollo preregistrato, 4 condizioni (baseline, drift, glitch, test negativo), mai ritoccato dopo aver visto i risultati. - Benchmark su agente reale (
test/agent_v2/,test/test_benchmark_v2_*): prima validazione non sintetica -- loop minimale con Qwen2 1.8B via Ollama (tool-calling manuale, il modello non restituiscetool_callsstrutturati nonostante il tag "tools"), 4 scenari con ground truth esatta. Trovato: la latenza reale ha una coda molto piu' pesante del rumore gaussiano sintetico, da cui il filtro one-sided sopra.
[1.1.11]¶
Investigated (nessun candidato promosso)¶
compute_damping_gating_smooth(Collatz continuo), mai benchmarkato, ora chiuso: il suo stesso docstring rimandava atest/test_collatz_smooth_experiment.pyper il confronto misurato -- file mai esistito. Scritto ora, giraOrcaper intero sui 7 scenari ditest/testKalman.pyuna volta con il gate costante (default) e una con la variante smooth. Risultato: smooth vince 0/7, pareggia 6/7 (RMSE identico entro 1e-4), perde su "Stealth sub-soglia" (RMSE 0.0029 vs 0.0012, oltre il doppio). Non promosso -- docstring aggiornato con il risultato reale al posto del rimando a un file inesistente.hybrid_shield(phi_ab, gia' vendorizzato per Armatura) come possibile gate per Orca: non e' uno scambio diretto (motore binario a ciclo su tutta la serie, non un gate continuo per elemento), quindi confrontato come pipeline completa (test/test_hybrid_shield_vs_orca_experiment.py) sugli stessi 7 scenari. Risultato misto, non un vincitore netto: vince 4/7 su impulsi isolati/collasso a zero/buco NaN (in 3 casi RMSE ~0.0000, quasi perfetto), ma perde nettamente su segnale sinusoidale strutturato e rumore Cauchy pervasivo (RMSE 2-3x peggiore di Orca) -- ottimo per anomalie isolate, pessimo quando il cambiamento e' diffuso e genuino, non un'anomalia da respingere.pressure_valve(JSD-adattivo, gia' inrobust_filters.py, mai collegato a nessuna pipeline) come terzo candidato: stesso confronto a 7 scenari (test/test_pressure_valve_vs_orca_experiment.py). Vince 1/7 ma in modo netto: RMSE 0.0000 esatto sulla rottura di livello permanente (l'unico dei tre a riconoscere un vero cambio di regime), ma fallisce clamorosamente sugli impulsi isolati (RMSE 86.6 su "impulsi alternati", peggio di non fare nulla). Causa reale trovata (non un'ipotesi): con 3 picchi ravvicinati in una finestra di 21 punti, mediana/IQR e mediana/MAD restano ESATTAMENTE 0 (18/20 punti identici, matematicamente corretto per stimatori con breakdown ~25-50%), quindi vengono scartati e resta solo media/std -- non robusta, contaminata dagli stessi picchi che dovrebbe rilevare. Fix parziale applicato: la finestra locale ora esclude il punto stesso (come gia' faceva solo il sotto-passo sigma-clipping), corretto in linea di principio ma verificato non sufficiente quando PIU' outlier restano comunque vicini tra loro nella stessa finestra (nessun cambiamento misurabile sui 7 scenari). Un M-estimator di Huber (IRLS, vedi Sung et al. 2019, arXiv:1912.04982, ricerca bibliografica reale via RAG locale) e' stato provato come alternativa piu' sofisticata e converge ANCH'ESSO a scala 0, per lo stesso motivo di fondo (la sua scala e' a sua volta una MAD dei residui). Conclusione onesta: non e' un problema di scelta dell'estimatore, e' che dividere per una scala locale legittimamente zero (baseline quasi piatta) e' indefinito per costruzione, qualunque stimatore si usi. Non risolto -- vedi il "LIMITE NOTO" nel docstring dipressure_valveper la via di fix non ancora tentata (scala di fallback dalla finestra di riferimento piu' ampia).- Conclusione dei tre esperimenti: nessuno dei tre candidati batte in modo netto il gate costante 0.85 di Orca su tutti gli scenari -- ognuno (incluso il default) ha una nicchia precisa (impulsi isolati, segnale diffuso/rumore pervasivo, cambio di regime permanente). Il gate costante resta quello di default; nessuna promozione fatta.
Fixed¶
core/vector.py,run_parallel_scenariosricompilava ad ogni chiamata:jax.jit(jax.vmap(...))veniva ricostruito DENTRO il metodo ad ogni invocazione -- una nuova closure Python ad ogni chiamata, quindi un nuovo oggettojax.jitla cui cache di compilazione non poteva mai fare hit con la precedente. Il "warm run" restava bloccato a ~130ms invece di scendere ai pochi ms attesi (la diagnosi originale, un report Colab, puntava al castnp.arraycome colpevole -- verificato e smentito: il vero costo era la ricompilazione ad ogni chiamata). Fix: motore jit+vmap spostato a livello di modulo, costruito una sola volta;base_statepassato come argomento tracciato invece che chiuso in closure, cosi' valori diversi non forzano una nuova compilazione. Verificato: da un plateau di ~140ms/chiamata a ~2.4ms/chiamata (~55x) su chiamate ripetute al metodo pubblico.utility/orca.py, riferimento pulito che attraversa lo zero esplodeva la compressione log10: un valore pulito quasi-zero (anche solo un residuo di floating point, mai esattamente0.0-- es.sin(4π) ≈ -4.9e-16) faceva esplodere il fattore di scala condivisofact_shared. Siccome lo stesso fattore si applica anche al corrotto (che NON e' vicino a zero), il suo controvalore compresso schizzava a ordini di grandezza sopra i punti vicini (misurato: da~1e-4tipico a~2e9in un caso), sballando la memoria causale diAdaptiveSignalStabilizerper l'intero batch, non solo per quel punto. Fix a due livelli: (1) l'esponente non viene piu' amplificato oltre la scala target (val_e); (2) il valore compresso del corrotto resta comunque limitato a un multiplo ampio (20x) della scala target, indipendentemente dalla causa esatta del salto. Verificato: MSE su un riferimento sinusoidale che attraversa lo zero da ~9 (peggio della modalita' cieca sullo stesso segnale) a ~0.02.
Changed¶
core/engine.py, naming e precisione non armonizzati col resto della libreria: le variabili interne diAdaptiveSignalStabilizer(_step_kernele la variante storica_legacy_step) si chiamavanophi_ab/v_dyn/trigger-- stessi nomi dicore/hybrid_engine.py(motore di Armatura) per una formula matematicamente diversa. Rinominate in modo non ambiguo (coherence_signal/coherence_mixecoherence_legacy/dynamic_shift_legacy/is_anomaly_legacy). Il file usava anchefloat32esplicito ovunque, mentre il resto della libreria richiedejax_enable_x64e gira infloat64-- i commenti giustificavano ilfloat32con una "compatibilita' di esportazione binaria nativa" che non esiste da nessuna parte nel repo (verificato con una ricerca sull'intero codebase). Convertito tutto afloat64, rimossi i commenti fuorvianti. Suite completa (189 test, inclusi i 5 file adversarialtest_bound*.py) invariata prima e dopo entrambe le modifiche.
Added¶
Orcausa oraUniversalMemoryGuardinvece di un check RAM ad-hoc:_gc_se_ram_bassa()duplicava a mano un checkpsutilgia' fatto meglio dacore/memory.py(che copre anche la VRAM, mai controllata prima). Cambio di comportamento onesto: sotto la soglia critica ora sollevaMemoryPressureErrorinvece di continuare in silenzio -- fail-fast con un errore leggibile invece di un OOM qualche chunk piu' avanti.Orcaricorda riferimenti puliti passati e li richiama viaapply_fast_resonance: in modalita' cieca (x_reference=None), prima di ricadere sulla stima locale (_blind_reference), Orca cerca nella propria memoria (per shape, fino areference_memory_size=32per default) un riferimento gia' visto simile all'input corrotto corrente (sogliareference_recall_min_score= 0.90, calibrata empiricamente: segnali correlati restano sopra 0.95 anche con rumore moderato, segnali scorrelati non superano ~0.75). Banca vuota o nessun match abbastanza simile -> comportamento identico a prima.chunk_thresholddiOrcaauto-tarato suAIHardwareProfiler: default cambiato da un intero fisso (1_000_000, uguale per qualunque host) aNone, che auto-deriva il valore scalando proporzionalmente amax_tensor_dimdell'host corrente (RAM/GPU-TPU). Su un host "base" (RAM<12GB, nessuna GPU/TPU) il risultato e' identico al vecchio default fisso -- nessuna regressione silenziosa sul caso comune. Un valore esplicito bypassa l'auto-tuning senza nemmeno istanziare il profiler.Orca(use_arbiter=True), opzione nuova, defaultFalse: instrada ogni punto verso il correttore giusto invece di forzarne uno solo su tutta la riga -- nato dai tre esperimenti sopra (nessun gate unico batteva gli altri due ovunque). Nuovo moduloutility/arbiter.py: classifica ogni punto come 'clean'/'spike'/'regime' confrontandolo con una finestra di riferimento larga e CAUSALE (solo punti prima di i, mai dopo -- stessa scala della "molla" JSD dipressure_valve, non la finestra locale stretta che si e' vista collassare sopra), poi la lunghezza e la coerenza interna della sequenza di punti devianti decide impulso isolato vs cambio di regime sostenuto. 'spike' -> rigetto duro (mediana della finestra larga); 'regime' -> valore grezzo (fiducia, stessa filosofia del gate costante); 'clean' -> quello che lo scudo standard a 4 fasi ha gia' prodotto (il suo smorzamento morbido, non un pass-through, altrimenti un segnale continuo non anomalo come un'oscillazione strutturata verrebbe lasciato passare intatto). Verificato su tutti i 7 scenari ditest/testKalman.py(test/test_arbiter_orca_integration.py): mai peggiore del default (use_arbiter=False, garanzia testata), migliore su 5/7 (impulsi isolati, sub-soglia, Cauchy, rottura di livello, buco NaN), pari sul collasso a zero, identico sul segnale strutturato (ricade correttamente sullo scudo standard, com'e' giusto: e' smorzamento continuo, non un'anomalia discreta). Popola ancheOrca.etichette_arbitro/incertezza_arbitro(l'incertezza della classificazione, non la dimensione della correzione -- segnale complementare amargine_ingresso, non ridondante) eOrca.tipi_corruzione_visti(slice_shape), un Counter dei tipi di corruzione gia' osservati per quella shape quandox_referenceera noto -- memoria separata dalla banca dei riferimenti puliti gia' esistente, che ricordava solo la FORMA, non il TIPO di corruzione. CRONOLOGIA di un limite trovato e risolto nella stessa sessione: la prima versione usava una finestra di riferimento SIMMETRICA, che a cavallo di una rottura di livello permanente mescolava vecchio e nuovo livello nella propria scala, non rilevando affatto la transizione (deviazione sempre 0) -- un primo confronto isolato aveva scambiato questo per un successo (RMSE 0.0), ma era un artefatto: i dati grezzi di quello scenario di test coincidono GIA' col target, quindi "passa tutto grezzo" da' RMSE 0 indipendentemente dall'etichetta. Attraverso Orca la mancata rilevazione si vedeva davvero: nessun miglioramento (9.4007 identico con o senza l'arbitro). Passando alla finestra causale, la rilevazione e' diventata reale (indici 60-74 etichettati 'regime', esattamente la transizione vera a 60) e l'RMSE reale scende a 6.0449 -- vediutility/arbiter.pyper il dettaglio completo.dense_armor.mcp_server, nuovo (extra[mcp]): server MCP con 5 tool (dense_armor_health,dense_armor_clean_signal,dense_armor_detect_anomalies,dense_armor_robust_filter,dense_armor_heal_series) cosi' un agente puo' ripulire una serie senza scrivere Python. Diretto e in-process, non un proxy HTTP verso un kernel separato come l'adattatore di Dense-Evolution -- Dense-Armor non ha una web UI da condividere. Vive apposta sottodense_armor.mcp_server, non un semplicemcp_server: con Dense-Evolution installato nello stesso ambiente (il suo adattatore si chiama esattamentemcp_server), un nome non annidato e' una collisione VERA, osservata direttamente (from mcp_server.server import mcprisolveva ai 25 tool di Dense-Evolution invece dei 5 di qui, a seconda dell'ordine di installazione), non un'ipotesi -- scoperta e corretta prima che questo finisse pubblicato. Nuovo script consoledense-armor-mcp, separato dadense-armor(la CLI di Armatura, invariata). Un bug reale trovato e corretto nello sviluppo: lo schema Pydantic divaluesdichiarava supporto NaN nel docstring ma rifiutava silenziosamentenullJSON dentro la lista (List[float]invece diList[Optional[float]]). Verificato end-to-end viamcp.call_tool()reale (non solo le funzioni Python dirette) intest/test_mcp_server.py, 222/222 test verdi in totale.
[1.1.9]¶
Fixed¶
core/compiler.pyricompilava ad ogni chiamata:run_dynamic_pipeline,run_pipeline_with_chunkingecompute_gradientsdefinivano il proprio kernel@jax.jitDENTRO il metodo -- un nuovo oggettojax.jit(quindi una cache di compilazione vuota) ad ogni singola chiamata, mai riuso della compilazione XLA anche a parita' di shape tra una chiamata e la successiva. Stesso bug gia' trovato e risolto nel motore diAdaptiveSignalStabilizer(vedi [1.0.3]), mai corretto qui. Trovato scrivendo un test che doveva dimostrare l'utilita' reale diPipelineProfiler(warm-up separato dal regime): con il bug, warm-up e regime erano praticamente identici. Fix: i tre kernel spostati a livello di modulo, compilati una sola volta. Verificato: 375ms -> 0.2ms a regime sulla stessa pipeline (>1700x), test di regressione aggiunto.utility/anwav.pyandava in crash su console Windows non-UTF-8: i verdetti su volume "troppo spinto"/"troppo silenzioso"/picco a rischio usavano emoji (❌,⚠️) neiprint()-- su cp1252 (l'encoding di default della console Windows) sollevavanoUnicodeEncodeError, quindi qualunque uso reale della funzione fuori da pytest (che forza UTF-8 intest/conftest.py) su un file rumoroso o silenzioso andava in crash. Rimosse le emoji dai messaggi.- Link rotto in questo stesso CHANGELOG bloccava il deploy del sito in silenzio: la voce
1.1.8 linkava
[Toolkit](../api/toolkit.md), un percorso relativo valido solo dentrodocs/-- maCHANGELOG.mdvive nella root del repo e viene incluso cosi' com'e' indocs/changelog.md.mkdocs build --strictrifiutava giustamente la build, e i due push precedenti non erano mai arrivati sul sito pubblico (la sezione toolkit digetting-started.mde gli esempi indocs/api/toolkit.mderano gia' su GitHub ma non ancora online). Corretto con l'URL completo del sito; verificato dal vivo che il sito pubblico ora li mostra davvero.
Changed¶
docs/api/toolkit.mdnon vende piu' ogni modulo allo stesso modo: aggiunte note oneste dove l'utilita' e' effettivamente sottile (TensorVaultsono matrici scrivibili in una riga, l'euristica RAM diAIHardwareProfilere' arbitraria,BitwisePermutationEnginefa un solo swap controllato per chiamata non una permutazione generale, l'update diParametricScenarioSimulatore' una EMA fissa non un modello configurabile, i formatter di logging sono wrapper sottili,StochasticAdversarialNoisesi sovrappone alla suite adversarial reale invece di aggiungerci qualcosa) e rafforzate quelle con prove reali (PipelineProfilerha scoperto il bug sopra; i preset producono filtraggio misurabilmente diverso;kappainapply_fast_resonancemodula davvero il punteggio, non e' cosine similarity travestita).
Added¶
- Test che dimostrano l'utilita' dichiarata dei moduli, non solo che "non crashano": preset
diversi (
SIGNAL_STABILIZER_PRESETS) producono un filtraggio misurabilmente diverso quando collegati aAdaptiveSignalStabilizer;kappainapply_fast_resonancemodula davvero il punteggio; il warm-up diPipelineProfilere' davvero piu' lento del regime (il test che ha scoperto il bug di ricompilazione sopra). docs/api/toolkit.md: ogni modulo del toolkit ha ora un esempio di codice reale verificato (non solo la firma auto-generata dai docstring), preceduto da cosa fa e a cosa serve.
[1.1.8]¶
Fixed¶
docs/getting-started.mdnon spiegava come usare il toolkit: la pagina aveva un quickstart per Orca, Armatura e robust_filters, ma per il toolkit (14 moduli, aggiunti in 1.1.6-1.1.7) solo un link nudo nella home del sito, nessun esempio eseguibile. Aggiunta una sezione "Standalone toolkit" con due esempi reali verificati (DynamicAICodegen,UniversalMemoryGuard) e link a Toolkit per il resto.
[1.1.7]¶
Nessuna modifica alla matematica/logica di Armatura/Orca in questa versione. I 14 moduli
del toolkit aggiunti a 1.1.6 sono ora davvero pubblici (documentati sul sito, show_source:
true), quindi tutto quello che c'era di poco professionale nel codice sorgente -- non solo nel
README -- ora si vede.
Added¶
- Sito: nuova pagina
docs/api/toolkit.md(mkdocstrings, pesca dai docstring reali) per i 14 moduli aggiunti in 1.1.6 -- prima invisibili sul sito, l'API Reference copriva solo i 5 moduli dello scudo. Linkata damkdocs.yml,docs/index.md,docs/api/index.md. - README: la sezione
$ toolkit --standalone(prima solo un blocco di import con commenti) riscritta con prosa reale, raggruppata per categoria (pipeline/chunking, hardware/profiling, tensori/configurazione, logging/provenance, audio/I/O dati, ricerca per similarità).
Changed¶
- Rimosso "Sentinel"/branding residuo dal codice sorgente dei 14 moduli toolkit e dei 5
moduli originali dello scudo (
armatura.py,core/damping_operator.py,utility/metro.py,utility/orca.py-- mancati nel passaggio di de-branding di 1.1.5, che si era fermato al testo rivolto all'esterno README/pyproject/sito, senza scendere nei singoli docstring conshow_source: true): banner "SENTINEL ENTERPRISE... BEAST MODE" e claim falsi di "logica quantistica" incore/chunk.py, blocco "Fix applicati/BILANCIAMENTO AUREO" (contenuto da changelog finito nel docstring) incore/compiler.py, riferimenti a file interni inesistenti (simulator.py,NoiseModel,test21.py/main.py) incore/vector.py/core/noise.py/core/chunk.py, intestazioni "Sentinel Metrology Framework" e vecchio percorso internoshield_/...nei 4 moduli originali. - Simboli pubblici rinominati (uniche API realmente rotte da questa release, erano appena
diventate pubbliche in 1.1.6):
execute_pipeline_beast_mode->execute_pipeline_chunked(core/chunk.py);SENTINEL_PRESETS->SIGNAL_STABILIZER_PRESETS(core/preset.py);get_enterprise_logger->get_json_file_logger, nome logger/file di defaultsentinel*/sentinel_dashboard.log->dense_armor*/dense_armor.log, campo JSON"framework": "Sentinel-TensorFlowEngine"->"dense-armor"(core/logger.py);AIEngineVisualizer.ENGINE_SIGNATURE/intestazione del trend report non dicono piùTensorFlowEngine-Sentinel(core/visualizer.py). utility/orca.py: rimossa unasys.path.insert(...)morta (residuo di quando il modulo girava come script standalone fuori dal pacchetto -- gli import relativi non ne hanno bisogno, e nient'altro nel file usavasys/osdopo quella riga) e i relativi import inutilizzati.armatura.py: docstring e messaggi di uso CLI aggiornati dapython armatura.py/from armatura import Armatura(invocazione da script standalone) apython -m dense_armor/from dense_armor import Armatura(pacchetto installato).
[1.1.6]¶
Nessuna modifica alla matematica/logica di Armatura/Orca in questa versione. Partita
dalla richiesta di portare la coverage Codecov al 100%: 14 file in core//utility/
risultavano a 0% di coverage. Prima di cancellarli come "codice morto" (la prima ipotesi),
trovato dense_armor/README.md (riferimento interno) che li documenta tutti come componenti
reali e intenzionali — indagine invertita, modulo per modulo, invece di procedere con la
cancellazione.
Fixed¶
core/__init__.pyvuoto: il vero file di inizializzazione del pacchetto (import/export di 9 classi:AIHardwareProfiler,StochasticAdversarialNoise,UniversalMemoryGuard,MemoryPressureError,ParametricScenarioSimulator,BitwisePermutationEngine,DynamicAICodegen,CMD_MAP,TensorVault,AIEngineVisualizer,PipelineProfiler) esisteva ma era salvato comecore/init.py(nome sbagliato, mai eseguito da Python) — il file col nome giusto era vuoto. Nessuna di queste classi era importabile dadense_armor.core.*nonostante il codice reale esistesse. Contenuto spostato nel file col nome corretto,init.pyeliminato.core/preset.py: l'intero dizionarioSENTINEL_PRESETS(4 profili calibrati) era definito due volte verbatim nello stesso file. Rimossa la duplicazione.utility/iodat.py:lodat()ritornava in silenzio un tensore di dati casuali finti se il file richiesto non esisteva (solo un log di warning, nessun errore) — ora sollevaFileNotFoundError.
Added¶
- Test reali per tutti e 14 i moduli precedentemente a 0% di coverage:
chunk.py,compiler.py,memory.py,preset.py,logger.py,tensor.py,vector.py,noise.py,profiler.py,visualizer.py(core/);anwav.py,diagnostic.py,iodat.py(utility/, con file WAV/HDF5/NetCDF reali generati nei test, non mock) — più le righe ancora scoperte inresonance_search.py. Coverage totale del pacchetto: 54% -> 91%. - README: nuova sezione
$ toolkit --standaloneche documenta questi moduli come seconda parte del pacchetto, indipendente dallo scudo anomalie di Armatura/Orca.
[1.1.5]¶
Nessuna modifica alla matematica/logica di Armatura/Orca/robust_filters in questa
versione -- solo infrastruttura di test, documentazione e metadati.
Added¶
- Suite di test adversarial reale (
test/test_boundA-E.py): i test dietro la tabella "robustezza adversarial" del README (PGD/BIM/MI-FGSM, affine/elastica, Carlini-Wagner L2/Linf, DeepFool, Fourier a banda estesa) esistevano solo in un progetto precursore ("sentinel3", mai parte di questo repository) da cuiAdaptiveSignalStabilizerera stato estratto. Riportati qui con una modifica sostanziale: l'import punta ora adense_armor.core.engine.AdaptiveSignalStabilizer(il pacchetto reale) invece del modulo locale standalone del progetto precursore -- stessa classe, stessa API, ri-eseguiti e confermati riprodurre esattamente i numeri gia' nel README (PGD 0.0130, BIM 0.0625, MI-FGSM 0.0784, C&W L2 78.96%, C&W Linf 64.39%, DeepFool 78.79%, Fourier 99.77%+): la tabella e' ora una misura reale e riproducibile, non piu' un dato non tracciato. - Sito di documentazione (GitHub Pages, https://tatopenn-cell.github.io/Dense-Armor/):
home, guida rapida, riferimento API generato dai docstring reali dei 5 moduli pubblici
(
Armatura,Orca,hybrid_engine,engine,robust_filters), changelog/licenza sorgentati daCHANGELOG.md/LICENSE.md-- stesso impianto mkdocs-material gia' in uso su Dense-Evolution.
Fixed¶
- Due assert instabili in CI:
test_boundB.py/test_boundD.pyavevano unassert t_elapsed >= 5.0che non verificava la correttezza della difesa, solo che il calcolo fosse durato "abbastanza" sulla macchina di sviluppo originale -- falliva su ogni runner CI piu' veloce (verificato: 4.41s invece dei 5.0s richiesti su GitHub Actions, riprodotto su ubuntu/windows-latest, ogni versione Python). Rimosso: le assert reali (nessuna divergenza numerica) sono l'unica verifica che conta.
Changed¶
- Rimosso "Sentinel" (nome interno originale del progetto) dal testo rivolto
all'esterno: descrizione PyPI (
pyproject.toml), README, home della documentazione, docstring di__init__.py, e l'etichetta nella definizione di Software inLICENSE.md(solo l'etichetta, nessuna modifica ai componenti elencati/coperti dalla licenza). IlCHANGELOG.md(storia reale del progetto) e i commenti interni nel codice restano invariati.
[1.1.4]¶
Added¶
pressure_valve: orchestratore automatico dei quattro rilevatori aggiunti in 1.1.3. Non un voto (quanti metodi segnalano un punto) ma la combinazione classica a minima varianza (stimatore BLUE): ogni metodo produce una coppia (centro, scala) locale, i pesi della combinazione sono derivati con un moltiplicatore di Lagrange minimizzando la varianza della combinazione pesata sotto il vincolo che i pesi sommino a 1 -- w_k proporzionale a 1/scala_k^2, un metodo la cui incertezza si gonfia (es. Chauvenet quando la finestra contiene gia' un outlier) viene automaticamente pesato meno, senza scartarlo esplicitamente. Decisione finale sempre binaria (marcato/sostituito con la mediana locale, o intatto). Soglia di default (soglia_pressione=8.0) calibrata empiricamente -- 1/300 falsi positivi su rumore gaussiano puro, rilevamento corretto di un outlier chiaro e di due outlier ravvicinati.- Soglia dinamica via JSD ("la molla"):
soglia_pressionenon e' piu' fissa -- si confronta la finestra locale con una piu' ampia (Jensen- Shannon divergence) e si allarga proporzionalmente quando le due distribuzioni divergono (una vera transizione di regime in corso), senza mai smettere di decidere in binario. La soglia puo' solo allargarsi (JSD >= 0), mai restringersi sotto il valore base. - Bug reale trovato e corretto durante la calibrazione: la JSD "ingenua" (bin fissi, epsilon quasi-zero) risultava PIU' alta su rumore stazionario puro che vicino a una vera transizione -- il contrario esatto dello scopo del meccanismo, un artefatto da pochi campioni per bin. Corretto con binning adattivo (~6 campioni/bin) e smoothing di Laplace vero (pseudo-conteggio, non solo un epsilon per evitare log(0)) -- riverificato: JSD media vicino a una vera transizione 1.0->5.0 ora 2-3x piu' alta che su rumore stazionario alla stessa ampiezza, la direzione corretta.
[1.1.3]¶
Added¶
dense_armor/utility/robust_filters.py: quattro rilevatori di anomalie "a basso costo" -- Chauvenet's criterion (1863), Tukey's fences/IQR, Hampel filter, sigma-clipping iterativo. Statistiche classiche, note e usate da decenni in telemetria/astronomia, nessun modello dinamico/stato -- solo aritmetica su una finestra locale centrata (pensate per pulizia offline/ batch, non il ciclo causale real-time dicore/hybrid_engine.py; adatte anche a un futuro porting embedded/Arduino, dove non ci si potra' appoggiare a dipendenze come numpy).pressure_valve: orchestratore automatico dei quattro metodi sopra. Non un voto (quanti metodi segnalano un punto) ma la combinazione classica a minima varianza (stimatore BLUE): ogni metodo produce una coppia (centro, scala) locale, e i pesi della combinazione sono derivati con un moltiplicatore di Lagrange minimizzando la varianza della combinazione pesata sotto il vincolo che i pesi sommino a 1 -- il risultato, w_k proporzionale a 1/scala_k^2, pesa automaticamente meno un metodo la cui incertezza si gonfia (es. Chauvenet quando la finestra contiene gia' un outlier), senza doverlo scartare esplicitamente. Decisione finale sempre binaria (marcato/sostituito con la mediana locale, o intatto) -- nessuno stato intermedio, coerente col resto dell'ecosistema. Soglia di default (soglia_pressione=8.0) calibrata empiricamente (non "3 sigma": la scala combinata e' sempre piu' stretta di ogni scala singola) -- verificato 1/300 falsi positivi su rumore gaussiano puro, rilevamento corretto di un outlier chiaro e di due outlier ravvicinati (dove Chauvenet da solo soffre), nessun tocco su un gradino genuino sostenuto.
[1.1.2]¶
Fixed¶
hybrid_shieldrestava bloccato su un equilibrio sbagliato dopo un cambiamento di livello genuino e sostenuto (core/hybrid_engine.py): trovato testandoArmatura(livello_ia=0.0)dal vivo su uno scenario base (salto da 1.0 a 5.0, poi 30 punti stabili al nuovo livello). La finestra di baseline locale era presa daout(già guarito) invece che daprocessed(grezzo) — una scelta deliberata per proteggere da uno spike isolato passato che sposta la baseline, ma con un effetto collaterale peggiore su un gradino vero: i primi 1-2 punti dopo la transizione vengono inevitabilmente respinti (nessun rilevatore causale distingue un gradino vero da uno spike al primo campione), quei punti respinti finiscono inout, e la finestra dei punti successivi li rimedia dentro il proprio calcolo — producendo una baseline "a metà strada" che respinge ANCHE i punti successivi genuini, in un loop che si autoalimenta. Il segnale restava bloccato per sempre su un valore intermedio sbagliato (~4.0 invece di 5.0), non solo per una manciata di passi transitori. Fix: la finestra torna a usareprocessed(grezzo), riallineata aia_utils.vector_healing.enhanced_dense_healing_hybrid(il riferimento che questo modulo dichiara di generalizzare) — il valore di fallback è ora la MEDIANA della finestra grezza, non la media: la mediana ignora già da sola un singolo spike isolato nella finestra, quindi la protezione originale resta valida senza bisogno del truccoout(riverificato direttamente). Il test di regressione esistente (test_gradino_sostenuto_non_viene_appiattito_ma_lo_spike_isolato_si) non lo intercettava (tolleranza/finestra di controllo troppo larghe, passava anche col bug) — aggiunto un nuovo test più severo che fallisce sul codice vecchio e passa su quello corretto.
[1.1.1]¶
Fixed¶
- NaN sanato con la media globale della serie invece di una stima locale
(
core/hybrid_engine.py,armatura.py): trovato testando l'esempio esatto del README con un'installazione pulita da PyPI subito dopo la 1.1.0. UnNaNveniva sostituito connp.nanmeancalcolata su TUTTA la serie — se uno spike enorme era presente ovunque nella serie (anche lontanissimo), contaminava il sostituto di ogni NaN, non solo di quelli vicini allo spike. Su serie brevi il sostituto risultava assurdo (2000.81 invece di ~1.28 sull'esempio del README, n=6); su serie più lunghe l'effetto si attenuava ma non spariva (51.24 invece di ~1.0 su n=200) — non era un caso limite di serie corte, era la logica stessa. Se il NaN cadeva anche entro il raggio locale di uno spike (fino a 20 passi), la volatilità locale gonfiata dallo spike impediva pure al trigger di ricorreggerlo verso la baseline. Fix: nuova funzione_local_nan_fill— ogni NaN è sostituito con la mediana dei vicini FINITI in una finestra locale (mediana, non media: robusta a uno spike che capiti nella stessa finestra), non con una statistica sull'intera serie. La marcatura come anomalia (anomalie) era già sempre corretta anche col bug — solo il valore restituito inpulitoper quel punto era sbagliato.
[1.1.0]¶
Changed¶
- Motore di
Armaturasostituito:Armatura.analizza()non usa piùAdaptiveSignalStabilizer(Stadio 1) +ABCollatz(Stadio 2). Il gate ABCollatz era già documentato in [1.0.10] come matematicamente non discriminante (il radicale di una traiettoria di Collatz non ha relazione monotona con l'intensità del rumore — verificato con sweep numerico, e una sigmoide monotona alternativa, pur costruita e testata, peggiorava sistematicamente su 7 scenari reali). Sostituito con un motore a trigger binario (dense_armor/core/hybrid_engine.py), portato e adattato dalla logica phi_ab/vettore-dinamico/trigger già verificata in Dense-Evolution (dense_evolution/healing.py, oggi in produzione su PyPI dense-evolution= 8.1.9) e in
ia_utils.vector_healing.enhanced_dense_healing_hybrid. Tre adattamenti necessari per il nuovo dominio (serie scalari a scala libera, non vettori di embedding normalizzati), tutti verificati con test manuali mirati prima di essere accettati: (1) la baseline della finestra locale usa i valori già guariti (out), non quelli grezzi, altrimenti un outlier passato continua a spostare la baseline perradiuspassi dopo di sé; (2) l'IPG (gradiente istantaneo) resta sui valori grezzi, non su quelli guariti, altrimenti un punto respinto non lascia mai più traccia e nessun gradino reale può essere riconosciuto in seguito; (3) la distanza tra stati è normalizzata sulla volatilità locale del segnale (deviazione standard delle differenze successive nella finestra), non su una costante fissa né sulla grandezza assoluta dei valori — altrimenti o qualunque cambiamento sopra ~1.4 unità restava permanentemente appiattito, o (nel tentativo intermedio) anche uno spike isolato passava intatto perché "proporzionalmente coerente" con la propria grandezza. Contratto pubblico diArmaturainvariato (analizza,referto,referto_json,deriva, stessa firma del costruttore);Kè ora binario (0.0/1.0) invece di una sigmoide continua, coerente con la natura binaria del trigger sottostante. Orcanon è stata toccata in questa release: continua a usareAdaptiveSignalStabilizer+ABCollatz+apply_damping_blend. I numeri di robustezza adversarial nel README (PGD/Carlini-Wagner/Fourier) restano validi e non ricalcolati, perché misurati su quel motore.
[1.0.11]¶
Fixed¶
K_anomalousinapply_damping_blend(dense_armor/core/damping_operator.py): la formulac_anom/(c_anom+diff)dava K_anomalous massimo a differenza quasi nulla (segnale già pulito, dovrebbe damparsi poco) e lo faceva decrescere fino al pavimento_ALPHA=0.25al crescere della differenza — l'opposto di quanto dichiarato nella docstring della funzione. Invertito il rapporto (diff/(c_anom+diff)). Testato su 30 seed × 4 livelli di rumore: il fix batte l'originale 30/30 a rumore medio/alto/molto alto (RMSE fino a -0.137), ma peggiora sistematicamente a rumore molto basso (limite noto, documentato e coperto da test di regressione dedicato).
Added¶
dense_armor/utility/healing.py—healing_filter: nuovo modulo, a sé stante (non integrato inarmatura.py/orca.py), porting concettuale daDense-Evolution/dense_evolution/healing.py. A differenza di ABCollatz e del damping Stadio 1 (giudicano un punto dal proprio residuo istantaneo), classifica ogni punto guardando quanti vicini condividono la stessa deviazione dalla baseline locale — deviazione isolata = rumore (sostituita con la mediana di una finestra più ampia), deviazione sostenuta dai vicini = cambiamento vero (lasciata passare). Batte una mediana mobile a raggio 2 su segnali con transizioni vere + spike (40/40 e 30/30 su più varianti testate); perde su denoising puro di segnali stazionari a basso/medio rumore (limite noto, non è il suo caso d'uso primario).
[1.0.10]¶
Fixed¶
- Gate ABCollatz dello Stadio 2 (
compute_damping_gating): era matematicamente saturo a ~0.85 sempre, indipendentemente dal rumore vero — causa: il radicale di un intero (derivato dalla traiettoria di Collatz) non ha nessuna relazione monotona con la sua grandezza (verificato con sweep numerico: rumore crescente 0->1000 dava discrepanze 0, 300, 220, 600, -8, 13920, 0, senza andamento). Non era un problema di scala/compressione: l'artefatto restava identico su dati grezzi. - Costruita e testata anche una sigmoide monotona e scala-invariante sul
rumore relativo, genuinamente discriminante — ma su 7 scenari reali
(
test/testKalman.py), sia in modalità cieca sia con riferimento vero esplicito, peggiora sistematicamente l'RMSE rispetto al fallback costante 0.85 (anche alzando il pavimento minimo fino a 0.84). Nel design attuale di Orca il riferimento (Stadio 1) è già una stima affidabile: correggere sempre con forza verso di esso batte qualunque discriminazione basata sul rumore locale. Ripristinato il fallback costante, ora dichiarato esplicitamente invece di emergere per accidente da una formula rotta.
Nessun cambio di comportamento a runtime per chi già usa il pacchetto (RMSE e tempi verificati pressoché identici prima/dopo su tutti gli scenari di test) — il fix è di correttezza/manutenibilità del codice, non di funzionalità.
[1.0.9]¶
Indagine approfondita partita dalla verifica di uno script esterno che testava (male, con una reimplementazione a mano non fedele) il fix 1.0.8 — ha portato a scoprire e risolvere 4 bug distinti in cascata nello scudo entrata, ciascuno mascherato dal precedente.
Fixed¶
- Compressione log10 (
Orca._execute_4_phase_input_shield): due fattori di scala indipendenti (uno da pulito, uno da corrotto) collassavano qualunque valore alla stessa magnitudine compressa, distruggendo ogni differenza relativa prima ancora che Stadio 1/Stadio 2 la vedessero (verificato: anche un pass-through totale senza nessuna vera protezione ricostruiva il pulito esatto, allo stesso modo). Ora un solo fattore condiviso, derivato dal pulito, applicato a entrambi. - Stadio 2 ingannato dal segnale già ammortizzato:
compute_damping_gatingvalutavaf1(output dello Stadio 1) invece del segnale originale, sotto-stimando l'anomalia se già parzialmente corretta a monte. - Contaminazione post-shock dello Stadio 1: il motore ricorsivo
(
AdaptiveSignalStabilizer) lasciava sempre passare almeno il 25% (k_anom_min) di un'anomalia enorme nel proprio stato interno, che poi decadeva lentamente contaminando per diversi passi anche campioni successivi perfettamente normali. - Costante
c_anomfissa non in scala con i dati compressi: era comparabile in grandezza al rumore in spazio compresso, impedendo alla soppressione naturale delle anomalie di funzionare anche con lo State Flush attivo.
Added¶
- Hard-clamp deterministico:
raw_noise(dati grezzi originali, prima di qualunque compressione) > 0.05 forza il gate finale a 0.99 (non più 0.85, per non ereditare il pavimento pensato per i disturbi ordinari). - State Flush: lo stesso segnale hard-clamp, già autorevole, passato
anche allo Stadio 1 (nuovo parametro opzionale
hard_clamp_masksufilter_batch_scenarios/_process_single_scenario/_step_kernel) — azzera il pavimento di guadagno minimo solo per il passo flaggato. c_anomscala-adattiva: proporzionale alla magnitudine locale corrente (prev_filtered, già nello stato ricorsivo) invece di una costante fissa assoluta — si adatta da sola sia a dati grezzi (Armatura,filter_data_streamdiretto) sia a dati compressi (Orca), senza imporre l'assunzione di scala di un solo chiamante nella classe genericaAdaptiveSignalStabilizer.
Verificato con numeri reali: un outlier da 9999 in una serie di valori ~1.3 è ora protetto a 3.61 (indistinguibile dagli altri campioni ~3.4-3.62 dopo il modello), tutti i vicini tornano normali, margine d'errore corretto. Tutti i parametri nuovi sono opzionali con default che preservano il comportamento esistente per chi non li usa.
[1.0.8]¶
Fixed¶
Orca._execute_4_phase_input_shield: lo Stadio 2 (gating ABC/Collatz) valutavaf1, l'output già ammortizzato dallo Stadio 1 (AdaptiveSignalStabilizer), invece del segnale originale — se lo Stadio 1 riduceva parzialmente un outlier enorme, lo Stadio 2 poteva sotto-stimare quanto fosse anomalo l'input reale. Ora valuta il segnale compresso pre-Damping.
Added¶
- Sbarramento deterministico (hard-clamping): la soglia di rumore critico
(0.05) è calcolata sui dati grezzi originali, prima di qualunque
compressione log10 (che rinormalizzerebbe ogni valore individualmente,
facendo perdere l'intensità reale del rumore). Se superata, il gate
finale viene forzato alla blindatura massima (0.85), bypassando il
calcolo ABC/Collatz solo per le macro-anomalie; sotto soglia la
pipeline sinergica originale resta invariata. Verificato: il leak
residuo su una macro-anomalia crolla a zero.
@jax.jit/jax.vmapintatti, nessuna nuova chiamata eager fuori dal kernel precompilato.
[1.0.7]¶
Fixed¶
evaluate_abc_discrepancyarrotondava semprebprima di calcolare il radicale, anche quando chiamata dacompute_damping_gating_smooth— che genera apposta valoricollatz_wavenon arrotondati tramiteexecute_collatz_step_smooth. La variante smooth collassava così silenziosamente sulla stessa matematica di quella discreta, un livello più in basso di dove il docstring già avvertiva del rischio. Aggiuntosmooth_mode: bool = False(gestito conjnp.where, compatibile conjax.jit/vmap);compute_damping_gating_smoothora passasmooth_mode=True. La variante discreta di default non è cambiata. Verificato: prima del fix le due varianti davano risultati identici anche su input non interi; ora divergono, come previsto.
[1.0.6]¶
Risolti i 3 punti lasciati aperti come Known Issues in 1.0.5.
Changed¶
- Logging (comportamento cambiato, non solo interno): le ~14 chiamate
print()che stampavano lo stato diOrca.protect_and_forwardad ogni inferenza ([ORCA] Attivazione...,orca/utility/iodat.py,core/compiler.pysave/load pipeline) sono oralogging.getLogger(__name__). Di default sono silenziose (nessun handler configurato dalla libreria, convenzione standard) — per vederle di nuovo:logging.basicConfig(level=logging.INFO)prima di usare la libreria. Le ~35print()rimaste (inarmatura.pymetodoreferto()e CLImain(),utility/anwav.py,utility/diagnostic.py) non sono state toccate: sono referti/output voluti quando l'utente chiama esplicitamente quelle funzioni, non rumore di background — convertirle avrebbe reso silenzioso per default uno strumento il cui scopo e' stampare un referto.
Fixed¶
- Test JIT flaky sotto pressione di RAM:
Orca._gc_se_ram_bassa()chiamavajax.clear_caches()(svuota la cache di compilazione JIT di TUTTO il processo) alla stessa soglia morbida digc.collect()— se la RAM libera scendeva anche solo temporaneamente sotto quel margine preventivo, la precompilazione fatta in__init__veniva vanificata e ogni chiamata successiva ricompilava XLA da zero. Orajax.clear_caches()scatta solo al limite duro (min_free_ram), non al margine preventivo. Il test di regressione (test_orca_protect_and_forward_usa_la_cache_jit_ non_ricompila_ogni_volta) ora finge anche RAM abbondante via mock, rendendolo indipendente dallo stato reale della macchina/CI. - Copertura docstring/type hint: dal 53.9%/58.8% (55/60 su 102 funzioni) al 100% — tutte le funzioni/metodi pubblici e privati hanno ora una docstring one-line e annotazioni di tipo su argomenti/ritorno.
except Exception:generico: ristretto a eccezioni specifiche dove identificabili —core/memory.py(subprocess/parsing dinvidia-smia(CalledProcessError, TimeoutExpired, FileNotFoundError, OSError, ValueError); query VRAMjax.devices()a(RuntimeError, AttributeError)),core/noise.py(jax.default_backend()aRuntimeError). Lasciati broad-by-design, ma documentati con un commento:memory.py(pulizia cache JIT best-effort, ora anche loggata a livello debug invece dipasssilenzioso) eutility/resonance_search.py::smoke_test(per definizione uno smoke test deve catturare qualunque fallimento).
[1.0.5]¶
Fixed¶
pyproject.toml: campolicensecome tabella TOML deprecata da setuptools (avviso ad ogni build, sarebbe diventato errore bloccante dal 18 febbraio 2027) sostituito con stringa SPDX standard"BUSL-1.1"+license-files = ["LICENSE.md"]. Richiedesetuptools>=77(bumpato inbuild-system.requires).- Residuo del vecchio nome del progetto ("sentinel02") nei commenti di
intestazione di
dense_armor/core/damping_operator.pyedense_armor/utility/metro.py— aggiornati al percorso corrente (shield_/...), nessun impatto funzionale.
Known Issues (non risolti in questa versione)¶
- Logging: 49 chiamate a
print()sparse nel codice per i messaggi[ORCA] .... Esiste gia'dense_armor/core/logger.pyma non e' importato da nessun altro modulo — chi integra la libreria non puo' abbassare la verbosita', reindirizzare su file o silenziare i log senza modificare il sorgente. - Copertura docstring/type hint: su 102 funzioni/metodi totali, 55 hanno una docstring (53.9%) e 60 hanno almeno un type hint tra argomenti/valore di ritorno (58.8%).
except Exception:generico: 5 punti che catturano tutto silenziosamente —core/memory.py(righe 56, 72, 96),core/noise.py(riga 43),utility/resonance_search.py(riga 73). Da rivedere caso per caso: quali eccezioni sono davvero previste (es. file mancante) e quali nascondono bug che dovrebbero propagarsi.
[1.0.4]¶
Changed¶
- Nessuna modifica al codice rispetto a 1.0.3 — solo bump di versione, perche' 1.0.3 risultava gia' occupata su PyPI da un caricamento precedente.
[1.0.3]¶
Fixed¶
filter_data_stream(motore diAdaptiveSignalStabilizer) ricompilava l'intero kernel JIT ad ogni chiamata invece di riusare quello precompilato — kernel 1D spostato in__init__, soglie/gain passati come argomenti jit invece che letti daself.*dentro la funzione.jnp.insertchiamato fuori dajax.jitsubito dopo lo scan (stesso problema, punto diverso) — sostituito connp.insertpost-conversione.Orca._execute_4_phase_input_shield/_execute_4_phase_output_shieldeseguivano la loro pipeline elementwise (jnp.where/jnp.isnan/ aritmetica) in modalita' eager, fuori da qualunquejax.jit— estratta in due kernel dedicati precompilati una sola volta in__init__. Eliminate anche chiamate ridondanti duplicate afilter_batch_scenarios/compute_damping_gating.- Risultato: 3 chiamate a
protect_and_forwardpassano da ~11s a ~0.03s (zero ricompilazioni XLA residue a regime, verificato concProfile). - CI: workflow lanciava ancora
pytest tests/dopo la rinomina della cartella intest/— corretto in.github/workflows/tests.ymleREADME.md.
Changed¶
- Cartella dei test rinominata da
tests/atest/.
[1.0.1]¶
Fixed¶
- Bug critico:
evaluate_abc_discrepancy(radicale ABCollatz) calcolava il radicale sua*b*cinvece che sul valore generato dalla traiettoria di Collatz — la traiettoria era di fatto ignorata, producendo lo stesso risultato indipendentemente dal valore Collatz reale (verificato: b=7 e b=999999 davano discrepanze identiche prima del fix). - Dipendenza core mancante
psutilaggiunta apyproject.toml(trovata testando l'installazione del wheel in un venv pulito).
Added¶
- Suite di test pytest (
test/) e CI GitHub Actions (.github/workflows/tests.yml), matrice Python 3.10/3.11/3.12 su ubuntu/windows + controllo build/metadata. - Istruzioni per lanciare la suite di test in locale nel README.
[1.0.0]¶
Prima pubblicazione.