Skip to content

feat: canonical POST /load, deprecate /ingest (RFC-009 Phase 5)#222

Merged
aaltshuler merged 2 commits into
mainfrom
feat/server-load-route
Jun 14, 2026
Merged

feat: canonical POST /load, deprecate /ingest (RFC-009 Phase 5)#222
aaltshuler merged 2 commits into
mainfrom
feat/server-load-route

Conversation

@aaltshuler

@aaltshuler aaltshuler commented Jun 14, 2026

Copy link
Copy Markdown
Collaborator

The last RFC-009 item — completes RFC-009. The CLI's non-deprecated load verb rode the deprecated /ingest route, so /ingest's eventual removal would silently break it. This adds a canonical /load, mirroring the shipped /mutate/change and /query/read deprecation pattern exactly.

feat(server): canonical POST /load, deprecate /ingest

  • Extract server_ingest's body into a shared run_ingest (branch-exists / fork-if-from, Cedar auth, admission, load_as, IngestOutput mapping).
  • server_load (canonical) → run_ingest, Json<IngestOutput>.
  • server_ingest (deprecated) → run_ingest + #[deprecated] + RFC 9745/8288 Deprecation: true / Link: </load>; rel="successor-version" headers.
  • Router mounts /load (same 32 MB body limit) beside /ingest; OpenAPI paths(...) gains server_load and flags server_ingest deprecated. openapi.json regenerated.

/load reuses IngestRequest/IngestOutput — exactly as canonical /mutate reuses Change*. A DTO rename is a separate, larger change (out of scope).

feat(cli): point remote load at /load

GraphClient::load's remote arm now POSTs /load; the deprecated ingest verb keeps riding /ingest. parity_load exercises /load (its documented flip).

Verification

OpenAPI drift test regenerated + strict-passes; new assertions (/load present + not-deprecated + bearer-secured, /ingest deprecated); data-route /load happy-path + /ingest deprecation-header tests; existing /ingest tests stay green (shim unchanged); parity_load green; full cargo test --workspace --locked → exit 0. Docs: server.md endpoint table; RFC-009 Phase 5 marked landed (incl. the hand-mount-vs-utoipa-axum registration finding — followed the existing manual pattern, migration deferred as orthogonal).

🤖 Generated with Claude Code

Greptile Summary

This PR completes RFC-009 Phase 5 by introducing a canonical POST /load endpoint alongside the now-deprecated POST /ingest, mirroring the existing /mutate/change and /query/read pattern exactly. The implementation is clean: shared logic is extracted into run_ingest, the canonical server_load wraps it with a plain Json return, and the deprecated server_ingest wraps it with RFC 9745 Deprecation: true / RFC 8288 Link: headers.

  • Server: run_ingest factored out; server_load (canonical) and server_ingest (deprecated shim) both delegate to it; router mounts /load with the same 32 MB body limit; openapi.json regenerated with /load added and /ingest flagged deprecated: true.
  • CLI: GraphClient::load's remote arm now POSTs /load; the deprecated ingest verb keeps riding /ingest; parity_load exercises the flip.
  • Tests: new load_endpoint_loads_into_existing_branch, ingest_endpoint_emits_deprecation_headers, openapi_load_is_not_deprecated, and openapi_ingest_is_deprecated assertions; existing /ingest tests stay green.

Confidence Score: 4/5

Safe to merge — the core server change is a clean extraction with shared logic behind both routes, all auth/admission/body-limit invariants preserved, and full test coverage for the happy path, deprecation headers, and OpenAPI flags.

The Rust-side changes are correct and well-tested. The one gap is that the TypeScript SDK in omnigraph-ts still hardcodes /ingest with no load() method and a stale spec/openapi.json; SDK consumers will silently receive Deprecation headers without a canonical upgrade path from the SDK until a companion PR lands there. Since /ingest is kept indefinitely the migration is non-breaking, but the SDK follow-up is the expected completion of this work.

No files in this PR need special attention; the companion update needed is in modernrelay/omnigraph-ts (regenerate SDK types from the new openapi.json and add a load() method).

Important Files Changed

Filename Overview
crates/omnigraph-server/src/handlers.rs Extracts shared run_ingest helper, adds canonical server_load, demotes server_ingest to a deprecated shim with RFC 9745/8288 headers — mirrors the existing /mutate/change pattern exactly.
crates/omnigraph-server/src/lib.rs Mounts /load with the same 32 MB body limit as /ingest; adds server_load to the OpenAPI paths list and keeps server_ingest with #[allow(deprecated)] at both call sites.
crates/omnigraph-cli/src/client.rs Redirects the canonical load verb's remote arm from /ingest to /load; deprecated ingest verb keeps targeting /ingest.
crates/omnigraph-server/tests/data_routes.rs Adds load_endpoint_loads_into_existing_branch (no deprecation header) and ingest_endpoint_emits_deprecation_headers (RFC 9745 + RFC 8288) tests; coverage is complete and well-structured.
crates/omnigraph-server/tests/openapi.rs Adds /load to EXPECTED_PATHS, adds openapi_load_is_not_deprecated and openapi_ingest_is_deprecated tests, and adds /load to the bearer-security assertion set.
openapi.json Regenerated artifact: /ingest gains deprecated: true and updated description; new /load operation added with identical request/response shapes.
crates/omnigraph-cli/tests/parity_matrix.rs Comment-only update to reflect that /load is now live; no functional change.
docs/dev/rfc-009-unify-access-paths.md Phase 5 marked as landed; documents the hand-mount vs. utoipa-axum registration finding and defers migration as orthogonal.
docs/user/server.md Endpoint table updated: /load row added as canonical, /ingest row updated with deprecated marker and header description.

Sequence Diagram

sequenceDiagram
    participant CLI as CLI load verb
    participant CLI2 as CLI ingest verb
    participant SL as POST /load server_load
    participant SI as POST /ingest server_ingest
    participant RI as run_ingest
    participant Engine as GraphHandle Engine

    CLI->>SL: POST /load (RFC-009 Phase 5)
    SL->>RI: run_ingest(state, handle, actor, request)
    RI->>Engine: branch_list / authorize / try_admit / load_as
    Engine-->>RI: LoadResult
    RI-->>SL: IngestOutput
    SL-->>CLI: 200 JSON no deprecation headers

    CLI2->>SI: POST /ingest deprecated alias
    SI->>RI: run_ingest(state, handle, actor, request)
    RI->>Engine: branch_list / authorize / try_admit / load_as
    Engine-->>RI: LoadResult
    RI-->>SI: IngestOutput
    SI-->>CLI2: 200 JSON + Deprecation true + Link rel successor-version
Loading

Fix All in Claude Code

Reviews (1): Last reviewed commit: "feat(cli): point remote load at /load (R..." | Re-trigger Greptile

Greptile also left 1 inline comment on this PR.

aaltshuler and others added 2 commits June 14, 2026 03:15
The CLI's non-deprecated `load` verb rode the deprecated `/ingest` route, so
`/ingest`'s eventual removal would silently break it. Add a canonical `/load`,
mirroring the shipped `/mutate`↔`/change` and `/query`↔`/read` pattern.

- Extract `server_ingest`'s body into a shared `run_ingest` (branch-exists /
  fork-if-`from`, Cedar auth, admission, `load_as`, `IngestOutput` mapping).
- `server_load` (canonical) → `run_ingest`, `Json<IngestOutput>`.
- `server_ingest` (deprecated) → `run_ingest` + `#[deprecated]` + RFC 9745/8288
  `Deprecation: true` / `Link: </load>; rel="successor-version"` headers.
- Router mounts `/load` (same 32 MB body limit) beside `/ingest`; OpenAPI
  `paths(...)` gains `server_load` and flags `server_ingest` deprecated.

`/load` reuses `IngestRequest`/`IngestOutput`, exactly as canonical `/mutate`
reuses `Change*` — a DTO rename is a separate, larger change (out of scope).

openapi.json regenerated. Tests: openapi `/load` present + not deprecated,
`/ingest` deprecated, `/load` bearer-secured; data_routes `/load` happy path +
`/ingest` deprecation headers. Existing `/ingest` route tests stay green (the
shim is unchanged). Docs: server.md endpoint table; RFC-009 Phase 5 marked
landed (incl. the hand-mount-vs-utoipa-axum registration finding).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`GraphClient::load`'s remote arm now POSTs to the canonical `/load` route
instead of the deprecated `/ingest`; the deprecated `ingest` verb keeps
riding `/ingest`. `parity_load` exercises `/load` on the remote arm (its
documented flip); the matrix exclusions comment is updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@aaltshuler aaltshuler requested a review from ragnorc as a code owner June 14, 2026 00:16
Comment on lines +1330 to 1347
pub(crate) async fn server_ingest(
State(state): State<AppState>,
Extension(handle): Extension<Arc<GraphHandle>>,
actor: Option<Extension<ResolvedActor>>,
Json(request): Json<IngestRequest>,
) -> std::result::Result<([(HeaderName, HeaderValue); 2], Json<IngestOutput>), ApiError> {
let output = run_ingest(
state,
handle,
actor.as_ref().map(|Extension(actor)| actor),
request,
)
.await?;
Ok((
deprecation_headers("</load>; rel=\"successor-version\""),
Json(output),
))
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 TypeScript SDK still targets /ingest with no /load method

omnigraph-ts's client.ts hardcodes POST /ingest in its ingest() method, and the SDK has no load() method for POST /load. Starting with this deployment, every ingest() call the SDK makes will receive Deprecation: true and Link: </load>; rel="successor-version" response headers with no corresponding canonical path for SDK consumers to migrate to. The spec/openapi.json pinned in that repo also predates this change and won't reflect the deprecation or the new /load operation. Since /ingest is kept indefinitely this isn't blocking, but a companion PR to regenerate the SDK types and add a load() method is the intended completion of this migration for TypeScript consumers.

Fix in Claude Code

@aaltshuler aaltshuler merged commit 8726ca9 into main Jun 14, 2026
9 checks passed
@aaltshuler aaltshuler deleted the feat/server-load-route branch June 14, 2026 00:32
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