You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Publish a client crate: HTTP client and wire types without the engine #1098
Applications that talk to a shared omnigraph-server (we're moving a task tracker onto one) want a typed Rust client, but today the only options are the CLI's internal client or omnigraph-api-types, which depends on omnigraph-engine and omnigraph-compiler for its engine-result → DTO mappings. Using the types pulls the whole engine into a client's build.
Proposal: (1) move the engine/compiler From mappings in omnigraph-api-types behind an engine feature (or into omnigraph-server), so the crate is plain serde/utoipa types; (2) extract omnigraph-cli/src/graph_http.rs into an omnigraph-client crate on top of it, covering /query (with snapshot), /mutate, /mutate/if-graph-commit and stored queries, with the contract header and typed ErrorCodes. The CLI then depends on omnigraph-client.
Benefit: clients build without the engine, and server and clients share one definition of the wire contract instead of hand-copied DTOs that drift across releases.
An application that calls a shared omnigraph-server needs the HTTP JSON types and a small Rust client. Today the wire crate omnigraph-api-types also depends on omnigraph-engine and omnigraph-compiler. A client build therefore compiles the engine, Lance, and DataFusion. The CLI’s HTTP code is not a library. It is pub(crate) and it is tied to CLI configuration.
The crate text says the types are plain serde and utoipa types. The manifest does not match that text.
Status
Checked again immediately before this comment. Issue #1098 is open. It has no comments. Its timeline has one event: the needs-triage label. No pull request is linked. No other issue or pull request uses the scope “client crate” or an engine feature on omnigraph-api-types. This issue stays open.
Reproduction
Main SHA: 7fd9cc9037c634e2f3284f1ede55a85a6f7b28e8 (7fd9cc90, “fix(storage): keep a bound handle's re-reads on its own session”).
Host: Linux, rustc 1.97.1 (8bab26f4f 2026-07-14).
Claim: the wire crate depends on the engine and the compiler
Verified.
crates/omnigraph-api-types/Cargo.toml lines 11–16 have no features. The normal dependencies are omnigraph-engine, omnigraph-compiler, serde, serde_json, and utoipa.
Workspace crates that depend on it: omnigraph-cli, omnigraph-server, omnigraph-gqt-core. The engine does not depend on the wire crate.
.github/workflows/publish-crates.yml publishes omnigraph-api-types only after omnigraph-engine, and the comment there records that dependency. This host got HTTP 403 from crates.io, so the public index was not read.
Claim: a client that uses the types compiles the engine
Verified.
cargo tree -p omnigraph-api-types -e normal --prefix none -f '{p}'
The normal graph has 550 unique packages, including omnigraph-api-types. The compiler’s normal graph has 74. 476 packages are in the wire-crate graph and not in the compiler graph. That set includes utoipa and the engine closure: lance v13.0.0, datafusion v54.0.0, Arrow 58, and the other OmniGraph crates (omnigraph-core, omnigraph-catalog, omnigraph-planner, omnigraph-policy, omnigraph-storage, omnigraph-seams).
This finished in 4m 07s. It compiled omnigraph-engine, lance v13.0.0, and datafusion v54.0.0. The existing target/ directory was about 5.5 GB. This was not a clean build. A clean build takes longer. The count is the package tree. A separate scratch crate was not created. An external crate that depends on this package gets this same normal closure.
Claim: the dependency exists for engine-result to DTO mappings
Verified, and the coupling is wider than From impls.
crates/omnigraph-api-types/src/lib.rs imports engine and compiler types at lines 6–19. Line 28 does pub use omnigraph_compiler::settings. That re-export is public, so every user of the crate compiles the compiler. The session-settings RFC keeps the definition in the compiler because a definition in the wire crate would cycle. See docs/rfcs/2026-09-16-session-settings.md, rejected alternative 13.
These public fields name foreign types. A feature around the impls does not remove the dependency while these fields stay:
IngestRequest.mode, GraphBatchLoadQuery.mode, IngestOutput.mode, and GraphBatchLoadOutput.mode are omnigraph::loader::LoadMode (a three-variant serde enum). LoadModeSchema in the same file already copies it for OpenAPI.
SchemaApplyOutput.steps is Vec<omnigraph_compiler::SchemaMigrationStep>. That enum carries compiler PropType, Constraint, and Annotation. OpenAPI already documents the field as Vec<Value>.
SettingsRequest fields are already plain (Option<String> and Option<i64>). SettingsRequest::assignments returns compiler SettingId and SettingValue. The OpenAPI bounds come from SettingId::spec().
From impls in this crate, engine type to wire type: MergeOutcome, MergeConflictKind, GraphCommitKind, ChangeEntityKind, ChangeOpKind. Compiler type to wire type: SystemColumns, QueryDiagnostic.
From impls in the other direction, wire type to engine type: EntityKindOutput to ChangeEntityKind, ChangeOpOutput to ChangeOpKind.
Free functions take or return engine or compiler types. Examples: commit_output, read_output, snapshot_payload, schema_apply_output, load_executed_output, change_scope, change_feed_output, blob_write_output, parse_blob_if_match, param_descriptor, merge_conflict_output, and the load and branch read helpers. QueryRequest, ChangeRequest, ReadOutput, ErrorOutput, and ErrorCode are already local. They still sit in a crate that cannot compile without the engine.
Rust’s orphan rule: a From impl must live in the crate that owns one of the types. omnigraph-server owns neither the wire type nor the engine type. The impl is illegal there. A third conversion crate has the same limit. A free function can move. A From impl cannot.
Claim: graph_http.rs is the HTTP client for query, mutate, conditional mutate, and stored queries
Partly. The file exists. It is not that client.
crates/omnigraph-cli/src/graph_http.rs (GraphHttpClient, pub(crate)) does the transport admission:
It parses http, https, and unix server addresses.
It sends omnigraph-http-api: 0.14 on every data request (HTTP_API_CONTRACT is "0.14").
It sends omnigraph-if-actor when the CLI has an expected actor.
It does a HEAD of healthz before every data request, with a 5 second timeout. Discovery failure sets request_dispatched: false.
It requires exactly one contract header. A data response that fails that check sets request_dispatched: true and the text says not to retry.
It builds reqwest with redirects off and retry(never()).
On HTTP 401 with no bearer, it attaches the CLI MissingCredential value.
It calls CLI modules: helpers::MissingCredential, helpers::remote_url, operator::InvocationConfig, targets (credential owner and request scope), and command_outcome. Errors use color_eyre. The bearer token is not stored here. The caller puts Authorization on the request. This file reads it back only to redact Retry-After.
The routes are GraphClient in crates/omnigraph-cli/src/client.rs:
mutate posts /mutate, or /mutate/if-graph-commit when a graph-commit precondition is set. The header name is omnigraph-if-graph-commit.
invoke_named posts /queries/{name}, or /queries/{name}/if-graph-commit.
Query posts /query with QueryRequest, which has snapshot.
crates/omnigraph-cli/src/helpers.rs decodes the body. A non-success body that is ErrorOutput becomes RemoteErrorCli. A verified HTTP 412 whose precondition_failure.expected matches the sent commit becomes PreconditionFailedCli. Some refusals omit ErrorCode and use a detail field (precondition_failure, recovery_required, and others). A client that matches only ErrorCode misses those.
crates/omnigraph-cli/src/command_outcome.rs is CLI process bookkeeping. Its first lines say it is not engine settlement. If the command did not prove that work never started, effects is unknown and the action is reconcile. Admission refusals and a verified precondition can be not_started. This policy is not in graph_http.rs.
A second private client is ServerClient in crates/omnigraph-gqt-core/src/served.rs. omnigraph-gqt-core is publish = false. That client sends the contract header. It does not probe healthz. It does not require the header on the response. It is another copy of the same calls.
Design
The observable contract is the HTTP JSON and the headers, at contract 0.14. The engine types are an implementation of that JSON for the server and the embedded CLI. The client build is a different observer. It must not compile the engine.
Do this in two steps. The first step removes the engine from client builds. The second step adds the client crate.
Step 1. One wire crate, local public types, conversions behind a feature
Keep omnigraph-api-types as the only owner of the wire types. RFC 0009 already put the shared DTOs here so the server and the CLI do not each build a body.
Make every public field a type that this crate owns, with the feature off and with the feature on. Cargo unifies features. If the public field type changes when the feature is on, any crate that enables the feature pulls the engine back into the client build.
Replace LoadMode on the wire with one local enum. Use the three snake_case names that LoadMode already serializes. Delete the shadow LoadModeSchema. One From<LoadMode> impl sits behind the feature.
Change SchemaApplyOutput.steps to the JSON OpenAPI already documents. Keep SchemaMigrationStep in the compiler. Add one round-trip test, behind the feature, that the wire JSON and the compiler enum match. Do not copy PropType into the wire crate. That would be a second schema language.
Put pub use omnigraph_compiler::settings, SettingsRequest::assignments, and the schema_with helpers behind the feature. SettingsRequest itself stays plain. The server enables the feature, so the published OpenAPI bounds still come from SettingId::spec(). The server OpenAPI drift test remains the publisher of the spec.
Put every From impl and every conversion function behind one feature named engine, default off. The feature turns on optional dependencies on omnigraph-engine and omnigraph-compiler. omnigraph-server, omnigraph-cli, and omnigraph-gqt-core enable it.
Do not move the From impls into omnigraph-server. The orphan rule rejects them. Do not move them into the engine either. The engine would then depend on the wire crate. The session-settings RFC rejected that cycle. The engine’s public surface would grow HTTP types. After more changes of that kind, the engine would own the wire document.
This is a Cargo API break for callers of the conversion functions and of LoadMode on these structs. The HTTP JSON stays the same when the serde names stay the same. Contract 0.14 does not have to change for this split. After the split, the publish workflow can publish the wire crate before the engine.
What this step gives up: it does not, by itself, give applications a ready-made client. That delay is accepted. A client published in the same change would promise today’s paths while other work is changing those paths.
Step 2. A small omnigraph-client after the type split
Do not move graph_http.rs as a file. Split by duty.
The client crate owns:
reqwest with redirects off and retries off
the contract header on the request, and a check for exactly one contract header on the response
the same discovery rule, or a documented narrower rule that still fails closed
POST /query with snapshot, POST /mutate, POST /mutate/if-graph-commit, and POST /queries/{name} (and the conditional stored-query path)
decode of ErrorOutput, including detail fields that have no ErrorCode
The caller owns:
the bearer string, or a small token trait
omnigraph-if-actor as an assertion the server checks, not as an actor the client assigns
files, keyring, WorkOS, and target configuration
#1024 is a CLI directory-mode bug. It stays in the CLI. #1025 and #978 are future token kinds. A token trait lets them plug in without a second client. #1019 and #1104 are the access read. Add that method when the route is stable. #1104 is open and moves evaluation toward the wire crate. That evaluation must stay behind the engine feature.
Unknown outcome. Do not copy command_outcome into the library. That type is one CLI process. The client rule is smaller. If the request was sent and the result is not a decoded success or a decoded refusal, the outcome is unknown and the client must not retry a write. #1076 and #1078 are open. They add a server disposition (effects: none, partial, unknown, and an action). That field is not on main at this SHA. When it lands, the client returns it unchanged. It must not compute a second outcome. #1093 and #1096 are open. They scope an unknown storage result to a key and a branch. The client still must not retry a write the server did not settle.
Change feeds. Pass cursors and page tokens through as opaque strings. #1009 says they are checksummed, not signed. A client that parses them takes a security bug. #1056 (wait instead of poll) is not built. Do not add wait in the client.
MCP. #991 is a different protocol on /mcp. It is out of scope for this client.
HTTP shape. #1089 is an open RFC. It plans one conventions pass, then a later contract batch that folds /if-graph-commit into a header and renames operations. A stable client that hard-codes those paths becomes a promise against that batch. Prefer the type split now. Publish omnigraph-client on the contract after that batch, or publish it as an unstable crate whose breaks follow HTTP_API_CONTRACT.
The CLI then depends on the client for the remote arm. The embedded arm stays on the engine. GQT’s ServerClient can call the same client later, so the response contract check has one implementation.
OpenAPI as the client generator
openapi.json is derived. omnigraph-server builds it with utoipa from these types. A generator (progenitor or a similar tool) can emit Rust types from that file. It cannot emit the outcome rule, the discovery probe, or the “do not retry” rule. Those rules are not in the spec. A generated type crate would be a second source next to omnigraph-api-types. Keep one type source. Use the spec as a test that the client calls documented operations.
Test plan
In CI, cargo tree -p omnigraph-api-types -e normal with default features must not contain omnigraph-engine, omnigraph-compiler, lance, or datafusion.
omnigraph-server, omnigraph-cli, and omnigraph-gqt-core enable the feature. Their existing tests stay the owners. The server OpenAPI drift test must still pass, and the settings schema must still match SettingId::spec().
One round-trip test locks SchemaMigrationStep JSON to SchemaApplyOutput.steps.
When the client crate exists, extend the CLI transport tests. Cover the contract header, a missing header, a repeated header, and no retry after a mutate is sent. Do not add a second HTTP harness.
Liability
Removed: client builds no longer compile Lance and DataFusion. The publish order no longer ties the wire crate to the engine. One conversion stays next to the wire types.
Added if the public types differ by feature: Cargo feature unification silently returns the engine to client builds. The public types must not differ.
Added if omnigraph-client is stable before the #1089 path batch: every path and the retry rule become a compatibility promise. That is the wrong promise to add while the HTTP conventions RFC is open.
Added if schema steps are copied as a second compiler enum: two schema languages. The JSON-plus-round-trip choice avoids that.
Lance
Lance is not relevant. Cargo.toml pins lance at =13.0.0. Lance v13.0.0 is the latest stable release (2026-10-07). Later tags are v14.0.0 betas, starting 2026-10-12. No open OmniGraph pull request changes the pin. This defect is the wire crate’s dependency edge. A Lance change does not remove that edge.
Related work
RFC 0009, unified access paths: one wire crate for server and CLI.
Session-settings RFC: settings definition stays in the compiler. The re-export is why the compiler dependency is not only a mapping.
#1089 HTTP API conventions (open RFC).
#1076, #1078 error disposition and CLI exit (open, not on this main). #1017 is the graph-access RFC they name.
The 4m 07s figure is one cargo check on this host, not a clean build. crates.io was not read (HTTP 403). disposition was read from the open pull requests. It is not in the tree at this SHA. Lance 14 beta pull requests were not reviewed one by one. None of them can delete this Cargo edge.
Activity
ragnorc commentedon Oct 21, 2026
Problem
An application that calls a shared
omnigraph-serverneeds the HTTP JSON types and a small Rust client. Today the wire crateomnigraph-api-typesalso depends onomnigraph-engineandomnigraph-compiler. A client build therefore compiles the engine, Lance, and DataFusion. The CLI’s HTTP code is not a library. It ispub(crate)and it is tied to CLI configuration.The crate text says the types are plain serde and utoipa types. The manifest does not match that text.
Status
Checked again immediately before this comment. Issue #1098 is open. It has no comments. Its timeline has one event: the
needs-triagelabel. No pull request is linked. No other issue or pull request uses the scope “client crate” or anenginefeature onomnigraph-api-types. This issue stays open.Reproduction
Main SHA:
7fd9cc9037c634e2f3284f1ede55a85a6f7b28e8(7fd9cc90, “fix(storage): keep a bound handle's re-reads on its own session”).Host: Linux,
rustc 1.97.1 (8bab26f4f 2026-07-14).Claim: the wire crate depends on the engine and the compiler
Verified.
crates/omnigraph-api-types/Cargo.tomllines 11–16 have no features. The normal dependencies areomnigraph-engine,omnigraph-compiler,serde,serde_json, andutoipa.Workspace crates that depend on it:
omnigraph-cli,omnigraph-server,omnigraph-gqt-core. The engine does not depend on the wire crate..github/workflows/publish-crates.ymlpublishesomnigraph-api-typesonly afteromnigraph-engine, and the comment there records that dependency. This host got HTTP 403 from crates.io, so the public index was not read.Claim: a client that uses the types compiles the engine
Verified.
The normal graph has 550 unique packages, including
omnigraph-api-types. The compiler’s normal graph has 74. 476 packages are in the wire-crate graph and not in the compiler graph. That set includesutoipaand the engine closure:lance v13.0.0,datafusion v54.0.0, Arrow 58, and the other OmniGraph crates (omnigraph-core,omnigraph-catalog,omnigraph-planner,omnigraph-policy,omnigraph-storage,omnigraph-seams).This finished in 4m 07s. It compiled
omnigraph-engine,lance v13.0.0, anddatafusion v54.0.0. The existingtarget/directory was about 5.5 GB. This was not a clean build. A clean build takes longer. The count is the package tree. A separate scratch crate was not created. An external crate that depends on this package gets this same normal closure.Claim: the dependency exists for engine-result to DTO mappings
Verified, and the coupling is wider than
Fromimpls.crates/omnigraph-api-types/src/lib.rsimports engine and compiler types at lines 6–19. Line 28 doespub use omnigraph_compiler::settings. That re-export is public, so every user of the crate compiles the compiler. The session-settings RFC keeps the definition in the compiler because a definition in the wire crate would cycle. Seedocs/rfcs/2026-09-16-session-settings.md, rejected alternative 13.These public fields name foreign types. A feature around the impls does not remove the dependency while these fields stay:
IngestRequest.mode,GraphBatchLoadQuery.mode,IngestOutput.mode, andGraphBatchLoadOutput.modeareomnigraph::loader::LoadMode(a three-variant serde enum).LoadModeSchemain the same file already copies it for OpenAPI.SchemaApplyOutput.stepsisVec<omnigraph_compiler::SchemaMigrationStep>. That enum carries compilerPropType,Constraint, andAnnotation. OpenAPI already documents the field asVec<Value>.SettingsRequestfields are already plain (Option<String>andOption<i64>).SettingsRequest::assignmentsreturns compilerSettingIdandSettingValue. The OpenAPI bounds come fromSettingId::spec().Fromimpls in this crate, engine type to wire type:MergeOutcome,MergeConflictKind,GraphCommitKind,ChangeEntityKind,ChangeOpKind. Compiler type to wire type:SystemColumns,QueryDiagnostic.Fromimpls in the other direction, wire type to engine type:EntityKindOutputtoChangeEntityKind,ChangeOpOutputtoChangeOpKind.Free functions take or return engine or compiler types. Examples:
commit_output,read_output,snapshot_payload,schema_apply_output,load_executed_output,change_scope,change_feed_output,blob_write_output,parse_blob_if_match,param_descriptor,merge_conflict_output, and the load and branch read helpers.QueryRequest,ChangeRequest,ReadOutput,ErrorOutput, andErrorCodeare already local. They still sit in a crate that cannot compile without the engine.Rust’s orphan rule: a
Fromimpl must live in the crate that owns one of the types.omnigraph-serverowns neither the wire type nor the engine type. The impl is illegal there. A third conversion crate has the same limit. A free function can move. AFromimpl cannot.Claim:
graph_http.rsis the HTTP client for query, mutate, conditional mutate, and stored queriesPartly. The file exists. It is not that client.
crates/omnigraph-cli/src/graph_http.rs(GraphHttpClient,pub(crate)) does the transport admission:http,https, andunixserver addresses.omnigraph-http-api: 0.14on every data request (HTTP_API_CONTRACTis"0.14").omnigraph-if-actorwhen the CLI has an expected actor.HEADofhealthzbefore every data request, with a 5 second timeout. Discovery failure setsrequest_dispatched: false.request_dispatched: trueand the text says not to retry.retry(never()).MissingCredentialvalue.It calls CLI modules:
helpers::MissingCredential,helpers::remote_url,operator::InvocationConfig,targets(credential owner and request scope), andcommand_outcome. Errors usecolor_eyre. The bearer token is not stored here. The caller putsAuthorizationon the request. This file reads it back only to redactRetry-After.The routes are
GraphClientincrates/omnigraph-cli/src/client.rs:mutateposts/mutate, or/mutate/if-graph-commitwhen a graph-commit precondition is set. The header name isomnigraph-if-graph-commit.invoke_namedposts/queries/{name}, or/queries/{name}/if-graph-commit./querywithQueryRequest, which hassnapshot.crates/omnigraph-cli/src/helpers.rsdecodes the body. A non-success body that isErrorOutputbecomesRemoteErrorCli. A verified HTTP 412 whoseprecondition_failure.expectedmatches the sent commit becomesPreconditionFailedCli. Some refusals omitErrorCodeand use a detail field (precondition_failure,recovery_required, and others). A client that matches onlyErrorCodemisses those.crates/omnigraph-cli/src/command_outcome.rsis CLI process bookkeeping. Its first lines say it is not engine settlement. If the command did not prove that work never started,effectsisunknownand the action isreconcile. Admission refusals and a verified precondition can benot_started. This policy is not ingraph_http.rs.A second private client is
ServerClientincrates/omnigraph-gqt-core/src/served.rs.omnigraph-gqt-coreispublish = false. That client sends the contract header. It does not probehealthz. It does not require the header on the response. It is another copy of the same calls.Design
The observable contract is the HTTP JSON and the headers, at contract
0.14. The engine types are an implementation of that JSON for the server and the embedded CLI. The client build is a different observer. It must not compile the engine.Do this in two steps. The first step removes the engine from client builds. The second step adds the client crate.
Step 1. One wire crate, local public types, conversions behind a feature
Keep
omnigraph-api-typesas the only owner of the wire types. RFC 0009 already put the shared DTOs here so the server and the CLI do not each build a body.Make every public field a type that this crate owns, with the feature off and with the feature on. Cargo unifies features. If the public field type changes when the feature is on, any crate that enables the feature pulls the engine back into the client build.
LoadModeon the wire with one local enum. Use the three snake_case names thatLoadModealready serializes. Delete the shadowLoadModeSchema. OneFrom<LoadMode>impl sits behind the feature.SchemaApplyOutput.stepsto the JSON OpenAPI already documents. KeepSchemaMigrationStepin the compiler. Add one round-trip test, behind the feature, that the wire JSON and the compiler enum match. Do not copyPropTypeinto the wire crate. That would be a second schema language.pub use omnigraph_compiler::settings,SettingsRequest::assignments, and theschema_withhelpers behind the feature.SettingsRequestitself stays plain. The server enables the feature, so the published OpenAPI bounds still come fromSettingId::spec(). The server OpenAPI drift test remains the publisher of the spec.Fromimpl and every conversion function behind one feature namedengine, default off. The feature turns on optional dependencies onomnigraph-engineandomnigraph-compiler.omnigraph-server,omnigraph-cli, andomnigraph-gqt-coreenable it.Do not move the
Fromimpls intoomnigraph-server. The orphan rule rejects them. Do not move them into the engine either. The engine would then depend on the wire crate. The session-settings RFC rejected that cycle. The engine’s public surface would grow HTTP types. After more changes of that kind, the engine would own the wire document.This is a Cargo API break for callers of the conversion functions and of
LoadModeon these structs. The HTTP JSON stays the same when the serde names stay the same. Contract0.14does not have to change for this split. After the split, the publish workflow can publish the wire crate before the engine.What this step gives up: it does not, by itself, give applications a ready-made client. That delay is accepted. A client published in the same change would promise today’s paths while other work is changing those paths.
Step 2. A small
omnigraph-clientafter the type splitDo not move
graph_http.rsas a file. Split by duty.The client crate owns:
POST /querywithsnapshot,POST /mutate,POST /mutate/if-graph-commit, andPOST /queries/{name}(and the conditional stored-query path)ErrorOutput, including detail fields that have noErrorCodeThe caller owns:
omnigraph-if-actoras an assertion the server checks, not as an actor the client assigns#1024is a CLI directory-mode bug. It stays in the CLI.#1025and#978are future token kinds. A token trait lets them plug in without a second client.#1019and#1104are the access read. Add that method when the route is stable.#1104is open and moves evaluation toward the wire crate. That evaluation must stay behind theenginefeature.Unknown outcome. Do not copy
command_outcomeinto the library. That type is one CLI process. The client rule is smaller. If the request was sent and the result is not a decoded success or a decoded refusal, the outcome is unknown and the client must not retry a write.#1076and#1078are open. They add a serverdisposition(effects:none,partial,unknown, and anaction). That field is not on main at this SHA. When it lands, the client returns it unchanged. It must not compute a second outcome.#1093and#1096are open. They scope an unknown storage result to a key and a branch. The client still must not retry a write the server did not settle.Change feeds. Pass cursors and page tokens through as opaque strings.
#1009says they are checksummed, not signed. A client that parses them takes a security bug.#1056(wait instead of poll) is not built. Do not addwaitin the client.MCP.
#991is a different protocol on/mcp. It is out of scope for this client.HTTP shape.
#1089is an open RFC. It plans one conventions pass, then a later contract batch that folds/if-graph-commitinto a header and renames operations. A stable client that hard-codes those paths becomes a promise against that batch. Prefer the type split now. Publishomnigraph-clienton the contract after that batch, or publish it as an unstable crate whose breaks followHTTP_API_CONTRACT.The CLI then depends on the client for the remote arm. The embedded arm stays on the engine. GQT’s
ServerClientcan call the same client later, so the response contract check has one implementation.OpenAPI as the client generator
openapi.jsonis derived.omnigraph-serverbuilds it with utoipa from these types. A generator (progenitor or a similar tool) can emit Rust types from that file. It cannot emit the outcome rule, the discovery probe, or the “do not retry” rule. Those rules are not in the spec. A generated type crate would be a second source next toomnigraph-api-types. Keep one type source. Use the spec as a test that the client calls documented operations.Test plan
cargo tree -p omnigraph-api-types -e normalwith default features must not containomnigraph-engine,omnigraph-compiler,lance, ordatafusion.omnigraph-server,omnigraph-cli, andomnigraph-gqt-coreenable the feature. Their existing tests stay the owners. The server OpenAPI drift test must still pass, and the settings schema must still matchSettingId::spec().SchemaMigrationStepJSON toSchemaApplyOutput.steps.Liability
Removed: client builds no longer compile Lance and DataFusion. The publish order no longer ties the wire crate to the engine. One conversion stays next to the wire types.
Added if the public types differ by feature: Cargo feature unification silently returns the engine to client builds. The public types must not differ.
Added if
omnigraph-clientis stable before the#1089path batch: every path and the retry rule become a compatibility promise. That is the wrong promise to add while the HTTP conventions RFC is open.Added if schema steps are copied as a second compiler enum: two schema languages. The JSON-plus-round-trip choice avoids that.
Lance
Lance is not relevant.
Cargo.tomlpinslanceat=13.0.0. Lance v13.0.0 is the latest stable release (2026-10-07). Later tags are v14.0.0 betas, starting 2026-10-12. No open OmniGraph pull request changes the pin. This defect is the wire crate’s dependency edge. A Lance change does not remove that edge.Related work
#1089HTTP API conventions (open RFC).#1076,#1078error disposition and CLI exit (open, not on this main).#1017is the graph-access RFC they name.#1093,#1096storage outcome scope (open).#1104access command (open).#1019access route.#1024credential directory mode.#1025OAuth.#978delegated credentials.#1009continuation tokens.#1056change-feed wait.#991MCP. Out of scope.Limitations
The 4m 07s figure is one
cargo checkon this host, not a clean build. crates.io was not read (HTTP 403).dispositionwas read from the open pull requests. It is not in the tree at this SHA. Lance 14 beta pull requests were not reviewed one by one. None of them can delete this Cargo edge.