@plazmodium/odin 0.3.2-beta → 0.3.4-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +82 -11
- package/builtin/ODIN.md +1045 -0
- package/builtin/agent-definitions/README.md +170 -0
- package/builtin/agent-definitions/_shared-context.md +377 -0
- package/builtin/agent-definitions/architect.md +627 -0
- package/builtin/agent-definitions/builder.md +716 -0
- package/builtin/agent-definitions/discovery.md +293 -0
- package/builtin/agent-definitions/documenter.md +238 -0
- package/builtin/agent-definitions/guardian.md +1049 -0
- package/builtin/agent-definitions/integrator.md +363 -0
- package/builtin/agent-definitions/planning.md +236 -0
- package/builtin/agent-definitions/product.md +405 -0
- package/builtin/agent-definitions/release.md +430 -0
- package/builtin/agent-definitions/reviewer.md +447 -0
- package/builtin/agent-definitions/watcher.md +402 -0
- package/builtin/skills/api/graphql/SKILL.md +548 -0
- package/builtin/skills/api/grpc/SKILL.md +554 -0
- package/builtin/skills/api/rest-api/SKILL.md +469 -0
- package/builtin/skills/api/trpc/SKILL.md +503 -0
- package/builtin/skills/architecture/clean-architecture/SKILL.md +141 -0
- package/builtin/skills/architecture/domain-driven-design/SKILL.md +129 -0
- package/builtin/skills/architecture/event-driven/SKILL.md +145 -0
- package/builtin/skills/architecture/microservices/SKILL.md +143 -0
- package/builtin/skills/architecture/tla-precheck/SKILL.md +171 -0
- package/builtin/skills/backend/golang-gin/SKILL.md +141 -0
- package/builtin/skills/backend/nodejs-express/SKILL.md +277 -0
- package/builtin/skills/backend/nodejs-fastify/SKILL.md +152 -0
- package/builtin/skills/backend/python-django/SKILL.md +128 -0
- package/builtin/skills/backend/python-fastapi/SKILL.md +140 -0
- package/builtin/skills/database/mongodb/SKILL.md +132 -0
- package/builtin/skills/database/postgresql/SKILL.md +120 -0
- package/builtin/skills/database/prisma-orm/SKILL.md +366 -0
- package/builtin/skills/database/redis/SKILL.md +140 -0
- package/builtin/skills/database/supabase/SKILL.md +416 -0
- package/builtin/skills/devops/aws/SKILL.md +382 -0
- package/builtin/skills/devops/docker/SKILL.md +359 -0
- package/builtin/skills/devops/github-actions/SKILL.md +435 -0
- package/builtin/skills/devops/kubernetes/SKILL.md +459 -0
- package/builtin/skills/devops/terraform/SKILL.md +453 -0
- package/builtin/skills/frontend/alpine-dev/SKILL.md +27 -0
- package/builtin/skills/frontend/angular-dev/SKILL.md +28 -0
- package/builtin/skills/frontend/astro-dev/SKILL.md +28 -0
- package/builtin/skills/frontend/htmx-dev/SKILL.md +28 -0
- package/builtin/skills/frontend/nextjs-dev/SKILL.md +470 -0
- package/builtin/skills/frontend/react-patterns/SKILL.md +166 -0
- package/builtin/skills/frontend/svelte-dev/SKILL.md +28 -0
- package/builtin/skills/frontend/tailwindcss/SKILL.md +131 -0
- package/builtin/skills/frontend/vuejs-dev/SKILL.md +28 -0
- package/builtin/skills/generic-dev/SKILL.md +307 -0
- package/builtin/skills/testing/cypress/SKILL.md +372 -0
- package/builtin/skills/testing/jest/SKILL.md +176 -0
- package/builtin/skills/testing/playwright/SKILL.md +341 -0
- package/builtin/skills/testing/unit-tests-eval-sdd/SKILL.md +73 -0
- package/builtin/skills/testing/unit-tests-sdd/SKILL.md +83 -0
- package/builtin/skills/testing/vitest/SKILL.md +249 -0
- package/dist/adapters/skills/filesystem.d.ts.map +1 -1
- package/dist/adapters/skills/filesystem.js +2 -18
- package/dist/adapters/skills/filesystem.js.map +1 -1
- package/dist/builtin-assets.d.ts +8 -0
- package/dist/builtin-assets.d.ts.map +1 -0
- package/dist/builtin-assets.js +90 -0
- package/dist/builtin-assets.js.map +1 -0
- package/dist/init.js +69 -11
- package/dist/init.js.map +1 -1
- package/dist/schemas.d.ts +1 -1
- package/dist/server.js +1 -1
- package/dist/server.js.map +1 -1
- package/dist/tools/prepare-phase-context.d.ts.map +1 -1
- package/dist/tools/prepare-phase-context.js +5 -0
- package/dist/tools/prepare-phase-context.js.map +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -3
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: domain-driven-design
|
|
3
|
+
description: Domain-Driven Design patterns — bounded contexts, aggregates, and strategic design
|
|
4
|
+
category: architecture
|
|
5
|
+
version: "1.0"
|
|
6
|
+
compatible_with:
|
|
7
|
+
- clean-architecture
|
|
8
|
+
- event-driven
|
|
9
|
+
- microservices
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Domain-Driven Design (DDD)
|
|
13
|
+
|
|
14
|
+
## Overview
|
|
15
|
+
|
|
16
|
+
DDD is a software design approach that models complex business domains through a shared ubiquitous language, bounded contexts, and tactical patterns like aggregates and domain events.
|
|
17
|
+
|
|
18
|
+
## Strategic Design
|
|
19
|
+
|
|
20
|
+
### Bounded Contexts
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
┌─────────────────┐ ┌──────────────────┐ ┌───────────────┐
|
|
24
|
+
│ Identity & │ │ Ordering │ │ Shipping │
|
|
25
|
+
│ Access │ │ │ │ │
|
|
26
|
+
│ │ │ Order │ │ Shipment │
|
|
27
|
+
│ User │◄──►│ LineItem │◄──►│ Tracking │
|
|
28
|
+
│ Role │ │ Cart │ │ Address │
|
|
29
|
+
│ Permission │ │ Payment │ │ Carrier │
|
|
30
|
+
└─────────────────┘ └──────────────────┘ └───────────────┘
|
|
31
|
+
Context Map: Published Language / Anti-Corruption Layer
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Each bounded context has its own:
|
|
35
|
+
- **Ubiquitous language** — "User" in Identity vs "Customer" in Ordering
|
|
36
|
+
- **Models** — same real-world concept, different representations
|
|
37
|
+
- **Data store** — ideally its own database/schema
|
|
38
|
+
|
|
39
|
+
### Context Mapping
|
|
40
|
+
|
|
41
|
+
| Pattern | When to Use |
|
|
42
|
+
|---------|------------|
|
|
43
|
+
| **Shared Kernel** | Two teams co-own a small shared model |
|
|
44
|
+
| **Customer-Supplier** | Upstream provides what downstream needs |
|
|
45
|
+
| **Anti-Corruption Layer** | Translate between contexts to prevent leaking |
|
|
46
|
+
| **Published Language** | Well-defined API/events for integration |
|
|
47
|
+
|
|
48
|
+
## Tactical Patterns
|
|
49
|
+
|
|
50
|
+
### Aggregate
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
// Aggregate Root — consistency boundary
|
|
54
|
+
class Order {
|
|
55
|
+
private items: OrderItem[] = [];
|
|
56
|
+
private status: OrderStatus = 'draft';
|
|
57
|
+
|
|
58
|
+
addItem(product: ProductRef, quantity: number, price: Money): void {
|
|
59
|
+
if (this.status !== 'draft') throw new DomainError('Cannot modify submitted order');
|
|
60
|
+
const existing = this.items.find(i => i.productId === product.id);
|
|
61
|
+
if (existing) {
|
|
62
|
+
existing.increaseQuantity(quantity);
|
|
63
|
+
} else {
|
|
64
|
+
this.items.push(new OrderItem(product.id, quantity, price));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
submit(): DomainEvent[] {
|
|
69
|
+
if (this.items.length === 0) throw new DomainError('Cannot submit empty order');
|
|
70
|
+
this.status = 'submitted';
|
|
71
|
+
return [new OrderSubmitted(this.id, this.total(), this.items.length)];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
get total(): Money {
|
|
75
|
+
return this.items.reduce((sum, item) => sum.add(item.subtotal()), Money.zero());
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Domain Events
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
// Events represent something that happened in the domain
|
|
84
|
+
class OrderSubmitted implements DomainEvent {
|
|
85
|
+
readonly occurredAt = new Date();
|
|
86
|
+
constructor(
|
|
87
|
+
public readonly orderId: string,
|
|
88
|
+
public readonly total: Money,
|
|
89
|
+
public readonly itemCount: number,
|
|
90
|
+
) {}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Handle in application layer
|
|
94
|
+
class OnOrderSubmitted {
|
|
95
|
+
constructor(private emailService: EmailService, private inventoryService: InventoryService) {}
|
|
96
|
+
|
|
97
|
+
async handle(event: OrderSubmitted): Promise<void> {
|
|
98
|
+
await this.emailService.sendOrderConfirmation(event.orderId);
|
|
99
|
+
await this.inventoryService.reserveItems(event.orderId);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Repository
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
// Repository per aggregate root — NOT per table
|
|
108
|
+
interface OrderRepository {
|
|
109
|
+
findById(id: OrderId): Promise<Order | null>;
|
|
110
|
+
save(order: Order): Promise<void>; // Saves entire aggregate
|
|
111
|
+
nextId(): OrderId;
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Best Practices
|
|
116
|
+
|
|
117
|
+
1. **One aggregate = one transaction** — don't modify multiple aggregates in one transaction
|
|
118
|
+
2. **Reference aggregates by ID** — not by direct object reference
|
|
119
|
+
3. **Small aggregates** — only include what's needed for invariant enforcement
|
|
120
|
+
4. **Domain events for cross-aggregate communication** — eventual consistency between aggregates
|
|
121
|
+
5. **Ubiquitous language** — code names must match domain expert vocabulary
|
|
122
|
+
6. **Anti-Corruption Layer** — always translate at context boundaries
|
|
123
|
+
|
|
124
|
+
## Gotchas
|
|
125
|
+
|
|
126
|
+
- **Anemic domain model** — entities with only getters/setters and all logic in services
|
|
127
|
+
- **Big aggregate** — putting everything in one aggregate kills performance
|
|
128
|
+
- **Premature context splitting** — start with one context, split when language diverges
|
|
129
|
+
- **Ignoring the domain expert** — DDD requires ongoing collaboration, not just patterns
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: event-driven
|
|
3
|
+
description: Event-driven architecture patterns — event sourcing, CQRS, message brokers, and async workflows
|
|
4
|
+
category: architecture
|
|
5
|
+
version: "1.0"
|
|
6
|
+
compatible_with:
|
|
7
|
+
- domain-driven-design
|
|
8
|
+
- microservices
|
|
9
|
+
- redis
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Event-Driven Architecture
|
|
13
|
+
|
|
14
|
+
## Overview
|
|
15
|
+
|
|
16
|
+
Event-driven architecture (EDA) uses events as the primary mechanism for communication between components. Events represent facts — things that have happened — and enable loose coupling, scalability, and auditability.
|
|
17
|
+
|
|
18
|
+
## Patterns
|
|
19
|
+
|
|
20
|
+
### Event Notification
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
Producer → Event Bus → Consumer(s)
|
|
24
|
+
|
|
25
|
+
OrderService NotificationService
|
|
26
|
+
│ │
|
|
27
|
+
├── publishes ──► OrderPlaced ──► │ sends email
|
|
28
|
+
│ │
|
|
29
|
+
InventoryService PaymentService
|
|
30
|
+
│ │
|
|
31
|
+
├── subscribes ─► OrderPlaced ──► │ charges card
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Event Sourcing
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
// Store events, not state — rebuild state by replaying events
|
|
38
|
+
class OrderEventStore {
|
|
39
|
+
async save(orderId: string, events: DomainEvent[]): Promise<void> {
|
|
40
|
+
for (const event of events) {
|
|
41
|
+
await this.db.insert('events', {
|
|
42
|
+
stream_id: orderId,
|
|
43
|
+
type: event.type,
|
|
44
|
+
data: JSON.stringify(event),
|
|
45
|
+
version: event.version,
|
|
46
|
+
occurred_at: event.occurredAt,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async load(orderId: string): Promise<DomainEvent[]> {
|
|
52
|
+
return this.db.query(
|
|
53
|
+
'SELECT * FROM events WHERE stream_id = $1 ORDER BY version ASC',
|
|
54
|
+
[orderId]
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Rebuild aggregate from events
|
|
60
|
+
function rehydrate(events: DomainEvent[]): Order {
|
|
61
|
+
const order = new Order();
|
|
62
|
+
for (const event of events) {
|
|
63
|
+
order.apply(event); // Mutates internal state
|
|
64
|
+
}
|
|
65
|
+
return order;
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### CQRS (Command Query Responsibility Segregation)
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
Commands (Write) Queries (Read)
|
|
73
|
+
│ │
|
|
74
|
+
▼ ▼
|
|
75
|
+
┌──────────┐ events ┌──────────────────┐
|
|
76
|
+
│ Write DB │ ──────────► │ Read Projections │
|
|
77
|
+
│ (events) │ │ (denormalized) │
|
|
78
|
+
└──────────┘ └──────────────────┘
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
// Command side — validates and stores events
|
|
83
|
+
async function placeOrder(cmd: PlaceOrderCommand): Promise<void> {
|
|
84
|
+
const order = await eventStore.load(cmd.orderId);
|
|
85
|
+
const events = order.place(cmd.items); // Returns new events
|
|
86
|
+
await eventStore.save(cmd.orderId, events);
|
|
87
|
+
await eventBus.publish(events);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Query side — read from optimized projections
|
|
91
|
+
async function getOrderSummary(orderId: string): Promise<OrderSummary> {
|
|
92
|
+
return readDb.query('SELECT * FROM order_summaries WHERE id = $1', [orderId]);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Projector — updates read model when events arrive
|
|
96
|
+
class OrderProjector {
|
|
97
|
+
async handle(event: OrderPlaced): Promise<void> {
|
|
98
|
+
await readDb.insert('order_summaries', {
|
|
99
|
+
id: event.orderId,
|
|
100
|
+
status: 'placed',
|
|
101
|
+
total: event.total,
|
|
102
|
+
item_count: event.items.length,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Message Broker Patterns
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
// Dead letter queue for failed messages
|
|
112
|
+
const config = {
|
|
113
|
+
queue: 'order-processing',
|
|
114
|
+
deadLetterQueue: 'order-processing-dlq',
|
|
115
|
+
maxRetries: 3,
|
|
116
|
+
retryDelay: [1000, 5000, 30000], // Exponential backoff
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// Idempotent consumers — handle duplicate delivery
|
|
120
|
+
async function handleOrderPlaced(event: OrderPlaced): Promise<void> {
|
|
121
|
+
const processed = await cache.get(`processed:${event.id}`);
|
|
122
|
+
if (processed) return; // Already handled
|
|
123
|
+
|
|
124
|
+
await processOrder(event);
|
|
125
|
+
await cache.set(`processed:${event.id}`, '1', 'EX', 86400);
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Best Practices
|
|
130
|
+
|
|
131
|
+
1. **Events are facts** — immutable, past-tense (`OrderPlaced`, not `PlaceOrder`)
|
|
132
|
+
2. **Idempotent consumers** — always handle duplicate delivery gracefully
|
|
133
|
+
3. **Schema evolution** — version events; add fields, don't rename/remove
|
|
134
|
+
4. **Dead letter queues** — capture failed messages for manual inspection
|
|
135
|
+
5. **Correlation IDs** — trace events across services with a shared ID
|
|
136
|
+
6. **Eventually consistent** — accept that read models may lag; design UX accordingly
|
|
137
|
+
7. **Start without event sourcing** — event notification is simpler; add sourcing when you need audit trails
|
|
138
|
+
|
|
139
|
+
## Gotchas
|
|
140
|
+
|
|
141
|
+
- **Ordering guarantees** — most message brokers guarantee order per partition/key, not globally
|
|
142
|
+
- **Eventual consistency** — read model may be stale; handle "not found yet" in consumers
|
|
143
|
+
- **Event versioning** — changing event schemas without migration breaks consumers
|
|
144
|
+
- **Debugging** — tracing an operation across async events is harder than a synchronous call stack
|
|
145
|
+
- **Two-phase commit trap** — don't try to atomically write to DB + publish event; use outbox pattern
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: microservices
|
|
3
|
+
description: Microservices design patterns — service boundaries, communication, resilience, and observability
|
|
4
|
+
category: architecture
|
|
5
|
+
version: "1.0"
|
|
6
|
+
depends_on:
|
|
7
|
+
- domain-driven-design
|
|
8
|
+
- event-driven
|
|
9
|
+
compatible_with:
|
|
10
|
+
- docker
|
|
11
|
+
- kubernetes
|
|
12
|
+
- rest-api
|
|
13
|
+
- grpc
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Microservices Architecture
|
|
17
|
+
|
|
18
|
+
## Overview
|
|
19
|
+
|
|
20
|
+
Microservices decompose an application into small, independently deployable services organized around business capabilities. Each service owns its data and communicates via APIs or events.
|
|
21
|
+
|
|
22
|
+
## Service Design
|
|
23
|
+
|
|
24
|
+
### Service Boundaries
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
✅ Good boundaries (aligned with business capabilities):
|
|
28
|
+
┌──────────┐ ┌───────────┐ ┌──────────┐ ┌───────────┐
|
|
29
|
+
│ Users │ │ Orders │ │ Payments │ │ Shipping │
|
|
30
|
+
│ │ │ │ │ │ │ │
|
|
31
|
+
│ - Auth │ │ - Cart │ │ - Charge │ │ - Track │
|
|
32
|
+
│ - Profile│ │ - Checkout│ │ - Refund │ │ - Fulfill │
|
|
33
|
+
│ - Prefs │ │ - History │ │ - Ledger │ │ - Label │
|
|
34
|
+
└──────────┘ └───────────┘ └──────────┘ └───────────┘
|
|
35
|
+
|
|
36
|
+
❌ Bad boundaries (tech layers):
|
|
37
|
+
┌──────────┐ ┌───────────┐ ┌──────────┐
|
|
38
|
+
│ Frontend │ │ Backend │ │ Database │ ← Not microservices
|
|
39
|
+
└──────────┘ └───────────┘ └──────────┘
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Communication
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
Synchronous (request/response):
|
|
46
|
+
REST API — Simple CRUD, public APIs
|
|
47
|
+
gRPC — Internal service-to-service, streaming
|
|
48
|
+
|
|
49
|
+
Asynchronous (event-driven):
|
|
50
|
+
Message Queue (RabbitMQ, SQS) — Task processing, commands
|
|
51
|
+
Event Stream (Kafka) — Event sourcing, data sync
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Core Patterns
|
|
55
|
+
|
|
56
|
+
### API Gateway
|
|
57
|
+
|
|
58
|
+
```yaml
|
|
59
|
+
# Route external requests to internal services
|
|
60
|
+
routes:
|
|
61
|
+
/api/users/*: upstream: http://user-service:3000
|
|
62
|
+
/api/orders/*: upstream: http://order-service:3000
|
|
63
|
+
/api/payments/*: upstream: http://payment-service:3000
|
|
64
|
+
|
|
65
|
+
# Cross-cutting concerns at the gateway:
|
|
66
|
+
# - Authentication / JWT validation
|
|
67
|
+
# - Rate limiting
|
|
68
|
+
# - Request logging
|
|
69
|
+
# - Response caching
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Service Communication
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
// Sync: REST with circuit breaker
|
|
76
|
+
import CircuitBreaker from 'opossum';
|
|
77
|
+
|
|
78
|
+
const paymentBreaker = new CircuitBreaker(
|
|
79
|
+
async (orderId: string) => {
|
|
80
|
+
return fetch(`http://payment-service/charges/${orderId}`);
|
|
81
|
+
},
|
|
82
|
+
{ timeout: 3000, errorThresholdPercentage: 50, resetTimeout: 30000 }
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// Fallback when circuit is open
|
|
86
|
+
paymentBreaker.fallback(() => ({ status: 'pending', message: 'Payment service unavailable' }));
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Saga Pattern (distributed transactions)
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
Choreography (events):
|
|
93
|
+
OrderCreated → PaymentCharged → InventoryReserved → ShipmentScheduled
|
|
94
|
+
↓ (if fails)
|
|
95
|
+
OrderCreated → PaymentCharged → InventoryFailed → PaymentRefunded → OrderCancelled
|
|
96
|
+
|
|
97
|
+
Orchestration (central coordinator):
|
|
98
|
+
OrderSaga:
|
|
99
|
+
1. Create order (pending)
|
|
100
|
+
2. Charge payment → if fails → cancel order
|
|
101
|
+
3. Reserve inventory → if fails → refund payment → cancel order
|
|
102
|
+
4. Schedule shipment → if fails → release inventory → refund → cancel
|
|
103
|
+
5. Mark order complete
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Service Mesh / Observability
|
|
107
|
+
|
|
108
|
+
```yaml
|
|
109
|
+
# Distributed tracing headers (propagate across services)
|
|
110
|
+
X-Request-ID: unique-per-request
|
|
111
|
+
X-Correlation-ID: unique-per-user-action
|
|
112
|
+
|
|
113
|
+
# Health check endpoint (every service)
|
|
114
|
+
GET /health
|
|
115
|
+
{
|
|
116
|
+
"status": "healthy",
|
|
117
|
+
"version": "1.2.3",
|
|
118
|
+
"uptime": 86400,
|
|
119
|
+
"dependencies": {
|
|
120
|
+
"database": "connected",
|
|
121
|
+
"cache": "connected",
|
|
122
|
+
"payment-service": "healthy"
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Best Practices
|
|
128
|
+
|
|
129
|
+
1. **Database per service** — no shared databases; communicate via APIs/events
|
|
130
|
+
2. **Smart endpoints, dumb pipes** — business logic in services, not in the messaging layer
|
|
131
|
+
3. **Design for failure** — circuit breakers, retries with backoff, fallbacks
|
|
132
|
+
4. **Health checks** — every service exposes `/health` with dependency status
|
|
133
|
+
5. **Distributed tracing** — propagate correlation IDs through all service calls
|
|
134
|
+
6. **Independent deployability** — each service has its own CI/CD pipeline
|
|
135
|
+
7. **Start with a monolith** — extract services when team/domain boundaries become clear
|
|
136
|
+
|
|
137
|
+
## Gotchas
|
|
138
|
+
|
|
139
|
+
- **Distributed monolith** — services that must deploy together aren't microservices
|
|
140
|
+
- **Data consistency** — no ACID across services; use sagas and eventual consistency
|
|
141
|
+
- **Network unreliability** — every service call can fail, timeout, or return stale data
|
|
142
|
+
- **Operational complexity** — monitoring, logging, tracing across N services is hard
|
|
143
|
+
- **Service discovery** — services need to find each other; use DNS, Consul, or Kubernetes services
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tla-precheck
|
|
3
|
+
description: "Formal design verification using TLA+ model checking via tla-precheck. Use when a feature involves state machines, lifecycle transitions, concurrent state mutations, or invariants that must hold across all interleavings."
|
|
4
|
+
category: architecture
|
|
5
|
+
compatible_with:
|
|
6
|
+
- clean-architecture
|
|
7
|
+
- domain-driven-design
|
|
8
|
+
- event-driven
|
|
9
|
+
depends_on: []
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# TLA+ PreCheck — Formal Design Verification
|
|
13
|
+
|
|
14
|
+
## Overview
|
|
15
|
+
|
|
16
|
+
[tla-precheck](https://github.com/kingbootoshi/tla-precheck) translates a TypeScript state machine DSL (`.machine.ts`) into TLA+ specifications, then runs the TLC model checker to exhaustively verify invariants and graph equivalence. Odin integrates it as an opt-in design verification step in the Architect → Guardian flow.
|
|
17
|
+
|
|
18
|
+
## When to Use
|
|
19
|
+
|
|
20
|
+
Write a `.machine.ts` file when the feature involves:
|
|
21
|
+
|
|
22
|
+
- **Entity lifecycle management** — status transitions, state machines with guard conditions
|
|
23
|
+
- **Concurrent operations on shared state** — multiple actors mutating the same resource
|
|
24
|
+
- **Financial/billing state changes** — charges, refunds, subscriptions where invariant violations cause real damage
|
|
25
|
+
- **Distributed workflow coordination** — where guard conditions are scattered across files/services
|
|
26
|
+
- **Any invariant that must hold across all possible interleavings** — not just the happy path
|
|
27
|
+
|
|
28
|
+
Complexity level (L1/L2/L3) is a secondary signal. Even a small L1 feature can contain a high-risk lifecycle invariant.
|
|
29
|
+
|
|
30
|
+
## The `.machine.ts` DSL
|
|
31
|
+
|
|
32
|
+
A machine definition describes states, events, transitions, and invariants:
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { defineMachine } from 'tla-precheck';
|
|
36
|
+
|
|
37
|
+
export default defineMachine({
|
|
38
|
+
moduleName: 'AgentRuns',
|
|
39
|
+
|
|
40
|
+
constants: {
|
|
41
|
+
Users: { type: 'set', values: ['u1', 'u2'] },
|
|
42
|
+
MaxConcurrent: { type: 'number', value: 1 },
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
state: {
|
|
46
|
+
runs: { type: 'map', keys: 'Users', values: ['idle', 'queued', 'running', 'completed'] },
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
init: {
|
|
50
|
+
runs: { u1: 'idle', u2: 'idle' },
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
actions: {
|
|
54
|
+
QueueRun: {
|
|
55
|
+
guard: (s) => Object.values(s.runs).some((v) => v === 'idle'),
|
|
56
|
+
update: (s) => {
|
|
57
|
+
const user = Object.keys(s.runs).find((u) => s.runs[u] === 'idle')!;
|
|
58
|
+
s.runs[user] = 'queued';
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
StartRun: {
|
|
62
|
+
guard: (s) => {
|
|
63
|
+
const running = Object.values(s.runs).filter((v) => v === 'running').length;
|
|
64
|
+
return running < MaxConcurrent && Object.values(s.runs).some((v) => v === 'queued');
|
|
65
|
+
},
|
|
66
|
+
update: (s) => {
|
|
67
|
+
const user = Object.keys(s.runs).find((u) => s.runs[u] === 'queued')!;
|
|
68
|
+
s.runs[user] = 'running';
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
CompleteRun: {
|
|
72
|
+
guard: (s) => Object.values(s.runs).some((v) => v === 'running'),
|
|
73
|
+
update: (s) => {
|
|
74
|
+
const user = Object.keys(s.runs).find((u) => s.runs[u] === 'running')!;
|
|
75
|
+
s.runs[user] = 'completed';
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
invariants: {
|
|
81
|
+
AtMostOneRunning: (s) =>
|
|
82
|
+
Object.values(s.runs).filter((v) => v === 'running').length <= MaxConcurrent,
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Odin Integration
|
|
88
|
+
|
|
89
|
+
### Architect Phase (Phase 3)
|
|
90
|
+
|
|
91
|
+
1. Identify state flows in the spec that need formal verification
|
|
92
|
+
2. Write a `.machine.ts` file for each critical flow
|
|
93
|
+
3. Document in spec section "4.6 State Machine Verification"
|
|
94
|
+
4. Ask the orchestrator to call `odin.verify_design` for each machine
|
|
95
|
+
5. If proof fails → fix the **design** (the DSL), not patch around it
|
|
96
|
+
6. Loop until all machines return `status: VERIFIED`
|
|
97
|
+
7. Record aggregated results as one `design_verification` phase artifact
|
|
98
|
+
|
|
99
|
+
### Guardian Phase (Phase 4)
|
|
100
|
+
|
|
101
|
+
Guardian reviews proof results in the Technical Soundness perspective:
|
|
102
|
+
|
|
103
|
+
- All machines `VERIFIED` → supports **Good**
|
|
104
|
+
- Any machine `VIOLATION` → **Blocking** (design must be fixed)
|
|
105
|
+
- State flows in spec but no proof run → **Needs Work**
|
|
106
|
+
- Tool not configured/available → **N/A**
|
|
107
|
+
|
|
108
|
+
### Configuration
|
|
109
|
+
|
|
110
|
+
```yaml
|
|
111
|
+
# .odin/config.yaml
|
|
112
|
+
formal_verification:
|
|
113
|
+
provider: tla-precheck # or "none" (default)
|
|
114
|
+
timeout_seconds: 120
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Requirements
|
|
118
|
+
|
|
119
|
+
- Java 17+ (for the TLC model checker)
|
|
120
|
+
- `tla-precheck` installed as a devDependency in the target project
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
npm install -D tla-precheck
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Verification Status Codes
|
|
127
|
+
|
|
128
|
+
| Status | Meaning |
|
|
129
|
+
|--------|---------|
|
|
130
|
+
| `VERIFIED` | Proof passed, invariants hold, graph equivalence confirmed |
|
|
131
|
+
| `VIOLATION` | Invariant violation found — design bug |
|
|
132
|
+
| `INVALID_MODEL` | DSL parse error or malformed machine — fix the `.machine.ts`, not the design |
|
|
133
|
+
| `TIMEOUT` | TLC exceeded time budget — simplify the state space or increase timeout |
|
|
134
|
+
| `UNAVAILABLE` | Java or tla-precheck not installed |
|
|
135
|
+
| `NOT_CONFIGURED` | `formal_verification.provider` is `none` |
|
|
136
|
+
| `INTERNAL_ERROR` | Unexpected failure in tla-precheck |
|
|
137
|
+
|
|
138
|
+
## Design Loop
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
Write .machine.ts
|
|
142
|
+
│
|
|
143
|
+
▼
|
|
144
|
+
odin.verify_design
|
|
145
|
+
│
|
|
146
|
+
┌───┴───┐
|
|
147
|
+
│ │
|
|
148
|
+
VERIFIED VIOLATION
|
|
149
|
+
│ │
|
|
150
|
+
▼ ▼
|
|
151
|
+
Done Read counter-example
|
|
152
|
+
Fix the DESIGN (DSL)
|
|
153
|
+
Re-run verification
|
|
154
|
+
└──────────────┘
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
The key insight: when proof fails, **fix the design**, not the code. The `.machine.ts` IS the design. If you can't make the invariant hold in the model, you can't make it hold in the implementation.
|
|
158
|
+
|
|
159
|
+
## Best Practices
|
|
160
|
+
|
|
161
|
+
- **Keep state spaces small** — use 2-3 constants per set, not production-scale values. TLC checks all interleavings exhaustively.
|
|
162
|
+
- **Name invariants descriptively** — `AtMostOneRunning` not `Inv1`. Guardian reviews these names.
|
|
163
|
+
- **One machine per concern** — don't model your entire system in one file. Model the billing flow, the auth flow, the workflow transitions separately.
|
|
164
|
+
- **Pin the version** — `tla-precheck` is early-stage (1 contributor, no releases). Pin exact version in `package.json`.
|
|
165
|
+
- **Check `equivalent`** — proof passing but `equivalent: false` means your TypeScript interpreter diverges from the TLA+ spec. This is a bug in the DSL, not the model checker.
|
|
166
|
+
|
|
167
|
+
## References
|
|
168
|
+
|
|
169
|
+
- [tla-precheck GitHub](https://github.com/kingbootoshi/tla-precheck)
|
|
170
|
+
- [TLA+ Learning Resources](https://lamport.azurewebsites.net/tla/learning.html)
|
|
171
|
+
- [Odin Architect Agent — Section 4.6](../../../agents/definitions/architect.md)
|