Skip to content

v2.4.0: Graph delta decode + verify; session + conformance hardening

Choose a tag to compare

@blackwell-systems blackwell-systems released this 13 Jul 05:25
· 32 commits to main since this release

Fixes

  • The conformance runner now hard-fails on any unhandled operation (instead of silently skipping it) and exercises session, delta, roundtrip, and pack-root fixtures end to end; the graph delta wire decode and verify path is now covered, so no operations remain allow-listed.
  • Implemented the graph delta wire decoder and verifier (decode_delta / verify_delta): parse a GCF profile=graph delta=true wire back into removed/added symbols and edge changes, apply them atomically to a base snapshot, recompute pack_root, and reject a wrong new_root with root_mismatch (SPEC 10.4). The ## added encoder now emits the trailing distance field (SPEC 3.4.1, Section 10.1). The shared graph-delta fixtures now run end to end: 001 (encode, gains the trailing distance), 002 (verified apply), 003 (root_mismatch rejection).
  • Session encoding correctness fix. encode_with_session assigned per-response local IDs instead of stable session-global IDs, so the cross-call dedup references (@N # previously transmitted) pointed at the wrong symbols, and the header emitted zero-valued budget/tokens/edges. Both are fixed to match the reference; graph session output is now byte-identical across all six SDKs. This had gone undetected because the conformance runner skipped the shared graph-session fixtures (now wired).
  • Added the graph-profile PackRoot (pack_root(symbols, edges), gcf-pack-root-v1, SPEC 10.2): the content-addressed sha256 over canonical, independently-sorted symbol/edge records, byte-identical to gcf-go/rust/typescript/swift/kotlin. The conformance runner now exercises the shared graph-pack-root fixtures, which it had been skipping (so this primitive was previously unimplemented and untested).
  • Buffered graph encoder now matches the reference byte-for-byte: symbols are ordered by distance then descending score with local IDs assigned in output order, and the header omits budget/tokens/edges when zero (previously symbols kept input order and zero-valued header fields were always emitted). The conformance runner now exercises the shared graph-encode fixtures (001-003), which it had been skipping - which is how this divergence went uncaught.
  • Buffered graph encoder: order edges by source ID, then target ID, then edge type (SPEC 16.1), instead of emitting them in input order. Decode-invariant (edges are a set) and does not affect pack_root (which sorts edge records independently), so no content addresses change. Pinned by shared fixture graph-encode/003. Streaming edges remain in producer-arrival order.
  • Decoder: reject an orphan .field attachment (a .field whose name is neither a ^-marked column of its row nor a >-containing field name, SPEC 7.4.6.1.4) instead of silently absorbing it as an undeclared extra field. Such a stray attachment previously decoded to a record no encoder produces, silently injecting a field onto the last-parsed row (a lossless round-trip hole); now rejected per SPEC 16.5 (orphan_attachment).
  • Decoder: reject an orphan positional inline body (a pipe-delimited line with no eligible ^{} attachment-marker cell) instead of silently dropping it. The object-body parser previously skipped any unrecognized line, so a stray positional body (e.g. a second Bob|b@t.com after a row's one inline cell was filled) vanished with no error (silent data loss); now rejected per SPEC 16.5 (orphan_inline_attachment).
  • Graph streaming trailer: the edge count is now always the last counts entry, even when the stream has no edges (positional counts=2,1,0; labeled counts=…,edges:0). A zero-edge stream previously dropped it, violating the SPEC §8.4 / §8.4.1 rule that the edge count is always present and last (the invariant that keeps the positional form unambiguous). The graph trailer is decoder-ignored, so this changes producer output only.

Streaming: opt-in labeled trailer counts (SPEC §8.4.1)

  • New labeled_trailer_counts keyword on StreamEncoder. When set, the ##! summary graph streaming trailer emits counts= in the labeled form label:count per group (e.g. counts=targets:2,related:1,edges:3) instead of the default positional values-only form (counts=2,1,3). Default false is byte-identical to prior output.
  • Opt-in and non-breaking: a producer-side comprehension aid for known weak consumers. The trailer counts remain informational (decoder-ignored) in both forms; neither changes the decoded payload. Mirrors the gcf-go reference.

Conformance and docs

  • Streaming graph trailer now emits distance_N group counts in pure group-header emission order (dropping a fixed targets,related,extended prefix), matching the other SDKs and deterministic per SPEC 16.1. Byte-identical for contract-conformant (ascending-distance) input; pinned by shared fixtures streaming-v2/010011.
  • The conformance runner now executes the graph-stream-encode fixtures (streaming-encode parity, previously decode-only): fixture 004 (positional trailer) and 005 (labeled trailer).
  • README: corrected the streaming example trailer from the defunct ## _summary … sections= to the real ##! summary … counts=; README now leads with the project diagram.
  • Added a generic-delta fuzz test (decoder never crashes; string round-trip).

Full Changelog: v2.3.0...v2.4.0