Skip to content

fix(cli): require explicit quads for batch verification#638

Closed
branarakic wants to merge 2 commits into
feat/ot-rfc-38-lu5from
codex/pr609-feedback-followup
Closed

fix(cli): require explicit quads for batch verification#638
branarakic wants to merge 2 commits into
feat/ot-rfc-38-lu5from
codex/pr609-feedback-followup

Conversation

@branarakic
Copy link
Copy Markdown
Contributor

Summary

  • require /api/shared-memory/verify-batch callers to pass explicit batch quads instead of reconstructing from the whole local graph
  • add a route-level regression test that proves omitted quads are rejected before local graph reads
  • update the LU-8 devnet harness to expect the validation error, then continue through the explicit-quads happy path

Verification

  • pnpm --filter @origintrail-official/dkg exec vitest run --config vitest.unit.config.ts test/memory-graph-events.test.ts --reporter dot
  • pnpm --filter @origintrail-official/dkg exec vitest run --config vitest.unit.config.ts --reporter dot (requires local bind permission; passed with escalated sandbox)
  • git diff --check

Notes

  • PR OT-RFC-38 LU-7/8/9/10 — catchup + verify + attestation + devnet harness #609 review threads for attestation membership and unknown KC 404 handling were already addressed in the merged R2 commit; this follow-up fixes the remaining unscoped verify-batch fallback.
  • A local CLI tsc --noEmit run is still blocked by existing workspace setup/type drift unrelated to this patch: stale/missing workspace package declarations for EPCIS and missing local @iarna/toml install.

let quads: Array<{ subject: string; predicate: string; object: string; graph: string }> = [];
if (Array.isArray(parsed.quads)) {
quads = parsed.quads.map((q: any) => ({
if (!Array.isArray(parsed.quads)) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Bug: This turns quads from an optional field into a required one for the public verify-batch API. The existing contract in the repo docs/changelog still allows callers to omit quads and verify against locally persisted SWM/data-graph content, so this will 400 existing clients even in single-batch/public-CG cases that were previously valid. If the local-reconstruction path is unsafe only for batch-scoped requests, keep rejecting that narrower case (for example when batchId is set) or add a compatibility/versioned path instead of breaking the endpoint outright.

log "✓ Scenario 1: verify-batch returned ok=true ($QUADS_CONSIDERED quads considered)"
log "verify-batch missing-quads response: $VERIFY_MISSING_QUADS"
MISSING_QUADS_ERROR=$(parse_json "$VERIFY_MISSING_QUADS" '.error')
if printf '%s' "$MISSING_QUADS_ERROR" | grep -q 'requires explicit `quads`'; then
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Issue: This scenario only checks for an error substring and never asserts the HTTP status. A regression that returns a 500 with the same message would still pass the test, so it does not actually prove request validation is happening. Capture the response status from curl and assert 400 here in addition to the error text.

if (!Array.isArray(parsed.quads)) {
return jsonResponse(res, 400, {
error:
`verify-batch requires explicit \`quads\` in the request body. ` +
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Bug: Making quads mandatory turns this into a full-batch upload API, which is a breaking contract change for existing callers and also caps verification to whatever fits under SMALL_BODY_BYTES (256 KB) because the whole plaintext batch now has to be posted back to the daemon. Large batches that were previously verifiable from local state will start failing at the HTTP boundary before hashing. Please either keep a backwards-compatible server-side path for safe cases, or raise/stream the request limit and version/deprecate the old behavior explicitly.

@branarakic
Copy link
Copy Markdown
Contributor Author

Superseded by PR #645 (merged into integration) and #649 (release: rc.10 testnet-ready cut). All commits from this PR are now on main. Unaddressed Codex review feedback (PR 609 follow-up: quads-mandatory contract break + missing HTTP status assertion) is being tracked + fixed in a dedicated post-rc.10 followup PR.

@branarakic branarakic closed this May 25, 2026
branarakic pushed a commit that referenced this pull request May 25, 2026
…ANGELOG fix

PR #625's runbook documented HTTP and CLI surfaces that don't exist on
the rc.10 daemon — operators following it from step 1 would 404 / 401
their way through every section. PR #638's LU-8 CHANGELOG entry promised
a server-side reconstruction path for `verify-batch` that the actual
route refuses. Both are doc bugs only, no code change.

docs/RFC38_LU6_TWO_LAPTOP_TESTNET_RUNBOOK.md:
 - Replaces `dkg show` (never a real top-level command) with the
   actual surface: `dkg status` (peerId/multiaddrs/role), `dkg auth
   show` (token, stripped of comments), and `GET /api/agent/identity`
   for the agent EOA. Same for `dkg show-cg` (compute the wire id as
   `keccak256(<cgId>)` since there's no CLI shortcut) and `dkg
   shared-memory host-mode stats` (use `GET /api/shared-memory/
   host-mode/stats` directly).
 - Every `$(cat ~/.dkg/auth.token)` interpolation now uses `dkg auth
   show` instead. The token file has a commented-header preamble by
   default, so the literal `cat` would inject `#\n<token>` into the
   `Authorization` header and 401 even on a healthy node. `dkg auth
   show` strips comments + blank lines, matching what
   `packages/node-ui/vite.config.ts` does for the same reason.
 - `/api/context-graph/list` filter corrected — the response is
   `{ contextGraphs: [...] }` (envelope, not bare array) and items
   expose `accessPolicy` (numeric: 0=public, 1=curated), not `access`.
   The old `jq '.[] | select(.access=="curated")'` filter would have
   silently matched nothing.
 - Member catchup now pins `peerId` to the core's libp2p identity.
   Without `peerId`, `/api/shared-memory/catchup` fans out to every
   connected peer — which in a two-laptop+core topology can hit
   laptop A directly or no peer at all, and won't reliably validate
   the "via the core" host-catchup path the runbook claims to verify.
   Showed how to grab the peer id from `/api/status` first.
 - Replaced the `/api/shared-memory/list?contextGraphId=...` curl
   (404, no such route) with a SPARQL `SELECT (COUNT(*) AS ?n)` via
   `/api/query` against the `_shared_memory` graph suffix — the same
   shape every devnet script uses for the equivalent assertion.

CHANGELOG.md:
 - LU-8 entry no longer claims "when `quads` is omitted the route
   reconstructs from the local SWM or post-publish CG data graph".
   That hasn't been true since the safety hardening in the published
   route (`packages/cli/src/daemon/routes/memory.ts:1042-1049`):
   `quads` is **required**, HTTP 400 on omission, because the daemon
   can't safely identify a single published batch's leaves inside a
   CG-wide store. Devnet scenario `scripts/devnet-test-rfc38-lu8.sh`
   SCENARIO 1 pins this contract with a HTTP-400 assertion. The
   CHANGELOG now matches the route's actual behaviour + rationale.

Co-authored-by: Cursor <cursoragent@cursor.com>
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.

1 participant