PranitInitial commit439f83c3 months agoHistory
839 lines · 30.6 KB · Zig
RawDownload
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,        &current_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.