Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ futures = "0.3"
getrandom = "0.3"
graphql-composition = "=0.12.2"
graphql-orm = { path = "crates/graphql-orm", version = "0.30.0", default-features = false }
graphql-orm-ai-tool-profiles = { path = "crates/graphql-orm-ai-tool-profiles", version = "0.10.4" }
graphql-orm-ai-tool-profiles = { path = "crates/graphql-orm-ai-tool-profiles", version = "0.11.0" }
graphql-orm-backup = { path = "crates/graphql-orm-backup", version = "0.7.2", default-features = false }
graphql-orm-operation-catalog = { path = "crates/graphql-orm-operation-catalog", version = "0.4.0" }
graphql-orm-router-protocol = { path = "crates/graphql-orm-router-protocol", version = "0.2.1" }
Expand Down
16 changes: 16 additions & 0 deletions crates/graphql-orm-ai-tool-profiles/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,22 @@ supersedes: []

# Changelog

## [0.11.0] - 2026-09-03

### Added

- `ToolExecutionError::ResultBudgetExceeded` lets a bounded host transport
distinguish an oversized GraphQL response from a resolver or transport
failure.
- `AiError::ResultBudgetExceeded` carries the stable
`AI_RESULT_BUDGET_EXCEEDED` public code.

### Security

- The new variants carry no response content, transport destination, schema,
policy, credential, or resolver detail. They are size proofs only and do
not weaken the descriptor's result-byte or disclosure checks.

## [0.10.4] - 2026-09-02

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion crates/graphql-orm-ai-tool-profiles/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "graphql-orm-ai-tool-profiles"
version = "0.10.4"
version = "0.11.0"
edition = "2024"
authors = ["Toby Martin <toby@dastari.net>"]
description = "Backend-neutral GraphQL AI tool profile compiler and manifest contracts"
Expand Down
14 changes: 14 additions & 0 deletions crates/graphql-orm-ai-tool-profiles/MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@ supersedes: []

# Migration Guide

## 0.10.4 to 0.11.0: typed result-budget failures

Adopt `graphql-orm-ai-tool-profiles` 0.11.0 with `graphql-orm-ai` 0.97.0 from
one reviewed full monorepo revision. Host transports should return
`ToolExecutionError::ResultBudgetExceeded` only when a bounded response is
refused because its byte ceiling was exceeded. Deliberately exhaustive
in-crate matches must handle the new non-exhaustive error variants.

The runtime converts that proof, and its own descriptor result-byte overflow,
to `AiError::ResultBudgetExceeded`. The stable public code is
`AI_RESULT_BUDGET_EXCEEDED`. There is no schema, database, data, GraphQL SDL,
manifest, capability, fingerprint, protected-content, credential, backup,
restore, or persistent AI schema-module migration.

## 0.10.3 to 0.10.4: resolver-first semantic discovery ranking

Adopt `graphql-orm-ai-tool-profiles` 0.10.4 from one reviewed full monorepo
Expand Down
2 changes: 1 addition & 1 deletion crates/graphql-orm-ai-tool-profiles/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ are separate runtime decisions and must remain default-deny.

```toml
[dependencies]
graphql-orm-ai-tool-profiles = { git = "https://github.com/Dastari/graphql-orm.git", rev = "<reviewed-full-40-character-commit-sha>", version = "0.10.4" }
graphql-orm-ai-tool-profiles = { git = "https://github.com/Dastari/graphql-orm.git", rev = "<reviewed-full-40-character-commit-sha>", version = "0.11.0" }
serde_json = "1"
```

Expand Down
12 changes: 12 additions & 0 deletions crates/graphql-orm-ai-tool-profiles/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ pub enum AiError {
/// Input failed a public schema contract.
#[error("invalid AI input: {0}")]
InvalidInput(String),
/// A tool result exceeded its reviewed byte or record budget.
#[error("AI tool result exceeded its reviewed budget")]
ResultBudgetExceeded,
/// Authentication dependency failed closed.
#[error("AI principal reauthorization failed")]
ReauthorizationFailed,
Expand Down Expand Up @@ -97,6 +100,7 @@ impl AiError {
Self::PreTransportBudgetDenied => "AI_BUDGET_DENIED",
Self::PreTransportProviderFailed => "AI_PROVIDER_FAILED",
Self::InvalidInput(_) => "AI_INVALID_INPUT",
Self::ResultBudgetExceeded => "AI_RESULT_BUDGET_EXCEEDED",
Self::ReauthorizationFailed => "AI_REAUTHORIZATION_FAILED",
Self::ToolExecutionFailed => "AI_TOOL_EXECUTION_FAILED",
Self::ProviderFailed => "AI_PROVIDER_FAILED",
Expand Down Expand Up @@ -135,4 +139,12 @@ mod tests {
"AI_PROVIDER_FAILED"
);
}

#[test]
fn result_budget_exceeded_has_a_distinct_public_code() {
assert_eq!(
AiError::ResultBudgetExceeded.public_code(),
"AI_RESULT_BUDGET_EXCEEDED"
);
}
}
3 changes: 3 additions & 0 deletions crates/graphql-orm-ai-tool-profiles/src/execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -562,4 +562,7 @@ pub enum ToolExecutionError {
/// Host execution failed safely.
#[error("tool GraphQL execution failed")]
Execution,
/// The host transport refused a response above its reviewed byte ceiling.
#[error("tool GraphQL result exceeded its reviewed budget")]
ResultBudgetExceeded,
}
20 changes: 20 additions & 0 deletions crates/graphql-orm-ai/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,26 @@ 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.0] - 2026-09-03

Persistent schema module: **0.64.0** (unchanged from 0.96.2).

### Fixed

- A host transport's typed oversized-response refusal and the runtime's own
descriptor byte-bound rejection now remain a distinct
`result_budget_exceeded` application-tool failure instead of being reported
as resolver validation failure.

### Security

- Oversized response bodies remain undisclosed. The typed result is
content-free and retryable, allowing a provider to narrow or paginate the
request without treating the resolver contract as invalid.

There is no database, data, table, column, index, constraint, backfill,
protected-payload, GraphQL SDL, backup, or restore migration.

## [0.96.2] - 2026-09-02

Persistent schema module: **0.64.0** (unchanged from 0.96.1).
Expand Down
2 changes: 1 addition & 1 deletion crates/graphql-orm-ai/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "graphql-orm-ai"
version = "0.96.2"
version = "0.97.0"
edition = "2024"
authors = ["Toby Martin <toby@dastari.net>"]
description = "Project-agnostic AI agent runtime for graphql-orm applications"
Expand Down
17 changes: 17 additions & 0 deletions crates/graphql-orm-ai/MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,23 @@ 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.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
one reviewed full monorepo revision. Bounded host transports should return
`ToolExecutionError::ResultBudgetExceeded` when they refuse an oversized
GraphQL response. The runtime preserves that proof as
`AiError::ResultBudgetExceeded`; application-tool execution emits the
content-free, retryable `result_budget_exceeded` failure envelope so the
provider can narrow or paginate its next request.

The same typed failure is returned when a successfully decoded result exceeds
the registered descriptor's `maximum_result_bytes`. Other transport,
resolver, disclosure, and authorization failures retain their prior
classification. 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.1 to 0.96.2: schema-aligned fixed-broker execution wrappers

Adopt `graphql-orm-ai` 0.96.2 from one reviewed full monorepo revision. No host
Expand Down
9 changes: 8 additions & 1 deletion crates/graphql-orm-ai/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "<reviewed-full-40-character-commit-sha>", version = "0.96.2", default-features = false, features = ["sqlite"] }
graphql-orm-ai = { git = "https://github.com/Dastari/graphql-orm.git", rev = "<reviewed-full-40-character-commit-sha>", version = "0.97.0", default-features = false, features = ["sqlite"] }
```

Exactly one persistence backend is required: `sqlite` (default), `postgres`,
Expand Down Expand Up @@ -118,6 +118,13 @@ and total-record bounds and rejects response fields outside the selected
disclosure shape. Total records include sibling and nested relationship
expansion, not merely the largest returned list.

A bounded host transport may reject an oversized response before decoding it
with `ToolExecutionError::ResultBudgetExceeded`. The runtime preserves that
proof, and its own descriptor-byte rejection, as the content-free retryable
`result_budget_exceeded` application-tool outcome. Providers can then narrow
or paginate the request without receiving response content or mistaking a
size limit for resolver validation failure.

Private remote execution carries a crate-authored
`AiRemoteGraphqlCapabilityBinding` to the deployment issuer. Static reads bind
their exact registered descriptor; generated reads additionally bind the
Expand Down
4 changes: 2 additions & 2 deletions crates/graphql-orm-ai/docs/implementation-status.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ supersedes: []

# Implementation Status

`graphql-orm-ai` is at crate version `0.96.2` with AI schema module
`graphql-orm-ai` is at crate version `0.97.0` with AI schema module
`0.64.0`. It uses workspace `graphql-orm` `0.30.0`, backend-neutral
`graphql-orm-ai-tool-profiles` `0.10.4`, and external `agql-auth`
`graphql-orm-ai-tool-profiles` `0.11.0`, and external `agql-auth`
`0.19.0` at `1d2e9fe2e1576105212a7b340a11abf8cad0382d`.

Completed stateless local-provider turns can carry a proof-bearing contained
Expand Down
3 changes: 3 additions & 0 deletions crates/graphql-orm-ai/docs/remote-graphql-execution.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ It must:
- propagate correlation, causation, actor/mechanism, and ordinary application
audit metadata;
- apply bounded response size/time limits before returning; and
- return `ToolExecutionError::ResultBudgetExceeded` only when the response is
refused for crossing that byte ceiling, without retaining or disclosing its
content; and
- ensure `PrivateDirect` authorization is never broader than the equivalent
`PrivateRouted` path.

Expand Down
7 changes: 6 additions & 1 deletion crates/graphql-orm-ai/src/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2309,7 +2309,7 @@ pub enum AiApplicationToolFailureCode {
SelectionTooLarge,
/// A selected relationship path exceeds the configured depth.
RelationshipDepthExceeded,
/// The complete selected result exceeds the aggregate record budget.
/// The complete selected result exceeds its byte or aggregate record budget.
ResultBudgetExceeded,
/// The loaded schema/catalogue/target/capability binding is stale.
CapabilityStale,
Expand Down Expand Up @@ -2412,6 +2412,7 @@ pub fn classify_safe_application_tool_error(
Some(AiApplicationToolFailureCode::CapabilityStale)
}
AiError::InvalidInput(_) => Some(AiApplicationToolFailureCode::InvalidArguments),
AiError::ResultBudgetExceeded => Some(AiApplicationToolFailureCode::ResultBudgetExceeded),
AiError::Forbidden => Some(AiApplicationToolFailureCode::AuthorizationDenied),
AiError::InvalidConfiguration(_) => Some(AiApplicationToolFailureCode::ToolUnavailable),
AiError::NotFound => Some(AiApplicationToolFailureCode::NotFound),
Expand Down Expand Up @@ -3343,6 +3344,10 @@ mod safe_failure_tests {
assert_eq!(encoded["code"], "authorization_denied");
assert_eq!(encoded["retryable"], false);
assert!(!format!("{encoded}").contains("policy"));
assert_eq!(
classify_safe_application_tool_error(&AiError::ResultBudgetExceeded),
Some(AiApplicationToolFailureCode::ResultBudgetExceeded)
);
assert!(classify_safe_application_tool_error(&AiError::Conflict).is_none());
assert!(classify_safe_application_tool_error(&AiError::PersistenceFailed).is_none());
}
Expand Down
25 changes: 16 additions & 9 deletions crates/graphql-orm-ai/src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ use crate::{
GraphqlInvocationContext, GraphqlRequestContextFactory, ModelRequest,
ProviderBackgroundBinding, ProviderBackgroundObservation, ProviderBackgroundRetrievalBinding,
ProviderBackgroundRetrievalContext, ProviderBackgroundSubmission, ProviderError,
ProviderEventStream, ProviderKind, ProviderRequestContext, ToolGraphqlRequest,
ToolGraphqlResponse, ToolMaturity,
ProviderEventStream, ProviderKind, ProviderRequestContext, ToolExecutionError,
ToolGraphqlRequest, ToolGraphqlResponse, ToolMaturity,
};
use graphql_orm::graphql::orm::{AiMutationExecutionPolicy, OrmSchemaModule, SchemaModuleCatalog};

Expand Down Expand Up @@ -365,7 +365,7 @@ impl AiRuntime {
.tool_bridge
.execute(principal_reference, descriptor, request)
.await
.map_err(|_| AiError::ToolExecutionFailed)?;
.map_err(Self::map_tool_execution_error)?;
self.finish_tool_execution(descriptor, disclosure_schema, response, authorization)
}

Expand Down Expand Up @@ -431,7 +431,7 @@ impl AiRuntime {
request,
)
.await
.map_err(|_| AiError::ToolExecutionFailed)?;
.map_err(Self::map_tool_execution_error)?;
self.finish_tool_execution(&descriptor, &disclosure_schema, response, authorization)
}

Expand Down Expand Up @@ -585,7 +585,7 @@ impl AiRuntime {
.map_err(|_| AiError::ToolExecutionFailed)?
.len() as u64;
if response_bytes > descriptor.maximum_result_bytes {
return Err(AiError::ToolExecutionFailed);
return Err(AiError::ResultBudgetExceeded);
}
let disclosure = disclosure_schema
.evaluate_graphql_with_record_limit(&response.data, descriptor.maximum_result_records)
Expand Down Expand Up @@ -629,7 +629,7 @@ impl AiRuntime {
prepared.request,
)
.await
.map_err(|_| AiError::ToolExecutionFailed)?;
.map_err(Self::map_tool_execution_error)?;
self.finish_tool_execution(
&prepared.descriptor,
&prepared.disclosure_schema,
Expand Down Expand Up @@ -674,7 +674,7 @@ impl AiRuntime {
&binding.authorization_state_digest,
)
.await
.map_err(|_| AiError::ToolExecutionFailed)?;
.map_err(Self::map_tool_execution_error)?;
self.finish_tool_execution(
&prepared.descriptor,
&prepared.disclosure_schema,
Expand Down Expand Up @@ -786,7 +786,7 @@ impl AiRuntime {
&binding.authorization_state_digest,
)
.await
.map_err(|_| AiError::ToolExecutionFailed)?;
.map_err(Self::map_tool_execution_error)?;
self.finish_tool_execution(descriptor, disclosure_schema, response, authorization)
}

Expand Down Expand Up @@ -820,7 +820,7 @@ impl AiRuntime {
.map_err(|_| AiError::ToolExecutionFailed)?
.len() as u64;
if response_bytes > descriptor.maximum_result_bytes {
return Err(AiError::ToolExecutionFailed);
return Err(AiError::ResultBudgetExceeded);
}
let disclosure = disclosure_schema
.evaluate_graphql_with_record_limit(&response.data, descriptor.maximum_result_records)
Expand All @@ -834,6 +834,13 @@ impl AiRuntime {
})
}

fn map_tool_execution_error(error: ToolExecutionError) -> AiError {
match error {
ToolExecutionError::ResultBudgetExceeded => AiError::ResultBudgetExceeded,
_ => AiError::ToolExecutionFailed,
}
}

/// Calls a registered provider only after start readiness and exact egress
/// authorization.
pub async fn stream_provider(
Expand Down
Loading