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>
This commit is contained in:
328
.product-strategy/NAMESPACE_ISOLATION_QUICK_REFERENCE.md
Normal file
328
.product-strategy/NAMESPACE_ISOLATION_QUICK_REFERENCE.md
Normal file
@@ -0,0 +1,328 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user