const std = @import("std");const session = @import("session.zig");const session_codec = @import("session_codec.zig");const session_event = @import("session_event.zig");const types = @import("../shared/types.zig"); const Allocator = std.mem.Allocator;const Sha256 = std.crypto.hash.sha2.Sha256; pub const manifest_max_bytes: usize = 64 * 1024;pub const checkpoint_max_bytes: usize = 32 * 1024 * 1024;pub const Digest = [Sha256.digest_length]u8; pub const Manifest = struct { id: []u8, authority_id: session_event.Identifier, log_generation: session_event.Identifier, created_at_ms: i64, updated_at_ms: i64, origin_workspace_root: []u8, workspace_root: []u8, conversation_language: session.ConversationLanguage, history_len: u64, total_input_tokens: u64, total_output_tokens: u64, last_event_seq: u64, event_log_bytes: u64, event_log_stat_fingerprint: Digest, generation_base_seq: u64, generation_base_bytes: u64, checkpoint_seq: ?u64, checkpoint_sha256: ?Digest, preferences: session_codec.DurableSessionPreferences, pub fn deinit(self: *Manifest, alloc: Allocator) void { alloc.free(self.id); alloc.free(self.origin_workspace_root); alloc.free(self.workspace_root); self.preferences.deinit(alloc); self.* = undefined; }}; pub const EventFileKind = enum(u8) { regular = 1, directory = 2, symbolic_link = 3, other = 255,}; pub const EventFileStat = struct { device: u64, inode: u64, kind: EventFileKind, mode: u64, link_count: u64, size: u64, mtime_ns: i128, ctime_ns: i128,}; pub const Checkpoint = struct { session_id: []u8, log_generation: session_event.Identifier, through_seq: u64, through_event_id: session_event.Identifier, through_event_log_bytes: u64, state: session_codec.DurableSessionState, pub fn deinit(self: *Checkpoint, alloc: Allocator) void { alloc.free(self.session_id); self.state.deinit(alloc); self.* = undefined; }}; pub const EventBoundary = struct { log_generation: session_event.Identifier, seq: u64, event_id: session_event.Identifier, event_log_bytes: u64, semantic: bool,}; pub fn encodeManifest(alloc: Allocator, manifest: Manifest) ![]u8 { var out: std.Io.Writer.Allocating = .init(alloc); defer out.deinit(); try out.writer.writeAll( "{\"schema_version\":3,\"storage_format\":\"event_log_v1\",\"id\":", ); try writeJsonString(&out.writer, manifest.id); try out.writer.writeAll(",\"authority_id\":"); try writeHexString(&out.writer, &manifest.authority_id); try out.writer.writeAll(",\"log_generation\":"); try writeHexString(&out.writer, &manifest.log_generation); try out.writer.print(",\"created_at_ms\":{d},\"updated_at_ms\":{d}", .{ manifest.created_at_ms, manifest.updated_at_ms, }); try out.writer.writeAll(",\"origin_workspace_root\":"); try writeJsonString(&out.writer, manifest.origin_workspace_root); try out.writer.writeAll(",\"workspace_root\":"); try writeJsonString(&out.writer, manifest.workspace_root); try out.writer.writeAll(",\"conversation_language\":"); try writeJsonString(&out.writer, manifest.conversation_language.view()); try out.writer.print( ",\"history_len\":{d},\"total_input_tokens\":{d},\"total_output_tokens\":{d},\"last_event_seq\":{d},\"event_log_bytes\":{d},\"event_log_stat_fingerprint\":", .{ manifest.history_len, manifest.total_input_tokens, manifest.total_output_tokens, manifest.last_event_seq, manifest.event_log_bytes, }, ); try writeHexString(&out.writer, &manifest.event_log_stat_fingerprint); try out.writer.print(",\"generation_base_seq\":{d},\"generation_base_bytes\":{d},\"checkpoint_seq\":", .{ manifest.generation_base_seq, manifest.generation_base_bytes, }); if (manifest.checkpoint_seq) |seq| { try out.writer.print("{d}", .{seq}); } else { try out.writer.writeAll("null"); } try out.writer.writeAll(",\"checkpoint_sha256\":"); if (manifest.checkpoint_sha256) |digest| { try writeHexString(&out.writer, &digest); } else { try out.writer.writeAll("null"); } try out.writer.writeAll(",\"preferences\":"); try writePreferences(&out.writer, manifest.preferences); try out.writer.writeByte('}'); if (out.written().len > manifest_max_bytes) return error.ManifestTooLarge; try validateManifest(manifest); return try out.toOwnedSlice();} pub fn decodeManifest(alloc: Allocator, bytes: []const u8) !Manifest { if (bytes.len == 0 or bytes.len > manifest_max_bytes) return error.ManifestTooLarge; var parsed = std.json.parseFromSlice(std.json.Value, alloc, bytes, .{ .parse_numbers = false, }) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => return error.InvalidManifest, }; defer parsed.deinit(); const root = try exactObject(parsed.value, &.{ "schema_version", "storage_format", "id", "authority_id", "log_generation", "created_at_ms", "updated_at_ms", "origin_workspace_root", "workspace_root", "conversation_language", "history_len", "total_input_tokens", "total_output_tokens", "last_event_seq", "event_log_bytes", "event_log_stat_fingerprint", "generation_base_seq", "generation_base_bytes", "checkpoint_seq", "checkpoint_sha256", "preferences", }); if (try requireU64(root, "schema_version") != 3 or !std.mem.eql(u8, try requireString(root, "storage_format"), "event_log_v1")) { return error.InvalidManifest; } const id = try dupeString(alloc, root, "id"); errdefer alloc.free(id); const origin = try dupeString(alloc, root, "origin_workspace_root"); errdefer alloc.free(origin); const current = try dupeString(alloc, root, "workspace_root"); errdefer alloc.free(current); const preferences = try parsePreferences( alloc, root.get("preferences") orelse return error.InvalidManifest, ); errdefer { var owned = preferences; owned.deinit(alloc); } const manifest = Manifest{ .id = id, .authority_id = try parseIdentifier(try requireString(root, "authority_id")), .log_generation = try parseIdentifier(try requireString(root, "log_generation")), .created_at_ms = try requireI64(root, "created_at_ms"), .updated_at_ms = try requireI64(root, "updated_at_ms"), .origin_workspace_root = origin, .workspace_root = current, .conversation_language = session_codec.parseConversationLanguage( try requireString(root, "conversation_language"), ) catch return error.InvalidManifest, .history_len = try requireU64(root, "history_len"), .total_input_tokens = try requireU64(root, "total_input_tokens"), .total_output_tokens = try requireU64(root, "total_output_tokens"), .last_event_seq = try requireU64(root, "last_event_seq"), .event_log_bytes = try requireU64(root, "event_log_bytes"), .event_log_stat_fingerprint = try parseDigest( try requireString(root, "event_log_stat_fingerprint"), ), .generation_base_seq = try requireU64(root, "generation_base_seq"), .generation_base_bytes = try requireU64(root, "generation_base_bytes"), .checkpoint_seq = try optionalU64(root.get("checkpoint_seq") orelse return error.InvalidManifest), .checkpoint_sha256 = try optionalDigest(root.get("checkpoint_sha256") orelse return error.InvalidManifest), .preferences = preferences, }; try validateManifest(manifest); return manifest;} pub fn eventFileStatFingerprint(stat: EventFileStat, expected_size: u64) !Digest { if (stat.kind != .regular or stat.link_count != 1 or stat.size != expected_size) { return error.InvalidEventFileStat; } var encoded: [8 * 5 + 16 * 2 + 1]u8 = undefined; var offset: usize = 0; encoded[offset] = @intFromEnum(stat.kind); offset += 1; writeInt(&encoded, &offset, u64, stat.device); writeInt(&encoded, &offset, u64, stat.inode); writeInt(&encoded, &offset, u64, stat.mode); writeInt(&encoded, &offset, u64, stat.link_count); writeInt(&encoded, &offset, u64, stat.size); writeInt(&encoded, &offset, i128, stat.mtime_ns); writeInt(&encoded, &offset, i128, stat.ctime_ns); var hasher = Sha256.init(.{}); hasher.update("fx:event-file-stat:v1\x00"); hasher.update(encoded[0..offset]); return hasher.finalResult();} pub fn isManifestStale(manifest: Manifest, current: EventFileStat) bool { const current_fingerprint = eventFileStatFingerprint(current, manifest.event_log_bytes) catch return true; return !std.mem.eql( u8, &manifest.event_log_stat_fingerprint, ¤t_fingerprint, );} pub fn stateMatchesManifest( state: session_codec.DurableSessionState, manifest: Manifest,) bool { return std.mem.eql(u8, state.id, manifest.id) and std.mem.eql( u8, state.origin_workspace_root, manifest.origin_workspace_root, ) and std.mem.eql(u8, state.workspace_root, manifest.workspace_root) and state.created_at_ms == manifest.created_at_ms and state.updated_at_ms == manifest.updated_at_ms and std.mem.eql( u8, state.conversation_language.view(), manifest.conversation_language.view(), ) and state.history.len == manifest.history_len and state.total_input_tokens == manifest.total_input_tokens and state.total_output_tokens == manifest.total_output_tokens and durablePreferencesEqual(state.preferences, manifest.preferences);} fn durablePreferencesEqual( left: session_codec.DurableSessionPreferences, right: session_codec.DurableSessionPreferences,) bool { return std.mem.eql(u8, left.model, right.model) and left.effort.eql(right.effort) and left.fast_mode == right.fast_mode;} pub fn encodeCheckpoint(alloc: Allocator, checkpoint: Checkpoint) ![]u8 { try validateCheckpoint(checkpoint); var out: std.Io.Writer.Allocating = .init(alloc); defer out.deinit(); try out.writer.writeAll("{\"schema_version\":1,\"session_id\":"); try writeJsonString(&out.writer, checkpoint.session_id); try out.writer.writeAll(",\"log_generation\":"); try writeHexString(&out.writer, &checkpoint.log_generation); try out.writer.print(",\"through_seq\":{d},\"through_event_id\":", .{ checkpoint.through_seq, }); try writeHexString(&out.writer, &checkpoint.through_event_id); try out.writer.print(",\"through_event_log_bytes\":{d},\"state\":", .{ checkpoint.through_event_log_bytes, }); _ = try session_codec.encodeState(checkpoint.state, &out.writer); try out.writer.writeByte('}'); if (out.written().len > checkpoint_max_bytes) return error.CheckpointTooLarge; return try out.toOwnedSlice();} pub fn decodeCheckpoint(alloc: Allocator, bytes: []const u8) !Checkpoint { return decodeCheckpointImpl(alloc, bytes) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.CheckpointTooLarge => return error.CheckpointTooLarge, else => return error.InvalidCheckpoint, };} fn decodeCheckpointImpl(alloc: Allocator, bytes: []const u8) !Checkpoint { if (bytes.len == 0 or bytes.len > checkpoint_max_bytes) return error.CheckpointTooLarge; var parsed = std.json.parseFromSlice(std.json.Value, alloc, bytes, .{ .parse_numbers = false, }) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => return error.InvalidCheckpoint, }; defer parsed.deinit(); const root = try exactObject(parsed.value, &.{ "schema_version", "session_id", "log_generation", "through_seq", "through_event_id", "through_event_log_bytes", "state", }); if (try requireU64(root, "schema_version") != 1) return error.InvalidCheckpoint; var state_json: std.Io.Writer.Allocating = .init(alloc); defer state_json.deinit(); try std.json.Stringify.value( root.get("state") orelse return error.InvalidCheckpoint, .{}, &state_json.writer, ); var state_source = std.Io.Reader.fixed(state_json.written()); var state = session_codec.decodeState(alloc, &state_source, .{}) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => return error.InvalidCheckpoint, }; errdefer state.deinit(alloc); const session_id = try dupeString(alloc, root, "session_id"); errdefer alloc.free(session_id); const checkpoint = Checkpoint{ .session_id = session_id, .log_generation = try parseIdentifier(try requireString(root, "log_generation")), .through_seq = try requireU64(root, "through_seq"), .through_event_id = try parseIdentifier(try requireString(root, "through_event_id")), .through_event_log_bytes = try requireU64(root, "through_event_log_bytes"), .state = state, }; try validateCheckpoint(checkpoint); return checkpoint;} pub fn validateCheckpointReference( manifest: Manifest, checkpoint_bytes: []const u8, checkpoint: Checkpoint, watermark: EventBoundary, checkpoint_boundary: EventBoundary,) !void { const expected_hash = manifest.checkpoint_sha256 orelse return error.StaleCheckpoint; if (!std.mem.eql(u8, &sha256(checkpoint_bytes), &expected_hash)) { return error.CheckpointHashMismatch; } if (manifest.checkpoint_seq == null or manifest.checkpoint_seq.? != checkpoint.through_seq or !std.mem.eql(u8, checkpoint.session_id, manifest.id) or !std.mem.eql(u8, &checkpoint.log_generation, &manifest.log_generation) or !std.mem.eql(u8, &watermark.log_generation, &manifest.log_generation) or !std.mem.eql(u8, &checkpoint_boundary.log_generation, &manifest.log_generation)) { return error.StaleCheckpoint; } if (!checkpoint_boundary.semantic or checkpoint_boundary.seq != checkpoint.through_seq or checkpoint_boundary.event_log_bytes != checkpoint.through_event_log_bytes or !std.mem.eql(u8, &checkpoint_boundary.event_id, &checkpoint.through_event_id)) { return error.InvalidCheckpointBoundary; } if (!watermark.semantic or watermark.seq != manifest.last_event_seq or watermark.event_log_bytes != manifest.event_log_bytes or checkpoint.through_seq > watermark.seq or checkpoint.through_event_log_bytes > watermark.event_log_bytes) { return error.StaleCheckpoint; } if (!std.mem.eql(u8, checkpoint.state.id, manifest.id)) { return error.StaleCheckpoint; }} pub fn sha256(bytes: []const u8) Digest { var digest: Digest = undefined; Sha256.hash(bytes, &digest, .{}); return digest;} fn validateManifest(manifest: Manifest) !void { const state = session_codec.DurableSessionState{ .id = manifest.id, .origin_workspace_root = manifest.origin_workspace_root, .workspace_root = manifest.workspace_root, .created_at_ms = manifest.created_at_ms, .updated_at_ms = manifest.updated_at_ms, .conversation_language = manifest.conversation_language, .preferences = manifest.preferences, .history = &.{}, .total_input_tokens = manifest.total_input_tokens, .total_output_tokens = manifest.total_output_tokens, }; session_codec.validateState(state) catch return error.InvalidManifest; if (manifest.last_event_seq == 0 or manifest.event_log_bytes == 0 or manifest.generation_base_seq == 0 or manifest.generation_base_seq > manifest.last_event_seq or manifest.generation_base_bytes == 0 or manifest.generation_base_bytes > manifest.event_log_bytes or ((manifest.checkpoint_seq == null) != (manifest.checkpoint_sha256 == null)) or (manifest.checkpoint_seq != null and (manifest.checkpoint_seq.? == 0 or manifest.checkpoint_seq.? > manifest.last_event_seq))) { return error.InvalidManifest; }} fn validateCheckpoint(checkpoint: Checkpoint) !void { session_codec.validateState(checkpoint.state) catch return error.InvalidCheckpoint; if (checkpoint.through_seq == 0 or checkpoint.through_event_log_bytes == 0 or !std.mem.eql(u8, checkpoint.session_id, checkpoint.state.id)) { return error.InvalidCheckpoint; }} fn writePreferences( writer: *std.Io.Writer, preferences: session_codec.DurableSessionPreferences,) !void { try writer.writeAll("{\"model\":"); try writeJsonString(writer, preferences.model); try writer.writeAll(",\"effort\":"); try writeJsonString(writer, preferences.effort.label()); try writer.print(",\"fast_mode\":{s}}}", .{ if (preferences.fast_mode) "true" else "false", });} fn parsePreferences(alloc: Allocator, value: std.json.Value) !session_codec.DurableSessionPreferences { const object = try exactObject(value, &.{ "model", "effort", "fast_mode" }); const model = try dupeString(alloc, object, "model"); errdefer alloc.free(model); return .{ .model = model, .effort = types.ReasoningEffort.parse(try requireString(object, "effort")) orelse return error.InvalidManifest, .fast_mode = try requireBool(object, "fast_mode"), };} fn exactObject(value: std.json.Value, keys: []const []const u8) !std.json.ObjectMap { if (value != .object or value.object.count() != keys.len) return error.InvalidManifest; var iterator = value.object.iterator(); while (iterator.next()) |entry| { var known = false; for (keys) |key| { if (std.mem.eql(u8, entry.key_ptr.*, key)) { known = true; break; } } if (!known) return error.InvalidManifest; } return value.object;} fn requireString(object: std.json.ObjectMap, key: []const u8) ![]const u8 { const value = object.get(key) orelse return error.InvalidManifest; if (value != .string) return error.InvalidManifest; return value.string;} fn dupeString(alloc: Allocator, object: std.json.ObjectMap, key: []const u8) ![]u8 { return try alloc.dupe(u8, try requireString(object, key));}Lines 1–500 of 839. Load more
const std = @import("std"); const session = @import("session.zig"); const session_codec = @import("session_codec.zig"); const session_event = @import("session_event.zig"); const types = @import("../shared/types.zig"); const Allocator = std.mem.Allocator; const Sha256 = std.crypto.hash.sha2.Sha256; pub const manifest_max_bytes: usize = 64 * 1024; pub const checkpoint_max_bytes: usize = 32 * 1024 * 1024; pub const Digest = [Sha256.digest_length]u8; pub const Manifest = struct { id: []u8, authority_id: session_event.Identifier, log_generation: session_event.Identifier, created_at_ms: i64, updated_at_ms: i64, origin_workspace_root: []u8, workspace_root: []u8, conversation_language: session.ConversationLanguage, history_len: u64, total_input_tokens: u64, total_output_tokens: u64, last_event_seq: u64, event_log_bytes: u64, event_log_stat_fingerprint: Digest, generation_base_seq: u64, generation_base_bytes: u64, checkpoint_seq: ?u64, checkpoint_sha256: ?Digest, preferences: session_codec.DurableSessionPreferences, pub fn deinit(self: *Manifest, alloc: Allocator) void { alloc.free(self.id); alloc.free(self.origin_workspace_root); alloc.free(self.workspace_root); self.preferences.deinit(alloc); self.* = undefined; } }; pub const EventFileKind = enum(u8) { regular = 1, directory = 2, symbolic_link = 3, other = 255, }; pub const EventFileStat = struct { device: u64, inode: u64, kind: EventFileKind, mode: u64, link_count: u64, size: u64, mtime_ns: i128, ctime_ns: i128, }; pub const Checkpoint = struct { session_id: []u8, log_generation: session_event.Identifier, through_seq: u64, through_event_id: session_event.Identifier, through_event_log_bytes: u64, state: session_codec.DurableSessionState, pub fn deinit(self: *Checkpoint, alloc: Allocator) void { alloc.free(self.session_id); self.state.deinit(alloc); self.* = undefined; } }; pub const EventBoundary = struct { log_generation: session_event.Identifier, seq: u64, event_id: session_event.Identifier, event_log_bytes: u64, semantic: bool, }; pub fn encodeManifest(alloc: Allocator, manifest: Manifest) ![]u8 { var out: std.Io.Writer.Allocating = .init(alloc); defer out.deinit(); try out.writer.writeAll( "{\"schema_version\":3,\"storage_format\":\"event_log_v1\",\"id\":", ); try writeJsonString(&out.writer, manifest.id); try out.writer.writeAll(",\"authority_id\":"); try writeHexString(&out.writer, &manifest.authority_id); try out.writer.writeAll(",\"log_generation\":"); try writeHexString(&out.writer, &manifest.log_generation); try out.writer.print(",\"created_at_ms\":{d},\"updated_at_ms\":{d}", .{ manifest.created_at_ms, manifest.updated_at_ms, }); try out.writer.writeAll(",\"origin_workspace_root\":"); try writeJsonString(&out.writer, manifest.origin_workspace_root); try out.writer.writeAll(",\"workspace_root\":"); try writeJsonString(&out.writer, manifest.workspace_root); try out.writer.writeAll(",\"conversation_language\":"); try writeJsonString(&out.writer, manifest.conversation_language.view()); try out.writer.print( ",\"history_len\":{d},\"total_input_tokens\":{d},\"total_output_tokens\":{d},\"last_event_seq\":{d},\"event_log_bytes\":{d},\"event_log_stat_fingerprint\":", .{ manifest.history_len, manifest.total_input_tokens, manifest.total_output_tokens, manifest.last_event_seq, manifest.event_log_bytes, }, ); try writeHexString(&out.writer, &manifest.event_log_stat_fingerprint); try out.writer.print(",\"generation_base_seq\":{d},\"generation_base_bytes\":{d},\"checkpoint_seq\":", .{ manifest.generation_base_seq, manifest.generation_base_bytes, }); if (manifest.checkpoint_seq) |seq| { try out.writer.print("{d}", .{seq}); } else { try out.writer.writeAll("null"); } try out.writer.writeAll(",\"checkpoint_sha256\":"); if (manifest.checkpoint_sha256) |digest| { try writeHexString(&out.writer, &digest); } else { try out.writer.writeAll("null"); } try out.writer.writeAll(",\"preferences\":"); try writePreferences(&out.writer, manifest.preferences); try out.writer.writeByte('}'); if (out.written().len > manifest_max_bytes) return error.ManifestTooLarge; try validateManifest(manifest); return try out.toOwnedSlice(); } pub fn decodeManifest(alloc: Allocator, bytes: []const u8) !Manifest { if (bytes.len == 0 or bytes.len > manifest_max_bytes) return error.ManifestTooLarge; var parsed = std.json.parseFromSlice(std.json.Value, alloc, bytes, .{ .parse_numbers = false, }) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => return error.InvalidManifest, }; defer parsed.deinit(); const root = try exactObject(parsed.value, &.{ "schema_version", "storage_format", "id", "authority_id", "log_generation", "created_at_ms", "updated_at_ms", "origin_workspace_root", "workspace_root", "conversation_language", "history_len", "total_input_tokens", "total_output_tokens", "last_event_seq", "event_log_bytes", "event_log_stat_fingerprint", "generation_base_seq", "generation_base_bytes", "checkpoint_seq", "checkpoint_sha256", "preferences", }); if (try requireU64(root, "schema_version") != 3 or !std.mem.eql(u8, try requireString(root, "storage_format"), "event_log_v1")) { return error.InvalidManifest; } const id = try dupeString(alloc, root, "id"); errdefer alloc.free(id); const origin = try dupeString(alloc, root, "origin_workspace_root"); errdefer alloc.free(origin); const current = try dupeString(alloc, root, "workspace_root"); errdefer alloc.free(current); const preferences = try parsePreferences( alloc, root.get("preferences") orelse return error.InvalidManifest, ); errdefer { var owned = preferences; owned.deinit(alloc); } const manifest = Manifest{ .id = id, .authority_id = try parseIdentifier(try requireString(root, "authority_id")), .log_generation = try parseIdentifier(try requireString(root, "log_generation")), .created_at_ms = try requireI64(root, "created_at_ms"), .updated_at_ms = try requireI64(root, "updated_at_ms"), .origin_workspace_root = origin, .workspace_root = current, .conversation_language = session_codec.parseConversationLanguage( try requireString(root, "conversation_language"), ) catch return error.InvalidManifest, .history_len = try requireU64(root, "history_len"), .total_input_tokens = try requireU64(root, "total_input_tokens"), .total_output_tokens = try requireU64(root, "total_output_tokens"), .last_event_seq = try requireU64(root, "last_event_seq"), .event_log_bytes = try requireU64(root, "event_log_bytes"), .event_log_stat_fingerprint = try parseDigest( try requireString(root, "event_log_stat_fingerprint"), ), .generation_base_seq = try requireU64(root, "generation_base_seq"), .generation_base_bytes = try requireU64(root, "generation_base_bytes"), .checkpoint_seq = try optionalU64(root.get("checkpoint_seq") orelse return error.InvalidManifest), .checkpoint_sha256 = try optionalDigest(root.get("checkpoint_sha256") orelse return error.InvalidManifest), .preferences = preferences, }; try validateManifest(manifest); return manifest; } pub fn eventFileStatFingerprint(stat: EventFileStat, expected_size: u64) !Digest { if (stat.kind != .regular or stat.link_count != 1 or stat.size != expected_size) { return error.InvalidEventFileStat; } var encoded: [8 * 5 + 16 * 2 + 1]u8 = undefined; var offset: usize = 0; encoded[offset] = @intFromEnum(stat.kind); offset += 1; writeInt(&encoded, &offset, u64, stat.device); writeInt(&encoded, &offset, u64, stat.inode); writeInt(&encoded, &offset, u64, stat.mode); writeInt(&encoded, &offset, u64, stat.link_count); writeInt(&encoded, &offset, u64, stat.size); writeInt(&encoded, &offset, i128, stat.mtime_ns); writeInt(&encoded, &offset, i128, stat.ctime_ns); var hasher = Sha256.init(.{}); hasher.update("fx:event-file-stat:v1\x00"); hasher.update(encoded[0..offset]); return hasher.finalResult(); } pub fn isManifestStale(manifest: Manifest, current: EventFileStat) bool { const current_fingerprint = eventFileStatFingerprint(current, manifest.event_log_bytes) catch return true; return !std.mem.eql( u8, &manifest.event_log_stat_fingerprint, ¤t_fingerprint, ); } pub fn stateMatchesManifest( state: session_codec.DurableSessionState, manifest: Manifest, ) bool { return std.mem.eql(u8, state.id, manifest.id) and std.mem.eql( u8, state.origin_workspace_root, manifest.origin_workspace_root, ) and std.mem.eql(u8, state.workspace_root, manifest.workspace_root) and state.created_at_ms == manifest.created_at_ms and state.updated_at_ms == manifest.updated_at_ms and std.mem.eql( u8, state.conversation_language.view(), manifest.conversation_language.view(), ) and state.history.len == manifest.history_len and state.total_input_tokens == manifest.total_input_tokens and state.total_output_tokens == manifest.total_output_tokens and durablePreferencesEqual(state.preferences, manifest.preferences); } fn durablePreferencesEqual( left: session_codec.DurableSessionPreferences, right: session_codec.DurableSessionPreferences, ) bool { return std.mem.eql(u8, left.model, right.model) and left.effort.eql(right.effort) and left.fast_mode == right.fast_mode; } pub fn encodeCheckpoint(alloc: Allocator, checkpoint: Checkpoint) ![]u8 { try validateCheckpoint(checkpoint); var out: std.Io.Writer.Allocating = .init(alloc); defer out.deinit(); try out.writer.writeAll("{\"schema_version\":1,\"session_id\":"); try writeJsonString(&out.writer, checkpoint.session_id); try out.writer.writeAll(",\"log_generation\":"); try writeHexString(&out.writer, &checkpoint.log_generation); try out.writer.print(",\"through_seq\":{d},\"through_event_id\":", .{ checkpoint.through_seq, }); try writeHexString(&out.writer, &checkpoint.through_event_id); try out.writer.print(",\"through_event_log_bytes\":{d},\"state\":", .{ checkpoint.through_event_log_bytes, }); _ = try session_codec.encodeState(checkpoint.state, &out.writer); try out.writer.writeByte('}'); if (out.written().len > checkpoint_max_bytes) return error.CheckpointTooLarge; return try out.toOwnedSlice(); } pub fn decodeCheckpoint(alloc: Allocator, bytes: []const u8) !Checkpoint { return decodeCheckpointImpl(alloc, bytes) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.CheckpointTooLarge => return error.CheckpointTooLarge, else => return error.InvalidCheckpoint, }; } fn decodeCheckpointImpl(alloc: Allocator, bytes: []const u8) !Checkpoint { if (bytes.len == 0 or bytes.len > checkpoint_max_bytes) return error.CheckpointTooLarge; var parsed = std.json.parseFromSlice(std.json.Value, alloc, bytes, .{ .parse_numbers = false, }) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => return error.InvalidCheckpoint, }; defer parsed.deinit(); const root = try exactObject(parsed.value, &.{ "schema_version", "session_id", "log_generation", "through_seq", "through_event_id", "through_event_log_bytes", "state", }); if (try requireU64(root, "schema_version") != 1) return error.InvalidCheckpoint; var state_json: std.Io.Writer.Allocating = .init(alloc); defer state_json.deinit(); try std.json.Stringify.value( root.get("state") orelse return error.InvalidCheckpoint, .{}, &state_json.writer, ); var state_source = std.Io.Reader.fixed(state_json.written()); var state = session_codec.decodeState(alloc, &state_source, .{}) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => return error.InvalidCheckpoint, }; errdefer state.deinit(alloc); const session_id = try dupeString(alloc, root, "session_id"); errdefer alloc.free(session_id); const checkpoint = Checkpoint{ .session_id = session_id, .log_generation = try parseIdentifier(try requireString(root, "log_generation")), .through_seq = try requireU64(root, "through_seq"), .through_event_id = try parseIdentifier(try requireString(root, "through_event_id")), .through_event_log_bytes = try requireU64(root, "through_event_log_bytes"), .state = state, }; try validateCheckpoint(checkpoint); return checkpoint; } pub fn validateCheckpointReference( manifest: Manifest, checkpoint_bytes: []const u8, checkpoint: Checkpoint, watermark: EventBoundary, checkpoint_boundary: EventBoundary, ) !void { const expected_hash = manifest.checkpoint_sha256 orelse return error.StaleCheckpoint; if (!std.mem.eql(u8, &sha256(checkpoint_bytes), &expected_hash)) { return error.CheckpointHashMismatch; } if (manifest.checkpoint_seq == null or manifest.checkpoint_seq.? != checkpoint.through_seq or !std.mem.eql(u8, checkpoint.session_id, manifest.id) or !std.mem.eql(u8, &checkpoint.log_generation, &manifest.log_generation) or !std.mem.eql(u8, &watermark.log_generation, &manifest.log_generation) or !std.mem.eql(u8, &checkpoint_boundary.log_generation, &manifest.log_generation)) { return error.StaleCheckpoint; } if (!checkpoint_boundary.semantic or checkpoint_boundary.seq != checkpoint.through_seq or checkpoint_boundary.event_log_bytes != checkpoint.through_event_log_bytes or !std.mem.eql(u8, &checkpoint_boundary.event_id, &checkpoint.through_event_id)) { return error.InvalidCheckpointBoundary; } if (!watermark.semantic or watermark.seq != manifest.last_event_seq or watermark.event_log_bytes != manifest.event_log_bytes or checkpoint.through_seq > watermark.seq or checkpoint.through_event_log_bytes > watermark.event_log_bytes) { return error.StaleCheckpoint; } if (!std.mem.eql(u8, checkpoint.state.id, manifest.id)) { return error.StaleCheckpoint; } } pub fn sha256(bytes: []const u8) Digest { var digest: Digest = undefined; Sha256.hash(bytes, &digest, .{}); return digest; } fn validateManifest(manifest: Manifest) !void { const state = session_codec.DurableSessionState{ .id = manifest.id, .origin_workspace_root = manifest.origin_workspace_root, .workspace_root = manifest.workspace_root, .created_at_ms = manifest.created_at_ms, .updated_at_ms = manifest.updated_at_ms, .conversation_language = manifest.conversation_language, .preferences = manifest.preferences, .history = &.{}, .total_input_tokens = manifest.total_input_tokens, .total_output_tokens = manifest.total_output_tokens, }; session_codec.validateState(state) catch return error.InvalidManifest; if (manifest.last_event_seq == 0 or manifest.event_log_bytes == 0 or manifest.generation_base_seq == 0 or manifest.generation_base_seq > manifest.last_event_seq or manifest.generation_base_bytes == 0 or manifest.generation_base_bytes > manifest.event_log_bytes or ((manifest.checkpoint_seq == null) != (manifest.checkpoint_sha256 == null)) or (manifest.checkpoint_seq != null and (manifest.checkpoint_seq.? == 0 or manifest.checkpoint_seq.? > manifest.last_event_seq))) { return error.InvalidManifest; } } fn validateCheckpoint(checkpoint: Checkpoint) !void { session_codec.validateState(checkpoint.state) catch return error.InvalidCheckpoint; if (checkpoint.through_seq == 0 or checkpoint.through_event_log_bytes == 0 or !std.mem.eql(u8, checkpoint.session_id, checkpoint.state.id)) { return error.InvalidCheckpoint; } } fn writePreferences( writer: *std.Io.Writer, preferences: session_codec.DurableSessionPreferences, ) !void { try writer.writeAll("{\"model\":"); try writeJsonString(writer, preferences.model); try writer.writeAll(",\"effort\":"); try writeJsonString(writer, preferences.effort.label()); try writer.print(",\"fast_mode\":{s}}}", .{ if (preferences.fast_mode) "true" else "false", }); } fn parsePreferences(alloc: Allocator, value: std.json.Value) !session_codec.DurableSessionPreferences { const object = try exactObject(value, &.{ "model", "effort", "fast_mode" }); const model = try dupeString(alloc, object, "model"); errdefer alloc.free(model); return .{ .model = model, .effort = types.ReasoningEffort.parse(try requireString(object, "effort")) orelse return error.InvalidManifest, .fast_mode = try requireBool(object, "fast_mode"), }; } fn exactObject(value: std.json.Value, keys: []const []const u8) !std.json.ObjectMap { if (value != .object or value.object.count() != keys.len) return error.InvalidManifest; var iterator = value.object.iterator(); while (iterator.next()) |entry| { var known = false; for (keys) |key| { if (std.mem.eql(u8, entry.key_ptr.*, key)) { known = true; break; } } if (!known) return error.InvalidManifest; } return value.object; } fn requireString(object: std.json.ObjectMap, key: []const u8) ![]const u8 { const value = object.get(key) orelse return error.InvalidManifest; if (value != .string) return error.InvalidManifest; return value.string; } fn dupeString(alloc: Allocator, object: std.json.ObjectMap, key: []const u8) ![]u8 { return try alloc.dupe(u8, try requireString(object, key)); }