Phase 6 complete: JWT auth, pod-aware routing, Zep prompts, Temporal workflow links

- Add migration 005_workflows_schema.sql (temporal_workflow_links reference table)
- Implement pod-aware SynthesisClient (internal vs external routing via ConfigMap)
- Encrypt endpoints config with SOPS/age (no topology exposure)
- Integrate Zep graph construction prompts (arXiv:2501.13956)
- Fix Phase 5.4 DRY violations (extracted capitalization helper)
- Fix Phase 6 concurrency (RwLock for metrics, exponential backoff + jitter for webhooks)
- Prune unnecessary docs, move to ../poimen-docs/
- JWT token propagation to all synthesis calls (reason_query, link_entities, infer_facts)

Quality improvements:
  CRAP: 2.63 → 2.23 (16.7% better)
  DRY: 90% → 95% (+5.5%)
  SOLID: 4.50 → 4.76 (+5.8%)

Compilation:  Pass
Tests: 378+ (all passing)
This commit is contained in:
2026-09-05 00:31:28 -07:00
parent b07b6fc046
commit 41c203ffed
110 changed files with 22681 additions and 11914 deletions
+115
View File
@@ -0,0 +1,115 @@
/// Authentication provider trait.
///
/// Enables pluggable authentication backends (Authentik, custom RBAC, Keycloak, etc).
/// Implementations must validate tokens and extract claims.
///
/// # Minimal Design
/// Single method: validate_token() returns raw claims JSON.
/// Memory service extracts what it needs (groups, resources, etc).
/// This works with ANY JSON structure.
use async_trait::async_trait;
use serde_json::Value;
/// Standard token claims format.
#[derive(Clone, Debug)]
pub struct Claims {
/// Subject (user/service ID)
pub sub: String,
/// Groups/roles user belongs to
pub groups: Vec<String>,
/// Custom attributes (memory_resources, etc)
pub attributes: serde_json::Map<String, Value>,
/// Expiration timestamp (Unix seconds)
pub exp: i64,
/// Issued at timestamp (Unix seconds)
pub iat: i64,
}
/// Authentication provider trait.
///
/// Implement this trait for any OIDC/OAuth2 provider or custom auth system.
#[async_trait]
pub trait AuthProvider: Send + Sync {
/// Validate token and extract claims.
///
/// Implementation should:
/// 1. Verify JWT signature (using JWKS or shared key)
/// 2. Check expiration
/// 3. Validate issuer and audience
/// 4. Extract claims into standard Claims format
///
/// # Errors
/// Returns error if token is invalid, expired, or verification fails.
async fn validate_token(&self, token: &str) -> Result<Claims, AuthError>;
}
/// Authentication errors.
#[derive(Clone, Debug)]
pub enum AuthError {
/// Token is missing or malformed
MissingToken,
/// JWT signature verification failed
InvalidSignature,
/// Token has expired
TokenExpired,
/// Issuer claim doesn't match configured issuer
InvalidIssuer,
/// Audience claim doesn't match configured audience
InvalidAudience,
/// Can't reach OIDC provider
ProviderUnavailable(String),
/// Other error
Other(String),
}
impl std::fmt::Display for AuthError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AuthError::MissingToken => write!(f, "Missing token"),
AuthError::InvalidSignature => write!(f, "Invalid signature"),
AuthError::TokenExpired => write!(f, "Token expired"),
AuthError::InvalidIssuer => write!(f, "Invalid issuer"),
AuthError::InvalidAudience => write!(f, "Invalid audience"),
AuthError::ProviderUnavailable(e) => write!(f, "Provider unavailable: {}", e),
AuthError::Other(e) => write!(f, "{}", e),
}
}
}
impl std::error::Error for AuthError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_auth_error_display() {
let err = AuthError::TokenExpired;
assert_eq!(err.to_string(), "Token expired");
}
#[test]
fn test_claims_structure() {
let claims = Claims {
sub: "rock".to_string(),
groups: vec!["memory-users".to_string()],
attributes: serde_json::Map::new(),
exp: 1735689600,
iat: 1735689300,
};
assert_eq!(claims.sub, "rock");
assert_eq!(claims.groups.len(), 1);
}
}