Skip to content

serve the configured graphs and their inventory, read-only (ADR-0029) - #130

Merged
kikashy merged 5 commits into
mainfrom
graph-serve
Aug 24, 2026
Merged

serve the configured graphs and their inventory, read-only (ADR-0029)#130
kikashy merged 5 commits into
mainfrom
graph-serve

Conversation

@kikashy

@kikashy kikashy commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Closes #126.

What

ADR-0029: experimental_list_graphs + experimental_get_graph { graph_id } — the graph siblings of list_packs/get_pack — plus the CLI twin experimental graph list, all served from one inventory function so the surfaces cannot disagree.

Design points, each argued in the ADR:

  • Separate payload types (GraphInventory/GraphDocument) — folding into PackInventory would put a removable experimental member inside a stable command's payload, the coupling ADR-0015/0017/0026 each refused.
  • Experimental-prefixed names — the marker is a stability statement the tool name must carry on MCP (ADR-0007/0021; get_pack_diagram's withdrawal is the cautionary precedent).
  • Serve-don't-validate — lenient identity reads (bare carrier decode, never the closed-schema Load); undecodable documents are listed with the reason and served with status undecodable.
  • sha256 bare hex per the payload convention; formatVersion on these payloads is the document's own declaration (the walk-payload meaning is untouched and the distinction stated).
  • No engine constructed — the three-site census and CONFORMANCE.md's eight-surfaces sentence do not move; the reference-only scan admits the new descriptions.

Tests

MCP: inventory + document against the graph fixture (exact-bytes equality, digest, identity echoes), the undecodable fixture (listed with reason, served undecodable), missing-configuration asymmetry, and argument discipline (null, case-folded alias via exactMembers, absent). CLI: the inventory in both formats. tools/list census updated to 13.

Review

Cross-vendor round to follow per the regime; disposition table will be posted with the reviewed SHA.

Material-decision impact: public-surface; review: #130 (comment) (r1), #130 (comment) (r2), plus the round-3 table on this PR

🤖 Generated with Claude Code

kikashy and others added 2 commits August 24, 2026 03:57
…loses #126)

A wire-only client could run every configured graph's matrix and fetch
every node's pack, and could not read the one artifact stating the
composition. experimental_list_graphs resolves every configured graph
(configured id beside the document's own id and version, result node,
node and edge counts, paths, and the description the configuration
schema always said was for someone reading the inventory) and
experimental_get_graph serves one document's exact bytes beside its
metadata, through the rooted reader, under the graph surface's own byte
limit. The CLI twin experimental graph list renders the same inventory
from the same function. Listing and serving are lenient exactly as the
pack side is — a mid-edit document is listed with the reason and served
undecodable — the missing-configuration asymmetry is get_pack's, the
payload digest follows the bare-hex payload convention, formatVersion
on these payloads is the document's own declaration, the graph_id
argument is held to its exact spelling and type, and neither tool
constructs an engine, so the conformance perimeter does not move.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Brian Jin <35789537+kikashy@users.noreply.github.com>
The mediums: a text frame carries UTF-8 and nothing else, so both fetch
tools now serve through one helper that refuses invalid bytes with the
path instead of letting the JSON encoder transcode them into
disagreement with the stated bytes and sha256 — closing the same live
defect on get_pack; get_pack also gains the exactMembers hold, closing
the case-folded PACK_ID alias the review proved live; counts that
cannot be taken are now absent rather than zero (nodeCount/edgeCount,
nullable, with rowsDeclared replacing the array-typed names the walk
payloads own); the new CLI help joins the conformance-claim scans; and
the wire shapes are asserted raw — future formatVersion served as
declared, counts absent on non-collection members, graphs as a JSON
array, experimental on both payloads, the oversized and root-escaping
and binary fixtures each refused with their reasons, and the two
missing-configuration notes held byte-identical across the pair. The
lows: decode details carry the pack-style path framing; the parity
prose is narrowed to the resolved inventories with the CLI's
missing-config error stated as the pack side's own; the README command
roster gains the list verb; and the package doc says every evaluation
payload where it said every payload.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Brian Jin <35789537+kikashy@users.noreply.github.com>
@kikashy

kikashy commented Aug 24, 2026

Copy link
Copy Markdown
Contributor Author

Cross-vendor review round 1 — disposition table

Reviewer: Codex (codex-cli 0.145.0). Reviewed SHA: cd8c495. 12 findings: 10 confirmed (5 medium, 5 low), 2 refuted (core implementation and roster mechanics verified clean, incl. the three-engine census and the rooted reader). All applied in ce5ca72 under the regime's dispositioned-finding exception.

# Finding Severity Disposition
1 "Exact bytes" false for invalid UTF-8: the JSON text frame transcodes to U+FFFD, disagreeing with bytes/sha256 — same live defect inherited by get_pack medium Fixed — both fetch tools serve through one servedDocument helper that refuses non-UTF-8 bytes with the path to read directly; ADR/changelog narrowed; mutation-verified
2 Malformed-but-decodable nodes/edges silently reported as honest zeros medium Fixed — nullable nodeCount/edgeCount, absent (never zero) for non-collection members; future fixture pins it on the raw wire
3 nodes/rows member names collide with the walk payloads' array-typed members low FixednodeCount, edgeCount, rowsDeclared
4 PACK_ID is a live case-fold bypass on get_pack (only the new tool had the hold) medium FixedexactMembers on get_pack, exact-message test, mutation-verified; recorded in the changelog as a companion defect fix
5 Graph decode details lacked the pack-style path framing low Fixed — wrapped via project.ReadFailureMessage
6 ADR overstated CLI/MCP parity for missing configuration low Fixed — narrowed to the resolved inventories; the CLI's packs-list-style error stated in ADR, changelog, and command comment
7 README "Implemented commands" roster omitted the new verb low Fixed
8 experimental graph list --help escaped the conformance-claim scans medium Fixed — added to claimSurfaces and the required-names list
9 doc.go "every payload" overclaim, made conspicuous by the new claim-free payloads low Fixed — "every evaluation payload"
10 Test adequacy: struct round-trips, hardcodable formatVersion, unpinned notes/Experimental/oversized/escape medium Fixed (with one declared exception) — raw-wire-key assertions, "formatVersion":"future" served as declared, byte-identical missing-config notes across the pair, experimental asserted on both payloads, oversized and symlink-escape and binary fixtures each refused with reasons. Declined: the counting/mutating reader seam for the single-read property — finding 11 verified single-read structurally, and a reader-injection seam is test infrastructure whose weight outbuys the residual risk; recorded here rather than silently skipped
11–12 Suspected single/double-read, limit, escape, engine-census, roster, ADR-mechanics defects Refuted by reviewer — verified clean against source

One pre-existing get_pack test asserting the old unknown-key message was updated to the sharper exactMembers diagnostic. Gate green at ce5ca72 (fmt, vet, full suite, bundled conformance).

🤖 Generated with Claude Code

All seven visible confirmed findings, low severity: the invocation-error
command roster learns the list verb; the invalid-UTF-8 refusal quotes
the configured path and names the configuration it is relative to;
exactMembers speaks in the grammatical number of its member list; the
serving claim states its exact boundary everywhere it is made (readable,
in-limit, valid-UTF-8 bytes failing decode are served undecodable; read
failures, root-escaping paths, oversized documents, and invalid UTF-8
are refusals carrying their reasons); the count condition is stated as
it is implemented (present exactly when identity decoding succeeded and
nodes is an object, edges an array) and the tool description names the
three members; the package doc enumerates the payload contracts as they
actually are; and the tests now assert raw positive counts and forbid
the walk payloads' member names, pin the pack-style detail framing, pin
the UTF-8 refusal on BOTH fetch tools with quoted paths, render unknown
counts as unknown in human output, and confine the one symlink case to
POSIX instead of skipping the whole shape suite on Windows.

Process note recorded on the pull request: round 2's finding 1 was lost
to output truncation on the orchestrating side, so round 3 runs as a
full fresh review rather than an increment.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Brian Jin <35789537+kikashy@users.noreply.github.com>
@kikashy

kikashy commented Aug 24, 2026

Copy link
Copy Markdown
Contributor Author

Cross-vendor review round 2 — disposition table

Reviewer: Codex (codex-cli 0.145.0). Reviewed SHA: ce5ca72. 16 findings; findings 2–16 were captured: 7 confirmed (all low), 8 refuted (every round-1 fix verified correct in source: the UTF-8 gate on both fetches, end-to-end nullable counts, the renames, the PACK_ID hold, path framing, parity narrowing, README roster, claim-scan closure).

Process defect, declared: finding 1 and the reviewer's SHA statement were lost to output truncation on the orchestrating side (a tail cap). Rather than disposition an invisible finding, round 3 runs as a full fresh review of the current SHA — anything finding 1 named that remains unfixed will re-surface there.

Confirmed findings, applied in 6053efe:

# Finding Disposition
2 JSON invocation errors misname the new CLI verb (roster switch omitted list) Fixed
3 UTF-8 refusal named an unquoted path with no configuration context Fixed — quoted, and "relative to the configuration at …"
4 exactMembers grammatical number wrong for a single member Fixed — "the accepted member is "pack_id""
5 Serving claims overbroad after the UTF-8 fix (tool description, ADR clause 2, changelog) Fixed — the exact served/refused boundary stated at all three
6 Count-condition prose described the wrong condition (nodes:[] IS a collection) Fixed — "present exactly when identity decoding succeeded and nodes is a JSON object / edges a JSON array", everywhere incl. the tool description
7 "Every evaluation payload" still false (refusal envelopes, corpus) Fixed — doc.go enumerates the three actual contracts
8 Tests under-discriminating (struct round-trips for positive counts, no human "unknown", UTF-8 only via graph, whole suite skipped on Windows) Fixed — raw positive counts + legacy keys forbidden, detail-framing prefix pinned, UTF-8 refusal pinned on both fetch tools with quoted paths, human unknown/number labels asserted, symlink case POSIX-only while the rest runs everywhere. The declined reader-seam stands as dispositioned in round 1

Gate green at 6053efe. Round 3 (full) to follow.

🤖 Generated with Claude Code

The medium: the inventory could be asked to amplify — a configuration
bounds neither its graph entries nor how many alias one document, so
thousands of aliases of one near-limit document meant gigabytes of
reads and echoes. The builder now caches reads by unique path, charges
each unique read once and each row's echoed identity every time, and
refuses past an 8-MiB budget with JPS-GRAPH-INVENTORY-BUDGET — the
inventory sibling of the walk's report budget, refusing for the same
reason: a truncated inventory would under-report silently. The lows:
experimental_list_graphs now holds its closed empty schema (a member on
a no-member schema, an explicit null, and a non-object are each
refused before any project read, with exactMembers growing the
zero-member grammar); the serving boundary is stated truthfully at the
three remaining sites (both fetch descriptions and the two client
guides: text, exact when valid UTF-8, refused with the path when not);
the human identity label says what is true either way (identity
unavailable) while the detail line says which; each inventory renderer
carries its own comment; and the tests now pin honest zeros beside
absent counts on wire and human output, the configured path and
description echoes, full identity emptiness on the undecodable row, the
null argument's exact refusal, the hostile no-argument shapes, the
budget refusal under an injected budget, and the invocation envelope
naming experimental graph list.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Brian Jin <35789537+kikashy@users.noreply.github.com>
@kikashy

kikashy commented Aug 24, 2026

Copy link
Copy Markdown
Contributor Author

Cross-vendor review round 3 (full fresh review) — disposition table

Reviewer: Codex (codex-cli 0.145.0). Reviewed SHA: 6053efe, stated in the review's first line and captured in full this time (findings-first, no output cap — the round-2 truncation cannot recur). 6 findings: 1 medium, 5 low, all confirmed; the review's own summary verifies everything else on inspection (nullable counts, rowsDeclared, exact-byte digest/size, document-declared formatVersion, rooted reads, byte limit, missing-config asymmetry, get_pack hold, evaluator census, rosters, no-write/no-engine claims), and it ran go test ./..., go vet, and git diff --check itself. All fixes in 0bb201f under the dispositioned-finding exception.

# Finding Severity Disposition
1 Inventory resource amplification: unbounded graph entries × 1 MiB documents × per-row echoes ⇒ multi-GiB from small files medium Fixed — reads cached by unique path (charged once), row echoes charged per row, 8 MiB budget refusing with JPS-GRAPH-INVENTORY-BUDGET (the walk's report-budget sibling: a truncated inventory would under-report silently); injectable-budget test, charging mutation-verified
2 experimental_list_graphs ignored its closed empty schema low Fixed — member/null/non-object each refused before any project read; exactMembers grows the zero-member grammar; hostile raw-frame tests, hold mutation-verified
3 Fetch descriptions and both client guides contradicted the UTF-8/text behavior ("JSON text", "exactly the bytes on disk") low Fixed — all five sites state: text; exact when valid UTF-8; refused with the configured path when not
4 Human label called every detailed row "document unreadable", undecodable included low Fixed — "identity unavailable", with the detail line saying which
5 Renderer comment attached to the wrong function low Fixed — each renderer carries its own
6 Tests under-discriminating (honest zeros, null's exact refusal, path/description echoes, full undecodable emptiness, requestedCommand roster) low Fixed — all five pinned, on wire and human output

Gate green at 0bb201f. Three rounds complete, every finding across them dispositioned; flipping the ADR to accepted for merge.

🤖 Generated with Claude Code

Three cross-vendor rounds are dispositioned on the pull request
(reviewed SHAs cd8c495, ce5ca72, 6053efe; fixes ce5ca72, 6053efe,
0bb201f), so the record flips on the merge that makes the decision.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Brian Jin <35789537+kikashy@users.noreply.github.com>
@kikashy
kikashy merged commit 437ae58 into main Aug 24, 2026
6 checks passed
@kikashy
kikashy deleted the graph-serve branch August 24, 2026 09:19
@github-actions

Copy link
Copy Markdown

🎉 Thank you for your contribution to Judgment Pack!

If you’d like another task, choose a path that interests you:

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.

Serve a declared graph document over MCP, the way get_pack serves a pack

1 participant