{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://yarramate.org/schema/question-catalogue/v1",
  "title": "YarraMate question catalogue",
  "description": "A versioned, opinionated catalogue of enrichment questions. Each question binds a deterministic trigger evaluated against a compiled graph-v2 workspace: the question is open wherever its trigger matches and closed when it no longer matches. Interview state is recomputed, never stored.",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "format",
    "id",
    "version",
    "profile",
    "waves",
    "questions"
  ],
  "properties": {
    "format": {
      "const": "yarramate/question-catalogue/v1"
    },
    "id": {
      "$ref": "#/$defs/id"
    },
    "version": {
      "type": "string",
      "pattern": "^[0-9]+\\.[0-9]+$"
    },
    "profile": {
      "description": "Profile identity whose vocabulary the triggers are expressed against.",
      "$ref": "#/$defs/profileIdentity"
    },
    "presentation": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "title"
      ],
      "properties": {
        "title": {
          "$ref": "#/$defs/nonEmptyText"
        },
        "description": {
          "$ref": "#/$defs/nonEmptyText"
        }
      }
    },
    "waves": {
      "description": "Ordered interview phases. Array order is interview order. The wave taxonomy is catalogue opinion, not engine semantics. MAY BE EMPTY (#345, ADR 0129): a catalogue that only contributes questions to waves ANOTHER catalogue declared is the ordinary shape of a project catalogue, and requiring a wave here would force it to declare one it does not want - which is refused as a duplicate the moment two catalogues do it. Evaluated alone, a catalogue with no waves and a question naming one is YM911, correctly: nothing declares it.",
      "type": "array",
      "minItems": 0,
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "name"
        ],
        "properties": {
          "id": {
            "$ref": "#/$defs/id"
          },
          "name": {
            "$ref": "#/$defs/nonEmptyText"
          },
          "description": {
            "$ref": "#/$defs/nonEmptyText"
          },
          "opensWhen": {
            "description": "Conditions that must all hold before this wave opens (#334, ADR 0125). A wave that has not opened asks nothing: its questions are premature rather than answered, and the report says so with `opened: false` and no questions rather than reporting them closed. Written in the same condition vocabulary a question's trigger uses, so a reviewer reads a gate the way they already read a trigger. Absent means the wave is always open, which is what every wave did before this existed.",
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/$defs/condition"
            }
          }
        }
      }
    },
    "questions": {
      "type": "array",
      "minItems": 1,
      "items": {
        "$ref": "#/$defs/question"
      }
    }
  },
  "$defs": {
    "id": {
      "type": "string",
      "pattern": "^[a-z][a-z0-9-]*$"
    },
    "nonEmptyText": {
      "type": "string",
      "minLength": 1
    },
    "profileIdentity": {
      "type": "string",
      "pattern": "^[a-z][a-z0-9/-]*@[0-9][0-9A-Za-z.-]*$"
    },
    "catalogueIdentity": {
      "type": "string",
      "pattern": "^[a-z][a-z0-9/-]*@[0-9][0-9A-Za-z.-]*$"
    },
    "qualifiedKind": {
      "type": "string",
      "pattern": "^[a-z][a-z0-9/-]*@[0-9][0-9A-Za-z.-]*#[A-Za-z][A-Za-z0-9-]*$"
    },
    "claimPredicate": {
      "type": "string",
      "pattern": "^[a-z][a-z0-9/@.#-]*$"
    },
    "subjectSelector": {
      "description": "Which subjects a subject-scope question applies to. Field vocabulary mirrors the projection query selector (ADR 0017).",
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "kinds": {
          "type": "array",
          "minItems": 1,
          "uniqueItems": true,
          "items": {
            "$ref": "#/$defs/qualifiedKind"
          }
        },
        "kindMatching": {
          "enum": [
            "exact",
            "descendants"
          ],
          "default": "descendants"
        },
        "statuses": {
          "type": "array",
          "minItems": 1,
          "uniqueItems": true,
          "items": {
            "enum": [
              "planned",
              "current",
              "retired"
            ]
          }
        },
        "documents": {
          "type": "array",
          "minItems": 1,
          "uniqueItems": true,
          "items": {
            "$ref": "#/$defs/id"
          }
        }
      }
    },
    "condition": {
      "description": "One deterministic trigger condition. All conditions in a trigger must hold (AND) for the question to be open.",
      "oneOf": [
        {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "condition",
            "predicate"
          ],
          "properties": {
            "condition": {
              "const": "missing-claim"
            },
            "predicate": {
              "$ref": "#/$defs/claimPredicate"
            }
          }
        },
        {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "condition",
            "kinds",
            "direction"
          ],
          "properties": {
            "condition": {
              "const": "missing-relationship"
            },
            "kinds": {
              "type": "array",
              "minItems": 1,
              "uniqueItems": true,
              "items": {
                "$ref": "#/$defs/qualifiedKind"
              }
            },
            "direction": {
              "enum": [
                "incoming",
                "outgoing",
                "any"
              ]
            },
            "kindMatching": {
              "enum": [
                "exact",
                "descendants"
              ]
            }
          }
        },
        {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "condition"
          ],
          "properties": {
            "condition": {
              "const": "isolated"
            }
          }
        },
        {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "condition",
            "kinds"
          ],
          "properties": {
            "condition": {
              "const": "no-subject-of-kind"
            },
            "kinds": {
              "type": "array",
              "minItems": 1,
              "uniqueItems": true,
              "items": {
                "$ref": "#/$defs/qualifiedKind"
              }
            },
            "kindMatching": {
              "enum": [
                "exact",
                "descendants"
              ],
              "default": "descendants"
            }
          }
        },
        {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "condition",
            "kinds"
          ],
          "properties": {
            "condition": {
              "const": "has-subject-of-kind"
            },
            "kinds": {
              "type": "array",
              "minItems": 1,
              "uniqueItems": true,
              "items": {
                "$ref": "#/$defs/qualifiedKind"
              }
            },
            "kindMatching": {
              "enum": [
                "exact",
                "descendants"
              ],
              "default": "descendants"
            }
          }
        },
        {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "condition",
            "kinds",
            "atLeast"
          ],
          "properties": {
            "condition": {
              "const": "below-subject-count"
            },
            "kinds": {
              "type": "array",
              "minItems": 1,
              "uniqueItems": true,
              "items": {
                "$ref": "#/$defs/qualifiedKind"
              }
            },
            "atLeast": {
              "description": "The floor the kind's population must reach. At least 2: atLeast 1 is no-subject-of-kind under another name, and the engine refuses it (YM918).",
              "type": "integer",
              "minimum": 1
            },
            "kindMatching": {
              "enum": [
                "exact",
                "descendants"
              ],
              "default": "descendants"
            }
          }
        },
        {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "condition"
          ],
          "properties": {
            "condition": {
              "const": "no-state-defined"
            }
          }
        },
        {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "condition",
            "kinds",
            "direction",
            "counterpartKinds"
          ],
          "properties": {
            "condition": {
              "const": "missing-linkage"
            },
            "kinds": {
              "type": "array",
              "minItems": 1,
              "uniqueItems": true,
              "items": {
                "$ref": "#/$defs/qualifiedKind"
              }
            },
            "direction": {
              "enum": [
                "outgoing",
                "incoming"
              ]
            },
            "counterpartKinds": {
              "type": "array",
              "minItems": 1,
              "uniqueItems": true,
              "items": {
                "$ref": "#/$defs/qualifiedKind"
              }
            },
            "kindMatching": {
              "enum": [
                "exact",
                "descendants"
              ],
              "default": "descendants"
            }
          }
        },
        {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "condition",
            "kinds",
            "direction",
            "counterpartKinds"
          ],
          "properties": {
            "condition": {
              "const": "has-linkage"
            },
            "kinds": {
              "type": "array",
              "minItems": 1,
              "uniqueItems": true,
              "items": {
                "$ref": "#/$defs/qualifiedKind"
              }
            },
            "direction": {
              "enum": [
                "outgoing",
                "incoming",
                "either"
              ]
            },
            "counterpartKinds": {
              "type": "array",
              "minItems": 1,
              "uniqueItems": true,
              "items": {
                "$ref": "#/$defs/qualifiedKind"
              }
            },
            "kindMatching": {
              "enum": [
                "exact",
                "descendants"
              ],
              "default": "descendants"
            }
          }
        },
        {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "condition",
            "kinds",
            "direction",
            "counterpartKinds"
          ],
          "properties": {
            "condition": {
              "const": "exists-linkage"
            },
            "kinds": {
              "type": "array",
              "minItems": 1,
              "uniqueItems": true,
              "items": {
                "$ref": "#/$defs/qualifiedKind"
              }
            },
            "direction": {
              "enum": [
                "outgoing",
                "incoming",
                "either"
              ]
            },
            "counterpartKinds": {
              "type": "array",
              "minItems": 1,
              "uniqueItems": true,
              "items": {
                "$ref": "#/$defs/qualifiedKind"
              }
            },
            "kindMatching": {
              "enum": [
                "exact",
                "descendants"
              ],
              "default": "descendants"
            }
          }
        },
        {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "condition",
            "kinds",
            "direction",
            "counterpartKinds"
          ],
          "properties": {
            "condition": {
              "const": "no-linkage-exists"
            },
            "kinds": {
              "type": "array",
              "minItems": 1,
              "uniqueItems": true,
              "items": {
                "$ref": "#/$defs/qualifiedKind"
              }
            },
            "direction": {
              "enum": [
                "outgoing",
                "incoming",
                "either"
              ]
            },
            "counterpartKinds": {
              "type": "array",
              "minItems": 1,
              "uniqueItems": true,
              "items": {
                "$ref": "#/$defs/qualifiedKind"
              }
            },
            "kindMatching": {
              "enum": [
                "exact",
                "descendants"
              ],
              "default": "descendants"
            }
          }
        },
        {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "condition",
            "kinds"
          ],
          "properties": {
            "condition": {
              "const": "missing-constraint"
            },
            "kinds": {
              "type": "array",
              "minItems": 1,
              "uniqueItems": true,
              "items": {
                "$ref": "#/$defs/qualifiedKind"
              }
            },
            "kindMatching": {
              "enum": [
                "exact",
                "descendants"
              ],
              "default": "descendants"
            }
          }
        },
        {
          "type": "object",
          "additionalProperties": false,
          "description": "The subject is an endpoint of at least one flow relationship that has no yarramate/flow/content claim.",
          "required": [
            "condition"
          ],
          "properties": {
            "condition": {
              "const": "missing-flow-content"
            }
          }
        },
        {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "condition",
            "predicate",
            "direction"
          ],
          "properties": {
            "condition": {
              "const": "missing-reference"
            },
            "predicate": {
              "$ref": "#/$defs/claimPredicate"
            },
            "direction": {
              "enum": [
                "outgoing",
                "incoming"
              ]
            }
          }
        },
        {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "condition",
            "topic"
          ],
          "properties": {
            "condition": {
              "const": "missing-attestation"
            },
            "topic": {
              "type": "string",
              "pattern": "^[a-z][a-z0-9-]*$"
            }
          }
        },
        {
          "type": "object",
          "additionalProperties": false,
          "description": "The subject resembles another subject of the same kind closely enough to be the same thing under two names, and no yarramate/identity/distinct-from claim dismisses the pair. Deterministic and lexical; the algorithm and thresholds are stated in ADR 0077. Questions using it may interpolate {counterparts}.",
          "required": [
            "condition"
          ],
          "properties": {
            "condition": {
              "const": "near-duplicate"
            }
          }
        },
        {
          "type": "object",
          "additionalProperties": false,
          "description": "The subject's kind is never tested by anything the compiler can check: it participates in no relationship whose kind pins the aspect at the subject's end, so reclassifying it to any other kind of any other aspect would still compile. The kind is a label rather than a constraint (ADR 0083).",
          "required": [
            "condition"
          ],
          "properties": {
            "condition": {
              "const": "unconstrained-kind"
            }
          }
        },
        {
          "type": "object",
          "additionalProperties": false,
          "description": "The subject supersedes a predecessor that is still current, and does not say in what respect. A succession that replaced its predecessor outright says so by the predecessor being gone; one where both remain is usually partial, and the respect is the part a reader needs (ADR 0109).",
          "required": [
            "condition"
          ],
          "properties": {
            "condition": {
              "const": "unscoped-succession"
            }
          }
        },
        {
          "type": "object",
          "additionalProperties": false,
          "description": "The workspace holds at least one concept (#334, ADR 0125). The guard a late wave needs to say \"only once the model has substance\": an empty model is not an architecture at rest, it has not started, and asking it how the planned architecture becomes real greets someone ahead of question one.",
          "required": [
            "condition"
          ],
          "properties": {
            "condition": {
              "const": "has-any-subject"
            }
          }
        },
        {
          "type": "object",
          "additionalProperties": false,
          "description": "The subject fills a slot of a pattern instance (ADR 0131). A guard, per the #334 split: it says a question applies here, and an ordinary condition beside it says what would answer it. Bare, it means bound into any slot of any instance; patternKinds narrows by the pattern's kind identity (never a document path, ADR 0129) and slots by part name. Evaluation reads pattern memberships from the compilation; a caller that does not supply them gets a condition that never holds.",
          "required": [
            "condition"
          ],
          "properties": {
            "condition": {
              "const": "fills-pattern-slot"
            },
            "patternKinds": {
              "type": "array",
              "minItems": 1,
              "uniqueItems": true,
              "items": {
                "$ref": "#/$defs/qualifiedKind"
              }
            },
            "slots": {
              "type": "array",
              "minItems": 1,
              "uniqueItems": true,
              "items": {
                "type": "string",
                "pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$"
              }
            }
          }
        },
        {
          "type": "object",
          "additionalProperties": false,
          "description": "The subject is a pattern INSTANCE with a slot nothing is bound into (#447): the mirror of fills-pattern-slot, and the mechanism by which a pattern becomes a questionnaire. Bare, it means any slot of this instance's pattern is unbound; patternKinds narrows by the pattern's kind identity and slots by part name. It fires for a required slot only where one survives a compile: an instance declaring parts that omits a required one is YM416, while an instance declaring no parts at all never reaches YM416 and is asked about every slot. Evaluation reads pattern vacancies from the compilation; a caller that does not supply them gets a condition that never holds.",
          "required": [
            "condition"
          ],
          "properties": {
            "condition": {
              "const": "missing-part"
            },
            "patternKinds": {
              "type": "array",
              "minItems": 1,
              "uniqueItems": true,
              "items": {
                "$ref": "#/$defs/qualifiedKind"
              }
            },
            "slots": {
              "type": "array",
              "minItems": 1,
              "uniqueItems": true,
              "items": {
                "type": "string",
                "pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$"
              }
            }
          }
        },
        {
          "type": "object",
          "additionalProperties": false,
          "description": "The workspace's evidence overlay records observations and every one is a frictionless confirmation: no contradicted, unknown, or not-observed result, and no recorded search (ADR 0107). A discovery that never records anything but success never tested a claim it might fail. The one condition that reads the evidence overlay rather than the compiled graph; where the evaluating consumer supplies no overlay it stays quiet (ADR 0120).",
          "required": [
            "condition"
          ],
          "properties": {
            "condition": {
              "const": "unchallenged-evidence"
            }
          }
        }
      ]
    },
    "question": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "id",
        "wave",
        "scope",
        "trigger",
        "question",
        "materiality",
        "authority"
      ],
      "properties": {
        "id": {
          "$ref": "#/$defs/id"
        },
        "wave": {
          "description": "Wave id declared in this catalogue (or an extended one).",
          "$ref": "#/$defs/id"
        },
        "scope": {
          "description": "subject: evaluated once per matching subject. workspace: evaluated once per workspace.",
          "enum": [
            "subject",
            "workspace"
          ]
        },
        "subjects": {
          "$ref": "#/$defs/subjectSelector"
        },
        "trigger": {
          "type": "array",
          "minItems": 1,
          "items": {
            "$ref": "#/$defs/condition"
          }
        },
        "question": {
          "description": "Human-facing phrasing. Subject-scope questions may interpolate {subject.id} and {subject.name}.",
          "$ref": "#/$defs/nonEmptyText"
        },
        "askPlain": {
          "description": "Optional plain-language phrasing of the same question, written to be read aloud in a stakeholder workshop. Interpolates {subject.id} and {subject.name} exactly like question. design --facilitate prefers it; when absent, renderers fall back to question and never block.",
          "$ref": "#/$defs/nonEmptyText"
        },
        "materiality": {
          "description": "Which architectural decision the answer changes. A question that cannot state this does not belong in the catalogue.",
          "$ref": "#/$defs/nonEmptyText"
        },
        "authority": {
          "description": "Who may answer. A label carried into reports, never a gate (ADR 0082). agent: an agent may propose the answer from repository evidence (still lands as a reviewable diff). human: a genuine design decision that must be escalated. either: an agent may propose, a human confirms.",
          "enum": [
            "agent",
            "human",
            "either"
          ]
        },
        "resolution": {
          "description": "Guidance for how an accepted answer should land as native authoring (which claim, relationship, or state to add).",
          "$ref": "#/$defs/nonEmptyText"
        },
        "since": {
          "description": "The catalogue version this question first appeared in. Lets consumers distinguish 'the catalogue deepened' from 'the model regressed' when a completed interview reopens (ADR 0063).",
          "type": "string",
          "pattern": "^[0-9]+\\.[0-9]+$"
        }
      },
      "allOf": [
        {
          "if": {
            "properties": {
              "scope": {
                "const": "subject"
              }
            }
          },
          "then": {
            "required": [
              "subjects"
            ]
          }
        },
        {
          "if": {
            "properties": {
              "scope": {
                "const": "workspace"
              }
            }
          },
          "then": {
            "not": {
              "required": [
                "subjects"
              ]
            }
          }
        }
      ]
    }
  }
}
