orama/vault/src/guardian.zig

90 lines
3.1 KiB
Zig

/// Top-level Guardian struct — orchestrates all subsystems.
///
/// The Guardian is the main runtime object that ties together:
/// - HTTP server (client-facing, port 7500)
/// - Peer protocol (guardian-to-guardian, port 7501)
/// - Node membership (via RQLite or static config)
/// - Heartbeat/health management
/// - Storage operations
const std = @import("std");
const log = @import("log.zig");
const config = @import("config.zig");
const node_list = @import("membership/node_list.zig");
const quorum = @import("membership/quorum.zig");
const heartbeat = @import("peer/heartbeat.zig");
pub const Guardian = struct {
cfg: config.Config,
nodes: node_list.NodeList,
allocator: std.mem.Allocator,
/// Random server secret for HMAC-based auth (generated at startup)
server_secret: [32]u8,
/// Share count cache (refreshed periodically)
share_count: u32,
pub fn init(allocator: std.mem.Allocator, cfg: config.Config) !Guardian {
// Generate server secret
var secret: [32]u8 = undefined;
std.crypto.random.bytes(&secret);
// Try to load node list from RQLite, fall back to self-only
var nodes = node_list.fetchFromRqlite(allocator, cfg.rqlite_url, cfg.client_port) catch blk: {
log.warn("failed to fetch node list from RQLite, running in single-node mode", .{});
const self_addr = [_][]const u8{cfg.listen_address};
break :blk try node_list.fromStatic(allocator, &self_addr, cfg.client_port);
};
// Mark self as alive
if (nodes.nodes.len > 0) {
nodes.self_index = 0;
nodes.nodes[0].state = .alive;
nodes.nodes[0].last_seen_ns = std.time.nanoTimestamp();
}
const share_count = heartbeat.countShares(cfg.data_dir);
return Guardian{
.cfg = cfg,
.nodes = nodes,
.allocator = allocator,
.server_secret = secret,
.share_count = share_count,
};
}
pub fn deinit(self: *Guardian) void {
self.nodes.deinit();
// Zero out server secret
@memset(&self.server_secret, 0);
}
/// Get current write quorum requirement.
pub fn writeQuorum(self: *const Guardian) usize {
return quorum.writeQuorum(self.nodes.aliveCount());
}
/// Get current Shamir threshold (read quorum).
pub fn readThreshold(self: *const Guardian) usize {
return self.nodes.threshold();
}
/// Refresh share count from disk.
pub fn refreshShareCount(self: *Guardian) void {
self.share_count = heartbeat.countShares(self.cfg.data_dir);
}
};
// ── Tests ────────────────────────────────────────────────────────────────────
test "guardian: init and deinit" {
const allocator = std.testing.allocator;
const cfg = config.Config{
.data_dir = "/tmp/nonexistent-vault-test",
};
var g = try Guardian.init(allocator, cfg);
defer g.deinit();
try std.testing.expectEqual(@as(u32, 0), g.share_count);
}