Repository navigation
Conversation
AppState::local opens a standalone graph read-only or read-write behind a generated bearer token and a Cedar policy generated for one actor, which it installs on the engine as well as at the HTTP layer. Every policy action has a local-server class, so an action added later is never granted until it is given one. A read-only local server refuses every write-class route in ingress before reading its body. No local server mounts the cluster deployment routes; each enables MCP ad-hoc reads and lists write tools only when it writes. A root inside a cluster is refused. serve_local runs the deployment's serving lifecycle, now one private type both entry points share, on a loopback listener the caller bound. It writes nothing to stdout, disarms the shutdown watchdog when it returns, and reports an uncertain outcome instead of exiting. install_log_subscriber puts the server's restricted log filter on another writer.
LocalScope::Cluster serves a cluster's storage root from its applied state for one actor, with the cluster's own policies, stored queries, embedding-provider bindings and external-Blob rules. A read-only one captures one ledger revision without admission, opens every graph read-only and validates it against that revision, and forbids every write action in each policy it loads, so policy answers report the refusal. A read-write one takes the cluster's serving admission and refuses while another owner holds it; serve_local releases it after a clean drain through the release the deployment runs, and keeps it when a completion is uncertain. PolicyEngine::forbidding adds a Cedar forbid for a set of actions, and a prepared graph open now names its access: writable within a storage scope, or read-only against a captured ledger revision.
The shutdown listener registered its SIGTERM and SIGINT handlers only when its spawned task first ran, and `serve_local` installed the lifecycle only after the caller had announced readiness. A signal in either window met the default disposition and ended the process without the drain. The lifecycle now registers both handlers before it returns, and `start_local` installs it so a caller announces between `start_local` and `LocalServing::serve`. `serve_local` remains the two calls in one.
…server # Conflicts: # crates/omnigraph-server/src/lib.rs # crates/omnigraph-server/tests/boot_settings.rs # docs/dev/control-plane.md
|
This commit adds Read-only capture takes no cluster lock. It opens each graph with Request changes. Both accesses drop the boot witness. A read-only open returns success when every graph fails. A cluster with no policy bundle leaves the engine open to writes. Blocking1.
|
…ne's policy - A relative or URL-special local root now names its cluster. `LocalBinding::from_root` makes a local root absolute, and the cluster storage URI is built with `Url::from_file_path`, so `./cluster` no longer becomes `file://./cluster`, which the cluster store refused in both modes. - A ledger revision that moves between a read-only open's capture and its validation refuses the whole server. `GraphOpenFailure` carries the cluster's refusal from `validate_opened`, and the read-only open turns it into `LocalOpenError::Cluster`. A graph whose schema drifted from its achieved contract is still blocked alone, as normal serving blocks it. - A user rule can share an id with the generated forbid. Cedar policy ids are namespaced `permit:` and `forbid:`, so the two can no longer collide and fail `PolicySet::add`. A matched rule still reports the bare rule id. - `LocalServing::release` stops a local server that never served. It closes request admission, drains the logical owners, releases a read-write cluster's serving admission as `serve` does after a clean drain, and disarms the watchdog. Before this, a caller that failed after `start_local` could only drop it, which keeps the admission lock. - `/readyz` and `/graphs` report the ledger revision served. Both access modes install the boot witness from the applied snapshot instead of the default revision 0. - A local cluster server refuses when no graph opens, as a deployment's startup does. Read-only now applies the same check as `finish_graph_startup`, and both modes honour `OMNIGRAPH_REQUIRE_ALL_GRAPHS` through the config's `require_all_graphs`, not a hard-coded `false`. - A graph the cluster binds no policy to gets `read` alone for the actor, which is what a deployment's routes grant without a bundle. The grant is installed on the engine too, so an embedder holding the handle can no longer write through an engine with no policy. - Read-write refusals before any graph opens release the admission, as a refused settings preflight does. The catalogue policy now loads before graph preparation. A failed startup after a graph open keeps the admission and names its lock in `LocalOpenError::AdmissionRetained`. - The read-only catalogue policy passes through the same write forbid as every other policy it loads.
|
This pull request lets one process serve a cluster's applied state on loopback to the same OS account. Read-only capture takes no cluster lock. It opens each graph with The commits after Request changes. Earlier findings
Blocking1.
|
A write whose outcome is unknown still stops the server that ran it, so `AppState::local` refuses `LocalAccess::ReadWrite` with `LocalOpenError::ReadWriteUnavailable` until it no longer does. The read-write path stays and stays tested: the `test-util` feature's `with_local_read_write` scope opens it for tests alone, enabled from dev-dependencies only (this crate's own included) and checked by the test-util guard, and a build with the feature still refuses outside the scope. `LocalBinding::local(path)` replaces the string constructor. It takes a root on this machine's file system, made absolute, and refuses a URI: a remote root's endpoint and credentials would come from the process environment when it opens, which a captured binding must not depend on. A read-only local server's engine refuses a direct write, now tested on the handle. The developer guide says what the read-write gate is, that an embedder a browser can reach adds the loopback guard in `extend`, and that Tokio keeps the signal handlers for the rest of the process. The invariants say the library serves one standalone graph on loopback while the binary stays cluster-only, and the release notes no longer claim read-write or a wider signal window than the code covers.
…server # Conflicts: # crates/omnigraph-server/src/local.rs # crates/omnigraph-server/tests/boot_settings.rs # docs/dev/control-plane.md
|
Addressed at d3170d6: fix 00ce414, plus a merge of #1075's 4708c68. Blocking
Optional
Policy ids are namespaced ( With #1075's gate, read-write cluster serving is refused too: |
|
This pull request serves a cluster's applied state on loopback to the same OS account. An embedder then uses the cluster's policies, stored queries, and bindings.
A client can read Request changes. Reviewed head Earlier findings
Blocking1. The support boundary names only a standalone graph
This pull request also serves one cluster read-only on loopback. That path takes no admission. The sentence is false. The developer guide is the boundary the next change applies. Write the boundary this branch implements. The library serves one standalone graph or one cluster, read-only, on loopback. A read-only open takes no cluster admission. Read-write stays refused until a write with an unknown outcome no longer stops the server. The draft RFC says this sentence should name both scopes. The control-plane page already does.
Inline, Optional1. The new text says
|
The support boundary in the invariants named only a standalone graph, though this change also serves one cluster read-only on loopback without cluster admission; it now names both scopes and that read-write stays refused until a write whose outcome is unknown no longer stops the server. The control-plane page and the comment above the boot witness said `/graphs` reports the ledger revision; only `/readyz` does.
|
Addressed at 1413287. Blocking
Optional
RFC differences: #1017's local embedding API now matches this branch.
|
…-write A read-only local server refused every write with "start it with --write to change the graph", though `AppState::local` refuses read-write outside the test scope and no command accepts `--write` yet. The refusal now says the server is read-only and that a read-write local server is not available yet, and the route test checks that it names no `--write`. `LocalBinding::local` folds `.` and `..` with the storage crate's lexical absolutizer, now re-exported beside `normalize_root_uri`, so one directory has one stored spelling. The embedding page no longer cites a command this change does not ship.
Brings in the read-only refusal that names no --write, the folded local binding root, and the embedding page without the serve reference.
Brings in the access route from ModernRelay#1073; its tests and the local server's sit side by side at the end of auth_policy.rs.
Brings in main through ModernRelay#1075: the access route from ModernRelay#1073, whose tests follow the local cluster server's at the end of auth_policy.rs.
Brings in ModernRelay#1075's fix through ModernRelay#1077: the read-only refusal names no --write, and a local binding folds `..` before it is stored.
The engine re-exports the storage crate's lexical absolutizer for the local server's binding; the split-crate re-export guard lists every such item by name, so it now names this one.
Brings in the engine's pinned re-export of absolutize_lexically.
|
This pull request serves one cluster on loopback to the same OS account. An embedder then uses the cluster's policies, stored queries, and bindings.
The commits after Approve. Reviewed head Earlier findings
BlockingNone. OptionalNone. Delta
Where the code and the RFC differRFC head The 2026-10-19 RFC note matches this branch on the points the last review left open:
One sample comment still differs. The RFC says A cluster with no server bundle still denies Sibling pull requestsMerge after #1075. This head contains #1075 at #1073 is merged. This head includes its access route through the parent merge of main. The access route evaluates the installed graph policy. A read-only cluster server has already run Current sibling heads, all still open unless noted:
Tradeoff and liabilityOne serving projection and one release function is the right shape. The support boundary now matches the code. The next change will treat a cluster local server as inside the published boundary. Public read-write stays refused. That keeps a long-lived process from holding a lock that A read-only server takes no admission, so The doc fix is the small change. Five more local scopes should keep this witness, this all-failed check, and this no-bundle policy. The sequential read-only open keeps one revision fail-closed. A deployment opens graphs concurrently. The extra latency is the cost of that check. ValidationHead Commands from an isolated checkout at this head, with
CI on this head: run 39277627199 passed Test Workspace, clippy, rustfmt, server aws, S3, and Azure. GQT ordinary and GQT served passed (run 39277626939). GQT (dst) failed This delta changes documentation. GQT cannot express the support-boundary sentence, or which route reports the ledger revision. I am not asking for a LimitationsI did not rerun the full workspace suite locally. CI did, on this head, apart from the one GQT case above. I did not run this path on S3 or Azure. The binding refuses those URIs. A fetch of the Lance object-store page returned no text. This delta does not change Lance dataset writes. The cluster lock is I did not re-review the #1075 read-write gate or the signal-handler registration, except where these merges meet this pull request's files. The uncertain-completion test uses a standalone graph. The cluster lifecycle test covers a clean shutdown and |
Disposition — 2026-10-20
The second serving model is discontinued. OmniGraph retains one cluster-backed
server model, including on localhost: the ordinary applied state, authentication,
policies, admission, routes and deployment lifecycle.
This PR is closed without merging. The standalone/local serving APIs, generated
local policies and credentials, and local-server discovery/election/reuse
registry are not proceeding. This decision supersedes the implementation and
sequencing proposed below; draft #1017 is being revised accordingly. No part of
this serving stack has shipped. General improvements to the existing server
may be proposed independently without introducing another serving mode.
Historical proposal
Stacked on #1075 (standalone local servers); review only the last commit.
What & why
LocalScope::Clusterserves a cluster's storage root from its applied state for one actor, with the cluster's own policies, stored queries, embedding-provider bindings and external-Blob rules:GraphReadAuthority(a changed revision or an outstanding deployment refuses), and adds a Cedarforbidfor every write action to each policy it loads, so policy answers report the refusal (rulelocal-read-only).state_lock_held).serve_localreleases it after a clean drain, through the release the deployment runs (now one shared function), and keeps it when a completion is uncertain.PolicyEngine::forbiddingadds the forbid; a prepared graph open now names its access (writable within a storage scope, or read-only against a captured revision).Backing issue / RFC
Checklist
a_forbidden_action_is_denied_whatever_permits_it; in-source read-only/read-write class consistency;auth_policyread-only (no lock, forbid decided, writes refused) and read-write (lock held, second owner refused, reader beside it, writes as its actor);boot_settings::local_servingcluster mode (lock while serving, released on a clean shutdown)changelog.d/local-cluster-server.added.mdLocal verification
RUST_MIN_STACK=16777216 cargo test -p omnigraph-server -p omnigraph-policy— all pass; after the last change (deriving the action list from the enum) thelocallib and integration tests were re-runcargo clippy -p omnigraph-server -p omnigraph-policy --all-targets --locked -- -D warnings -W clippy::dbg_macro,cargo fmt --all --check,python3 scripts/check-docs.py,typos— cleanNotes for reviewers
A read-write local server of a cluster whose graphs all fail to open keeps the admission it took, as a deployment does at startup.