2026-04-10 20:49:41 +03:00
|
|
|
[workspace]
|
|
|
|
|
resolver = "2"
|
|
|
|
|
members = [
|
|
|
|
|
"crates/omnigraph-compiler",
|
|
|
|
|
"crates/omnigraph",
|
|
|
|
|
"crates/omnigraph-cli",
|
policy: chassis core — omnigraph-policy crate + Omnigraph::enforce() (MR-722) (#102)
PR #2 of the policy chassis series (PR #1 = MR-731, merged in #101).
The structural fix that moves Cedar enforcement from HTTP-only to
engine-wide. apply_schema is the proof-of-concept writer; PR #3 fans
the enforce() call out to the remaining six (mutate_as, load,
ingest_as, branch_create_from, branch_delete, branch_merge).
## What lands
### New crate: omnigraph-policy
The 844-line policy.rs moves from `omnigraph-server` into a new
`omnigraph-policy` workspace crate so both engine and server can
depend on it. Cedar dependency moves with it. The server's policy.rs
becomes a re-export shim (`pub use omnigraph_policy::*`) so existing
`omnigraph_server::PolicyAction` etc. paths keep working — CLI and
test consumers don't have to migrate in one go.
### New trait: PolicyChecker
```rust
pub trait PolicyChecker: Send + Sync {
fn check(&self, action: PolicyAction, scope: &ResourceScope,
actor: &str) -> Result<(), PolicyError>;
}
```
`PolicyEngine` (Cedar-backed) implements it. `Omnigraph::with_policy()`
takes `Arc<dyn PolicyChecker>`. Engine tests mock the trait without
spinning up Cedar. MR-725 will extend the trait with `predicate_for()`
for query-layer pushdown — additive, no call-site changes.
### New enum: ResourceScope
Four variants — Graph, Branch, TargetBranch, BranchTransition —
mapping cleanly to today's `(branch, target_branch)` shape on
PolicyRequest via `to_branch_pair()`. Each engine writer picks the
variant that matches the existing HTTP-layer convention so engine
and HTTP evaluate the same Cedar decision.
**Invariant**: ResourceScope stays at branch granularity. Per-type
and per-row scope are MR-725's territory, not engine-layer's.
Adding Type/Row variants here creates two places per-type policy
can be evaluated, which can drift. See chassis design refinements
comment on MR-722 (2026-05-17).
### Omnigraph::with_policy() + enforce()
* New `policy: Option<Arc<dyn PolicyChecker>>` field on Omnigraph,
None by default (preserves embedded/dev no-enforcement mode).
* `with_policy(self, checker)` setter — builder-style, consumes self.
* `enforce(action, scope, actor)` — the gate. When policy is None,
no-op. When policy is Some AND actor is None, hard error — silent
bypass via "I forgot the actor" is exactly the footgun this gate
is here to prevent.
### apply_schema_as: first writer wired
* New public method `apply_schema_as(source, options, actor)` that
calls `enforce(SchemaApply, TargetBranch("main"), actor)` before
acquiring the schema-apply lock or doing any other work.
* Existing `apply_schema(source)` and `apply_schema_with_options(...)`
delegate to it with actor=None (no-actor variants).
* HTTP handler `server_schema_apply` updated to call apply_schema_as
with the resolved actor. AppState construction injects the
PolicyEngine into Omnigraph via `with_policy`. HTTP-layer
authorize_request still fires first; the engine gate is the
redundant-but-correct backstop and the only path that protects SDK
/ embedded callers. PR #3 removes the HTTP redundancy.
### OmniError::Policy
New error variant for engine-layer policy denial / evaluation
failure. ApiError::from_omni maps it to 403.
### MR-724 Admin action — Option A reservation
PolicyAction::Admin kept in the enum with a load-bearing doc
comment naming its future consumers (hot reload, audit log query,
approvals list per MR-726 / MR-732 / MR-734). No enforce(Admin, ...)
call site exists yet — the variant is reserved so the action
vocabulary is complete from chassis day one. MR-724 closes when
the first consumer surface ships.
### New SDK-side integration test
`crates/omnigraph/tests/policy_engine_chassis.rs` — four tests
covering:
* Policy denies for unauthorized actor → OmniError::Policy
* Policy permits for authorized actor → apply succeeds
* Policy installed + no actor → hard error (forget-the-actor footgun)
* No policy → no-op (embedded/dev default still works)
These exercise the engine path directly — no HTTP layer involved.
## Test results
- cargo test --workspace --locked --no-fail-fast: 851 passed, 0 failed
* 45 server tests (existing) pass
* 14 schema_apply tests (existing) pass
* 4 new chassis tests pass
* 60 OpenAPI tests pass (no HTTP API surface changes)
* No regressions across the workspace
## Architectural decisions baked in
Per MR-722 chassis design refinements comment (2026-05-17):
1. PolicyChecker is a trait, not just a concrete. Engine and server
consume the trait. MR-725 adds predicate_for() additively.
2. ResourceScope stays at branch granularity. No Type/Row variants.
3. Coarse-vs-fine framing pinned: engine-layer is action gate;
query-layer (MR-725) is predicate gate. Both backed by same Cedar
engine; non-overlapping responsibilities.
4. Admin action reserved for policy-management surfaces (MR-724
Option A).
## Pending follow-ups (PR #3+)
- Fan-out enforce() to mutate_as, load, ingest_as, branch_create_from,
branch_delete, branch_merge (PR #3).
- Remove HTTP-layer authorize_request redundancy once engine gate
covers all writers (PR #3).
- CLI policy injection into Omnigraph for non-`policy validate|test|explain`
subcommands (PR #3 or follow-up).
- MR-723 default-deny 3-state matrix (PR #4).
- MR-736 severity warn/deny (PR #5).
- AGENTS.md scope-of-enforcement rewrite once chassis fully lands.
- Coarse-vs-fine framing in docs/user/policy.md.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 00:36:36 +03:00
|
|
|
"crates/omnigraph-policy",
|
2026-04-10 20:49:41 +03:00
|
|
|
"crates/omnigraph-server",
|
|
|
|
|
]
|
|
|
|
|
default-members = [
|
|
|
|
|
"crates/omnigraph",
|
|
|
|
|
"crates/omnigraph-cli",
|
|
|
|
|
"crates/omnigraph-server",
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
[workspace.dependencies]
|
|
|
|
|
arrow-array = "57"
|
|
|
|
|
arrow-ipc = "57"
|
|
|
|
|
arrow-schema = "57"
|
|
|
|
|
arrow-select = "57"
|
|
|
|
|
arrow-cast = { version = "57", features = ["prettyprint"] }
|
|
|
|
|
arrow-ord = "57"
|
|
|
|
|
|
2026-05-01 10:42:21 +02:00
|
|
|
datafusion = { version = "52", default-features = false }
|
2026-04-10 20:49:41 +03:00
|
|
|
datafusion-physical-plan = "52"
|
|
|
|
|
datafusion-physical-expr = "52"
|
|
|
|
|
datafusion-execution = "52"
|
|
|
|
|
datafusion-common = "52"
|
|
|
|
|
datafusion-expr = "52"
|
|
|
|
|
datafusion-functions-aggregate = "52"
|
|
|
|
|
|
|
|
|
|
lance = { version = "4.0.0", default-features = false, features = ["aws"] }
|
|
|
|
|
lance-datafusion = "4.0.0"
|
|
|
|
|
lance-file = "4.0.0"
|
|
|
|
|
lance-index = "4.0.0"
|
|
|
|
|
lance-linalg = "4.0.0"
|
|
|
|
|
lance-namespace = "4.0.0"
|
|
|
|
|
lance-namespace-impls = "4.0.0"
|
|
|
|
|
lance-table = "4.0.0"
|
|
|
|
|
|
|
|
|
|
ulid = "1"
|
|
|
|
|
futures = "0.3"
|
|
|
|
|
async-trait = "0.1"
|
2026-04-25 14:22:14 +03:00
|
|
|
chrono = { version = "0.4", default-features = false, features = ["clock"] }
|
2026-04-10 20:49:41 +03:00
|
|
|
pest = "2"
|
|
|
|
|
pest_derive = "2"
|
|
|
|
|
thiserror = "2"
|
|
|
|
|
tokio = { version = "1", features = ["rt-multi-thread", "macros", "time", "net", "signal", "sync"] }
|
|
|
|
|
clap = { version = "4", features = ["derive"] }
|
|
|
|
|
serde = { version = "1", features = ["derive"] }
|
|
|
|
|
serde_json = "1"
|
|
|
|
|
serde_yaml = "0.9"
|
|
|
|
|
tracing = "0.1"
|
|
|
|
|
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
|
|
|
|
|
tower = "0.5"
|
|
|
|
|
tower-http = { version = "0.6", features = ["trace"] }
|
|
|
|
|
color-eyre = "0.6"
|
|
|
|
|
tempfile = "3"
|
|
|
|
|
ahash = "0.8"
|
2026-05-07 15:25:22 +02:00
|
|
|
arc-swap = "1"
|
2026-04-10 20:49:41 +03:00
|
|
|
base64 = "0.22"
|
|
|
|
|
ariadne = "0.4"
|
|
|
|
|
regex = "1"
|
|
|
|
|
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
|
|
|
|
|
object_store = { version = "0.12.5", default-features = false, features = ["aws"] }
|
|
|
|
|
fail = "0.5"
|
|
|
|
|
time = { version = "0.3", features = ["formatting"] }
|
|
|
|
|
axum = { version = "0.8", features = ["json", "macros"] }
|
Add OpenAPI spec generation via utoipa with /openapi.json endpoint
Integrate utoipa 5 to auto-generate an OpenAPI 3.1 spec from the existing
Axum handlers and serde types. All 16 endpoints are annotated with path
metadata, request/response schemas, security requirements, and tags. A
public /openapi.json endpoint serves the spec without requiring auth.
Includes 59 tests covering path completeness, HTTP methods, schema fields,
enum variants, security scheme, path/query parameters, request bodies,
response references, and endpoint integration.
https://claude.ai/code/session_01NfoPVx21rZUQned1f7WpXY
2026-04-11 13:11:14 +00:00
|
|
|
utoipa = { version = "5", features = ["axum_extras"] }
|
2026-04-10 20:49:41 +03:00
|
|
|
url = "2"
|
|
|
|
|
cedar-policy = "4.9"
|
|
|
|
|
sha2 = "0.10"
|
2026-04-17 21:40:51 +03:00
|
|
|
subtle = "2"
|
2026-04-10 20:49:41 +03:00
|
|
|
|
|
|
|
|
[profile.dev]
|
|
|
|
|
debug = 0
|
|
|
|
|
|
|
|
|
|
[profile.dev.package."*"]
|
|
|
|
|
opt-level = 2
|
|
|
|
|
|
|
|
|
|
[profile.release]
|
|
|
|
|
opt-level = 2
|
|
|
|
|
lto = "thin"
|
|
|
|
|
codegen-units = 16
|
|
|
|
|
strip = true
|