Repository navigation
feat(cli)!: replace legacy routing with explicit targets - #1090
Conversation
|
I'm closing #1080 in favour of this one. It's the additive interim; this PR is the accepted breaking design and already carries #1080's target code. Three things from #1080 that this branch doesn't have yet. I can bring them as a patch onto whichever split part owns the CLI grammar:
The |
|
Recommendation: request changes. The head reviewed is Why, what, how, impactThe CLI currently chooses a destination from several flags, a profile, a folder file, and a positional URI. Those selectors do not share one rule, so a command can reach a different backend from the one the operator named. This PR replaces that with one captured target. The order is That is the right shape for a pre-stable break. One binding, one resolution order, and no compatibility adapter removes a class of wrong-destination bugs. The cost is a breaking 0.14 CLI. Scripts that pass Two protocol-1 behaviors do not match the contract this PR states. A named S3 target still inherits ambient AWS settings that can change signing, TLS, conditional writes, and credential refresh. The published binding hash does not use the same endpoint identity that credential checks already use. Both are cheap to fix before protocol 1 is consumed, and expensive after. Blocking1. A named S3 target freezes every parsed
|
| PR | Alignment |
|---|---|
| #1059 | Same --as allow-list. Rebase will conflict in planes.rs. The exhaustive match is the part to keep. |
| #1078 | Exit statuses are unchanged here. Every TargetError exits 2. Rebase onto the outcome table after #1076. |
| #1076 | Supplies the ErrorCode registry the nine target codes still need. |
| #1081, #1083, #1084 | Built on #1080. They need this target model, protocols, and the canonical digest. |
| #1075, #1077 | Server loopback work. They stay compatible if they compare the same canonical binding digest. |
| #675 | Stale draft of this RFC. |
Liability
The resolver is one function with one order. Five more command families can call it without a new flag. That lowers long-term liability, and the large deletion of the old selectors is real evidence of that.
The S3 copy raises liability in the other direction. Five more AmazonS3ConfigKey variants would become named-target behavior with no review. An allow-list stops that. The canonical digest is one identity function shared by credentials, JSON, and later local-server election. A raw-string hash becomes a second identity.
Net line change is about +13,000 / −10,900 across 164 files. The deletion of the old routing paths is the part that removes liability. The new target module is the replacement source of truth, which is appropriate. The captured-option map is the part that adds liability until it is classed.
Validation
- Head:
84283920f89cc089ae36a3efec72610318bf9634. Base:9e2425d26a03b1b5628030a359ee51f096d3ca1c. No other review on this head. - Code inspection of target resolution, credential scope, import, command applicability, and the pinned Lance 11.0.0 AWS and Azure store providers (
with_env_s3,build_aws_credential,with_env_azure). - Lance object-store guide: credentials default to the provider chain; static keys and
aws_provider_schemeoverride that chain; region and endpoint may come from the environment or from storage options. - Storage test above, on this head. Result: the unclassified keys are captured. The probe was removed. It was not committed.
- URL folding check with
url2.5.8, as listed under blocking finding 2. - I did not re-run the author-reported CLI suite, GQT, or the change-feed failpoint. I did not run live S3 or Azure.
GQT cannot express provider options, CLI JSON, or a Lance session cache. The regression for finding 1 belongs in omnigraph-storage binding tests. The regression for the session fix belongs on the existing change-feed failpoint owner.
… reads The review of c4641d7 found the engine section did not match the publisher on main: a row change must record a graph commit, and the preconditions read only the head and branch identity, so a head-preserving tombstone or drop record stopped nobody. - Control facts (tombstone, registration, drop record) change no table row and record no commit; check_publish_precondition reads the facts that name an attempt on every attempt, for every precondition. - A create registers on main before it writes its ref, so the registration is the visibility point and cleanup reads it instead of an age. Until PR 963 lands, a registration refuses when main's contract moved past its fork; after it, PR 963's rule wins. - Only the upgrade census raises the new stamp. - RFC 0059 (PR ModernRelay#1090) owns addressing and the target and credential commands; deployments pass their root through --target. - The access route is ModernRelay#1073's; this RFC adds the command. A direct graph inside a cluster refuses and names the root. - Storage outcomes are PR 1093's; storage_unavailable goes, and disposition.check is a later phase.
ModernRelay#1090 landed on main, so the RFC no longer waits on it: blocked_on is empty, and the Motivation, rollout and Appendix A5 say the target commands build on main.
Main replaced the legacy routing flags with explicit targets (ModernRelay#1090). The actor assertion sits on that structure: `GraphClient::from_target` and the served cluster client send `--as` as `Omnigraph-If-Actor` on a server target, where ModernRelay#1090 refused it, and only a managed target still refuses `--as`. `accepts_actor` keeps deciding which commands take it. The HTTP contract stays 0.14.
It brings main's explicit targets (ModernRelay#1090) and Lance 13. A target refusal that ModernRelay#1090 reports with the command's evidence carries `disposition`, as every other data-write failure does.
Brings in main through ModernRelay#1076, including ModernRelay#1090's explicit targets. The exit table keeps its classification: the removed use and profile commands leave it, and the target and credential commands, which keep their own failure reports, join the unclassified ones. ModernRelay#1078's tests move to --target, ModernRelay#1090's usage-error tests expect exit 64, and the diagnostics checks pass the destination line ModernRelay#1090 prints first.
* docs(rfc): propose local graph sessions and client access Draft RFC for issue ModernRelay#980: `omnigraph serve` local sessions with session records, a thin `omnigraph studio` launcher, `omnigraph mcp` for agents, and `omnigraph credential` for static tokens. Wider-blast-radius changes are listed in Appendix A with their filed issues. * docs(rfc): add installing Studio as a later, signature-gated phase * docs(rfc): link Studio's release prerequisites and correct their current state * docs(rfc): reconcile local sessions with main and open PRs, and define the target writer * docs(rfc): name local servers and settle records, tokens and agent paths Rename the session to a local server and --session to --via-server. Move server records from /tmp, which macOS ages out after three days, to per-user directories resolved from the account, with the machine they belong to. Never write a token to a terminal, list live local servers in targets --json, give each agent context one write path, add platform coverage, and gather every refusal code in one table. * docs(rfc): redesign graph access around seven rules and fold in cross-process writes Rewrite the RFC from first principles: name the target, never the path; correctness in the engine across processes; one set of operations on three surfaces; one serving concept with explicit audiences, folding the omnigraph-server binary into omnigraph serve; --as as a declaration or an assertion; one outcome model and exit table; and introspection through target list and access. Bring cross-process writer safety and the access route into scope, with the engine design on the __manifest version CAS, and land the interfaces before the engine phase. Rename the file to match the new title. * docs(rfc): align outcomes, serving lifecycle and late writes with the storage-uncertainty fix * docs(rfc): elect one local server per destination and close the reuse, handoff and MCP gaps from review * rfc: align graph access with the phase 1 and phase 3 implementations The actor header rides the HTTP contract bump to 0.14 instead of a capability response header, and /mcp compares it after authentication alone. Rule 6 states the outcome table the server implements, with an earlier publication marked on the engine's error and outcome.check waiting for an attempt id. The local embedding API names what the implementation settled: LocalBinding::local for local roots, ReadWriteUnavailable, ClusterGraph instead of promoting a graph to its cluster, start_local and LocalServing::release, and one captured ledger revision per read-only cluster server. * rfc: align graph access with the disposition, the If-Actor header and the records' machine id Phase 1's review renamed the error object and the actor header, and phase 3's review tightened the records' machine id. The RFC now says what the implementations do: - The actor header is Omnigraph-If-Actor, after HTTP's conditional If-* headers, since a mismatch is a failed condition (412). - The error object is `disposition`: `execution` is gone because it only repeated `effects`; `effects` gains `partial` for a refused step after an earlier publication of the same operation; `action` gains `fix` for a request that fails the same way on resend. Each code names its own action, and partial effects exit 2. - A record's machine is 32 lowercase hexadecimal digits that are not all zeros. Any other value, systemd's first-boot `uninitialized` marker included, refuses rather than falling back to another source. - Windows records wait for their own pull request: until they check the directory's access list and read MachineGuid, they refuse. - The CLI's MCP bridge bullet lists what the bridge actually changes: the graph binding, the readOnlyHint filter and local cancellation. * rfc: decide branch control through control facts each publish attempt reads The review of c4641d7 found the engine section did not match the publisher on main: a row change must record a graph commit, and the preconditions read only the head and branch identity, so a head-preserving tombstone or drop record stopped nobody. - Control facts (tombstone, registration, drop record) change no table row and record no commit; check_publish_precondition reads the facts that name an attempt on every attempt, for every precondition. - A create registers on main before it writes its ref, so the registration is the visibility point and cleanup reads it instead of an age. Until PR 963 lands, a registration refuses when main's contract moved past its fork; after it, PR 963's rule wins. - Only the upgrade census raises the new stamp. - RFC 0059 (PR ModernRelay#1090) owns addressing and the target and credential commands; deployments pass their root through --target. - The access route is ModernRelay#1073's; this RFC adds the command. A direct graph inside a cluster refuses and names the root. - Storage outcomes are PR 1093's; storage_unavailable goes, and disposition.check is a later phase. * docs(rfc): discontinue the second serving model * docs(rfc): record MCP bridge closure and defer selective reuse * rfc: treat RFC 0059's implementation as merged ModernRelay#1090 landed on main, so the RFC no longer waits on it: blocked_on is empty, and the Motivation, rollout and Appendix A5 say the target commands build on main. * rfc: accept graph access and cross-process writes Maintainer decision: the actor assertion, the error-code registry and disposition, the exit table, the access command, operation parity, the existing-server MCP bridge and Studio opener, and the engine control-fact design with its qualification gates. Appendix A stays separate decisions. * rfc: give every control fact its attempt id and gate the upgrade on registrations The review of eeb6e12 found two holes in the engine section: - A control fact records no graph commit, so the head-equality read-back cannot tell two facts apart after a lost acknowledgement, and the owner's own retry met its refusal. Each control-fact operation now mints one attempt id that every fact it writes records: the read-back compares it, a pending control fact is keyed by it, and a retry treats its own fact as landed. Deletes and drops converge. - System-column upgrade pins main's head, not its version, so a registration did not stop it. Its attempts now read main's registrations every attempt, until PR 963. Cleanup's registration rule now covers refs as well as trees, under the collector's dependency closure; a create no longer completes a crashed create's registration; the pending-branch service_unavailable covers one branch; branch list shows a registered branch without a ref as not ready; the --as help quote is current. * rfc: name the access command's test owner and target terms * rfc: settle a control fact from the fact, and keep the collector's keep set The review of ef44175 recommended merging, with four optional corrections: read-back has three results; a caller settles a control fact from the fact, since the head does not move; cleanup candidates still pass protected_manifest_branches and retained_merge_bases; a crash loses the attempt id, so only delete and drop converge after one. A not-ready branch's listing fields come from its registration. * rfc: fence an unknown control fact by its version key Storage outcomes per operation dropped its pending-publication registry: an unknown publication is recorded nowhere and blocks nothing, and its version key fences it. A control fact follows the same fence, and the one-branch service_unavailable row goes. Read-back follows ModernRelay#1092's split, a not-ready branch's absent listing fields are named, and the access evidence uses RFC 0059's target kinds. * rfc: align the access view field and disposition.check with actor views * rfc: settle a control fact when its attempted version is readable --------- Co-authored-by: aaltshuler <andrew@collectivelab.io>
What & why
Replace overlapping routing flags and folder/profile fallback with one captured destination:
--target NAME|ADDRESS, thendefaults.target, otherwise an error.--configsupplies source files only. Resolved destinations and known write scopes are delivered to stderr before backend effects; unsupported command/target combinations refuse.This is the breaking 0.14 CLI migration. It removes legacy routing implementations, selectors,
use,profile, positional backend arguments exceptinit URI, and--quiet.target list/show/add/removeedits complete entries; explicit offlinetarget importconverts legacy configuration and preserves retry custody.credential getexports a selected endpoint-bound server credential.The captured binding reaches control storage and native Lance on the current Lance 13 baseline. Provider options preserve native authentication while fixing destination, signing, TLS and conditional-write behavior; unsupported ambient transport overrides refuse even after native environment refill. Endpoint digests use credential scope's canonical identity, and version JSON exposes
protocols.targetandprotocols.credential. Managed identity, same-ID observation, source-free recovery and graph publication retain their existing owners.The remaining actor classifier and parity-harness cleanup from #1059 is included. RFC 0059, current user examples, embedded skill and breaking release note match the implementation. Access reporting and optional error refinements remain separate. The discontinued second serving model and #1084's MCP work are not included; the existing cluster-backed server remains the sole serving model.
Backing issue / RFC
Checklist
Local verification
After integrating current main/Lance 13:
RUST_MIN_STACK=16777216 cargo test -p omnigraph-cli -p omnigraph-server --locked --lib --bin omnigraph-server --test integration: CLI 219 unit + 323 integration passed; server 208 unit + 1 binary + 270 integration passed. Existing ignored journeys remain ignored.cargo clippy --workspace --all-targets --locked -- -D warnings -W clippy::dbg_macroand the virtual-host endpoint rerun passed. Exact-head CI must pass before merge.Notes for reviewers
No compatibility routing adapters remain. Older binaries require a separate operator home. Existing graph-format upgrades,
omnigraph-serverand GQT command lines remain supported. Provider tests do not claim live S3/Azure or real identity-provider qualification; those retain their documented environments and support limits. No storage-cost golden change: option capture adds no storage operations or publication path.