Theme
Lesson 06 — Why database transactions cannot control the outside world
Outcome
Add the Operation concept and use a fake external effect to understand durable intent.
Why this comes now
The next milestones touch Git and processes. Before that, learn the failure mode that causes many orchestrators to get stuck after crashes.
Understand
SQLite can atomically change SQLite. It cannot atomically start Claude, create a directory, or run Git. If you mutate DB state and then perform I/O, a crash between them creates disagreement. If you perform I/O first and DB fails, you get the opposite disagreement.
Forge records an Operation in the same transaction as the domain intent, commits it, then a worker performs the side effect. “Command accepted” means intent is durable—not that the outside world finished.
Build the real project
- Add Operation types: kind, aggregate target, idempotency key, status, attempt, timestamps, structured payload/result/error.
- Add operations table/migration/repository.
- Create a simple application command that updates a harmless domain/test record and inserts a fake
WRITE_MARKEROperation transactionally. - Prove that the Operation exists even before a worker runs it.
- Document which future actions will become Operations.
Completion gate
A test commits domain change + Operation atomically. Inject a transaction failure and prove neither commits. Explain in a test name/comment why the external effect is not run inside the transaction.
Pitfalls to avoid
Do not build Kafka-style infrastructure. Do not turn every database update into an Operation. Operations exist for external side effects/recovery boundaries.
References
OPERATIONS_AND_RECOVERY.md, T3 Code architecture: https://github.com/pingdotgg/t3code/blob/main/docs/internals/overview.md
Checkpoint
Write one short Learning Log note describing a crash boundary you now understand better.