fix(database): enable SQLite WAL before schema reconciliation
This commit is contained in:
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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<JobTrackerContext>();
|
||||
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<JobTrackerContext>();
|
||||
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<UserManager<ApplicationUser>>();
|
||||
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<JobTrackerContext>();
|
||||
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<Database> 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<string, string?>
|
||||
{
|
||||
["Data:Root"] = root,
|
||||
["Database:Provider"] = "sqlite",
|
||||
["ConnectionStrings:JobTracker"] = connectionString,
|
||||
["Auth:AdminEmail"] = "",
|
||||
["Auth:AdminPassword"] = "",
|
||||
});
|
||||
builder.Logging.ClearProviders();
|
||||
var currentUser = new Mock<ICurrentUserService>();
|
||||
currentUser.SetupGet(service => service.UserId).Returns("owner");
|
||||
builder.Services.AddSingleton(currentUser.Object);
|
||||
builder.Services.AddSingleton<AppPaths>();
|
||||
builder.Services.AddSingleton<IStartupReadiness, StartupReadiness>();
|
||||
builder.Services.AddDbContext<JobTrackerContext>(options => options.UseSqlite(connectionString)
|
||||
.ConfigureWarnings(warnings => warnings.Ignore(RelationalEventId.PendingModelChangesWarning)));
|
||||
builder.Services.AddIdentityCore<ApplicationUser>().AddRoles<IdentityRole>().AddEntityFrameworkStores<JobTrackerContext>();
|
||||
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<JobTrackerContext>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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."
|
||||
]
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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": {
|
||||
|
||||
Reference in New Issue
Block a user