milestone 30: overview as a dashboard, explicit health contract, period aggregations

This commit is contained in:
2026-08-22 16:45:15 +02:00
parent 0e83477d80
commit 623667e475
89 changed files with 7222 additions and 4239 deletions
+113 -10
View File
@@ -20,9 +20,8 @@
//! having no store.
//!
//! **Time is a parameter, not a seam.** Every method that stamps a row takes
//! `now_s` (`purge`/`purgeAll` only delete, so they take none),
//! matching `upstream/history.zig` and `storage/logger.zig`. Production callers
//! read `Clock.real`; tests pass literals. There is no clock in here and no
//! `now_s` (`purge`/`purgeAll` only delete, so they take none), matching
//! `storage/logger.zig`. Production callers read `Clock.real`; tests pass literals. There is no clock in here and no
//! function pointer standing in for one.
//!
//! **`resolve` is on the DNS hot path.** `pool.recordSuccess` calls it after
@@ -39,9 +38,15 @@ const events_repo = @import("repositories/events_repo.zig");
const log = std.log.scoped(.events);
/// Every failure episode nxdns can record. **The enum is the truth**: its
/// cardinality is what the exhaustive tests, the API and the frontend copy map
/// all count, and the dotted string is only its wire form.
/// Every failure episode nxdns can **emit**, and nothing else. **The enum is
/// the truth** about what a running process writes: the exhaustive tests count
/// it, and the dotted string is only its wire form.
///
/// It is not the whole documented union. The API and the frontend copy map hold
/// these plus `legacy_wire_codes`, because a stored row outlives its producer —
/// see there. A code whose producer is gone moves to that list rather than
/// staying here: a member here is a code `Store.report` accepts, and a report
/// under a dead code opens an episode nothing can ever resolve.
pub const Code = enum {
disk_space,
disk_probe,
@@ -52,7 +57,6 @@ pub const Code = enum {
query_log_write,
query_log_maintenance,
query_log_recreated,
upstream_history_write,
upstream_exchange,
client_names_storage,
clients_storage,
@@ -75,7 +79,6 @@ pub fn wire(code: Code) []const u8 {
.query_log_write => "query_log.write",
.query_log_maintenance => "query_log.maintenance",
.query_log_recreated => "query_log.recreated",
.upstream_history_write => "upstream_history.write",
.upstream_exchange => "upstream.exchange",
.client_names_storage => "client_names.storage",
.clients_storage => "clients.storage",
@@ -92,7 +95,6 @@ pub fn component(code: Code) []const u8 {
.blocklist_refresh, .blocklist_snapshot, .blocklist_storage => "blocklist",
.certificate_reload => "certificate",
.query_log_write, .query_log_maintenance, .query_log_recreated => "query_log",
.upstream_history_write => "upstream_history",
.upstream_exchange => "upstream",
.client_names_storage => "client_names",
.clients_storage => "clients",
@@ -101,6 +103,21 @@ pub fn component(code: Code) []const u8 {
};
}
/// Wire codes that stored rows still carry and no code emits.
///
/// They live as text, not as `Code` members, because the read path is the only
/// path that meets them: the list endpoint passes a stored code straight
/// through, and `events_repo.componentOf` derives the component from the text.
/// The one thing the store must still do with them is close whatever a past
/// release left open — see `Store.init`.
///
/// The documented event-code union is these plus every `Code`, which is what
/// keeps a real response describing an old row inside the contract.
pub const legacy_wire_codes = [_][]const u8{
// Milestone 30 deleted the upstream-minute history subsystem.
"upstream_history.write",
};
/// Fixed per emit call, not per code: the same disk monitor reports a
/// transition to `warn` as a warning and one to `critical` as an error.
///
@@ -198,6 +215,17 @@ pub const Store = struct {
pub fn init(io: std.Io, database: *db.Db, now_s: i64) db.Error!Store {
var store: Store = .{ .database = database };
// Before the mirror load and the prune, both of which would otherwise
// read a state this is about to change: an episode left open here would
// sit in the mirror forever and hold `untracked_active_count` above
// zero, and one resolved after the prune would keep its row for another
// ninety days. `now_s` is the resolution time on purpose — the store's
// own open time, not the episode's stale `last_seen`, which a database
// older than the retention window would prune in this very call.
for (legacy_wire_codes) |code| {
_ = try events_repo.resolveActiveByCode(database, now_s, code);
}
var chunk: [16]events_repo.ActiveRow = undefined;
var after: i64 = 0;
while (store.active.len < mirror_capacity) {
@@ -688,7 +716,7 @@ const testing = std.testing;
test "wire and component are exhaustive, unique and agree with each other" {
const all = std.enums.values(Code);
try testing.expectEqual(@as(usize, 15), all.len);
try testing.expectEqual(@as(usize, 14), all.len);
for (all, 0..) |code, i| {
const text = wire(code);
@@ -722,6 +750,7 @@ const Fixture = struct {
io: std.Io = undefined,
database: db.Db = undefined,
store: Store = undefined,
text_buf: [256]u8 = undefined,
fn init(self: *Fixture, now_s: i64) !void {
self.threaded = .init(testing.allocator, .{});
@@ -744,8 +773,82 @@ const Fixture = struct {
fn count(self: *Fixture, sql: []const u8) !i64 {
return self.database.queryInt(sql);
}
fn text(self: *Fixture, sql: []const u8) ![]const u8 {
var stmt = try self.database.prepare(sql);
defer stmt.deinit();
if (!try stmt.step()) return error.NoRow;
const value = stmt.columnText(0);
@memcpy(self.text_buf[0..value.len], value);
return self.text_buf[0..value.len];
}
};
test "an m29 flush-failure episode is resolved once at init and stays listable" {
var fx: Fixture = .{};
try fx.init(1000);
defer fx.deinit();
// The state an m29 database is left in: a live episode under a code that no
// producer emits any more, so nothing will ever resolve it. Seeded through
// the repository and not through `report`, because the live emitter cannot
// name this code — that it cannot is half of what m30 changed.
_ = try events_repo.insertActive(
&fx.database,
1000,
legacy_wire_codes[0],
"history",
"history",
"warning",
"Busy",
);
try testing.expectEqual(
@as(i64, 1),
try fx.count("SELECT count(*) FROM operational_events WHERE resolved_at IS NULL"),
);
// Reopening is what m30 upgrades on. Ninety days later, so a resolution
// stamped with the episode's own `last_seen` would be pruned by this very
// call rather than left for an operator to read.
const reopened_at = 1000 + Store.resolved_retention_s + 86_400;
const upgraded = try Store.init(fx.io, &fx.database, reopened_at);
try testing.expectEqual(@as(i64, 1), try fx.count("SELECT count(*) FROM operational_events"));
try testing.expectEqual(
@as(i64, reopened_at),
try fx.count("SELECT resolved_at FROM operational_events"),
);
try testing.expectEqualStrings(
"upstream_history.write",
try fx.text("SELECT code FROM operational_events"),
);
// Nothing is open, so the mirror is empty and the SQL slow path is off.
try testing.expectEqual(@as(u32, 0), upgraded.active.len);
try testing.expectEqual(@as(u32, 0), upgraded.untracked_active_count);
// A second start finds nothing left to close, and the sweep is idempotent.
const again = try Store.init(fx.io, &fx.database, reopened_at + 60);
try testing.expectEqual(@as(u32, 0), again.active.len);
try testing.expectEqual(
@as(i64, reopened_at),
try fx.count("SELECT resolved_at FROM operational_events"),
);
}
test "a legacy wire code is not something the live emitter can name" {
for (legacy_wire_codes) |legacy| {
try testing.expectEqual(@as(?Code, null), parseWire(legacy));
for (std.enums.values(Code)) |code| {
try testing.expect(!std.mem.eql(u8, legacy, wire(code)));
}
// The read path still has to answer for it, and does so from the text.
const dot = std.mem.indexOfScalar(u8, legacy, '.') orelse
return error.TestUnexpectedResult;
try testing.expectEqualStrings(legacy[0..dot], events_repo.componentOf(legacy));
try testing.expect(legacy.len <= events_repo.code_capacity);
}
}
test "a failure opens one episode and repeats of it count rather than multiply" {
var fx: Fixture = .{};
try fx.init(1000);