Files
aether/.product-strategy/NAMESPACE_ISOLATION_QUICK_REFERENCE.md
Hugo Nijhuis 271f5db444
Some checks failed
CI / build (push) Successful in 21s
CI / integration (push) Failing after 2m1s
Move product strategy documentation to .product-strategy directory
Organize all product strategy and domain modeling documentation into a
dedicated .product-strategy directory for better separation from code.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-12 23:57:20 +01:00

10 KiB

Namespace Isolation: Quick Reference Card

Core Invariant

Events in namespace X must be invisible to queries from namespace Y (except via explicit wildcard subscriptions by trusted components)


Three Enforcement Layers

Layer 1: Memory (EventBus)

Publish("tenant-a", event)
  → exactSubscribers["tenant-a"] ← event ✓
  → exactSubscribers["tenant-b"] (blocked) ✗
  → wildcardSubscribers (">") ← event ✓ (intentional)

Layer 2: Storage (JetStreamEventStore)

NewJetStreamEventStoreWithNamespace(conn, "events", "tenant-a")
  → Stream: "tenant_a_events" (separate NATS stream)
GetEvents(store1) → queries "tenant_a_events" only ✓
GetEvents(store2) → queries "tenant_b_events" only (no cross-namespace) ✓

Layer 3: Network (NATSEventBus)

Publish("tenant-a", event)
  → NATS subject: "aether.events.tenant-a"
Subscribe("tenant-b")
  → NATS subject: "aether.events.tenant-b" (blocked at NATS) ✗
Subscribe("*")
  → NATS subject: "aether.events.*" (intentional wildcard) ✓

Value Objects

Object Type Example Purpose
Namespace string "tenant-a", "prod.orders" Logical boundary identifier
SubjectPattern string "", "prod.", "prod.>" NATS pattern for matching namespaces
SubscriptionFilter struct {EventTypes: ["OrderPlaced"]} Optional filtering (AND logic)

Commands at a Glance

Command Input Output Enforces
DefineNamespace string Namespace Namespace Name Safety
PublishToNamespace namespace, event delivery to subscribers Namespace Boundary Isolation
SubscribeToNamespace pattern, filter channel Isolation (exact) or Bypass (wildcard)
CreateNamespacedEventStore streamName, namespace EventStore Namespace Boundary Isolation

Policies (If X Then Y)

Trigger Action Invariant
Publish to namespace Deliver to exact subscribers of that namespace Isolation enforced
Wildcard subscription Deliver from all matching namespaces Isolation bypassed (intentional)
Save event Store in namespace-scoped stream Storage isolation
Namespace format invalid Sanitize (spaces, dots, *, >) to underscores Name safety

Code Locations (Key Files)

/aether/
├─ eventbus.go (268 lines)
│  ├─ exactSubscribers[ns]  (isolation boundary)
│  ├─ wildcardSubscribers   (intentional bypass)
│  └─ Publish() routing logic
│
├─ nats_eventbus.go (231 lines)
│  ├─ NATS subject: aether.events.{namespace}
│  └─ Cross-node replication with pattern support
│
├─ pattern.go (197 lines)
│  ├─ MatchNamespacePattern() (NATS-native matching)
│  ├─ SubscriptionFilter (EventTypes + ActorPattern)
│  └─ IsWildcardPattern() detector
│
└─ store/jetstream.go (382 lines)
   ├─ Namespace → stream name mapping
   ├─ sanitizeSubject() (security)
   └─ Per-namespace streams

Pattern Matching Rules

Namespace: "prod.orders.acme" (3 tokens)

Pattern → Match?
────────────────
"prod.orders.acme"   → ✓ (exact)
"prod.orders.*"      → ✓ (* matches "acme")
"prod.>"             → ✓ (> matches "orders.acme")
"prod.*"             → ✗ (* matches single token only)
"*"                  → ✗ (* doesn't match dots)
">"                  → ✓ (> matches everything)

Rules:
• "." separates tokens
• "*" = exactly one token (no dots)
• ">" = one or more tokens (end only)
• Exact string = exact match

Common Operations

Exact Subscription (Isolation Enforced)

bus := aether.NewEventBus()
ch := bus.Subscribe("tenant-abc")     // Only tenant-abc events
event := Event{ActorID: "order-123", ...}
bus.Publish("tenant-abc", event)      // ch receives ✓
bus.Publish("tenant-def", event)      // ch receives nothing ✗

Wildcard Subscription (Isolation Bypassed)

bus := aether.NewEventBus()
ch := bus.Subscribe(">")               // ALL namespaces
bus.Publish("tenant-abc", event)       // ch receives ✓
bus.Publish("tenant-def", event)       // ch receives ✓
// CAUTION: Only for trusted logging/auditing code

Namespaced Event Store

store1 := store.NewJetStreamEventStoreWithNamespace(
    natsConn, "events", "tenant-a")
store2 := store.NewJetStreamEventStoreWithNamespace(
    natsConn, "events", "tenant-b")

store1.SaveEvent(event)                // Goes to "tenant_a_events" stream
store2.GetEvents("order-123", 0)       // Queries "tenant_b_events" only
                                       // event not found (different stream)

Filtered Subscription

filter := &aether.SubscriptionFilter{
    EventTypes: []string{"OrderPlaced", "OrderShipped"},  // OR logic
    ActorPattern: "order-*",                             // AND with types
}
ch := bus.SubscribeWithFilter("tenant-a", filter)
// Receives only OrderPlaced or OrderShipped events for order-* actors

Sanitization Examples

Input Purpose Output Example
"prod abc" Namespace "prod_abc" Spaces → underscores
"prod.orders" Namespace "prod_orders" Dots → underscores
"tenant*abc" Namespace "tenant_abc" Stars → underscores
"tenant>abc" Namespace "tenant_abc" Greater → underscores

Why? NATS subject tokens can't contain these characters.


Invariants: Verification Checklist

  • Events in namespace X are NOT visible to namespace Y (exact subscriptions)
  • GetEvents(store_a) does NOT return events from store_b
  • NATS subjects are correctly namespaced (aether.events.{namespace})
  • Pattern matching works correctly (* = one token, > = multiple)
  • Wildcard subscriptions are documented and audited
  • Namespace names are sanitized before storage
  • Cross-node publishing respects namespace boundaries

Anti-Patterns (What NOT to Do)

Don't ✓ Do Instead Why
Subscribe(getTenantPattern()) Validate pattern is exact (or audited) Wildcards can leak data
Accept namespace from untrusted input Validate/sanitize first Prevent injection
Use namespace as tenant ID directly Layer namespace over tenant abstraction Aether is primitives, not framework
Rely on wildcard for "secure" observation Document and audit wildcard use Wildcards bypass isolation
Ignore pattern matching rules Use NATS-native patterns Inconsistency breaks isolation

Security Checklist

For Namespace Isolation Review

  • Wildcard subscriptions used only in trusted code (logging, ops, admin)
  • Application validates namespace format before use
  • No way for external clients to trigger wildcard subscriptions
  • Audit logging enabled for all wildcard subscriptions
  • Integration tests verify cross-namespace isolation
  • Code reviews check for accidental "*" or ">" patterns
  • Documentation warns about wildcard risks

For Multi-Tenant Deployments

  • Each tenant has distinct namespace (e.g., "tenant-123")
  • No global (">" ) subscriptions except trusted ops code
  • Namespace validation prevents tenant ID escaping
  • Storage streams are completely separate (not shared)
  • Cross-tenant queries fail (GetEvents blocks at stream level)

Refactoring Priorities (Quick Decision)

Do Next?

  1. P1: Add namespace to Event metadata (2-3 days)

    • Impact: HIGH (enables better auditing)
    • Risk: LOW (backward compatible)
  2. P2: Explicit namespace validation (1 day)

    • Impact: MEDIUM (prevents silent errors)
    • Risk: LOW (validates format)
  3. P3: NamespacedEventBus wrapper (2-3 days)

    • Impact: MEDIUM (easier to use safely)
    • Risk: LOW (additive)

Skip? P4-P5 are important but lower priority.


Test Cases to Implement

Must-Have Integration Tests

// Storage isolation
SaveEvent(store1, "tenant-a", event)
GetEvents(store2, "tenant-b", event.ActorID)  empty 

// Pub/sub isolation
Publish("tenant-a", event)
Subscribe("tenant-a")  receives 
Subscribe("tenant-b")  blocked 

// Pattern matching
Publish("prod.orders", event)
Subscribe("prod.*")  receives 
Subscribe("*.orders")  blocked 

// Cross-node
node1.Publish("tenant-a", event)
node2.Subscribe("tenant-b")  blocked 

Implementation Status Summary

Aspect Status Notes
Exact namespace isolation ✓ Done EventBus + JetStream enforce it
NATS-native patterns ✓ Done Subject-level routing
Wildcard subscriptions ✓ Done Documented as intentional exception
Pattern matching consistency ✓ Done MatchNamespacePattern() correct
Namespace in Event metadata ✗ Pending Add for audit trail
Explicit validation ✗ Pending Add to prevent silent sanitization
NamespacedEventBus wrapper ✗ Pending Convenience layer
Integration tests ✗ Pending Verify isolation at all layers

Glossary

  • Namespace: Logical boundary (tenant, domain, environment)
  • Pattern: NATS-style wildcard for matching multiple namespaces
  • Exact Subscription: Subscribe to specific namespace (isolation enforced)
  • Wildcard Subscription: Subscribe with pattern (isolation bypassed, trusted components only)
  • Invariant: Business rule that must never be broken
  • Subject: NATS address for routing (aether.events.{namespace})
  • Stream: JetStream storage container (one per namespace)
  • Sanitization: Replace unsafe characters (spaces, dots, *, >) with underscores

Decision Framework

When using namespaces, ask:

  1. Is this namespace logically distinct?

    • Yes → use separate namespace
    • No → same namespace is fine
  2. Can users accidentally subscribe to wildcard?

    • Yes → add validation
    • No → proceed
  3. Need cross-namespace visibility?

    • Yes → use wildcard (document and audit)
    • No → exact subscriptions (isolation enforced)
  4. Is this in trusted code?

    • Yes → wildcard subscriptions okay (with audit)
    • No → exact subscriptions only

More Information

  • Full Domain Model: DOMAIN_MODEL_NAMESPACE_ISOLATION.md
  • Implementation Gaps: NAMESPACE_ISOLATION_SUMMARY.md
  • Architecture Diagrams: NAMESPACE_ISOLATION_ARCHITECTURE.md
  • All Contexts: DOMAIN_MODEL_INDEX.md

Last Updated: 2026-01-12 | Status: Complete | Ready for Implementation: Yes