fix(shared): F-Q-004 unify error variants — Forbidden on QueueError, rename Unavailable→Backend

Brings QueueError / PubsubError / InvokeError in line with sibling
shape used by KvError / DocsError / FilesError / SecretsError:

- QueueError gains an explicit Forbidden variant (previously authz
  denial was squashed into Rejected("forbidden") in queue_service.rs:68,
  losing the structured variant a 403-translation layer would need).
- QueueError::Unavailable renamed → Backend.
- PubsubError::Unavailable renamed → Backend.
- InvokeError::Unavailable renamed → Backend.
- Call sites updated (queue_service, pubsub_service, invoke_service,
  Noop* stubs, From<PubsubRepoError> impl, one test assertion).
- New unit test verifying authed-denied → QueueError::Forbidden.

AUDIT.md anchor: F-Q-004.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-07 19:44:23 +02:00
parent 450badaabf
commit b192cf2cc9
6 changed files with 78 additions and 25 deletions

View File

@@ -129,7 +129,7 @@ impl PubsubServiceImpl {
impl From<PubsubRepoError> for PubsubError {
fn from(e: PubsubRepoError) -> Self {
Self::Unavailable(e.to_string())
Self::Backend(e.to_string())
}
}
@@ -227,7 +227,7 @@ impl PubsubService for PubsubServiceImpl {
let (Some(topic_repo), Some(secrets)) = (self.topics.as_ref(), self.secrets.as_ref())
else {
return Err(PubsubError::Unavailable(
return Err(PubsubError::Backend(
"subscriber tokens are not wired in".into(),
));
};
@@ -253,7 +253,7 @@ impl PubsubService for PubsubServiceImpl {
let registered = topic_repo
.get(cx.app_id, name)
.await
.map_err(|e| PubsubError::Unavailable(e.to_string()))?;
.map_err(|e| PubsubError::Backend(e.to_string()))?;
if !registered.is_some_and(|t| t.external_subscribable) {
return Err(PubsubError::SubscriberToken(format!(
"pubsub::subscriber_token: topic {name} is not externally subscribable"
@@ -264,7 +264,7 @@ impl PubsubService for PubsubServiceImpl {
let key = secrets
.get_or_create_signing_key(cx.app_id)
.await
.map_err(|e| PubsubError::Unavailable(e.to_string()))?;
.map_err(|e| PubsubError::Backend(e.to_string()))?;
let now = chrono::Utc::now().timestamp();
let claims = TokenClaims {
app_id: cx.app_id,
@@ -472,7 +472,7 @@ mod tests {
.publish_durable(&anon_cx(app), "user.created", serde_json::json!(1))
.await
.unwrap_err();
assert!(matches!(err, PubsubError::Unavailable(_)));
assert!(matches!(err, PubsubError::Backend(_)));
// Rollback: no partial fan-out survived.
assert_eq!(repo.written_count(), 0);
}