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

329 lines
10 KiB
Markdown

# 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)
```go
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)
```go
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
```go
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
```go
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
```go
// 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