TfP Docs

ReviewProtocol schema

Generated from the canonical schema schemas/review-protocol.schema.json by scripts/generate-schema-docs.mjs — do not edit this page by hand.

Frozen contract (Stage 7A, 2026-09-02; seats and claims added 2026-09-02). Immutable instructions describing a review computation, in the tfp.review/v0.1 language: a bounded, statically validated DAG with exactly two node types — review (humans or agents exercise judgment under an explicit rubric and structured output schema) and reduce (a deterministic operation from a closed catalogue over earlier structured outputs, never a model). Conditional continuation exists in one form only: a node may run when a named reduce node emitted accept, reject, or unresolved. There are no loops, expressions, user code, API calls, timers, or mutable state; a protocol describes judgment, not arbitrary work. A review stage of a VerifierContract pins one ReviewProtocol; a ReviewProcess is one execution of it; ReviewAssignments are its reviewers' work. Reviewer configuration and rubrics are written inline. Content-addressed and versioned like Contracts: a changed document is a new protocol.

Schema version: 0.3.1 · $id: https://tokensforprogress.org/schemas/review-protocol.schema.json

Fields

id

Type: string · Required: yes · Mutable: no · Pattern: ^rpt_[0-9a-z]{10,32}$

Stable opaque identifier — what Contract review stages pin and what every ReviewProcess records.

Example: "rpt_01jq6p3c0gxk"

slug

Type: string · Required: yes · Mutable: yes · Pattern: ^[a-z0-9]+(-[a-z0-9]+)*$

URL-safe display name, unique per publisher.

Example: "extraction-2of3-escalate"

publisher_user_id

Type: string · Required: yes · Mutable: no · Pattern: ^usr_[0-9a-z]{10,32}$

Example: "usr_01jf90aa4rrc"

content_hash

Type: string · Required: yes · Mutable: no · Pattern: ^sha256:[0-9a-f]{64}$

Deterministic digest of the canonical serialization of document. Every ReviewProcess and VerificationRun review stage records it, so a process is reconstructible even after Campaign defaults change.

Example: "sha256:2b3c4d5e6f708192a3b4c5d6e7f80912a3b4c5d6e7f8091a2b3c4d5e6f7081920"

document

Type: object · Required: yes · Mutable: no

The authored tfp.review/v0.1 document: a small, statically validated, bounded DAG of review and reduce nodes. This is what is hashed and what a ReviewProcess snapshots.

document.version

Type: string · Required: yes · Mutable: —

document.purpose

Type: acceptance | triage | ranking | audit · Required: yes · Mutable: —

What the review means. acceptance: does one item meet a bar? triage: does it deserve further scarce attention? ranking: how do candidates compare? audit: how well did another evaluation process perform? Purpose is meaning, not a separate engine.

Example: "acceptance"

document.title

Type: string · Required: yes · Mutable: —

Example: "Extraction accuracy: three independent agents, expert on disagreement"

document.subject_kind

Type: submission | problem_proposal | campaign_proposal · Required: yes · Mutable: —

What one ReviewProcess of this protocol examines. v0 protocols review one subject; set-level ranking is expressed as per-candidate scored review with aggregation deferred to the consuming feature.

Example: "submission"

document.nodes

Type: array · Required: yes · Mutable: —

The DAG. Dependencies are implied by reduce inputs, review visibility.prior_nodes, and when.node; static validation requires the graph to be acyclic.

document.output

Type: string · Required: yes · Mutable: — · Pattern: ^[a-z][a-z0-9_]{0,39}$

The reduce node whose outcome is the protocol's result. Must be a reduce node, and every other node must be an ancestor of it or conditional on one.

Example: "final"

document.policy

Type: object · Required: yes · Mutable: —

Execution policy for the ReviewProcess. Finite by construction, so a process never stays open indefinitely: the node deadline bounds every seat.

created_at

Type: string · Required: yes · Mutable: no

Example: "2026-09-10T09:00:00Z"

yanked_at

Type: string \| null · Required: yes · Mutable: yes · Default: null

Write-once withdrawal from recommendation. Existing Contract pins keep resolving; existing processes keep their meaning.

Example: null

JSON Schema source

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://tokensforprogress.org/schemas/review-protocol.schema.json",
  "title": "ReviewProtocol",
  "description": "Frozen contract (Stage 7A, 2026-09-02; seats and claims added 2026-09-02). Immutable instructions describing a review computation, in the tfp.review/v0.1 language: a bounded, statically validated DAG with exactly two node types — `review` (humans or agents exercise judgment under an explicit rubric and structured output schema) and `reduce` (a deterministic operation from a closed catalogue over earlier structured outputs, never a model). Conditional continuation exists in one form only: a node may run when a named reduce node emitted accept, reject, or unresolved. There are no loops, expressions, user code, API calls, timers, or mutable state; a protocol describes judgment, not arbitrary work. A `review` stage of a VerifierContract pins one ReviewProtocol; a ReviewProcess is one execution of it; ReviewAssignments are its reviewers' work. Reviewer configuration and rubrics are written inline. Content-addressed and versioned like Contracts: a changed document is a new protocol.",
  "x-tfp-version": "0.3.1",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "id",
    "slug",
    "publisher_user_id",
    "content_hash",
    "document",
    "created_at",
    "yanked_at"
  ],
  "properties": {
    "id": {
      "type": "string",
      "pattern": "^rpt_[0-9a-z]{10,32}$",
      "x-mutable": false,
      "description": "Stable opaque identifier — what Contract review stages pin and what every ReviewProcess records.",
      "examples": [
        "rpt_01jq6p3c0gxk"
      ]
    },
    "slug": {
      "type": "string",
      "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$",
      "maxLength": 64,
      "x-mutable": true,
      "description": "URL-safe display name, unique per publisher.",
      "examples": [
        "extraction-2of3-escalate"
      ]
    },
    "publisher_user_id": {
      "type": "string",
      "pattern": "^usr_[0-9a-z]{10,32}$",
      "x-mutable": false,
      "examples": [
        "usr_01jf90aa4rrc"
      ]
    },
    "content_hash": {
      "type": "string",
      "pattern": "^sha256:[0-9a-f]{64}$",
      "x-mutable": false,
      "description": "Deterministic digest of the canonical serialization of `document`. Every ReviewProcess and VerificationRun review stage records it, so a process is reconstructible even after Campaign defaults change.",
      "examples": [
        "sha256:2b3c4d5e6f708192a3b4c5d6e7f80912a3b4c5d6e7f8091a2b3c4d5e6f7081920"
      ]
    },
    "document": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "version",
        "purpose",
        "title",
        "subject_kind",
        "nodes",
        "output",
        "policy"
      ],
      "description": "The authored tfp.review/v0.1 document: a small, statically validated, bounded DAG of review and reduce nodes. This is what is hashed and what a ReviewProcess snapshots.",
      "properties": {
        "version": {
          "type": "string",
          "const": "tfp.review/v0.1"
        },
        "purpose": {
          "type": "string",
          "enum": [
            "acceptance",
            "triage",
            "ranking",
            "audit"
          ],
          "description": "What the review means. acceptance: does one item meet a bar? triage: does it deserve further scarce attention? ranking: how do candidates compare? audit: how well did another evaluation process perform? Purpose is meaning, not a separate engine.",
          "examples": [
            "acceptance"
          ]
        },
        "title": {
          "type": "string",
          "minLength": 1,
          "maxLength": 200,
          "examples": [
            "Extraction accuracy: three independent agents, expert on disagreement"
          ]
        },
        "subject_kind": {
          "type": "string",
          "enum": [
            "submission",
            "problem_proposal",
            "campaign_proposal"
          ],
          "description": "What one ReviewProcess of this protocol examines. v0 protocols review one subject; set-level ranking is expressed as per-candidate scored review with aggregation deferred to the consuming feature.",
          "examples": [
            "submission"
          ]
        },
        "nodes": {
          "type": "array",
          "minItems": 1,
          "maxItems": 16,
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "type"
            ],
            "properties": {
              "id": {
                "type": "string",
                "pattern": "^[a-z][a-z0-9_]{0,39}$",
                "description": "Unique within the protocol.",
                "examples": [
                  "panel"
                ]
              },
              "type": {
                "type": "string",
                "enum": [
                  "review",
                  "reduce"
                ],
                "description": "The only two node types in v0. `review` obtains judgment — from Users who claim its seats (the default) or, on an agent node, from a TfP-hosted model invocation; `reduce` combines structured outputs deterministically and never invokes a model."
              },
              "title": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 120,
                "default": null
              },
              "when": {
                "type": [
                  "object",
                  "null"
                ],
                "additionalProperties": false,
                "default": null,
                "required": [
                  "node",
                  "outcome"
                ],
                "description": "Closed-outcome conditional continuation: this node runs only when the named reduce node emitted the named outcome. The only conditional construct in v0. Null means the node always runs once its dependencies complete.",
                "properties": {
                  "node": {
                    "type": "string",
                    "pattern": "^[a-z][a-z0-9_]{0,39}$"
                  },
                  "outcome": {
                    "type": "string",
                    "enum": [
                      "accept",
                      "reject",
                      "unresolved"
                    ]
                  }
                }
              },
              "reviewers": {
                "type": [
                  "object",
                  "null"
                ],
                "additionalProperties": false,
                "required": [
                  "kind",
                  "count",
                  "independent"
                ],
                "description": "Who performs this node's judgment. Humans and agents are the two reviewer kinds. A human using an agent remains the accountable reviewer; the assistance is recorded as provenance on the assignment, not as a third kind.",
                "properties": {
                  "kind": {
                    "type": "string",
                    "enum": [
                      "human",
                      "agent"
                    ],
                    "examples": [
                      "agent"
                    ]
                  },
                  "count": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 25,
                    "description": "How many distinct reviewers must complete this node.",
                    "examples": [
                      3
                    ]
                  },
                  "independent": {
                    "type": "boolean",
                    "description": "When true, reviewers on this node cannot see one another's responses before submitting their own. Each is placed in its own independence group.",
                    "examples": [
                      true
                    ]
                  },
                  "eligibility": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "additionalProperties": false,
                    "default": null,
                    "required": [
                      "rule"
                    ],
                    "description": "For human reviewers: who may claim a seat on this node. TfP never assigns a human; eligible Users discover open seats and claim them. Required when kind is human; null for agents.",
                    "properties": {
                      "rule": {
                        "type": "string",
                        "enum": [
                          "any_participant",
                          "organization_members",
                          "campaign_reviewers",
                          "named_users"
                        ],
                        "examples": [
                          "campaign_reviewers"
                        ]
                      },
                      "organization_id": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "pattern": "^org_[0-9a-z]{10,32}$",
                        "default": null,
                        "description": "Required when rule is organization_members."
                      },
                      "user_ids": {
                        "type": [
                          "array",
                          "null"
                        ],
                        "default": null,
                        "maxItems": 50,
                        "items": {
                          "type": "string",
                          "pattern": "^usr_[0-9a-z]{10,32}$"
                        },
                        "uniqueItems": true,
                        "description": "Required when rule is named_users."
                      }
                    },
                    "allOf": [
                      {
                        "if": {
                          "properties": {
                            "rule": {
                              "const": "organization_members"
                            }
                          },
                          "required": [
                            "rule"
                          ]
                        },
                        "then": {
                          "properties": {
                            "organization_id": {
                              "type": "string"
                            }
                          }
                        }
                      },
                      {
                        "if": {
                          "properties": {
                            "rule": {
                              "const": "named_users"
                            }
                          },
                          "required": [
                            "rule"
                          ]
                        },
                        "then": {
                          "properties": {
                            "user_ids": {
                              "type": "array",
                              "minItems": 1
                            }
                          }
                        }
                      }
                    ]
                  },
                  "allowed": {
                    "type": [
                      "array",
                      "null"
                    ],
                    "default": null,
                    "minItems": 1,
                    "maxItems": 20,
                    "items": {
                      "type": "object",
                      "additionalProperties": false,
                      "required": [
                        "provider",
                        "model",
                        "config"
                      ],
                      "properties": {
                        "provider": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 60,
                          "description": "Model provider identifier.",
                          "examples": [
                            "anthropic"
                          ]
                        },
                        "model": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 120,
                          "description": "Exact model identifier.",
                          "examples": [
                            "claude-fable-5-1"
                          ]
                        },
                        "config": {
                          "type": "object",
                          "description": "Provider-specific configuration TfP's hosted path executes; part of the reviewer's identity for this protocol. Provider-generic in shape, but a document is valid only if TfP's hosted path supports every key (Anthropic today: `effort` low|medium|high|xhigh|max and `max_tokens` 256..64000). Provider defaults are resolved when an assignment is issued, so the assignment records the effective configuration that ran.",
                          "examples": [
                            {
                              "effort": "high"
                            }
                          ]
                        }
                      }
                    },
                    "description": "For TfP-hosted agent reviewers only: the exact provider/model/config combinations TfP itself will invoke to fill this node (an optional mechanism; the default is human reviewers who bring their own agents and declare them as assistance). Required when kind is agent; null for humans. There is no global model-strength hierarchy; a protocol names what it accepts."
                  },
                  "assistance": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "additionalProperties": false,
                    "default": null,
                    "required": [
                      "required",
                      "allowed"
                    ],
                    "description": "Human nodes only. Whether reviewers must declare an agent that assisted them, and which are allowed. TfP enforces the declaration on submission and records it; it cannot observe what was actually used.",
                    "properties": {
                      "required": {
                        "type": "boolean"
                      },
                      "allowed": {
                        "type": "array",
                        "maxItems": 20,
                        "items": {
                          "type": "object",
                          "additionalProperties": false,
                          "required": [
                            "provider",
                            "model"
                          ],
                          "properties": {
                            "provider": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 60
                            },
                            "model": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 120
                            }
                          }
                        },
                        "description": "Provider/model pairs in the author's own words — the reviewer runs these, TfP does not, so no entry need be one TfP can invoke. Empty means any declared assistance is acceptable."
                      }
                    }
                  }
                },
                "allOf": [
                  {
                    "if": {
                      "properties": {
                        "kind": {
                          "const": "human"
                        }
                      }
                    },
                    "then": {
                      "properties": {
                        "eligibility": {
                          "type": "object"
                        },
                        "allowed": {
                          "type": "null"
                        }
                      },
                      "required": [
                        "eligibility"
                      ]
                    }
                  },
                  {
                    "if": {
                      "properties": {
                        "kind": {
                          "const": "agent"
                        }
                      }
                    },
                    "then": {
                      "properties": {
                        "allowed": {
                          "type": "array"
                        },
                        "eligibility": {
                          "type": "null"
                        }
                      },
                      "required": [
                        "allowed"
                      ]
                    }
                  }
                ],
                "default": null
              },
              "visibility": {
                "type": [
                  "object",
                  "null"
                ],
                "additionalProperties": false,
                "required": [
                  "prior_nodes",
                  "stage_evidence",
                  "hidden"
                ],
                "description": "What a reviewer on this node may inspect. The subject itself, and its Submission and Artifacts where applicable, are always visible; everything else is opt-in.",
                "properties": {
                  "prior_nodes": {
                    "type": "array",
                    "maxItems": 20,
                    "items": {
                      "type": "string",
                      "pattern": "^[a-z][a-z0-9_]{0,39}$"
                    },
                    "uniqueItems": true,
                    "description": "Ids of earlier nodes whose outputs this node's reviewers may read — how a meta-reviewer sees the panel's reports. Must be dependencies of this node.",
                    "examples": [
                      []
                    ]
                  },
                  "stage_evidence": {
                    "type": "boolean",
                    "description": "Whether read-only evidence from earlier VerifierContract stages (a sandbox stage's evidence summary, for example) is shown.",
                    "examples": [
                      true
                    ]
                  },
                  "hidden": {
                    "type": "array",
                    "maxItems": 5,
                    "uniqueItems": true,
                    "items": {
                      "type": "string",
                      "enum": [
                        "submitter_identity",
                        "submitter_organization",
                        "prior_scores",
                        "attempt_notes"
                      ]
                    },
                    "description": "Blinding: information withheld from reviewers on this node.",
                    "examples": [
                      [
                        "submitter_identity"
                      ]
                    ]
                  }
                },
                "default": null
              },
              "conflicts": {
                "type": [
                  "object",
                  "null"
                ],
                "additionalProperties": false,
                "required": [
                  "exclude_submitter",
                  "exclude_submitter_organization"
                ],
                "description": "Reviewers excluded because of their relationship to the subject. Applies to human reviewers only. Null on a human node means the defaults: exclude_submitter true, exclude_submitter_organization false. Null (or anything) on an agent node has no effect.",
                "properties": {
                  "exclude_submitter": {
                    "type": "boolean",
                    "examples": [
                      true
                    ]
                  },
                  "exclude_submitter_organization": {
                    "type": "boolean",
                    "description": "Exclude current members of the Organization the Attempt is attributed to.",
                    "examples": [
                      false
                    ]
                  }
                },
                "default": null
              },
              "skills": {
                "type": [
                  "array",
                  "null"
                ],
                "default": null,
                "maxItems": 10,
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "skill_version_id",
                    "requirement"
                  ],
                  "properties": {
                    "skill_version_id": {
                      "type": "string",
                      "pattern": "^skv_[0-9a-z]{10,32}$"
                    },
                    "requirement": {
                      "type": "string",
                      "enum": [
                        "required",
                        "recommended"
                      ]
                    }
                  }
                },
                "description": "SkillVersions supplied to reviewers on this node as context — a technoeconomic-analysis review can require the exact TEA methodology Skill. Provided automatically to agents and surfaced to humans; the assignment records what was supplied."
              },
              "rubric": {
                "type": [
                  "object",
                  "null"
                ],
                "additionalProperties": false,
                "required": [
                  "instructions_md",
                  "response_schema",
                  "rationale",
                  "citations",
                  "confidence"
                ],
                "description": "What reviewers are asked and the exact shape of what they must return. Inline with the protocol; there is no separate Rubric object. Four things come back from every review: the domain-specific `response` (validated against response_schema), and the universal rationale_md, citations, and confidence, whose requirements are declared here so analysis across protocols reads the same fields.",
                "properties": {
                  "instructions_md": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 20000,
                    "description": "Human-readable instructions given to every reviewer on this node.",
                    "examples": [
                      "Assess whether the extracted reaction record faithfully represents the cited source. Cite the passage supporting each judgment."
                    ]
                  },
                  "response_schema": {
                    "type": "object",
                    "description": "JSON Schema (draft 2020-12) every response must satisfy: the domain-specific structured judgment. Reducers reference fields declared here. Do not declare rationale, citations, or confidence here; those are universal fields with their own requirements below.",
                    "examples": [
                      {
                        "type": "object",
                        "required": [
                          "verdict"
                        ],
                        "properties": {
                          "verdict": {
                            "enum": [
                              "pass",
                              "fail",
                              "uncertain"
                            ]
                          }
                        }
                      }
                    ]
                  },
                  "rationale": {
                    "type": "string",
                    "enum": [
                      "required",
                      "optional"
                    ],
                    "description": "Whether every completed assignment must carry a non-empty rationale_md.",
                    "examples": [
                      "required"
                    ]
                  },
                  "citations": {
                    "type": "object",
                    "additionalProperties": false,
                    "required": [
                      "min_items"
                    ],
                    "description": "Minimum number of structured citations each completed assignment must carry; 0 makes them optional.",
                    "properties": {
                      "min_items": {
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 50,
                        "examples": [
                          1
                        ]
                      }
                    }
                  },
                  "confidence": {
                    "type": "string",
                    "enum": [
                      "required",
                      "optional"
                    ],
                    "description": "Whether every completed assignment must declare a confidence in [0, 1] on the assignment's universal confidence field.",
                    "examples": [
                      "required"
                    ]
                  }
                },
                "default": null
              },
              "inputs": {
                "type": [
                  "array",
                  "null"
                ],
                "default": null,
                "minItems": 1,
                "maxItems": 20,
                "items": {
                  "type": "string",
                  "pattern": "^[a-z][a-z0-9_]{0,39}$"
                },
                "uniqueItems": true,
                "description": "reduce: the upstream nodes read, in order. k_of_n and threshold take exactly one review node; coalesce takes one or more reduce nodes in priority order."
              },
              "op": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "k_of_n",
                  "threshold",
                  "coalesce",
                  null
                ],
                "default": null,
                "description": "reduce: the deterministic operation. The v0 catalogue is closed: k_of_n (counting a predicate over responses, with separate accept and reject thresholds), threshold (an aggregate of a numeric field against accept/reject cut-offs), coalesce (the first resolved outcome among earlier reducers)."
              },
              "config": {
                "type": [
                  "object",
                  "null"
                ],
                "additionalProperties": false,
                "description": "Operation configuration. k_of_n: field, accept, reject (eq/in predicates with disjoint values). threshold: field, aggregate, accept_gte, reject_lt. coalesce: none. See the reducer catalogue in docs/reference/reviews.md.",
                "properties": {
                  "field": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120,
                    "description": "Field of each upstream response the reducer reads.",
                    "examples": [
                      "verdict"
                    ]
                  },
                  "accept": {
                    "type": "object",
                    "additionalProperties": false,
                    "required": [
                      "predicate",
                      "k"
                    ],
                    "properties": {
                      "predicate": {
                        "type": "object",
                        "additionalProperties": false,
                        "required": [
                          "op",
                          "value"
                        ],
                        "properties": {
                          "op": {
                            "type": "string",
                            "enum": [
                              "eq",
                              "in"
                            ],
                            "description": "k_of_n compares by membership only, so accept and reject match sets can be proven disjoint statically. Numeric comparison belongs to the threshold reducer."
                          },
                          "value": {
                            "description": "A scalar for eq, an array of scalars for in."
                          }
                        }
                      },
                      "k": {
                        "type": "integer",
                        "minimum": 1,
                        "description": "How many responses must match."
                      }
                    },
                    "description": "k_of_n: accept when at least k responses satisfy the predicate."
                  },
                  "reject": {
                    "type": "object",
                    "additionalProperties": false,
                    "required": [
                      "predicate",
                      "k"
                    ],
                    "properties": {
                      "predicate": {
                        "type": "object",
                        "additionalProperties": false,
                        "required": [
                          "op",
                          "value"
                        ],
                        "properties": {
                          "op": {
                            "type": "string",
                            "enum": [
                              "eq",
                              "in"
                            ],
                            "description": "k_of_n compares by membership only, so accept and reject match sets can be proven disjoint statically. Numeric comparison belongs to the threshold reducer."
                          },
                          "value": {
                            "description": "A scalar for eq, an array of scalars for in."
                          }
                        }
                      },
                      "k": {
                        "type": "integer",
                        "minimum": 1,
                        "description": "How many responses must match."
                      }
                    },
                    "description": "k_of_n: reject when at least k responses satisfy the predicate. Static validation requires the accept and reject value sets to be disjoint and accept.k + reject.k > reviewer count, so both outcomes can never hold; when neither holds the outcome is unresolved."
                  },
                  "aggregate": {
                    "type": "string",
                    "enum": [
                      "mean",
                      "median",
                      "min",
                      "max"
                    ],
                    "description": "threshold: how numeric responses are combined."
                  },
                  "accept_gte": {
                    "type": "number",
                    "description": "threshold: accept when the aggregate is at least this."
                  },
                  "reject_lt": {
                    "type": "number",
                    "description": "threshold: reject when the aggregate is below this. Must not exceed accept_gte; between them the outcome is unresolved."
                  }
                },
                "default": null
              }
            },
            "allOf": [
              {
                "if": {
                  "properties": {
                    "type": {
                      "const": "review"
                    }
                  }
                },
                "then": {
                  "required": [
                    "reviewers",
                    "visibility",
                    "rubric"
                  ],
                  "properties": {
                    "reviewers": {
                      "type": "object"
                    },
                    "visibility": {
                      "type": "object"
                    },
                    "rubric": {
                      "type": "object"
                    },
                    "inputs": {
                      "type": "null"
                    },
                    "op": {
                      "type": "null"
                    },
                    "config": {
                      "type": "null"
                    }
                  }
                }
              },
              {
                "if": {
                  "properties": {
                    "type": {
                      "const": "reduce"
                    }
                  }
                },
                "then": {
                  "required": [
                    "inputs",
                    "op",
                    "config"
                  ],
                  "properties": {
                    "inputs": {
                      "type": "array"
                    },
                    "op": {
                      "type": "string"
                    },
                    "config": {
                      "type": "object"
                    },
                    "reviewers": {
                      "type": "null"
                    },
                    "visibility": {
                      "type": "null"
                    },
                    "conflicts": {
                      "type": "null"
                    },
                    "rubric": {
                      "type": "null"
                    },
                    "skills": {
                      "type": "null"
                    }
                  }
                }
              }
            ]
          },
          "description": "The DAG. Dependencies are implied by reduce inputs, review visibility.prior_nodes, and when.node; static validation requires the graph to be acyclic."
        },
        "output": {
          "type": "string",
          "pattern": "^[a-z][a-z0-9_]{0,39}$",
          "description": "The reduce node whose outcome is the protocol's result. Must be a reduce node, and every other node must be an ancestor of it or conditional on one.",
          "examples": [
            "final"
          ]
        },
        "policy": {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "assignment_timeout_hours"
          ],
          "description": "Execution policy for the ReviewProcess. Finite by construction, so a process never stays open indefinitely: the node deadline bounds every seat.",
          "properties": {
            "assignment_timeout_hours": {
              "type": "integer",
              "minimum": 1,
              "maximum": 720,
              "description": "The node deadline: how long, from the moment a review node opens its seats, eligible reviewers have to claim and complete them. A seat still open at the deadline ends the process inconclusive (reviewers_unavailable).",
              "examples": [
                72
              ]
            },
            "max_replacements_per_node": {
              "type": "integer",
              "minimum": 0,
              "maximum": 10,
              "description": "DEPRECATED, ignored. Retired with the pull-based change (2026-09-02): accepted only so earlier documents still validate; omit it from new documents. Seats reopen on their own; the node deadline is the bound.",
              "examples": [
                2
              ]
            },
            "claim_hold_minutes": {
              "type": "integer",
              "minimum": 15,
              "maximum": 1440,
              "default": 120,
              "description": "How long a claim holds a seat before it lapses and the seat reopens. Short by design, so nobody can sit on a seat; must be shorter than assignment_timeout_hours.",
              "examples": [
                120
              ]
            }
          }
        }
      },
      "x-mutable": false
    },
    "created_at": {
      "type": "string",
      "format": "date-time",
      "x-mutable": false,
      "examples": [
        "2026-09-10T09:00:00Z"
      ]
    },
    "yanked_at": {
      "type": [
        "string",
        "null"
      ],
      "format": "date-time",
      "x-mutable": true,
      "default": null,
      "description": "Write-once withdrawal from recommendation. Existing Contract pins keep resolving; existing processes keep their meaning.",
      "examples": [
        null
      ]
    }
  }
}