Skip to content

Commit df00540

Browse files
committed
release: CA-O 2.2.14 con punto de restauracion obligatorio pre-overclock
- Las 3 vias de escritura crean si o si el punto del sistema; si falla, cero writes - 6 tests de la puerta; suite 1860 passed, 0 failed; pipeline 4/4 exit 0
1 parent 24f2291 commit df00540

11 files changed

Lines changed: 475 additions & 41 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
Formato basado en [Keep a Changelog](https://keepachangelog.com/es-ES/1.1.0/).
44

55
## [Unreleased]
6+
## [2.2.14] - 2026-10-13
7+
### Anadido
8+
- **Punto de restauracion obligatorio antes de overclock.** Las tres vias de escritura (perfil, tuning por knob, automatico) crean si o si un punto de restauracion del sistema (`SystemRestore` por WMI) antes de tocar nada; si no se puede crear, no se escribe (ni journal, ni checkpoints, ni driver). Vale tambien ante cancelacion o error.
9+
### Probado
10+
- Suite 1860 passed, 0 failed (Security 274, Benchmark 17, Infrastructure 202, Core 931, UI 376, Integration 60). 6 tests nuevos de la puerta.
611
## [2.2.13] - 2026-10-13
712
### Anadido
813
- **Overclock automatico con propuesta.** Boton en la tarjeta 00 PERFIL que recorre cada control escribible con biseccion hasta su techo (mismo `TuningEngine`, checkpoints, recovery D4 y puerta anti-cheat; se detiene si hay anti-cheat o ajustes pendientes). Al terminar PROPONE el perfil validado y el usuario pulsa Aplicar: nunca muta silicio sin confirmacion.

‎README.md‎

Lines changed: 26 additions & 25 deletions
Large diffs are not rendered by default.

‎Version.props‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
<Project>
22
<!-- Single source of truth for versioning (FASE 28). All other files derive from here. -->
33
<PropertyGroup>
4-
<Version>2.2.13</Version>
5-
<AssemblyVersion>2.2.13.0</AssemblyVersion>
6-
<FileVersion>2.2.13.0</FileVersion>
7-
<PackageVersion>2.2.13</PackageVersion>
8-
<ProductVersion>2.2.13</ProductVersion>
9-
<InformationalVersion>2.2.13+$(GHTag)</InformationalVersion>
4+
<Version>2.2.14</Version>
5+
<AssemblyVersion>2.2.14.0</AssemblyVersion>
6+
<FileVersion>2.2.14.0</FileVersion>
7+
<PackageVersion>2.2.14</PackageVersion>
8+
<ProductVersion>2.2.14</ProductVersion>
9+
<InformationalVersion>2.2.14+$(GHTag)</InformationalVersion>
1010
</PropertyGroup>
1111
</Project>
1212

‎docs/ARCHITECTURE.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ Nunca se registran secretos, credenciales ni contenido de entrada del usuario (s
114114
- **i18n**: diccionario tipado es-ES/en-US (`Localizer`); migración a `.resw` planificada sin cambiar llamadas (`Localizer.Get(key)`).
115115
- **HAGS/VBS/MPO/etc.**: nunca tratados como ganancias universales; HAGS es WorkloadDependent+Conditional+RequiresReboot, VBS es Critical+SecurityTradeoff+ExpertOnly.
116116

117-
## Pruebas (1854 passed, 0 failed — Release)
117+
## Pruebas (1860 passed, 0 failed — Release)
118118

119119
| Suite | Cubre | Count |
120120
|---|---|---|
@@ -123,5 +123,5 @@ Nunca se registran secretos, credenciales ni contenido de entrada del usuario (s
123123
| `CA-O.Integration.Tests` | `E2EFlowsTests` 9 pruebas (8 casos numerados + benchmark: Abrir→Analizar→Persistir→Vanguard→Restore→Benchmark) + `TransactionJournalRecovery` + contrato docs + `PrivilegedServicePackagingTests` (payload self-contained) | 60 |
124124
| `CA-O.Infrastructure.Tests` | `HistoryRobustness` (malformed), `SnapshotRepository` (TX identity), `SystemContextCache` dual-TTL | 202 |
125125
| `CA-O.Benchmark.Tests` | `SystemBenchmarkRunner` (suelo 3%, mediana) | 17 |
126-
| `CA-O.UI.Tests` | `ViewModelTests` (Analyze/Dashboard con `SystemAnalysisService` + `Correlation`), Overclock (filas visuales + mood de tuning), tours por pagina (83 pasos, destinos verificados), auto-tuning con propuesta | 370 |
127-
| **Total** | **Gates 1-5 `verify.ps1` + `build-release` con `gui-installer`** | **1854** |
126+
| `CA-O.UI.Tests` | `ViewModelTests` (Analyze/Dashboard con `SystemAnalysisService` + `Correlation`), Overclock (filas visuales + mood de tuning), tours por pagina (83 pasos, destinos verificados), auto-tuning con propuesta, puerta de restore point | 376 |
127+
| **Total** | **Gates 1-5 `verify.ps1` + `build-release` con `gui-installer`** | **1860** |

‎installer/CA-O.iss‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
; (lee la versión de BuildConstants.cs y firma con CAO_SIGN_THUMBPRINT si existe)
44

55
#ifndef AppVersion
6-
#define AppVersion "2.2.13"
6+
#define AppVersion "2.2.14"
77
#endif
88
#ifndef RepoRoot
99
#define RepoRoot ".."

‎src/CA-O.Setup/Program.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// Instalador CA-O de un solo exe: ventana con progreso (sin consola).
66
// Descarga el paquete, deriva al setup gráfico bonito y sale; si no hay
77
// GUI, instala por aquí con la misma ventana de progreso.
8-
var productVersion = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version?.ToString(3) ?? "2.2.13";
8+
var productVersion = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version?.ToString(3) ?? "2.2.14";
99
var logFile = Path.Combine(Path.GetTempPath(), "CA-O-Setup.log");
1010
try { File.AppendAllText(logFile, $"\n[{DateTime.Now:O}] Setup iniciado\n"); } catch { }
1111

‎src/CA-O.Shared/Constants/BuildConstants.cs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ namespace CAO.Shared.Constants;
1111
public static class BuildConstants
1212
{
1313
// Version - SINGLE SOURCE OF TRUTH
14-
public const string ProductVersion = "2.2.13";
14+
public const string ProductVersion = "2.2.14";
1515
public const string ProductVersionMajor = "2";
1616
public const string ProductVersionMinor = "2";
17-
public const string ProductVersionPatch = "13";
17+
public const string ProductVersionPatch = "14";
1818

1919
// Product Identity
2020
public const string ProductName = "CA-O";

‎src/CA-O.UI/Resources/Localizer.cs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,8 @@ public static class Localizer
200200
["overclock-profile-note"] = "Los presets aplican valores del catalogo; solo un ajuste guiado produce un perfil validado.",
201201
["overclock-profile-applying"] = "Aplicando perfil...",
202202
["overclock-profile-restoring"] = "Restaurando valores...",
203+
["overclock-restore-point-creating"] = "Creando punto de restauración...",
204+
["overclock-restore-point-blocked"] = "Sin punto de restauración no se escribe nada: {0}",
203205
["overclock-row-timed-out"] = "El backend no contesto a tiempo; la fila quedo sin leer.",
204206
["overclock-gpu-driver-blocked"] = "Este driver no implementa el ajuste. Actualiza el driver de NVIDIA para habilitarlo.",
205207
["overclock_gpu_write_limits"] = "Rango seguro segun el driver",
@@ -653,6 +655,8 @@ public static class Localizer
653655
["overclock-profile-note"] = "Presets apply catalog values; only a guided tuning run produces a validated profile.",
654656
["overclock-profile-applying"] = "Applying profile...",
655657
["overclock-profile-restoring"] = "Restoring previous values...",
658+
["overclock-restore-point-creating"] = "Creating a restore point...",
659+
["overclock-restore-point-blocked"] = "No restore point, no writes: {0}",
656660
["overclock-row-timed-out"] = "The backend did not answer in time; the row was left unread.",
657661
["overclock-gpu-driver-blocked"] = "This driver does not implement the tuning. Update the NVIDIA driver to enable it.",
658662
["overclock_gpu_write_limits"] = "Safe range per the driver",

‎src/CA-O.UI/ViewModels/OverclockViewModel.cs‎

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Collections.ObjectModel;
22
using System.Globalization;
33
using System.Text;
4+
using CAO.Core.Abstractions;
45
using CAO.Core.Overclock;
56
using CAO.Core.Overclock.Reporting;
67
using CAO.Core.Overclock.Stability;
@@ -11,6 +12,7 @@
1112
using CAO.Infrastructure.Overclock;
1213
using CAO.Infrastructure.Overclock.Reporting;
1314
using CAO.Infrastructure.Persistence;
15+
using CAO.Infrastructure.SystemInterop;
1416
using CAO.Shared;
1517
using System;
1618
using System.Collections.Generic;
@@ -133,6 +135,7 @@ public sealed partial class OverclockViewModel : ObservableObject
133135
private IValidatedProfileStore? _profileStore;
134136
private ITuningCheckpointStore? _checkpointStore;
135137
private IOverclockJournal? _journal;
138+
private IRestorePointService? _restorePointService;
136139
internal static readonly TimeSpan DefaultRowTimeout = TimeSpan.FromSeconds(30);
137140

138141
internal const string RowTimedOutReasonKey = "overclock-row-timed-out";
@@ -158,7 +161,8 @@ public OverclockViewModel(
158161
IValidatedProfileStore? profileStore = null,
159162
ITuningCheckpointStore? checkpointStore = null,
160163
IOverclockJournal? journal = null,
161-
TimeSpan? rowTimeout = null)
164+
TimeSpan? rowTimeout = null,
165+
IRestorePointService? restorePointService = null)
162166
{
163167
_registry = registry;
164168
_backends = backends;
@@ -172,6 +176,7 @@ public OverclockViewModel(
172176
_profileStore = profileStore;
173177
_checkpointStore = checkpointStore;
174178
_journal = journal;
179+
_restorePointService = restorePointService;
175180
}
176181

177182
/// <summary>
@@ -218,6 +223,59 @@ public OverclockViewModel(
218223
private IOverclockJournal JournalFor(Guid runId) =>
219224
_journal ?? new TransactionOverclockJournal(new FileTransactionJournal(), runId);
220225

226+
/// <summary>
227+
/// The real WMI service, built on first use like every other hardware door:
228+
/// constructing the page must never pay for a restore point nobody asked
229+
/// for. Injectable so tests drive the gate without touching WMI.
230+
/// </summary>
231+
private IRestorePointService RestorePoints =>
232+
_restorePointService ??= new WmiRestorePointService();
233+
234+
/// <summary>
235+
/// Puerta única de escritura: sin punto de restauración no se escribe nada.
236+
/// Orden del operador: crear SÍ O SÍ el punto antes de cualquier escritura
237+
/// de overclock; si no se puede crear, las tres vías (perfil, knob, auto)
238+
/// vuelven con mensaje y cero escrituras. Un timeout o una cancelación es
239+
/// un fallo, no un permiso: también bloquea.
240+
/// </summary>
241+
internal async Task<bool> EnsureRestorePointAsync(CancellationToken ct)
242+
{
243+
StatusLine = Localizer.Get("overclock-restore-point-creating");
244+
CrashLog.Step("overclock.restore-point-start");
245+
try
246+
{
247+
var (success, reason) = await RestorePoints
248+
.CreateAsync("CA-O overclock: punto previo a escritura", ct)
249+
.ConfigureAwait(true);
250+
if (success)
251+
{
252+
CrashLog.Step("overclock.restore-point-done");
253+
return true;
254+
}
255+
256+
StatusLine = Localizer.Format("overclock-restore-point-blocked", reason);
257+
CrashLog.Step("overclock.restore-point-blocked");
258+
return false;
259+
}
260+
catch (OperationCanceledException)
261+
{
262+
StatusLine = Localizer.Format(
263+
"overclock-restore-point-blocked", Localizer.Get("overclock_cancelled"));
264+
CrashLog.Step("overclock.restore-point-cancelled");
265+
return false;
266+
}
267+
catch (Exception ex)
268+
{
269+
// El servicio real ya traga sus internals WMI; si algo llega aquí
270+
// es plomería, y su mensaje no sale al usuario.
271+
CrashLog.Write(ex);
272+
StatusLine = Localizer.Format(
273+
"overclock-restore-point-blocked", Localizer.Get("overclock_probe_failed"));
274+
CrashLog.Step("overclock.restore-point-failed");
275+
return false;
276+
}
277+
}
278+
221279
// Single source of truth: the registry decides the composition, the
222280
// ViewModel never re-lists the backends by hand (that divergence is
223281
// exactly how the CPU backend went missing once already).
@@ -1169,6 +1227,15 @@ private async Task StartTuningAsync(string knobId, CancellationToken ct)
11691227
IsBusy = true;
11701228
try
11711229
{
1230+
// Sin punto no hay bisección: el motor escribe checkpoints, journal
1231+
// y valores del driver desde el primer punto.
1232+
if (!await EnsureRestorePointAsync(ct).ConfigureAwait(true))
1233+
{
1234+
SetMood(MoodForTuning(false, null));
1235+
CrashLog.Step("tuning.blocked-no-restore-point");
1236+
return;
1237+
}
1238+
11721239
StatusLine = Localizer.Get("overclock-tuning-running");
11731240
SetMood(MoodForTuning(true, null));
11741241
var progress = new Progress<TuningProgress>(p =>
@@ -1402,6 +1469,17 @@ private async Task StartAutoTuningAsync(CancellationToken ct)
14021469
return;
14031470
}
14041471

1472+
// Después de las puertas locales (recovery, anti-cheat, accionables):
1473+
// crear un punto para una pasada que ya está bloqueada sería pagar
1474+
// minutos de WMI para no escribir nada. Sigue garantizando cero
1475+
// escrituras si falla, porque ningún write corre antes de aquí.
1476+
if (!await EnsureRestorePointAsync(ct).ConfigureAwait(true))
1477+
{
1478+
ApplySummary = StatusLine;
1479+
CrashLog.Step("auto.blocked-no-restore-point");
1480+
return;
1481+
}
1482+
14051483
StatusLine = Localizer.Get("overclock-auto-running");
14061484
SetMood(MoodForTuning(true, null));
14071485
CrashLog.Step("auto.start");
@@ -1587,6 +1665,15 @@ private async Task ApplyProfileAsync(CancellationToken ct)
15871665
CrashLog.Step("refresh.apply-start");
15881666
try
15891667
{
1668+
// Sin punto no hay apply: ExecuteApplyAsync abre el journal, guarda
1669+
// checkpoints y escribe en el driver desde el primer knob.
1670+
if (!await EnsureRestorePointAsync(ct).ConfigureAwait(true))
1671+
{
1672+
ApplySummary = StatusLine;
1673+
CrashLog.Step("refresh.apply-blocked-no-restore-point");
1674+
return;
1675+
}
1676+
15901677
var work = CollectApplyWork(profile);
15911678
if (work.Count == 0)
15921679
{

0 commit comments

Comments
 (0)