milestone 8: web server, rest api, sse, auth, metrics and static assets

This commit is contained in:
2026-08-02 00:54:13 +02:00
parent a8092bb1b9
commit 5253c47303
59 changed files with 19640 additions and 150 deletions
+52 -7
View File
@@ -7,9 +7,11 @@
//! the moment the query finishes. That is the whole reason this file has fixed
//! buffers instead of slices.
//!
//! The privacy transforms of §11.4 run inside `log`, before the entry is
//! enqueued, so nothing downstream — the database now, Phase 8's event stream
//! later — can observe a value the operator asked to hide.
//! The privacy transforms of §11.4 run in `transformed`, before the entry is
//! enqueued, so nothing downstream — the database or the event stream — can
//! observe a value the operator asked to hide. `log` is the two halves in
//! order; `QuerySink` calls them separately so both of its consumers see the
//! one transformed entry.
//!
//! Log rows are expendable. A full queue drops the oldest unflushed entry, a
//! failed batch is dropped whole, and a disk that crossed the critical
@@ -191,10 +193,23 @@ pub const Logger = struct {
/// Applies the privacy transforms and enqueues without ever blocking the
/// query path. A full queue loses its oldest unflushed entry (§11.4).
pub fn log(self: *Logger, io: std.Io, entry: Entry) void {
var transformed = entry;
if (self.cfg.hide_domains) transformed.setDomain(hidden_marker);
if (self.cfg.hide_client_ips) transformed.setClientIp(hidden_marker);
self.enqueue(io, transformed);
self.logTransformed(io, self.transformed(entry));
}
/// The §11.4 privacy transforms, on their own. `QuerySink` runs them once
/// and hands the result to every consumer, so nothing downstream — the
/// database or the event stream — can observe a value the operator asked
/// to hide.
pub fn transformed(self: *const Logger, entry: Entry) Entry {
var out = entry;
if (self.cfg.hide_domains) out.setDomain(hidden_marker);
if (self.cfg.hide_client_ips) out.setClientIp(hidden_marker);
return out;
}
/// `log` without the transforms, for a caller that already applied them.
pub fn logTransformed(self: *Logger, io: std.Io, entry: Entry) void {
self.enqueue(io, entry);
}
/// Retries until the put succeeds, and each failed attempt drops exactly
@@ -556,6 +571,36 @@ test "log hides only the field its switch names" {
try testing.expectEqualStrings("192.0.2.10", untouched.clientIp());
}
test "the split halves reproduce log byte for byte" {
var threaded: std.Io.Threaded = .init(testing.allocator, .{});
defer threaded.deinit();
const io = threaded.io();
const configs = [_]model.Logging{
.{},
.{ .hide_domains = true },
.{ .hide_client_ips = true },
.{ .hide_domains = true, .hide_client_ips = true },
};
for (configs) |cfg| {
var buf: [4]Entry = undefined;
var logger: Logger = .init(cfg, &buf);
const source = sampleEntry(100, "tracker.example");
logger.log(io, source);
logger.logTransformed(io, logger.transformed(source));
const from_log = try logger.queue.getOne(io);
const from_halves = try logger.queue.getOne(io);
try testing.expectEqualSlices(
u8,
std.mem.asBytes(&from_log),
std.mem.asBytes(&from_halves),
);
}
}
test "a full queue drops the oldest entry and counts it" {
var threaded: std.Io.Threaded = .init(testing.allocator, .{});
defer threaded.deinit();