diff --git a/JobTrackerApi.Tests/DatabaseBackupRunnerTests.cs b/JobTrackerApi.Tests/DatabaseBackupRunnerTests.cs index f55b764..8f2f8b5 100644 --- a/JobTrackerApi.Tests/DatabaseBackupRunnerTests.cs +++ b/JobTrackerApi.Tests/DatabaseBackupRunnerTests.cs @@ -54,6 +54,39 @@ public sealed class DatabaseBackupRunnerTests : IDisposable Assert.Equal(2L, (long)(await count.ExecuteScalarAsync())!); } + [Fact] + public async Task RunOnce_includes_committed_WAL_rows_in_an_independent_restorable_snapshot() + { + CreateSourceDb(out var connectionString); + await using var source = new SqliteConnection(connectionString); + await source.OpenAsync(); + await using var write = source.CreateCommand(); + write.CommandText = "PRAGMA journal_mode=WAL;"; + Assert.Equal("wal", await write.ExecuteScalarAsync()); + write.CommandText = "INSERT INTO Sample (Name) VALUES ('committed-in-wal');"; + await write.ExecuteNonQueryAsync(); + Assert.True(new FileInfo(Path.Combine(_root, "source.db-wal")).Length > 0); + + var backupPath = await CreateRunner(connectionString).RunOnceAsync(CancellationToken.None); + Assert.NotNull(backupPath); + await using var backup = new SqliteConnection($"Data Source={backupPath};Mode=ReadOnly;Pooling=False"); + await backup.OpenAsync(); + await using var query = backup.CreateCommand(); + query.CommandText = "PRAGMA integrity_check;"; + Assert.Equal("ok", await query.ExecuteScalarAsync()); + query.CommandText = "SELECT COUNT(*) FROM Sample;"; + Assert.Equal(3L, await query.ExecuteScalarAsync()); + query.CommandText = "SELECT Name FROM Sample WHERE Id=3;"; + Assert.Equal("committed-in-wal", await query.ExecuteScalarAsync()); + + write.CommandText = "INSERT INTO Sample (Name) VALUES ('after-snapshot');"; + await write.ExecuteNonQueryAsync(); + query.CommandText = "SELECT COUNT(*) FROM Sample;"; + Assert.Equal(3L, await query.ExecuteScalarAsync()); + write.CommandText = "SELECT COUNT(*) FROM Sample;"; + Assert.Equal(4L, await write.ExecuteScalarAsync()); + } + [Fact] public async Task RunOnce_prunes_backups_beyond_retention() { diff --git a/JobTrackerApi.Tests/SqliteStartupConcurrencyTests.cs b/JobTrackerApi.Tests/SqliteStartupConcurrencyTests.cs new file mode 100644 index 0000000..fd6dad2 --- /dev/null +++ b/JobTrackerApi.Tests/SqliteStartupConcurrencyTests.cs @@ -0,0 +1,142 @@ +using JobTrackerApi.Data; +using JobTrackerApi.Models; +using JobTrackerApi.Services; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Identity; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Moq; +using Xunit; + +namespace JobTrackerApi.Tests; + +public sealed class SqliteStartupConcurrencyTests +{ + [Fact] + public async Task Fresh_and_populated_startup_enable_WAL_without_changing_owned_rows() + { + await using var database = await Database.CreateAsync(); + await using var connection = new SqliteConnection(database.ConnectionString); + await connection.OpenAsync(); + await using var journal = connection.CreateCommand(); + journal.CommandText = "PRAGMA journal_mode;"; + Assert.Equal("wal", await journal.ExecuteScalarAsync()); + + journal.CommandText = "PRAGMA journal_mode=DELETE;"; + Assert.Equal("delete", await journal.ExecuteScalarAsync()); + await connection.CloseAsync(); + await database.App.InitializeJobTrackerAsync(); + + await connection.OpenAsync(); + journal.CommandText = "PRAGMA journal_mode;"; + Assert.Equal("wal", await journal.ExecuteScalarAsync()); + await using var scope = database.App.Services.CreateAsyncScope(); + var db = scope.ServiceProvider.GetRequiredService(); + Assert.Equal(new[] { "other", "owner" }, await db.Users.OrderBy(user => user.Id).Select(user => user.Id).ToArrayAsync()); + Assert.Equal(new[] { "Other company", "Owned company" }, await db.Companies.IgnoreQueryFilters().OrderBy(company => company.Name).Select(company => company.Name).ToArrayAsync()); + Assert.All(await db.Users.ToListAsync(), user => Assert.Equal("en", user.UiLanguage)); + Assert.Empty(await db.Database.GetPendingMigrationsAsync()); + } + + [Fact] + public async Task An_open_read_cursor_does_not_block_usage_reservation_or_Identity_preference_save() + { + await using var database = await Database.CreateAsync(); + await using var connection = new SqliteConnection(database.ConnectionString); + await connection.OpenAsync(); + await using var command = connection.CreateCommand(); + command.CommandText = "SELECT Id FROM Companies ORDER BY Id;"; + await using var reader = await command.ExecuteReaderAsync(); + Assert.True(await reader.ReadAsync()); + + await using var scope = database.App.Services.CreateAsyncScope(); + var db = scope.ServiceProvider.GetRequiredService(); + var reservation = await new AiUsageMeter(db, TimeProvider.System).ReserveAsync( + "owner", new AccountEntitlements(true, true, 1_000_000, 10, 10_000), + "synchronous", "held-reader", "synchronous.summary", 40, 10, default); + Assert.True(reservation.Created); + var users = scope.ServiceProvider.GetRequiredService>(); + var owner = await users.FindByIdAsync("owner"); + Assert.NotNull(owner); + owner.UiLanguage = "nb-NO"; + Assert.True((await users.UpdateAsync(owner)).Succeeded); + + await using var verifyScope = database.App.Services.CreateAsyncScope(); + var verify = verifyScope.ServiceProvider.GetRequiredService(); + var usage = Assert.Single(await verify.AiUsageRecords.AsNoTracking().ToListAsync()); + Assert.Equal(reservation.Record.Id, usage.Id); + Assert.Equal("owner", usage.OwnerUserId); + Assert.Equal("held-reader", usage.SourceId); + Assert.Equal(1, usage.CallCount); + Assert.Equal("nb-NO", (await verify.Users.AsNoTracking().SingleAsync(user => user.Id == "owner")).UiLanguage); + Assert.Equal("en", (await verify.Users.AsNoTracking().SingleAsync(user => user.Id == "other")).UiLanguage); + Assert.Equal(2, await verify.Companies.IgnoreQueryFilters().CountAsync()); + Assert.True(await reader.ReadAsync()); + Assert.False(await reader.ReadAsync()); + } + + private sealed class Database(WebApplication app, string root, string connectionString) : IAsyncDisposable + { + public WebApplication App { get; } = app; + public string ConnectionString { get; } = connectionString; + + public static async Task CreateAsync() + { + var root = Path.Combine(Path.GetTempPath(), $"jobtracker-startup-lock-{Guid.NewGuid():N}"); + var connectionString = $"Data Source={Path.Combine(root, "jobtracker.db")};Default Timeout=1;Pooling=False"; + var builder = WebApplication.CreateBuilder(new WebApplicationOptions + { + EnvironmentName = "Production", + ApplicationName = typeof(StartupInitializationExtensions).Assembly.GetName().Name, + }); + builder.Configuration.AddInMemoryCollection(new Dictionary + { + ["Data:Root"] = root, + ["Database:Provider"] = "sqlite", + ["ConnectionStrings:JobTracker"] = connectionString, + ["Auth:AdminEmail"] = "", + ["Auth:AdminPassword"] = "", + }); + builder.Logging.ClearProviders(); + var currentUser = new Mock(); + currentUser.SetupGet(service => service.UserId).Returns("owner"); + builder.Services.AddSingleton(currentUser.Object); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddDbContext(options => options.UseSqlite(connectionString) + .ConfigureWarnings(warnings => warnings.Ignore(RelationalEventId.PendingModelChangesWarning))); + builder.Services.AddIdentityCore().AddRoles().AddEntityFrameworkStores(); + var app = builder.Build(); + var database = new Database(app, root, connectionString); + try + { + await app.InitializeJobTrackerAsync(); + await using var scope = app.Services.CreateAsyncScope(); + var db = scope.ServiceProvider.GetRequiredService(); + db.Users.AddRange( + new ApplicationUser { Id = "owner", UserName = "owner@example.test", UiLanguage = "en" }, + new ApplicationUser { Id = "other", UserName = "other@example.test", UiLanguage = "en" }); + db.Companies.AddRange( + new Company { Name = "Owned company", OwnerUserId = "owner" }, + new Company { Name = "Other company", OwnerUserId = "other" }); + await db.SaveChangesAsync(); + return database; + } + catch + { + await database.DisposeAsync(); + throw; + } + } + + public async ValueTask DisposeAsync() + { + await App.DisposeAsync(); + var fullRoot = Path.GetFullPath(root); + Assert.StartsWith(Path.GetFullPath(Path.GetTempPath()), fullRoot, StringComparison.OrdinalIgnoreCase); + Assert.StartsWith("jobtracker-startup-lock-", Path.GetFileName(fullRoot), StringComparison.Ordinal); + if (Directory.Exists(fullRoot)) Directory.Delete(fullRoot, recursive: true); + } + } +} diff --git a/JobTrackerApi/Services/StartupInitializationExtensions.cs b/JobTrackerApi/Services/StartupInitializationExtensions.cs index 3b060b5..9e20044 100644 --- a/JobTrackerApi/Services/StartupInitializationExtensions.cs +++ b/JobTrackerApi/Services/StartupInitializationExtensions.cs @@ -267,6 +267,9 @@ public static class StartupInitializationExtensions var provider = (app.Configuration["Database:Provider"] ?? "sqlite").Trim().ToLowerInvariant(); var useSqliteBootstrap = provider is not "mysql" and not "mariadb"; + // Reconciliation opens the file before EF creates it; enable WAL explicitly so readers do not block commits. + if (useSqliteBootstrap) db.Database.ExecuteSqlRaw("PRAGMA journal_mode = WAL;"); + // The schema reconciler. Runs TWICE: once before Migrate() and once after. // // Before: legacy databases need hand-added columns repaired and the legacy diff --git a/docs/database/migrations.md b/docs/database/migrations.md index 9184437..1743430 100644 --- a/docs/database/migrations.md +++ b/docs/database/migrations.md @@ -2,6 +2,8 @@ The API applies EF Core migrations on startup and performs guarded legacy-schema reconciliation for SQLite and MariaDB/MySQL compatibility. Fresh and repeated disposable SQLite migration chains are the normal local verification; generated provider SQL is not proof of a live production migration. +SQLite startup explicitly enables write-ahead logging (WAL) before reconciliation opens the database file. This lets concurrent readers coexist with committed usage and preference writes; it changes journaling, not stored fields or ownership. MariaDB/MySQL initialization is unchanged. Use the existing `VACUUM INTO` backup runner for a consistent SQLite snapshot, including committed WAL contents; copying only a running database's `.db` file is insufficient. Disposable startup, held-reader/write and independent backup tests cover this boundary; they do not replace production restore qualification. + The `Job`/`JobApplication` opportunity cutover is an expand/verify/contract change. Keep dual writes and compatibility reads until a production report and restored-backup rehearsal pass, then observe one release before removing legacy columns in a later migration. Never edit the history of an applied migration. ### Production cutover validation diff --git a/docs/redesign/phase-7/p7-12/CURRENT_FAILURES.md b/docs/redesign/phase-7/p7-12/CURRENT_FAILURES.md index 6f45773..0b7c5e1 100644 --- a/docs/redesign/phase-7/p7-12/CURRENT_FAILURES.md +++ b/docs/redesign/phase-7/p7-12/CURRENT_FAILURES.md @@ -1,6 +1,6 @@ # Patched-source integrated run — 21 October -This is investigation evidence, not final acceptance. The ordinary351-case run started05:32:57UTC against [patched entry](evidence/patched-entry.json), with zero configured retries. The run is still active at this observation; passing partial output does not establish a passing suite. +This is investigation evidence, not final acceptance. The ordinary351-case run started05:32:57UTC against [patched entry](evidence/patched-entry.json), with zero configured retries. It was explicitly stopped05:55:08UTC after144passing and3failing logged cases; passing partial output does not establish a passing suite. [Raw hashes](evidence/lock-observation.json) and the [subsequent bounded repair](SQLITE_CONCURRENCY.md) preserve the distinction between this failed attempt and successor verification. ## Reproduced SQLite failures diff --git a/docs/redesign/phase-7/p7-12/SQLITE_CONCURRENCY.md b/docs/redesign/phase-7/p7-12/SQLITE_CONCURRENCY.md new file mode 100644 index 0000000..933b7e5 --- /dev/null +++ b/docs/redesign/phase-7/p7-12/SQLITE_CONCURRENCY.md @@ -0,0 +1,30 @@ +# SQLite startup concurrency repair + +## Scope and reproduced failure + +The [patched-source run](CURRENT_FAILURES.md) was explicitly stopped05:55:08UTC after144passing and3failing logged cases out of351discovered cases. This is an interrupted failure, not a completed result. [Portable raw hashes](evidence/lock-observation.json) retain its command, traces and log even when later execution archives their paths. + +Two unchanged client assertions receive SQLite error5/HTTP500: owned restored-job readback and language preference persistence. Read-only inspection of the isolated database reports `journal_mode=delete`, `locking_mode=normal`. Actual bootstrap opens the file during reconciliation before EF's normal database creation path can establish WAL. A deterministic regression invokes that bootstrap without starting a listener, worker or provider, holds a SELECT cursor on one connection and attempts the actual usage reservation on another. Before repair it reproduces the browser's `SqliteDataRecord.Dispose → NextResult → AffectedCountModificationCommandBatch → AiUsageMeter.ReserveAsync` failure. The separate startup assertion reports expected`wal`, actual`delete`. + +The precise competing connection in the browser was not identified. The deterministic mechanism and unsafe journal mode are demonstrated; no claim is made that WAL eliminates all possible SQLite writer contention. + +## Bounded change and preservation + +Startup executes `PRAGMA journal_mode = WAL` once before SQLite reconciliation. The existing provider branch excludes MariaDB/MySQL. No schema, stored field, tenant filter, Identity contract, quota rule, summary output, write payload or save semantics changes. There is no new retry, swallowed exception, relaxed timeout or disabled assertion. Existing `VACUUM INTO` backup already supports WAL; [database guidance](../../../database/migrations.md) explains consistent snapshots. + +The new ordinary backend cases verify fresh and populated startup, reopening persistent mode after a deliberately DELETE-mode database, unchanged owned/foreign rows and completed migration history; usage INSERT and Identity preference UPDATE while the SELECT cursor remains open; committed WAL contents in an independent integrity-checked backup that does not change after later source writes. Local file cleanup stays within the uniquely named disposable fixture directory. + +## Validation + +| Run | Actual result | Qualification | +|---|---|---| +| Original focused RED,05:58:07–05:58:24UTC | 4PASS/2FAIL/0skipped | Startup mode and actual held-reader usage write fail; backup cases pass | +| Initial focused successor,05:58:44–05:58:53UTC | 5PASS/1FAIL/0skipped | Actual concurrent writes pass; populated-mode verification still used the external connection that set DELETE | +| Reopened-verification successor,05:59:34–05:59:41UTC | 6PASS/0FAIL/0skipped | Fixture closes the DELETE-setting connection before restart and reopens for the original expected`wal` assertion; no production change or weaker expectation | +| Fresh full backend,05:59:54–06:00:10UTC | **1060PASS/0FAIL/0skipped** | Current patched Release assemblies; plus3from1057:2startup/concurrency cases and1backup case | +| Ordinary affected browser vector,06:00:43–06:03:24UTC | **17PASS/0FAIL/0skipped/0flaky** | Actual Audit, application forms and paginated Users cases; zero configured retries | +| Release build,06:03:48–06:03:50UTC | PASS/0warnings/0errors | API and backend test projects, locked existing restore, warnings treated as errors | + +[Portable repair evidence](evidence/sqlite-repair.json) records the exact command/TRX/log paths and byte hashes for every backend attempt and the17-case native vector. Release build is recorded under `.artifacts/phase7/current/backend-career/p702-release-20261021T060348Z/`; its historical helper label does not make it Phase2 evidence. Complete351-case regression and the separate lanes remain required. + +PRES-E02/E08/E09, S03/S04/S05, W01/W04 and the wider57-group matrix remain binding. This finite regression does not supply human/device acceptance or production restore, ingress/configuration/schema/rollback evidence. Production readiness stays **NO-GO**. No Phase8 or withheld P5-13 implementation is introduced. diff --git a/docs/redesign/phase-7/p7-12/evidence/lock-observation.json b/docs/redesign/phase-7/p7-12/evidence/lock-observation.json new file mode 100644 index 0000000..d2166f4 --- /dev/null +++ b/docs/redesign/phase-7/p7-12/evidence/lock-observation.json @@ -0,0 +1,55 @@ +{ + "observedUtc": "2026-10-21T05:57:11.930894+00:00", + "entry": "patched-entry.json", + "command": { + "name": "main", + "command": [ + "rtk", + "proxy", + "node", + "node_modules/@playwright/test/cli.js", + "test", + "--retries=0", + "--reporter=list,json", + "--output=E:\\Storage\\Projects\\Active\\Job tracker\\.artifacts\\phase7\\current\\ui\\main-results" + ], + "startedUtc": "2026-10-21T05:32:58.246829+00:00", + "finishedUtc": "2026-10-21T05:55:08.627992+00:00", + "exitCode": 1, + "failureLog": "E:\\Storage\\Projects\\Active\\Job tracker\\.artifacts\\phase7\\current\\ui\\main.log" + }, + "partialLogObservations": { + "passed": 144, + "failed": 3, + "configuredInventory": 351 + }, + "qualification": "Explicitly interrupted failed run at05:55:08UTC; not a completed suite. No assertion was retried or suppressed. Later independent lanes did not start. Raw paths may move to attempts during the next run; byte hashes remain authoritative.", + "sqliteReadOnlyObservation": { + "path": "C:/Users/Connor/AppData/Local/Temp/jobtracker-e2e-16580-mvhoel1k/jobtracker.db", + "journalMode": "delete", + "lockingMode": "normal", + "qualification": "Read-only connection immediately closed. This identifies journal mode, not the competing lock holder or extended error code." + }, + "raw": [ + { + "path": ".artifacts/phase7/current/ui/main.log", + "sha256": "26c263a3ea76804ce0c44628b265108a2d11d092194d2bbc898b091939a50651" + }, + { + "path": ".artifacts/phase7/current/ui/main-command.json", + "sha256": "93b00dcfe0233e2cd345946904cdcf7be0921dc8335c739c1408465f2c7baa97" + }, + { + "path": ".artifacts/phase7/current/ui/main-results/phase7-admin-audit--P7-10--d26fe-y-user-requests-stay-denied-chromium/trace.zip", + "sha256": "a8d68428c620ca623ae1be822b7792e0499bd98d5413bf4e101709ae3530e314" + }, + { + "path": ".artifacts/phase7/current/ui/main-results/phase7-admin-users--P7-10--ff1fd-nd-protected-admin-controls-chromium/trace.zip", + "sha256": "2885e25285ffa40101749385142c2cb308d012cabc49da8a8f90f06e7a44a718" + }, + { + "path": ".artifacts/phase7/current/ui/main-results/phase7-application-forms---65823-without-asset-GET-or-writes-chromium/trace.zip", + "sha256": "341e0ee675b6478e2d115bbde836c54ef5f7c001fe6b7b5eeb794366a3f69cda" + } + ] +} diff --git a/docs/redesign/phase-7/p7-12/evidence/sqlite-repair-static-checks.json b/docs/redesign/phase-7/p7-12/evidence/sqlite-repair-static-checks.json new file mode 100644 index 0000000..d64df4a --- /dev/null +++ b/docs/redesign/phase-7/p7-12/evidence/sqlite-repair-static-checks.json @@ -0,0 +1,104 @@ +{ + "schemaVersion": 1, + "checkpoint": "p7-12", + "status": "PASS", + "checkedUtc": "2026-10-21T06:04:19.064310+00:00", + "commands": [ + { + "name": "secret-scan", + "command": [ + "rtk", + "proxy", + "C:\\Users\\Connor\\AppData\\Local\\Programs\\Python\\Python312\\python.exe", + "-X", + "utf8", + "scripts/supply-chain.py", + "scan-secrets" + ], + "startedUtc": "2026-10-21T06:04:15.650300+00:00", + "finishedUtc": "2026-10-21T06:04:18.265071+00:00", + "exitCode": 0, + "log": ".artifacts/phase7/current/static/20261021T060415Z/secret-scan.log", + "logSha256Raw": "be5119e6057287d397e87759f855c91e634e3bfac61c8be7584d05592ba2dfcd" + }, + { + "name": "working-whitespace", + "command": [ + "rtk", + "proxy", + "git", + "diff", + "--check" + ], + "startedUtc": "2026-10-21T06:04:18.265562+00:00", + "finishedUtc": "2026-10-21T06:04:18.351167+00:00", + "exitCode": 0, + "log": ".artifacts/phase7/current/static/20261021T060415Z/working-whitespace.log", + "logSha256Raw": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "name": "staged-whitespace", + "command": [ + "rtk", + "proxy", + "git", + "diff", + "--cached", + "--check" + ], + "startedUtc": "2026-10-21T06:04:18.351659+00:00", + "finishedUtc": "2026-10-21T06:04:18.433899+00:00", + "exitCode": 0, + "log": ".artifacts/phase7/current/static/20261021T060415Z/staged-whitespace.log", + "logSha256Raw": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "name": "phase7-whitespace", + "command": [ + "rtk", + "proxy", + "git", + "diff", + "--check", + "b5319981ad3d1bd635daff861453d23c0de5320b" + ], + "startedUtc": "2026-10-21T06:04:18.434374+00:00", + "finishedUtc": "2026-10-21T06:04:18.638948+00:00", + "exitCode": 0, + "log": ".artifacts/phase7/current/static/20261021T060415Z/phase7-whitespace.log", + "logSha256Raw": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + ], + "untrackedSecretScan": { + "files": 0, + "findings": 0 + }, + "documentation": { + "files": 45, + "relativeLinks": 691, + "errors": [] + }, + "preservation": { + "groups": 57, + "statuses": { + "PARTIAL": 56, + "PASS": 0, + "FAIL": 0, + "DEFERRED": 1 + }, + "originalRoutesPreserved": true, + "currentConcreteRoutes": 33 + }, + "production": { + "main": "5f835e54a9b74e6b71418422e28431b2af06453f", + "originMain": "5f835e54a9b74e6b71418422e28431b2af06453f", + "status": "NO-GO", + "qualification": "Local fetched refs; direct push observation recorded separately." + }, + "hashPolicy": "Portable JSON UTF8 LF; raw log hashes measure exact saved bytes.", + "limits": [ + "Pattern scanner skips binary and files over2MiB; not historical Git/production secrets.", + "Local links check target existence, not URL/heading anchors.", + "Static evidence is not runtime/manual/device/provider/production acceptance." + ] +} diff --git a/docs/redesign/phase-7/p7-12/evidence/sqlite-repair.json b/docs/redesign/phase-7/p7-12/evidence/sqlite-repair.json new file mode 100644 index 0000000..bddd1fc --- /dev/null +++ b/docs/redesign/phase-7/p7-12/evidence/sqlite-repair.json @@ -0,0 +1,220 @@ +{ + "observedUtc": "2026-10-21T06:03:47.685086+00:00", + "backend": [ + { + "name": "p712-sqlite-wal-red-20261021T055807Z", + "command": [ + "rtk", + "proxy", + "C:/Users/Connor/.dotnet/dotnet.exe", + "test", + "JobTrackerApi.Tests/JobTrackerApi.Tests.csproj", + "--configuration", + "Release", + "--no-restore", + "--filter", + "FullyQualifiedName~SqliteStartupConcurrencyTests|FullyQualifiedName~DatabaseBackupRunnerTests", + "--logger", + "trx;LogFileName=career.trx", + "--results-directory", + "E:\\Storage\\Projects\\Active\\Job tracker\\.artifacts\\phase7\\current\\backend-career\\p712-sqlite-wal-red-20261021T055807Z" + ], + "startedUtc": "2026-10-21T05:58:07.053581+00:00", + "finishedUtc": "2026-10-21T05:58:24.710760+00:00", + "exitCode": 1, + "logSha256": "fc09eea2f4090a1b28458cb3d3330c4066f12c9ebf5eb4b370b1f024992e19ab", + "counts": { + "total": 6, + "passed": 4, + "failed": 2, + "notExecuted": 0 + }, + "resultSha256": "3df5bf5e12e3a61da8188c97182963852a836ba0dd5f5a1b8c68111d59d62159", + "raw": [ + { + "path": ".artifacts/phase7/current/backend-career/p712-sqlite-wal-red-20261021T055807Z/command.json", + "sha256": "496d0c473184e8e8a47f7a371e3ae685d50c6560c08c5efebdd49504b81bef74" + }, + { + "path": ".artifacts/phase7/current/backend-career/p712-sqlite-wal-red-20261021T055807Z/career.log", + "sha256": "fc09eea2f4090a1b28458cb3d3330c4066f12c9ebf5eb4b370b1f024992e19ab" + }, + { + "path": ".artifacts/phase7/current/backend-career/p712-sqlite-wal-red-20261021T055807Z/career.trx", + "sha256": "3df5bf5e12e3a61da8188c97182963852a836ba0dd5f5a1b8c68111d59d62159" + } + ] + }, + { + "name": "p712-sqlite-wal-green-20261021T055844Z", + "command": [ + "rtk", + "proxy", + "C:/Users/Connor/.dotnet/dotnet.exe", + "test", + "JobTrackerApi.Tests/JobTrackerApi.Tests.csproj", + "--configuration", + "Release", + "--no-restore", + "--filter", + "FullyQualifiedName~SqliteStartupConcurrencyTests|FullyQualifiedName~DatabaseBackupRunnerTests", + "--logger", + "trx;LogFileName=career.trx", + "--results-directory", + "E:\\Storage\\Projects\\Active\\Job tracker\\.artifacts\\phase7\\current\\backend-career\\p712-sqlite-wal-green-20261021T055844Z" + ], + "startedUtc": "2026-10-21T05:58:44.038215+00:00", + "finishedUtc": "2026-10-21T05:58:53.717954+00:00", + "exitCode": 1, + "logSha256": "515d76ceb2153821639bc700f593c761181728f73ba634dba69e325ea3ae26f8", + "counts": { + "total": 6, + "passed": 5, + "failed": 1, + "notExecuted": 0 + }, + "resultSha256": "9e1a6cf48888ee2e0793e98eb44da489fd1008611af2ccfa4d7f2f47cc987f52", + "raw": [ + { + "path": ".artifacts/phase7/current/backend-career/p712-sqlite-wal-green-20261021T055844Z/command.json", + "sha256": "e41307845a10ac889e78b20e09f4c871c378823987a9f8daece4029d4f1f8a2e" + }, + { + "path": ".artifacts/phase7/current/backend-career/p712-sqlite-wal-green-20261021T055844Z/career.log", + "sha256": "515d76ceb2153821639bc700f593c761181728f73ba634dba69e325ea3ae26f8" + }, + { + "path": ".artifacts/phase7/current/backend-career/p712-sqlite-wal-green-20261021T055844Z/career.trx", + "sha256": "9e1a6cf48888ee2e0793e98eb44da489fd1008611af2ccfa4d7f2f47cc987f52" + } + ] + }, + { + "name": "p712-sqlite-wal-green-reopened-20261021T055934Z", + "command": [ + "rtk", + "proxy", + "C:/Users/Connor/.dotnet/dotnet.exe", + "test", + "JobTrackerApi.Tests/JobTrackerApi.Tests.csproj", + "--configuration", + "Release", + "--no-restore", + "--filter", + "FullyQualifiedName~SqliteStartupConcurrencyTests|FullyQualifiedName~DatabaseBackupRunnerTests", + "--logger", + "trx;LogFileName=career.trx", + "--results-directory", + "E:\\Storage\\Projects\\Active\\Job tracker\\.artifacts\\phase7\\current\\backend-career\\p712-sqlite-wal-green-reopened-20261021T055934Z" + ], + "startedUtc": "2026-10-21T05:59:34.116441+00:00", + "finishedUtc": "2026-10-21T05:59:41.908713+00:00", + "exitCode": 0, + "logSha256": "7e0bc56242ee07b416cc5f525020bcbe26719e373a633e512a105456be9d99f5", + "counts": { + "total": 6, + "passed": 6, + "failed": 0, + "notExecuted": 0 + }, + "resultSha256": "f665c8fee93546b483e437a57fbbd8565f744e420e84126687ce58ee7c4de2cb", + "raw": [ + { + "path": ".artifacts/phase7/current/backend-career/p712-sqlite-wal-green-reopened-20261021T055934Z/command.json", + "sha256": "c2a5f73f1507a1415fdd2a754b308ce964e5623c575675e29ccc5fbcdffebbca" + }, + { + "path": ".artifacts/phase7/current/backend-career/p712-sqlite-wal-green-reopened-20261021T055934Z/career.log", + "sha256": "7e0bc56242ee07b416cc5f525020bcbe26719e373a633e512a105456be9d99f5" + }, + { + "path": ".artifacts/phase7/current/backend-career/p712-sqlite-wal-green-reopened-20261021T055934Z/career.trx", + "sha256": "f665c8fee93546b483e437a57fbbd8565f744e420e84126687ce58ee7c4de2cb" + } + ] + }, + { + "name": "p712-sqlite-wal-full-20261021T055954Z", + "command": [ + "rtk", + "proxy", + "C:/Users/Connor/.dotnet/dotnet.exe", + "test", + "JobTrackerApi.Tests/JobTrackerApi.Tests.csproj", + "--configuration", + "Release", + "--no-build", + "--logger", + "trx;LogFileName=career.trx", + "--results-directory", + "E:\\Storage\\Projects\\Active\\Job tracker\\.artifacts\\phase7\\current\\backend-career\\p712-sqlite-wal-full-20261021T055954Z" + ], + "startedUtc": "2026-10-21T05:59:54.473875+00:00", + "finishedUtc": "2026-10-21T06:00:10.139200+00:00", + "exitCode": 0, + "logSha256": "23e0cee4d8a88d88d8b770235a44402114d01d5bfdf8462d98e30daeb678d94f", + "counts": { + "total": 1060, + "passed": 1060, + "failed": 0, + "notExecuted": 0 + }, + "resultSha256": "b9109fc0a1c8c24129f6ef5a450540a5826cad3de599c3a84d4652ae8682ec66", + "raw": [ + { + "path": ".artifacts/phase7/current/backend-career/p712-sqlite-wal-full-20261021T055954Z/command.json", + "sha256": "163bcbce9dce2a553c078dbb4a0cb9835fb5d45947d401c03d2b33c84e07529c" + }, + { + "path": ".artifacts/phase7/current/backend-career/p712-sqlite-wal-full-20261021T055954Z/career.log", + "sha256": "23e0cee4d8a88d88d8b770235a44402114d01d5bfdf8462d98e30daeb678d94f" + }, + { + "path": ".artifacts/phase7/current/backend-career/p712-sqlite-wal-full-20261021T055954Z/career.trx", + "sha256": "b9109fc0a1c8c24129f6ef5a450540a5826cad3de599c3a84d4652ae8682ec66" + } + ] + } + ], + "affectedBrowser": { + "stats": { + "startTime": "2026-10-21T06:00:43.909Z", + "duration": 160381.752, + "expected": 17, + "skipped": 0, + "unexpected": 0, + "flaky": 0 + }, + "raw": [ + { + "path": ".artifacts/phase7/current/ui/lock-repair.json", + "sha256": "ffee3294ad9d65fbd5773b3a44c71b74bb24bfcd4b2c41c2a11166d62142627c" + }, + { + "path": ".artifacts/phase7/current/ui/lock-repair.log", + "sha256": "144fb525ba4f91a716aee3f79769cea249547c884cc108b8512617a4f62a133b" + } + ] + }, + "runtimeSources": [ + { + "path": "JobTrackerApi/Services/StartupInitializationExtensions.cs", + "sha256": "41557a2d51ddf988bcf129115bfb496add4fe053bd0bb295c0751e5de23e0679" + }, + { + "path": "JobTrackerApi.Tests/SqliteStartupConcurrencyTests.cs", + "sha256": "536a801f74b9499340e53c2dcf1e138d00498bc1413558d64db69732f80adc1a" + }, + { + "path": "JobTrackerApi.Tests/DatabaseBackupRunnerTests.cs", + "sha256": "3107d265b272775eead9b26e0b56f8560db15f9102f0f421fe7fcfc63ec9bbc3" + } + ], + "countDelta": { + "previousBackend": 1057, + "currentBackend": 1060, + "newStartupConcurrencyCases": 2, + "newWalBackupCases": 1 + }, + "qualification": "Focused vectors overlap ordinary full-suite cases and are never added to its count. Full browser acceptance, manual/device evaluation and production release qualification remain separate." +} diff --git a/docs/redesign/phase-7/p7-12/evidence/test-counts.json b/docs/redesign/phase-7/p7-12/evidence/test-counts.json index cea9b95..1cef186 100644 --- a/docs/redesign/phase-7/p7-12/evidence/test-counts.json +++ b/docs/redesign/phase-7/p7-12/evidence/test-counts.json @@ -160,13 +160,15 @@ }, "backend": { "baseline": 1033, - "current": 1057, + "current": 1060, "addedCases": { "careerEvidenceSQLite": 1, "companyCompatibilitySQLite": 3, "documentsOwnerProjection": 12, "emailDraftRotationAndUTC": 7, - "operationNotificationUTC": 1 + "operationNotificationUTC": 1, + "sqliteStartupConcurrency": 2, + "committedWalBackup": 1 } }, "sidecar": {