milestone 20: declarative configuration for iac

This commit is contained in:
2026-08-11 23:31:40 +02:00
parent 2f29121e27
commit d76afc147a
74 changed files with 6722 additions and 1949 deletions
+42
View File
@@ -1199,6 +1199,14 @@ pub const Manager = struct {
if (state != .ok) return true;
const last = row.last_updated orelse return true;
// The Pi has no RTC, so a fetch stamped while the clock ran ahead of
// real time (a pre-NTP boot, a restored image) leaves a `last_updated`
// in the future. Plain interval arithmetic would then suspend every
// refresh until real time caught up with the poison stamp, and the
// reconcile engine preserves runtime columns faithfully, so nothing
// else would ever clear it. A stamp from the future is not evidence of
// a recent fetch.
if (last > now) return true;
return now - last >= model.updateIntervalSeconds(self.update);
}
@@ -1674,6 +1682,40 @@ test "acquire before any reload returns null and holds no lock" {
manager.lock.unlock(io);
}
test "needsRefresh treats a last_updated in the future as due" {
var threaded: std.Io.Threaded = .init(testing.allocator, .{});
defer threaded.deinit();
const io = threaded.io();
var database = try openMigrated();
defer database.close();
var f: fetcher.Fetcher = undefined;
var manager = try testManager(&database, &f);
defer manager.deinit(io);
// `.ok` is the only state that consults the clock at all; every other one
// is already due, so the arithmetic below would be unreachable without it.
var statuses = [_]SourceStatus{.{ .id = 1, .state = .ok }};
manager.statuses = &statuses;
defer manager.statuses = &.{};
const row = testRow(1, true);
const stamp = row.last_updated.?;
const interval = model.updateIntervalSeconds(manager.update);
// The ordinary cases still hold: fresh is not due, stale is.
try testing.expect(!manager.needsRefresh(io, row, stamp + 1));
try testing.expect(manager.needsRefresh(io, row, stamp + interval));
// The Pi has no RTC. A fetch stamped while the clock ran ahead of real
// time leaves `now - last` negative, which reads as "fetched moments ago"
// and suspends every refresh until real time catches the poison stamp —
// for a whole day here, and for as long as the clock was wrong in general.
try testing.expect(manager.needsRefresh(io, row, stamp - 1));
try testing.expect(manager.needsRefresh(io, row, stamp - 86_400));
}
test "the disk gate skips a scheduled refresh only while writes are critical" {
var threaded: std.Io.Threaded = .init(testing.allocator, .{});
defer threaded.deinit();