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 .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ jobs:
contents: read
id-token: write
env:
RUSTUP_TOOLCHAIN: nightly-2026-01-22
RUSTUP_TOOLCHAIN: nightly-2026-04-16
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
Expand Down Expand Up @@ -393,7 +393,7 @@ jobs:
name: Dylint Tests
runs-on: ubuntu-latest
env:
RUSTUP_TOOLCHAIN: nightly-2026-01-22
RUSTUP_TOOLCHAIN: nightly-2026-04-16
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
Expand Down
8 changes: 8 additions & 0 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions gears/credstore/credstore/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ name = "credstore"
workspace = true

[dependencies]
# Trait boundaries of consumed SDKs are `CanonicalError` (ADR 0005).
toolkit-canonical-errors = { workspace = true }
credstore-sdk = { workspace = true }
types-registry-sdk = { workspace = true }

Expand Down
9 changes: 6 additions & 3 deletions gears/credstore/credstore/src/domain/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,12 @@ pub enum DomainError {
// introduce typed variants) so `.source()` returns the original error, then
// remove these allows.
#[allow(unknown_lints, de1302_error_from_to_string)]
impl From<types_registry_sdk::TypesRegistryError> for DomainError {
fn from(e: types_registry_sdk::TypesRegistryError) -> Self {
Self::Internal(e.to_string())
impl From<toolkit_canonical_errors::CanonicalError> for DomainError {
fn from(e: toolkit_canonical_errors::CanonicalError) -> Self {
// Prefer the in-process diagnostic for `Internal`/`Unknown` (the wire
// strips it); other categories carry their message in `detail()` via
// `to_string()`.
Self::Internal(e.diagnostic().map_or_else(|| e.to_string(), str::to_owned))
}
}

Expand Down
8 changes: 5 additions & 3 deletions gears/credstore/credstore/src/domain/error_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ use toolkit::plugins::ChoosePluginError;

use super::*;

// ── From<TypesRegistryError> ─────────────────────────────────────────────
// ── From<CanonicalError> ─────────────────────────────────────────────────

#[test]
fn from_types_registry_error_becomes_internal() {
let src = types_registry_sdk::TypesRegistryError::internal("oops");
fn from_canonical_error_becomes_internal() {
// The types-registry trait boundary is now `CanonicalError` (ADR 0005);
// credstore folds any such error into its opaque `Internal`.
let src = types_registry_sdk::testing::internal("oops");
let dst = DomainError::from(src);
assert!(matches!(dst, DomainError::Internal(_)));
}
Expand Down
13 changes: 5 additions & 8 deletions gears/credstore/credstore/src/domain/service_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ use std::sync::Arc;

use credstore_sdk::{OwnerId, SecretMetadata, SecretValue, SharingMode, TenantId};
use toolkit::client_hub::{ClientHub, ClientScope};
use types_registry_sdk::TypesRegistryError;
use types_registry_sdk::testing::{MockTypesRegistryClient, make_test_instance};
use types_registry_sdk::testing::{self, MockTypesRegistryClient, make_test_instance};

use super::*;
use crate::domain::test_support::{MockPlugin, test_ctx};
Expand Down Expand Up @@ -80,9 +79,8 @@ async fn get_retries_resolution_on_each_call_when_registry_absent() {
// Use a failing registry (not an empty hub) so list() is actually invoked and
// we can assert the call count proves no caching.
let hub = Arc::new(ClientHub::default());
let registry = Arc::new(
MockTypesRegistryClient::new().with_list_error(TypesRegistryError::internal("unavailable")),
);
let registry =
Arc::new(MockTypesRegistryClient::new().with_list_error(testing::internal("unavailable")));
hub.register::<dyn TypesRegistryClient>(registry.clone() as Arc<dyn TypesRegistryClient>);
let svc = Service::new(hub, "constructorfabric".into());
let key = SecretRef::new("my-key").unwrap();
Expand Down Expand Up @@ -147,9 +145,8 @@ async fn resolve_plugin_returns_invalid_when_content_malformed() {
#[tokio::test]
async fn resolve_plugin_returns_internal_when_registry_list_fails() {
let hub = Arc::new(ClientHub::default());
let registry: Arc<dyn TypesRegistryClient> = Arc::new(
MockTypesRegistryClient::new().with_list_error(TypesRegistryError::internal("db down")),
);
let registry: Arc<dyn TypesRegistryClient> =
Arc::new(MockTypesRegistryClient::new().with_list_error(testing::internal("db down")));
hub.register::<dyn TypesRegistryClient>(registry);

let svc = Service::new(hub, "constructorfabric".into());
Expand Down
6 changes: 5 additions & 1 deletion gears/mini-chat/mini-chat/src/gts/permissions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,11 @@ mod tests {
.get_instance("gts.cf.toolkit.authz.permission.v1~cf.mini_chat._.nonexistent.v1")
.await;
assert!(
result.is_err() && result.unwrap_err().is_not_found(),
result.is_err()
&& matches!(
result.unwrap_err(),
toolkit_canonical_errors::CanonicalError::NotFound { .. }
),
"unknown permission id must produce NotFound"
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -847,7 +847,10 @@ impl<R: TenantRepo> BootstrapService<R> {
{
Ok(Ok(entity)) => entity,
Ok(Err(err)) => {
if err.is_not_found() {
if matches!(
err,
toolkit_canonical_errors::CanonicalError::NotFound { .. }
) {
emit_metric(
AM_BOOTSTRAP_LIFECYCLE,
MetricKind::Counter,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ use async_trait::async_trait;
use std::collections::HashMap;
use std::sync::Arc;
use time::OffsetDateTime;
use toolkit_canonical_errors::CanonicalError;
use types_registry_sdk::{
GtsInstance, GtsTypeId, GtsTypeSchema, InstanceQuery, RegisterResult, TypeSchemaQuery,
TypesRegistryClient, TypesRegistryError,
TypesRegistryClient,
};
use uuid::Uuid;

Expand Down Expand Up @@ -127,7 +128,7 @@ impl StubTypesRegistry {
/// [`crate::domain::gts_validation::validate_tenant_name_via_gts`]
/// — called from `insert_root_provisioning` — has a registered
/// schema to validate `cfg.root_name` against. Without this the
/// helper would short-circuit on `GtsTypeSchemaNotFound` and the
/// helper would short-circuit on `CanonicalError::NotFound` and the
/// bounds would not gate the saga in tests.
fn canned_tenant_schema() -> GtsTypeSchema {
GtsTypeSchema::try_new(
Expand All @@ -153,16 +154,16 @@ impl TypesRegistryClient for StubTypesRegistry {
async fn register(
&self,
_entities: Vec<serde_json::Value>,
) -> Result<Vec<RegisterResult>, TypesRegistryError> {
) -> Result<Vec<RegisterResult>, CanonicalError> {
unreachable!("not exercised by bootstrap")
}
async fn register_type_schemas(
&self,
_type_schemas: Vec<serde_json::Value>,
) -> Result<Vec<RegisterResult>, TypesRegistryError> {
) -> Result<Vec<RegisterResult>, CanonicalError> {
unreachable!("not exercised by bootstrap")
}
async fn get_type_schema(&self, type_id: &str) -> Result<GtsTypeSchema, TypesRegistryError> {
async fn get_type_schema(&self, type_id: &str) -> Result<GtsTypeSchema, CanonicalError> {
// Dispatch by the two ids bootstrap consults:
// * `ROOT_TENANT_TYPE` — preflight tenant-type eligibility
// (`preflight_root_tenant_type`).
Expand All @@ -182,55 +183,55 @@ impl TypesRegistryClient for StubTypesRegistry {
async fn get_type_schema_by_uuid(
&self,
_type_uuid: Uuid,
) -> Result<GtsTypeSchema, TypesRegistryError> {
) -> Result<GtsTypeSchema, CanonicalError> {
unreachable!("not exercised by bootstrap")
}
async fn get_type_schemas(
&self,
_type_ids: Vec<String>,
) -> HashMap<String, Result<GtsTypeSchema, TypesRegistryError>> {
) -> HashMap<String, Result<GtsTypeSchema, CanonicalError>> {
unreachable!("not exercised by bootstrap")
}
async fn get_type_schemas_by_uuid(
&self,
_type_uuids: Vec<Uuid>,
) -> HashMap<Uuid, Result<GtsTypeSchema, TypesRegistryError>> {
) -> HashMap<Uuid, Result<GtsTypeSchema, CanonicalError>> {
unreachable!("not exercised by bootstrap")
}
async fn list_type_schemas(
&self,
_query: TypeSchemaQuery,
) -> Result<Vec<GtsTypeSchema>, TypesRegistryError> {
) -> Result<Vec<GtsTypeSchema>, CanonicalError> {
unreachable!("not exercised by bootstrap")
}
async fn register_instances(
&self,
_instances: Vec<serde_json::Value>,
) -> Result<Vec<RegisterResult>, TypesRegistryError> {
) -> Result<Vec<RegisterResult>, CanonicalError> {
unreachable!("not exercised by bootstrap")
}
async fn get_instance(&self, _id: &str) -> Result<GtsInstance, TypesRegistryError> {
async fn get_instance(&self, _id: &str) -> Result<GtsInstance, CanonicalError> {
unreachable!("not exercised by bootstrap")
}
async fn get_instance_by_uuid(&self, _uuid: Uuid) -> Result<GtsInstance, TypesRegistryError> {
async fn get_instance_by_uuid(&self, _uuid: Uuid) -> Result<GtsInstance, CanonicalError> {
unreachable!("not exercised by bootstrap")
}
async fn get_instances(
&self,
_ids: Vec<String>,
) -> HashMap<String, Result<GtsInstance, TypesRegistryError>> {
) -> HashMap<String, Result<GtsInstance, CanonicalError>> {
unreachable!("not exercised by bootstrap")
}
async fn get_instances_by_uuid(
&self,
_uuids: Vec<Uuid>,
) -> HashMap<Uuid, Result<GtsInstance, TypesRegistryError>> {
) -> HashMap<Uuid, Result<GtsInstance, CanonicalError>> {
unreachable!("not exercised by bootstrap")
}
async fn list_instances(
&self,
_query: InstanceQuery,
) -> Result<Vec<GtsInstance>, TypesRegistryError> {
) -> Result<Vec<GtsInstance>, CanonicalError> {
unreachable!("not exercised by bootstrap")
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
//! against the supplied field values — adapting the
//! `cf-resource-group::domain::validation::validate_metadata_via_gts`
//! pattern to AM's needs with one deliberate divergence: AM maps a
//! non-not-found `TypesRegistryError` to
//! non-not-found `CanonicalError` to
//! [`DomainError::ServiceUnavailable`] (HTTP 503) rather than
//! `DomainError::validation` (HTTP 400). A registry outage is an
//! infrastructure event, not a payload problem, and the 503 envelope
Expand Down Expand Up @@ -55,14 +55,13 @@
//! 1 AND 255)` constraint is the last-line guard and bootstrap
//! must succeed before the catalog is seeded.
//!
//! Failures fall into four categories:
//! * `validate_new_user_payload_via_gts` +
//! `TypesRegistryError::GtsTypeSchemaNotFound` →
//! Failures fall into four categories (the types-registry client returns
//! [`CanonicalError`](toolkit_canonical_errors::CanonicalError) per ADR 0005):
//! * `validate_new_user_payload_via_gts` + `CanonicalError::NotFound` →
//! [`DomainError::ServiceUnavailable`] (fail-closed, see above).
//! * `validate_tenant_name_via_gts` +
//! `TypesRegistryError::GtsTypeSchemaNotFound` → `Ok(())`
//! * `validate_tenant_name_via_gts` + `CanonicalError::NotFound` → `Ok(())`
//! (DB CHECK + bootstrap-before-catalog ordering).
//! * Other `TypesRegistryError` (transport / availability) →
//! * Any other `CanonicalError` (transport / availability) →
//! [`DomainError::ServiceUnavailable`] for both helpers.
//! * Schema returned but `jsonschema::validator_for` rejects it →
//! [`DomainError::Internal`] (catalog drift — operator action
Expand All @@ -80,7 +79,8 @@

use account_management_sdk::IdpNewUser;
use serde_json::Value;
use types_registry_sdk::{TypesRegistryClient, TypesRegistryError};
use toolkit_canonical_errors::CanonicalError;
use types_registry_sdk::TypesRegistryClient;

use crate::domain::error::DomainError;

Expand Down Expand Up @@ -199,7 +199,7 @@ async fn lookup_effective_properties(
) -> Result<Option<std::collections::BTreeMap<String, Value>>, DomainError> {
match types_registry.get_type_schema(type_id).await {
Ok(schema) => Ok(Some(schema.effective_properties())),
Err(TypesRegistryError::GtsTypeSchemaNotFound(_)) => Ok(None),
Err(CanonicalError::NotFound { .. }) => Ok(None),
Err(err) => Err(DomainError::service_unavailable(format!(
"GTS type schema lookup failed for `{type_id}`: {err}"
))),
Expand Down
Loading
Loading