Repository navigation
feat(server): report what a credential may do through an access route - #1073
Conversation
GET /graphs/{graph_id}/access and the MCP access tool return the
caller's resolved actor, the actions its credential allows on the whole
graph, and, for a named branch and target branch, the actions allowed
there. Each action is evaluated through authorize with the branch and
target branch shaped as the route that performs it shapes them, so the
answer matches those routes' decisions. The route needs no action of
its own, reads no graph data and judges branches by name.
|
This route tells a caller which of its own actions the installed policy allows, so the caller can show those actions before a write.
The HTTP contract stays at 0.13. That matches the RFC plan to move the version once, together with the actor header and the error outcome. RFC comparisonI compared this commit with draft RFC #1017 at The code agrees with the RFC on these points:
Two differences are in the RFC text, and I would keep the code:
TradeoffThe shape table in OrderingMerge this after the RFC is accepted, and before #1081. #1081 already contains this commit and then moves the decision into FindingsBlocking: none. Optional:
Recommendation: approve. ValidationReviewed commit: Local, from a detached checkout of that commit:
GitHub checks on this commit are success, including Lint (clippy), Format (rustfmt), Test Workspace, GQ Logic Tests, and Test omnigraph-server --features aws. Devin Review completed as a skip because the trial has no credits, so it is not a review of this head. This change adds a route. It does not fix a query or storage defect. GQT cannot see an HTTP authorization report, so there is no I did not re-run the full workspace gate locally. I used the green checks on this commit for that. The change does not call Lance, so I did not re-read the Lance format pages. |
An actor whose rules allow each remaining action only through its own slot (schema apply and the commit controls by target, a fork by target into an unprotected branch, `invoke_query` graph-wide) now fixes which slot each action's decision uses, so a swapped source and target fails. The guide's sample comes from the page's example policy, and it says what the branch-free decision covers, that rules covering every branch only together do not reach it, and that `schema_apply` on a branch other than `main` does not make schema apply possible there.
|
Optional items, addressed:
I did not add the signed-data-credential case: it needs a data-token trust fixture, and |
|
Recommendation: approve. Blocking findings: none. Optional findings: two.
Earlier findingsThe previous review is #1073 (comment) at
RFC #1017 at
|
be18e46 to
6bb1108
Compare
`graph_wide` holds an action one rule allows on every branch: a rule with no branch scope, `branch_scope: any` or `target_branch_scope: any`. Rules that cover every branch only together leave the action out, so a caller reads `actions` for a branch. The type's description, which openapi.json copies, and the policy guide now say so, and the guide names `commit show` as the scope-free read; a commit list authorizes `read` on its branch. The signed-data test now asks the access route for a credential with no policy and expects its actor with both lists empty, and checks that the route refuses forged and retired tokens like every other protected route.
The access route from ModernRelay#1073 now rides HTTP contract 0.14 with the actor header: its policy guide example sends 0.14, and its OpenAPI operation lists the contract value and the optional omnigraph-expect-actor parameter like every other protected route.
Brings in main through ModernRelay#1074: the access route from ModernRelay#1073 under HTTP contract 0.14.
Brings in main through ModernRelay#1076: the access route from ModernRelay#1073 under HTTP contract 0.14.
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 main through ModernRelay#1078: the access route from ModernRelay#1073 under HTTP contract 0.14.
Brings in main through ModernRelay#1080: the access route as ModernRelay#1073 landed, whose handler and output type this branch already carries in their evaluated form (the conflict keeps this side), HTTP contract 0.14, and `schema plan` refusing a root target.
… 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): 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
GET /graphs/{graph_id}/accessand the MCPaccesstool tell a caller what its own credential may do: its actor, the actions it holds graph-wide, and, givenbranch(andtarget_branch), the actions it holds there. Every answer comes from the sameauthorize()decision the routes make, so a client can learn its access without probing writes. It requires authentication like every graph route and no graph action;target_branchwithoutbranchis a bad request.Backing issue / RFC
Checklist
auth_policy::access_reports_the_callers_own_actions_as_routes_decide_them, MCP parity rows and tool lists, OpenAPI expected pathschangelog.d/graph-access-route.added.mdLocal verification
RUST_MIN_STACK=16777216 cargo test -p omnigraph-server(after rebasing onto main 0a62bb7) — all passcargo clippy -p omnigraph-server -p omnigraph-api-types --all-targets --locked -- -D warnings -W clippy::dbg_macro— cleancargo fmt --all --check,python3 scripts/check-docs.py— cleanNotes for reviewers
openapi.jsonis regenerated for the new path. The HTTP contract version is not bumped here; it moves once at release preparation.