{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://raw.githubusercontent.com/hegemonart/get-design-done/main/reference/schemas/events.schema.json",
  "title": "Event",
  "description": "One line of .design/telemetry/events.jsonl — the append-only telemetry stream produced by Plan 20-06. Each event is a single JSON object followed by a newline. See .planning/phases/20-gdd-sdk-foundation/20-06-PLAN.md.",
  "type": "object",
  "additionalProperties": false,
  "required": ["type", "timestamp", "sessionId", "payload"],
  "properties": {
    "type": {
      "type": "string",
      "minLength": 1,
      "description": "Free-form event type identifier. Pre-registered seeds: state.mutation, state.transition, stage.entered, stage.exited, hook.fired, error, capability_gap, kfm-candidate, router_pick, verify_outcome, rollout_started, rollout_advanced, rollout_stuck, budget_forecast, project_cap_warning, project_cap_halt, live_session_start, live_pick, live_generate, live_accept, live_discard, live_session_end, instinct_emitted, instinct_promoted, instinct_decayed, risk_assessment."
    },
    "timestamp": {
      "type": "string",
      "format": "date-time",
      "description": "ISO-8601 timestamp of event emission."
    },
    "sessionId": {
      "type": "string",
      "minLength": 1,
      "description": "Stable identifier per GDD pipeline run; correlates events across stages."
    },
    "stage": {
      "type": "string",
      "enum": ["brief", "explore", "plan", "design", "verify"],
      "description": "Optional — present when the event is scoped to a pipeline stage."
    },
    "cycle": {
      "type": "string",
      "minLength": 1,
      "description": "Optional — present when the event is scoped to a cycle identifier."
    },
    "payload": {
      "type": "object",
      "description": "Event-type-specific payload. Opaque at the envelope level. When type === 'capability_gap', the payload is narrowed by the conditional schema in allOf[0] to the 7-field CapabilityGapPayload (Phase 29 D-02)."
    },
    "_meta": {
      "type": "object",
      "additionalProperties": false,
      "required": ["pid", "host", "source"],
      "properties": {
        "pid": { "type": "integer", "minimum": 0 },
        "host": { "type": "string" },
        "source": { "type": "string" }
      },
      "description": "Writer-injected provenance. Never set by callers."
    },
    "_truncated": {
      "type": "boolean",
      "description": "Writer-set flag indicating the payload exceeded maxLineBytes and has been replaced by a placeholder."
    }
  },
  "definitions": {
    "TrajectoryRef": {
      "type": "object",
      "additionalProperties": false,
      "required": ["trajectory_path", "byte_start", "byte_end", "content_hash"],
      "properties": {
        "trajectory_path": {
          "type": "string",
          "minLength": 1,
          "description": "Repo-relative or absolute path to the trajectory-JSONL file containing the referenced evidence slice."
        },
        "byte_start": {
          "type": "integer",
          "minimum": 0,
          "description": "Inclusive byte offset into the trajectory file marking the start of the evidence slice."
        },
        "byte_end": {
          "type": "integer",
          "minimum": 0,
          "description": "Exclusive byte offset into the trajectory file marking the end of the evidence slice."
        },
        "content_hash": {
          "type": "string",
          "pattern": "^sha256:[0-9a-f]{64}$",
          "description": "Hash-pinned content fingerprint (Phase 29 D-07). Detects retroactive chain mutation."
        }
      },
      "description": "Pointer to a slice of a trajectory-JSONL file (Phase 29 D-07). Hash-pinned so consumers can detect chain mutation. Used inside CapabilityGapPayload.evidence_refs to keep payloads small while preserving audit-traceability — pointers, NOT duplicated trajectory content."
    },
    "CapabilityGapPayload": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "event_id",
        "parent_event_id",
        "source",
        "context_hash",
        "intent_summary",
        "suggested_kind",
        "evidence_refs"
      ],
      "properties": {
        "event_id": {
          "type": "string",
          "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
          "description": "UUIDv4 identifying this capability_gap event. Stable across emit + read cycles."
        },
        "parent_event_id": {
          "type": ["string", "null"],
          "description": "UUIDv4 of the parent chain event when the gap was surfaced inside a larger trajectory; null when this is a root emit (e.g. /gdd:fast bail-out)."
        },
        "source": {
          "type": "string",
          "enum": ["fast", "router", "reflector_pattern"],
          "description": "Which surface detected the gap. fast = /gdd:fast no-skill-match path; router = gdd-router unmatched-intent path; reflector_pattern = reflector pattern-detection pass (Plan 29-02)."
        },
        "context_hash": {
          "type": "string",
          "minLength": 1,
          "description": "Stable hash of the intent/context that surfaced the gap. Used by 29-03 aggregation to cluster recurring gaps with the same hash."
        },
        "intent_summary": {
          "type": "string",
          "minLength": 1,
          "maxLength": 256,
          "description": "Short human-readable description of the failed lookup. Capped at 256 chars per D-02 — keeps payload small + auditable."
        },
        "suggested_kind": {
          "type": "string",
          "enum": ["agent", "skill"],
          "description": "Drafter hint for 29-04 incubator-author. fast emits 'skill' (narrow primitive); router emits 'agent' (multi-step workflow); reflector_pattern may emit either."
        },
        "evidence_refs": {
          "type": "array",
          "items": { "$ref": "#/definitions/TrajectoryRef" },
          "description": "Pointers to trajectory-JSONL slices that evidence the gap (D-07). Hash-pinned to detect chain mutation. Empty array allowed for low-evidence emits (e.g. /gdd:fast which has no trajectory)."
        }
      },
      "description": "Phase 29 D-02 capability_gap payload — exactly 7 fields, additionalProperties: false. Validated when the envelope's type === 'capability_gap' via the allOf[0] conditional."
    },
    "KfmCandidatePayload": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "event_id",
        "source",
        "article_url",
        "article_title",
        "suggested_symptom",
        "suggested_pattern_hint",
        "raw_excerpt"
      ],
      "properties": {
        "event_id": {
          "type": "string",
          "minLength": 1,
          "description": "Stable identifier for this kfm-candidate event. Used by Plan 30.5-03 reflector to de-dupe re-emissions of the same authority-watcher article hit."
        },
        "source": {
          "type": "string",
          "const": "authority_watcher",
          "description": "Phase 30.5-03 D-06 — the kfm-candidate event class is emitted EXCLUSIVELY by the authority-watcher pipeline. No other producer is authorised."
        },
        "article_url": {
          "type": "string",
          "format": "uri",
          "description": "Permalink to the source article. Logged into the incubator draft body so the user can audit provenance."
        },
        "article_title": {
          "type": "string",
          "minLength": 1,
          "description": "Article title verbatim — feeds the symptom-derivation heuristic + the draft origin header."
        },
        "suggested_symptom": {
          "type": "string",
          "minLength": 1,
          "description": "Reflector-consumed symptom string. Derived from the article H1/H2 + title; capped to a single line."
        },
        "suggested_pattern_hint": {
          "type": "string",
          "description": "Best-effort regex fragment proposed by the watcher heuristic. Empty string when the watcher cannot infer a pattern; the user fills `pattern` via the apply-reflections edit action."
        },
        "raw_excerpt": {
          "type": "string",
          "maxLength": 500,
          "description": "Up-to-500-char excerpt of the article body. Truncated with an ellipsis if longer. Hard cap is enforced by the schema; the watcher truncates before emission."
        }
      },
      "description": "Phase 30.5-03 D-06 kfm-candidate payload — 7 fields, additionalProperties: false. Validated when the envelope's type === 'kfm-candidate' via the allOf[1] conditional."
    },
    "RouterPickPayload": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "event_id",
        "source",
        "picked_skill",
        "context_hash",
        "rank",
        "alternatives",
        "ts"
      ],
      "properties": {
        "event_id": {
          "type": "string",
          "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
          "description": "UUIDv4 identifying this router_pick event. Stable across emit + read cycles."
        },
        "source": {
          "type": "string",
          "const": "router",
          "description": "Phase 32-08 D-02 — the router_pick event class is emitted EXCLUSIVELY by the gdd-router skill at its resolved-pick point. No other producer is authorised."
        },
        "picked_skill": {
          "type": "string",
          "minLength": 1,
          "description": "The skill or agent the router auto-picked for this intent. Phase 33 baselines per-skill auto-pick rates from this field (pick-rate regression)."
        },
        "context_hash": {
          "type": "string",
          "minLength": 1,
          "description": "sha256 of the intent/context that drove the pick — NEVER the raw prompt (no PII, mirrors CapabilityGapPayload.context_hash discipline). Used by Phase 33 aggregation to cluster picks for the same context."
        },
        "rank": {
          "type": "integer",
          "minimum": 0,
          "description": "Rank of the picked_skill among the candidates considered (0 = top pick). Lets Phase 33 distinguish confident top picks from close calls."
        },
        "alternatives": {
          "type": "array",
          "items": { "type": "string" },
          "description": "Other candidate skill/agent names the router considered (names only — no scores, no prompt text). May be empty when the router had a single match. Surfaces which skills the router weighs but does not reach for."
        },
        "ts": {
          "type": "string",
          "format": "date-time",
          "description": "ISO-8601 timestamp of the pick emission."
        }
      },
      "description": "Phase 32-08 D-02 router_pick payload — 7 fields, additionalProperties: false, NO PII (context_hash only). Records which skill the router auto-picked per intent — the instrument that surfaces under-reached skills. Validated when the envelope's type === 'router_pick' via the allOf[2] conditional."
    },
    "RiskAssessmentPayload": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "event_id",
        "tool_name",
        "risk_score",
        "suggested_action",
        "reasons"
      ],
      "properties": {
        "event_id": {
          "type": "string",
          "minLength": 1,
          "description": "Stable identifier for this risk_assessment event. Used by Phase 56 calibration to de-dupe re-emissions and to pair an assessment with its post-apply outcome (accept / reject / undo)."
        },
        "tool_name": {
          "type": "string",
          "minLength": 1,
          "description": "The tool the risk gate scored — Write | Edit | MultiEdit | Bash | NotebookEdit (or any writer tool). Mirrors the PreToolUse tool_name the gdd-risk-gate hook received."
        },
        "risk_score": {
          "type": "number",
          "minimum": 0,
          "maximum": 1,
          "description": "The clamped 0..1 score from scripts/lib/risk/compute-risk.cjs computeRisk(). 0 = no risk, 1 = maximal. Drives suggested_action via the THRESHOLDS table."
        },
        "suggested_action": {
          "type": "string",
          "enum": ["allow", "review", "require_confirmation", "block"],
          "description": "The action the risk scorer recommends for this score. allow < review < require_confirmation < block (ascending threshold bands). The gdd-risk-gate hook silences 'allow', advises on 'review'/'require_confirmation', and blocks on 'block'."
        },
        "reasons": {
          "type": "array",
          "items": { "type": "string" },
          "description": "Ordered, human-readable contributing factors from computeRisk().reasons (e.g. 'base:Bash=0.55', 'file:STATE.md(x2+0)', 'input:destructive-bash=+0.3'). May be empty for a zero-risk assessment."
        },
        "agent": {
          "type": "string",
          "minLength": 1,
          "description": "Optional — the writer agent whose action was scored (e.g. 'design-fixer'). Used by Phase 56 calibration to maintain per-agent rolling statistics. Absent when the gate fired outside an agent context."
        },
        "decision_context": {
          "type": "string",
          "description": "Optional — a short, PII-free context tag the agent layer attaches when routing the assessment (e.g. a finding id or routing verdict). Free-form; never the raw prompt."
        }
      },
      "description": "Phase 56 (R8) risk_assessment payload — 5 required fields + 2 optional (agent, decision_context), additionalProperties: false. Emitted by the gdd-risk-gate PreToolUse hook for every scored writer action; read by sdk/dashboard/data/risk-surface.cjs ({risk_score, suggested_action}) and gdd-events --type risk_assessment. Validated when the envelope's type === 'risk_assessment' via the allOf[3] conditional."
    }
  },
  "allOf": [
    {
      "if": {
        "properties": { "type": { "const": "capability_gap" } },
        "required": ["type"]
      },
      "then": {
        "properties": {
          "payload": { "$ref": "#/definitions/CapabilityGapPayload" }
        }
      }
    },
    {
      "if": {
        "properties": { "type": { "const": "kfm-candidate" } },
        "required": ["type"]
      },
      "then": {
        "properties": {
          "payload": { "$ref": "#/definitions/KfmCandidatePayload" }
        }
      }
    },
    {
      "if": {
        "properties": { "type": { "const": "router_pick" } },
        "required": ["type"]
      },
      "then": {
        "properties": {
          "payload": { "$ref": "#/definitions/RouterPickPayload" }
        }
      }
    },
    {
      "if": {
        "properties": { "type": { "const": "risk_assessment" } },
        "required": ["type"]
      },
      "then": {
        "properties": {
          "payload": { "$ref": "#/definitions/RiskAssessmentPayload" }
        }
      }
    }
  ]
}
