From ff32d8ed8c0ec7125c8d08288f4fcfbb1a00769e Mon Sep 17 00:00:00 2001 From: Toby Martin Date: Sat, 22 Aug 2026 08:20:10 +1000 Subject: [PATCH 1/3] feat(router): configure generic scope matching --- Cargo.lock | 6 +- Cargo.toml | 2 +- crates/graphql-orm-router/CHANGELOG.md | 14 + crates/graphql-orm-router/Cargo.toml | 2 +- crates/graphql-orm-router/MIGRATION.md | 17 ++ crates/graphql-orm-router/README.md | 3 +- .../graphql-orm-router/docs/configuration.md | 20 ++ .../examples/router.example.json | 3 + crates/graphql-orm-router/src/agql.rs | 11 +- crates/graphql-orm-router/src/file_config.rs | 271 +++++++++++++++++- 10 files changed, 337 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a947ad1e..15b8ff06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -87,8 +87,8 @@ dependencies = [ [[package]] name = "agql-auth" -version = "0.15.0" -source = "git+https://github.com/Dastari/agql-auth.git?rev=e841ffd382082ad7419be259fe957f949b956ff7#e841ffd382082ad7419be259fe957f949b956ff7" +version = "0.16.0" +source = "git+https://github.com/Dastari/agql-auth.git?rev=e6439aa034babb6827e9253977f760667ea6b7eb#e6439aa034babb6827e9253977f760667ea6b7eb" dependencies = [ "argon2", "async-graphql", @@ -3193,7 +3193,7 @@ dependencies = [ [[package]] name = "graphql-orm-router" -version = "0.1.4" +version = "0.2.0" dependencies = [ "agql-auth", "arc-swap", diff --git a/Cargo.toml b/Cargo.toml index 98fa550e..f38fee65 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ license = "MIT" repository = "https://github.com/Dastari/graphql-orm" [workspace.dependencies] -agql-auth = { git = "https://github.com/Dastari/agql-auth.git", rev = "e841ffd382082ad7419be259fe957f949b956ff7", version = "0.15.0" } +agql-auth = { git = "https://github.com/Dastari/agql-auth.git", rev = "e6439aa034babb6827e9253977f760667ea6b7eb", version = "0.16.0" } arc-swap = "1.7" async-graphql = { version = "7", features = ["dataloader", "uuid"] } async-graphql-parser = "7" diff --git a/crates/graphql-orm-router/CHANGELOG.md b/crates/graphql-orm-router/CHANGELOG.md index fa108847..97295b46 100644 --- a/crates/graphql-orm-router/CHANGELOG.md +++ b/crates/graphql-orm-router/CHANGELOG.md @@ -10,6 +10,20 @@ supersedes: [] # Changelog +## 0.2.0 - 2026-08-22 + +- Added strict `scopeMatcher` file configuration. Omission and `kind: exact` + preserve exact-string behavior; `kind: hierarchical` exposes the generic + separator, wildcard, universal-wildcard, super-scope, and exact-only options + from the optional resource-server matcher. +- Updated the optional `agql-auth` adapter to 0.16.0 for consumer-supplied + exact-only fixed scopes and resource-qualified scope patterns. +- Applied the configured matcher uniformly to fixed and rendered operation + requirements. Subgraph resolver authorization remains authoritative. + +No descriptor, GraphQL schema, token wire, or stored-data migration is +required. Hierarchical file configuration requires the `auth-agql` feature. + ## 0.1.4 - 2026-08-13 - Updated the optional resource-server-only `agql-auth` adapter to 0.15.0 at diff --git a/crates/graphql-orm-router/Cargo.toml b/crates/graphql-orm-router/Cargo.toml index b85258be..b336356f 100644 --- a/crates/graphql-orm-router/Cargo.toml +++ b/crates/graphql-orm-router/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "graphql-orm-router" -version = "0.1.4" +version = "0.2.0" edition = "2024" rust-version = "1.90" description = "Federated GraphQL router for graphql-orm and project-neutral subgraphs" diff --git a/crates/graphql-orm-router/MIGRATION.md b/crates/graphql-orm-router/MIGRATION.md index 858efd15..60bee4cc 100644 --- a/crates/graphql-orm-router/MIGRATION.md +++ b/crates/graphql-orm-router/MIGRATION.md @@ -10,6 +10,23 @@ supersedes: [] # graphql-orm-router migration guide +## 0.1.4 to 0.2.0 + +Replace the reviewed full-revision pin and rebuild the router. Hosts that also +depend directly on `agql-auth` must align to version 0.16.0 at the exact +reviewed revision so Cargo resolves one auth type universe. + +Omitted `scopeMatcher` configuration remains exact and needs no migration. +To opt into the generic hierarchy, build with `auth-agql` and add +`{"scopeMatcher":{"kind":"hierarchical"}}`; then explicitly review +separator, wildcard, universal-wildcard, `superScopes`, `exactOnlyScopes`, and +`exactOnlyScopePatterns` policy. Exact-only requirements are checked before a +super scope or wildcard, including rendered resource-qualified requirements. + +No descriptor, GraphQL schema, token wire, or stored-data migration is +required. Run `--check` against the complete graph and exercise both ordinary +and exact-only authorization cases before deployment. + ## 0.1.3 to 0.1.4 Replace the reviewed full-revision pin and rebuild the router. When a host diff --git a/crates/graphql-orm-router/README.md b/crates/graphql-orm-router/README.md index b71a2e74..2a3a888a 100644 --- a/crates/graphql-orm-router/README.md +++ b/crates/graphql-orm-router/README.md @@ -26,7 +26,7 @@ This unpublished package is Git-only: ```toml [dependencies] -graphql-orm-router = { git = "https://github.com/Dastari/graphql-orm.git", rev = "", version = "0.1.4" } +graphql-orm-router = { git = "https://github.com/Dastari/graphql-orm.git", rev = "", version = "0.2.0" } ``` Enable `auth-agql` only when adapting a separately configured @@ -68,6 +68,7 @@ composition warnings before serving. | Surface | Default or boundary | | --- | --- | | Authentication | Fail-closed; anonymous mode is explicit development-only. | +| Scope matching | Exact by default; hierarchical matching requires `auth-agql` and explicit file or programmatic configuration. | | Public path | `/graphql`; `/health` and `/readiness` are also exposed. | | Subgraphs | At least one static source; file configuration requires strict JSON. | | Dynamic registration | Disabled unless authenticated administration and exact network policy are configured. | diff --git a/crates/graphql-orm-router/docs/configuration.md b/crates/graphql-orm-router/docs/configuration.md index 08c5e1b0..98ae3628 100644 --- a/crates/graphql-orm-router/docs/configuration.md +++ b/crates/graphql-orm-router/docs/configuration.md @@ -58,6 +58,7 @@ an unbounded value. | `graphqlPath` | `/graphql`; absolute, non-root, without query or fragment. | | `anonymousDevelopment` | `false`; mutually exclusive with authentication and unsuitable for production. | | `authentication` | Required unless anonymous development is explicitly enabled. | +| `scopeMatcher` | Omitted means exact-string matching. `kind: "hierarchical"` requires the `auth-agql` feature. | | `subgraphs` | At least one static source is required. | | `forwardedHeaders` | Empty. Sensitive, hop-by-hop, cookie, and authorization names are rejected. | | `schemaFetchTimeoutMs` | 10000. | @@ -88,6 +89,25 @@ array. JWKS uses HTTPS. Plain HTTP is accepted only for loopback when The router validates RS256 public keys only. Configuration has no private-key, token-signing, session, refresh-token, or RSA-decryption field. +Scope matching is a separate resource-server policy. Omission or +`{"kind":"exact"}` preserves exact-string compatibility. With the +`auth-agql` feature, `kind: "hierarchical"` accepts these optional fields: + +| Field | Default or rule | +| --- | --- | +| `separator` | `.`; one visible character. | +| `wildcard` | `*`; one non-empty segment without whitespace. | +| `wildcardMatchesMultiSegment` | `true`; a trailing wildcard matches the remaining hierarchy. | +| `allowUniversalWildcard` | `false`; a bare wildcard has no implicit authority. | +| `superScopes` | Empty; each listed grant satisfies ordinary requirements. | +| `exactOnlyScopes` | Empty; each listed requirement accepts only an equal grant. | +| `exactOnlyScopePatterns` | Empty; matching resource-qualified requirements accept only equal grants. | + +Exact-only requirements are evaluated before super-scope and wildcard rules. +Lists are normalized and deduplicated at startup. The router applies this one +matcher to fixed and rendered operation requirements; resolver guards remain +authoritative. + For programmatic setup, `JwksAuthenticationConfig::new` requires a JWKS URL, issuer, and non-empty audiences. It defaults to a 15-minute key cache, 5-minute refresh interval, 5-second request timeout, 1 MiB JWKS body limit, diff --git a/crates/graphql-orm-router/examples/router.example.json b/crates/graphql-orm-router/examples/router.example.json index 0b9f6331..7fa1dde6 100644 --- a/crates/graphql-orm-router/examples/router.example.json +++ b/crates/graphql-orm-router/examples/router.example.json @@ -8,6 +8,9 @@ "refreshIntervalSeconds": 300, "cacheTtlSeconds": 900 }, + "scopeMatcher": { + "kind": "exact" + }, "subgraphs": [ { "name": "products", diff --git a/crates/graphql-orm-router/src/agql.rs b/crates/graphql-orm-router/src/agql.rs index f0ccfc93..89f99b50 100644 --- a/crates/graphql-orm-router/src/agql.rs +++ b/crates/graphql-orm-router/src/agql.rs @@ -19,9 +19,7 @@ impl AgqlAuthenticationProvider { /// Adapts the validator's exact or host-configured matcher to router policy. pub fn scope_matcher(&self) -> AgqlScopeMatcher { - AgqlScopeMatcher { - matcher: self.validator.scope_matcher(), - } + AgqlScopeMatcher::new(self.validator.scope_matcher()) } } @@ -60,6 +58,13 @@ pub struct AgqlScopeMatcher { matcher: Arc, } +impl AgqlScopeMatcher { + /// Adapts a host-configured `agql-auth` matcher to router policy. + pub fn new(matcher: Arc) -> Self { + Self { matcher } + } +} + impl fmt::Debug for AgqlScopeMatcher { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("AgqlScopeMatcher(..)") diff --git a/crates/graphql-orm-router/src/file_config.rs b/crates/graphql-orm-router/src/file_config.rs index 65498e47..a7ef47c4 100644 --- a/crates/graphql-orm-router/src/file_config.rs +++ b/crates/graphql-orm-router/src/file_config.rs @@ -5,10 +5,16 @@ use std::{ use serde::Deserialize; +#[cfg(feature = "auth-agql")] +use agql_auth::{HierarchicalScopeMatch, HierarchicalScopeOptions}; + +#[cfg(feature = "auth-agql")] +use crate::AgqlScopeMatcher; use crate::{ - AdminConfig, JwksAuthenticationConfig, JwksAuthenticationProvider, LegacyScopeClaims, - NetworkCidr, NetworkPolicy, RequestLimits, RouterConfig, RouterError, RouterErrorKind, - RouterLogLevel, RouterTelemetryConfig, StaticSubgraph, SubscriptionConfig, TrustedSubgraph, + AdminConfig, ExactScopeMatcher, JwksAuthenticationConfig, JwksAuthenticationProvider, + LegacyScopeClaims, NetworkCidr, NetworkPolicy, RequestLimits, RouterConfig, RouterError, + RouterErrorKind, RouterLogLevel, RouterTelemetryConfig, StaticSubgraph, SubscriptionConfig, + TrustedSubgraph, }; const MAX_CONFIG_BYTES: u64 = 1024 * 1024; @@ -28,6 +34,7 @@ pub struct RouterFileConfig { #[serde(default)] anonymous_development: bool, authentication: Option, + scope_matcher: Option, subgraphs: Vec, #[serde(default)] forwarded_headers: Vec, @@ -89,6 +96,9 @@ impl RouterFileConfig { let mut config = RouterConfig::builder(listener) .with_graphql_path(self.graphql_path) .allow_anonymous_development(self.anonymous_development); + if let Some(scope_matcher) = self.scope_matcher { + config = scope_matcher.apply(config)?; + } if let Some(authentication) = self.authentication { let mut jwks = JwksAuthenticationConfig::new( authentication.jwks_url, @@ -263,6 +273,159 @@ struct FileAuthentication { allow_insecure_loopback_jwks: bool, } +/// File-owned scope policy. Omission and `kind: exact` preserve the router's +/// historical exact-string behavior. +#[derive(Clone, Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +struct FileScopeMatcher { + kind: FileScopeMatcherKind, + separator: Option, + wildcard: Option, + wildcard_matches_multi_segment: Option, + allow_universal_wildcard: Option, + #[serde(default)] + super_scopes: Vec, + #[serde(default)] + exact_only_scopes: Vec, + #[serde(default)] + exact_only_scope_patterns: Vec, +} + +#[derive(Clone, Copy, Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +enum FileScopeMatcherKind { + Exact, + Hierarchical, +} + +impl FileScopeMatcher { + fn apply(self, config: RouterConfig) -> Result { + match self.kind { + FileScopeMatcherKind::Exact => { + if self.separator.is_some() + || self.wildcard.is_some() + || self.wildcard_matches_multi_segment.is_some() + || self.allow_universal_wildcard.is_some() + || !self.super_scopes.is_empty() + || !self.exact_only_scopes.is_empty() + || !self.exact_only_scope_patterns.is_empty() + { + return Err(invalid( + "scopeMatcher kind `exact` does not accept hierarchical options", + )); + } + Ok(config.with_scope_matcher(Arc::new(ExactScopeMatcher))) + } + FileScopeMatcherKind::Hierarchical => build_hierarchical_scope_matcher( + config, + self.separator, + self.wildcard, + self.wildcard_matches_multi_segment, + self.allow_universal_wildcard, + self.super_scopes, + self.exact_only_scopes, + self.exact_only_scope_patterns, + ), + } + } +} + +#[cfg(feature = "auth-agql")] +#[allow(clippy::too_many_arguments)] +fn build_hierarchical_scope_matcher( + config: RouterConfig, + separator: Option, + wildcard: Option, + wildcard_matches_multi_segment: Option, + allow_universal_wildcard: Option, + mut super_scopes: Vec, + mut exact_only_scopes: Vec, + mut exact_only_scope_patterns: Vec, +) -> Result { + let defaults = HierarchicalScopeOptions::default(); + let separator = separator.unwrap_or(defaults.separator); + let wildcard = wildcard.unwrap_or(defaults.wildcard); + validate_scope_syntax(separator, &wildcard)?; + for (field, scopes) in [ + ("superScopes", super_scopes.as_slice()), + ("exactOnlyScopes", exact_only_scopes.as_slice()), + ( + "exactOnlyScopePatterns", + exact_only_scope_patterns.as_slice(), + ), + ] { + validate_scope_values(field, scopes)?; + } + super_scopes.sort(); + super_scopes.dedup(); + exact_only_scopes.sort(); + exact_only_scopes.dedup(); + exact_only_scope_patterns.sort(); + exact_only_scope_patterns.dedup(); + let matcher = HierarchicalScopeMatch::new(HierarchicalScopeOptions { + separator, + wildcard, + wildcard_matches_multi_segment: wildcard_matches_multi_segment + .unwrap_or(defaults.wildcard_matches_multi_segment), + allow_universal_wildcard: allow_universal_wildcard + .unwrap_or(defaults.allow_universal_wildcard), + super_scopes, + exact_only_scopes, + exact_only_scope_patterns, + }); + Ok(config.with_scope_matcher(Arc::new(AgqlScopeMatcher::new(Arc::new(matcher))))) +} + +#[cfg(not(feature = "auth-agql"))] +#[allow(clippy::too_many_arguments)] +fn build_hierarchical_scope_matcher( + _config: RouterConfig, + _separator: Option, + _wildcard: Option, + _wildcard_matches_multi_segment: Option, + _allow_universal_wildcard: Option, + _super_scopes: Vec, + _exact_only_scopes: Vec, + _exact_only_scope_patterns: Vec, +) -> Result { + Err(invalid( + "scopeMatcher kind `hierarchical` requires the `auth-agql` feature", + )) +} + +#[cfg(feature = "auth-agql")] +fn validate_scope_syntax(separator: char, wildcard: &str) -> Result<(), RouterError> { + if separator.is_whitespace() || separator.is_control() { + return Err(invalid("scopeMatcher separator must be visible")); + } + if wildcard.is_empty() + || wildcard.contains(separator) + || wildcard + .chars() + .any(|character| character.is_whitespace() || character.is_control()) + { + return Err(invalid( + "scopeMatcher wildcard must be a non-empty segment without whitespace", + )); + } + Ok(()) +} + +#[cfg(feature = "auth-agql")] +fn validate_scope_values(field: &str, scopes: &[String]) -> Result<(), RouterError> { + if scopes.iter().any(|scope| { + scope.is_empty() + || scope + .chars() + .any(|character| character.is_whitespace() || character.is_control()) + }) { + return Err(invalid(format!( + "scopeMatcher {field} contains an empty scope or whitespace" + ))); + } + Ok(()) +} + #[derive(Clone, Debug, Deserialize)] #[serde(rename_all = "camelCase", deny_unknown_fields)] struct FileSubgraph { @@ -561,6 +724,12 @@ mod tests { config.subgraphs[0].schema_headers["authorization"], "Bearer schema-secret" ); + assert!( + config + .scope_matcher + .matches("products.read", "products.read") + ); + assert!(!config.scope_matcher.matches("products.*", "products.read")); assert!(!format!("{config:?}").contains("schema-secret")); } @@ -574,4 +743,100 @@ mod tests { .is_err() ); } + + #[cfg(feature = "auth-agql")] + #[test] + fn hierarchical_file_matcher_applies_super_wildcard_and_exact_only_matrix() { + let json = FILE.replacen( + "\"authentication\":", + r#""scopeMatcher": { + "kind": "hierarchical", + "superScopes": ["platform.admin"], + "exactOnlyScopes": ["payments.credentials.release"], + "exactOnlyScopePatterns": ["payments.account.*.credentials.release"] + }, + "authentication":"#, + 1, + ); + let config = RouterFileConfig::from_json(&json) + .unwrap() + .into_router_config_with(|name| { + Ok((name == "PRODUCTS_SCHEMA_TOKEN").then(|| "Bearer secret".to_owned())) + }) + .unwrap(); + let matcher = config.scope_matcher; + + for (granted, required, expected) in [ + ("platform.admin", "orders.read", true), + ("orders.*", "orders.read", true), + ("orders.read", "orders.read", true), + ("platform.admin", "payments.credentials.release", false), + ("payments.*", "payments.credentials.release", false), + ( + "payments.credentials.release", + "payments.credentials.release", + true, + ), + ( + "platform.admin", + "payments.account.7.credentials.release", + false, + ), + ( + "payments.account.*", + "payments.account.7.credentials.release", + false, + ), + ( + "payments.account.7.credentials.release", + "payments.account.7.credentials.release", + true, + ), + ] { + assert_eq!( + matcher.matches(granted, required), + expected, + "grant {granted:?} for requirement {required:?}" + ); + } + } + + #[cfg(not(feature = "auth-agql"))] + #[test] + fn hierarchical_file_matcher_requires_auth_agql_feature() { + let json = FILE.replacen( + "\"authentication\":", + r#""scopeMatcher": {"kind": "hierarchical"}, + "authentication":"#, + 1, + ); + let error = RouterFileConfig::from_json(&json) + .unwrap() + .into_router_config_with(|name| { + Ok((name == "PRODUCTS_SCHEMA_TOKEN").then(|| "Bearer secret".to_owned())) + }) + .unwrap_err(); + assert!(error.to_string().contains("auth-agql")); + } + + #[test] + fn exact_file_matcher_rejects_hierarchical_options() { + let json = FILE.replacen( + "\"authentication\":", + r#""scopeMatcher": {"kind": "exact", "superScopes": ["platform.admin"]}, + "authentication":"#, + 1, + ); + let error = RouterFileConfig::from_json(&json) + .unwrap() + .into_router_config_with(|name| { + Ok((name == "PRODUCTS_SCHEMA_TOKEN").then(|| "Bearer secret".to_owned())) + }) + .unwrap_err(); + assert!( + error + .to_string() + .contains("kind `exact` does not accept hierarchical options") + ); + } } From 87effa5f63ac8d1b5a65b6387dd53a26ad39ced6 Mon Sep 17 00:00:00 2001 From: Toby Martin Date: Sat, 22 Aug 2026 09:18:04 +1000 Subject: [PATCH 2/3] test: align auth bridge fixtures with matcher release --- MIGRATION.md | 13 +++++++++++-- crates/graphql-orm-backup/AGENTS.md | 4 ++-- crates/graphql-orm-storage/AGENTS.md | 4 ++-- .../tests/backend_coexistence_fixture.rs | 6 +++--- .../fixtures/backend-coexistence/Cargo.lock | 16 ++++++++-------- .../backend-coexistence/auth-service/Cargo.toml | 2 +- docs/reference/graphql-orm/agql-auth-bridge.md | 17 +++++++++++------ docs/reference/workspace-packages.md | 4 ++-- 8 files changed, 40 insertions(+), 26 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index 0beccbba..8e4d57e8 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -13,6 +13,15 @@ supersedes: [] `graphql-orm` is distributed from GitHub only. Use a reviewed full 40-character commit in `rev`; neither the runtime nor macros crate is published to crates.io. +## Current Git revision: agql-auth 0.16 alignment + +Hosts enabling `auth-agql` or depending directly on `agql-auth` must align to +version 0.16.0 at the workspace's reviewed full revision. This retains one +public auth type universe. Version 0.16 adds consumer-supplied exact-only +hierarchical matcher policy; the ORM bridge remains projection-only and does +not select or install that policy. No database, schema, token, or stored-data +migration is required. + ## 0.22.1 to 0.23.0: explicit collection-bound semantics Update `graphql-orm`, `graphql-orm-macros`, and @@ -1183,6 +1192,6 @@ access path. - No JWT, OIDC, cookie, wildcard, or application-specific scope logic was added to `graphql-orm`. - PostgreSQL RLS helper functions still use exact scope matching. -- The current `auth-agql` feature targets `agql-auth` 0.15.0 at revision - `e841ffd382082ad7419be259fe957f949b956ff7`; earlier release sections above +- The current `auth-agql` feature targets `agql-auth` 0.16.0 at revision + `e6439aa034babb6827e9253977f760667ea6b7eb`; earlier release sections above retain their historical pins. diff --git a/crates/graphql-orm-backup/AGENTS.md b/crates/graphql-orm-backup/AGENTS.md index 43107853..9241d892 100644 --- a/crates/graphql-orm-backup/AGENTS.md +++ b/crates/graphql-orm-backup/AGENTS.md @@ -37,8 +37,8 @@ This crate is a reusable backup and restore companion for applications that use Keep downstream applications on one reviewed monorepo revision so ORM, backup, and storage share the same canonical source/type universe. - `graphql-orm` owns its optional `agql-auth` integration and pins - `agql-auth` 0.15.0 at - `e841ffd382082ad7419be259fe957f949b956ff7`. This crate must not enable or + `agql-auth` 0.16.0 at + `e6439aa034babb6827e9253977f760667ea6b7eb`. This crate must not enable or depend directly on application authorization. - Applying and dry-run restore compare the manifest backend/schema hash with the target before target checks or writes. Preserve that fail-closed diff --git a/crates/graphql-orm-storage/AGENTS.md b/crates/graphql-orm-storage/AGENTS.md index a506607c..a725e9bd 100644 --- a/crates/graphql-orm-storage/AGENTS.md +++ b/crates/graphql-orm-storage/AGENTS.md @@ -36,8 +36,8 @@ This crate is a reusable storage companion for applications that use `graphql-or through workspace path dependencies and one root `Cargo.lock`. Keep downstream companion packages on one reviewed monorepo revision. - `graphql-orm` owns the optional `agql-auth` integration, pinned at - `agql-auth` 0.15.0 revision - `e841ffd382082ad7419be259fe957f949b956ff7`; this crate does not depend on + `agql-auth` 0.16.0 revision + `e6439aa034babb6827e9253977f760667ea6b7eb`; this crate does not depend on application authorization. - The storage provider boundary is now the streaming `BlobStore` trait. - `ObjectStorage` extends `BlobStore`; custom providers must implement `BlobStore` first. diff --git a/crates/graphql-orm/tests/backend_coexistence_fixture.rs b/crates/graphql-orm/tests/backend_coexistence_fixture.rs index 56e8b0cb..98bfa324 100644 --- a/crates/graphql-orm/tests/backend_coexistence_fixture.rs +++ b/crates/graphql-orm/tests/backend_coexistence_fixture.rs @@ -66,13 +66,13 @@ fn assert_direct_host_dependency_resolves_one_exact_agql_auth_universe() { .filter(|package| package["name"] == "agql-auth") .collect::>(); assert_eq!(agql_auth.len(), 1, "resolved metadata:\n{metadata}"); - assert_eq!(agql_auth[0]["version"], "0.15.0"); + assert_eq!(agql_auth[0]["version"], "0.16.0"); let source = agql_auth[0]["source"] .as_str() .expect("agql-auth source must be present"); assert!( - source.contains("rev=e841ffd382082ad7419be259fe957f949b956ff7") - && source.ends_with("#e841ffd382082ad7419be259fe957f949b956ff7"), + source.contains("rev=e6439aa034babb6827e9253977f760667ea6b7eb") + && source.ends_with("#e6439aa034babb6827e9253977f760667ea6b7eb"), "unexpected agql-auth source: {source}", ); } diff --git a/crates/graphql-orm/tests/fixtures/backend-coexistence/Cargo.lock b/crates/graphql-orm/tests/fixtures/backend-coexistence/Cargo.lock index f400a5e7..89e2e805 100644 --- a/crates/graphql-orm/tests/fixtures/backend-coexistence/Cargo.lock +++ b/crates/graphql-orm/tests/fixtures/backend-coexistence/Cargo.lock @@ -10,8 +10,8 @@ checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" [[package]] name = "agql-auth" -version = "0.15.0" -source = "git+https://github.com/Dastari/agql-auth.git?rev=e841ffd382082ad7419be259fe957f949b956ff7#e841ffd382082ad7419be259fe957f949b956ff7" +version = "0.16.0" +source = "git+https://github.com/Dastari/agql-auth.git?rev=e6439aa034babb6827e9253977f760667ea6b7eb#e6439aa034babb6827e9253977f760667ea6b7eb" dependencies = [ "argon2", "async-graphql", @@ -925,7 +925,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -1329,7 +1329,7 @@ dependencies = [ [[package]] name = "graphql-orm-ai" -version = "0.82.0" +version = "0.86.0" dependencies = [ "agql-auth", "async-graphql", @@ -1357,7 +1357,7 @@ dependencies = [ [[package]] name = "graphql-orm-ai-tool-profiles" -version = "0.6.0" +version = "0.9.0" dependencies = [ "async-graphql", "async-graphql-parser", @@ -2756,7 +2756,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -3363,10 +3363,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.3", + "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] diff --git a/crates/graphql-orm/tests/fixtures/backend-coexistence/auth-service/Cargo.toml b/crates/graphql-orm/tests/fixtures/backend-coexistence/auth-service/Cargo.toml index a990f2bb..e8a1c7cd 100644 --- a/crates/graphql-orm/tests/fixtures/backend-coexistence/auth-service/Cargo.toml +++ b/crates/graphql-orm/tests/fixtures/backend-coexistence/auth-service/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] -agql-auth = { git = "https://github.com/Dastari/agql-auth.git", rev = "e841ffd382082ad7419be259fe957f949b956ff7", version = "0.15.0" } +agql-auth = { git = "https://github.com/Dastari/agql-auth.git", rev = "e6439aa034babb6827e9253977f760667ea6b7eb", version = "0.16.0" } async-graphql = { version = "7", features = ["dataloader", "uuid"] } graphql-orm = { path = "../../../../", default-features = false, features = [ "sqlite", diff --git a/docs/reference/graphql-orm/agql-auth-bridge.md b/docs/reference/graphql-orm/agql-auth-bridge.md index a96ae698..d798a71a 100644 --- a/docs/reference/graphql-orm/agql-auth-bridge.md +++ b/docs/reference/graphql-orm/agql-auth-bridge.md @@ -20,9 +20,9 @@ graphql-orm = { git = "https://github.com/Dastari/graphql-orm.git", rev = " From ff88dad519a39bda4c80f9d66d538656c5ea6265 Mon Sep 17 00:00:00 2001 From: Toby Martin Date: Sat, 22 Aug 2026 13:33:48 +1000 Subject: [PATCH 3/3] chore: align matcher consumers with agql-auth 0.16 --- CHANGELOG.md | 19 +++++++ Cargo.lock | 8 +-- Cargo.toml | 4 +- MIGRATION.md | 15 +++--- README.md | 2 +- crates/graphql-orm-ai/CHANGELOG.md | 17 +++++++ crates/graphql-orm-ai/Cargo.toml | 2 +- crates/graphql-orm-ai/MIGRATION.md | 15 ++++++ crates/graphql-orm-ai/README.md | 2 +- .../docs/implementation-status.md | 6 +-- .../src/providers/codex_app_server.rs | 2 +- crates/graphql-orm-backup/AGENTS.md | 4 +- crates/graphql-orm-macros/Cargo.toml | 2 +- crates/graphql-orm-macros/README.md | 4 +- crates/graphql-orm-macros/src/operations.rs | 8 ++- crates/graphql-orm-router/CHANGELOG.md | 5 +- crates/graphql-orm-router/MIGRATION.md | 4 +- crates/graphql-orm-router/src/file_config.rs | 50 +++++++++++++++---- crates/graphql-orm-storage/AGENTS.md | 4 +- crates/graphql-orm/Cargo.toml | 4 +- crates/graphql-orm/README.md | 2 +- crates/graphql-orm/src/lib.rs | 7 +-- .../tests/backend_coexistence_fixture.rs | 4 +- .../fixtures/backend-coexistence/Cargo.lock | 16 +++--- .../auth-service/Cargo.toml | 2 +- crates/graphql-orm/tests/runtime_records.rs | 12 ++--- .../active/ai-production-readiness/README.md | 4 +- .../reference/graphql-orm/agql-auth-bridge.md | 6 +-- docs/reference/graphql-orm/backends.md | 6 +-- docs/reference/graphql-orm/mssql.md | 2 +- docs/reference/workspace-packages.md | 8 +-- 31 files changed, 162 insertions(+), 84 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ce6e194..3ffd88ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,25 @@ This file is the authoritative user-facing release chronology. The former [release-notes ledger](docs/archive/2026/graphql-orm-release-notes.md) is retained for historical context. +## 0.24.0 - 2026-08-22 + +Companion macros crate: `graphql-orm-macros` **0.24.0**. Generated ORM and +macro behavior is unchanged. + +- Updated the optional `auth-agql` dependency to `agql-auth` 0.16.0 at merged + revision `3bc38cd94794f1e868a9cc3a5551047b95a32105`. The bridge remains a + projection of an already accepted principal and does not select matcher + policy or gain token-issuer responsibilities. +- Hosts that enable `auth-agql` and also depend directly on `agql-auth` must + use the same exact revision so public principal and assurance types resolve + from one source universe. +- Collapsed an equivalent macro-generation branch and removed a redundant + doctest wrapper to remain warnings-clean under the pinned Clippy toolchain; + emitted tokens and the compile-fail probe are unchanged. + +No database, GraphQL SDL, generated-code, migration-history, backup, token, or +stored-data migration is required. + ## 0.23.0 - 2026-08-14 Companion macros crate: `graphql-orm-macros` **0.23.0**. Backend-neutral diff --git a/Cargo.lock b/Cargo.lock index 15b8ff06..e5b8afac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -88,7 +88,7 @@ dependencies = [ [[package]] name = "agql-auth" version = "0.16.0" -source = "git+https://github.com/Dastari/agql-auth.git?rev=e6439aa034babb6827e9253977f760667ea6b7eb#e6439aa034babb6827e9253977f760667ea6b7eb" +source = "git+https://github.com/Dastari/agql-auth.git?rev=3bc38cd94794f1e868a9cc3a5551047b95a32105#3bc38cd94794f1e868a9cc3a5551047b95a32105" dependencies = [ "argon2", "async-graphql", @@ -3075,7 +3075,7 @@ dependencies = [ [[package]] name = "graphql-orm" -version = "0.23.0" +version = "0.24.0" dependencies = [ "agql-auth", "async-graphql", @@ -3104,7 +3104,7 @@ dependencies = [ [[package]] name = "graphql-orm-ai" -version = "0.88.0" +version = "0.89.0" dependencies = [ "agql-auth", "async-graphql", @@ -3172,7 +3172,7 @@ dependencies = [ [[package]] name = "graphql-orm-macros" -version = "0.23.0" +version = "0.24.0" dependencies = [ "convert_case 0.7.1", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index f38fee65..2aad7630 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ license = "MIT" repository = "https://github.com/Dastari/graphql-orm" [workspace.dependencies] -agql-auth = { git = "https://github.com/Dastari/agql-auth.git", rev = "e6439aa034babb6827e9253977f760667ea6b7eb", version = "0.16.0" } +agql-auth = { git = "https://github.com/Dastari/agql-auth.git", rev = "3bc38cd94794f1e868a9cc3a5551047b95a32105", version = "0.16.0" } arc-swap = "1.7" async-graphql = { version = "7", features = ["dataloader", "uuid"] } async-graphql-parser = "7" @@ -37,7 +37,7 @@ cynic-parser = { version = "=0.11.2", features = ["pretty"] } futures = "0.3" getrandom = "0.3" graphql-composition = "=0.12.2" -graphql-orm = { path = "crates/graphql-orm", version = "0.23.0", default-features = false } +graphql-orm = { path = "crates/graphql-orm", version = "0.24.0", default-features = false } graphql-orm-ai-tool-profiles = { path = "crates/graphql-orm-ai-tool-profiles", version = "0.9.0" } graphql-orm-backup = { path = "crates/graphql-orm-backup", version = "0.7.1", default-features = false } graphql-orm-operation-catalog = { path = "crates/graphql-orm-operation-catalog", version = "0.3.0" } diff --git a/MIGRATION.md b/MIGRATION.md index 8e4d57e8..6f7f8c97 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -13,13 +13,16 @@ supersedes: [] `graphql-orm` is distributed from GitHub only. Use a reviewed full 40-character commit in `rev`; neither the runtime nor macros crate is published to crates.io. -## Current Git revision: agql-auth 0.16 alignment +## 0.23.0 to 0.24.0: agql-auth 0.16 alignment Hosts enabling `auth-agql` or depending directly on `agql-auth` must align to -version 0.16.0 at the workspace's reviewed full revision. This retains one -public auth type universe. Version 0.16 adds consumer-supplied exact-only -hierarchical matcher policy; the ORM bridge remains projection-only and does -not select or install that policy. No database, schema, token, or stored-data +version 0.16.0 at merged revision +`3bc38cd94794f1e868a9cc3a5551047b95a32105`. Adopt `graphql-orm` and the +aligned `graphql-orm-macros` 0.24.0 from the same reviewed monorepo revision. +This retains one public auth type universe. Version 0.16 adds +consumer-supplied exact-only hierarchical matcher policy; the ORM bridge +remains projection-only and does not select or install that policy. No +database, schema, GraphQL SDL, generated-code, token, backup, or stored-data migration is required. ## 0.22.1 to 0.23.0: explicit collection-bound semantics @@ -1193,5 +1196,5 @@ access path. - No JWT, OIDC, cookie, wildcard, or application-specific scope logic was added to `graphql-orm`. - PostgreSQL RLS helper functions still use exact scope matching. - The current `auth-agql` feature targets `agql-auth` 0.16.0 at revision - `e6439aa034babb6827e9253977f760667ea6b7eb`; earlier release sections above + `3bc38cd94794f1e868a9cc3a5551047b95a32105`; earlier release sections above retain their historical pins. diff --git a/README.md b/README.md index 439664e6..ecca1eb5 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ reviewed full SHA for the release: ```toml [dependencies] -graphql-orm = { git = "https://github.com/Dastari/graphql-orm.git", rev = "", version = "0.23.0", default-features = false, features = ["sqlite"] } +graphql-orm = { git = "https://github.com/Dastari/graphql-orm.git", rev = "", version = "0.24.0", default-features = false, features = ["sqlite"] } ``` Choose exactly the backend support needed by each service. Cargo can unify diff --git a/crates/graphql-orm-ai/CHANGELOG.md b/crates/graphql-orm-ai/CHANGELOG.md index 0d39b988..bace13c6 100644 --- a/crates/graphql-orm-ai/CHANGELOG.md +++ b/crates/graphql-orm-ai/CHANGELOG.md @@ -18,6 +18,23 @@ 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.89.0] - 2026-08-22 + +Persistent schema module: **0.63.0** (unchanged from 0.88.0). + +### Changed + +- Aligned the public `agql-auth` type universe to 0.16.0 at merged revision + `3bc38cd94794f1e868a9cc3a5551047b95a32105`. This matters to consumers because + the AI prelude publicly re-exports `CurrentPrincipalResolver`, + `PrincipalReference`, and `ResolvedPrincipal`. +- Hosts that depend directly on `agql-auth` must use that same full revision. + AI authorization, principal rehydration, provider, and persistence behavior + are unchanged. + +There is no schema, data, protected-payload, GraphQL SDL, backup or restore +migration in this release. + ## [0.88.0] - 2026-08-22 Persistent schema module: **0.63.0** (unchanged from 0.87.0). diff --git a/crates/graphql-orm-ai/Cargo.toml b/crates/graphql-orm-ai/Cargo.toml index 4fb20183..282daf02 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.88.0" +version = "0.89.0" 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 b89476a1..02e2db84 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.88.0 to 0.89.0: agql-auth 0.16 type-universe alignment + +Adopt `graphql-orm-ai` 0.89.0 from one reviewed full monorepo revision and +align every direct `agql-auth` dependency to version 0.16.0 at merged revision +`3bc38cd94794f1e868a9cc3a5551047b95a32105`. The AI prelude publicly +re-exports `CurrentPrincipalResolver`, `PrincipalReference`, and +`ResolvedPrincipal`, so resolving another source revision would create a +different public Rust type universe even though these type contracts are +otherwise unchanged. + +The AI schema module remains **0.63.0**. There is no database, data, table, +column, index, constraint, backfill, GraphQL SDL, protected-payload, backup or +restore migration. AI authorization, principal rehydration, and provider +behavior are unchanged. + ## 0.87.0 to 0.88.0: direct GPT-5.6 dynamic tools on Codex 0.148.0 Adopt `graphql-orm-ai` 0.88.0 from one reviewed full monorepo revision. The AI diff --git a/crates/graphql-orm-ai/README.md b/crates/graphql-orm-ai/README.md index 419d2159..826460f3 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.87.0", default-features = false, features = ["sqlite"] } +graphql-orm-ai = { git = "https://github.com/Dastari/graphql-orm.git", rev = "", version = "0.89.0", 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 bb23b1f0..faba2761 100644 --- a/crates/graphql-orm-ai/docs/implementation-status.md +++ b/crates/graphql-orm-ai/docs/implementation-status.md @@ -10,10 +10,10 @@ supersedes: [] # Implementation Status -`graphql-orm-ai` is at crate version `0.87.0` with AI schema module -`0.63.0`. It uses workspace `graphql-orm` `0.23.0`, backend-neutral +`graphql-orm-ai` is at crate version `0.89.0` with AI schema module +`0.63.0`. It uses workspace `graphql-orm` `0.24.0`, backend-neutral `graphql-orm-ai-tool-profiles` `0.9.0`, and external `agql-auth` -`0.15.0` at `e841ffd382082ad7419be259fe957f949b956ff7`. +`0.16.0` at `3bc38cd94794f1e868a9cc3a5551047b95a32105`. Completed stateless local-provider turns can carry a proof-bearing contained native-item refusal after authoritative usage settlement. Those runs close as diff --git a/crates/graphql-orm-ai/src/providers/codex_app_server.rs b/crates/graphql-orm-ai/src/providers/codex_app_server.rs index 1aa06250..7e522799 100644 --- a/crates/graphql-orm-ai/src/providers/codex_app_server.rs +++ b/crates/graphql-orm-ai/src/providers/codex_app_server.rs @@ -6601,7 +6601,7 @@ pub(crate) mod tests { .and_then(Value::as_str) .is_some_and(|description| description.contains("maximum 25")) ); - assert!(schema.to_string().find("anyOf").is_none()); + assert!(!schema.to_string().contains("anyOf")); } fn named_semantic_type(name: &str, nullable: bool) -> GraphqlSemanticTypeRef { diff --git a/crates/graphql-orm-backup/AGENTS.md b/crates/graphql-orm-backup/AGENTS.md index 9241d892..295cd1c0 100644 --- a/crates/graphql-orm-backup/AGENTS.md +++ b/crates/graphql-orm-backup/AGENTS.md @@ -32,13 +32,13 @@ This crate is a reusable backup and restore companion for applications that use ## Current Agent Handoff - Current crate version is `0.7.1`. -- The optional ORM adapter resolves `graphql-orm` 0.22.1 from the workspace. +- The optional ORM adapter resolves `graphql-orm` 0.24.0 from the workspace. Internal packages use workspace path dependencies and the root `Cargo.lock`. Keep downstream applications on one reviewed monorepo revision so ORM, backup, and storage share the same canonical source/type universe. - `graphql-orm` owns its optional `agql-auth` integration and pins `agql-auth` 0.16.0 at - `e6439aa034babb6827e9253977f760667ea6b7eb`. This crate must not enable or + `3bc38cd94794f1e868a9cc3a5551047b95a32105`. This crate must not enable or depend directly on application authorization. - Applying and dry-run restore compare the manifest backend/schema hash with the target before target checks or writes. Preserve that fail-closed diff --git a/crates/graphql-orm-macros/Cargo.toml b/crates/graphql-orm-macros/Cargo.toml index fede9790..a7bbe756 100644 --- a/crates/graphql-orm-macros/Cargo.toml +++ b/crates/graphql-orm-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "graphql-orm-macros" -version = "0.23.0" +version = "0.24.0" edition = "2024" authors = ["Toby Martin"] description = "Procedural macros for async-graphql and ORM-backed entities, relations, and CRUD operations." diff --git a/crates/graphql-orm-macros/README.md b/crates/graphql-orm-macros/README.md index 5bb87c5a..e6fde125 100644 --- a/crates/graphql-orm-macros/README.md +++ b/crates/graphql-orm-macros/README.md @@ -16,13 +16,13 @@ macro/runtime versions aligned: ```toml [dependencies] -graphql-orm = { git = "https://github.com/Dastari/graphql-orm.git", rev = "", version = "0.23.0", default-features = false, features = ["sqlite"] } +graphql-orm = { git = "https://github.com/Dastari/graphql-orm.git", rev = "", version = "0.24.0", default-features = false, features = ["sqlite"] } ``` Direct use is supported for tooling that needs the macro package: ```toml -graphql-orm-macros = { git = "https://github.com/Dastari/graphql-orm.git", rev = "", version = "0.23.0", default-features = false, features = ["sqlite"] } +graphql-orm-macros = { git = "https://github.com/Dastari/graphql-orm.git", rev = "", version = "0.24.0", default-features = false, features = ["sqlite"] } ``` The direct dependency still requires a compatible `graphql-orm` runtime in the diff --git a/crates/graphql-orm-macros/src/operations.rs b/crates/graphql-orm-macros/src/operations.rs index 0c442f93..988b8258 100644 --- a/crates/graphql-orm-macros/src/operations.rs +++ b/crates/graphql-orm-macros/src/operations.rs @@ -4867,12 +4867,10 @@ pub(crate) fn generate_graphql_operations( #(#retention_event_id_values)* ::graphql_orm::graphql::orm::composite_key_id(&bind_values) }} + } else if pk_is_bytes { + quote! { ::graphql_orm::graphql::orm::binary_key_id(&self.#pk_field) } } else { - if pk_is_bytes { - quote! { ::graphql_orm::graphql::orm::binary_key_id(&self.#pk_field) } - } else { - quote! { self.#pk_field.to_string() } - } + quote! { self.#pk_field.to_string() } }; let retention_surface = if entity_meta.retention_policy.is_some() { quote! { diff --git a/crates/graphql-orm-router/CHANGELOG.md b/crates/graphql-orm-router/CHANGELOG.md index 97295b46..ca5507e0 100644 --- a/crates/graphql-orm-router/CHANGELOG.md +++ b/crates/graphql-orm-router/CHANGELOG.md @@ -18,8 +18,9 @@ supersedes: [] from the optional resource-server matcher. - Updated the optional `agql-auth` adapter to 0.16.0 for consumer-supplied exact-only fixed scopes and resource-qualified scope patterns. -- Applied the configured matcher uniformly to fixed and rendered operation - requirements. Subgraph resolver authorization remains authoritative. +- Supplied the configured matcher to the existing authorization path shared by + fixed and rendered operation requirements; operation evaluation code is + unchanged. Subgraph resolver authorization remains authoritative. No descriptor, GraphQL schema, token wire, or stored-data migration is required. Hierarchical file configuration requires the `auth-agql` feature. diff --git a/crates/graphql-orm-router/MIGRATION.md b/crates/graphql-orm-router/MIGRATION.md index 60bee4cc..98b15912 100644 --- a/crates/graphql-orm-router/MIGRATION.md +++ b/crates/graphql-orm-router/MIGRATION.md @@ -108,8 +108,8 @@ New access tokens should use the OAuth space-delimited `scope` claim. The legacy `scopes` array is accepted only with `acceptLegacyScopes: true`; a token containing conflicting forms is rejected. The optional `auth-agql` feature is a validation/matching adapter only and introduces no issuer responsibilities. -It resolves `agql-auth` 0.15.0 at exact revision -`e841ffd382082ad7419be259fe957f949b956ff7`; hosts with a direct dependency +It resolves `agql-auth` 0.16.0 at exact revision +`3bc38cd94794f1e868a9cc3a5551047b95a32105`; hosts with a direct dependency must use the same source and revision. Configure legacy acceptance directly with `agql_auth::AccessTokenValidatorBuilder::legacy_scope_claims` before wrapping the validator in `AgqlAuthenticationProvider::new`. diff --git a/crates/graphql-orm-router/src/file_config.rs b/crates/graphql-orm-router/src/file_config.rs index a7ef47c4..d066cf3b 100644 --- a/crates/graphql-orm-router/src/file_config.rs +++ b/crates/graphql-orm-router/src/file_config.rs @@ -362,17 +362,23 @@ fn build_hierarchical_scope_matcher( exact_only_scopes.dedup(); exact_only_scope_patterns.sort(); exact_only_scope_patterns.dedup(); - let matcher = HierarchicalScopeMatch::new(HierarchicalScopeOptions { - separator, - wildcard, - wildcard_matches_multi_segment: wildcard_matches_multi_segment - .unwrap_or(defaults.wildcard_matches_multi_segment), - allow_universal_wildcard: allow_universal_wildcard - .unwrap_or(defaults.allow_universal_wildcard), - super_scopes, - exact_only_scopes, - exact_only_scope_patterns, - }); + let options = HierarchicalScopeOptions::default() + .with_separator(separator) + .with_wildcard(wildcard) + .with_wildcard_matches_multi_segment( + wildcard_matches_multi_segment.unwrap_or(defaults.wildcard_matches_multi_segment), + ) + .with_allow_universal_wildcard( + allow_universal_wildcard.unwrap_or(defaults.allow_universal_wildcard), + ) + .with_super_scopes(super_scopes) + .with_exact_only_scopes(exact_only_scopes) + .with_exact_only_scope_patterns(exact_only_scope_patterns); + let matcher = HierarchicalScopeMatch::new(options).map_err(|error| { + invalid(format!( + "scopeMatcher hierarchical options are invalid: {error}" + )) + })?; Ok(config.with_scope_matcher(Arc::new(AgqlScopeMatcher::new(Arc::new(matcher))))) } @@ -839,4 +845,26 @@ mod tests { .contains("kind `exact` does not accept hierarchical options") ); } + + #[cfg(feature = "auth-agql")] + #[test] + fn hierarchical_file_matcher_rejects_bare_wildcard_exact_only_pattern() { + let json = FILE.replacen( + "\"authentication\":", + r#""scopeMatcher": { + "kind": "hierarchical", + "allowUniversalWildcard": true, + "exactOnlyScopePatterns": ["*"] + }, + "authentication":"#, + 1, + ); + let error = RouterFileConfig::from_json(&json) + .unwrap() + .into_router_config_with(|name| { + Ok((name == "PRODUCTS_SCHEMA_TOKEN").then(|| "Bearer secret".to_owned())) + }) + .unwrap_err(); + assert!(error.to_string().contains("must not be the bare wildcard")); + } } diff --git a/crates/graphql-orm-storage/AGENTS.md b/crates/graphql-orm-storage/AGENTS.md index a725e9bd..8a41dc10 100644 --- a/crates/graphql-orm-storage/AGENTS.md +++ b/crates/graphql-orm-storage/AGENTS.md @@ -32,12 +32,12 @@ This crate is a reusable storage companion for applications that use `graphql-or ## Current Agent Handoff - Current crate version is `0.6.1`. -- The workspace resolves `graphql-orm` 0.22.1 and `graphql-orm-backup` 0.7.1 +- The workspace resolves `graphql-orm` 0.24.0 and `graphql-orm-backup` 0.7.1 through workspace path dependencies and one root `Cargo.lock`. Keep downstream companion packages on one reviewed monorepo revision. - `graphql-orm` owns the optional `agql-auth` integration, pinned at `agql-auth` 0.16.0 revision - `e6439aa034babb6827e9253977f760667ea6b7eb`; this crate does not depend on + `3bc38cd94794f1e868a9cc3a5551047b95a32105`; this crate does not depend on application authorization. - The storage provider boundary is now the streaming `BlobStore` trait. - `ObjectStorage` extends `BlobStore`; custom providers must implement `BlobStore` first. diff --git a/crates/graphql-orm/Cargo.toml b/crates/graphql-orm/Cargo.toml index 3d12db14..d971757f 100644 --- a/crates/graphql-orm/Cargo.toml +++ b/crates/graphql-orm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "graphql-orm" -version = "0.23.0" +version = "0.24.0" edition = "2024" description = "Runtime support crate for graphql-orm-macros" license = "MIT" @@ -65,7 +65,7 @@ futures = "0.3" geo = { version = "0.33", optional = true, default-features = false } geo-types = { version = "0.7", optional = true } geojson = { version = "1", optional = true, default-features = true } -graphql-orm-macros = { path = "../graphql-orm-macros", version = "0.23.0", default-features = false } +graphql-orm-macros = { path = "../graphql-orm-macros", version = "0.24.0", default-features = false } graphql-orm-operation-catalog = { workspace = true } rust_decimal = { workspace = true } serde = { version = "1", features = ["derive"] } diff --git a/crates/graphql-orm/README.md b/crates/graphql-orm/README.md index 9fd6d25a..6317338c 100644 --- a/crates/graphql-orm/README.md +++ b/crates/graphql-orm/README.md @@ -29,7 +29,7 @@ backend: ```toml [dependencies] -graphql-orm = { git = "https://github.com/Dastari/graphql-orm.git", rev = "", version = "0.23.0", default-features = false, features = ["sqlite"] } +graphql-orm = { git = "https://github.com/Dastari/graphql-orm.git", rev = "", version = "0.24.0", default-features = false, features = ["sqlite"] } ``` This unpublished package has no docs.rs release. Use this Git README and the diff --git a/crates/graphql-orm/src/lib.rs b/crates/graphql-orm/src/lib.rs index 8b2ac422..849c2aae 100644 --- a/crates/graphql-orm/src/lib.rs +++ b/crates/graphql-orm/src/lib.rs @@ -508,7 +508,6 @@ pub mod types; /// pub name: String, /// } /// -/// fn main() {} /// ``` /// /// ...but generate no single-key write helpers: @@ -535,10 +534,8 @@ pub mod types; /// pub name: String, /// } /// -/// fn main() { -/// let _ = CompositeWriteRecord::create; -/// let _ = CompositeWriteRecord::update_by_id; -/// } +/// let _ = CompositeWriteRecord::create; +/// let _ = CompositeWriteRecord::update_by_id; /// ``` #[cfg(all(feature = "sqlite", not(any(feature = "postgres", feature = "mssql"))))] pub mod generated_api_absence_probes {} diff --git a/crates/graphql-orm/tests/backend_coexistence_fixture.rs b/crates/graphql-orm/tests/backend_coexistence_fixture.rs index 98bfa324..53c58559 100644 --- a/crates/graphql-orm/tests/backend_coexistence_fixture.rs +++ b/crates/graphql-orm/tests/backend_coexistence_fixture.rs @@ -71,8 +71,8 @@ fn assert_direct_host_dependency_resolves_one_exact_agql_auth_universe() { .as_str() .expect("agql-auth source must be present"); assert!( - source.contains("rev=e6439aa034babb6827e9253977f760667ea6b7eb") - && source.ends_with("#e6439aa034babb6827e9253977f760667ea6b7eb"), + source.contains("rev=3bc38cd94794f1e868a9cc3a5551047b95a32105") + && source.ends_with("#3bc38cd94794f1e868a9cc3a5551047b95a32105"), "unexpected agql-auth source: {source}", ); } diff --git a/crates/graphql-orm/tests/fixtures/backend-coexistence/Cargo.lock b/crates/graphql-orm/tests/fixtures/backend-coexistence/Cargo.lock index 89e2e805..9b77c1d5 100644 --- a/crates/graphql-orm/tests/fixtures/backend-coexistence/Cargo.lock +++ b/crates/graphql-orm/tests/fixtures/backend-coexistence/Cargo.lock @@ -11,7 +11,7 @@ checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" [[package]] name = "agql-auth" version = "0.16.0" -source = "git+https://github.com/Dastari/agql-auth.git?rev=e6439aa034babb6827e9253977f760667ea6b7eb#e6439aa034babb6827e9253977f760667ea6b7eb" +source = "git+https://github.com/Dastari/agql-auth.git?rev=3bc38cd94794f1e868a9cc3a5551047b95a32105#3bc38cd94794f1e868a9cc3a5551047b95a32105" dependencies = [ "argon2", "async-graphql", @@ -925,7 +925,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -1304,7 +1304,7 @@ dependencies = [ [[package]] name = "graphql-orm" -version = "0.23.0" +version = "0.24.0" dependencies = [ "agql-auth", "async-graphql", @@ -1329,7 +1329,7 @@ dependencies = [ [[package]] name = "graphql-orm-ai" -version = "0.86.0" +version = "0.89.0" dependencies = [ "agql-auth", "async-graphql", @@ -1372,7 +1372,7 @@ dependencies = [ [[package]] name = "graphql-orm-macros" -version = "0.23.0" +version = "0.24.0" dependencies = [ "convert_case", "proc-macro2", @@ -2756,7 +2756,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -3363,10 +3363,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.3", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/crates/graphql-orm/tests/fixtures/backend-coexistence/auth-service/Cargo.toml b/crates/graphql-orm/tests/fixtures/backend-coexistence/auth-service/Cargo.toml index e8a1c7cd..63a48838 100644 --- a/crates/graphql-orm/tests/fixtures/backend-coexistence/auth-service/Cargo.toml +++ b/crates/graphql-orm/tests/fixtures/backend-coexistence/auth-service/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] -agql-auth = { git = "https://github.com/Dastari/agql-auth.git", rev = "e6439aa034babb6827e9253977f760667ea6b7eb", version = "0.16.0" } +agql-auth = { git = "https://github.com/Dastari/agql-auth.git", rev = "3bc38cd94794f1e868a9cc3a5551047b95a32105", version = "0.16.0" } async-graphql = { version = "7", features = ["dataloader", "uuid"] } graphql-orm = { path = "../../../../", default-features = false, features = [ "sqlite", diff --git a/crates/graphql-orm/tests/runtime_records.rs b/crates/graphql-orm/tests/runtime_records.rs index a06bc959..34553747 100644 --- a/crates/graphql-orm/tests/runtime_records.rs +++ b/crates/graphql-orm/tests/runtime_records.rs @@ -10,16 +10,16 @@ use graphql_orm::graphql::orm::{ use graphql_orm::graphql::orm::{RuntimeFieldState, RuntimeProjection}; #[cfg(feature = "sqlite")] -const _: () = assert!( +const _: [(); 1] = [(); ::RUNTIME_ROW_DECODING_SUPPORTED -); + as usize]; #[cfg(feature = "mssql")] -const _: () = assert!( +const _: [(); 1] = [(); !::RUNTIME_ROW_DECODING_SUPPORTED -); -const _: () = assert!( + as usize]; +const _: [(); 1] = [(); !::RUNTIME_ROW_DECODING_SUPPORTED -); + as usize]; fn collection_id(value: &str) -> CollectionId { CollectionId::new(value).expect("test collection ID") diff --git a/docs/plans/active/ai-production-readiness/README.md b/docs/plans/active/ai-production-readiness/README.md index f62b8685..3991e7d9 100644 --- a/docs/plans/active/ai-production-readiness/README.md +++ b/docs/plans/active/ai-production-readiness/README.md @@ -31,12 +31,12 @@ retention, or restore proofs remain closed. ## Dependencies -- `graphql-orm` 0.23.0 schema-module, transaction, fencing, operation-metadata, +- `graphql-orm` 0.24.0 schema-module, transaction, fencing, operation-metadata, and restore contracts. - `graphql-orm-backup` 0.7 snapshot, repository, verification, and restore orchestration. - `graphql-orm-storage` 0.6 streaming provider boundary. -- Exact external `agql-auth` 0.15 revision declared by the workspace. +- Exact external `agql-auth` 0.16 revision declared by the workspace. - Test-owned SQLite and disposable PostgreSQL infrastructure. ## Acceptance gates diff --git a/docs/reference/graphql-orm/agql-auth-bridge.md b/docs/reference/graphql-orm/agql-auth-bridge.md index d798a71a..2d486b36 100644 --- a/docs/reference/graphql-orm/agql-auth-bridge.md +++ b/docs/reference/graphql-orm/agql-auth-bridge.md @@ -16,13 +16,13 @@ supersedes: [] ## Dependency ```toml -graphql-orm = { git = "https://github.com/Dastari/graphql-orm.git", rev = "", version = "0.23.0", features = ["sqlite", "auth-agql"] } +graphql-orm = { git = "https://github.com/Dastari/graphql-orm.git", rev = "", version = "0.24.0", features = ["sqlite", "auth-agql"] } # Host applications may depend on agql-auth directly as well. The optional # graphql-orm auth-agql feature pins the exact upstream release: # git = "https://github.com/Dastari/agql-auth.git" -# rev = "e6439aa034babb6827e9253977f760667ea6b7eb" +# rev = "3bc38cd94794f1e868a9cc3a5551047b95a32105" # version = "0.16.0" -agql-auth = { git = "https://github.com/Dastari/agql-auth.git", rev = "e6439aa034babb6827e9253977f760667ea6b7eb", version = "0.16.0" } +agql-auth = { git = "https://github.com/Dastari/agql-auth.git", rev = "3bc38cd94794f1e868a9cc3a5551047b95a32105", version = "0.16.0" } ``` Both projects are intentionally Git-only. Cargo's crates.io packaging flow cannot package diff --git a/docs/reference/graphql-orm/backends.md b/docs/reference/graphql-orm/backends.md index 85fd0828..b022fb32 100644 --- a/docs/reference/graphql-orm/backends.md +++ b/docs/reference/graphql-orm/backends.md @@ -17,7 +17,7 @@ database schema. Schema ownership and migration behavior are controlled by runti ## Features ```toml -graphql-orm = { git = "https://github.com/Dastari/graphql-orm.git", rev = "", version = "0.23.0", default-features = false, features = ["sqlite"] } +graphql-orm = { git = "https://github.com/Dastari/graphql-orm.git", rev = "", version = "0.24.0", default-features = false, features = ["sqlite"] } ``` Available backend features: @@ -43,8 +43,8 @@ or managed RLS. See [Microsoft SQL Server](mssql.md). Optional non-backend features: -- `auth-agql` - optional one-way bridge from upstream `agql-auth` 0.15 - (`git` rev `e841ffd382082ad7419be259fe957f949b956ff7`) into `AuthSubject` / +- `auth-agql` - optional one-way bridge from upstream `agql-auth` 0.16 + (`git` rev `3bc38cd94794f1e868a9cc3a5551047b95a32105`) into `AuthSubject` / `DbAuthContext` and declared assurance evaluation. The `mssql` feature activates optional `tiberius`, `tokio-util`, and Tokio TCP support. Projects that diff --git a/docs/reference/graphql-orm/mssql.md b/docs/reference/graphql-orm/mssql.md index c7939822..1ef26a01 100644 --- a/docs/reference/graphql-orm/mssql.md +++ b/docs/reference/graphql-orm/mssql.md @@ -23,7 +23,7 @@ not provide an MSSQL driver. For a service that only uses SQL Server, select the `mssql` backend feature: ```toml -graphql-orm = { git = "https://github.com/Dastari/graphql-orm.git", rev = "", version = "0.23.0", default-features = false, features = ["mssql"] } +graphql-orm = { git = "https://github.com/Dastari/graphql-orm.git", rev = "", version = "0.24.0", default-features = false, features = ["mssql"] } ``` When exactly one of `sqlite`, `postgres`, or `mssql` is enabled, the legacy implicit backend remains diff --git a/docs/reference/workspace-packages.md b/docs/reference/workspace-packages.md index f671978f..1a4ca3d2 100644 --- a/docs/reference/workspace-packages.md +++ b/docs/reference/workspace-packages.md @@ -18,11 +18,11 @@ changes. | Package | Version | Path | Default features | Direct internal dependencies | | --- | --- | --- | --- | --- | -| `graphql-orm` | `0.23.0` | `crates/graphql-orm` | `sqlite` | `graphql-orm-macros`, `graphql-orm-operation-catalog`, `graphql-orm-router-protocol` (dev-only) | -| `graphql-orm-ai` | `0.88.0` | `crates/graphql-orm-ai` | `sqlite` | `graphql-orm`, `graphql-orm-ai-tool-profiles`, `graphql-orm-storage` | +| `graphql-orm` | `0.24.0` | `crates/graphql-orm` | `sqlite` | `graphql-orm-macros`, `graphql-orm-operation-catalog`, `graphql-orm-router-protocol` (dev-only) | +| `graphql-orm-ai` | `0.89.0` | `crates/graphql-orm-ai` | `sqlite` | `graphql-orm`, `graphql-orm-ai-tool-profiles`, `graphql-orm-storage` | | `graphql-orm-ai-tool-profiles` | `0.9.0` | `crates/graphql-orm-ai-tool-profiles` | none | `graphql-orm-operation-catalog`, `graphql-orm-router-protocol` (dev-only) | | `graphql-orm-backup` | `0.7.1` | `crates/graphql-orm-backup` | `local` | `graphql-orm` (optional), `graphql-orm-storage` | -| `graphql-orm-macros` | `0.23.0` | `crates/graphql-orm-macros` | `sqlite` | none | +| `graphql-orm-macros` | `0.24.0` | `crates/graphql-orm-macros` | `sqlite` | none | | `graphql-orm-operation-catalog` | `0.3.0` | `crates/graphql-orm-operation-catalog` | none | `graphql-orm-router-protocol` (optional) | | `graphql-orm-router` | `0.2.0` | `crates/graphql-orm-router` | none | `graphql-orm-router-protocol` | | `graphql-orm-router-protocol` | `0.2.1` | `crates/graphql-orm-router-protocol` | none | none | @@ -30,7 +30,7 @@ changes. External exact-revision dependency: -- `agql-auth` requirement `^0.16.0`, source `git+https://github.com/Dastari/agql-auth.git?rev=e6439aa034babb6827e9253977f760667ea6b7eb`, consumed by `graphql-orm`, `graphql-orm-ai`, `graphql-orm-router`. +- `agql-auth` requirement `^0.16.0`, source `git+https://github.com/Dastari/agql-auth.git?rev=3bc38cd94794f1e868a9cc3a5551047b95a32105`, consumed by `graphql-orm`, `graphql-orm-ai`, `graphql-orm-router`.