Contracts
A contract binds a provider phase to a consumer phase. Once both owners agree to it, the consumer can start before the provider is done.
Carol v0.1.22
Normally a phase waits for the phases in its Depends on cell to be done: until then, propose ranks it priority:p3. A contract lets two phases go ahead side by side. It writes down the interface the provider phase promises the consumer phase, as criteria Zurdo can read, and once both sides' owners agree to it, the consumer's dependency counts as met.
The contract file
A contract lives at docs/<initiative>/contracts/<name>.md, beside the tickets:
---
provider: billing/phase-02
provider_owner: alice
consumer: billing/phase-03
consumer_owner: bob
---
## Interface
Phase-02 (Refunds) promises phase-03 (Dunning) a `refund_issued` event
on the billing queue, carrying the invoice id and the refunded amount.
## Acceptance Criteria
- [ ] the event type exists [grep: RefundIssued in src/billing/events.ts]
- [ ] the event carries the invoice id [grep: invoiceId in src/billing/events.ts]
| Part | Holds |
|---|---|
provider, consumer | Each side as <initiative>/phase-NN. Both must be phases of the initiative that holds the contract. |
provider_owner, consumer_owner | Each side's owner, as a GitHub login. One person may own both sides. |
## Interface | What the provider promises, in prose |
## Acceptance Criteria | Criteria in Zurdo's grammar, with hints |
Projecting it
carol scope sweeps contracts/*.md the way it sweeps tickets. Each contract becomes one issue under the scope issue. Its body links both sides' epics (or their proposals, before an epic exists) and quotes the interface and the criteria. As with everything else, the file is canonical and the issue is a projection.
Before it writes anything, scope checks each contract's criteria with zurdo validate, wrapped as a one-task PRD. It refuses the whole run with exit 2 if Zurdo rejects a contract, or if a side isn't a phase of the initiative. A criterion that already holds draws Zurdo's grep-tautology warning: it's printed, not refused, but such a criterion promises nothing new.
scope also creates the two agreement labels, carol:agreed:provider and carol:agreed:consumer.
Depending on a contract
The consumer row names the contract in its Depends on cell, as contract:<name>, instead of the provider phase:
| Phase | Title | PRD | Status | Depends on |
|---|---|---|---|---|
| phase-02 | Refunds | prds/prd-02-refunds.md | running | phase-01 |
| phase-03 | Dunning | | ready | contract:refund-events |
The consumer's proposal is then blocked by the contract issue rather than by the provider. An unknown contract name, or a contract whose consumer is another phase, is a parse error (exit 2) in every command.
Agreeing
Each side agrees by adding its label to the contract issue:
- the provider's owner adds
carol:agreed:provider; - the consumer's owner adds
carol:agreed:consumer.
A side has agreed when its label was last added by that side's owner, and the owner is an acceptor. A label added by anyone else gets one comment from Carol and stays where it is, but doesn't count.
| Contract | The consumer is ranked |
|---|---|
| Not yet agreed by both sides | priority:p3, "contract <name> is not agreed" |
| Agreed by both sides | As if the dependency were met, even while the provider isn't done |
[limits] in_flight still caps how many phases run at once (see Human gates).
Changing a contract
Edit the file and run carol scope again. If the criteria changed, the plan updates the issue body and removes both carol:agreed:* labels. The promise changed, so both owners agree again. An edit to the interface prose alone keeps the agreement.
Note
In v0.1.22, Carol records the promise and the agreement. It doesn't yet run a contract's criteria against the code. Until it does, the provider's own PRD should carry the same criteria, so zurdo run checks them.
Next: Status & handoffs