Make impossible states unrepresentable
Prefer a union to optional fields
An optional field that means different things in different states is a bug waiting for a reader. The workflow asks for a union where each variant carries exactly its own facts.
Left to itself, an agent starts typing. It discovers the behaviour by writing code, then discovers the edge cases through review, then patches each one where it was found. After three rounds the code has guards in four layers and nobody can say what the rules are.
PR_WORKFLOW.md reverses that. It opens:
Use this workflow for every non-trivial feature, bug fix, or refactor. The work must happen in this order. Do not start with implementation and discover the behavior through review.
The workflow separates three things that are often blurred together:
A strict schema cannot stop a stale form overwriting a newer choice. A transaction cannot make incomplete provider facts complete. Design all three.
Every plan opens with one sentence naming what gets better if no later pull request ships, and the production route that receives the change. The workflow gives a bad and a good example:
Bad: Add payment resource schemas for future use.
Good: Current Square callbacks reject a charge whose currency differs from the signed checkout currency.
This one rule kills the most common agent plan: a "foundation" pull request of types and helpers with no caller, followed by a second pull request that never quite arrives.
The plan fills in a fixed set of sections. A section that does not
apply says None; it is never left out.
Make impossible states unrepresentable
An optional field that means different things in different states is a bug waiting for a reader. The workflow asks for a union where each variant carries exactly its own facts.
type Result =
| { kind: "completed"; payment: CompletedPayment }
| { kind: "pending"; paymentId: string }
| { kind: "failed"; reason: FailureReason }
| { kind: "unavailable"; retryAt: string };
When a feature has a lifecycle, the tables above are not written by hand. Separate tables drift, silently, because nothing compares them. Instead the plan declares the state machine as data, and each table becomes a projection of that one declaration. The plan's invariants are written as laws over the declaration, so each one is a test rather than a sentence.
Before presenting the plan, the agent must try to break it. The workflow lists the minimum questions, including:
Then it stops. No tests, no code, until a person explicitly approves this version of the plan:
Silence, a previous broad goal, or approval of an earlier draft is not approval of the current contract.
Pull requests are split by complete behaviour, never by layer. Good slices from the document: "Every provider rejects blank resource ids." "A failed provider read can never become an unpaid result." Bad slices: "Add types." "Add repositories." "Add one provider while keeping a parallel implementation for the others."
The workflow names the signals that a design is wrong, and says to go back to the contract instead of adding another guard:
Agents are very good at patching. This list tells them when patching has become the problem.