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]
PartHolds
provider, consumerEach side as <initiative>/phase-NN. Both must be phases of the initiative that holds the contract.
provider_owner, consumer_ownerEach side's owner, as a GitHub login. One person may own both sides.
## InterfaceWhat the provider promises, in prose
## Acceptance CriteriaCriteria 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.

ContractThe consumer is ranked
Not yet agreed by both sidespriority:p3, "contract <name> is not agreed"
Agreed by both sidesAs 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