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.2" }
graphql-orm-ai-tool-profiles = { path = "crates/graphql-orm-ai-tool-profiles", version = "0.10.3" }
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: 15 additions & 1 deletion crates/graphql-orm-ai-tool-profiles/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,27 @@ title: "graphql-orm-ai-tool-profiles changelog"
kind: reference
status: active
owner: graphql-orm-ai-maintainers
last_reviewed: 2026-08-22
last_reviewed: 2026-09-01
review_by: 2027-02-11
supersedes: []
---

# Changelog

## [0.10.3] - 2026-09-01

### Added

- `AiError::PreTransportProviderFailed` represents a provider failure whose
adapter and call executor proved occurred before dispatch. It retains the
stable `AI_PROVIDER_FAILED` public code.

### Security

- The variant is proof-bearing and must not classify a generic provider error.
Failures after possible dispatch remain `ProviderFailed` and preserve the
uncertain-effect recovery boundary.

## [0.10.2] - 2026-08-26

### Added
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.2"
version = "0.10.3"
edition = "2024"
authors = ["Toby Martin <toby@dastari.net>"]
description = "Backend-neutral GraphQL AI tool profile compiler and manifest contracts"
Expand Down
15 changes: 14 additions & 1 deletion crates/graphql-orm-ai-tool-profiles/MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,26 @@ title: "graphql-orm-ai-tool-profiles migration guide"
kind: reference
status: active
owner: graphql-orm-ai-maintainers
last_reviewed: 2026-08-22
last_reviewed: 2026-09-01
review_by: 2027-02-11
supersedes: []
---

# Migration Guide

## 0.10.2 to 0.10.3: proof-bearing pre-dispatch provider failure

Adopt `graphql-orm-ai-tool-profiles` 0.10.3 with `graphql-orm-ai` 0.95.12 from
one reviewed full monorepo revision. Deliberately exhaustive internal matches
must handle `AiError::PreTransportProviderFailed` separately from generic
`ProviderFailed`. Only the provider-call executor may return it after the
adapter reports `RejectedBeforeDispatch` and the unstarted budget reservation
is durably released.

The new variant keeps the stable `AI_PROVIDER_FAILED` public code. There is no
schema, database, data, GraphQL SDL, manifest, capability, fingerprint,
protected-content, credential, or persistent AI schema-module migration.

## 0.10.1 to 0.10.2: bounded relationship-argument projection

Adopt `graphql-orm-ai-tool-profiles` 0.10.2 from one reviewed full monorepo
Expand Down
4 changes: 2 additions & 2 deletions crates/graphql-orm-ai-tool-profiles/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: "graphql-orm-ai-tool-profiles"
kind: reference
status: active
owner: graphql-orm-ai-maintainers
last_reviewed: 2026-08-16
last_reviewed: 2026-09-01
review_by: 2027-02-11
supersedes: []
---
Expand All @@ -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.2" }
graphql-orm-ai-tool-profiles = { git = "https://github.com/Dastari/graphql-orm.git", rev = "<reviewed-full-40-character-commit-sha>", version = "0.10.3" }
serde_json = "1"
```

Expand Down
18 changes: 18 additions & 0 deletions crates/graphql-orm-ai-tool-profiles/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@ pub enum AiError {
/// provider/tool loop must use [`Self::BudgetDenied`] instead.
#[error("AI budget denied")]
PreTransportBudgetDenied,
/// Provider execution failed before the request crossed its dispatch boundary.
///
/// This is a proof-bearing execution classification, not a generic provider
/// error. It may be returned only after the adapter reports
/// `RejectedBeforeDispatch` and the executor durably releases the unstarted
/// budget reservation. A failure after possible dispatch must use
/// [`Self::ProviderFailed`] and remain recovery-required.
#[error("AI provider operation failed before dispatch")]
PreTransportProviderFailed,
/// Input failed a public schema contract.
#[error("invalid AI input: {0}")]
InvalidInput(String),
Expand Down Expand Up @@ -86,6 +95,7 @@ impl AiError {
Self::EgressDenied => "AI_EGRESS_DENIED",
Self::BudgetDenied => "AI_BUDGET_DENIED",
Self::PreTransportBudgetDenied => "AI_BUDGET_DENIED",
Self::PreTransportProviderFailed => "AI_PROVIDER_FAILED",
Self::InvalidInput(_) => "AI_INVALID_INPUT",
Self::ReauthorizationFailed => "AI_REAUTHORIZATION_FAILED",
Self::ToolExecutionFailed => "AI_TOOL_EXECUTION_FAILED",
Expand Down Expand Up @@ -117,4 +127,12 @@ mod tests {
"AI_PROVIDER_FAILED"
);
}

#[test]
fn pre_transport_provider_failure_keeps_the_provider_failure_public_code() {
assert_eq!(
AiError::PreTransportProviderFailed.public_code(),
"AI_PROVIDER_FAILED"
);
}
}
26 changes: 26 additions & 0 deletions crates/graphql-orm-ai/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,32 @@ 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.95.12] - 2026-09-01

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

### Fixed

- The strict Codex actor now exposes an exact retained-resume readiness proof.
A host can wait for both the correlated `thread/resume` response and matching
`thread/started` notification, in either order, before preparing a turn. The
existing content-free retained-usage snapshot remains the only reviewed
fallback.
- Typed Codex failures proven to occur before `turn/start` are now classified
before dispatch. Their budget reservation is released, a retained cursor is
fenced for cleanup, and the run closes as retryable `Failed` instead of
incorrectly requiring uncertain-effect recovery.

### Security

- Only the typed newly-bound and retained pre-turn rejection variants can cross
the proof-bearing pre-dispatch path. Ordinary adapter failures and every
failure during or after `turn/start` remain possibly dispatched and continue
to require recovery.

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

## [0.95.11] - 2026-09-01

Persistent schema module: **0.64.0** (unchanged from 0.95.10).
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.95.11"
version = "0.95.12"
edition = "2024"
authors = ["Toby Martin <toby@dastari.net>"]
description = "Project-agnostic AI agent runtime for graphql-orm applications"
Expand Down
21 changes: 21 additions & 0 deletions crates/graphql-orm-ai/MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,27 @@ 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.95.11 to 0.95.12: retained-resume readiness and pre-dispatch proof

Adopt `graphql-orm-ai` 0.95.12 and `graphql-orm-ai-tool-profiles` 0.10.3 from
one reviewed full monorepo revision. A Codex process host must keep accepting
strict actor frames after `thread/resume` until
`AiCodexAppServerProtocolActor::retained_resume_ready` returns true. A
correlated response alone is not readiness: the matching `thread/started`
notification must also arrive in either order, unless the reviewed
content-free retained-usage snapshot completes the lifecycle.

Hosts may return `ProviderError::RetainedTurnRejected` only before writing the
business `turn/start` request. The Codex adapter converts that typed proof, and
the existing typed newly-bound rejection, into `RejectedBeforeDispatch`.
Reservations are released and retained cursors are fenced for cleanup. The run
closes as retryable `Failed/provider_pre_transport_failed`. Do not wrap a
write, timeout, protocol error, or other failure during or after `turn/start`;
those outcomes remain uncertain and recovery-required.

The AI schema module remains **0.64.0**. There is no database, data, GraphQL
SDL, protected-payload, backup, restore, or data backfill migration.

## 0.95.10 to 0.95.11: absence-proven descriptor replacement

Adopt `graphql-orm-ai` 0.95.11 from one reviewed full monorepo revision. No
Expand Down
12 changes: 11 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.95.11", 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.95.12", default-features = false, features = ["sqlite"] }
```

Exactly one persistence backend is required: `sqlite` (default), `postgres`,
Expand Down Expand Up @@ -160,6 +160,16 @@ under its current server-authored descriptor; the historical descriptor grants
no replacement authority. A lost rebind fence discards the fresh empty
provider session and defers safely before any business input or tool request.

A retained Codex host must not treat the `thread/resume` response as sufficient
readiness. Continue feeding the strict protocol actor until
`retained_resume_ready` proves both that response and the matching
`thread/started` notification in either order, or the reviewed content-free
usage-snapshot fallback. Only a typed rejection raised before writing
`turn/start` is safely pre-dispatch: it releases the unstarted reservation,
requires retained-cursor cleanup, and closes with the retryable
`provider_pre_transport_failed` code. Every failure during or after
`turn/start` remains uncertain.

Codex cleanup treats a successful empty `thread/delete` result as authoritative.
The provider profile ID and versioned cursor kind form its stable provider-state
namespace. A protected cursor is opened only after its authenticated binding to
Expand Down
13 changes: 9 additions & 4 deletions crates/graphql-orm-ai/docs/implementation-status.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ title: "Implementation Status"
kind: reference
status: active
owner: graphql-orm-ai-maintainers
last_reviewed: 2026-08-28
last_reviewed: 2026-09-01
review_by: 2027-02-01
supersedes: []
---

# Implementation Status

`graphql-orm-ai` is at crate version `0.95.9` with AI schema module
`0.64.0`. It uses workspace `graphql-orm` `0.27.0`, backend-neutral
`graphql-orm-ai-tool-profiles` `0.10.2`, and external `agql-auth`
`graphql-orm-ai` is at crate version `0.95.12` with AI schema module
`0.64.0`. It uses workspace `graphql-orm` `0.30.0`, backend-neutral
`graphql-orm-ai-tool-profiles` `0.10.3`, and external `agql-auth`
`0.19.0` at `1d2e9fe2e1576105212a7b340a11abf8cad0382d`.

Completed stateless local-provider turns can carry a proof-bearing contained
Expand Down Expand Up @@ -56,6 +56,11 @@ verification evidence belongs in the focused guides.
response-authoritative deletion, empty reasoning lifecycle, and retained
cumulative-usage replay are admitted only through typed content-free
controls that cannot become model output or current-run usage. Retained
resume readiness requires its correlated response and matching started
notification in either order, or the reviewed content-free usage-snapshot
fallback. A typed failure proven before business `turn/start` releases its
reservation, fences cleanup, and remains safely retryable; later failures
retain uncertain-effect recovery. Retained
developer instructions are compile-time static, registration-fingerprinted,
and distinct from request input. Reasoning effort is a separately typed,
model-profile-validated turn field frozen into each retained-session
Expand Down
1 change: 1 addition & 0 deletions crates/graphql-orm-ai/src/orm_background.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4039,6 +4039,7 @@ fn classify_background_retrieval_error(
| ProviderError::Rejected
| ProviderError::StatelessNativeItemRejected
| ProviderError::NewlyBoundTurnRejected(_)
| ProviderError::RetainedTurnRejected(_)
| ProviderError::Cancelled
| ProviderError::Classified(_) => {
AiOpenAiBackgroundRetrievalAttempt::RecoveryRequired(background_retrieval_failure(
Expand Down
72 changes: 69 additions & 3 deletions crates/graphql-orm-ai/src/orm_coordinator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1722,6 +1722,11 @@ impl AiReadOnlyAgentCoordinator {
.finish_failed(&lease, &guard, "provider_budget_denied")
.await;
}
Err(ProviderTurnFailure::PreTransportProvider) => {
return self
.finish_failed(&lease, &guard, "provider_pre_transport_failed")
.await;
}
Err(ProviderTurnFailure::StatelessNativeItemRejected) => {
return self
.finish_failed(&lease, &guard, "provider_native_item_rejected")
Expand Down Expand Up @@ -2514,6 +2519,7 @@ impl AiReadOnlyAgentCoordinator {
enum ProviderTurnFailure {
Provider,
BudgetDenied,
PreTransportProvider,
StatelessNativeItemRejected,
Deferred,
LeaseLost(AiError),
Expand All @@ -2525,16 +2531,18 @@ enum ProviderTurnFailure {
/// Separates proof-bearing refusals from an uncertain provider turn.
///
/// The budget reservation is taken before the transport boundary and inside
/// the same call that later dispatches. A denial therefore proves that no
/// bytes crossed the provider boundary, that no provider turn was consumed,
/// and that the atomic reservation transaction left nothing held. A stateless
/// the same call that later dispatches. A budget denial or a typed adapter
/// pre-dispatch rejection therefore proves that no bytes crossed the provider
/// boundary, that no provider turn was consumed, and that the atomic
/// reservation transaction left nothing held. A stateless
/// native-item refusal is separately proof-bearing only after the
/// executor has committed authoritative usage and proven that no answer or
/// admitted host tool effect exists. Every other executor error keeps the
/// fail-closed uncertain classification.
const fn classify_provider_turn_failure(error: &AiError) -> ProviderTurnFailure {
match error {
AiError::PreTransportBudgetDenied => ProviderTurnFailure::BudgetDenied,
AiError::PreTransportProviderFailed => ProviderTurnFailure::PreTransportProvider,
AiError::StatelessNativeItemRejected => ProviderTurnFailure::StatelessNativeItemRejected,
_ => ProviderTurnFailure::Provider,
}
Expand Down Expand Up @@ -3913,6 +3921,64 @@ mod tests {
);
}

#[tokio::test]
async fn pre_transport_provider_rejection_fails_cleanly_and_is_retryable() {
let lease = AiRunLease::test_running(principal_reference());
let run = Arc::new(TestRunControl::new());
let provider = Arc::new(TestProviderExecutor {
responses: Mutex::new(VecDeque::from([Err(AiError::PreTransportProviderFailed)])),
delay: None,
});
let planner = Arc::new(TestChatPlanner {
scope: test_scope(),
continuation_count: AtomicUsize::new(0),
});
let forbidden = Arc::new(ChatForbiddenBoundaries::default());
let coordinator = AiReadOnlyAgentCoordinator::new(
run.clone(),
provider.clone(),
forbidden.clone(),
Arc::new(TestOutputWriter),
forbidden.clone(),
Arc::new(TestCheckpointWriter),
Arc::new(TestRuleResolver),
planner,
limits(50),
);

let outcome = coordinator
.execute_claimed(&lease)
.await
.expect("a proven pre-transport rejection is a clean terminal failure");

assert_eq!(
outcome,
Failed {
provider_turns: 0,
total_tool_calls: 0,
}
);
assert_eq!(run.final_states(), vec![AiRunState::Failed]);
assert_eq!(
run.final_codes(),
vec!["provider_pre_transport_failed".to_owned()]
);
assert!(run.scheduled_retry_codes().is_empty());
assert_eq!(provider.remaining_responses(), 0);
assert_eq!(forbidden.tool_calls.load(Ordering::SeqCst), 0);
assert_eq!(forbidden.provider_checkpoints.load(Ordering::SeqCst), 0);
assert_eq!(
crate::classify_run_retry(
crate::AiRunRetryEvidence {
terminal: crate::AiRunTerminalEvent::Failed,
produced_assistant_output: false,
},
Some("provider_pre_transport_failed"),
),
crate::AiRunRetryAdmission::Allowed
);
}

#[tokio::test]
async fn metered_stateless_native_item_refusal_is_failed_and_retryable() {
let lease = AiRunLease::test_running(principal_reference());
Expand Down
Loading