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
7 changes: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@ jobs:
run: cargo test --manifest-path crates/graphql-orm/tests/fixtures/repository-aggregate-consumer/Cargo.toml --locked --no-default-features --features postgres --test runtime_strings -- --ignored --test-threads=1
- name: Execute the private runtime string consumer on owned SQLite
run: cargo run --manifest-path crates/graphql-orm/tests/fixtures/repository-aggregate-consumer/Cargo.toml --locked --no-default-features --features sqlite --example runtime_strings
- name: Execute opt-in runtime GraphQL reads and mutations on owned backends
env:
CARGO_BUILD_JOBS: "2"
run: |
cargo test -p graphql-orm --locked --no-default-features --features sqlite,runtime-graphql --lib --test runtime_graphql --test runtime_mutations --test runtime_queries --test runtime_relations -- --test-threads=1
cargo test -p graphql-orm --locked --no-default-features --features postgres,runtime-graphql --lib --test runtime_graphql --test runtime_mutations -- --test-threads=1
cargo test -p graphql-orm --locked --no-default-features --features mssql,runtime-graphql --test runtime_graphql_unsupported
- name: Execute owned runtime migration preservation and compatibility
run: |
cargo test -p graphql-orm --locked --no-default-features --features postgres --test runtime_mutations --test runtime_migrations --test runtime_schema_ir --test runtime_migration_memory --test migration_planner --test migration_apply --test legacy_migration_history -- --test-threads=1
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,13 @@ jobs:
with:
components: clippy
- uses: Swatinem/rust-cache@v2
- name: Execute opt-in runtime GraphQL reads and mutations on owned backends
env:
CARGO_BUILD_JOBS: "2"
run: |
cargo test -p graphql-orm --locked --no-default-features --features sqlite,runtime-graphql --lib --test runtime_graphql --test runtime_mutations --test runtime_queries --test runtime_relations -- --test-threads=1
cargo test -p graphql-orm --locked --no-default-features --features postgres,runtime-graphql --lib --test runtime_graphql --test runtime_mutations -- --test-threads=1
cargo test -p graphql-orm --locked --no-default-features --features mssql,runtime-graphql --test runtime_graphql_unsupported
- name: Verify ORM backend lanes
run: |
cargo test -p graphql-orm --locked
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ for historical context.

## 0.43.0 (unreleased)

- Reconcile accepted runtime strings/binding and A–D source at aligned 0.43.0.
Retain immutable reviewed predecessors and execute PostgreSQL prefix/suffix
CAS, SQLite empty suffixes and opt-in GraphQL conformance in owned backend lanes.

- Add opt-in typed runtime GraphQL mutations delegating to guarded B transactions,
with atomic host journals and independently authorized selected outputs.
- Add an object-safe, default-deny post-commit record-release callback, explicit
Expand Down
5 changes: 5 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ neither the runtime nor macros crate is published to crates.io.

## 0.42.0 to 0.43.0: opt-in runtime GraphQL mutations

The combined 0.43.0 candidate contains the reviewed independent string-binding
fix and runtime slices A–D. Intermediate unreleased version entries below record
reviewed branch contracts; they are not immutable published release identities.
Owner review/merge and an exact-source workspace release remain adoption gates.

No data migration or static API/SDL change. Mutation registration requires an
explicit options builder, checked mutation root, certified environment and host
write authority. Existing read authorities remain source-compatible; releasing
Expand Down
138 changes: 133 additions & 5 deletions crates/graphql-orm/tests/runtime_mutations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1225,8 +1225,7 @@ async fn host_policy_only_revision_changes_reject_unchanged_schema_requests() {
f.finish().await;
}

// PostgreSQL executes only rejection cases until the reviewed #112/A/B reconciliation.
// The renderer's native-slot accounting is independently covered by unit tests.
// Combined #112/A/B execution checks emitted slots at and below both backend budgets.
#[tokio::test]
async fn emitted_suffix_bind_budgets_precede_target_reads_for_cas_and_authority() {
let f = Fixture::new().await;
Expand Down Expand Up @@ -1313,9 +1312,6 @@ async fn emitted_suffix_bind_budgets_precede_target_reads_for_cas_and_authority(
postgres_slots
};
for budget in [required - 1, required, required + 1] {
if Backend::DIALECT == DatabaseBackend::Postgres && budget >= required {
continue;
}
let id = (case * 3 + budget + 2 - required) as i64;
f.run(f.create(id), f.authority()).await.unwrap();
let request = f
Expand Down Expand Up @@ -1368,6 +1364,138 @@ async fn emitted_suffix_bind_budgets_precede_target_reads_for_cas_and_authority(
f.finish().await;
}

#[tokio::test]
async fn reconciled_prefix_suffix_cas_preserves_unicode_empty_and_bound_values() {
let f = Fixture::new().await;
let samples = ["alpha", "ALPHA", "πfoo", "πfoo%", "suffix", "Ω", ""];
for (case, (operator, operand, matched)) in [
(RuntimeScalarOperator::StartsWith, "π", vec![2, 3]),
(RuntimeScalarOperator::EndsWith, "foo", vec![2]),
(RuntimeScalarOperator::EndsWith, "", (0..7).collect()),
(RuntimeScalarOperator::StartsWith, "", (0..7).collect()),
(RuntimeScalarOperator::StartsWith, "AL", vec![1]),
(RuntimeScalarOperator::EndsWith, "%", vec![3]),
]
.into_iter()
.enumerate()
{
let expected = f
.schema()
.runtime_compare(
&f.collection(),
&f.field("label"),
operator,
RuntimeValue::String(operand.into()),
Default::default(),
)
.unwrap();
// SQL-renderable authority uses another repeated native value as well as
// the tenant restriction; no caller value is interpolated into SQL.
let tenant_suffix = f
.schema()
.runtime_compare(
&f.collection(),
&f.field("tenant"),
RuntimeScalarOperator::EndsWith,
RuntimeValue::String("orth".into()),
Default::default(),
)
.unwrap();
let visible = f
.schema()
.runtime_and(
&f.collection(),
vec![f.authority().predicate.unwrap(), tenant_suffix],
Default::default(),
)
.unwrap();
for (offset, label) in samples.iter().enumerate() {
let id = (case * samples.len() + offset) as i64;
let create = f
.schema()
.runtime_create_request(
&f.collection(),
&[
(f.field("id"), RuntimeValue::Integer(id)),
(f.field("tenant"), RuntimeValue::String("north".into())),
(f.field("label"), RuntimeValue::String((*label).into())),
],
None,
Default::default(),
)
.unwrap();
f.run(create, f.authority()).await.unwrap();
let update = f
.schema()
.runtime_update_request(
f.key(id),
&[(f.field("score"), RuntimeValue::Integer(8))],
Some(expected.clone()),
Some(f.projection()),
Default::default(),
)
.unwrap();
let mut authority = f.authority();
authority.predicate = Some(visible.clone());
let result = f.run(update.clone(), authority).await;
if matched.contains(&offset) {
let effect = result.unwrap();
assert!(matches!(
effect.record().unwrap().state(&f.field("score")).unwrap(),
RuntimeFieldState::Value(RuntimeValue::Integer(8))
));
assert!(
matches!(effect.record().unwrap().state(&f.field("label")).unwrap(), RuntimeFieldState::Value(RuntimeValue::String(actual)) if actual == label)
);
// A structural condition independent of a built-in version field
// catches the now-stale score even when the string still matches.
let score = f
.schema()
.runtime_compare(
&f.collection(),
&f.field("score"),
RuntimeScalarOperator::Eq,
RuntimeValue::Integer(7),
Default::default(),
)
.unwrap();
let stale = f
.schema()
.runtime_and(
&f.collection(),
vec![expected.clone(), score],
Default::default(),
)
.unwrap();
let retry = f
.schema()
.runtime_update_request(
f.key(id),
&[(f.field("score"), RuntimeValue::Integer(9))],
Some(stale),
None,
Default::default(),
)
.unwrap();
assert_eq!(
f.run(retry, f.authority())
.await
.unwrap_err()
.public_error()
.runtime_mutation_code(),
Some("conflict")
);
} else {
assert_eq!(
result.unwrap_err().public_error().runtime_mutation_code(),
Some("conflict")
);
}
}
}
f.finish().await;
}

#[cfg(feature = "runtime-graphql")]
#[path = "support/runtime_graphql_mutations.rs"]
mod graphql_mutations;
43 changes: 43 additions & 0 deletions crates/graphql-orm/tests/support/runtime_graphql_mutations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1009,3 +1009,46 @@ async fn repeated_groups_keep_validation_directives_and_projection_denial_before
assert_eq!(f.count().await, 1);
f.finish().await;
}

#[tokio::test]
async fn reconciled_string_cas_runs_once_through_graphql_and_atomic_journals() {
let f = Fixture::new().await;
f.run(f.create(1), f.authority()).await.unwrap();
let schema = api(&f);
let probe = Arc::new(GroupProbe::default());
let field = r#"updateRecord(key:{id:"1"},input:{label:"updated"},expected:{and:[{label:{startsWith:"ini"}},{label:{endsWith:"tial"}},{label:{endsWith:""}}]},expectedSchema:$schema)"#;
let response = schema
.execute(request(&f, &repeated(field)).data(group_context(
&f,
&probe,
0,
0,
Default::default(),
)))
.await;
assert!(response.errors.is_empty(), "{:?}", response.errors);
assert_eq!(
response.data.into_json().unwrap()["same"]["record"],
serde_json::json!({"label":"updated","score":"7","renamed":"updated"})
);
assert_eq!(probe.intents.load(Ordering::SeqCst), 1);
assert_eq!(probe.hooks.lock().unwrap().len(), 1);
assert_eq!(probe.released.lock().unwrap().len(), 1);
assert_eq!(Journal::count_all(&f.db).await.unwrap(), 1);
let stale = schema
.execute(request(&f, &repeated(field)).data(group_context(
&f,
&probe,
0,
0,
Default::default(),
)))
.await;
assert_eq!(stale.errors.len(), 3);
assert!(stale.errors.iter().all(|error| error.message == "conflict"));
assert_eq!(probe.intents.load(Ordering::SeqCst), 2);
assert_eq!(probe.hooks.lock().unwrap().len(), 1);
assert_eq!(probe.released.lock().unwrap().len(), 1);
assert_eq!(Journal::count_all(&f.db).await.unwrap(), 1);
f.finish().await;
}
56 changes: 32 additions & 24 deletions docs/plans/active/runtime-platform-capabilities/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ PR A implements the owned migration slice; its current public mechanics are in
B is accepted at `04a1216aff86141ff6500b1dd0bf9adb4b0c6447` as C's committed
predecessor. C is accepted at `9d4cca34cb04ab0fd27c8313215001591f495c43`; its
[current mechanics and approved custom-directive limitation](../../../reference/graphql-orm/runtime-graphql.md)
are documented independently. D is implemented for review on that exact C revision, including the approved
additive post-commit output callback. It remains unaccepted and unreleased.
are documented independently. D is accepted at
`3a59d08be5b663ac952de418eb02dc6321ee08e7`, including the post-commit output
callback and D1 response-key grouping. All slices remain unreleased; final
combined review is pending.
Final reconciliation and owner merge/release remain #112 → A → B → C → D. A proposal
or unpublished implementation is not downstream adoption evidence.

Expand Down Expand Up @@ -1189,25 +1191,31 @@ is advertised until its contract is committed and reviewed.

## Current checkpoint

The revised A–D contract at `c53a5de966ca5089a5e4aa2d727e144bae83e6bd`
is approved. Digibase accepts A at `f2665ce68ed7fdbccb272cb8a783b6379ae5fd82`:
FK corrections R1/R2 and PostgreSQL DateTime correction R3 are resolved. A's
reviewed-predecessor gate for B is satisfied. A remains unmerged/unreleased,
with ORM/macros 0.40.0. Owner merge/release of independent #112 precedes A's final
reconciliation; Actions remain paused.

B's independent implementation is based on that exact committed A revision,
with aligned unpublished ORM/macros 0.41.0. It adds schema-bound requests,
explicit intent/preimage/result authority, pinned structural CAS, exact return
projections, atomic repository journal writes and guarded operation/hook
cancellation. B is accepted at `04a1216aff86141ff6500b1dd0bf9adb4b0c6447`. Its reference and compiled
host example describe environment/policy revision pinning, backend gates and
ambiguous commit limitations. The static-only inner-timeout limitation remains.

C is accepted at `9d4cca34cb04ab0fd27c8313215001591f495c43`. D is implemented with response-key grouping corrections
for review, but remains unaccepted/unreleased and dependent on its
respective reviewed predecessors. Preserve host-managed AEAD cursor protection,
lossless JSON text, required create keys and independent post-commit payload
read authorization. Reconcile B onto the final reviewed A after #112 integration
and rerun combined backend contracts before downstream adoption. No product or
sibling repository changes, owner merge or release publication are included.
All A–D implementations are accepted at exact committed revisions:

| Slice | PR | Accepted revision |
| --- | --- | --- |
| Independent strings/binding | #112 | `01d4134da18028ec58e318e0ffd30ab244d0d8e1` |
| A | #97 | `f2665ce68ed7fdbccb272cb8a783b6379ae5fd82` |
| B | #113 | `04a1216aff86141ff6500b1dd0bf9adb4b0c6447` |
| C | #114 | `9d4cca34cb04ab0fd27c8313215001591f495c43` |
| D | #115 | `3a59d08be5b663ac952de418eb02dc6321ee08e7` |

The independent combined candidate merges these reviewed revisions in that order
and keeps each as an ancestor. ORM/macros are aligned at unpublished 0.43.0,
including #112's runtime predicates/native binding identity and all A–D
corrections. The canonical owned backend runner now includes opt-in C/D tests.
PostgreSQL success cases in B's emitted-bind budget test are enabled; new
Unicode/empty prefix/suffix CAS and repeated GraphQL string-CAS/journal tests
exercise the combined engine. Individual reviewed branches and sibling
repositories remain unchanged.

Final combined-source verification and review remain pending. Owner merge/release
order stays #112 → A → B → C → D; Actions remain paused, local builds use two jobs
and sequential backends. Retain migration/FK/DateTime/RLS preservation, pinned
authority and CAS, atomic journals, cancellation poisoning, confidential AEAD
cursors, lossless scalars, corrected aliases/filter intents and terminal repeated
mutation groups. Custom-directive factories, modifying cascades and the static
inner-timeout limitation remain documented boundaries. No merge/publication or
downstream adoption is claimed. The owner release must bind an immutable workspace
tag, full source SHA, package/lockfile hashes and external revision provenance.
11 changes: 6 additions & 5 deletions docs/reference/graphql-orm/runtime-graphql.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,11 +208,12 @@ Docker containers and verify exact ownership before cleanup. MSSQL remains an
unsupported runtime decoder and is rejected before ORM I/O; compilation is not
live MSSQL execution. GitHub Actions remain paused.

The unreconciled B predecessor does not yet contain #112. Successful PostgreSQL
StartsWith/EndsWith execution and the SQLite empty-suffix correction remain gated
on reviewed #112 → A → B → C reconciliation. This does not weaken those release
acceptance requirements. No merge, publication or downstream pin is implied by an
unpublished C branch. D requires separate reviewed C/B predecessors.
The combined candidate contains reviewed #112 → A → B → C → D. Its owned
backend runner executes PostgreSQL prefix/suffix CAS success and exact bind-budget
boundaries, SQLite empty-suffix behavior, and repeated GraphQL string-CAS groups
with atomic journals. Individual A–D heads remain unchanged. Final combined
source review and owner-controlled merge/release are still adoption gates; local
execution does not supply an immutable release identity or authorize a downstream pin.


## Typed mutations and committed output authorization
Expand Down
9 changes: 6 additions & 3 deletions docs/reference/graphql-orm/runtime-mutations.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,9 @@ retain their existing limitation: catching an inner static mutation timeout and
returning `Ok` does not guarantee rollback.

Bulk/nested writes, new upsert semantics, dynamic GraphQL and durable delivery are
outside B. C/D remain separate reviewed successors. Owner merge/release of #112
and final reconciliation of A/B are required before combined downstream adoption;
this branch does not claim to contain the independent string-binding fix.
outside B. C/D remain separate reviewed successors. The combined candidate
contains the independent #112 string-binding fix and exercises successful
PostgreSQL prefix/suffix CAS and emitted-bind boundaries without test-side skips.
SQLite empty suffixes match every non-null string. Final combined review and
owner merge/release remain required before downstream adoption. Static APIs and
their cancellation limitation are unchanged.
Loading