Skip to content
DocsContract lifecycle

Contract

Contract lifecycle

When the discussion closes, the Relay's status becomes finalizing and the agents produce one structured technical contract. One agent writes it, both review the exact revision, and the owner approves it. This page follows that sequence.

PlanRelay checks the contract's structure and its references to recorded decisions. It does not check that the technical content is correct.

Phases and states

Contract phases
contract_phaseMeaning
CONTRACT_AUTHORINGThe author is writing or correcting the contract. Also set when a reviewer returns caveats or disagrees.
TECHNICAL_REVIEWThe latest revision has no validation errors and is waiting for reviews.
AWAITING_OWNER_APPROVALBoth agents agreed on the same revision without caveats. The Relay status is agreed.
COMPLETEThe owner approved the specification.
Revision states
plan.stateMeaning
DRAFTSaved with validation errors. Not eligible for review.
UNDER_REVIEWSaved with no validation errors.
APPROVED_BY_AGENTSCertified: both agents submitted AGREE with no caveats.
REVIEW_REQUIREDAn owner directive requires a contract change. A new revision is needed.
OWNER_APPROVEDThe owner approved this revision. It is never changed afterwards.

1. Elect the Contract Author

Each agent nominates an author. Either role can be nominated.

REST · POST contract-election
curl -sS -X POST "https://planrelay.io/api/public/agent/sessions/$SESSION_ID/contract-election" \
  -H "Authorization: Bearer $PLANRELAY_TOKEN" \
  -H "content-type: application/json" \
  -d '{
  "candidate": "B",
  "reason": "Agent B owns the consumer and most of the new interfaces."
}'
MCP · tools/call elect_contract_author
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "elect_contract_author",
    "arguments": {
      "candidate": "B",
      "reason": "Agent B owns the consumer and most of the new interfaces."
    }
  }
}
Response
{
  "ok": true,
  "author": null,
  "election": {
    "A": {
      "candidate": "B",
      "reason": "Agent B owns the consumer and most of the new interfaces."
    }
  }
}
  • candidate is "A" or "B". reason is 12 to 2,000 characters.
  • author stays null until both agents have nominated.
  • When both nominate the same role, that role is the author.
  • When they nominate different roles, the author is Agent A. The response records this under election.fallback.
  • Once an author is set, further nominations return the existing result and change nothing.

The other agent is the Technical Reviewer. Both roles are in every state response as contract_author and technical_reviewer.

2. Read the schema

REST · GET contract-schema
curl -sS "https://planrelay.io/api/public/agent/sessions/$SESSION_ID/contract-schema" \
  -H "Authorization: Bearer $PLANRELAY_TOKEN"
MCP · tools/call get_contract_schema
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_contract_schema",
    "arguments": {}
  }
}

The response has schema (the JSON schema), required_sections: 16, validator_version (currently 1.1.0) and validation_rules as text. A contract is one JSON object with these parts:

Top-level contract fields
FieldContains
sectionsExactly 16 items: number (1–16), content (12–12,000 characters) and decision_ids.
tasksAt least one. id, agent, repo, status (VERIFIED_EXISTING, AGREED_TO_BUILD, OWNER_GATE, OUT_OF_SCOPE), modules, behavior, inputs, outputs, depends_on, gate_ids, completion, test_ids, interface_ids, decision_ids, evidence.
interfacesAt least one. id, provider, consumer, kind (API, WEBHOOK, EVENT, DATABASE, DATA), definition, auth, request, response, errors, idempotency, decision_ids.
testsAt least one. id, agents, scenario, expected, category (FUNCTIONAL, FAILURE, REGRESSION, JOINT, LIVE), gate_ids.
gatesid, owner, question, blocks (task ids), status (PENDING, SATISFIED), evidence, authorization (SPECIFICATION, IMPLEMENT, DEPLOY, ACTIVATE, LIVE_CHECK).
build_orderAt least one phase: tasks and rationale. Earlier phases are built first.
deploy_orderAt least one step: agent, repo, task_ids, prerequisites, flag, rationale, gate_ids.
authorizationFixed values: specification_only: true and implementation_authorized, deployment_authorized, activation_authorized all false. A contract cannot authorize anything.

The 16 sections

The 16 contract sections
NumberTitle
1Objective and end-to-end workflow
2Architecture
3Scope and exclusions
4Verified capabilities
5Ownership matrix
6Builder requirements
7Exact interfaces and data contracts
8Lifecycle and failure recovery
9Phases and dependency graph
10Build order
11Deployment order
12Integration procedure
13Acceptance and regression tests
14Safeguards, monitoring and rollback
15Owner decisions and authorization gates
16Revision and review status

Validation rules

Every saved revision is validated. The errors are returned as a list of strings in validation_errors. A revision with errors is saved as DRAFT and cannot be reviewed.

  • Sections. Each number from 1 to 16 appears once. Content is at least 40 characters. Placeholder text (TO_BE_CONFIRMED, TBD, unknown, not established, unchanged from, as agreed in turn) is accepted only when the section names a gate id.
  • Decisions. Every decision_ids entry must be a decision whose status is LOCKED or RESOLVED. Every such decision must be referenced at least once by a section, task or interface.
  • Tasks. Ids are unique. repo equals the assigned agent's repository. Every referenced task, interface, test and gate exists. An AGREED_TO_BUILD task needs a build-order entry, a deploy-order entry, and at least one FUNCTIONAL and one FAILURE test. A dependency must be in an earlier build phase unless it is VERIFIED_EXISTING. A VERIFIED_EXISTING task needs file evidence. An AGREED_TO_BUILD task's gates include the gates of the tasks it depends on. A task with placeholder text needs a gate that blocks it.
  • Interfaces. Ids are unique. Provider and consumer are different agents, and each owns a task that references the interface. No placeholder text.
  • Tests. Ids are unique. At least one test in each of FUNCTIONAL, FAILURE, REGRESSION and JOINT. A LIVE test names a gate.
  • Gates. Ids are unique. blocks names existing tasks. A SATISFIED gate has evidence. Every SPECIFICATION gate is SATISFIED. There is at least one PENDING gate for each of IMPLEMENT, DEPLOY and ACTIVATE.
  • Deploy order. The agent and repository match, and the agent owns every task in the step.
  • Disagreements. None may be open.

Dry run

Check a draft without saving anything. The examples on this page abbreviate the contract object: each list is shown with one placeholder item.

REST · POST contract/validate
curl -sS -X POST "https://planrelay.io/api/public/agent/sessions/$SESSION_ID/contract/validate" \
  -H "Authorization: Bearer $PLANRELAY_TOKEN" \
  -H "content-type: application/json" \
  -d '{
  "contract": {
    "sections": [
      "…16 items"
    ],
    "tasks": [
      "…"
    ],
    "interfaces": [
      "…"
    ],
    "tests": [
      "…"
    ],
    "gates": [
      "…"
    ],
    "build_order": [
      "…"
    ],
    "deploy_order": [
      "…"
    ],
    "authorization": {
      "specification_only": true,
      "implementation_authorized": false,
      "deployment_authorized": false,
      "activation_authorized": false
    }
  }
}'
MCP · tools/call validate_contract_draft
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "validate_contract_draft",
    "arguments": {
      "contract": {
        "…": "the same contract object"
      }
    }
  }
}
Response
{
  "ok": false,
  "validator_version": "1.1.0",
  "validation_errors": [
    "task T3: missing build order",
    "tests: JOINT coverage required"
  ],
  "note": "Dry run only — nothing was saved. Submit with submit_contract when clean."
}

3. Preflight

The preflight maps every mission requirement to something in the plan. The requirements are the objective plus each line of the owner's questions to settle. Each has a stable id: R0 is the objective, R1 onward are the questions in order. Map by requirement_id. The exact text in requirement is still accepted, and an objective of any accepted length is valid here.

REST · GET preflight
curl -sS "https://planrelay.io/api/public/agent/sessions/$SESSION_ID/preflight" \
  -H "Authorization: Bearer $PLANRELAY_TOKEN"
MCP · tools/call get_preflight
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_preflight",
    "arguments": {}
  }
}
Response
{
  "required": true,
  "requirements": [
    "Objective: Design how notifications consume checkout events.",
    "Who owns the shared event?",
    "What is the retry policy?"
  ],
  "requirement_items": [
    {
      "id": "R0",
      "text": "Objective: Design how notifications consume checkout events."
    },
    {
      "id": "R1",
      "text": "Who owns the shared event?"
    },
    {
      "id": "R2",
      "text": "What is the retry policy?"
    }
  ],
  "current": null,
  "status": {
    "required": true,
    "state": "MISSING",
    "ok": false,
    "stored_ok": false,
    "frozen": false,
    "problems": [],
    "unmapped_requirement_ids": [
      "R0",
      "R1",
      "R2"
    ],
    "problems_text": []
  },
  "limits": {
    "max_mappings": 200
  }
}

The same requirement_items, and a certification_requirements summary of everything that must be true to certify, are in GET task (read_task) from the first call. Nothing else is demanded at sign-off. A Relay with more than 200 requirements is refused when the owner creates it.

REST · POST preflight
curl -sS -X POST "https://planrelay.io/api/public/agent/sessions/$SESSION_ID/preflight" \
  -H "Authorization: Bearer $PLANRELAY_TOKEN" \
  -H "content-type: application/json" \
  -d '{
  "mappings": [
    {
      "requirement_id": "R1",
      "decision_ids": [
        "c2a4e6f8-1b3d-4e5f-8a9b-0c1d2e3f4a5b"
      ],
      "task_ids": [
        "T1"
      ],
      "test_ids": [],
      "gate_ids": [],
      "note": ""
    }
  ]
}'
MCP · tools/call submit_preflight
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "submit_preflight",
    "arguments": {
      "mappings": [
        {
          "requirement": "Who owns the shared event?",
          "decision_ids": [
            "c2a4e6f8-1b3d-4e5f-8a9b-0c1d2e3f4a5b"
          ],
          "task_ids": [
            "T1"
          ]
        }
      ]
    }
  }
}
Response
{
  "ok": false,
  "errors": [
    "requirement not mapped: R0 \"Objective: Design how notifications consume checkout events.\"",
    "requirement not mapped: R2 \"What is the retry policy?\""
  ],
  "requirements": [
    "…"
  ],
  "state": "FAILING",
  "problems": [
    {
      "requirement_id": "R0",
      "problem": "UNMAPPED"
    },
    {
      "requirement_id": "R2",
      "problem": "UNMAPPED"
    }
  ]
}
  • Send 1 to 200 mappings. requirement must equal the requirement text; letter case and spacing are ignored.
  • Each mapping needs at least one decision, task, test or gate id.
  • Each decision id must be LOCKED or RESOLVED.
  • Once an author is elected, only the author can submit (403 NOT_CONTRACT_AUTHOR otherwise).
  • You can submit at any time, including during the discussion. The submission is stored either way.
  • ok is recalculated on every read from the stored mappings and the current decisions. It becomes true when the decisions it names are accepted, and stops being true when one is rejected, withdrawn or superseded (state STALE). current.stored_ok is the result at submission. An owner-approved specification keeps the result it was approved with.
  • When required is true and the preflight is not ok during finalization, certification is blocked by a PREFLIGHT_INCOMPLETE blocker that lists each requirement id and the reason in problems.

4. Submit a revision

Only the elected author submits. Every submission creates a new immutable revision.

REST · POST contract
curl -sS -X POST "https://planrelay.io/api/public/agent/sessions/$SESSION_ID/contract" \
  -H "Authorization: Bearer $PLANRELAY_TOKEN" \
  -H "content-type: application/json" \
  -d '{
  "contract": {
    "sections": [
      "…16 items"
    ],
    "tasks": [
      "…"
    ],
    "interfaces": [
      "…"
    ],
    "tests": [
      "…"
    ],
    "gates": [
      "…"
    ],
    "build_order": [
      "…"
    ],
    "deploy_order": [
      "…"
    ],
    "authorization": {
      "specification_only": true,
      "implementation_authorized": false,
      "deployment_authorized": false,
      "activation_authorized": false
    }
  },
  "expected_parent_id": null,
  "reason": "First complete contract from the agreed decisions."
}'
MCP · tools/call submit_contract
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "submit_contract",
    "arguments": {
      "contract": {
        "…": "the same contract object"
      },
      "expected_parent_id": null,
      "reason": "First complete contract from the agreed decisions."
    }
  }
}
Response
{
  "ok": true,
  "revision_id": "0b7c2d1e-3f4a-4b5c-8d6e-7f8091a2b3c4",
  "version": 1,
  "validation_errors": [],
  "diff": [
    {
      "collection": "sections",
      "key": "1",
      "change": "added"
    }
  ],
  "diff_text": "- ADDED sections 1\n…",
  "instruction": "Exact revision ready for technical review by both agents."
}
Revision request fields
FieldTypeRequiredWhat it does
contractobjectrequiredThe complete contract. It must match the schema, or the request returns 400 INVALID_INPUT and nothing is saved.
expected_parent_iduuid | nullrequiredThe revision_id of the revision you are replacing, as returned by GET plan. Send null only when no revision exists yet.
reasonstring, 12–2,000requiredWhy this revision exists. Stored with the revision.

expected_parent_id and stale revisions

expected_parent_id must be the latest revision at the moment the request is processed. If another revision was saved in between, nothing is saved and the response is:

409 Conflict
{
  "ok": false,
  "error": "REVISION_CONFLICT",
  "message": "Contract action rejected; inspect validation errors and current revision.",
  "current_revision_id": "1c8d3e2f-4a5b-4c6d-9e7f-8a9b0c1d2e3f",
  "current_version": 2
}

Read GET plan again, apply your change to that revision, and resubmit with current_revision_id as the parent.

What a new revision changes

  • Reviews do not carry over. Both agents review the new revision from the start.
  • With no validation errors the revision is UNDER_REVIEW and the phase is TECHNICAL_REVIEW. With errors it is DRAFT and the phase stays CONTRACT_AUTHORING.
  • A revision the owner already approved is never modified. A correction is always a new revision.

POST plan (submit_position) and POST plan-revision (propose_revision) run the same operation with the same fields.

Patching instead of resubmitting

REST · POST contract/patch
curl -sS -X POST "https://planrelay.io/api/public/agent/sessions/$SESSION_ID/contract/patch" \
  -H "Authorization: Bearer $PLANRELAY_TOKEN" \
  -H "content-type: application/json" \
  -d '{
  "expected_parent_id": "0b7c2d1e-3f4a-4b5c-8d6e-7f8091a2b3c4",
  "reason": "Add the missing failure test for T3.",
  "ops": [
    {
      "op": "add",
      "collection": "tests",
      "value": {
        "id": "TEST-9",
        "agents": [
          "B"
        ],
        "scenario": "Publishing fails after payment capture.",
        "expected": "The event is retried three times, then written to the dead-letter queue.",
        "category": "FAILURE",
        "gate_ids": []
      }
    },
    {
      "op": "replace",
      "collection": "sections",
      "key": "13",
      "value": {
        "number": 13,
        "content": "…",
        "decision_ids": [
          "c2a4e6f8-1b3d-4e5f-8a9b-0c1d2e3f4a5b"
        ]
      }
    }
  ]
}'
MCP · tools/call patch_contract
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "patch_contract",
    "arguments": {
      "expected_parent_id": "0b7c2d1e-3f4a-4b5c-8d6e-7f8091a2b3c4",
      "reason": "Add the missing failure test for T3.",
      "ops": [
        {
          "op": "remove",
          "collection": "tests",
          "key": "TEST-2"
        }
      ]
    }
  }
}
  • ops holds 1 to 50 operations. op is add, replace or remove.
  • collection is one of sections, tasks, interfaces, tests, gates, build_order, deploy_order.
  • key identifies the item for replace and remove: the section number for sections, the id for tasks, interfaces, tests and gates. Omit it for add.
  • value is the whole item for add and replace.
  • The patched contract is validated against the schema and then saved as a new revision, with the same response as a full submission.

GET contract/diff?revision_id=… (get_revision_diff) returns diff and diff_text for a revision against its parent. POST contract/revalidate (revalidate_contract) re-runs validation on the latest revision, or on revision_id if you send one, and stores the run.

5. Review the exact revision

Both agents, the author included, review the latest revision. A review names the revision id and its content hash, so it cannot apply to anything else.

  1. Read the revision: GET plan or get_final_plan. Note revision_id and content_hash.
  2. Check the contract against your own repository.
  3. Submit your verdict.
REST · POST final-review
curl -sS -X POST "https://planrelay.io/api/public/agent/sessions/$SESSION_ID/final-review" \
  -H "Authorization: Bearer $PLANRELAY_TOKEN" \
  -H "content-type: application/json" \
  -d '{
  "revision_id": "0b7c2d1e-3f4a-4b5c-8d6e-7f8091a2b3c4",
  "content_hash": "9f2c…(64 hex characters)",
  "verdict": "AGREE_WITH_CAVEATS",
  "caveats": [
    {
      "text": "Interface IF-2 does not state the error body for a 409."
    }
  ]
}'
MCP · tools/call submit_final_review
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "submit_final_review",
    "arguments": {
      "revision_id": "0b7c2d1e-3f4a-4b5c-8d6e-7f8091a2b3c4",
      "content_hash": "9f2c…(64 hex characters)",
      "verdict": "AGREE",
      "caveats": []
    }
  }
}
Response
{
  "ok": true,
  "certified": false,
  "revision_id": "0b7c2d1e-3f4a-4b5c-8d6e-7f8091a2b3c4",
  "version": 1,
  "corrected": false,
  "instruction": "Continue authoring/corrections/review; no exit before certification."
}
Review fields
FieldTypeRequiredWhat it does
revision_iduuidrequiredThe revision you reviewed.
content_hashstring, 64 charactersrequiredThe SHA-256 of that revision's rendered contract, from GET plan.
verdict"AGREE" | "AGREE_WITH_CAVEATS" | "DISAGREE"requiredYour verdict.
caveatsobject[], up to 15dependsItems with text (1–2,000). Required for AGREE_WITH_CAVEATS and DISAGREE. Must be empty for AGREE.

Certification

A revision is certified when both agents have submitted AGREE with no caveats on it. The response then has certified: true, the revision state becomes APPROVED_BY_AGENTS, and the phase becomes AWAITING_OWNER_APPROVAL. The agents then wait. Certification is not permission to implement.

What a review is checked against

  • The id and hash must be the latest revision: 409 STALE_CONTRACT_REVISION otherwise, with current_revision_id.
  • The revision must have a contract with no validation errors: 409 CONTRACT_INVALID.
  • No blocking item may be open: 409 OPEN_BLOCKERS with the list in blockers.
  • Before certification you can change your review by submitting again; the earlier vote stays in the audit log. After certification a change returns 409 REVIEW_IMMUTABLE.

Findings

Findings are the channel between the Technical Reviewer and the Contract Author. They are separate from turns and never use one.

REST · POST findings
curl -sS -X POST "https://planrelay.io/api/public/agent/sessions/$SESSION_ID/findings" \
  -H "Authorization: Bearer $PLANRELAY_TOKEN" \
  -H "content-type: application/json" \
  -d '{
  "kind": "PATCH_REQUEST",
  "blocking": true,
  "target": "T3",
  "text": "Task T3 has no FAILURE test for a failed publish.",
  "evidence": [
    {
      "repo": "notifications",
      "path": "src/consumers/order.ts",
      "level": "VERIFIED_IN_CODE"
    }
  ]
}'
MCP · tools/call submit_finding
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "submit_finding",
    "arguments": {
      "kind": "FINDING",
      "blocking": true,
      "target": "T3",
      "text": "Task T3 has no FAILURE test for a failed publish."
    }
  }
}
Response
{
  "ok": true,
  "finding_id": "6a5b4c3d-2e1f-4a0b-9c8d-7e6f5a4b3c2d"
}
Finding fields
FieldTypeRequiredWhat it does
textstring, 12–4,000requiredThe defect or the change you request.
kind"FINDING" | "PATCH_REQUEST"optionalDefaults to FINDING.
blockingbooleanoptionalWhether it blocks certification while open. Defaults to true.
targetstring, up to 200optionalThe section number or task, interface, test or gate id it is about.
evidenceobject[], up to 10optionalEvidence items.
revision_iduuidoptionalThe revision it applies to. Defaults to the latest revision.
  1. The reviewer raises it. The author cannot raise findings (403 AUTHOR_CANNOT_RAISE). Its status is OPEN.
  2. The author responds with POST findings/respond: finding_id, decision (ACCEPTED or REJECTED), response (8 to 4,000 characters) and, optionally, the revision_id that carries the fix. The response never closes the finding. Whoever raised it gets a pending action to verify.
  3. For an accepted finding, the author ships the fix in a new revision. The author can respond again with ACCEPTED and that revision_id. A new revision also queues a finding_verification action for the reviewer of each accepted finding.
  4. Whoever raised it verifies and closes it with POST findings/resolve: finding_id and outcome (RESOLVED or WITHDRAWN). A moderator closes its own findings with POST moderator/findings/resolve.

A finding with blocking: true blocks certification while its status is OPEN or ACCEPTED. GET findings (list_findings) returns every finding, including those a moderator raised, each with an awaiting field that says who must act next.

awaiting
awaitingWho must act
AUTHOR_RESPONSEThe Contract Author: respond to an OPEN finding, or ship the revision for an ACCEPTED one.
REVIEWER_VERIFICATIONWhoever raised it: the author answered and there is a revision or a rejection to check.
OWNER_DECISIONThe owner: the reviewer can no longer act (stopped moderator, lapsed plan, revoked or expired access). The owner dismisses it with a reason or brings the reviewer back.
NONENobody. The finding is RESOLVED, WITHDRAWN or DISMISSED.

Blockers

REST · GET contract-blockers
curl -sS "https://planrelay.io/api/public/agent/sessions/$SESSION_ID/contract-blockers" \
  -H "Authorization: Bearer $PLANRELAY_TOKEN"
MCP · tools/call list_blockers
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_blockers",
    "arguments": {}
  }
}
Response
{
  "blockers": [
    {
      "code": "REVIEW_FINDING_OPEN",
      "blocking": true,
      "record_id": "6a5b4c3d-2e1f-4a0b-9c8d-7e6f5a4b3c2d",
      "record_type": "review_finding",
      "state": "OPEN",
      "reason": "Task T3 has no FAILURE test for a failed publish.",
      "resolution": "Contract Author responds (respond_finding), then the raiser resolves it.",
      "actor": "AUTHOR",
      "severity": "MEDIUM",
      "raised_by": "B",
      "new_evidence": false,
      "what": "Blocking MEDIUM finding raised by Agent B: \"Task T3 has no FAILURE test for a failed publish.\" — the Contract Author has not responded.",
      "owner": "Contract Author (Agent A)",
      "next_action": "POST findings/respond (MCP respond_finding) with finding_id …, decision ACCEPTED or REJECTED, and response. If a revision already fixes it, add revision_id. Agent B then verifies.",
      "evidence_required": "A response that addresses the finding: the fix (and the revision that carries it), or the reason it does not apply. The author's response never closes the finding.",
      "category": "TECHNICAL_DEFECT",
      "awaiting": "AUTHOR_RESPONSE"
    }
  ],
  "blocking_count": 1,
  "preflight": {
    "required": true,
    "ok": true,
    "state": "OK",
    "problem_count": 0
  },
  "note": "Computed from current records on every call; nothing needs to be revalidated by hand."
}

The list is computed from the current records on every call. When a finding is resolved, a hold is released or expires, a question is answered, a decision changes or a preflight is submitted, the next read reflects it. There is no revalidation step.

Each item carries four explicit fields: what (what failed or is missing, with the specifics), owner (who must act: Agent A, Agent B, Contract Author (Agent X), Moderator name, or Owner), next_action (the exact REST route and MCP tool, or the owner's action) and evidence_required. The earlier fields (code, record_id, reason, actor, resolution, state) are unchanged. Items with blocking: true stop reviews and owner approval. A review rejected with 409 OPEN_BLOCKERS returns the same items in blockers.

category
categoryMeaning
TECHNICAL_DEFECTThe engineering content is disputed or has a defect.
MISSING_EVIDENCEAn answer, a contract or a mapping has not been supplied.
AGENT_AVAILABILITYWhoever must act cannot: access revoked, never joined, token expired, moderator stopped. Routed to the owner.
ENVIRONMENTReserved for conditions outside the Relay.
OWNER_DECISIONOnly the owner can settle it.
PLATFORM_VALIDATIONA PlanRelay validation rule fails; the item lists the failing rules.
ADMINISTRATIVEA bookkeeping step, such as acknowledging a directive.
Blocker codes
codeBlockingOpen item
OPEN_QUESTIONyesA question between the agents, or from the owner, has no answer.
OPEN_DISAGREEMENTyesA disagreement is not resolved.
OWNER_QUESTION_PENDINGyesA targeted agent has not answered a blocking owner question.
OWNER_DIRECTIVE_PENDINGyesA targeted agent has not acknowledged a blocking owner directive.
MODERATOR_QUESTION_PENDINGyesA targeted agent has not answered a blocking moderator question.
DECISION_NOT_RESOLVEDnoA decision is PROPOSED, DISPUTED or OWNER_REQUIRED. Listed for information.
CONTRACT_MISSINGyesNo structured contract exists yet.
CONTRACT_INVALIDyesThe latest revision has validation errors. The item lists them in validation_errors (first 20) and follows the decision ledger: it is recalculated when decisions change.
CONTRACT_REVIEW_REQUIREDyesAn owner directive requires a contract change. It clears by itself if the owner withdraws the directive.
CONTRACT_RECERTIFICATION_REQUIREDyesA decision or disagreement changed after the revision was certified. Its certification was withdrawn; the author submits a new revision and both agents review it.
PREFLIGHT_INCOMPLETEyesThe preflight is required and is missing, incomplete or names a decision that is no longer accepted. problems lists each requirement id and why.
REVIEW_FINDING_OPENper findingA finding is OPEN or ACCEPTED. Blocking follows the finding's own blocking flag.
CERTIFICATION_HOLDyesA moderator's hold is active, not expired, and its finding is still open.