Skip to main content

RFC-047: Cross-Proxy Namespace Reservation with Lease Management

Abstract

This RFC specifies the cross-proxy namespace reservation system with JWT-based lease management for Prism. When clients request a namespace, proxies coordinate with the admin plane to ensure global uniqueness and issue time-limited leases. Clients receive JWT tokens scoped to their namespace that grant configuration permissions. Leases must be refreshed periodically with a grace period before expiration. The system supports standalone proxy mode for single-tenant deployments without admin plane dependencies.

Key Benefits:

  • Global Uniqueness: Admin plane ensures namespace names are unique across all proxy instances
  • Secure Configuration: JWT tokens grant namespace-scoped configuration permissions
  • Lease Lifecycle: TTL-based leases with refresh mechanism prevent abandoned namespaces
  • Graceful Degradation: Standalone proxies operate without admin plane coordination
  • Audit Trail: All reservations logged for compliance and debugging

Motivation

Problem Statement

Current namespace creation in Prism lacks coordination between proxy instances:

Problem 1: No Cross-Proxy Coordination

  • Multiple proxies can create namespaces with the same name independently
  • No global namespace registry
  • Name collisions lead to data routing errors and security violations

Problem 2: Unauthorized Configuration

  • No token-based authorization for namespace configuration
  • Any client can modify any namespace
  • No audit trail of namespace operations

Problem 3: Namespace Abandonment

  • Namespaces created but never used remain indefinitely
  • No mechanism to reclaim unused namespace allocations
  • Resource waste from zombie namespaces

Problem 4: Standalone vs Multi-Proxy Modes

  • Single-tenant deployments don't need admin plane coordination
  • No configuration option to disable admin plane dependency
  • Deployment complexity for simple use cases

Goals

  1. Cross-Proxy Reservation: Clients request namespaces through any proxy; admin plane ensures uniqueness
  2. JWT-Based Authorization: Namespace tokens grant configuration permissions scoped to specific namespace
  3. Lease Lifecycle Management: TTL-based leases with refresh mechanism and grace periods
  4. Configuration-Time Binding: Namespace reservation happens before pattern instantiation
  5. Standalone Mode: Proxies can operate without admin plane for single-tenant deployments
  6. Audit Logging: All reservation operations logged with client identity and timestamps

Non-Goals

  • Data Plane Authorization: This RFC covers namespace reservation/configuration only, not data access
  • Multi-Cluster Namespaces: Cross-cluster namespace coordination (see RFC-012)
  • Namespace Migration: Moving namespaces between proxies after creation
  • Hierarchical Namespaces: Nested or qualified namespace names (use flat naming)

Architecture Overview

System Components

┌─────────────────────────────────────────────────────────────────┐
│ Client Applications │
└────────────────────────┬────────────────────────────────────────┘
│ 1. Request namespace via any proxy

┌────────────────────────▼────────────────────────────────────────┐
│ Prism Proxy Fleet │
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
│ │ Proxy-01 │ │ Proxy-02 │ │ Proxy-03 │ │
│ │ (us-east-1a) │ │ (us-east-1b) │ │ (us-west-2a) │ │
│ └───────┬───────┘ └───────┬───────┘ └───────┬───────┘ │
│ │ │ │ │
└──────────┼──────────────────┼──────────────────┼────────────────┘
│ 2. Forward reservation request to admin plane

┌──────────▼──────────────────▼──────────────────▼────────────────┐
│ Admin Control Plane │
│ (Raft Consensus) │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Namespace Registry │ │
│ │ - Global uniqueness enforcement │ │
│ │ - Lease issuance and tracking │ │
│ │ - JWT token generation │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ SQLite Storage (ADR-054) │ │
│ │ - namespaces: name, owner, created_at, lease_expires │ │
│ │ - leases: lease_id, namespace, expires_at, jwt_token │ │
│ │ - audit_log: operation, actor, timestamp, result │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└──────────┬───────────────────────────────────────────────────────┘
│ 3. Return JWT token + lease info

┌──────────▼───────────────────────────────────────────────────────┐
│ Client with JWT Token │
│ - Can configure namespace (add patterns, backends) │
│ - Must refresh lease before expiration │
│ - Token scoped to single namespace │
└──────────────────────────────────────────────────────────────────┘

Data Flow: Namespace Reservation

Lease Lifecycle

Timeline of namespace lease:

t=0h t=12h t=23h t=24h t=25h
│────────────┼──────────────────┼──────────────┼────────────┼─────>
│ │ │ │ │
│ │ │ Grace Period │ Lease │ Namespace
│ Active │ Refresh OK │ Warning │ Expires │ Deleted
│ │ │ │ │
│ │ │ │ │
└─ Reserve └─ Client can └─ Client └─ JWT └─ Admin
Namespace refresh lease should invalid purges
anytime refresh namespace


States:
1. Active (0h-23h): Lease valid, client can operate normally
2. Grace Period (23h-24h): Lease about to expire, client receives warnings
3. Expired (24h+): JWT token invalid, client must re-reserve or extend
4. Purged (25h+): Namespace deleted if lease not renewed

Refresh Behavior:
- Client can refresh at any time during Active period
- Each refresh extends lease by TTL from current time
- Recommended: Refresh at 50% of TTL (12h for 24h lease)
- Grace period: Last 1 hour before expiration

Namespace Reservation Protocol

Protobuf Definition

// proto/prism/admin/v1/namespace.proto

syntax = "proto3";

package prism.admin.v1;

import "google/protobuf/duration.proto";
import "google/protobuf/timestamp.proto";

// Namespace reservation and lease management
service NamespaceReservationService {
// Reserve a new namespace globally
rpc ReserveNamespace(ReserveNamespaceRequest)
returns (ReserveNamespaceResponse);

// Refresh an existing namespace lease
rpc RefreshLease(RefreshLeaseRequest)
returns (RefreshLeaseResponse);

// Release a namespace (explicit cleanup)
rpc ReleaseNamespace(ReleaseNamespaceRequest)
returns (ReleaseNamespaceResponse);

// Get namespace information
rpc GetNamespace(GetNamespaceRequest)
returns (GetNamespaceResponse);

// List all namespaces (admin only)
rpc ListNamespaces(ListNamespacesRequest)
returns (ListNamespacesResponse);
}

message ReserveNamespaceRequest {
// Namespace name (must be unique globally)
string name = 1;

// Principal requesting namespace (user or service identity)
string principal = 2;

// Optional: Team or organization
string team = 3;

// Optional: Metadata for namespace
map<string, string> metadata = 4;

// Optional: Custom TTL (default: 24 hours)
google.protobuf.Duration lease_ttl = 5;
}

message ReserveNamespaceResponse {
// Success indicator
bool success = 1;

// JWT token for namespace operations
string jwt_token = 2;

// Lease identifier
string lease_id = 3;

// Lease expiration timestamp
google.protobuf.Timestamp expires_at = 4;

// Time until expiration
google.protobuf.Duration ttl = 5;

// Recommended refresh time (50% of TTL)
google.protobuf.Timestamp refresh_after = 6;

// Namespace details
NamespaceInfo namespace = 7;
}

message RefreshLeaseRequest {
// Namespace name
string namespace = 1;

// Current JWT token (for authorization)
string jwt_token = 2;

// Optional: Extend lease by custom duration
google.protobuf.Duration extend_by = 3;
}

message RefreshLeaseResponse {
bool success = 1;

// New JWT token (existing token becomes invalid)
string jwt_token = 2;

// New expiration time
google.protobuf.Timestamp expires_at = 3;

// Time until expiration
google.protobuf.Duration ttl = 4;
}

message ReleaseNamespaceRequest {
string namespace = 1;
string jwt_token = 2;
}

message ReleaseNamespaceResponse {
bool success = 1;
string message = 2;
}

message GetNamespaceRequest {
string name = 1;
}

message GetNamespaceResponse {
NamespaceInfo namespace = 1;
LeaseInfo lease = 2;
}

message ListNamespacesRequest {
// Pagination
int32 page_size = 1;
string page_token = 2;

// Filters
optional string owner_filter = 3;
optional string team_filter = 4;
optional bool include_expired = 5;
}

message ListNamespacesResponse {
repeated NamespaceInfo namespaces = 1;
string next_page_token = 2;
int32 total_count = 3;
}

message NamespaceInfo {
string name = 1;
string owner = 2;
string team = 3;
google.protobuf.Timestamp created_at = 4;
google.protobuf.Timestamp updated_at = 5;
map<string, string> metadata = 6;
NamespaceStatus status = 7;
}

message LeaseInfo {
string lease_id = 1;
string namespace = 2;
google.protobuf.Timestamp expires_at = 3;
google.protobuf.Timestamp last_refreshed_at = 4;
int32 refresh_count = 5;
bool in_grace_period = 6;
}

enum NamespaceStatus {
NAMESPACE_STATUS_UNSPECIFIED = 0;
NAMESPACE_STATUS_ACTIVE = 1;
NAMESPACE_STATUS_GRACE_PERIOD = 2;
NAMESPACE_STATUS_EXPIRED = 3;
NAMESPACE_STATUS_RELEASED = 4;
}

JWT Token Structure

Namespace JWT tokens follow RFC 7519 with custom claims:

{
"iss": "prism-admin",
"sub": "user@example.com",
"aud": ["prism-proxy"],
"exp": 1735084800,
"iat": 1735048800,
"nbf": 1735048800,
"jti": "lease_abc123",
"prism": {
"namespace": "orders-prod",
"lease_id": "abc123-def456-789",
"permissions": [
"namespace:configure",
"pattern:create",
"pattern:update",
"backend:bind"
],
"owner": "user@example.com",
"team": "payments-team"
}
}

Standard Claims:

  • iss: Issuer (prism-admin)
  • sub: Subject (principal who reserved namespace)
  • aud: Audience (prism-proxy instances)
  • exp: Expiration time (Unix timestamp)
  • iat: Issued at time
  • nbf: Not before time
  • jti: JWT ID (lease_id for revocation)

Custom Claims (prism):

  • namespace: Namespace name this token is scoped to
  • lease_id: Lease identifier for tracking
  • permissions: Operations this token grants
  • owner: Principal who owns this namespace
  • team: Team or organization

Implementation

Admin Plane: Namespace Registry

// cmd/prism-admin/namespace_registry.go

package main

import (
"context"
"crypto/rsa"
"database/sql"
"time"

"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
)

type NamespaceRegistry struct {
db *sql.DB
jwtPrivKey *rsa.PrivateKey
jwtPubKey *rsa.PublicKey
defaultTTL time.Duration
}

func NewNamespaceRegistry(db *sql.DB, privKey *rsa.PrivateKey, pubKey *rsa.PublicKey) *NamespaceRegistry {
return &NamespaceRegistry{
db: db,
jwtPrivKey: privKey,
jwtPubKey: pubKey,
defaultTTL: 24 * time.Hour,
}
}

func (nr *NamespaceRegistry) ReserveNamespace(
ctx context.Context,
req *ReserveNamespaceRequest,
) (*ReserveNamespaceResponse, error) {
// Validate namespace name
if err := validateNamespaceName(req.Name); err != nil {
return nil, err
}

// Start transaction
tx, err := nr.db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable})
if err != nil {
return nil, err
}
defer tx.Rollback()

// Check if namespace already exists
var existing string
err = tx.QueryRowContext(ctx,
"SELECT name FROM namespaces WHERE name = ? AND (lease_expires IS NULL OR lease_expires > ?)",
req.Name, time.Now()).Scan(&existing)

if err == nil {
// Namespace exists
return nil, ErrNamespaceExists
} else if err != sql.ErrNoRows {
return nil, err
}

// Generate lease
leaseID := uuid.New().String()
ttl := nr.defaultTTL
if req.LeaseTtl != nil {
ttl = req.LeaseTtl.AsDuration()
}
expiresAt := time.Now().Add(ttl)

// Generate JWT token
token, err := nr.generateJWT(req.Name, req.Principal, req.Team, leaseID, expiresAt)
if err != nil {
return nil, err
}

// Insert namespace
_, err = tx.ExecContext(ctx, `
INSERT INTO namespaces (name, owner, team, created_at, updated_at, lease_expires, metadata)
VALUES (?, ?, ?, ?, ?, ?, ?)
`, req.Name, req.Principal, req.Team, time.Now(), time.Now(), expiresAt, marshalMetadata(req.Metadata))
if err != nil {
return nil, err
}

// Insert lease
_, err = tx.ExecContext(ctx, `
INSERT INTO leases (lease_id, namespace, expires_at, last_refreshed_at, jwt_token, refresh_count)
VALUES (?, ?, ?, ?, ?, 0)
`, leaseID, req.Name, expiresAt, time.Now(), token)
if err != nil {
return nil, err
}

// Audit log
_, err = tx.ExecContext(ctx, `
INSERT INTO audit_log (operation, actor, namespace, timestamp, result, details)
VALUES (?, ?, ?, ?, ?, ?)
`, "ReserveNamespace", req.Principal, req.Name, time.Now(), "success", "")
if err != nil {
return nil, err
}

// Commit transaction
if err := tx.Commit(); err != nil {
return nil, err
}

return &ReserveNamespaceResponse{
Success: true,
JwtToken: token,
LeaseId: leaseID,
ExpiresAt: timestamppb.New(expiresAt),
Ttl: durationpb.New(ttl),
RefreshAfter: timestamppb.New(time.Now().Add(ttl / 2)),
Namespace: buildNamespaceInfo(req.Name, req.Principal, req.Team, req.Metadata),
}, nil
}

func (nr *NamespaceRegistry) RefreshLease(
ctx context.Context,
req *RefreshLeaseRequest,
) (*RefreshLeaseResponse, error) {
// Verify JWT token
claims, err := nr.verifyJWT(req.JwtToken)
if err != nil {
return nil, ErrInvalidToken
}

// Ensure token is for correct namespace
if claims.Namespace != req.Namespace {
return nil, ErrTokenNamespaceMismatch
}

// Start transaction
tx, err := nr.db.BeginTx(ctx, nil)
if err != nil {
return nil, err
}
defer tx.Rollback()

// Get current lease
var leaseID string
var currentExpiry time.Time
err = tx.QueryRowContext(ctx,
"SELECT lease_id, expires_at FROM leases WHERE namespace = ? AND lease_id = ?",
req.Namespace, claims.LeaseID).Scan(&leaseID, &currentExpiry)
if err != nil {
return nil, ErrLeaseNotFound
}

// Check if lease has expired
if time.Now().After(currentExpiry) {
return nil, ErrLeaseExpired
}

// Calculate new expiration
extendBy := nr.defaultTTL
if req.ExtendBy != nil {
extendBy = req.ExtendBy.AsDuration()
}
newExpiry := time.Now().Add(extendBy)

// Generate new JWT token
newToken, err := nr.generateJWT(req.Namespace, claims.Subject, claims.Team, leaseID, newExpiry)
if err != nil {
return nil, err
}

// Update lease
_, err = tx.ExecContext(ctx, `
UPDATE leases
SET expires_at = ?, last_refreshed_at = ?, jwt_token = ?, refresh_count = refresh_count + 1
WHERE lease_id = ? AND namespace = ?
`, newExpiry, time.Now(), newToken, leaseID, req.Namespace)
if err != nil {
return nil, err
}

// Update namespace lease_expires
_, err = tx.ExecContext(ctx,
"UPDATE namespaces SET lease_expires = ?, updated_at = ? WHERE name = ?",
newExpiry, time.Now(), req.Namespace)
if err != nil {
return nil, err
}

// Audit log
_, err = tx.ExecContext(ctx, `
INSERT INTO audit_log (operation, actor, namespace, timestamp, result)
VALUES (?, ?, ?, ?, ?)
`, "RefreshLease", claims.Subject, req.Namespace, time.Now(), "success")
if err != nil {
return nil, err
}

if err := tx.Commit(); err != nil {
return nil, err
}

return &RefreshLeaseResponse{
Success: true,
JwtToken: newToken,
ExpiresAt: timestamppb.New(newExpiry),
Ttl: durationpb.New(extendBy),
}, nil
}

func (nr *NamespaceRegistry) generateJWT(
namespace, principal, team, leaseID string,
expiresAt time.Time,
) (string, error) {
claims := jwt.MapClaims{
"iss": "prism-admin",
"sub": principal,
"aud": []string{"prism-proxy"},
"exp": expiresAt.Unix(),
"iat": time.Now().Unix(),
"nbf": time.Now().Unix(),
"jti": leaseID,
"prism": map[string]interface{}{
"namespace": namespace,
"lease_id": leaseID,
"permissions": []string{
"namespace:configure",
"pattern:create",
"pattern:update",
"backend:bind",
},
"owner": principal,
"team": team,
},
}

token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
return token.SignedString(nr.jwtPrivKey)
}

func (nr *NamespaceRegistry) verifyJWT(tokenString string) (*PrismClaims, error) {
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
return nil, ErrInvalidSigningMethod
}
return nr.jwtPubKey, nil
})

if err != nil || !token.Valid {
return nil, ErrInvalidToken
}

claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return nil, ErrInvalidClaims
}

prismClaims := claims["prism"].(map[string]interface{})

return &PrismClaims{
Subject: claims["sub"].(string),
Namespace: prismClaims["namespace"].(string),
LeaseID: prismClaims["lease_id"].(string),
Team: prismClaims["team"].(string),
}, nil
}

// Background job to clean up expired leases
func (nr *NamespaceRegistry) StartLeaseCleanupJob(ctx context.Context) {
ticker := time.NewTicker(1 * time.Hour)
defer ticker.Stop()

for {
select {
case <-ctx.Done():
return
case <-ticker.C:
nr.cleanupExpiredLeases(ctx)
}
}
}

func (nr *NamespaceRegistry) cleanupExpiredLeases(ctx context.Context) error {
// Delete namespaces with expired leases (after grace period)
gracePeriod := 1 * time.Hour
cutoff := time.Now().Add(-gracePeriod)

result, err := nr.db.ExecContext(ctx, `
DELETE FROM namespaces
WHERE lease_expires < ?
AND NOT EXISTS (
SELECT 1 FROM patterns WHERE patterns.namespace = namespaces.name
)
`, cutoff)

if err != nil {
return err
}

rows, _ := result.RowsAffected()
if rows > 0 {
log.Printf("Cleaned up %d expired namespaces", rows)
}

return nil
}

Proxy: Standalone Mode

// prism-proxy/src/namespace/mod.rs

use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
use jsonwebtoken::{EncodingKey, DecodingKey, Validation, Algorithm};

pub struct NamespaceManager {
mode: OperatingMode,
admin_client: Option<AdminClient>,
local_registry: Arc<RwLock<HashMap<String, NamespaceEntry>>>,
jwt_secret: Option<EncodingKey>,
}

pub enum OperatingMode {
Standalone, // No admin plane, local namespace management
Coordinated, // Admin plane coordination for multi-proxy deployment
}

impl NamespaceManager {
pub fn new_standalone(jwt_secret: String) -> Self {
Self {
mode: OperatingMode::Standalone,
admin_client: None,
local_registry: Arc::new(RwLock::new(HashMap::new())),
jwt_secret: Some(EncodingKey::from_secret(jwt_secret.as_bytes())),
}
}

pub fn new_coordinated(admin_endpoint: String) -> Self {
Self {
mode: OperatingMode::Coordinated,
admin_client: Some(AdminClient::new(admin_endpoint)),
local_registry: Arc::new(RwLock::new(HashMap::new())),
jwt_secret: None,
}
}

pub async fn reserve_namespace(
&self,
req: ReserveNamespaceRequest,
) -> Result<ReserveNamespaceResponse> {
match &self.mode {
OperatingMode::Standalone => self.reserve_standalone(req).await,
OperatingMode::Coordinated => self.reserve_coordinated(req).await,
}
}

async fn reserve_standalone(
&self,
req: ReserveNamespaceRequest,
) -> Result<ReserveNamespaceResponse> {
let mut registry = self.local_registry.write().await;

// Check if namespace exists
if registry.contains_key(&req.name) {
return Err(Error::NamespaceExists);
}

// Create namespace entry
let entry = NamespaceEntry {
name: req.name.clone(),
owner: req.principal.clone(),
created_at: Utc::now(),
lease_expires: None, // No expiration in standalone mode
};

registry.insert(req.name.clone(), entry);

// Generate JWT token (no expiration for standalone)
let token = self.generate_standalone_jwt(&req.name, &req.principal)?;

Ok(ReserveNamespaceResponse {
success: true,
jwt_token: token,
lease_id: "standalone".to_string(),
expires_at: None,
ttl: None,
refresh_after: None,
namespace: Some(NamespaceInfo {
name: req.name,
owner: req.principal,
created_at: Some(prost_types::Timestamp::from(Utc::now())),
..Default::default()
}),
})
}

async fn reserve_coordinated(
&self,
req: ReserveNamespaceRequest,
) -> Result<ReserveNamespaceResponse> {
// Forward to admin plane
let admin_client = self.admin_client.as_ref().unwrap();
let response = admin_client.reserve_namespace(req).await?;

// Cache namespace locally for fast lookups
let mut registry = self.local_registry.write().await;
registry.insert(response.namespace.as_ref().unwrap().name.clone(), NamespaceEntry {
name: response.namespace.as_ref().unwrap().name.clone(),
owner: response.namespace.as_ref().unwrap().owner.clone(),
created_at: Utc::now(),
lease_expires: response.expires_at.map(|ts| ts.try_into().unwrap()),
});

Ok(response)
}

fn generate_standalone_jwt(&self, namespace: &str, principal: &str) -> Result<String> {
let claims = serde_json::json!({
"iss": "prism-proxy-standalone",
"sub": principal,
"aud": ["prism-proxy"],
"prism": {
"namespace": namespace,
"lease_id": "standalone",
"permissions": [
"namespace:configure",
"pattern:create",
"pattern:update",
"backend:bind"
],
"owner": principal
}
});

let header = jsonwebtoken::Header::new(Algorithm::HS256);
let token = jsonwebtoken::encode(
&header,
&claims,
self.jwt_secret.as_ref().unwrap()
)?;

Ok(token)
}
}

Configuration

Proxy Configuration

# prism-proxy.yaml

# Operating mode
namespace_management:
mode: coordinated # or "standalone"

# For coordinated mode
admin_endpoint: "admin.prism.local:8981"
admin_tls:
enabled: true
ca_cert: /etc/prism/certs/ca.crt

# For standalone mode
jwt_secret: ${PRISM_JWT_SECRET}

# Lease refresh settings
refresh:
enabled: true
check_interval: 30m # Check lease expiry every 30 min
refresh_threshold: 0.5 # Refresh when 50% of TTL elapsed
grace_period_warning: 1h # Warn when entering grace period

Admin Configuration

# prism-admin.yaml

namespace_registry:
# JWT signing
jwt_private_key: /etc/prism/keys/jwt-private.pem
jwt_public_key: /etc/prism/keys/jwt-public.pem
jwt_algorithm: RS256

# Lease settings
default_lease_ttl: 24h
max_lease_ttl: 168h # 7 days
min_lease_ttl: 1h
grace_period: 1h # Grace period before deletion

# Cleanup job
cleanup:
enabled: true
interval: 1h
delete_after_expiry: 1h # Delete 1h after lease expires

Client Usage Examples

Reserve Namespace

# Python client example

from prism_client import PrismClient

client = PrismClient(proxy_endpoint="localhost:8980")

# Reserve namespace
response = client.reserve_namespace(
name="orders-prod",
principal="user@example.com",
team="payments-team",
metadata={"environment": "production", "region": "us-east-1"}
)

# Store JWT token
jwt_token = response.jwt_token
lease_id = response.lease_id
expires_at = response.expires_at

print(f"Namespace reserved: {response.namespace.name}")
print(f"Lease expires at: {expires_at}")
print(f"Refresh after: {response.refresh_after}")

# Save token for future operations
with open(".prism-token", "w") as f:
f.write(jwt_token)

Configure Namespace with JWT

# Configure namespace (requires JWT token)

client = PrismClient(
proxy_endpoint="localhost:8980",
jwt_token=jwt_token # From reservation
)

# Create KeyValue pattern in namespace
client.create_pattern(
namespace="orders-prod",
pattern_type="keyvalue",
backend="redis-main",
config={
"cache_ttl": "5m",
"consistency": "strong"
}
)

Refresh Lease

# Refresh lease before expiration

client = PrismClient(proxy_endpoint="localhost:8980")

response = client.refresh_lease(
namespace="orders-prod",
jwt_token=jwt_token
)

# Update stored token (old token is now invalid)
new_jwt_token = response.jwt_token
new_expires_at = response.expires_at

with open(".prism-token", "w") as f:
f.write(new_jwt_token)

print(f"Lease refreshed, new expiration: {new_expires_at}")

Automatic Lease Refresh

# Client SDK with automatic refresh

from prism_client import PrismClient
import threading
import time

class LeaseRefresher:
def __init__(self, client, namespace, token, expires_at):
self.client = client
self.namespace = namespace
self.token = token
self.expires_at = expires_at
self.running = True
self.thread = threading.Thread(target=self._refresh_loop)
self.thread.daemon = True
self.thread.start()

def _refresh_loop(self):
while self.running:
# Calculate time until refresh (50% of TTL)
ttl = (self.expires_at - time.time())
refresh_in = ttl * 0.5

if refresh_in > 0:
time.sleep(refresh_in)

# Refresh lease
try:
response = self.client.refresh_lease(
namespace=self.namespace,
jwt_token=self.token
)

self.token = response.jwt_token
self.expires_at = response.expires_at.timestamp()

print(f"Lease refreshed for {self.namespace}")
except Exception as e:
print(f"Failed to refresh lease: {e}")
# Exponential backoff retry
time.sleep(60)

def stop(self):
self.running = False
self.thread.join()

# Usage
client = PrismClient(proxy_endpoint="localhost:8980")
response = client.reserve_namespace(name="orders-prod", principal="user@example.com")

refresher = LeaseRefresher(
client=client,
namespace="orders-prod",
token=response.jwt_token,
expires_at=response.expires_at.timestamp()
)

# Lease will be refreshed automatically in background

Security Considerations

JWT Token Security

Storage:

  • Tokens should be stored securely (e.g., keychain, environment variables)
  • Never commit tokens to version control
  • Rotate tokens regularly by refreshing leases

Transmission:

  • Always use TLS for gRPC connections
  • Tokens sent in gRPC metadata: authorization: Bearer <token>
  • Admin plane validates token signature on every request

Revocation:

  • Tokens become invalid after expiration
  • Refreshing lease invalidates old token
  • Admin can revoke leases explicitly
  • Lease table tracks active tokens for revocation

Authorization Model

Token Permissions:

  • namespace:configure: Modify namespace settings
  • pattern:create: Create new patterns in namespace
  • pattern:update: Update existing patterns
  • pattern:delete: Delete patterns
  • backend:bind: Bind backends to patterns

Enforcement:

  • Proxy validates JWT signature using admin's public key
  • Proxy checks prism.namespace claim matches requested namespace
  • Proxy verifies exp claim has not passed
  • Proxy checks prism.permissions contains required permission

Audit Logging

All namespace operations logged:

CREATE TABLE audit_log (
id UUID PRIMARY KEY,
timestamp TIMESTAMPTZ NOT NULL,
operation TEXT NOT NULL, -- ReserveNamespace, RefreshLease, etc.
actor TEXT NOT NULL, -- Principal from JWT
namespace TEXT, -- Affected namespace
result TEXT NOT NULL, -- success, error
error_message TEXT,
request_metadata JSONB,
INDEX idx_audit_timestamp ON audit_log(timestamp DESC),
INDEX idx_audit_namespace ON audit_log(namespace),
INDEX idx_audit_actor ON audit_log(actor)
);

Database Schema

-- Namespace registry
CREATE TABLE namespaces (
name TEXT PRIMARY KEY,
owner TEXT NOT NULL,
team TEXT,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL,
lease_expires TIMESTAMPTZ,
metadata JSONB,
status TEXT DEFAULT 'active'
);

CREATE INDEX idx_namespaces_lease_expires ON namespaces(lease_expires);
CREATE INDEX idx_namespaces_owner ON namespaces(owner);

-- Lease tracking
CREATE TABLE leases (
lease_id TEXT PRIMARY KEY,
namespace TEXT NOT NULL REFERENCES namespaces(name) ON DELETE CASCADE,
expires_at TIMESTAMPTZ NOT NULL,
last_refreshed_at TIMESTAMPTZ NOT NULL,
jwt_token TEXT NOT NULL,
refresh_count INTEGER DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_leases_namespace ON leases(namespace);
CREATE INDEX idx_leases_expires_at ON leases(expires_at);

-- Audit log
CREATE TABLE audit_log (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
timestamp TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
operation TEXT NOT NULL,
actor TEXT NOT NULL,
namespace TEXT,
result TEXT NOT NULL,
error_message TEXT,
details JSONB
);

CREATE INDEX idx_audit_timestamp ON audit_log(timestamp DESC);
CREATE INDEX idx_audit_namespace ON audit_log(namespace);
CREATE INDEX idx_audit_actor ON audit_log(actor);

Revision History

  • 2025-10-25: Initial draft - Cross-proxy namespace reservation with JWT-based lease management