Files
AgentCompany/builtin-skills/software-engineer/clean-architecture/references/bound-defer-decisions.md
T
ZacharyZhang-NY e74dafcd36 Silence render-disposed spam, unbreak bundled skills, drop board wake
Three independently ugly bugs that all fed the same log flood:

* Electron's `WebFrameMain.send` writes every "Render frame was disposed"
  to stderr via `console.error`. When the worker fans a second
  agent-event past a reloading renderer, we produced thousands of
  identical lines and stalled the main loop on stderr I/O. Added a
  `mainFrame.detached` guard so the send is short-circuited before
  Electron's own catch block fires, and installed a targeted filter in
  `suppress-warnings.ts` (now actually imported from the entry point)
  so the specific `console.error("Error sending from webFrameMain:"…)`
  noise never reaches the terminal. Any other payload still surfaces.

* `dispatchAgentMessage` accepted the human-reader sentinel as a wake
  target when an agent replied to `__board__` — `wakeAgentIfPossible`
  then called `db.getAgent("__board__")`, threw, and the dispatcher
  kept repeating on every send. Strip `HUMAN_SENDER_ID` at
  `resolveMessageWakeTargets`, and keep a defense-in-depth guard in the
  heartbeat so a stray caller (future automation fan-out) cannot
  reintroduce the crash.

* Pi's skills loader rejects any SKILL.md whose frontmatter `name`
  diverges from its parent directory. The four bundled `my-writing-style`
  copies declared `name: writing-style-skill`, so every install emitted
  a warning and dropped the skill. They also lived as orphan gitlinks
  (mode 160000 without .gitmodules), and the global `*.md` gitignore
  hid every other SKILL.md from version control. Fixed the frontmatter
  to match the dir, converted the dangling submodules to plain trees,
  and allowlisted `builtin-skills/**/*.md` so a fresh clone carries the
  catalogue instead of silently losing it.
2026-04-20 08:13:48 -04:00

106 lines
3.0 KiB
Markdown

---
title: Defer Framework and Database Decisions
impact: MEDIUM-HIGH
impactDescription: preserves optionality, reduces early commitment risk
tags: bound, deferral, decisions, optionality
---
## Defer Framework and Database Decisions
A good architecture allows major decisions about frameworks, databases, and delivery mechanisms to be deferred until the last responsible moment. The longer you wait, the more information you have.
**Incorrect (early commitment to specifics):**
```typescript
// Day 1: "Let's use Prisma with PostgreSQL and Next.js"
// domain/Order.ts - Coupled to Prisma from the start
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
export async function createOrder(data: OrderInput) {
// Business logic intertwined with Prisma
const order = await prisma.order.create({
data: {
items: {
create: data.items.map(item => ({
productId: item.productId,
quantity: item.quantity,
price: item.price
}))
},
total: data.items.reduce((sum, i) => sum + i.price * i.quantity, 0)
},
include: { items: true }
})
return order
}
// 6 months later: "PostgreSQL doesn't scale for our read patterns,
// we need DynamoDB" - Massive rewrite required
```
**Correct (defer database decision):**
```typescript
// Day 1: Focus on business rules, defer database choice
// domain/Order.ts - Pure business logic
export class Order {
private items: OrderItem[] = []
addItem(product: Product, quantity: number): void {
if (quantity <= 0) throw new InvalidQuantityError()
this.items.push(new OrderItem(product, quantity))
}
calculateTotal(): Money {
return this.items.reduce(
(sum, item) => sum.add(item.lineTotal()),
Money.zero()
)
}
}
// application/ports/OrderRepository.ts - Interface only
export interface OrderRepository {
save(order: Order): Promise<void>
findById(id: OrderId): Promise<Order | null>
}
// For now: Simple in-memory implementation for testing
// infrastructure/InMemoryOrderRepository.ts
export class InMemoryOrderRepository implements OrderRepository {
private orders = new Map<string, Order>()
async save(order: Order): Promise<void> {
this.orders.set(order.id.value, order)
}
}
// Later, when you know more: Add real database
// infrastructure/PrismaOrderRepository.ts
// OR
// infrastructure/DynamoOrderRepository.ts
// OR
// infrastructure/MongoOrderRepository.ts
// The domain never changes regardless of database choice
```
**Decisions worth deferring:**
- Database vendor (PostgreSQL vs MongoDB vs DynamoDB)
- ORM choice (Prisma vs TypeORM vs raw SQL)
- Web framework (Express vs Fastify vs Hono)
- Message queue (RabbitMQ vs Kafka vs SQS)
- Cache provider (Redis vs Memcached)
**Benefits:**
- Learn requirements before committing
- Prototype faster with simple implementations
- Switch vendors without domain rewrites
Reference: [Clean Architecture - The Database is a Detail](https://www.oreilly.com/library/view/clean-architecture-a/9780134494272/ch30.xhtml)