diff --git a/src/internal/ingest/ingest.zig b/src/internal/ingest/ingest.zig index e9ee978..c5a532d 100644 --- a/src/internal/ingest/ingest.zig +++ b/src/internal/ingest/ingest.zig @@ -959,11 +959,9 @@ test "repair hot tail skips an undecodable archived record without failing inges defer consumer.deinit(); // A complete repository can contain opaque record bytes outside - // DAG-CBOR. A float decoder error killed the experiment while a verifier - // repair was filling the hot tail from its fetched repository. zat 0.4.2 - // accepts finite 64-bit floats, so this uses a 32-bit float, which - // DAG-CBOR still forbids. - const float_record = "\xa1\x61x\xfa\x3f\x80\x00\x00"; + // DAG-CBOR. This exact float decoder error killed the experiment while a + // verifier repair was filling the hot tail from its fetched repository. + const float_record = "\xa1\x61x\xfb\x3f\xf0\x00\x00\x00\x00\x00\x00"; try appendArchivedTailRow(&consumer, .{ .seq = 42, .witnessed_at = 100, diff --git a/src/internal/serve/wire.zig b/src/internal/serve/wire.zig index 6b94341..0842ee7 100644 --- a/src/internal/serve/wire.zig +++ b/src/internal/serve/wire.zig @@ -194,7 +194,9 @@ fn writeCborValue(s: *std.json.Stringify, value: cbor.Value) !void { switch (value) { .unsigned => |v| try s.write(v), .negative => |v| try s.write(v), - .float => |v| try s.write(v), + // upstream's cbor.ToJSON rejects floats as outside the atproto data + // model (atmos cbor/json.go), so the record is unencodable, not emitted + .float => return error.WriteFailed, // record text can be any bytes a PDS chose to write; the same // array-instead-of-string hazard applies to it as to the envelope .text => |v| try writeString(s, v), @@ -356,6 +358,29 @@ test "record with cid link and bytes" { try testing.expect(std.mem.indexOf(u8, json, "\"blob\":{\"$bytes\":\"AQID\"}") != null); } +// Mirrors upstream: atmos decodes a finite float64 but cbor.ToJSON rejects it +// as outside the atproto data model, so the subscribe encoder errors instead +// of emitting a JSON number. +test "record with a float is unencodable" { + var out: std.Io.Writer.Allocating = .init(testing.allocator); + defer out.deinit(); + const cid = try cbor.Cid.forDagCbor(testing.allocator, "x"); + defer testing.allocator.free(cid.raw); + var arena = std.heap.ArenaAllocator.init(testing.allocator); + defer arena.deinit(); + const record = try cbor.decodeAll(arena.allocator(), "\xa1\x61x\xfb\x3f\xf0\x00\x00\x00\x00\x00\x00"); + try testing.expectError(error.WriteFailed, encodeCommit(testing.allocator, &out.writer, .{ + .did = "did:plc:abc", + .time_us = 1, + .rev = "3l3qo2vutsw2b", + .operation = "create", + .collection = "app.bsky.feed.post", + .rkey = "k", + .record = record, + .cid = cid, + })); +} + // === network.bsky.jetstream.subscribeEvents: proposal-0015 wire === // One xrpc.v1.json message frame per event: // {"$type":"message","payload":{"$type":"network.bsky.jetstream.subscribeEvents#", ...}}