Skip to content

fix(graph): model the edge read routes' actual hydrated shape - #22

Merged
rrader26 merged 1 commit into
mainfrom
fix/graph-edge-hydrated-shape
Aug 19, 2026
Merged

fix(graph): model the edge read routes' actual hydrated shape#22
rrader26 merged 1 commit into
mainfrom
fix/graph-edge-hydrated-shape

Conversation

@rrader26

Copy link
Copy Markdown
Contributor

Follow-up to #21. The squash-merge of that PR did not include this fix, so
main currently ships a MemoryEdge type that does not match anything the API
returns.

The bug

listEdges(), traverse(), and getEntity().edges do not return the raw
memory_edge row. They return the server's GraphTraversalEdge:

{
  "id": "g1",
  "subject": { "id": "e1", "canonicalName": "NVIDIA CORP", ... },
  "predicate": "reported_metric",
  "object":  { "id": "e2", "canonicalName": "Cost of Revenue", ... },
  "objectLiteral": null,
  "weight": 0.85,
  "hop": 0
}

subject and object are fully hydrated entities, not ids. There is no
subjectId, objectId, or brainId on the wire at all — which is exactly what
the shipped MemoryEdge declares.

How it was found

By running the Rust port of the same surface against production, where strict
decoding failed outright:

Error: Decode(Error("missing field `subjectId`", line: 1, column: 1066))

TypeScript's structural typing hid it completely — the call compiles, the
request succeeds, and you get back objects whose every declared field is
undefined at runtime. Anyone reading edge.subjectId gets undefined and no
error.

The change

Replaces MemoryEdge with GraphTraversalEdge, matching the server type name.
The raw row shape is dropped rather than kept alongside: no read route returns
it, and a type nothing returns is a trap.

hop is documented for what it actually does — 0 from listEdges (no seed),
1-indexed from traverse.

Verification

Typecheck and build clean, and verified against app.memmesh.ai — the corrected
type decodes real edges:

hop=0 NVIDIA CORP -[reported_metric]-> Cost of Revenue (w=0.85)
hop=0 NVIDIA CORP -[ticker_symbol]-> NVDA (w=0.85)
hop=0 Cost of Revenue -[disclosed_in]-> 10-K (w=0.85)

The Python (#5), Rust (#5), .NET (#5), and Go (#5) PRs all carry the corrected
shape already, so merging this brings TypeScript back in line with the other
four.

`listEdges`, `traverse`, and `getEntity().edges` do not return the raw
`memory_edge` row. They return the server's `GraphTraversalEdge`: `subject`
and `object` are fully hydrated entities rather than ids, plus a `hop`
counter — and there is no `subjectId`, `objectId`, or `brainId` on the wire
at all.

Caught by running the Rust port against production, where strict decoding
failed with `missing field 'subjectId'`. TypeScript's structural typing hid
it — the call compiled and then handed back objects whose declared fields
were all undefined at runtime.

Drops the raw-row `MemoryEdge` type rather than keeping it alongside. No
read route returns that shape, and a type nothing returns is a trap.

`hop` is 0 from `listEdges` (no seed) and 1-indexed from `traverse`.

Verified live: NVIDIA CORP -[reported_metric]-> Cost of Revenue, decoded
through the corrected type.
@rrader26
rrader26 merged commit 4ba805d into main Aug 19, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants