diff --git a/Cargo.lock b/Cargo.lock index 83a670e..4d866e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3104,7 +3104,7 @@ dependencies = [ [[package]] name = "graphql-orm-ai" -version = "0.97.0" +version = "0.97.1" dependencies = [ "agql-auth", "async-graphql", diff --git a/crates/graphql-orm-ai/CHANGELOG.md b/crates/graphql-orm-ai/CHANGELOG.md index 3b2d95d..9b0843c 100644 --- a/crates/graphql-orm-ai/CHANGELOG.md +++ b/crates/graphql-orm-ai/CHANGELOG.md @@ -18,6 +18,22 @@ checkpoint facts. For the current workspace baseline and active gates, use the [implementation status](docs/implementation-status.md) and the central [AI production-readiness plan](../../docs/plans/active/ai-production-readiness/README.md). +## [0.97.1] - 2026-09-04 + +Persistent schema module: **0.64.0** (unchanged from 0.97.0). + +### Fixed + +- Remote GraphQL tool authorization failures now remain `Forbidden`, allowing + the existing safe application-tool boundary to report the non-retryable + `authorization_denied` result instead of mislabelling the failure as resolver + validation. +- Principal reauthorization failures now remain `ReauthorizationFailed` + rather than being collapsed into a resolver failure. + +There is no database, data, table, column, index, constraint, backfill, +protected-payload, GraphQL SDL, backup, or restore migration. + ## [0.97.0] - 2026-09-03 Persistent schema module: **0.64.0** (unchanged from 0.96.2). diff --git a/crates/graphql-orm-ai/Cargo.toml b/crates/graphql-orm-ai/Cargo.toml index 8a3ddbd..fc58c6f 100644 --- a/crates/graphql-orm-ai/Cargo.toml +++ b/crates/graphql-orm-ai/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "graphql-orm-ai" -version = "0.97.0" +version = "0.97.1" edition = "2024" authors = ["Toby Martin "] description = "Project-agnostic AI agent runtime for graphql-orm applications" diff --git a/crates/graphql-orm-ai/MIGRATION.md b/crates/graphql-orm-ai/MIGRATION.md index 1b13ddf..6df57f0 100644 --- a/crates/graphql-orm-ai/MIGRATION.md +++ b/crates/graphql-orm-ai/MIGRATION.md @@ -19,6 +19,21 @@ they describe. For the current workspace baseline and active delivery gates, use [implementation status](docs/implementation-status.md) and the central [AI production-readiness plan](../../docs/plans/active/ai-production-readiness/README.md). +## 0.97.0 to 0.97.1: preserve remote tool authorization failures + +Adopt `graphql-orm-ai` 0.97.1 from one reviewed full monorepo revision. Hosts +do not need to change their trait implementations. A +`ToolExecutionError::Authorization` now becomes `AiError::Forbidden`, and +`ToolExecutionError::Reauthorization` becomes +`AiError::ReauthorizationFailed`. The existing application-tool failure +classifier therefore exposes an authorization denial as the content-free, +non-retryable `authorization_denied` envelope instead of +`resolver_validation_failed`. Reauthorization failures remain outside that +safe retry envelope. + +The AI schema module remains **0.64.0**. There is no database, data, GraphQL +SDL, protected-payload, backup, restore, or data backfill migration. + ## 0.96.2 to 0.97.0: recoverable application-tool result budgets Adopt `graphql-orm-ai` 0.97.0 and `graphql-orm-ai-tool-profiles` 0.11.0 from diff --git a/crates/graphql-orm-ai/README.md b/crates/graphql-orm-ai/README.md index ed1dd63..7e1af99 100644 --- a/crates/graphql-orm-ai/README.md +++ b/crates/graphql-orm-ai/README.md @@ -28,7 +28,7 @@ for AI, ORM, storage, backup, and tool-profile packages: ```toml [dependencies] -graphql-orm-ai = { git = "https://github.com/Dastari/graphql-orm.git", rev = "", version = "0.97.0", default-features = false, features = ["sqlite"] } +graphql-orm-ai = { git = "https://github.com/Dastari/graphql-orm.git", rev = "", version = "0.97.1", default-features = false, features = ["sqlite"] } ``` Exactly one persistence backend is required: `sqlite` (default), `postgres`, diff --git a/crates/graphql-orm-ai/docs/implementation-status.md b/crates/graphql-orm-ai/docs/implementation-status.md index 79d142a..a663627 100644 --- a/crates/graphql-orm-ai/docs/implementation-status.md +++ b/crates/graphql-orm-ai/docs/implementation-status.md @@ -10,7 +10,7 @@ supersedes: [] # Implementation Status -`graphql-orm-ai` is at crate version `0.97.0` with AI schema module +`graphql-orm-ai` is at crate version `0.97.1` with AI schema module `0.64.0`. It uses workspace `graphql-orm` `0.30.0`, backend-neutral `graphql-orm-ai-tool-profiles` `0.11.0`, and external `agql-auth` `0.19.0` at `1d2e9fe2e1576105212a7b340a11abf8cad0382d`. diff --git a/crates/graphql-orm-ai/src/runtime.rs b/crates/graphql-orm-ai/src/runtime.rs index 10fd474..d5ee5e7 100644 --- a/crates/graphql-orm-ai/src/runtime.rs +++ b/crates/graphql-orm-ai/src/runtime.rs @@ -836,6 +836,8 @@ impl AiRuntime { fn map_tool_execution_error(error: ToolExecutionError) -> AiError { match error { + ToolExecutionError::Reauthorization => AiError::ReauthorizationFailed, + ToolExecutionError::Authorization => AiError::Forbidden, ToolExecutionError::ResultBudgetExceeded => AiError::ResultBudgetExceeded, _ => AiError::ToolExecutionFailed, } diff --git a/crates/graphql-orm-ai/tests/runtime_contracts.rs b/crates/graphql-orm-ai/tests/runtime_contracts.rs index 9b09ca0..cf14f23 100644 --- a/crates/graphql-orm-ai/tests/runtime_contracts.rs +++ b/crates/graphql-orm-ai/tests/runtime_contracts.rs @@ -68,6 +68,22 @@ impl AuthenticatedGraphqlExecutor for Executor { context: GraphqlRequestContext, request: ToolGraphqlRequest, ) -> Result { + if request + .variables + .get("rejectAuthorization") + .and_then(serde_json::Value::as_bool) + .unwrap_or(false) + { + return Err(ToolExecutionError::Authorization); + } + if request + .variables + .get("rejectReauthorization") + .and_then(serde_json::Value::as_bool) + .unwrap_or(false) + { + return Err(ToolExecutionError::Reauthorization); + } if request .variables .get("rejectOversizedResult") @@ -224,7 +240,9 @@ fn runtime() -> AiRuntime { "properties": { "emitUnknown": { "type": "boolean" }, "emitOversizedResult": { "type": "boolean" }, + "rejectAuthorization": { "type": "boolean" }, "rejectOversizedResult": { "type": "boolean" }, + "rejectReauthorization": { "type": "boolean" }, "incompleteAuthorization": { "type": "boolean" } }, "additionalProperties": false @@ -409,7 +427,7 @@ async fn runtime_rejects_invalid_arguments_and_non_disclosed_resolver_fields() { current_request(&runtime, json!({"incompleteAuthorization": true})), ) .await, - Err(AiError::ToolExecutionFailed) + Err(AiError::Forbidden) )); let mut stale_schema = current_request(&runtime, json!({})); @@ -446,6 +464,35 @@ async fn runtime_preserves_result_budget_failures_from_transport_and_descriptor_ } } +#[tokio::test] +async fn runtime_preserves_authorization_and_reauthorization_failures() { + let runtime = runtime(); + open_runtime(&runtime); + let principal_reference = principal(&["records:read"]).reference(); + let tool_id = AiToolId::parse("records.current").expect("tool ID"); + + assert!(matches!( + runtime + .execute_tool( + &principal_reference, + &tool_id, + current_request(&runtime, json!({"rejectAuthorization": true})), + ) + .await, + Err(AiError::Forbidden) + )); + assert!(matches!( + runtime + .execute_tool( + &principal_reference, + &tool_id, + current_request(&runtime, json!({"rejectReauthorization": true})), + ) + .await, + Err(AiError::ReauthorizationFailed) + )); +} + #[test] fn runtime_builder_requires_every_security_boundary() { let result = AiRuntime::builder().build(); diff --git a/crates/graphql-orm/tests/fixtures/backend-coexistence/Cargo.lock b/crates/graphql-orm/tests/fixtures/backend-coexistence/Cargo.lock index 48be612..327e864 100644 --- a/crates/graphql-orm/tests/fixtures/backend-coexistence/Cargo.lock +++ b/crates/graphql-orm/tests/fixtures/backend-coexistence/Cargo.lock @@ -1329,7 +1329,7 @@ dependencies = [ [[package]] name = "graphql-orm-ai" -version = "0.97.0" +version = "0.97.1" dependencies = [ "agql-auth", "async-graphql", diff --git a/docs/reference/workspace-packages.md b/docs/reference/workspace-packages.md index 9aaacf7..59ae68c 100644 --- a/docs/reference/workspace-packages.md +++ b/docs/reference/workspace-packages.md @@ -19,7 +19,7 @@ changes. | Package | Version | Path | Default features | Direct internal dependencies | | --- | --- | --- | --- | --- | | `graphql-orm` | `0.30.0` | `crates/graphql-orm` | `sqlite` | `graphql-orm-macros`, `graphql-orm-operation-catalog`, `graphql-orm-router-protocol` (dev-only) | -| `graphql-orm-ai` | `0.97.0` | `crates/graphql-orm-ai` | `sqlite` | `graphql-orm`, `graphql-orm-ai-tool-profiles`, `graphql-orm-storage` | +| `graphql-orm-ai` | `0.97.1` | `crates/graphql-orm-ai` | `sqlite` | `graphql-orm`, `graphql-orm-ai-tool-profiles`, `graphql-orm-storage` | | `graphql-orm-ai-tool-profiles` | `0.11.0` | `crates/graphql-orm-ai-tool-profiles` | none | `graphql-orm-operation-catalog`, `graphql-orm-router-protocol` (dev-only) | | `graphql-orm-backup` | `0.7.2` | `crates/graphql-orm-backup` | `local` | `graphql-orm` (optional), `graphql-orm-storage` | | `graphql-orm-macros` | `0.30.0` | `crates/graphql-orm-macros` | `sqlite` | none |