Skip to content

Commit 9cbc625

Browse files
fix: order graph edges by source then target in buffered encoder (SPEC 16.1)
The buffered graph encoder emitted edges in input order; SPEC 16.1 requires ordering by source ID then target ID. Edges are now sorted by (source ID, target ID, edge type) before emitting. Decode-invariant (edges are a set) and does not affect pack_root (which sorts edge records independently), so no content addresses change. Streaming edges remain in producer-arrival order. Pinned by shared fixture graph-encode/003.
1 parent e6b1594 commit 9cbc625

2 files changed

Lines changed: 23 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
### Fixes
66

7+
- 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.
78
- 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`).
89
- 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`).
910
- 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.

src/encode.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,15 +81,34 @@ export function encode(p: Payload): string {
8181
}
8282
}
8383

84-
// Edges section.
84+
// Edges section. Order edges by source ID then target ID (then edge type for
85+
// parallel edges) so the wire is canonical regardless of the order edges were
86+
// provided (SPEC 16.1). Edge reordering is decode-invariant (edges are a set)
87+
// and does not affect pack_root, which sorts edge records independently.
8588
if (p.edges.length > 0) {
86-
lines.push(`## edges [${validEdges}]`);
89+
interface ResolvedEdge {
90+
srcIdx: number;
91+
tgtIdx: number;
92+
edgeType: string;
93+
status?: string;
94+
}
95+
const resolved: ResolvedEdge[] = [];
8796
for (const e of p.edges) {
8897
const srcIdx = symIndex.get(e.source);
8998
const tgtIdx = symIndex.get(e.target);
9099
if (srcIdx === undefined || tgtIdx === undefined) continue;
100+
resolved.push({ srcIdx, tgtIdx, edgeType: e.edgeType, status: e.status });
101+
}
102+
// Stable sort (Array.prototype.sort is stable in modern Node).
103+
resolved.sort((a, b) => {
104+
if (a.srcIdx !== b.srcIdx) return a.srcIdx - b.srcIdx;
105+
if (a.tgtIdx !== b.tgtIdx) return a.tgtIdx - b.tgtIdx;
106+
return a.edgeType < b.edgeType ? -1 : a.edgeType > b.edgeType ? 1 : 0;
107+
});
91108

92-
let line = `@${tgtIdx}<@${srcIdx} ${e.edgeType}`;
109+
lines.push(`## edges [${validEdges}]`);
110+
for (const e of resolved) {
111+
let line = `@${e.tgtIdx}<@${e.srcIdx} ${e.edgeType}`;
93112
if (e.status && e.status !== 'unchanged') {
94113
line += ` ${e.status}`;
95114
}

0 commit comments

Comments
 (0)