milestone 11: systemd and docker packaging, operator and architecture docs, config and api reference, docs drift guards

This commit is contained in:
2026-08-02 15:24:10 +02:00
parent a589df7515
commit bdb6ffab7a
29 changed files with 1936 additions and 94 deletions
+57
View File
@@ -0,0 +1,57 @@
//! Textual-containment guards that keep the hand-written docs honest
//! (milestone-11 ruling 3). They assert presence, not correctness — the same
//! contract as openapi.zig's route guard.
const std = @import("std");
const docs = @import("docs_files");
const routes = @import("web/routes.zig");
const model = @import("config/model.zig");
test "every served operation has its own table row in docs/api.md" {
const gpa = std.testing.allocator;
for (routes.table) |route| {
// Matches one full method + path cell pair ("| GET | `/api/groups` |"),
// so neither a same-path sibling method nor a longer-path prefix can
// satisfy the check for a missing operation.
const needle = try std.fmt.allocPrint(gpa, "| {s} | `{s}` |", .{
@tagName(route.method), route.pattern,
});
defer gpa.free(needle);
if (std.mem.indexOf(u8, docs.api_md, needle) == null) {
std.debug.print("operation row missing from docs/api.md: {s}\n", .{needle});
return error.OperationMissingFromApiDoc;
}
}
}
test "every settings key appears in docs/config-reference.md" {
const gpa = std.testing.allocator;
var pairs: std.ArrayList(model.SettingPair) = .empty;
defer {
model.freeSettings(gpa, pairs.items);
pairs.deinit(gpa);
}
try model.toSettings(.{}, gpa, &pairs);
for (pairs.items) |pair| {
if (std.mem.indexOf(u8, docs.config_reference_md, pair.key) == null) {
std.debug.print("settings key missing from docs/config-reference.md: {s}\n", .{pair.key});
return error.SettingsKeyMissingFromConfigDoc;
}
}
}
test "every cli subcommand has its own reference heading in docs/operator.md" {
const gpa = std.testing.allocator;
const subcommands = [_][]const u8{ "run", "check", "export", "import", "version", "help" };
for (subcommands) |name| {
// Anchors on the reference-section heading ("### `import FILE`" starts
// with "### `import"), so prose mentions elsewhere cannot mask a
// removed command section.
const needle = try std.fmt.allocPrint(gpa, "### `{s}", .{name});
defer gpa.free(needle);
if (std.mem.indexOf(u8, docs.operator_md, needle) == null) {
std.debug.print("subcommand heading missing from docs/operator.md: {s}\n", .{needle});
return error.SubcommandMissingFromOperatorDoc;
}
}
}