feat: canonical POST /load, deprecate /ingest (RFC-009 Phase 5)#222
Conversation
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>
| 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), | ||
| )) | ||
| } |
There was a problem hiding this comment.
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.
The last RFC-009 item — completes RFC-009. The CLI's non-deprecated
loadverb rode the deprecated/ingestroute, so/ingest's eventual removal would silently break it. This adds a canonical/load, mirroring the shipped/mutate↔/changeand/query↔/readdeprecation pattern exactly.feat(server): canonical POST /load, deprecate /ingestserver_ingest's body into a sharedrun_ingest(branch-exists / fork-if-from, Cedar auth, admission,load_as,IngestOutputmapping).server_load(canonical) →run_ingest,Json<IngestOutput>.server_ingest(deprecated) →run_ingest+#[deprecated]+ RFC 9745/8288Deprecation: true/Link: </load>; rel="successor-version"headers./load(same 32 MB body limit) beside/ingest; OpenAPIpaths(...)gainsserver_loadand flagsserver_ingestdeprecated.openapi.jsonregenerated./loadreusesIngestRequest/IngestOutput— exactly as canonical/mutatereusesChange*. A DTO rename is a separate, larger change (out of scope).feat(cli): point remote load at /loadGraphClient::load's remote arm now POSTs/load; the deprecatedingestverb keeps riding/ingest.parity_loadexercises/load(its documented flip).Verification
OpenAPI drift test regenerated + strict-passes; new assertions (
/loadpresent + not-deprecated + bearer-secured,/ingestdeprecated); data-route/loadhappy-path +/ingestdeprecation-header tests; existing/ingesttests stay green (shim unchanged);parity_loadgreen; fullcargo test --workspace --locked→ exit 0. Docs: server.md endpoint table; RFC-009 Phase 5 marked landed (incl. the hand-mount-vs-utoipa-axumregistration 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 /loadendpoint alongside the now-deprecatedPOST /ingest, mirroring the existing/mutate↔/changeand/query↔/readpattern exactly. The implementation is clean: shared logic is extracted intorun_ingest, the canonicalserver_loadwraps it with a plainJsonreturn, and the deprecatedserver_ingestwraps it with RFC 9745Deprecation: true/ RFC 8288Link:headers.run_ingestfactored out;server_load(canonical) andserver_ingest(deprecated shim) both delegate to it; router mounts/loadwith the same 32 MB body limit;openapi.jsonregenerated with/loadadded and/ingestflaggeddeprecated: true.GraphClient::load's remote arm now POSTs/load; the deprecatedingestverb keeps riding/ingest;parity_loadexercises the flip.load_endpoint_loads_into_existing_branch,ingest_endpoint_emits_deprecation_headers,openapi_load_is_not_deprecated, andopenapi_ingest_is_deprecatedassertions; existing/ingesttests 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
/ingestwith noload()method and a stalespec/openapi.json; SDK consumers will silently receiveDeprecationheaders without a canonical upgrade path from the SDK until a companion PR lands there. Since/ingestis 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 newopenapi.jsonand add aload()method).Important Files Changed
run_ingesthelper, adds canonicalserver_load, demotesserver_ingestto a deprecated shim with RFC 9745/8288 headers — mirrors the existing/mutate↔/changepattern exactly./loadwith the same 32 MB body limit as/ingest; addsserver_loadto the OpenAPI paths list and keepsserver_ingestwith#[allow(deprecated)]at both call sites.loadverb's remote arm from/ingestto/load; deprecatedingestverb keeps targeting/ingest.load_endpoint_loads_into_existing_branch(no deprecation header) andingest_endpoint_emits_deprecation_headers(RFC 9745 + RFC 8288) tests; coverage is complete and well-structured./loadtoEXPECTED_PATHS, addsopenapi_load_is_not_deprecatedandopenapi_ingest_is_deprecatedtests, and adds/loadto the bearer-security assertion set./ingestgainsdeprecated: trueand updated description; new/loadoperation added with identical request/response shapes./loadis now live; no functional change.utoipa-axumregistration finding and defers migration as orthogonal./loadrow added as canonical,/ingestrow 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-versionReviews (1): Last reviewed commit: "feat(cli): point remote load at /load (R..." | Re-trigger Greptile