milestone 19: hygiene sweep - dead ecs surface, single-source constants, tls classification, frontend state hazards, docker smoke network fix
CI / test (push) Successful in 1m46s
CI / test-aarch64 (push) Successful in 5m30s
CI / frontend (push) Successful in 46s
CI / cross (push) Successful in 8m12s
CI / docker (push) Successful in 3m46s

This commit is contained in:
2026-08-07 20:39:27 +02:00
parent 6f67940995
commit 6c507992e4
59 changed files with 1020 additions and 382 deletions
+46 -14
View File
@@ -332,10 +332,10 @@ pub fn applyPut(
const hash_changed = password != null and !std.mem.eql(u8, previous_hash, cfg.web.password_hash);
const replacement: ?[]u8 = if (hash_changed) try state.gpa.dupe(u8, cfg.web.password_hash) else null;
if (writeSettings(arena, database, cfg)) |err| {
writeSettings(arena, database, cfg) catch |err| {
if (replacement) |hash| state.gpa.free(hash);
return .{ .fail = .{ .internal = err } };
}
};
if (replacement) |hash| {
// Ruling 17, both halves: the running server must verify against the
@@ -354,26 +354,36 @@ pub fn applyPut(
/// costs a few dozen upserts and buys the guarantee that the table is exactly
/// what `model.toSettings` says the merged configuration is — no key can be
/// missed and none can be left behind.
fn writeSettings(arena: Allocator, database: *db.Db, cfg: model.Config) ?db.Error {
fn writeSettings(arena: Allocator, database: *db.Db, cfg: model.Config) db.Error!void {
var pairs: std.ArrayList(model.SettingPair) = .empty;
model.toSettings(cfg, arena, &pairs) catch return error.OutOfMemory;
try model.toSettings(cfg, arena, &pairs);
var tx = db.Tx.begin(database) catch |err| return err;
var tx = try db.Tx.begin(database);
errdefer tx.rollback();
try write_fault.check();
for (pairs.items) |pair| {
settings_repo.putSetting(database, pair.key, pair.value) catch |err| {
tx.rollback();
return err;
};
try settings_repo.putSetting(database, pair.key, pair.value);
}
tx.commit() catch |err| {
tx.rollback();
return err;
};
return null;
try tx.commit();
}
/// Fails one `writeSettings` after its transaction has begun, so a test can
/// prove the `errdefer` above rolls that transaction back rather than leaving
/// the shared connection inside it. Test builds only, and it reduces to nothing
/// everywhere else — the rotation seam's shape (logging.zig).
const write_fault = if (builtin.is_test) struct {
var armed: bool = false;
fn check() db.Error!void {
if (!armed) return;
armed = false;
return error.Internal;
}
} else struct {
fn check() db.Error!void {}
};
fn problem(arena: Allocator, cfg: model.Config) error{OutOfMemory}!?[]const u8 {
return mutations.firstProblem(arena, cfg);
}
@@ -581,6 +591,28 @@ test "a put that would not validate writes nothing" {
try testing.expectEqual(@as(u16, 53), stored.dns.port);
}
test "a write that fails after the transaction begins rolls it back" {
var bench: mutations.Bench = undefined;
try bench.init(testing.allocator);
defer bench.deinit(testing.allocator);
try seeded(&bench);
var patch: Patch = .{};
patch.dns = .{ .port = 5353 };
write_fault.armed = true;
const outcome = try applyPut(&bench.state, bench.io(), bench.arena(), patch);
try testing.expect(outcome.fail == .internal);
// BEGIN IMMEDIATE inside an open transaction is an error, so a second
// `begin` succeeding is what proves the errdefer ran.
var tx = try db.Tx.begin(&bench.database);
tx.rollback();
const stored = try mutations.loadConfig(bench.arena(), &bench.database);
try testing.expectEqual(@as(u16, 53), stored.dns.port);
}
test "an enum value the model does not know names the key it came from" {
var bench: mutations.Bench = undefined;
try bench.init(testing.allocator);