{
  "components": {
    "schemas": {
      "AdapterConfig": {
        "additionalProperties": false,
        "description": "Configuration for a named adapter instance.\n\nThe `type` field determines which adapter crate handles this config. Adapter-specific fields are captured via `serde(flatten)`.\n\nCredential fields (`token`, `client_secret`, `api_key`, `api_secret`, `password`, `oauth_token`) are wrapped in [`RedactedString`] so that `Debug` output never leaks secrets.",
        "properties": {
          "account": {
            "description": "Snowflake account identifier (e.g., \"xy12345.us-east-1\").",
            "type": [
              "string",
              "null"
            ]
          },
          "api_key": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/RedactedString"
              },
              {
                "type": "null"
              }
            ]
          },
          "api_secret": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/RedactedString"
              },
              {
                "type": "null"
              }
            ]
          },
          "cache": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/FivetranCacheConfig"
              },
              {
                "type": "null"
              }
            ],
            "description": "Optional cache backend for the Fivetran state envelope.\n\nWhen set on a `type = \"fivetran\"` adapter, the resolved envelope is read from and written to the configured cache backend so concurrent `rocky` processes share one fetcher per org. Ignored on every other adapter type. When absent the adapter behaves as if `backend = \"none\"` — every fetch goes straight to the Fivetran API."
          },
          "circuit_breaker": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/FivetranCircuitBreakerConfig"
              },
              {
                "type": "null"
              }
            ],
            "description": "Optional per-account circuit breaker (Fivetran-only).\n\nTrips after `failure_threshold` consecutive remote failures and short-circuits subsequent HTTP attempts with a `CircuitOpen` error until a cooldown elapses. Coordinated across processes via the configured backend (Valkey). Ignored on non-fivetran adapters; absent block defaults to `AlwaysClosed` (no breaker)."
          },
          "client_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "client_secret": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/RedactedString"
              },
              {
                "type": "null"
              }
            ]
          },
          "database": {
            "description": "Default database for the session.",
            "type": [
              "string",
              "null"
            ]
          },
          "destination_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "extra": {
            "additionalProperties": true,
            "description": "Escape hatch for adapter-specific keys this struct doesn't model.\n\n`AdapterConfig` is `#[serde(deny_unknown_fields)]` so typos at the top level (`tooken` for `token`) surface as parse errors rather than silent ignores. That guard, however, also blocks legitimate adapter-specific settings — an out-of-tree Trino adapter wanting a `default_schema` slot, a Postgres adapter wanting a non-standard `application_name`, etc. — from ever reaching the adapter.\n\nAuthors of such adapters declare the keys under a nested `[extra]` table:\n\n```toml [adapter.my_trino] type = \"trino\" host = \"https://trino.example.com\" token = \"${TRINO_JWT}\"\n\n[adapter.my_trino.extra] default_schema = \"analytics\" x_trino_user = \"service-account\" ```\n\nTop-level typos still error (`tooken = \"...\"` is still rejected); only keys nested under `[adapter.<name>.extra]` flow through to the adapter unchanged. Adapters read these via `.extra.get(\"...\")` and validate them themselves — Rocky doesn't schema-check the contents.\n\nValues are `serde_json::Value` so the field survives `just codegen` (`toml::Value` doesn't derive `JsonSchema`); TOML scalars / tables / arrays still round-trip through serde because they all map to the JSON shape.",
            "type": "object"
          },
          "host": {
            "type": [
              "string",
              "null"
            ]
          },
          "http_path": {
            "type": [
              "string",
              "null"
            ]
          },
          "kind": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/AdapterKind"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Role this adapter block plays. See [`AdapterKind`] for the required-vs-optional rules per adapter type."
          },
          "location": {
            "description": "BigQuery processing location (e.g., \"US\", \"EU\", \"us-central1\").",
            "type": [
              "string",
              "null"
            ]
          },
          "oauth_token": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/RedactedString"
              },
              {
                "type": "null"
              }
            ],
            "description": "OAuth access token (pre-obtained from an IdP)."
          },
          "password": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/RedactedString"
              },
              {
                "type": "null"
              }
            ],
            "description": "Snowflake password (for password auth)."
          },
          "pat": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/RedactedString"
              },
              {
                "type": "null"
              }
            ],
            "description": "Programmatic Access Token (issued via Snowsight User Profile). Sent as a Bearer token with the `PROGRAMMATIC_ACCESS_TOKEN` token-type header — distinct from `oauth_token`."
          },
          "path": {
            "description": "Optional file path for a persistent DuckDB database. When unset, the adapter uses an in-memory database. A persistent path is required when the same DuckDB adapter is also used as a discovery source — discovery and warehouse share the same database.",
            "type": [
              "string",
              "null"
            ]
          },
          "private_key_path": {
            "description": "Path to RSA private key file (PEM) for key-pair auth.",
            "type": [
              "string",
              "null"
            ]
          },
          "project_id": {
            "description": "Google Cloud project ID.",
            "type": [
              "string",
              "null"
            ]
          },
          "ratelimit": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/FivetranRatelimitConfig"
              },
              {
                "type": "null"
              }
            ],
            "description": "Optional cross-pod rate-limit budget backend (Fivetran-only).\n\nPhase 1 ratelimit coordination is per-host (file in `${TMPDIR}/rocky-fivetran-ratelimit/`). Setting `backend = \"valkey\"` here lifts the budget into a shared store so several pods on different hosts observe the same `wake_at` window after one of them is throttled. Ignored on non-fivetran adapters. When absent the adapter falls back to the per-host file backend."
          },
          "retry": {
            "allOf": [
              {
                "$ref": "#/components/schemas/RetryConfig"
              }
            ],
            "default": {
              "backoff_multiplier": 2.0,
              "circuit_breaker_recovery_timeout_secs": null,
              "circuit_breaker_threshold": 5,
              "initial_backoff_ms": 1000,
              "jitter": true,
              "max_backoff_ms": 30000,
              "max_retries": 3,
              "max_retries_per_run": null
            },
            "description": "Retry policy for this adapter."
          },
          "role": {
            "description": "Snowflake role to use for the session.",
            "type": [
              "string",
              "null"
            ]
          },
          "stampede": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/FivetranStampedeConfig"
              },
              {
                "type": "null"
              }
            ],
            "description": "Optional distributed cache-stampede lock (Fivetran-only).\n\nOn a cold-start herd, N processes simultaneously miss the cache, fan out N API calls, and write back N times. The stampede lock elects a single leader to issue the API call; followers poll the cache until the leader publishes the envelope. Ignored on non-fivetran adapters. When absent the adapter behaves as if every process is the leader (the pre-stampede behavior)."
          },
          "timeout_secs": {
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "token": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/RedactedString"
              },
              {
                "type": "null"
              }
            ]
          },
          "type": {
            "description": "Adapter type: \"databricks\", \"fivetran\", \"duckdb\", etc.",
            "type": "string"
          },
          "username": {
            "description": "Snowflake username (for password or key-pair auth).",
            "type": [
              "string",
              "null"
            ]
          },
          "warehouse": {
            "description": "Snowflake warehouse to use for query execution.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "type"
        ],
        "type": "object"
      },
      "AdapterKind": {
        "description": "Role an adapter block plays in the pipeline.\n\nSet via `kind = \"data\"` or `kind = \"discovery\"` in an `[adapter.*]` block. Required on discovery-only adapter types (`fivetran`, `airbyte`, `iceberg`, `manual`) so the adapter's role is self-evident in the raw config file — a reader shouldn't have to know the Rust trait surface of each adapter to tell whether it moves data.\n\nOptional on single-role warehouse types (defaults to `Data`) and on the dual-capable DuckDB adapter (absent means \"register both roles\").",
        "oneOf": [
          {
            "description": "Warehouse data movement (reads / writes table bytes).",
            "enum": [
              "data"
            ],
            "type": "string"
          },
          {
            "description": "Metadata-only discovery (enumerates source schemas / connectors).",
            "enum": [
              "discovery"
            ],
            "type": "string"
          }
        ]
      },
      "AdaptersFieldSchema": {
        "anyOf": [
          {
            "allOf": [
              {
                "$ref": "#/components/schemas/AdapterConfig"
              }
            ],
            "description": "Flat `[adapter]` block with `type` directly under it (single adapter)."
          },
          {
            "additionalProperties": {
              "$ref": "#/components/schemas/AdapterConfig"
            },
            "description": "Named `[adapter.<name>]` blocks (one or more adapters keyed by name).",
            "type": "object"
          }
        ],
        "description": "Schema-only helper that mirrors the deserializer's acceptance of both flat (`[adapter] type = \"...\"`) and named (`[adapter.foo] type = \"...\"`) adapter shapes.\n\n[`normalize_toml_shorthands`] rewrites the flat form into `adapter.default` before [`RockyConfig`] is deserialized, so the Rust type only sees the named form. The IDE schema, however, validates the raw TOML — so it must accept both shapes directly. 38 of 46 committed POC `rocky.toml` files use the flat form; the schema would reject all of them without this anyOf."
      },
      "AggregateCheckToggle": {
        "anyOf": [
          {
            "description": "Legacy boolean toggle. Severity defaults to `error`.",
            "type": "boolean"
          },
          {
            "description": "Explicit struct form with per-check severity.",
            "properties": {
              "enabled": {
                "default": false,
                "type": "boolean"
              },
              "severity": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/TestSeverity"
                  }
                ],
                "default": "error"
              }
            },
            "type": "object"
          }
        ],
        "description": "Per-check-kind toggle that accepts either a plain boolean (legacy form) or a struct with `enabled` + `severity`.\n\n```toml # Legacy — still supported row_count = true\n\n# New — per-check severity [pipeline.x.checks.row_count] enabled  = true severity = \"warning\" ```"
      },
      "AggregateCmp": {
        "description": "Comparison operator for `Aggregate` assertions. Each comparison has a long form (`lt`, `lte`, `gt`, `gte`, `eq`, `ne`) and an equivalent symbolic alias (`<`, `<=`, `>`, `>=`, `==`, `!=`).",
        "enum": [
          "lt",
          "<",
          "lte",
          "<=",
          "gt",
          ">",
          "gte",
          ">=",
          "eq",
          "==",
          "ne",
          "!="
        ],
        "type": "string"
      },
      "AggregateOp": {
        "description": "Aggregate operator used by [`TestType::Aggregate`].",
        "oneOf": [
          {
            "description": "`SUM(column)` — column required.",
            "enum": [
              "sum"
            ],
            "type": "string"
          },
          {
            "description": "`COUNT(*)` — column ignored.",
            "enum": [
              "count"
            ],
            "type": "string"
          },
          {
            "description": "`AVG(column)` — column required.",
            "enum": [
              "avg"
            ],
            "type": "string"
          },
          {
            "description": "`MIN(column)` — column required.",
            "enum": [
              "min"
            ],
            "type": "string"
          },
          {
            "description": "`MAX(column)` — column required.",
            "enum": [
              "max"
            ],
            "type": "string"
          }
        ]
      },
      "AiContractColumnProfile": {
        "description": "Observed profile of one column, as reported by `rocky ai-contract`.",
        "properties": {
          "distinct": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "max": {
            "type": [
              "string",
              "null"
            ]
          },
          "min": {
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "type": "string"
          },
          "null_rate": {
            "format": "double",
            "type": "number"
          },
          "nulls": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "observed_values": {
            "description": "Observed low-cardinality domain. Empty above the cardinality cap. This is reported as evidence; it is not encoded into the contract file.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "rows": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "type": {
            "description": "Inferred Rocky type name.",
            "type": "string"
          }
        },
        "required": [
          "distinct",
          "name",
          "null_rate",
          "nulls",
          "observed_values",
          "rows",
          "type"
        ],
        "type": "object"
      },
      "AiContractOutput": {
        "description": "JSON output for `rocky ai-contract <model>`.\n\nReports the AI-drafted data contract for a model, grounded in the observed per-column profile of its target table. The drafted contract is compile-verified against the model before it's reported, so a successful response is a contract that `rocky compile` accepts.",
        "properties": {
          "attempts": {
            "description": "Number of LLM attempts taken to reach a compile-verified contract.",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "command": {
            "type": "string"
          },
          "contract_toml": {
            "description": "The drafted contract serialized as `.contract.toml`.",
            "type": "string"
          },
          "model": {
            "description": "The model the contract was drafted for.",
            "type": "string"
          },
          "profile": {
            "description": "The observed per-column profile that grounded the draft.",
            "items": {
              "$ref": "#/components/schemas/AiContractColumnProfile"
            },
            "type": "array"
          },
          "saved_path": {
            "description": "Path the contract was written to, when `--save` was passed.",
            "type": [
              "string",
              "null"
            ]
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "attempts",
          "command",
          "contract_toml",
          "model",
          "profile",
          "version"
        ],
        "type": "object"
      },
      "AiExplainOutput": {
        "description": "JSON output for `rocky ai explain`.",
        "properties": {
          "command": {
            "type": "string"
          },
          "explanations": {
            "items": {
              "$ref": "#/components/schemas/AiExplanation"
            },
            "type": "array"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "command",
          "explanations",
          "version"
        ],
        "type": "object"
      },
      "AiExplanation": {
        "properties": {
          "intent": {
            "type": "string"
          },
          "model": {
            "type": "string"
          },
          "saved": {
            "type": "boolean"
          }
        },
        "required": [
          "intent",
          "model",
          "saved"
        ],
        "type": "object"
      },
      "AiGenerateOutput": {
        "description": "JSON output for `rocky ai \"<intent>\"`.",
        "properties": {
          "attempts": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "body_path": {
            "description": "Path of the body file (`.rocky` or `.sql`) written to the models directory, when emission succeeded.",
            "type": [
              "string",
              "null"
            ]
          },
          "command": {
            "type": "string"
          },
          "format": {
            "type": "string"
          },
          "intent": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "sidecar_path": {
            "description": "Path of the `.toml` sidecar written next to the body, when emission succeeded. The sidecar carries the materialization strategy and target coordinates so Rocky's model loader picks the generated model up without manual editing.",
            "type": [
              "string",
              "null"
            ]
          },
          "source": {
            "type": "string"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "attempts",
          "command",
          "format",
          "intent",
          "name",
          "source",
          "version"
        ],
        "type": "object"
      },
      "AiSection": {
        "additionalProperties": false,
        "description": "Configuration for the AI intent layer (`rocky ai`, `rocky ai-explain`, `rocky ai-sync`, `rocky ai-test`).\n\n`max_tokens` doubles as: 1. The per-request `max_tokens` cap on the Anthropic Messages API. 2. The cumulative output-token budget across the compile-verify retry loop — when the running total exceeds this value, the loop fail-stops instead of issuing another retry. This bounds the worst-case spend when the LLM produces runaway responses that fail validation.\n\nThe default ([`DEFAULT_AI_MAX_TOKENS`]) preserves Rocky's pre-1.x hard-coded behaviour. Increase for projects that legitimately need longer generations (large model surfaces, verbose tests).\n\n```toml [ai] max_tokens = 8192 ```",
        "properties": {
          "max_tokens": {
            "default": 4096,
            "description": "Per-request `max_tokens` and cumulative output-token budget across retries. Default [`DEFAULT_AI_MAX_TOKENS`].",
            "format": "uint32",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "type": "object"
      },
      "AiSyncOutput": {
        "description": "JSON output for `rocky ai sync`.",
        "properties": {
          "command": {
            "type": "string"
          },
          "proposals": {
            "items": {
              "$ref": "#/components/schemas/AiSyncProposal"
            },
            "type": "array"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "command",
          "proposals",
          "version"
        ],
        "type": "object"
      },
      "AiSyncProposal": {
        "properties": {
          "diff": {
            "type": "string"
          },
          "intent": {
            "type": "string"
          },
          "model": {
            "type": "string"
          },
          "proposed_source": {
            "type": "string"
          }
        },
        "required": [
          "diff",
          "intent",
          "model",
          "proposed_source"
        ],
        "type": "object"
      },
      "AiTestAssertion": {
        "properties": {
          "description": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "sql": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "description",
          "name"
        ],
        "type": "object"
      },
      "AiTestModelResult": {
        "properties": {
          "model": {
            "type": "string"
          },
          "saved": {
            "type": "boolean"
          },
          "tests": {
            "items": {
              "$ref": "#/components/schemas/AiTestAssertion"
            },
            "type": "array"
          }
        },
        "required": [
          "model",
          "saved",
          "tests"
        ],
        "type": "object"
      },
      "AiTestOutput": {
        "description": "JSON output for `rocky ai test`.",
        "properties": {
          "command": {
            "type": "string"
          },
          "results": {
            "items": {
              "$ref": "#/components/schemas/AiTestModelResult"
            },
            "type": "array"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "command",
          "results",
          "version"
        ],
        "type": "object"
      },
      "AllowedTypeChange": {
        "description": "A permitted type widening (e.g., INT to BIGINT) that won't trigger a violation.",
        "properties": {
          "from": {
            "type": "string"
          },
          "to": {
            "type": "string"
          }
        },
        "required": [
          "from",
          "to"
        ],
        "type": "object"
      },
      "AnomalyOutput": {
        "description": "Row count anomaly detected by historical baseline comparison.",
        "properties": {
          "baseline_avg": {
            "format": "double",
            "type": "number"
          },
          "current_count": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "deviation_pct": {
            "format": "double",
            "type": "number"
          },
          "reason": {
            "type": "string"
          },
          "table": {
            "type": "string"
          }
        },
        "required": [
          "baseline_avg",
          "current_count",
          "deviation_pct",
          "reason",
          "table"
        ],
        "type": "object"
      },
      "ApplyOutput": {
        "description": "JSON output for `rocky apply <plan-id>`.\n\nWraps the inner apply output (compact / archive / run) with a top-level `plan_id` envelope so consumers can correlate the apply result back to the plan that generated it without examining the inner payload's command field.\n\n## Shape decision: envelope (not discriminated enum)\n\nA discriminated enum with `CompactApplyOutput | ArchiveApplyOutput | RunOutput` would produce noisier `JsonSchema` derivations (nested `oneOf` with overlapping field names). An envelope with `inner: serde_json::Value` is simpler and lets consumers fall back to the per-command schema they already know for the `command` field (`\"compact apply\"` / `\"archive apply\"` / `\"run\"`).",
        "properties": {
          "command": {
            "description": "Always `\"apply\"` — the top-level command selector in `parse_rocky_output`.",
            "type": "string"
          },
          "plan_id": {
            "description": "The plan that was applied (full 64-char blake3 hex).",
            "type": "string"
          },
          "plan_kind": {
            "description": "`PlanKind` wire name: `\"compact\"`, `\"archive\"`, or `\"run\"`.",
            "type": "string"
          },
          "result": {
            "description": "The full inner apply result, embedded verbatim. Shape depends on `plan_kind`: - `\"compact\"` → `CompactApplyOutput` - `\"archive\"` → `ArchiveApplyOutput` - `\"run\"`     → `RunOutput`"
          },
          "success": {
            "description": "Whether all statements / materializations succeeded.",
            "type": "boolean"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "command",
          "plan_id",
          "plan_kind",
          "result",
          "success",
          "version"
        ],
        "type": "object"
      },
      "ApprovalArtifact": {
        "description": "On-disk approval record for a single approver against a single branch.\n\nOne file per approver under `./.rocky/approvals/<branch>/<approval_id>.json`. `branch_state_hash` binds the approval to the branch's content-addressed state at sign time; if the branch's hash changes (config bytes change), every existing artifact for that branch becomes stale and `branch promote` will refuse to honour it.",
        "properties": {
          "approval_id": {
            "description": "Sortable monotonic identifier — timestamp prefix + random tail.",
            "type": "string"
          },
          "approver": {
            "$ref": "#/components/schemas/ApproverIdentity"
          },
          "branch": {
            "type": "string"
          },
          "branch_state_hash": {
            "type": "string"
          },
          "message": {
            "type": [
              "string",
              "null"
            ]
          },
          "signature": {
            "$ref": "#/components/schemas/ApprovalSignature"
          },
          "signed_at": {
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "approval_id",
          "approver",
          "branch",
          "branch_state_hash",
          "signature",
          "signed_at"
        ],
        "type": "object"
      },
      "ApprovalSignature": {
        "description": "Signature attached to an [`ApprovalArtifact`].",
        "properties": {
          "algorithm": {
            "$ref": "#/components/schemas/SignatureAlgorithm"
          },
          "digest": {
            "description": "Hex-encoded digest. For `Blake3CanonicalJson`, this is the 32-byte blake3 hash printed as 64 lowercase hex characters.",
            "type": "string"
          }
        },
        "required": [
          "algorithm",
          "digest"
        ],
        "type": "object"
      },
      "ApproveOutput": {
        "description": "JSON output for `rocky branch approve`.",
        "properties": {
          "artifact": {
            "$ref": "#/components/schemas/ApprovalArtifact"
          },
          "artifact_path": {
            "description": "Where the artifact landed on disk.",
            "type": "string"
          },
          "command": {
            "type": "string"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "artifact",
          "artifact_path",
          "command",
          "version"
        ],
        "type": "object"
      },
      "ApproverIdentity": {
        "description": "Identity of an approver, captured at sign time.\n\nEmail is sourced from `git config user.email`; name from `git config user.name`. Hostname is best-effort from the `hostname` crate and surfaced as an audit aid only — it is not part of the trust boundary. Set `ROCKY_SCRUB_HOST` in the environment to replace the hostname with `\"redacted\"` when recording public demos.",
        "properties": {
          "email": {
            "type": "string"
          },
          "host": {
            "type": "string"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "source": {
            "$ref": "#/components/schemas/ApproverSource"
          }
        },
        "required": [
          "email",
          "host",
          "source"
        ],
        "type": "object"
      },
      "ApproverSource": {
        "description": "Where the approval signature was produced.\n\nReserved for future CI / OIDC paths. Today only the `Local` variant is emitted by the CLI.",
        "enum": [
          "local",
          "ci_oidc",
          "pat"
        ],
        "type": "string"
      },
      "ArchiveApplyOutput": {
        "description": "JSON output for `rocky archive apply <plan_id>`.",
        "properties": {
          "command": {
            "type": "string"
          },
          "executed_at": {
            "format": "date-time",
            "type": "string"
          },
          "plan_id": {
            "description": "The plan that was applied.",
            "type": "string"
          },
          "statements": {
            "description": "Per-statement results, in the order they were executed.",
            "items": {
              "$ref": "#/components/schemas/StatementResult"
            },
            "type": "array"
          },
          "success": {
            "description": "`true` when all statements succeeded; `false` on the first failure.",
            "type": "boolean"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "command",
          "executed_at",
          "plan_id",
          "statements",
          "success",
          "version"
        ],
        "type": "object"
      },
      "ArchiveOutput": {
        "description": "JSON output for `rocky archive`.\n\nMirrors [`CompactOutput`]: single-model invocations populate `model` and leave the catalog-scope fields absent; `--catalog` invocations populate `catalog`, `scope = \"catalog\"`, `tables`, and `totals`. The flat `statements` list carries every statement across every table.",
        "properties": {
          "catalog": {
            "description": "Set when invoked as `rocky archive --catalog <name>`.",
            "type": [
              "string",
              "null"
            ]
          },
          "command": {
            "type": "string"
          },
          "dry_run": {
            "type": "boolean"
          },
          "model": {
            "type": [
              "string",
              "null"
            ]
          },
          "older_than": {
            "type": "string"
          },
          "older_than_days": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "plan_id": {
            "description": "Plan identifier (full 64-char blake3 hex). Populated when the plan is persisted to `.rocky/plans/` so it can be applied later via `rocky archive apply <plan_id>`. Absent when plan persistence is skipped.",
            "type": [
              "string",
              "null"
            ]
          },
          "scope": {
            "description": "`\"catalog\"` for the catalog-scoped path; absent for single-model invocations.",
            "type": [
              "string",
              "null"
            ]
          },
          "statements": {
            "items": {
              "$ref": "#/components/schemas/NamedStatement"
            },
            "type": "array"
          },
          "tables": {
            "additionalProperties": {
              "$ref": "#/components/schemas/ArchiveTableEntry"
            },
            "type": [
              "object",
              "null"
            ]
          },
          "totals": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/ArchiveTotals"
              },
              {
                "type": "null"
              }
            ]
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "command",
          "dry_run",
          "older_than",
          "older_than_days",
          "statements",
          "version"
        ],
        "type": "object"
      },
      "ArchiveTableEntry": {
        "description": "Per-table archive plan inside a `--catalog` envelope.",
        "properties": {
          "statements": {
            "items": {
              "$ref": "#/components/schemas/NamedStatement"
            },
            "type": "array"
          }
        },
        "required": [
          "statements"
        ],
        "type": "object"
      },
      "ArchiveTotals": {
        "description": "Aggregate counts over a `rocky archive --catalog` invocation.",
        "properties": {
          "statement_count": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "table_count": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "statement_count",
          "table_count"
        ],
        "type": "object"
      },
      "AssetKind": {
        "description": "Asset kind discriminator for [`CatalogAsset`].",
        "enum": [
          "Source",
          "Model",
          "View",
          "MaterializedView"
        ],
        "type": "string"
      },
      "AttemptRecord": {
        "description": "One attempt at materializing a model, recorded when the run loop's classified-retry layer is active.\n\nA model that succeeds on the first try records a single attempt; a model that failed transiently and was re-run records one entry per attempt, the last of which is the outcome that stuck. The trail is **execution metadata** — it lives on [`ModelExecution`] alongside the identity hashes, never inside them, so a retried-then-succeeded execution stays byte-indistinguishable downstream from a first-try success (same `recipe_hash` / `input_hash` / output hash).\n\nReused verbatim on `MaterializationOutput.attempts` (the run-JSON surface), so it derives `JsonSchema`; that is the one attempt type on both the wire and the state record.",
        "properties": {
          "attempt": {
            "description": "1-based attempt index. Attempt 1 is the first try; 2+ are retries.",
            "format": "uint32",
            "minimum": 0.0,
            "type": "integer"
          },
          "backoff_ms": {
            "description": "Milliseconds slept as backoff *after* this attempt before the next retry. `None` on the final (kept) attempt and on any attempt that was not followed by a retry.",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "duration_ms": {
            "description": "Wall-clock duration of this attempt, in milliseconds.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "error": {
            "description": "The failure message (truncated), for a failed attempt; `None` on success.",
            "type": [
              "string",
              "null"
            ]
          },
          "failure_class": {
            "description": "The run-loop [`FailureClass`](crate::failure_class::FailureClass) label (`\"transient\"` / `\"permanent\"` / `\"unknown\"`) for a failed attempt; `None` on a successful attempt.",
            "type": [
              "string",
              "null"
            ]
          },
          "outcome": {
            "description": "`\"success\"` or `\"failed\"`.",
            "type": "string"
          },
          "transient_kind": {
            "description": "The transient sub-kind label (`\"network\"`, `\"timeout\"`, …) when the failure was transient; `None` otherwise.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "attempt",
          "duration_ms",
          "outcome"
        ],
        "type": "object"
      },
      "AuditChainBlastRadius": {
        "description": "Blast-radius link of the custody chain: the models that transitively consume the subject, recomputed from the current compiled graph.",
        "properties": {
          "availability": {
            "$ref": "#/components/schemas/SectionAvailability"
          },
          "direct": {
            "description": "Direct downstream consumers (one hop).",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "model": {
            "description": "The model the blast radius was computed for.",
            "type": [
              "string",
              "null"
            ]
          },
          "note": {
            "type": [
              "string",
              "null"
            ]
          },
          "total": {
            "description": "Size of the transitive closure.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "transitive": {
            "description": "All transitive downstream consumers (the full closure, sorted).",
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "availability",
          "direct",
          "total",
          "transitive"
        ],
        "type": "object"
      },
      "AuditChainDecisions": {
        "description": "Decision link of the custody chain: the policy-decision ledger scoped to the subject (by model for a model selector, by plan_id for a plan selector).",
        "properties": {
          "availability": {
            "$ref": "#/components/schemas/SectionAvailability"
          },
          "entries": {
            "description": "The matching decisions, newest first.",
            "items": {
              "$ref": "#/components/schemas/AuditDecisionEntry"
            },
            "type": "array"
          },
          "note": {
            "type": [
              "string",
              "null"
            ]
          },
          "total": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "availability",
          "entries",
          "total"
        ],
        "type": "object"
      },
      "AuditChainPlan": {
        "description": "Plan link of the custody chain: what the governing plan changed, read from the plan file's embedded change-classification.",
        "properties": {
          "availability": {
            "$ref": "#/components/schemas/SectionAvailability"
          },
          "changes": {
            "description": "The per-model change classification the plan carried — the persisted stand-in for the typed diff. The full field-level `diff_project_ir` output is not persisted for run plans, only this classification is.",
            "items": {
              "$ref": "#/components/schemas/AuditPlanChange"
            },
            "type": "array"
          },
          "diff_available": {
            "description": "Whether the base↔head change classification was available when the plan was authored. `false` means every planned model was treated as a breaking change (fail-closed), and `changes` reflects that.",
            "type": "boolean"
          },
          "kind": {
            "description": "The plan kind (`run` / `ai_authored` / …).",
            "type": [
              "string",
              "null"
            ]
          },
          "note": {
            "type": [
              "string",
              "null"
            ]
          },
          "plan_id": {
            "description": "The plan that governed the subject (the newest one, for a model selector). `null` when no plan file could be located.",
            "type": [
              "string",
              "null"
            ]
          },
          "principal": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/PolicyPrincipal"
              },
              {
                "type": "null"
              }
            ],
            "description": "The plan's authoring principal."
          }
        },
        "required": [
          "availability",
          "changes",
          "diff_available"
        ],
        "type": "object"
      },
      "AuditChainRuns": {
        "description": "Run link of the custody chain: runs that materialized the subject.",
        "properties": {
          "availability": {
            "$ref": "#/components/schemas/SectionAvailability"
          },
          "note": {
            "type": [
              "string",
              "null"
            ]
          },
          "runs": {
            "description": "Matching runs, newest first.",
            "items": {
              "$ref": "#/components/schemas/AuditRunEntry"
            },
            "type": "array"
          },
          "total": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "availability",
          "runs",
          "total"
        ],
        "type": "object"
      },
      "AuditChainVerify": {
        "description": "Verify-after link of the custody chain.\n\nPost-apply verification outcomes are persisted to the decision ledger as custody rows with a non-empty `verify_after` check list — the two-step `rocky apply` gate writes one per verified apply, and the drift auto-apply path writes them under its `autoapply-verify:<run_id>` plan id. This link renders those rows for the subject's plan. When none exist the link says so plainly (\"not recorded\") — never a smoothed-over \"verification passed\".",
        "properties": {
          "availability": {
            "$ref": "#/components/schemas/SectionAvailability"
          },
          "entries": {
            "description": "The verification outcomes, newest first.",
            "items": {
              "$ref": "#/components/schemas/AuditVerifyEntry"
            },
            "type": "array"
          },
          "note": {
            "type": [
              "string",
              "null"
            ]
          },
          "total": {
            "description": "Count of verification custody rows found for the subject.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "availability",
          "entries",
          "total"
        ],
        "type": "object"
      },
      "AuditDecisionEntry": {
        "description": "One recorded policy decision in the [`AuditOutput`] ledger.",
        "properties": {
          "capability": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PolicyCapability"
              }
            ],
            "description": "The capability that was evaluated."
          },
          "effect": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PolicyEffect"
              }
            ],
            "description": "The resolved verdict (`allow` / `require_review` / `deny`)."
          },
          "model": {
            "description": "The model the decision was about.",
            "type": "string"
          },
          "plan_id": {
            "description": "The plan the decision governed.",
            "type": "string"
          },
          "principal": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PolicyPrincipal"
              }
            ],
            "description": "Who was acting (`human` / `agent`)."
          },
          "reason": {
            "description": "Human-readable explanation of how the effect was reached.",
            "type": "string"
          },
          "rule_id": {
            "description": "Index of the winning `[[policy.rules]]` entry, or `null` for the default posture.",
            "format": "uint",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "timestamp": {
            "description": "RFC 3339 timestamp when the decision was recorded.",
            "type": "string"
          }
        },
        "required": [
          "capability",
          "effect",
          "model",
          "plan_id",
          "principal",
          "reason",
          "timestamp"
        ],
        "type": "object"
      },
      "AuditEvent": {
        "description": "Single audit-trail event emitted during `branch promote`.\n\nRouted to stdout JSON only in v1; persistent audit storage is a follow-up.",
        "properties": {
          "actor": {
            "$ref": "#/components/schemas/ApproverIdentity"
          },
          "at": {
            "format": "date-time",
            "type": "string"
          },
          "branch": {
            "type": "string"
          },
          "branch_state_hash": {
            "type": "string"
          },
          "breaking_changes": {
            "description": "Breaking-change findings carried by `BreakingChangesBlocked` and `BreakingChangesAllowed` events. Always absent for other kinds.",
            "items": {
              "$ref": "#/components/schemas/BreakingFinding"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "kind": {
            "$ref": "#/components/schemas/AuditEventKind"
          },
          "reason": {
            "description": "Free-form context. Populated for `ApprovalSkipped` to record the origin of the skip, and for `BreakingChangesGateSkipped` to record why the gate could not run (e.g. \"base ref did not compile\"). Empty for routine state events.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "actor",
          "at",
          "branch",
          "branch_state_hash",
          "kind"
        ],
        "type": "object"
      },
      "AuditEventKind": {
        "description": "Categorical kind of an [`AuditEvent`] emitted by `branch promote`.",
        "oneOf": [
          {
            "enum": [
              "promote_started",
              "promote_completed",
              "promote_failed"
            ],
            "type": "string"
          },
          {
            "description": "Emitted when the approval gate was bypassed via `--skip-approval` or the `ROCKY_BRANCH_APPROVAL_SKIP` env-var override. The skip reason is recorded so a future audit can tell flag-skip apart from env-skip.",
            "enum": [
              "approval_skipped"
            ],
            "type": "string"
          },
          {
            "description": "Emitted when the semantic breaking-change gate blocked the promote. The findings list is carried on the parent [`AuditEvent`] so reviewers see exactly which structural changes triggered the block.",
            "enum": [
              "breaking_changes_blocked"
            ],
            "type": "string"
          },
          {
            "description": "Emitted when the semantic breaking-change gate detected one or more `Breaking`-severity findings but the operator overrode the block via `--allow-breaking`. Records the findings so the override leaves an audit trail equivalent to `ApprovalSkipped`.",
            "enum": [
              "breaking_changes_allowed"
            ],
            "type": "string"
          },
          {
            "description": "Emitted when the semantic breaking-change gate could not run — for example because the base ref failed to compile under the current Rocky version. Fail-open: the gate is skipped and the promote proceeds, but the reason is recorded so the bypass is auditable.",
            "enum": [
              "breaking_changes_gate_skipped"
            ],
            "type": "string"
          },
          {
            "description": "Emitted by `rocky plan promote` when the plan has been successfully written to `.rocky/plans/<plan_id>.json`. Carries the plan_id so an audit consumer can correlate plan creation to a subsequent `rocky apply` event without scanning the filesystem.",
            "enum": [
              "promote_plan_created"
            ],
            "type": "string"
          }
        ]
      },
      "AuditForOutput": {
        "description": "JSON output for `rocky audit --for <table|run|plan>` — the custody chain.\n\nA one-query drill-down assembled link by link from the data Rocky already records: who proposed the change and what the policy plane decided ([`Self::decisions`]), what the plan changed ([`Self::plan`]), which runs materialized it ([`Self::runs`]), what a post-apply verification found ([`Self::verify_after`]), and what sits downstream in its blast radius ([`Self::blast_radius`]).\n\nEvery link fails closed the same way the estate brief does: a link whose signal is genuinely not recorded renders [`SectionAvailability::Unavailable`] with a note, never a fabricated or assumed value. Post-apply verification outcomes are persisted as decision-ledger custody rows (non-empty `verify_after`), so [`Self::verify_after`] renders them when they exist and says \"not recorded\" when none exist for the subject. The run ledger is not keyed to policy decisions, so a `run` selector cannot join back to a decision. The blast radius is recomputed from the current compiled graph (a live query, not a stored snapshot).",
        "properties": {
          "blast_radius": {
            "allOf": [
              {
                "$ref": "#/components/schemas/AuditChainBlastRadius"
              }
            ],
            "description": "What sits downstream of the subject — the CLL blast radius."
          },
          "command": {
            "type": "string"
          },
          "decisions": {
            "allOf": [
              {
                "$ref": "#/components/schemas/AuditChainDecisions"
              }
            ],
            "description": "Who proposed the change and what the policy plane decided — the persisted decision ledger, scoped to this subject."
          },
          "plan": {
            "allOf": [
              {
                "$ref": "#/components/schemas/AuditChainPlan"
              }
            ],
            "description": "What the governing plan changed (its embedded per-model change classification)."
          },
          "resolved": {
            "description": "Whether the selector matched anything at all (a decision, a run, a plan file, or a model in the graph). `false` when nothing referenced it — every link is then empty and says why.",
            "type": "boolean"
          },
          "runs": {
            "allOf": [
              {
                "$ref": "#/components/schemas/AuditChainRuns"
              }
            ],
            "description": "Runs that materialized the subject."
          },
          "subject": {
            "description": "The selector as supplied on the command line.",
            "type": "string"
          },
          "subject_kind": {
            "allOf": [
              {
                "$ref": "#/components/schemas/AuditSubjectKind"
              }
            ],
            "description": "What the selector resolved to."
          },
          "verify_after": {
            "allOf": [
              {
                "$ref": "#/components/schemas/AuditChainVerify"
              }
            ],
            "description": "What a post-apply verification found — the verification custody rows recorded against the subject's plan."
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "blast_radius",
          "command",
          "decisions",
          "plan",
          "resolved",
          "runs",
          "subject",
          "subject_kind",
          "verify_after",
          "version"
        ],
        "type": "object"
      },
      "AuditOutput": {
        "description": "JSON output for `rocky audit` — the agent-policy decision ledger.\n\nLists every policy decision recorded at a mutating enforcement seam (`rocky apply` / promote), oldest first. Reads are never recorded, so this is exclusively the trail of *governed mutations* the plane evaluated.",
        "properties": {
          "command": {
            "type": "string"
          },
          "decisions": {
            "description": "Every recorded policy decision, oldest first.",
            "items": {
              "$ref": "#/components/schemas/AuditDecisionEntry"
            },
            "type": "array"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "command",
          "decisions",
          "version"
        ],
        "type": "object"
      },
      "AuditPlanChange": {
        "description": "One model's change classification inside [`AuditChainPlan`].",
        "properties": {
          "capability": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PolicyCapability"
              }
            ],
            "description": "The change class the plan recorded for this model (`schema_change.additive` / `schema_change.breaking` / `value_change` / a bare verb)."
          },
          "model": {
            "type": "string"
          }
        },
        "required": [
          "capability",
          "model"
        ],
        "type": "object"
      },
      "AuditRunEntry": {
        "description": "One run inside [`AuditChainRuns`].",
        "properties": {
          "finished_at": {
            "type": "string"
          },
          "run_id": {
            "type": "string"
          },
          "started_at": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "triggering_identity": {
            "description": "Best-effort caller identity recorded on the run.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "finished_at",
          "run_id",
          "started_at",
          "status"
        ],
        "type": "object"
      },
      "AuditScorecardOutput": {
        "description": "JSON output for `rocky audit --scorecard` — a trust-calibration digest.\n\nA decisions-by-group aggregation over the persisted policy-decision ledger, windowed by `--window`. It is the evidence base for widening or tightening autonomy, and it informs human judgment only — nothing here is wired to any automatic policy change.\n\nOnly metrics the ledger actually persists are computed. Post-apply verification outcomes *are* persisted (custody rows with a non-empty `verify_after` check list), so [`Self::verify_after`] reports their pass rate when any fall in the window. Reverts and escalation-resolution latency are not persisted; they are declared in [`Self::unavailable_metrics`] as `unavailable` with the reason, never faked into a number — and `verify_after_pass_rate` joins that list only when no verification row falls in the window. A ledger read failure renders the whole scorecard `unavailable` rather than a smoothed-over zero.",
        "properties": {
          "availability": {
            "allOf": [
              {
                "$ref": "#/components/schemas/SectionAvailability"
              }
            ],
            "description": "Whether the scorecard could be composed at all: `unavailable` when the ledger could not be read (fail-closed), `no_data` when no decision falls in the window, `available` otherwise."
          },
          "by": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ScorecardDimension"
              }
            ],
            "description": "The grouping dimension (`--by`)."
          },
          "command": {
            "type": "string"
          },
          "groups": {
            "description": "One row per group, ranked by decision count descending.",
            "items": {
              "$ref": "#/components/schemas/ScorecardGroup"
            },
            "type": "array"
          },
          "note": {
            "type": [
              "string",
              "null"
            ]
          },
          "total_decisions": {
            "description": "Total decisions in the window, across all groups.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "unavailable_metrics": {
            "description": "Metrics the persisted ledger cannot support for this window, declared plainly rather than computed. Each is `unavailable` with the reason it cannot be derived.",
            "items": {
              "$ref": "#/components/schemas/ScorecardUnavailableMetric"
            },
            "type": "array"
          },
          "verify_after": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/ScorecardVerifyAfter"
              },
              {
                "type": "null"
              }
            ],
            "description": "Aggregate of the post-apply verification custody rows in the window (rows with a non-empty `verify_after` check list). `null` when no verification row falls in the window — then `verify_after_pass_rate` is declared in [`Self::unavailable_metrics`] instead."
          },
          "version": {
            "type": "string"
          },
          "window": {
            "description": "The window as requested (`all`, `30d`, `24h`, …).",
            "type": "string"
          },
          "window_start": {
            "description": "Lower bound of the window (RFC 3339), or `null` for all-time.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "availability",
          "by",
          "command",
          "groups",
          "total_decisions",
          "unavailable_metrics",
          "version",
          "window"
        ],
        "type": "object"
      },
      "AuditSubjectKind": {
        "description": "What `rocky audit --for <selector>` resolved its selector to.\n\nThe selector is resolved in priority order: a 64-char hex string with a plan file on disk is a [`AuditSubjectKind::Plan`]; a string matching a `run_id` in the run ledger is a [`AuditSubjectKind::Run`]; a string the decision ledger keys rows by is likewise a [`AuditSubjectKind::Plan`] (the plan file may be gone, or the id may be a decision-only custody key that never had one); anything else is treated as a [`AuditSubjectKind::Model`] name.",
        "oneOf": [
          {
            "description": "A model / table name — the primary custody-chain entry point.",
            "enum": [
              "model"
            ],
            "type": "string"
          },
          {
            "description": "A `run_id` from the run ledger.",
            "enum": [
              "run"
            ],
            "type": "string"
          },
          {
            "description": "A `plan_id` — a 64-char blake3 hex with a plan file on disk, or any id the decision ledger keys rows by (including decision-only custody ids like `freeze:…` / `draft:…` / `autoapply:…`, which never had a plan file).",
            "enum": [
              "plan"
            ],
            "type": "string"
          }
        ]
      },
      "AuditVerifyEntry": {
        "description": "One post-apply verification outcome inside [`AuditChainVerify`] — a decision-ledger custody row with a non-empty `verify_after` check list.\n\nThe ledger records the required check names, the aggregate verdict (`allow` = every named check passed, `deny` = a check failed or was absent and the apply halted), and a human-readable reason; per-check pass/fail detail beyond that lives only in the reason string, which is rendered verbatim rather than re-parsed.",
        "properties": {
          "checks": {
            "description": "The named post-apply checks the verification required.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "passed": {
            "description": "Whether the verification passed (`allow` custody row) or failed (`deny`).",
            "type": "boolean"
          },
          "plan_id": {
            "description": "The plan id the custody row was filed under (the applied plan, or the auto-apply path's `autoapply-verify:<run_id>`).",
            "type": "string"
          },
          "reason": {
            "description": "The recorded outcome, verbatim from the custody row.",
            "type": "string"
          },
          "timestamp": {
            "description": "RFC 3339 timestamp when the verification outcome was recorded.",
            "type": "string"
          }
        },
        "required": [
          "checks",
          "passed",
          "plan_id",
          "reason",
          "timestamp"
        ],
        "type": "object"
      },
      "AutonomyBudget": {
        "additionalProperties": false,
        "description": "An autonomy budget on a policy rule — the SRE error-budget move applied to agent authority.\n\nA rule that carries `autonomy_budget = { failures = 3, window = \"7d\" }` tolerates at most `failures - 1` post-apply verification failures inside a rolling `window`. The moment the count reaches `failures`, the budget is exhausted and the rule **automatically degrades to `require_review`** at enforcement time (an `allow` can no longer stand un-reviewed). This is a one-directional breaker: it only ever tightens the rule, never widens it. Widening autonomy remains a deliberate human act on scorecard evidence.\n\nThe failure count is a *projection over the existing decision ledger* — it is never a persisted counter, so recovery is automatic: once the failing applies age out of the window the count falls below the limit and the rule returns to its authored effect. The rule is never made more permissive than the effect the author wrote.",
        "properties": {
          "failures": {
            "description": "Verify-after failures within `window` that exhaust the budget. The `failures`-th failure trips the breaker (must be `>= 1`).",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "window": {
            "description": "Rolling window over which failures are counted, as a `<N>d` / `<N>h` duration (e.g. `\"7d\"`, `\"24h\"`). Failures older than this do not count.",
            "type": "string"
          }
        },
        "required": [
          "failures",
          "window"
        ],
        "type": "object"
      },
      "BackfillCostEstimate": {
        "description": "A best-effort, label-as-estimate cost projection for a backfill.\n\nThe projection re-uses the same historical-observed cost formula as `rocky cost`: each closure model's most recent recorded execution is priced by the warehouse cost model. It is offline (no warehouse round-trip) and therefore approximate — `is_estimate` is always `true`.",
        "properties": {
          "basis": {
            "description": "How the figure was derived: `\"historical_observed\"` when at least one closure model had a prior execution to price, else `\"unavailable\"`.",
            "type": "string"
          },
          "is_estimate": {
            "description": "Always `true` — this is a projection, not a measured cost.",
            "type": "boolean"
          },
          "per_model": {
            "description": "Per-model breakdown, in closure order.",
            "items": {
              "$ref": "#/components/schemas/BackfillModelCost"
            },
            "type": "array"
          },
          "total_cost_usd": {
            "description": "Summed estimated compute cost in USD, or `None` when no closure model has priced execution history (or the warehouse is unbilled, e.g. DuckDB).",
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          }
        },
        "required": [
          "basis",
          "is_estimate",
          "per_model"
        ],
        "type": "object"
      },
      "BackfillModelCost": {
        "description": "Per-model entry in a [`BackfillCostEstimate`].",
        "properties": {
          "cost_usd": {
            "description": "Estimated compute cost in USD for one rebuild, or `None` when the model has no priced execution history.",
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          },
          "duration_ms": {
            "description": "Observed duration (ms) of the priced historical execution, when found.",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "model_name": {
            "description": "The model name.",
            "type": "string"
          },
          "source_run_id": {
            "description": "The run the historical figure was read from, for provenance.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "model_name"
        ],
        "type": "object"
      },
      "BackfillOutput": {
        "description": "The composed, review-gated recovery plan emitted by `rocky backfill`.\n\nA backfill re-runs *existing* recipes over a scoped window — it never rewrites SQL to \"fix\" data. The command composes the set of models to rebuild (the affected models plus their downstream lineage closure), the order to rebuild them in, the partition window where models are partitioned, and an estimated cost. The plan is persisted and **always** requires a human sign-off (`rocky review <plan-id> --approve`) before `rocky apply` will execute it, regardless of any configured policy — backfills are where blast radius hides.",
        "properties": {
          "apply_command": {
            "description": "The exact command that executes the plan once reviewed.",
            "type": "string"
          },
          "command": {
            "description": "Always `\"backfill\"`.",
            "type": "string"
          },
          "cost_estimate": {
            "allOf": [
              {
                "$ref": "#/components/schemas/BackfillCostEstimate"
              }
            ],
            "description": "Best-effort cost projection for the rebuild. Always an *estimate*."
          },
          "execution_layers": {
            "description": "Topological execution layers over the closure. Models in the same layer are independent; each layer runs after the previous completes.",
            "items": {
              "items": {
                "type": "string"
              },
              "type": "array"
            },
            "type": "array"
          },
          "message": {
            "description": "Human-readable one-line summary.",
            "type": "string"
          },
          "models": {
            "description": "The full set of models to rebuild — the seeds plus their downstream lineage closure — in topological (dependency-first) order.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "partition_scope": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/BackfillPartitionScope"
              },
              {
                "type": "null"
              }
            ],
            "description": "Partition window applied to partitioned models in the closure, when a `--from`/`--to` range was supplied. Absent when the backfill is a full rebuild of each model."
          },
          "plan_id": {
            "description": "The persisted plan identifier (64-char blake3 hex). Feed it to `rocky review <plan-id> --approve` then `rocky apply <plan-id>`.",
            "type": "string"
          },
          "requires_review": {
            "description": "Always `true` — a backfill plan is unconditionally review-gated.",
            "type": "boolean"
          },
          "review_command": {
            "description": "The exact command that clears the review gate.",
            "type": "string"
          },
          "seed_models": {
            "description": "The models that triggered the backfill (the failure/gap seeds), before the downstream closure is added.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "trigger": {
            "description": "What surfaced the affected models: `\"manual\"` (explicit `--model`) or `\"last_run_failure\"` (seeded from the previous run's failed models, i.e. the contained/quarantined window).",
            "type": "string"
          },
          "version": {
            "description": "Rocky version that composed the plan.",
            "type": "string"
          },
          "warnings": {
            "description": "Composition warnings the reviewer must weigh before approving. Today: `incremental` / `microbatch` closure members, whose transformation re-run is a bare `INSERT INTO … <model SQL>` — depending on the model's own filtering it appends duplicate rows or loads nothing, rather than rebuilding a window. Empty (and omitted from JSON) when the closure carries no such member.",
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "apply_command",
          "command",
          "cost_estimate",
          "execution_layers",
          "message",
          "models",
          "plan_id",
          "requires_review",
          "review_command",
          "seed_models",
          "trigger",
          "version"
        ],
        "type": "object"
      },
      "BackfillPartitionScope": {
        "description": "The partition window a backfill scopes partitioned models to.",
        "properties": {
          "from": {
            "description": "Range lower bound (`--from`).",
            "type": [
              "string",
              "null"
            ]
          },
          "models": {
            "description": "The closure models that declare a partitioned (`time_interval`) materialization — the ones the window actually applies to. Empty when no model in the closure is partitioned (the window is then inert).",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "to": {
            "description": "Range upper bound (`--to`).",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "models"
        ],
        "type": "object"
      },
      "BindingType": {
        "description": "Workspace binding access level.",
        "enum": [
          "READ_WRITE",
          "READ_ONLY"
        ],
        "type": "string"
      },
      "BisectionStatsOutput": {
        "description": "CLI-side mirror of [`rocky_core::compare::bisection::BisectionStats`]. Kept separate so the JSON schema lives alongside the rest of the `rocky preview diff` output types — same pattern as [`BudgetBreachOutput`] mirroring `rocky_core::config::BudgetBreach`.",
        "properties": {
          "chunks_examined": {
            "description": "Total non-empty chunk-checksum entries returned across both sides and all recursion levels.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "depth_capped": {
            "description": "`true` if the runner hit the `max_depth` cap before any chunk fell below the leaf threshold. The dense range was still materialized exhaustively, so the diff is correct — just slower.",
            "type": "boolean"
          },
          "depth_max": {
            "description": "Maximum recursion depth reached.",
            "format": "uint32",
            "minimum": 0.0,
            "type": "integer"
          },
          "leaves_materialized": {
            "description": "Number of leaf chunks materialized for row-by-row diff.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "null_pk_rows_base": {
            "description": "Rows on the base side whose primary-key column was NULL. Excluded from chunk membership; counted at the root so a null-PK divergence surfaces instead of silently dropping rows.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "null_pk_rows_branch": {
            "description": "Rows on the branch side whose primary-key column was NULL.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "split_strategy": {
            "description": "How the runner split the primary-key space into chunks. One of `\"int_range\"`, `\"composite\"`, `\"hash_bucket\"`, `\"first_column\"`.",
            "type": "string"
          }
        },
        "required": [
          "chunks_examined",
          "depth_capped",
          "depth_max",
          "leaves_materialized",
          "null_pk_rows_base",
          "null_pk_rows_branch",
          "split_strategy"
        ],
        "type": "object"
      },
      "BranchApprovalConfig": {
        "additionalProperties": false,
        "description": "`[branch.approval]` configuration block.\n\nDefaults are deliberately permissive — a project that doesn't add the section keeps the v0 `branch promote` behaviour. Flipping `required = true` opts in to the gate; the rest of the knobs tune the strictness once the gate is on.",
        "properties": {
          "allowed_signers": {
            "default": [],
            "description": "When non-empty, only approvals from these signer emails count toward `min_approvers`. Empty (default) accepts any signer.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "max_age_seconds": {
            "default": 86400,
            "description": "Approvals older than this many seconds are rejected even if their branch_state_hash still matches. Default 86400 (24h).",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "min_approvers": {
            "default": 1,
            "description": "Minimum number of valid approvals required when `required = true`. \"Valid\" means: signature verifies, branch_state_hash matches the current state, signed within `max_age_seconds`, and (when `allowed_signers` is non-empty) the signer's email is in the list.",
            "format": "uint32",
            "minimum": 0.0,
            "type": "integer"
          },
          "required": {
            "default": false,
            "description": "When true, `branch promote` refuses to run unless at least `min_approvers` valid approval artifacts are on disk for the branch. When false (default), the gate is bypassed silently.",
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "BranchDeleteOutput": {
        "description": "JSON output for `rocky branch delete`.",
        "properties": {
          "command": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "removed": {
            "description": "Whether a record was actually removed (false if the branch didn't exist).",
            "type": "boolean"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "command",
          "name",
          "removed",
          "version"
        ],
        "type": "object"
      },
      "BranchEntry": {
        "description": "A single branch record in JSON output.",
        "properties": {
          "created_at": {
            "type": "string"
          },
          "created_by": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "type": "string"
          },
          "schema_prefix": {
            "type": "string"
          }
        },
        "required": [
          "created_at",
          "created_by",
          "name",
          "schema_prefix"
        ],
        "type": "object"
      },
      "BranchListOutput": {
        "description": "JSON output for `rocky branch list`.",
        "properties": {
          "branches": {
            "items": {
              "$ref": "#/components/schemas/BranchEntry"
            },
            "type": "array"
          },
          "command": {
            "type": "string"
          },
          "total": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "branches",
          "command",
          "total",
          "version"
        ],
        "type": "object"
      },
      "BranchOutput": {
        "description": "JSON output for `rocky branch create` and `rocky branch show`.",
        "properties": {
          "branch": {
            "$ref": "#/components/schemas/BranchEntry"
          },
          "command": {
            "type": "string"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "branch",
          "command",
          "version"
        ],
        "type": "object"
      },
      "BranchPromoteOutput": {
        "description": "JSON output for `rocky branch promote`.",
        "properties": {
          "approvals_rejected": {
            "description": "Approval artifacts loaded from disk that failed verification, with the reason for rejection. Surfaced even on a successful promote so operators can spot stale artifacts to clean up.",
            "items": {
              "$ref": "#/components/schemas/RejectedApproval"
            },
            "type": "array"
          },
          "approvals_used": {
            "description": "Approval artifacts that satisfied the gate at promote time. Empty when the gate was disabled (`required = false`) or skipped.",
            "items": {
              "$ref": "#/components/schemas/ApprovalArtifact"
            },
            "type": "array"
          },
          "audit": {
            "description": "Audit-trail events emitted during this invocation, in order. At minimum: `PromoteStarted` plus one of `PromoteCompleted` / `PromoteFailed`. `ApprovalSkipped` precedes `PromoteStarted` when the gate was bypassed.",
            "items": {
              "$ref": "#/components/schemas/AuditEvent"
            },
            "type": "array"
          },
          "branch": {
            "type": "string"
          },
          "branch_state_hash": {
            "type": "string"
          },
          "breaking_changes": {
            "description": "Semantic breaking-change findings produced by the pre-promote gate. Empty when the gate ran and found no breaking changes; absent when the gate was skipped (compile failure on either side). When present and non-empty either the promote was blocked or `--allow-breaking` was set — see the audit trail for which.",
            "items": {
              "$ref": "#/components/schemas/BreakingFinding"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "command": {
            "type": "string"
          },
          "success": {
            "description": "True when every target's SQL succeeded.",
            "type": "boolean"
          },
          "targets": {
            "description": "One entry per managed target the promote attempted, in dispatch order.",
            "items": {
              "$ref": "#/components/schemas/PromoteTarget"
            },
            "type": "array"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "approvals_rejected",
          "approvals_used",
          "audit",
          "branch",
          "branch_state_hash",
          "command",
          "success",
          "targets",
          "version"
        ],
        "type": "object"
      },
      "BranchSection": {
        "additionalProperties": false,
        "description": "Top-level `[branch]` configuration section.\n\nCurrently scopes the optional approval gate consumed by `rocky branch promote`. Default-constructed (no `[branch]` block in `rocky.toml`) leaves the gate disabled — `branch promote` skips the approval loop and behaves like the unguarded baseline.",
        "properties": {
          "approval": {
            "allOf": [
              {
                "$ref": "#/components/schemas/BranchApprovalConfig"
              }
            ],
            "default": {
              "allowed_signers": [],
              "max_age_seconds": 86400,
              "min_approvers": 1,
              "required": false
            }
          }
        },
        "type": "object"
      },
      "BreakingChange": {
        "description": "A single typed semantic change between two `ProjectIr` snapshots.\n\nEach variant carries the minimum identifying context (model + column + before/after values) needed for a CLI / PR-preview surface to render a useful message without re-loading either IR.",
        "oneOf": [
          {
            "description": "A model present on the base side is absent on the head side.",
            "properties": {
              "kind": {
                "enum": [
                  "model_removed"
                ],
                "type": "string"
              },
              "model": {
                "type": "string"
              }
            },
            "required": [
              "kind",
              "model"
            ],
            "type": "object"
          },
          {
            "description": "A model present on the head side is absent on the base side.",
            "properties": {
              "kind": {
                "enum": [
                  "model_added"
                ],
                "type": "string"
              },
              "model": {
                "type": "string"
              }
            },
            "required": [
              "kind",
              "model"
            ],
            "type": "object"
          },
          {
            "description": "A column present on the base side is absent on the head side.",
            "properties": {
              "column": {
                "type": "string"
              },
              "data_type": {
                "type": "string"
              },
              "kind": {
                "enum": [
                  "column_dropped"
                ],
                "type": "string"
              },
              "model": {
                "type": "string"
              }
            },
            "required": [
              "column",
              "data_type",
              "kind",
              "model"
            ],
            "type": "object"
          },
          {
            "description": "A column was added.",
            "properties": {
              "column": {
                "type": "string"
              },
              "data_type": {
                "type": "string"
              },
              "kind": {
                "enum": [
                  "column_added"
                ],
                "type": "string"
              },
              "model": {
                "type": "string"
              },
              "nullable": {
                "type": "boolean"
              }
            },
            "required": [
              "column",
              "data_type",
              "kind",
              "model",
              "nullable"
            ],
            "type": "object"
          },
          {
            "description": "A column's data type changed.",
            "properties": {
              "column": {
                "type": "string"
              },
              "kind": {
                "enum": [
                  "column_type_changed"
                ],
                "type": "string"
              },
              "model": {
                "type": "string"
              },
              "narrowing": {
                "description": "`true` when the new type cannot represent every value of the old type (Int64 → Int32, Decimal precision shrink, Timestamp → Date).",
                "type": "boolean"
              },
              "new_type": {
                "type": "string"
              },
              "old_type": {
                "type": "string"
              }
            },
            "required": [
              "column",
              "kind",
              "model",
              "narrowing",
              "new_type",
              "old_type"
            ],
            "type": "object"
          },
          {
            "description": "A column's nullability flipped.",
            "properties": {
              "column": {
                "type": "string"
              },
              "kind": {
                "enum": [
                  "column_nullability_changed"
                ],
                "type": "string"
              },
              "model": {
                "type": "string"
              },
              "new_nullable": {
                "type": "boolean"
              },
              "old_nullable": {
                "type": "boolean"
              }
            },
            "required": [
              "column",
              "kind",
              "model",
              "new_nullable",
              "old_nullable"
            ],
            "type": "object"
          },
          {
            "description": "A column's position in the output schema changed. `SELECT *` downstream consumers see different positional projection.",
            "properties": {
              "column": {
                "type": "string"
              },
              "kind": {
                "enum": [
                  "column_reordered"
                ],
                "type": "string"
              },
              "model": {
                "type": "string"
              },
              "new_index": {
                "format": "uint",
                "minimum": 0.0,
                "type": "integer"
              },
              "old_index": {
                "format": "uint",
                "minimum": 0.0,
                "type": "integer"
              }
            },
            "required": [
              "column",
              "kind",
              "model",
              "new_index",
              "old_index"
            ],
            "type": "object"
          },
          {
            "description": "The materialization strategy switched between top-level variants (e.g. `FullRefresh` → `Incremental`).",
            "properties": {
              "kind": {
                "enum": [
                  "materialization_strategy_changed"
                ],
                "type": "string"
              },
              "model": {
                "type": "string"
              },
              "new_strategy": {
                "type": "string"
              },
              "old_strategy": {
                "type": "string"
              }
            },
            "required": [
              "kind",
              "model",
              "new_strategy",
              "old_strategy"
            ],
            "type": "object"
          },
          {
            "description": "Materialization-specific key columns changed without the top-level variant changing (e.g. `Merge` `unique_key` rewritten, `Incremental` `timestamp_column` swapped).",
            "properties": {
              "key_kind": {
                "description": "Which key changed: `unique_key`, `timestamp_column`, `partition_by`, `time_column`, `granularity`, `target_lag`, `update_columns`, `storage_prefix`, or `partition_columns`.",
                "type": "string"
              },
              "kind": {
                "enum": [
                  "materialization_key_changed"
                ],
                "type": "string"
              },
              "model": {
                "type": "string"
              },
              "new": {
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "old": {
                "items": {
                  "type": "string"
                },
                "type": "array"
              }
            },
            "required": [
              "key_kind",
              "kind",
              "model",
              "new",
              "old"
            ],
            "type": "object"
          },
          {
            "description": "Replication column-selection mode flipped between `All` and `Explicit(...)` or the explicit list changed.",
            "properties": {
              "kind": {
                "enum": [
                  "replication_columns_changed"
                ],
                "type": "string"
              },
              "model": {
                "type": "string"
              },
              "new": {
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "old": {
                "items": {
                  "type": "string"
                },
                "type": "array"
              }
            },
            "required": [
              "kind",
              "model",
              "new",
              "old"
            ],
            "type": "object"
          },
          {
            "description": "`LakehouseOptions::partition_by` changed without the strategy changing. Distinct from `MaterializationKeyChanged` since this lives on `format_options` rather than the strategy enum.",
            "properties": {
              "kind": {
                "enum": [
                  "partition_by_changed"
                ],
                "type": "string"
              },
              "model": {
                "type": "string"
              },
              "new": {
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "old": {
                "items": {
                  "type": "string"
                },
                "type": "array"
              }
            },
            "required": [
              "kind",
              "model",
              "new",
              "old"
            ],
            "type": "object"
          },
          {
            "description": "Target table reference renamed (catalog, schema, or table component).",
            "properties": {
              "kind": {
                "enum": [
                  "target_renamed"
                ],
                "type": "string"
              },
              "model": {
                "type": "string"
              },
              "new": {
                "type": "string"
              },
              "old": {
                "type": "string"
              }
            },
            "required": [
              "kind",
              "model",
              "new",
              "old"
            ],
            "type": "object"
          },
          {
            "description": "Source reference rebound to a different table.",
            "properties": {
              "kind": {
                "enum": [
                  "source_changed"
                ],
                "type": "string"
              },
              "model": {
                "type": "string"
              },
              "new": {
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "old": {
                "items": {
                  "type": "string"
                },
                "type": "array"
              }
            },
            "required": [
              "kind",
              "model",
              "new",
              "old"
            ],
            "type": "object"
          },
          {
            "description": "A column mask was added, removed, or its strategy changed.",
            "properties": {
              "column": {
                "type": "string"
              },
              "kind": {
                "enum": [
                  "column_mask_changed"
                ],
                "type": "string"
              },
              "model": {
                "type": "string"
              },
              "new_strategy": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "old_strategy": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "column",
              "kind",
              "model"
            ],
            "type": "object"
          },
          {
            "description": "Lakehouse format switched (e.g. `DeltaTable` → `IcebergTable`).",
            "properties": {
              "kind": {
                "enum": [
                  "lakehouse_format_changed"
                ],
                "type": "string"
              },
              "model": {
                "type": "string"
              },
              "new": {
                "type": "string"
              },
              "old": {
                "type": "string"
              }
            },
            "required": [
              "kind",
              "model",
              "new",
              "old"
            ],
            "type": "object"
          },
          {
            "description": "SQL body changed without any other detectable structural change. Surfaced as `Info` because the typed-output shape is unchanged, but runtime row contents may differ.",
            "properties": {
              "kind": {
                "enum": [
                  "sql_body_changed"
                ],
                "type": "string"
              },
              "model": {
                "type": "string"
              }
            },
            "required": [
              "kind",
              "model"
            ],
            "type": "object"
          }
        ]
      },
      "BreakingFinding": {
        "description": "A classified finding produced by [`diff_project_ir`].",
        "properties": {
          "change": {
            "$ref": "#/components/schemas/BreakingChange"
          },
          "severity": {
            "$ref": "#/components/schemas/BreakingSeverity"
          }
        },
        "required": [
          "change",
          "severity"
        ],
        "type": "object"
      },
      "BreakingSeverity": {
        "description": "Severity classification for a single semantic change.",
        "oneOf": [
          {
            "description": "Will break a downstream consumer that reads the model's prior shape.",
            "enum": [
              "breaking"
            ],
            "type": "string"
          },
          {
            "description": "May break consumers depending on usage; e.g. nullable → NOT NULL, column reordered.",
            "enum": [
              "warning"
            ],
            "type": "string"
          },
          {
            "description": "Informational only — purely additive or backwards-compatible.",
            "enum": [
              "info"
            ],
            "type": "string"
          }
        ]
      },
      "BriefActiveFreeze": {
        "description": "An active policy freeze inside [`BriefAutonomySection`].",
        "properties": {
          "frozen_at": {
            "description": "RFC 3339 wall clock when the freeze was recorded — the citation.",
            "type": "string"
          },
          "plan_id": {
            "description": "The freeze decision's `plan_id`.",
            "type": "string"
          },
          "principal": {
            "$ref": "#/components/schemas/PolicyPrincipal"
          },
          "scope": {
            "description": "The scope selector the freeze targets.",
            "type": "string"
          }
        },
        "required": [
          "frozen_at",
          "plan_id",
          "principal",
          "scope"
        ],
        "type": "object"
      },
      "BriefAgentActivitySection": {
        "description": "Agent-activity section — the policy-decision ledger rolled up by principal.",
        "properties": {
          "allow": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "availability": {
            "$ref": "#/components/schemas/SectionAvailability"
          },
          "by_principal": {
            "description": "One roll-up per acting principal (`human` / `agent`).",
            "items": {
              "$ref": "#/components/schemas/BriefPrincipalActivity"
            },
            "type": "array"
          },
          "decisions": {
            "description": "Every decision in the window, newest first, each fully cited.",
            "items": {
              "$ref": "#/components/schemas/BriefDecisionEntry"
            },
            "type": "array"
          },
          "deny": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "note": {
            "type": [
              "string",
              "null"
            ]
          },
          "require_review": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "total": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "allow",
          "availability",
          "by_principal",
          "decisions",
          "deny",
          "require_review",
          "total"
        ],
        "type": "object"
      },
      "BriefAutonomySection": {
        "description": "Autonomy section — rules whose autonomy budget is currently exhausted (degraded to `require_review`) and policy freezes in force.\n\nThis is a *current-state* projection over the whole decision ledger — each budget uses its own configured window, independent of the digest's `--since`. It fails closed: `unavailable` when the ledger or config can't be read, `no_data` when nothing is degraded or frozen.",
        "properties": {
          "active_freezes": {
            "description": "Policy freezes currently in force.",
            "items": {
              "$ref": "#/components/schemas/BriefActiveFreeze"
            },
            "type": "array"
          },
          "availability": {
            "$ref": "#/components/schemas/SectionAvailability"
          },
          "degraded_rules": {
            "description": "Rules whose budget is exhausted right now — each auto-degraded to `require_review` until its failures age out of the window.",
            "items": {
              "$ref": "#/components/schemas/BriefDegradedRule"
            },
            "type": "array"
          },
          "note": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "active_freezes",
          "availability",
          "degraded_rules"
        ],
        "type": "object"
      },
      "BriefBudgetStatus": {
        "description": "Budget-burn status against the configured per-run ceiling.",
        "properties": {
          "max_usd_per_run": {
            "description": "The configured `[budget] max_usd` per-run ceiling.",
            "format": "double",
            "type": "number"
          },
          "runs_over_budget": {
            "description": "How many runs in the window exceeded the ceiling.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "worst_run_cost_usd": {
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          },
          "worst_run_id": {
            "description": "The priciest run in the window (its citation), when a cost was computed.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "max_usd_per_run",
          "runs_over_budget"
        ],
        "type": "object"
      },
      "BriefCostSection": {
        "description": "Cost section — per-run cost re-derived over the window, plus budget burn.",
        "properties": {
          "adapter_type": {
            "description": "The billed-warehouse adapter cost was computed against, if resolvable from `rocky.toml`.",
            "type": [
              "string",
              "null"
            ]
          },
          "availability": {
            "$ref": "#/components/schemas/SectionAvailability"
          },
          "budget": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/BriefBudgetStatus"
              },
              {
                "type": "null"
              }
            ],
            "description": "Budget-burn status against the configured per-run `[budget] max_usd`, when one is set."
          },
          "note": {
            "type": [
              "string",
              "null"
            ]
          },
          "per_run": {
            "description": "Per-run cost in the window, priciest first. Each cites its `run_id`.",
            "items": {
              "$ref": "#/components/schemas/BriefRunCost"
            },
            "type": "array"
          },
          "run_count": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "total_bytes_scanned": {
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "total_cost_usd": {
            "description": "Summed observed cost across the window. `null` when no run produced enough data to compute a cost (e.g. DuckDB, which has no billing).",
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          },
          "total_duration_ms": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "availability",
          "per_run",
          "run_count",
          "total_duration_ms"
        ],
        "type": "object"
      },
      "BriefDecisionEntry": {
        "description": "One recorded policy decision, cited for the digest.\n\n`decision_ref` is the composite ledger key (`\"{timestamp}|{plan_id}|{model}\"`) — the stable identity a governor drills into via `rocky audit`. `plan_id` and `rule_id` are the other two citations: which plan the decision governed and which `[[policy.rules]]` entry won.",
        "properties": {
          "capability": {
            "$ref": "#/components/schemas/PolicyCapability"
          },
          "decision_ref": {
            "description": "Composite ledger key that uniquely identifies this decision.",
            "type": "string"
          },
          "effect": {
            "$ref": "#/components/schemas/PolicyEffect"
          },
          "model": {
            "description": "The model the decision was about.",
            "type": "string"
          },
          "plan_id": {
            "description": "The plan the decision governed.",
            "type": "string"
          },
          "principal": {
            "$ref": "#/components/schemas/PolicyPrincipal"
          },
          "reason": {
            "description": "Human-readable explanation of how the effect was reached.",
            "type": "string"
          },
          "rule_id": {
            "description": "Index of the winning `[[policy.rules]]` entry, or `null` for the default posture.",
            "format": "uint",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "timestamp": {
            "description": "RFC 3339 timestamp when the decision was recorded.",
            "type": "string"
          }
        },
        "required": [
          "capability",
          "decision_ref",
          "effect",
          "model",
          "plan_id",
          "principal",
          "reason",
          "timestamp"
        ],
        "type": "object"
      },
      "BriefDegradedRule": {
        "description": "A budget-exhausted (degraded) rule inside [`BriefAutonomySection`].",
        "properties": {
          "failures": {
            "description": "Verify-after failures counted in the window.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "limit": {
            "description": "The rule's configured failure ceiling.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "rule_id": {
            "description": "Index of the degraded `[[policy.rules]]` entry.",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "window": {
            "description": "The rule's configured window (`7d`, `24h`, …).",
            "type": "string"
          }
        },
        "required": [
          "failures",
          "limit",
          "rule_id",
          "window"
        ],
        "type": "object"
      },
      "BriefDriftEntry": {
        "description": "A single schema-drift observation inside [`BriefDriftSection`].",
        "properties": {
          "change": {
            "description": "Human-readable description of the change.",
            "type": "string"
          },
          "graph_hash": {
            "description": "The DAG graph hash the change was observed against — the citation for this drift snapshot.",
            "type": "string"
          },
          "timestamp": {
            "type": "string"
          }
        },
        "required": [
          "change",
          "graph_hash",
          "timestamp"
        ],
        "type": "object"
      },
      "BriefDriftSection": {
        "description": "Drift section — schema drift recorded in the state store.",
        "properties": {
          "availability": {
            "$ref": "#/components/schemas/SectionAvailability"
          },
          "events": {
            "description": "Drift events in the window, newest first.",
            "items": {
              "$ref": "#/components/schemas/BriefDriftEntry"
            },
            "type": "array"
          },
          "note": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "availability",
          "events"
        ],
        "type": "object"
      },
      "BriefEscalationsSection": {
        "description": "Escalations section — `require_review` decisions still awaiting a human.",
        "properties": {
          "availability": {
            "$ref": "#/components/schemas/SectionAvailability"
          },
          "note": {
            "type": [
              "string",
              "null"
            ]
          },
          "pending": {
            "description": "The pending decisions, ranked, each fully cited.",
            "items": {
              "$ref": "#/components/schemas/BriefDecisionEntry"
            },
            "type": "array"
          },
          "ranking": {
            "description": "How the queue is ordered. `\"recency\"` in v0; blast-radius ranking (CLL × classification × staleness) is a later phase.",
            "type": "string"
          },
          "total": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "availability",
          "pending",
          "ranking",
          "total"
        ],
        "type": "object"
      },
      "BriefFailedModel": {
        "description": "A non-successful model execution inside a [`BriefRunEntry`].",
        "properties": {
          "model_name": {
            "type": "string"
          },
          "status": {
            "type": "string"
          }
        },
        "required": [
          "model_name",
          "status"
        ],
        "type": "object"
      },
      "BriefFreshnessEntry": {
        "description": "A single model's freshness reading inside [`BriefFreshnessSection`].",
        "properties": {
          "freshness_lag_seconds": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "model_name": {
            "type": "string"
          },
          "observed_at": {
            "type": "string"
          },
          "run_id": {
            "type": "string"
          }
        },
        "required": [
          "freshness_lag_seconds",
          "model_name",
          "observed_at",
          "run_id"
        ],
        "type": "object"
      },
      "BriefFreshnessSection": {
        "description": "Freshness / SLO section, derived from recorded quality snapshots.",
        "properties": {
          "availability": {
            "$ref": "#/components/schemas/SectionAvailability"
          },
          "models": {
            "description": "Per-model freshness lag, worst first. Each cites its `run_id`.",
            "items": {
              "$ref": "#/components/schemas/BriefFreshnessEntry"
            },
            "type": "array"
          },
          "note": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "availability",
          "models"
        ],
        "type": "object"
      },
      "BriefOutput": {
        "description": "JSON output for `rocky brief` — the governor's estate digest.\n\nA typed projection of the state store and the policy-decision ledger over a time window: agent activity by principal, runs, drift, freshness, quality, cost, and the ranked queue of decisions still awaiting a human. It is composed template-first from typed queries — there is no narration layer — and every line item carries a ledger citation (`run_id`, `plan_id`, or the composite `decision_ref`) so a claim can be independently checked against `rocky audit` / `rocky replay`.",
        "properties": {
          "agent_activity": {
            "allOf": [
              {
                "$ref": "#/components/schemas/BriefAgentActivitySection"
              }
            ],
            "description": "Agent- and human-authored policy decisions in the window, grouped by principal and effect."
          },
          "autonomy": {
            "allOf": [
              {
                "$ref": "#/components/schemas/BriefAutonomySection"
              }
            ],
            "description": "Autonomy-budget degradations and active policy freezes — the dynamic tightening currently in force across the estate."
          },
          "command": {
            "type": "string"
          },
          "cost": {
            "allOf": [
              {
                "$ref": "#/components/schemas/BriefCostSection"
              }
            ],
            "description": "Cost and budget burn across the window's runs."
          },
          "drift": {
            "allOf": [
              {
                "$ref": "#/components/schemas/BriefDriftSection"
              }
            ],
            "description": "Schema drift observed in the window."
          },
          "escalations": {
            "allOf": [
              {
                "$ref": "#/components/schemas/BriefEscalationsSection"
              }
            ],
            "description": "Decisions that resolved to `require_review` and still need a human, ranked."
          },
          "freshness": {
            "allOf": [
              {
                "$ref": "#/components/schemas/BriefFreshnessSection"
              }
            ],
            "description": "Freshness / SLO status in the window."
          },
          "generated_at": {
            "description": "RFC 3339 wall clock when the digest was generated (the window's upper bound).",
            "type": "string"
          },
          "quality": {
            "allOf": [
              {
                "$ref": "#/components/schemas/BriefQualitySection"
              }
            ],
            "description": "Data-quality status in the window."
          },
          "runs": {
            "allOf": [
              {
                "$ref": "#/components/schemas/BriefRunsSection"
              }
            ],
            "description": "Pipeline runs in the window, with the ones needing attention listed."
          },
          "since_mode": {
            "allOf": [
              {
                "$ref": "#/components/schemas/BriefSinceMode"
              }
            ],
            "description": "How the window was resolved."
          },
          "since_timestamp": {
            "description": "RFC 3339 lower bound of the window. `null` only for a first-ever `--since last` with no stored cursor — the digest then spans all of recorded history.",
            "type": [
              "string",
              "null"
            ]
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "agent_activity",
          "autonomy",
          "command",
          "cost",
          "drift",
          "escalations",
          "freshness",
          "generated_at",
          "quality",
          "runs",
          "since_mode",
          "version"
        ],
        "type": "object"
      },
      "BriefPrincipalActivity": {
        "description": "Per-principal decision counts inside [`BriefAgentActivitySection`].",
        "properties": {
          "allow": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "deny": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "principal": {
            "$ref": "#/components/schemas/PolicyPrincipal"
          },
          "require_review": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "total": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "allow",
          "deny",
          "principal",
          "require_review",
          "total"
        ],
        "type": "object"
      },
      "BriefQualityEntry": {
        "description": "A single model's quality reading inside [`BriefQualitySection`].",
        "properties": {
          "max_null_rate": {
            "description": "The highest per-column null rate observed for the model, if any columns were profiled.",
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          },
          "model_name": {
            "type": "string"
          },
          "observed_at": {
            "type": "string"
          },
          "row_count": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "run_id": {
            "type": "string"
          }
        },
        "required": [
          "model_name",
          "observed_at",
          "row_count",
          "run_id"
        ],
        "type": "object"
      },
      "BriefQualitySection": {
        "description": "Quality / check section, derived from recorded quality snapshots.",
        "properties": {
          "availability": {
            "$ref": "#/components/schemas/SectionAvailability"
          },
          "models": {
            "description": "Per-model quality readings, newest first. Each cites its `run_id`.",
            "items": {
              "$ref": "#/components/schemas/BriefQualityEntry"
            },
            "type": "array"
          },
          "note": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "availability",
          "models"
        ],
        "type": "object"
      },
      "BriefRunCost": {
        "description": "Per-run cost inside [`BriefCostSection`].",
        "properties": {
          "bytes_scanned": {
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "cost_usd": {
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          },
          "duration_ms": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "run_id": {
            "type": "string"
          }
        },
        "required": [
          "duration_ms",
          "run_id"
        ],
        "type": "object"
      },
      "BriefRunEntry": {
        "description": "A single run needing attention inside [`BriefRunsSection`].",
        "properties": {
          "failed_models": {
            "description": "Models within the run that did not report `success`.",
            "items": {
              "$ref": "#/components/schemas/BriefFailedModel"
            },
            "type": "array"
          },
          "finished_at": {
            "type": "string"
          },
          "run_id": {
            "description": "The run's identifier — the citation to drill in via `rocky replay`.",
            "type": "string"
          },
          "started_at": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "trigger": {
            "type": "string"
          }
        },
        "required": [
          "failed_models",
          "finished_at",
          "run_id",
          "started_at",
          "status",
          "trigger"
        ],
        "type": "object"
      },
      "BriefRunsSection": {
        "description": "Runs section — the run ledger rolled up by status.",
        "properties": {
          "attention": {
            "description": "Runs that did not fully succeed, newest first — the exception view. Each cites its `run_id`.",
            "items": {
              "$ref": "#/components/schemas/BriefRunEntry"
            },
            "type": "array"
          },
          "availability": {
            "$ref": "#/components/schemas/SectionAvailability"
          },
          "failed": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "note": {
            "type": [
              "string",
              "null"
            ]
          },
          "partial_failure": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "succeeded": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "total": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "attention",
          "availability",
          "failed",
          "partial_failure",
          "succeeded",
          "total"
        ],
        "type": "object"
      },
      "BriefSinceMode": {
        "description": "How the digest window was resolved.",
        "oneOf": [
          {
            "description": "Everything recorded since the previous `--since last` digest (the stored cursor). Advances the cursor on success.",
            "enum": [
              "last"
            ],
            "type": "string"
          },
          {
            "description": "A rolling 24-hour window ending now. Does not touch the cursor.",
            "enum": [
              "24h"
            ],
            "type": "string"
          },
          {
            "description": "A rolling 7-day window ending now. Does not touch the cursor.",
            "enum": [
              "7d"
            ],
            "type": "string"
          }
        ]
      },
      "BudgetBreachAction": {
        "description": "What to do when a [`BudgetConfig`] limit is exceeded by an actual run.\n\n`Warn` always fires the `budget_breach` event; `Error` additionally causes `rocky run` to exit with a non-zero status so orchestrators can gate downstream work on the breach.",
        "enum": [
          "warn",
          "error"
        ],
        "type": "string"
      },
      "BudgetBreachOutput": {
        "description": "One budget breach surfaced on [`RunOutput::budget_breaches`].\n\nKept as a CLI-side struct (rather than re-using [`rocky_core::config::BudgetBreach`]) so the JSON schema lives alongside the other `rocky run` output types. The fields mirror `BudgetBreach` one-to-one.",
        "properties": {
          "actual": {
            "format": "double",
            "type": "number"
          },
          "limit": {
            "format": "double",
            "type": "number"
          },
          "limit_type": {
            "description": "Which limit was breached: `\"max_usd\"`, `\"max_duration_ms\"`, or `\"max_bytes_scanned\"`.",
            "type": "string"
          }
        },
        "required": [
          "actual",
          "limit",
          "limit_type"
        ],
        "type": "object"
      },
      "BudgetConfig": {
        "additionalProperties": false,
        "description": "Declarative run-level budget for cost, duration, and data volume. All limits are optional; when unset the dimension is not enforced.\n\nA breach is detected at end of run by comparing [`BudgetConfig`] against the observed [`crate::cost::compute_observed_cost_usd`] total, the run wall clock, and the aggregate `bytes_scanned` summed across every materialization. Limits are independent and composed with all-OR — any single dimension breach trips the `budget_breach` event. Per-model budgets are deferred to a later wave; the first iteration enforces run-level totals only.",
        "properties": {
          "max_bytes_scanned": {
            "default": null,
            "description": "Maximum allowed total bytes scanned across every materialization in the run. Useful for CI gates that want to fail when a regression bloats scan volume even if the dollar cost stays within `max_usd` (e.g. a BigQuery query that suddenly stops pruning partitions).\n\nAggregated from per-model `bytes_scanned` figures the adapter reports — today that's BigQuery's `totalBytesBilled`; Databricks / Snowflake / DuckDB still inherit `None`, in which case the dimension is skipped rather than treated as zero (matching `max_usd`).",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "max_duration_ms": {
            "default": null,
            "description": "Maximum allowed run wall-clock duration in milliseconds.",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "max_usd": {
            "default": null,
            "description": "Maximum allowed run cost in USD. When set and exceeded, emits `budget_breach` on the event bus; when paired with `on_breach = \"error\"`, also fails the run.",
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          },
          "on_breach": {
            "allOf": [
              {
                "$ref": "#/components/schemas/BudgetBreachAction"
              }
            ],
            "default": "warn",
            "description": "What to do when a limit is breached. Defaults to `warn` — fire the event, keep the run successful. Set to `error` to fail the run."
          }
        },
        "type": "object"
      },
      "ByteCalibration": {
        "description": "Byte-level calibration block populated by `--calibrate-bytes`.\n\nSamples a handful of tables (default: 3), pulls their rows via `SELECT *`, hashes each row group with blake3 in-process, and computes byte-level dedup on the sample. The `lower_bound_multiplier` is the ratio `byte_dedup_pct / partition_dedup_pct` — it tells you how much of a floor the cheap partition-level measurement actually is.",
        "properties": {
          "byte_dedup_pct": {
            "description": "Byte-level blake3 dedup on the same sample.",
            "format": "double",
            "type": "number"
          },
          "lower_bound_multiplier": {
            "description": "`byte_dedup_pct / partition_dedup_pct`. A value >1.0 means the cheap partition-level measurement is genuinely underestimating.",
            "format": "double",
            "type": "number"
          },
          "partition_dedup_pct": {
            "description": "Partition-level `SUM(HASH())` dedup recomputed on the sample (so it's directly comparable to `byte_dedup_pct`).",
            "format": "double",
            "type": "number"
          },
          "tables_sampled": {
            "description": "Which tables were hashed at byte level.",
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "byte_dedup_pct",
          "lower_bound_multiplier",
          "partition_dedup_pct",
          "tables_sampled"
        ],
        "type": "object"
      },
      "CacheConfig": {
        "additionalProperties": false,
        "description": "Top-level `[cache]` configuration.\n\nHolds every cache surface that lives at the project level (i.e. has a `rocky.toml` knob). Today that's only the schema cache, but the shape is deliberately extensible: if a future `[cache.query]` or `[cache.plan]` surfaces, it lands as a new field on this struct with a `#[serde(default)]` attribute and its own `*Config` type — no breaking change to existing `rocky.toml` files.",
        "properties": {
          "schemas": {
            "allOf": [
              {
                "$ref": "#/components/schemas/SchemaCacheConfig"
              }
            ],
            "default": {
              "enabled": true,
              "replicate": false,
              "ttl_seconds": 86400
            },
            "description": "Schema cache. Stores `DESCRIBE TABLE` results in `state.redb` so leaf models typecheck against real warehouse types without a live round-trip on every compile."
          }
        },
        "type": "object"
      },
      "CatalogAsset": {
        "description": "A single asset (model or source) in the catalog.",
        "properties": {
          "columns": {
            "items": {
              "$ref": "#/components/schemas/CatalogColumn"
            },
            "type": "array"
          },
          "downstream_models": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "fqn": {
            "description": "Fully-qualified target identifier (`catalog.schema.table`) when resolvable, otherwise the model name.",
            "type": "string"
          },
          "intent": {
            "description": "Free-form natural-language description of the asset's purpose, when supplied via the model's sidecar config.",
            "type": [
              "string",
              "null"
            ]
          },
          "kind": {
            "allOf": [
              {
                "$ref": "#/components/schemas/AssetKind"
              }
            ],
            "description": "Whether the asset is a project-managed model or an external source."
          },
          "last_materialized_at": {
            "description": "Timestamp of the last successful materialization, when known.",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "last_run_id": {
            "description": "Identifier of the last successful run that produced the asset, when known.",
            "type": [
              "string",
              "null"
            ]
          },
          "model_name": {
            "description": "Model or source name as it appears in lineage edges.",
            "type": "string"
          },
          "recipe_identity": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/RecipeIdentityView"
              },
              {
                "type": "null"
              }
            ],
            "description": "Recipe-identity triple from the asset's most recent successful materialization, when known. See [`RecipeIdentityView`]. `None` for source-kind assets (no execution) and for models built before the triple was captured."
          },
          "upstream_models": {
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "columns",
          "downstream_models",
          "fqn",
          "kind",
          "model_name",
          "upstream_models"
        ],
        "type": "object"
      },
      "CatalogColumn": {
        "description": "A column on a catalog asset.",
        "properties": {
          "data_type": {
            "description": "Declared or inferred type of the column, when known.",
            "type": [
              "string",
              "null"
            ]
          },
          "description": {
            "description": "Human-readable description from the sidecar `[columns]` table, when set.",
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "type": "string"
          },
          "nullable": {
            "description": "Whether the column accepts nulls, when known.",
            "type": [
              "boolean",
              "null"
            ]
          }
        },
        "required": [
          "name"
        ],
        "type": "object"
      },
      "CatalogEdge": {
        "description": "A single column-level lineage edge.",
        "properties": {
          "confidence": {
            "allOf": [
              {
                "$ref": "#/components/schemas/EdgeConfidence"
              }
            ],
            "description": "Confidence the producing edge is fully understood. `Medium` is emitted for star-expanded projections (where lineage is inferred rather than parsed from an explicit projection); `High` for edges sourced from explicit columns."
          },
          "source_column": {
            "type": "string"
          },
          "source_model": {
            "type": "string"
          },
          "target_column": {
            "type": "string"
          },
          "target_model": {
            "type": "string"
          },
          "transform": {
            "description": "Transform kind: \"direct\", \"cast\", \"expression\", or \"aggregation: <fn>\". Stringified to match the existing lineage edge shape.",
            "type": "string"
          }
        },
        "required": [
          "confidence",
          "source_column",
          "source_model",
          "target_column",
          "target_model",
          "transform"
        ],
        "type": "object"
      },
      "CatalogOutput": {
        "description": "JSON output for `rocky catalog`.\n\nA persisted, queryable snapshot of column-level lineage across the project. Emitted to `./.rocky/catalog/catalog.json` (default) so any non-Rocky consumer (BI tool, governance dashboard, PR bot) can read the artifact without invoking the engine. The CLI's own stdout is a one-screen summary; this struct is the structured payload backing `--output json`.",
        "properties": {
          "assets": {
            "items": {
              "$ref": "#/components/schemas/CatalogAsset"
            },
            "type": "array"
          },
          "command": {
            "type": "string"
          },
          "config_hash": {
            "description": "Stable fingerprint of the `rocky.toml` bytes. Lets a downstream consumer detect whether the catalog was built against a config that has since changed.",
            "type": "string"
          },
          "edges": {
            "items": {
              "$ref": "#/components/schemas/CatalogEdge"
            },
            "type": "array"
          },
          "generated_at": {
            "format": "date-time",
            "type": "string"
          },
          "last_run_id": {
            "description": "Identifier of the last successful run that produced these assets, when known. Absent until the catalog is enriched with run-history metadata.",
            "type": [
              "string",
              "null"
            ]
          },
          "project_name": {
            "description": "Pipeline name used to build the catalog. When the project has multiple pipelines, this is the first one in declaration order.",
            "type": "string"
          },
          "stats": {
            "$ref": "#/components/schemas/CatalogStats"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "assets",
          "command",
          "config_hash",
          "edges",
          "generated_at",
          "project_name",
          "stats",
          "version"
        ],
        "type": "object"
      },
      "CatalogStats": {
        "description": "Aggregate counts for the emitted catalog.",
        "properties": {
          "asset_count": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "assets_with_star": {
            "description": "Number of assets whose lineage was derived from `SELECT *` (and is therefore inferred rather than parsed). Surfaces partial-lineage coverage as a single number in the summary.",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "column_count": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "duration_ms": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "edge_count": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "orphan_columns": {
            "description": "Number of columns with no producing edge — typically the result of a star expansion that could not resolve an upstream column.",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "asset_count",
          "assets_with_star",
          "column_count",
          "duration_ms",
          "edge_count",
          "orphan_columns"
        ],
        "type": "object"
      },
      "CheckResult": {
        "anyOf": [
          {
            "description": "Row count comparison between source and target tables.",
            "properties": {
              "source_count": {
                "format": "uint64",
                "minimum": 0.0,
                "type": "integer"
              },
              "target_count": {
                "format": "uint64",
                "minimum": 0.0,
                "type": "integer"
              }
            },
            "required": [
              "source_count",
              "target_count"
            ],
            "type": "object"
          },
          {
            "description": "Column presence comparison between source and target schemas.",
            "properties": {
              "extra": {
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "missing": {
                "items": {
                  "type": "string"
                },
                "type": "array"
              }
            },
            "required": [
              "extra",
              "missing"
            ],
            "type": "object"
          },
          {
            "description": "Data freshness measured as lag from the max timestamp to now.",
            "properties": {
              "lag_seconds": {
                "format": "uint64",
                "minimum": 0.0,
                "type": "integer"
              },
              "threshold_seconds": {
                "format": "uint64",
                "minimum": 0.0,
                "type": "integer"
              }
            },
            "required": [
              "lag_seconds",
              "threshold_seconds"
            ],
            "type": "object"
          },
          {
            "description": "Null rate for a specific column, sampled via TABLESAMPLE.",
            "properties": {
              "column": {
                "type": "string"
              },
              "null_rate": {
                "format": "double",
                "type": "number"
              },
              "threshold": {
                "format": "double",
                "type": "number"
              }
            },
            "required": [
              "column",
              "null_rate",
              "threshold"
            ],
            "type": "object"
          },
          {
            "description": "Row-level assertion evaluated via the `TestDecl` surface (`not_null`, `unique`, `accepted_values`, `relationships`, `expression`, `row_count_range`).\n\n`failing_rows` is the count of rows that violated the assertion; `0` means the check passed. For `row_count_range`, `failing_rows` stores the observed row count (the caller asserts pass/fail via the configured bounds).",
            "properties": {
              "column": {
                "description": "Column under test, when the assertion has one.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "failing_rows": {
                "description": "Number of failing rows (0 when passed). For `row_count_range`, this stores the observed total row count.",
                "format": "uint64",
                "minimum": 0.0,
                "type": "integer"
              },
              "kind": {
                "description": "Assertion kind — the `TestType` discriminant serialized as snake_case (e.g., `\"not_null\"`, `\"accepted_values\"`).",
                "type": "string"
              }
            },
            "required": [
              "failing_rows",
              "kind"
            ],
            "type": "object"
          },
          {
            "description": "User-defined SQL check evaluated against a threshold.",
            "properties": {
              "query": {
                "type": "string"
              },
              "result_value": {
                "format": "uint64",
                "minimum": 0.0,
                "type": "integer"
              },
              "threshold": {
                "format": "uint64",
                "minimum": 0.0,
                "type": "integer"
              }
            },
            "required": [
              "query",
              "result_value",
              "threshold"
            ],
            "type": "object"
          },
          {
            "description": "Cross-source overlap: the same business key found in more than one sibling source feeding a shared consolidation target.",
            "properties": {
              "contributing_tables": {
                "description": "Fully-qualified sibling tables that were compared.",
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "overlap_count": {
                "description": "Count of distinct keys that appear in more than one sibling table.",
                "format": "uint64",
                "minimum": 0.0,
                "type": "integer"
              },
              "sample": {
                "description": "Bounded sample of overlapping key values (stringified) for triage.",
                "items": {
                  "type": "string"
                },
                "type": "array"
              }
            },
            "required": [
              "contributing_tables",
              "overlap_count",
              "sample"
            ],
            "type": "object"
          }
        ],
        "description": "Result of a single data quality check.",
        "properties": {
          "name": {
            "type": "string"
          },
          "passed": {
            "type": "boolean"
          },
          "severity": {
            "allOf": [
              {
                "$ref": "#/components/schemas/TestSeverity"
              }
            ],
            "default": "error",
            "description": "Severity reported when the check fails. `error` causes the quality pipeline to exit non-zero (subject to `fail_on_error`); `warning` is advisory and does not fail the run."
          }
        },
        "required": [
          "name",
          "passed"
        ],
        "type": "object"
      },
      "ChecksConfig": {
        "additionalProperties": false,
        "description": "Data quality checks configuration (row count, column match, freshness, null rate, custom).",
        "properties": {
          "anomaly_threshold_pct": {
            "default": 50.0,
            "description": "Row count anomaly detection threshold (percentage deviation from baseline). Default: 50.0 (50% deviation triggers anomaly). Set to 0 to disable.",
            "format": "double",
            "type": "number"
          },
          "assertions": {
            "default": [],
            "description": "Row-level assertions (`not_null`, `unique`, `accepted_values`, `relationships`, `expression`, `row_count_range`) executed against specific tables in the quality pipeline. Each entry targets a single table by name and reuses the `TestDecl` surface from declarative model tests.",
            "items": {
              "$ref": "#/components/schemas/QualityAssertion"
            },
            "type": "array"
          },
          "column_match": {
            "allOf": [
              {
                "$ref": "#/components/schemas/AggregateCheckToggle"
              }
            ],
            "default": false
          },
          "cross_source_overlap": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/CrossSourceOverlapConfig"
              },
              {
                "type": "null"
              }
            ],
            "description": "Cross-source overlap check — flags the same business key appearing in more than one sibling source feeding a shared consolidation target. Disabled when unset. See [`CrossSourceOverlapConfig`]."
          },
          "custom": {
            "default": [],
            "items": {
              "$ref": "#/components/schemas/CustomCheckConfig"
            },
            "type": "array"
          },
          "enabled": {
            "default": false,
            "type": "boolean"
          },
          "fail_on_error": {
            "default": true,
            "description": "When `true` (default), the quality run exits non-zero if any error-severity check fails. Set `false` to force the pipeline to always succeed and leave failure handling to downstream consumers of the JSON output.",
            "type": "boolean"
          },
          "freshness": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/FreshnessConfig"
              },
              {
                "type": "null"
              }
            ],
            "default": null
          },
          "null_rate": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/NullRateConfig"
              },
              {
                "type": "null"
              }
            ],
            "default": null
          },
          "quarantine": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/QuarantineConfig"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Row quarantine — split/tag/drop rows that violate error-severity row-level assertions. Disabled when unset. See [`QuarantineConfig`]."
          },
          "row_count": {
            "allOf": [
              {
                "$ref": "#/components/schemas/AggregateCheckToggle"
              }
            ],
            "default": false
          }
        },
        "type": "object"
      },
      "ChecksConfigOutput": {
        "description": "Pipeline-level checks configuration projected into the discover output.\n\nThis is intentionally a thin projection of `rocky_core::config::ChecksConfig` — only the fields downstream orchestrators currently consume are exposed. Add fields as new integrations need them.",
        "properties": {
          "configured_checks": {
            "additionalProperties": {
              "items": {
                "$ref": "#/components/schemas/ResolvedCheckNameOutput"
              },
              "type": "array"
            },
            "description": "Resolved per-model check names the pipeline will emit as `CheckResult.name` at run time, keyed by unqualified table/model name. Only the NON-default checks are listed — the four defaults (row_count/column_match/freshness/anomaly) are well-known and pre-declared by consumers. A consumer (e.g. Dagster) declares an asset-check spec per name so the spec name byte-matches the emitted result. Empty (and omitted) when no non-default checks are configured.",
            "type": "object"
          },
          "freshness": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/FreshnessConfigOutput"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "type": "object"
      },
      "CiDiffOutput": {
        "description": "JSON output for `rocky ci-diff`.\n\nReports which models changed between two git refs, with optional column-level structural diffs when compilation succeeds on both sides.",
        "properties": {
          "base_ref": {
            "description": "Git ref used as the comparison base (e.g. `main`).",
            "type": "string"
          },
          "breaking_findings": {
            "description": "Classified semantic findings from the typed-IR breaking-change classifier. Only populated when `ci-diff` is invoked with `--semantic` and both base + HEAD compiles succeed; omitted from JSON output when empty.",
            "items": {
              "$ref": "#/components/schemas/BreakingFinding"
            },
            "type": "array"
          },
          "command": {
            "type": "string"
          },
          "head_ref": {
            "description": "Git ref for the incoming changes (typically `HEAD`).",
            "type": "string"
          },
          "markdown": {
            "description": "Pre-rendered Markdown suitable for posting as a GitHub PR comment.",
            "type": "string"
          },
          "models": {
            "description": "Per-model diff results.",
            "items": {
              "$ref": "#/components/schemas/DiffResult"
            },
            "type": "array"
          },
          "summary": {
            "allOf": [
              {
                "$ref": "#/components/schemas/DiffSummary"
              }
            ],
            "description": "Aggregate counts of changed models."
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "base_ref",
          "command",
          "head_ref",
          "markdown",
          "models",
          "summary",
          "version"
        ],
        "type": "object"
      },
      "CiOutput": {
        "description": "JSON output for `rocky ci`.",
        "properties": {
          "command": {
            "type": "string"
          },
          "compile_ok": {
            "type": "boolean"
          },
          "diagnostics": {
            "items": {
              "$ref": "#/components/schemas/Diagnostic"
            },
            "type": "array"
          },
          "exit_code": {
            "format": "int32",
            "type": "integer"
          },
          "failures": {
            "items": {
              "$ref": "#/components/schemas/TestFailure"
            },
            "type": "array"
          },
          "models_compiled": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "tests_failed": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "tests_ok": {
            "type": "boolean"
          },
          "tests_passed": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "command",
          "compile_ok",
          "diagnostics",
          "exit_code",
          "failures",
          "models_compiled",
          "tests_failed",
          "tests_ok",
          "tests_passed",
          "version"
        ],
        "type": "object"
      },
      "ClassificationAction": {
        "description": "Classification-tag application row in `PlanOutput.classification_actions`.",
        "properties": {
          "column": {
            "description": "Column name the tag will be applied to.",
            "type": "string"
          },
          "model": {
            "description": "Model name the action targets.",
            "type": "string"
          },
          "tag": {
            "description": "Free-form classification tag (e.g. `\"pii\"`, `\"confidential\"`).",
            "type": "string"
          }
        },
        "required": [
          "column",
          "model",
          "tag"
        ],
        "type": "object"
      },
      "ClassificationsConfig": {
        "additionalProperties": false,
        "description": "Advisory settings for column classification.\n\n```toml [classifications] allow_unmasked = [\"internal\"] ```\n\nAny classification tag listed in `allow_unmasked` suppresses the W004 \"tag has no masking strategy\" compiler warning. This is the escape hatch for teams that want to tag columns for discovery/lineage without requiring a matching `[mask]` strategy for every tag.",
        "properties": {
          "allow_unmasked": {
            "default": [],
            "description": "Classification tags that are allowed to appear in a model's `[classification]` block without a corresponding `[mask]` strategy.",
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "type": "object"
      },
      "ClearSchemaCacheOutput": {
        "description": "JSON output for `rocky state clear-schema-cache`.\n\n`dry_run = true` reports what *would* be deleted without touching redb; `dry_run = false` deletes the entries. `entries_deleted` is the actual removed count (or would-be-removed count in dry-run).",
        "properties": {
          "command": {
            "type": "string"
          },
          "dry_run": {
            "description": "`true` when `--dry-run` was set; the cache is left untouched.",
            "type": "boolean"
          },
          "entries_deleted": {
            "description": "Number of entries removed (or that would be removed in dry-run mode). Zero when the cache was already empty.",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "command",
          "dry_run",
          "entries_deleted",
          "version"
        ],
        "type": "object"
      },
      "CollisionCandidateOutput": {
        "description": "A cross-source collision: one external object id mapped to more than one target path (the same object onboarded twice under different schemas).",
        "properties": {
          "external_object_id": {
            "description": "The shared external object id (e.g. a Fivetran ad-account id).",
            "type": "string"
          },
          "sources": {
            "description": "The distinct source schemas that resolve to it (≥2), sorted.",
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "external_object_id",
          "sources"
        ],
        "type": "object"
      },
      "ColumnChangeType": {
        "description": "The kind of change observed for a single column.",
        "oneOf": [
          {
            "description": "Column was added in the incoming side.",
            "enum": [
              "added"
            ],
            "type": "string"
          },
          {
            "description": "Column was removed (present on base, absent on incoming).",
            "enum": [
              "removed"
            ],
            "type": "string"
          },
          {
            "description": "Column data type changed between base and incoming.",
            "enum": [
              "type_changed"
            ],
            "type": "string"
          }
        ]
      },
      "ColumnClassificationStatus": {
        "description": "Per-`(model, column)` report: the classification tag and the resolved masking status across every evaluated environment.",
        "properties": {
          "classification": {
            "description": "Free-form classification tag (e.g., `\"pii\"`, `\"confidential\"`). The engine does not enum-constrain these — projects coin tags as needed.",
            "type": "string"
          },
          "column": {
            "description": "Column name — the key under the model's `[classification]` block.",
            "type": "string"
          },
          "envs": {
            "description": "One entry per evaluated environment. When `--env X` is set, this is a single-element list labeled `\"X\"`. When unset, it expands across the union of the defaults (labeled `\"default\"`) and every named `[mask.<env>]` override block.",
            "items": {
              "$ref": "#/components/schemas/EnvMaskingStatus"
            },
            "type": "array"
          },
          "model": {
            "description": "Model name — matches `ModelConfig::name` (the `.toml` sidecar key or the `.sql`/`.rocky` filename stem).",
            "type": "string"
          }
        },
        "required": [
          "classification",
          "column",
          "envs",
          "model"
        ],
        "type": "object"
      },
      "ColumnDiff": {
        "description": "A single column-level difference within a model.",
        "properties": {
          "change_type": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ColumnChangeType"
              }
            ],
            "description": "Kind of change."
          },
          "column_name": {
            "description": "Column name.",
            "type": "string"
          },
          "new_type": {
            "description": "New data type (empty for removed columns).",
            "type": [
              "string",
              "null"
            ]
          },
          "old_type": {
            "description": "Previous data type (empty for added columns).",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "change_type",
          "column_name"
        ],
        "type": "object"
      },
      "ColumnLineageOutput": {
        "description": "JSON output for `rocky lineage <model> --column <col>`.",
        "properties": {
          "column": {
            "type": "string"
          },
          "command": {
            "type": "string"
          },
          "direction": {
            "description": "Direction of the trace walk: `\"upstream\"` (producers) or `\"downstream\"` (consumers). Defaults to upstream when `--column` is set without direction flags, matching the historical default.",
            "type": "string"
          },
          "downstream_consumers": {
            "description": "Every downstream column that transitively consumes `(model, column)`, deduplicated and deterministically sorted. An author-time \"what does changing this column affect\" signal, always populated regardless of `direction` so the default (upstream) trace still carries the blast radius. Inspection only — this never feeds a build/skip/reuse decision. Empty when the column has no consumers.",
            "items": {
              "$ref": "#/components/schemas/LineageQualifiedColumn"
            },
            "type": "array"
          },
          "model": {
            "type": "string"
          },
          "trace": {
            "items": {
              "$ref": "#/components/schemas/LineageEdgeRecord"
            },
            "type": "array"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "column",
          "command",
          "direction",
          "model",
          "trace",
          "version"
        ],
        "type": "object"
      },
      "ColumnTrendPoint": {
        "properties": {
          "null_rate": {
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          },
          "row_count": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "run_id": {
            "type": "string"
          },
          "timestamp": {
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "row_count",
          "run_id",
          "timestamp"
        ],
        "type": "object"
      },
      "CompactApplyOutput": {
        "description": "JSON output for `rocky compact apply <plan_id>`.",
        "properties": {
          "command": {
            "type": "string"
          },
          "executed_at": {
            "format": "date-time",
            "type": "string"
          },
          "plan_id": {
            "description": "The plan that was applied.",
            "type": "string"
          },
          "statements": {
            "description": "Per-statement results, in the order they were executed.",
            "items": {
              "$ref": "#/components/schemas/StatementResult"
            },
            "type": "array"
          },
          "success": {
            "description": "`true` when all statements succeeded; `false` on the first failure (remaining statements are still reported with `success: false` and `duration_ms: 0`).",
            "type": "boolean"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "command",
          "executed_at",
          "plan_id",
          "statements",
          "success",
          "version"
        ],
        "type": "object"
      },
      "CompactDedupOutput": {
        "description": "JSON output for `rocky compact --measure-dedup` (Layer 0 storage experiment).\n\nMeasures cross-table partition dedup across all Rocky-managed tables in a project. The `raw` and `semantic` numbers are both **partition-level lower bounds** on byte-level dedup — two partitions that share 99% of their row groups but differ on a single row will appear fully distinct here. The optional `calibration` block (populated by `--calibrate-bytes`) hashes actual row-group bytes on a sampled subset of tables to give a sharper, more expensive second number.\n\nThe measurement is **engine-scoped**: warehouse-native `HASH()` is not portable across engines (Databricks ≠ Snowflake ≠ DuckDB), so the `engine` field must be carried with any published result.",
        "properties": {
          "calibration": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/ByteCalibration"
              },
              {
                "type": "null"
              }
            ],
            "description": "Byte-level calibration on a sampled subset of tables. Present only when `--calibrate-bytes` was passed. Produces a sharper estimate at the cost of pulling raw rows and hashing them locally."
          },
          "command": {
            "type": "string"
          },
          "duration_ms": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "engine": {
            "description": "Warehouse engine identifier (`\"databricks\"`, `\"snowflake\"`, `\"duckdb\"`). Measurements are not comparable across engines because `HASH()` is not portable.",
            "type": "string"
          },
          "excluded_columns": {
            "description": "Columns excluded from the `semantic` hash.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "measured_at": {
            "format": "date-time",
            "type": "string"
          },
          "partitions_scanned": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "per_table": {
            "description": "Per-table breakdown showing which tables contribute most to the duplicate set.",
            "items": {
              "$ref": "#/components/schemas/TableDedupContribution"
            },
            "type": "array"
          },
          "project": {
            "description": "Project name from `rocky.toml`.",
            "type": "string"
          },
          "raw": {
            "allOf": [
              {
                "$ref": "#/components/schemas/DedupSummary"
              }
            ],
            "description": "Dedup computed over all columns (what a byte-level chunk store would actually deduplicate)."
          },
          "scope": {
            "description": "Whether the measurement was scoped to Rocky-managed tables (`\"managed\"`) or included all warehouse tables (`\"all\"`).",
            "type": "string"
          },
          "semantic": {
            "allOf": [
              {
                "$ref": "#/components/schemas/DedupSummary"
              }
            ],
            "description": "Dedup computed after excluding Rocky-owned metadata columns. The gap between `raw` and `semantic` is \"dedup we'd unlock if we ignored Rocky's own per-row metadata.\""
          },
          "tables_scanned": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "top_dedup_pairs": {
            "description": "Top-N table pairs by shared partition count (from the `semantic` measurement).",
            "items": {
              "$ref": "#/components/schemas/DedupPair"
            },
            "type": "array"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "command",
          "duration_ms",
          "engine",
          "excluded_columns",
          "measured_at",
          "partitions_scanned",
          "per_table",
          "project",
          "raw",
          "scope",
          "semantic",
          "tables_scanned",
          "top_dedup_pairs",
          "version"
        ],
        "type": "object"
      },
      "CompactOutput": {
        "description": "JSON output for `rocky compact`.\n\nTwo shapes share this struct:\n\n- **Single-model (`rocky compact <fqn>`)**: `model` is set; `scope`, `catalog`, `tables`, and `totals` are absent. Byte-stable with envelopes that predate the catalog-scope flag. - **Catalog scope (`rocky compact --catalog <name>`)**: `model` is absent, `scope = \"catalog\"`, `catalog` is set, `tables` keys per-FQN statement bundles, and `totals` carries aggregate counts. The flat `statements` field still carries every SQL statement across all tables for consumers that just iterate it.",
        "properties": {
          "catalog": {
            "description": "Set when invoked as `rocky compact --catalog <name>`. Stores the catalog identifier as resolved (lowercased to match the managed-table resolver's normalization).",
            "type": [
              "string",
              "null"
            ]
          },
          "command": {
            "type": "string"
          },
          "dry_run": {
            "type": "boolean"
          },
          "model": {
            "description": "Set when invoked as `rocky compact <fqn>`.",
            "type": [
              "string",
              "null"
            ]
          },
          "plan_id": {
            "description": "Plan identifier (full 64-char blake3 hex). Populated when the plan is persisted to `.rocky/plans/` so it can be applied later via `rocky compact apply <plan_id>`. Absent when plan persistence is skipped (e.g. `--measure-dedup` path).",
            "type": [
              "string",
              "null"
            ]
          },
          "scope": {
            "description": "`\"catalog\"` for the catalog-scoped path; absent for single-model invocations to keep their envelope byte-stable.",
            "type": [
              "string",
              "null"
            ]
          },
          "statements": {
            "description": "Flat list of every SQL statement across every table. Single-model invocations have one bundle here; `--catalog` invocations have the concatenation of every per-table bundle (in the same order as `tables`'s key iteration order).",
            "items": {
              "$ref": "#/components/schemas/NamedStatement"
            },
            "type": "array"
          },
          "tables": {
            "additionalProperties": {
              "$ref": "#/components/schemas/CompactTableEntry"
            },
            "description": "Per-table breakdown, keyed by fully-qualified table name. Present only on `--catalog` invocations.",
            "type": [
              "object",
              "null"
            ]
          },
          "target_size_mb": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "totals": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/CompactTotals"
              },
              {
                "type": "null"
              }
            ],
            "description": "Aggregate counts across the catalog. Present only on `--catalog` invocations."
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "command",
          "dry_run",
          "statements",
          "target_size_mb",
          "version"
        ],
        "type": "object"
      },
      "CompactTableEntry": {
        "description": "Per-table compaction plan inside a `--catalog` envelope.",
        "properties": {
          "statements": {
            "items": {
              "$ref": "#/components/schemas/NamedStatement"
            },
            "type": "array"
          }
        },
        "required": [
          "statements"
        ],
        "type": "object"
      },
      "CompactTotals": {
        "description": "Aggregate counts over a `rocky compact --catalog` invocation.",
        "properties": {
          "statement_count": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "table_count": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "statement_count",
          "table_count"
        ],
        "type": "object"
      },
      "CompareOutput": {
        "description": "JSON output for `rocky compare`.",
        "properties": {
          "command": {
            "type": "string"
          },
          "filter": {
            "type": "string"
          },
          "overall_verdict": {
            "type": "string"
          },
          "results": {
            "items": {
              "$ref": "#/components/schemas/TableCompareResult"
            },
            "type": "array"
          },
          "tables_compared": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "tables_failed": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "tables_passed": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "tables_warned": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "command",
          "filter",
          "overall_verdict",
          "results",
          "tables_compared",
          "tables_failed",
          "tables_passed",
          "tables_warned",
          "version"
        ],
        "type": "object"
      },
      "CompileOutput": {
        "description": "JSON output for `rocky compile`.",
        "properties": {
          "command": {
            "type": "string"
          },
          "compile_timings": {
            "$ref": "#/components/schemas/PhaseTimings"
          },
          "diagnostics": {
            "items": {
              "$ref": "#/components/schemas/Diagnostic"
            },
            "type": "array"
          },
          "execution_layers": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "expanded_sql": {
            "additionalProperties": {
              "type": "string"
            },
            "description": "Expanded SQL for each model after macro substitution. Only populated when `--expand-macros` is passed. Keys are model names, values are the SQL after all `@macro()` calls have been replaced.",
            "type": "object"
          },
          "has_errors": {
            "type": "boolean"
          },
          "models": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "models_detail": {
            "description": "Per-model details extracted from each model's TOML frontmatter. Empty when the project has no models. Downstream consumers (`dagster-rocky` to surface per-model freshness policies and partition-keyed assets) iterate this list rather than re-loading model frontmatter themselves.",
            "items": {
              "$ref": "#/components/schemas/ModelDetail"
            },
            "type": "array"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "command",
          "compile_timings",
          "diagnostics",
          "execution_layers",
          "has_errors",
          "models",
          "version"
        ],
        "type": "object"
      },
      "ComplianceException": {
        "description": "A single compliance exception — a classified column with no resolved masking strategy in some environment, and whose classification tag is **not** on the advisory allow list.",
        "properties": {
          "column": {
            "type": "string"
          },
          "env": {
            "type": "string"
          },
          "model": {
            "type": "string"
          },
          "reason": {
            "description": "Human-readable explanation. Always of the shape `\"no masking strategy resolves for classification tag '<tag>'\"` in v1; future variants (e.g., adapter-specific violations) may widen this.",
            "type": "string"
          }
        },
        "required": [
          "column",
          "env",
          "model",
          "reason"
        ],
        "type": "object"
      },
      "ComplianceOutput": {
        "description": "JSON output for `rocky compliance`.\n\nA thin rollup over the project's governance config: classification sidecars (`[classification]` per model) + project-level `[mask]` / `[mask.<env>]` policy. Answers the question **\"are all classified columns masked wherever policy says they should be?\"** without making any warehouse calls — purely a static resolver over `rocky.toml` + model sidecars.\n\nConsumers (CI gates, dagster) dispatch on the top-level `command` field (`\"compliance\"`).",
        "properties": {
          "command": {
            "description": "Always `\"compliance\"`. Consumers key dispatch off this field.",
            "type": "string"
          },
          "exceptions": {
            "description": "The unmasked-where-expected list. An exception fires when a classification tag has no resolved masking strategy **and** the tag is not on the project-level `[classifications] allow_unmasked` advisory list.",
            "items": {
              "$ref": "#/components/schemas/ComplianceException"
            },
            "type": "array"
          },
          "per_column": {
            "description": "One entry per `(model, column)` pair carrying a classification tag. When `--exceptions-only` is set, this list is filtered to only the pairs whose `envs` contain at least one exception.",
            "items": {
              "$ref": "#/components/schemas/ColumnClassificationStatus"
            },
            "type": "array"
          },
          "summary": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ComplianceSummary"
              }
            ],
            "description": "Aggregate tallies over the full `per_column` / `exceptions` lists."
          },
          "version": {
            "description": "`CARGO_PKG_VERSION` at the time this payload was emitted.",
            "type": "string"
          }
        },
        "required": [
          "command",
          "exceptions",
          "per_column",
          "summary",
          "version"
        ],
        "type": "object"
      },
      "ComplianceSummary": {
        "description": "Aggregate counters for a compliance report.\n\nAll three counters are over `(model, column, env)` triples — so a single classified column evaluated across three envs contributes up to three to `total_classified`. This matches the granularity of `total_exceptions` and `total_masked`, keeping the invariant `total_classified = total_masked + total_exceptions + <allow_listed unresolved>` (the third term is not counted in either bucket, so it surfaces implicitly as the gap).",
        "properties": {
          "total_classified": {
            "description": "Total `(model, column, env)` triples across every classified column in the project, expanded across the env enumeration.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "total_exceptions": {
            "description": "Triples where no masking strategy resolved and the classification tag is **not** on the `allow_unmasked` advisory list. Matches the length of [`ComplianceOutput::exceptions`].",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "total_masked": {
            "description": "Triples where a masking strategy successfully resolved (`enforced = true`). `MaskStrategy::None` (\"explicit identity — no masking\") counts as masked here because the project has deliberately opted out, which is a conscious policy decision rather than an enforcement gap.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "total_classified",
          "total_exceptions",
          "total_masked"
        ],
        "type": "object"
      },
      "CompositeKind": {
        "description": "Kind of composite (multi-column) assertion.",
        "oneOf": [
          {
            "description": "`(col1, col2, ...)` is unique across all rows.",
            "enum": [
              "unique"
            ],
            "type": "string"
          }
        ]
      },
      "ConcurrencyMode": {
        "anyOf": [
          {
            "enum": [
              "adaptive"
            ],
            "type": "string"
          },
          {
            "minimum": 1.0,
            "type": "integer"
          }
        ],
        "description": "Concurrency strategy: the literal `\"adaptive\"` for AIMD-based throttling, or a positive integer for fixed concurrency."
      },
      "Confidence": {
        "description": "Confidence level for an incrementality recommendation.",
        "oneOf": [
          {
            "description": "Multiple strong signals (name + type + SQL context).",
            "enum": [
              "high"
            ],
            "type": "string"
          },
          {
            "description": "Two signals (e.g., name + type, or name + SQL context).",
            "enum": [
              "medium"
            ],
            "type": "string"
          },
          {
            "description": "Single signal (e.g., name pattern only).",
            "enum": [
              "low"
            ],
            "type": "string"
          }
        ]
      },
      "ContainedModelOutput": {
        "description": "One model withheld from a run because an upstream failed (or was itself withheld) and failure-containment continued the disjoint subgraphs.\n\nThis is the blast radius of a failure — the run's `errors[]` name the root cause(s). A withheld model was **not built** this run: its target was left untouched (never rebuilt on a failed upstream's stale/missing output). This is model-graph containment, distinct from the quality pipeline's row-level `quarantine` surface.",
        "properties": {
          "blocked_by": {
            "description": "The failed-or-withheld upstream(s) that reach this model — its direct poisoned dependencies. The run's `errors[]` carry the root-cause detail.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "model": {
            "description": "The withheld model's name (matches the model entry in the project DAG).",
            "type": "string"
          },
          "unblock_hint": {
            "description": "Operator hint: resolve the named upstream failure(s), then re-run.",
            "type": "string"
          }
        },
        "required": [
          "blocked_by",
          "model",
          "unblock_hint"
        ],
        "type": "object"
      },
      "ContractConfig": {
        "description": "Data contract configuration — enforced at copy/load time.",
        "properties": {
          "allowed_type_changes": {
            "default": [],
            "description": "Type changes that are allowed (widening only).",
            "items": {
              "$ref": "#/components/schemas/AllowedTypeChange"
            },
            "type": "array"
          },
          "protected_columns": {
            "default": [],
            "description": "Column names that must never be removed from the target.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "required_columns": {
            "default": [],
            "description": "Columns that must exist with specific types.",
            "items": {
              "$ref": "#/components/schemas/RequiredColumn"
            },
            "type": "array"
          }
        },
        "type": "object"
      },
      "ContractResult": {
        "description": "Result of contract validation.",
        "properties": {
          "passed": {
            "type": "boolean"
          },
          "violations": {
            "items": {
              "$ref": "#/components/schemas/ContractViolation"
            },
            "type": "array"
          },
          "warnings": {
            "default": [],
            "description": "Non-fatal warnings — e.g. a contract clause that can't be meaningfully enforced in this context. Does not affect `passed`.",
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "passed",
          "violations"
        ],
        "type": "object"
      },
      "ContractViolation": {
        "description": "A single contract rule violation with the rule name, affected column, and message.",
        "properties": {
          "column": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "rule": {
            "type": "string"
          }
        },
        "required": [
          "column",
          "message",
          "rule"
        ],
        "type": "object"
      },
      "CostGroup": {
        "description": "One grouped row in [`CostOutput::groups`], emitted when `rocky cost` is run with `--by <dimension>`.\n\nEach group sums the per-model figures of the executions that share the grouping key (a tenant, or a model name). The cost roll-up uses the same `compute_observed_cost_usd` figures as [`PerModelCostHistorical`], so a `--by tenant` total equals the sum of its members' `cost_usd`.",
        "properties": {
          "dimension": {
            "description": "Dimension the grouping was performed on: `\"tenant\"` or `\"model\"`.",
            "type": "string"
          },
          "key": {
            "description": "The grouping key's value — the tenant name, the model name, or the literal `\"<unattributed>\"` for the `--by tenant` bucket that collects executions with no recorded tenant.",
            "type": "string"
          },
          "model_count": {
            "description": "Number of model executions rolled into this group.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "total_bytes_scanned": {
            "description": "Sum of the group's member `bytes_scanned`. `None` when no member reported bytes scanned.",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "total_bytes_written": {
            "description": "Sum of the group's member `bytes_written`. `None` when no member reported bytes written.",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "total_cost_usd": {
            "description": "Sum of every member's `cost_usd` that produced a number. `None` when no member produced a cost.",
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          },
          "total_duration_ms": {
            "description": "Wall-clock time summed across the group's member executions.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "dimension",
          "key",
          "model_count",
          "total_duration_ms"
        ],
        "type": "object"
      },
      "CostHint": {
        "description": "Heuristic cost estimate derived from DAG-aware cardinality propagation.\n\nThese numbers are directional — useful for comparing models within a project and surfacing expensive operations in the LSP, but not precise enough to substitute for a warehouse EXPLAIN. Use `rocky estimate` for warehouse-backed estimates.",
        "properties": {
          "confidence": {
            "description": "Confidence level: `\"low\"`, `\"medium\"`, or `\"high\"`.",
            "type": "string"
          },
          "estimated_bytes": {
            "description": "Estimated total output bytes.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "estimated_cost_usd": {
            "description": "Estimated compute cost in USD.",
            "format": "double",
            "type": "number"
          },
          "estimated_rows": {
            "description": "Estimated number of output rows.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "confidence",
          "estimated_bytes",
          "estimated_cost_usd",
          "estimated_rows"
        ],
        "type": "object"
      },
      "CostOutput": {
        "description": "JSON output for `rocky cost <run_id|latest>`.\n\nHistorical per-run cost attribution read from the embedded state store's [`rocky_core::state::RunRecord`]. Re-derives per-model cost via [`rocky_core::cost::compute_observed_cost_usd`] — the same formula [`RunOutput::populate_cost_summary`] applies at the end of a live run. The per-model and per-run totals make the \"what did my last run cost?\" question answerable from the recorded run alone, without re-materialising tables.\n\n# Adapter type resolution\n\nThe `RunRecord` only carries `config_hash`, not the adapter type. The command loads `rocky.toml` to resolve the billed-warehouse type. When the config can't be loaded (working-dir mismatch, missing file, parse error), the output degrades gracefully: `adapter_type` stays `None` and `cost_usd` is `None` on every model, but durations and byte counts are still populated from the stored record.\n\n# BigQuery note\n\nBecause [`rocky_core::state::ModelExecution::bytes_scanned`] is persisted, this command can return a real cost figure for BigQuery runs even though the live `rocky run` path currently reports `cost_usd: None` for BQ (adapter bytes-scanned plumbing is a follow-up).",
        "properties": {
          "adapter_type": {
            "description": "Adapter type the cost formula was parameterised against, for audit. Mirrors `AdapterConfig.type`. `None` when the config couldn't be loaded or the adapter isn't a billed warehouse.",
            "type": [
              "string",
              "null"
            ]
          },
          "command": {
            "type": "string"
          },
          "duration_ms": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "finished_at": {
            "type": "string"
          },
          "groups": {
            "description": "Grouped cost rollup, present only when `--by <dimension>` is passed. `--by model` produces one group per model; `--by tenant` produces one group per tenant (models with no recorded tenant land in an `\"<unattributed>\"` bucket). `None` (and omitted from JSON) for a plain `rocky cost` invocation, so the default output shape is unchanged. `per_model` is always present regardless of grouping.",
            "items": {
              "$ref": "#/components/schemas/CostGroup"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "per_model": {
            "items": {
              "$ref": "#/components/schemas/PerModelCostHistorical"
            },
            "type": "array"
          },
          "run_id": {
            "type": "string"
          },
          "started_at": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "total_bytes_scanned": {
            "description": "Sum of per-model `bytes_scanned`. `None` when no model reported bytes scanned.",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "total_bytes_written": {
            "description": "Sum of per-model `bytes_written`. `None` when no model reported bytes written.",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "total_cost_usd": {
            "description": "Sum of every per-model `cost_usd` that produced a number. `None` when no model produced a cost.",
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          },
          "total_duration_ms": {
            "description": "Wall-clock time summed across every model execution.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "trigger": {
            "type": "string"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "command",
          "duration_ms",
          "finished_at",
          "per_model",
          "run_id",
          "started_at",
          "status",
          "total_duration_ms",
          "trigger",
          "version"
        ],
        "type": "object"
      },
      "CostSection": {
        "additionalProperties": false,
        "description": "Cost estimation configuration.\n\nControls pricing assumptions used by [`crate::optimize::recommend_strategy`] when analyzing materialization costs and generating recommendations.",
        "properties": {
          "compute_cost_per_dbu": {
            "default": 0.4,
            "description": "Cost per DBU (default: $0.40).",
            "format": "double",
            "type": "number"
          },
          "min_history_runs": {
            "default": 5,
            "description": "Minimum runs before making cost recommendations.",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "storage_cost_per_gb_month": {
            "default": 0.023,
            "description": "Cost per GB of storage per month (default: $0.023).",
            "format": "double",
            "type": "number"
          },
          "warehouse_size": {
            "default": "Medium",
            "description": "Warehouse size for cost estimation (e.g., \"Small\", \"Medium\", \"Large\").",
            "type": "string"
          }
        },
        "type": "object"
      },
      "CrossSourceOverlapConfig": {
        "additionalProperties": false,
        "description": "Cross-source overlap check: flags the same business key appearing in more than one *sibling* source feeding a shared consolidation target (the \"same account onboarded twice under two paths\" case). Siblings are grouped by the runner; this config carries only the key + thresholds.\n\n`keys` (a column tuple) and `key_expr` (a derived SQL expression) are mutually exclusive — exactly one must be set, mirroring `unique` / `unique_expr`.",
        "properties": {
          "key_expr": {
            "description": "Derived business-key expression (e.g. `md5(a || '-' || b)`), for sources without a single natural key. Mutually exclusive with `keys`. Passed through verbatim (trusted config, like `unique_expr`).",
            "type": [
              "string",
              "null"
            ]
          },
          "keys": {
            "default": [],
            "description": "Business-key columns whose shared value across sibling tables signals a duplicate. Mutually exclusive with `key_expr`.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "max_overlap_rows": {
            "default": 0,
            "description": "Overlap-key count above which the check fails. Default 0 — any overlap fails.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "sample": {
            "default": 20,
            "description": "Maximum overlapping keys attached to the result for triage.",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "severity": {
            "allOf": [
              {
                "$ref": "#/components/schemas/TestSeverity"
              }
            ],
            "default": "error",
            "description": "Severity reported when the overlap-key count exceeds `max_overlap_rows`."
          }
        },
        "type": "object"
      },
      "CustomCheckConfig": {
        "additionalProperties": false,
        "description": "A user-defined SQL check with a name, query template, and pass/fail threshold.",
        "properties": {
          "name": {
            "type": "string"
          },
          "severity": {
            "allOf": [
              {
                "$ref": "#/components/schemas/TestSeverity"
              }
            ],
            "default": "error",
            "description": "Severity reported when this check fails."
          },
          "sql": {
            "type": "string"
          },
          "threshold": {
            "default": 0,
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "name",
          "sql"
        ],
        "type": "object"
      },
      "DagEdgeOutput": {
        "description": "One directed edge in the DAG output.",
        "properties": {
          "edge_type": {
            "description": "Semantic classification: `\"data\"`, `\"check\"`, `\"governance\"`.",
            "type": "string"
          },
          "from": {
            "description": "Upstream node ID.",
            "type": "string"
          },
          "to": {
            "description": "Downstream node ID.",
            "type": "string"
          }
        },
        "required": [
          "edge_type",
          "from",
          "to"
        ],
        "type": "object"
      },
      "DagNodeOutput": {
        "description": "One node in the enriched DAG, projected for orchestrators.\n\nCross-references the engine's internal `UnifiedNode` with model configs, seeds, and pipeline configs to attach the metadata that orchestrators need (target, strategy, freshness, partition shape).",
        "properties": {
          "depends_on": {
            "description": "Upstream node IDs (derived from DAG edges).",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "freshness": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/ModelFreshnessConfig"
              },
              {
                "type": "null"
              }
            ],
            "description": "Per-model freshness expectation from the model sidecar."
          },
          "id": {
            "description": "Unique identifier: `{kind}:{name}` (e.g. `transformation:stg_orders`).",
            "type": "string"
          },
          "kind": {
            "description": "Node kind: `source`, `load`, `transformation`, `quality`, `snapshot`, `seed`, `test`, `replication`.",
            "type": "string"
          },
          "label": {
            "description": "Human-readable label (usually the pipeline or model name).",
            "type": "string"
          },
          "partition_shape": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/PartitionShapeOutput"
              },
              {
                "type": "null"
              }
            ],
            "description": "Partition shape for time-interval models. `None` for unpartitioned strategies."
          },
          "pipeline": {
            "description": "Pipeline name from `rocky.toml`, if applicable.",
            "type": [
              "string",
              "null"
            ]
          },
          "strategy": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/StrategyConfig"
              },
              {
                "type": "null"
              }
            ],
            "description": "Materialization strategy. Present for transformation nodes."
          },
          "target": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/TargetConfig"
              },
              {
                "type": "null"
              }
            ],
            "description": "Target table coordinates. Present for transformation, seed, load, and snapshot nodes."
          }
        },
        "required": [
          "id",
          "kind",
          "label"
        ],
        "type": "object"
      },
      "DagOutput": {
        "description": "JSON output for `rocky dag`.\n\nProjects the engine's internal [`UnifiedDag`] into an enriched, orchestrator-friendly shape: every pipeline stage becomes a node with its target table coordinates, materialization strategy, freshness SLA, partition shape, and direct upstream dependencies.\n\nConsumers (dagster-rocky) can build a complete, connected asset graph from a single `rocky dag --output json` call.\n\n[`UnifiedDag`]: rocky_core::unified_dag::UnifiedDag",
        "properties": {
          "column_lineage": {
            "description": "Column-level lineage edges across all models. Only populated when `--column-lineage` is passed; empty otherwise.",
            "items": {
              "$ref": "#/components/schemas/LineageEdgeRecord"
            },
            "type": "array"
          },
          "command": {
            "type": "string"
          },
          "edges": {
            "description": "Directed edges between nodes (from → to).",
            "items": {
              "$ref": "#/components/schemas/DagEdgeOutput"
            },
            "type": "array"
          },
          "execution_layers": {
            "description": "Topologically-sorted execution layers. Nodes within the same layer have no mutual dependencies and can execute in parallel.",
            "items": {
              "items": {
                "type": "string"
              },
              "type": "array"
            },
            "type": "array"
          },
          "nodes": {
            "description": "Every stage in the pipeline as an enriched DAG node.",
            "items": {
              "$ref": "#/components/schemas/DagNodeOutput"
            },
            "type": "array"
          },
          "schema_version": {
            "default": "1",
            "description": "Version of the graph-export contract this payload conforms to.\n\nDistinct from `version` (the engine release, which churns every release): `schema_version` identifies the *shape* of the graph export — the node/edge/lineage fields orchestrators build an asset graph from. It is bumped only on a backward-incompatible change to that shape, so an orchestrator can pin against it across engine releases. Additive, backward-compatible field additions do not bump it (and surface through codegen-drift CI instead). Always emitted; older payloads that predate the field are treated as `\"1\"`.",
            "type": "string"
          },
          "summary": {
            "allOf": [
              {
                "$ref": "#/components/schemas/DagSummaryOutput"
              }
            ],
            "description": "Summary counts for the DAG."
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "command",
          "edges",
          "execution_layers",
          "nodes",
          "summary",
          "version"
        ],
        "type": "object"
      },
      "DagRunNodeOutput": {
        "description": "Per-node record in a [`DagRunOutput`].",
        "properties": {
          "duration_ms": {
            "description": "Wall-clock duration of this node's execution.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "error": {
            "description": "Failure reason. Present iff `status = \"failed\"` (or `\"skipped\"` due to upstream failure).",
            "type": [
              "string",
              "null"
            ]
          },
          "id": {
            "description": "Node identifier (e.g. `transformation:stg_orders`).",
            "type": "string"
          },
          "kind": {
            "description": "Node kind: `source`, `replication`, `transformation`, `quality`, `snapshot`, `load`, `seed`, `test`.",
            "type": "string"
          },
          "label": {
            "description": "Human-readable label (usually the pipeline or model name).",
            "type": "string"
          },
          "layer": {
            "description": "Layer index (0-based; nodes in the same layer ran concurrently).",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "status": {
            "description": "Status: `pending`, `running`, `completed`, `failed`, `skipped`.",
            "type": "string"
          }
        },
        "required": [
          "duration_ms",
          "id",
          "kind",
          "label",
          "layer",
          "status"
        ],
        "type": "object"
      },
      "DagRunOutput": {
        "description": "Output of `rocky run --dag`: per-node execution results plus aggregate counts.",
        "properties": {
          "command": {
            "type": "string"
          },
          "completed": {
            "description": "Nodes that completed successfully.",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "duration_ms": {
            "description": "Wall-clock duration of the entire DAG execution.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "failed": {
            "description": "Nodes whose dispatcher returned an error.",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "nodes": {
            "description": "Per-node execution records, sorted by (layer, id).",
            "items": {
              "$ref": "#/components/schemas/DagRunNodeOutput"
            },
            "type": "array"
          },
          "skipped": {
            "description": "Nodes skipped because an upstream failed (or because the dispatcher declined to handle them, e.g. Test nodes).",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "total_layers": {
            "description": "Number of execution layers (Kahn topological depth).",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "total_nodes": {
            "description": "Total nodes across all layers.",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "command",
          "completed",
          "duration_ms",
          "failed",
          "nodes",
          "skipped",
          "total_layers",
          "total_nodes",
          "version"
        ],
        "type": "object"
      },
      "DagSummaryOutput": {
        "description": "Summary counts for the DAG.",
        "properties": {
          "counts_by_kind": {
            "additionalProperties": {
              "format": "uint",
              "minimum": 0.0,
              "type": "integer"
            },
            "type": "object"
          },
          "execution_layers": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "total_edges": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "total_nodes": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "counts_by_kind",
          "execution_layers",
          "total_edges",
          "total_nodes"
        ],
        "type": "object"
      },
      "DeclarativeTestResult": {
        "description": "Result of a single declarative test assertion.",
        "properties": {
          "column": {
            "description": "Column under test, if applicable.",
            "type": [
              "string",
              "null"
            ]
          },
          "detail": {
            "description": "Human-readable detail (e.g., \"3 NULL rows found\" or execution error).",
            "type": [
              "string",
              "null"
            ]
          },
          "model": {
            "description": "Model name the test belongs to.",
            "type": "string"
          },
          "severity": {
            "description": "Severity declared in the sidecar (\"error\" or \"warning\").",
            "type": "string"
          },
          "sql": {
            "description": "The SQL that was executed.",
            "type": "string"
          },
          "status": {
            "description": "\"pass\", \"fail\", or \"error\".",
            "type": "string"
          },
          "table": {
            "description": "Fully-qualified target table (catalog.schema.table).",
            "type": "string"
          },
          "test_type": {
            "description": "Test type (e.g., \"not_null\", \"unique\", \"row_count_range\").",
            "type": "string"
          }
        },
        "required": [
          "model",
          "severity",
          "sql",
          "status",
          "table",
          "test_type"
        ],
        "type": "object"
      },
      "DeclarativeTestSummary": {
        "description": "Summary of declarative test execution (from `[[tests]]` in model sidecars).",
        "properties": {
          "errored": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "failed": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "passed": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "results": {
            "items": {
              "$ref": "#/components/schemas/DeclarativeTestResult"
            },
            "type": "array"
          },
          "total": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "warned": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "errored",
          "failed",
          "passed",
          "results",
          "total",
          "warned"
        ],
        "type": "object"
      },
      "DedupPair": {
        "description": "A pair of tables that share duplicate partitions.",
        "properties": {
          "shared_partitions": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "shared_rows": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "table_a": {
            "type": "string"
          },
          "table_b": {
            "type": "string"
          }
        },
        "required": [
          "shared_partitions",
          "shared_rows",
          "table_a",
          "table_b"
        ],
        "type": "object"
      },
      "DedupPolicy": {
        "description": "Policy controlling which terminal outcomes count for [`IdempotencyConfig::dedup_on`].",
        "oneOf": [
          {
            "description": "Only successful runs stamp a persistent dedup entry; failed runs leave the key claimable for a retry. Default — matches the common \"dedup successful notifications, allow retries\" use case.",
            "enum": [
              "success"
            ],
            "type": "string"
          },
          {
            "description": "Any terminal status stamps a persistent entry — subsequent calls with the same key always skip, regardless of whether the prior run succeeded. Use when replays are expensive even for failures.",
            "enum": [
              "any"
            ],
            "type": "string"
          }
        ]
      },
      "DedupSummary": {
        "description": "Summary of a single dedup measurement (raw or semantic).\n\n`dedup_ratio` is a **lower bound** on true byte-level dedup — see the docstring on [`CompactDedupOutput`].",
        "properties": {
          "dedup_ratio": {
            "description": "0.0..=1.0. Partition-level lower bound on byte-level dedup.",
            "format": "double",
            "type": "number"
          },
          "duplicate_partitions": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "estimated_savings_pct": {
            "description": "0.0..=100.0. Estimated storage savings if duplicate partitions were collapsed to a single copy.",
            "format": "double",
            "type": "number"
          },
          "total_rows": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "unique_partitions": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "dedup_ratio",
          "duplicate_partitions",
          "estimated_savings_pct",
          "total_rows",
          "unique_partitions"
        ],
        "type": "object"
      },
      "Diagnostic": {
        "description": "A compiler diagnostic (error, warning, or informational message).\n\n`code` and `message` use `Arc<str>` (§P3.5) — cloning a `Diagnostic` in the LSP publish loop becomes a refcount bump. Construction still accepts any `Into<String>` / `&str` via the helper constructors below; the arc wrap happens once at construction time.",
        "properties": {
          "code": {
            "description": "Diagnostic code (e.g., \"E001\", \"W001\").",
            "type": "string"
          },
          "message": {
            "description": "Human-readable message.",
            "type": "string"
          },
          "model": {
            "description": "Which model this diagnostic relates to.",
            "type": "string"
          },
          "severity": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Severity"
              }
            ],
            "description": "Severity level."
          },
          "span": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/SourceSpan"
              },
              {
                "type": "null"
              }
            ],
            "description": "Source location (if available)."
          },
          "suggestion": {
            "description": "Suggested fix (if any).",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "code",
          "message",
          "model",
          "severity"
        ],
        "type": "object"
      },
      "Dialect": {
        "description": "Target dialect for transpilation.\n\nSerializes lowercase (`databricks`, `snowflake`, `bigquery`, `duckdb`) so the long-form names can sit in `rocky.toml` under the `[portability]` block without translation. The CLI's short-form flag values (`dbx`/`sf`/`bq`/`duckdb`) are kept as ergonomics in the `TargetDialect` clap enum and convert to this type at the boundary.",
        "enum": [
          "databricks",
          "snowflake",
          "bigquery",
          "duckdb"
        ],
        "type": "string"
      },
      "DiffResult": {
        "description": "Diff result for a single model between two pipeline states.",
        "properties": {
          "column_changes": {
            "description": "Column-level changes (empty for unchanged models).",
            "items": {
              "$ref": "#/components/schemas/ColumnDiff"
            },
            "type": "array"
          },
          "model_name": {
            "description": "Fully-qualified model name (e.g. `catalog.schema.table`).",
            "type": "string"
          },
          "row_count_after": {
            "description": "Row count on the incoming (PR) side. `None` for removed models.",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "row_count_before": {
            "description": "Row count on the base (target branch) side. `None` for added models.",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "sample_changed_rows": {
            "description": "Optional sample of changed rows for human review.",
            "items": {
              "items": {
                "type": "string"
              },
              "type": "array"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "status": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ModelDiffStatus"
              }
            ],
            "description": "High-level status."
          }
        },
        "required": [
          "model_name",
          "status"
        ],
        "type": "object"
      },
      "DiffSummary": {
        "description": "High-level summary across all models in a diff run.",
        "properties": {
          "added": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "modified": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "removed": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "total_models": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "unchanged": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "added",
          "modified",
          "removed",
          "total_models",
          "unchanged"
        ],
        "type": "object"
      },
      "DiscoverOutput": {
        "description": "JSON output for `rocky discover`.",
        "properties": {
          "checks": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/ChecksConfigOutput"
              },
              {
                "type": "null"
              }
            ],
            "description": "Pipeline-level data quality check configuration. Present when the pipeline declares a `[checks]` block in `rocky.toml`. Downstream orchestrators (e.g. Dagster) consume this to attach asset-level freshness policies and check expectations without re-reading `rocky.toml` themselves."
          },
          "collision_candidates": {
            "description": "Groups of ≥2 discovered sources sharing the SAME external object id but resolving to DIFFERENT target paths — the same underlying object onboarded twice. Populated only when discovery's `on_collision` is `warn`/`error` and the adapter supplies `external_object_id`; empty (and omitted from the wire format) otherwise.",
            "items": {
              "$ref": "#/components/schemas/CollisionCandidateOutput"
            },
            "type": "array"
          },
          "command": {
            "type": "string"
          },
          "excluded_tables": {
            "description": "Tables filtered out of `sources` because they were reported by the discovery adapter but do not exist in the source warehouse. Same shape as `RunOutput.excluded_tables` so consumers can use one parser. Empty when nothing was filtered.",
            "items": {
              "$ref": "#/components/schemas/ExcludedTableOutput"
            },
            "type": "array"
          },
          "failed_sources": {
            "description": "Sources the discovery adapter attempted to fetch metadata for and failed (transient HTTP error, timeout, rate-limit budget exhausted, auth blip). Their absence from `sources` does NOT mean they were removed upstream — consumers diffing against a prior run must treat failed sources as \"unknown state, do not delete.\" Empty when discovery completed cleanly. See FR-014.",
            "items": {
              "$ref": "#/components/schemas/FailedSourceOutput"
            },
            "type": "array"
          },
          "new_sources": {
            "description": "Source schemas seen for the first time relative to the prior persisted `discover` snapshot — the catch-a-duplicate-at-onboarding signal. Populated only when the pipeline's discovery config sets `report_new_sources`; empty (and omitted from the wire format) otherwise. The first-ever discover of a pipeline establishes the baseline and reports none.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "schemas_cached": {
            "description": "Number of schema-cache entries written by this invocation.\n\nPopulated by `rocky discover --with-schemas` — the explicit warm-up path for the schema cache. Zero — and omitted from the wire format — when `--with-schemas` isn't set, so fixtures captured without the flag stay byte-stable.",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "sources": {
            "items": {
              "$ref": "#/components/schemas/SourceOutput"
            },
            "type": "array"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "command",
          "sources",
          "version"
        ],
        "type": "object"
      },
      "DiscoveryConfig": {
        "additionalProperties": false,
        "description": "Discovery configuration within a pipeline source.",
        "properties": {
          "adapter": {
            "default": "default",
            "description": "Name of the adapter to use for discovery (references a key in `[adapter.*]`). Defaults to `\"default\"`.",
            "type": "string"
          },
          "on_collision": {
            "allOf": [
              {
                "$ref": "#/components/schemas/OnCollision"
              }
            ],
            "default": "off",
            "description": "What to do when discover finds the same external object id mapped to more than one target path (likely the same object onboarded twice). `off` (default) skips detection entirely; `warn` reports `collision_candidates` + emits an event; `error` additionally fails the discover. Only adapters that supply `external_object_id` (e.g. Fivetran) participate; others are silently skipped."
          },
          "report_new_sources": {
            "default": false,
            "description": "When `true`, `rocky discover` diffs the discovered source inventory against the prior persisted snapshot and reports first-seen sources in `new_sources`. Off by default — the diff and its state write only happen when opted in, so existing projects pay nothing.",
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "DoctorOutput": {
        "description": "Doctor output structure.",
        "properties": {
          "checks": {
            "items": {
              "$ref": "#/components/schemas/HealthCheck"
            },
            "type": "array"
          },
          "command": {
            "type": "string"
          },
          "overall": {
            "type": "string"
          },
          "suggestions": {
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "checks",
          "command",
          "overall",
          "suggestions"
        ],
        "type": "object"
      },
      "DriftActionOutput": {
        "properties": {
          "action": {
            "type": "string"
          },
          "reason": {
            "type": "string"
          },
          "table": {
            "type": "string"
          }
        },
        "required": [
          "action",
          "reason",
          "table"
        ],
        "type": "object"
      },
      "DriftOutput": {
        "description": "JSON output for `rocky drift`.",
        "properties": {
          "command": {
            "type": "string"
          },
          "drift": {
            "$ref": "#/components/schemas/DriftSummary"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "command",
          "drift",
          "version"
        ],
        "type": "object"
      },
      "DriftSummary": {
        "properties": {
          "actions_taken": {
            "items": {
              "$ref": "#/components/schemas/DriftActionOutput"
            },
            "type": "array"
          },
          "tables_checked": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "tables_drifted": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "actions_taken",
          "tables_checked",
          "tables_drifted"
        ],
        "type": "object"
      },
      "EdgeConfidence": {
        "description": "Confidence grading for a [`CatalogEdge`].\n\nCoarse-grained on purpose: every consumer threshold-checks at \"good enough\", and a 3-bucket enum is simpler than a numeric score. A numeric `confidence_score` field can be added alongside this enum later without breaking callers.",
        "enum": [
          "High",
          "Medium",
          "Low"
        ],
        "type": "string"
      },
      "EncodingRecommendationOutput": {
        "properties": {
          "column": {
            "type": "string"
          },
          "data_type": {
            "type": "string"
          },
          "estimated_cardinality": {
            "type": "string"
          },
          "reasoning": {
            "type": "string"
          },
          "recommended_encoding": {
            "type": "string"
          }
        },
        "required": [
          "column",
          "data_type",
          "estimated_cardinality",
          "reasoning",
          "recommended_encoding"
        ],
        "type": "object"
      },
      "EnvMaskingStatus": {
        "description": "Masking status for one `(model, column, env)` triple.",
        "properties": {
          "enforced": {
            "description": "`true` iff `masking_strategy != \"unresolved\"`. Allow-listed tags (on `[classifications] allow_unmasked`) still report `enforced = false` — the allow list only suppresses the [`ComplianceException`] emission, not the underlying fact that no strategy resolved.",
            "type": "boolean"
          },
          "env": {
            "description": "Environment label. `\"default\"` when the row came from the unscoped `[mask]` block; otherwise the name of the matching `[mask.<env>]` override, or the raw `--env` value.",
            "type": "string"
          },
          "masking_strategy": {
            "description": "Resolved masking strategy. One of `\"hash\"`, `\"redact\"`, `\"partial\"`, `\"none\"`, or `\"unresolved\"` when no strategy applies to this classification tag in this env.",
            "type": "string"
          }
        },
        "required": [
          "enforced",
          "env",
          "masking_strategy"
        ],
        "type": "object"
      },
      "EnvelopeVersion": {
        "description": "Envelope schema version. A newtype enum (rather than a free-form string) so adding a new variant is a compile-time event and so downstream deserializers can refuse unknown versions at parse time instead of accepting a string they can't actually handle.",
        "oneOf": [
          {
            "description": "Initial envelope shape.",
            "enum": [
              "1.0"
            ],
            "type": "string"
          }
        ]
      },
      "ErrorEnvelope": {
        "description": "Structured error body returned by the `rocky serve` HTTP API.\n\nEvery non-2xx `/api/v1` response carries this envelope: the HTTP status line carries the error *class* (`400`/`401`/`404`/`409`/`500`/`503`) and the body carries a stable machine token plus a human message and an optional actionable hint. Embedders switch on [`code`](Self::code) and surface [`message`](Self::message) / [`remediation_hint`](Self::remediation_hint) to operators.\n\nStable codes emitted today: `engine_not_ready` (no compile available yet), `engine_busy` (state locked by a running job — retryable), `model_not_found`, `job_not_found`, `mutation_in_progress` (a `run`/`apply` job already holds the mutation permit — carries [`running_job_id`](Self::running_job_id)), `bad_request`, `unauthorized`, `internal_error`.",
        "properties": {
          "code": {
            "description": "Stable machine token, e.g. `\"model_not_found\"`.",
            "type": "string"
          },
          "message": {
            "description": "Human-readable description of what went wrong.",
            "type": "string"
          },
          "remediation_hint": {
            "description": "Actionable next step, or `null` when none applies.",
            "type": [
              "string",
              "null"
            ]
          },
          "running_job_id": {
            "description": "The `job_id` of the run/apply job currently holding the mutation permit, on a `409 mutation_in_progress` (and, when known, on a `503 engine_busy`). Omitted for every other error. Additive, serde-defaulted — embedders that predate it simply never see it.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "code",
          "message"
        ],
        "type": "object"
      },
      "EstimateOutput": {
        "description": "JSON output for `rocky estimate`.",
        "properties": {
          "command": {
            "type": "string"
          },
          "estimates": {
            "items": {
              "$ref": "#/components/schemas/ModelEstimate"
            },
            "type": "array"
          },
          "total_estimated_cost_usd": {
            "description": "Total estimated cost in USD across all models. `None` when no individual model produced a cost estimate.",
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          },
          "total_models": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "command",
          "estimates",
          "total_models",
          "version"
        ],
        "type": "object"
      },
      "ExcludedTableOutput": {
        "description": "A table that the discovery adapter reported but that is missing from the source warehouse, so the run skipped it. Tracked separately from `errors` because it is not a runtime failure — the row never made it past the pre-flight existence check.",
        "properties": {
          "asset_key": {
            "description": "Dagster-style asset key path (`[source_type, ...components, table]`).",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "reason": {
            "description": "Why the table was excluded. Currently always `\"missing_from_source\"` but kept as a free-form field so future causes (disabled, sync_paused, ...) can be added without a schema break.",
            "type": "string"
          },
          "source_schema": {
            "description": "Source schema the table was expected to live in.",
            "type": "string"
          },
          "table_name": {
            "description": "Bare table name as reported by the discovery adapter.",
            "type": "string"
          }
        },
        "required": [
          "asset_key",
          "reason",
          "source_schema",
          "table_name"
        ],
        "type": "object"
      },
      "ExecutionConfig": {
        "additionalProperties": false,
        "description": "Controls parallelism and error handling for table processing.\n\nRocky processes tables within a run concurrently using async tasks. Tune `concurrency` based on your warehouse capacity.",
        "properties": {
          "concurrency": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ConcurrencyMode"
              }
            ],
            "default": "adaptive",
            "description": "Concurrency strategy (default: `\"adaptive\"`).\n\n- `\"adaptive\"` — AIMD throttle that starts at 32 and adjusts based on rate-limit signals. Best for remote warehouses (Databricks, Snowflake). - An integer (e.g. `8`) — fixed concurrency, always this many in-flight tables. Use for local adapters (DuckDB) or when you know the limit. - `1` — serial execution."
          },
          "error_rate_abort_pct": {
            "default": 50,
            "description": "Abort remaining tables if error rate exceeds this percentage (0-100). Prevents wasting compute when the warehouse is unhealthy. Default: 50 (abort if more than half of completed tables failed). Set to 0 to disable.",
            "format": "uint32",
            "minimum": 0.0,
            "type": "integer"
          },
          "fail_fast": {
            "default": false,
            "description": "If true, abort all remaining tables on first error. If false, process all tables and report errors at the end (partial success).",
            "type": "boolean"
          },
          "table_retries": {
            "default": 1,
            "description": "Number of times to retry failed tables after the initial parallel phase. Default: 1. Set to 0 to disable auto-retry.",
            "format": "uint32",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "type": "object"
      },
      "ExecutionSummary": {
        "description": "Summary of execution parallelism and throughput.",
        "properties": {
          "adaptive_concurrency": {
            "description": "Whether adaptive concurrency (AIMD throttle) was enabled for this run.",
            "type": "boolean"
          },
          "concurrency": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "final_concurrency": {
            "description": "Final concurrency level at end of run (may differ from initial if adaptive concurrency adjusted it). Only present when adaptive concurrency is enabled.",
            "format": "uint",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "rate_limits_detected": {
            "description": "Number of rate-limit signals (HTTP 429 / UC_REQUEST_LIMIT_EXCEEDED) that triggered concurrency reduction. Only present when adaptive concurrency is enabled.",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "tables_failed": {
            "description": "Source tables whose **copy failed** on the replication path (the `table_errors` count). Transformation-model runtime failures — including contained-cause failures — are reported in `errors` and the top-level `RunOutput.tables_failed`, not here; transformation-only runs leave the whole `execution` block unpopulated. For overall run pass/fail, read the top-level `tables_failed` / `status`, never this field.",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "tables_processed": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "concurrency",
          "tables_failed",
          "tables_processed"
        ],
        "type": "object"
      },
      "FailedSourceOutput": {
        "description": "A source the discovery adapter attempted to fetch metadata for and failed.\n\nSurfaced on `DiscoverOutput.failed_sources` so downstream consumers can distinguish a transient fetch failure from a deletion when diffing successive discover snapshots (FR-014).",
        "properties": {
          "cooldown_seconds": {
            "description": "Backoff hint in whole seconds. Populated by adapters whose failure mode carries a known cooldown — currently only the Fivetran adapter when its shared circuit breaker trips. Orchestrators use it as a `retry_after` hint when scheduling a delayed re-discover. Absent for failure classes without an engine-supplied hint.",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "error_class": {
            "description": "Coarse error class so consumers can branch without parsing the `message`. One of `\"transient\"` / `\"timeout\"` / `\"rate_limit\"` / `\"auth\"` / `\"unknown\"`.",
            "type": "string"
          },
          "id": {
            "description": "Adapter-side identifier for the source (e.g. Fivetran connector_id).",
            "type": "string"
          },
          "message": {
            "description": "Human-readable error from the adapter — for logs / debugging only. Don't pattern-match on this; use `error_class` for branching.",
            "type": "string"
          },
          "schema": {
            "description": "Source schema name the adapter would have written into.",
            "type": "string"
          },
          "source_type": {
            "description": "Adapter type (`\"fivetran\"`, `\"airbyte\"`, `\"iceberg\"`, ...).",
            "type": "string"
          }
        },
        "required": [
          "error_class",
          "id",
          "message",
          "schema",
          "source_type"
        ],
        "type": "object"
      },
      "FailureAction": {
        "oneOf": [
          {
            "description": "Stop pipeline immediately.",
            "enum": [
              "abort"
            ],
            "type": "string"
          },
          {
            "description": "Log warning, continue execution.",
            "enum": [
              "warn"
            ],
            "type": "string"
          },
          {
            "description": "Silent continue.",
            "enum": [
              "ignore"
            ],
            "type": "string"
          }
        ]
      },
      "FailureKind": {
        "description": "Coarse-grained failure classification for an entry on [`RunOutput::errors`]. Lets orchestrators branch on the kind of failure (retry, page someone, surface in the UI) without parsing the free-form `error` string.\n\nVariants partition the [`rocky_databricks::connector::ConnectorError`] and [`rocky_snowflake::connector::ConnectorError`] spaces; `Unknown` is the fallback for non-connector failures (drift, governance, adapter-internal errors) where the error reached the output layer already type-erased.",
        "oneOf": [
          {
            "description": "TCP / TLS / DNS / connection-establishment failure.",
            "enum": [
              "connection-failed"
            ],
            "type": "string"
          },
          {
            "description": "Credentials rejected, token expired or otherwise invalid.",
            "enum": [
              "auth-failed"
            ],
            "type": "string"
          },
          {
            "description": "Warehouse rejected the SQL — syntax error, schema mismatch, missing permission, semantic analysis failure.",
            "enum": [
              "query-rejected"
            ],
            "type": "string"
          },
          {
            "description": "Retry-worthy failure — 5xx, network glitch, statement aborted by a transient warehouse condition.",
            "enum": [
              "transient"
            ],
            "type": "string"
          },
          {
            "description": "Rate limit hit or a configured budget cap (retry budget, circuit breaker, account-level quota).",
            "enum": [
              "quota-exceeded"
            ],
            "type": "string"
          },
          {
            "description": "Requested catalog / schema / table not present.",
            "enum": [
              "not-found"
            ],
            "type": "string"
          },
          {
            "description": "A model failed to compile (parse / type-check / contract diagnostic) before any SQL was issued — e.g. a `time_interval` model whose `time_column` is absent from its SELECT output (E020). The model never reached the warehouse; the run surfaces it as a failure instead of silently skipping it.",
            "enum": [
              "compile-error"
            ],
            "type": "string"
          },
          {
            "description": "Fallback when the failure could not be classified — e.g. errors raised outside the connector layer that reach this struct type-erased through `anyhow::Error`.",
            "enum": [
              "unknown"
            ],
            "type": "string"
          }
        ]
      },
      "FivetranCacheBackend": {
        "description": "Cache backend selector for [`FivetranCacheConfig`]. See the parent struct for the TOML shape and per-backend semantics.",
        "oneOf": [
          {
            "description": "No-op backend; every fetch goes to the Fivetran API. Default when `[adapter.fivetran.cache]` is absent.",
            "enum": [
              "none"
            ],
            "type": "string"
          },
          {
            "description": "Local-filesystem JSON files. Cheapest, no external dependency.",
            "enum": [
              "file"
            ],
            "type": "string"
          },
          {
            "description": "S3 / GCS / Azure / `file://` via the `object_store` crate.",
            "enum": [
              "object_store"
            ],
            "type": "string"
          },
          {
            "description": "Valkey / Redis. Requires the `valkey` Cargo feature.",
            "enum": [
              "valkey"
            ],
            "type": "string"
          },
          {
            "description": "Valkey (primary) + object_store (secondary). Requires the `valkey` Cargo feature.",
            "enum": [
              "tiered"
            ],
            "type": "string"
          }
        ]
      },
      "FivetranCacheConfig": {
        "additionalProperties": false,
        "description": "Persistent state cache configuration for the Fivetran adapter (FR-A).\n\nCache backends shared across processes let several `rocky` invocations against one Fivetran org dedupe their discover fetches — the first process pays the API cost, every subsequent process within the TTL window reads the canonical envelope from the cache. See `engine/crates/rocky-fivetran/src/state_cache/` for the implementation details.\n\n## TOML shape\n\n```toml [adapter.fivetran.cache] backend = \"tiered\"                              # \"none\" | \"file\" | \"object_store\" | \"valkey\" | \"tiered\" file_root = \".rocky/fivetran-state/\"            # required for backend = \"file\" object_store_url = \"s3://my-bucket/rocky/fv/\"   # required for backend = \"object_store\" / \"tiered\" valkey_url = \"rediss://valkey:6379/\"            # required for backend = \"valkey\" / \"tiered\" valkey_ttl_seconds = 600                        # default 600 ```\n\n## Backend selection\n\n- `none` — disable the cache; default when the block is absent. - `file` — local-filesystem JSON files under `file_root`. - `object_store` — S3 / GCS / Azure / `file://`; URL parsed by `object_store::parse_url`. Credentials come from the SDK default chain (`AWS_*` env vars, IAM role, `GOOGLE_APPLICATION_CREDENTIALS`, etc.) — Rocky doesn't introduce its own credential surface. - `valkey` — Redis / Valkey; requires building rocky-fivetran with the `valkey` Cargo feature. URL accepts `redis://` and `rediss://` (TLS). - `tiered` — composes Valkey (primary, fast) + object-store (secondary, durable). Requires both URLs.",
        "properties": {
          "backend": {
            "allOf": [
              {
                "$ref": "#/components/schemas/FivetranCacheBackend"
              }
            ],
            "default": "none",
            "description": "Which backend to instantiate. Defaults to [`FivetranCacheBackend::None`] so a stub `[adapter.fivetran.cache]` block with no `backend` key is well-defined."
          },
          "file_root": {
            "description": "Root directory for `backend = \"file\"`. Required for that backend; ignored otherwise. May contain `${VAR}` / `${VAR:-default}` env-var references; resolved at config-load time.",
            "type": [
              "string",
              "null"
            ]
          },
          "object_store_url": {
            "description": "URL passed to `object_store::parse_url` for `backend = \"object_store\"` or `backend = \"tiered\"` (secondary layer). Examples: `s3://bucket/prefix/`, `gs://bucket/prefix/`, `az://container/prefix/`, `file:///path/`.",
            "type": [
              "string",
              "null"
            ]
          },
          "valkey_ttl_seconds": {
            "description": "TTL applied to every `SET` against the Valkey backend. Defaults to 600s when unset. Ignored for non-Valkey backends.",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "valkey_url": {
            "description": "Connection URL for `backend = \"valkey\"` or `backend = \"tiered\"` (primary layer). Standard `redis://` (plain) or `rediss://` (TLS).",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "type": "object"
      },
      "FivetranCircuitBreakerBackend": {
        "description": "Circuit-breaker backend selector for the Fivetran adapter (Layer 3). See [`FivetranCircuitBreakerConfig`] for the TOML shape.",
        "oneOf": [
          {
            "description": "No-op breaker; state is always `Closed`. Default when the block is absent.",
            "enum": [
              "none"
            ],
            "type": "string"
          },
          {
            "description": "Shared per-account state machine in Valkey. Requires the `valkey` Cargo feature.",
            "enum": [
              "valkey"
            ],
            "type": "string"
          }
        ]
      },
      "FivetranCircuitBreakerConfig": {
        "additionalProperties": false,
        "description": "Circuit-breaker config for the Fivetran adapter (Layer 3).\n\nTrips after `failure_threshold` consecutive remote failures (5xx, network errors, exhausted retries) and short-circuits subsequent HTTP attempts with `FivetranError::CircuitOpen` until `cooldown_seconds` elapses. State transitions follow the standard `Closed → Open → HalfOpen → Closed` pattern, with exponentially extended cooldown on repeated half-open failures (capped at `cooldown_max_seconds`).\n\nState is shared across processes via Valkey so a Fivetran outage trips one breaker for the entire org rather than each process independently tripping its own.\n\n## TOML shape\n\n```toml [adapter.fivetran.circuit_breaker] backend = \"valkey\" valkey_url = \"rediss://valkey:6379/\" failure_threshold = 5 window_seconds = 60 cooldown_seconds = 300 cooldown_max_seconds = 3600 ```\n\n## Fail-open\n\nWhen the state store is unreachable the client behaves as if the breaker is `Closed` — coordination failure must not refuse live traffic.",
        "properties": {
          "backend": {
            "allOf": [
              {
                "$ref": "#/components/schemas/FivetranCircuitBreakerBackend"
              }
            ],
            "default": "none",
            "description": "Which backend to instantiate. Defaults to [`FivetranCircuitBreakerBackend::None`] so a stub block with no `backend` key is well-defined."
          },
          "cooldown_max_seconds": {
            "description": "Upper bound on the exponentially-extended cooldown after repeated half-open failures. Defaults to 3600s.",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "cooldown_seconds": {
            "description": "Initial cooldown (seconds) before the breaker transitions `Open → HalfOpen` for a probe. Defaults to 300s.",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "failure_threshold": {
            "description": "Consecutive failures required to transition `Closed → Open`. Defaults to 5 when unset.",
            "format": "uint32",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "valkey_url": {
            "description": "Connection URL for `backend = \"valkey\"`. Standard `redis://` (plain) or `rediss://` (TLS).",
            "type": [
              "string",
              "null"
            ]
          },
          "window_seconds": {
            "description": "Failure-counting window in seconds. Failures older than the window are not counted toward `failure_threshold`. Defaults to 60s when unset.",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "type": "object"
      },
      "FivetranColumnConfig": {
        "description": "One column entry inside the per-table schema config.",
        "properties": {
          "enabled": {
            "default": false,
            "description": "Whether this column is enabled for sync.",
            "type": "boolean"
          },
          "hashed": {
            "default": false,
            "description": "Whether Fivetran is applying its column-hash transform on this column (PII masking flag).",
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "FivetranConnectorStatus": {
        "description": "Sync-status block on a connector. Two fields today; left as a struct rather than flattened so future Fivetran additions land without breaking the envelope shape.",
        "properties": {
          "setup_state": {
            "type": "string"
          },
          "sync_state": {
            "type": "string"
          }
        },
        "required": [
          "setup_state",
          "sync_state"
        ],
        "type": "object"
      },
      "FivetranConnectorSummary": {
        "description": "Per-connector summary projected into the envelope.\n\nStrict superset of what `dagster_fivetran.FivetranWorkspaceData` consumes — the Python side can derive that shape from this one losslessly. A round-trip parity test against the dagster-fivetran public type lives on the Python side and is tracked as a follow-up.",
        "properties": {
          "failed_at": {
            "description": "Last failed sync timestamp, if any.",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "group_id": {
            "description": "Fivetran group ID owning this connector.",
            "type": [
              "string",
              "null"
            ]
          },
          "id": {
            "type": "string"
          },
          "name": {
            "description": "Human-friendly connector name. Fivetran returns this on the `groups/{id}/connectors` payload but the adapter currently captures only the `schema` field; the envelope surfaces both so downstream UIs can render the friendly name without re-querying. Falls back to `schema` when the upstream payload omits `name`.",
            "type": "string"
          },
          "paused": {
            "description": "Whether the connector is paused.",
            "type": "boolean"
          },
          "schema": {
            "description": "Destination schema this connector writes into.",
            "type": "string"
          },
          "service": {
            "description": "Fivetran service tag (e.g. `\"shopify\"`).",
            "type": "string"
          },
          "status": {
            "allOf": [
              {
                "$ref": "#/components/schemas/FivetranConnectorStatus"
              }
            ],
            "description": "Setup + sync state at fetch time."
          },
          "succeeded_at": {
            "description": "Last successful sync timestamp, if any.",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "name",
          "paused",
          "schema",
          "service",
          "status"
        ],
        "type": "object"
      },
      "FivetranDestination": {
        "description": "Fivetran destination metadata projected into the envelope.\n\nFields use the same names Fivetran's `GET /v1/destinations/{id}` endpoint returns — Rocky's adapter takes the wire shape verbatim so downstream consumers can move between the envelope and the upstream API without rename pain.",
        "properties": {
          "id": {
            "description": "Fivetran destination identifier (e.g. `\"dest_xyz\"`). Globally unique within an account.",
            "type": "string"
          },
          "region": {
            "description": "Region the destination is hosted in (e.g. `\"us-east-1\"`). Optional — older destination records may omit it.",
            "type": [
              "string",
              "null"
            ]
          },
          "service": {
            "description": "Fivetran service tag for the destination warehouse (e.g. `\"snowflake\"`, `\"big_query\"`). Optional.",
            "type": [
              "string",
              "null"
            ]
          },
          "setup_status": {
            "description": "Setup status of the destination, when present.",
            "type": [
              "string",
              "null"
            ]
          },
          "time_zone": {
            "description": "Destination time zone (e.g. `\"UTC\"`). Optional for the same reason as `region`.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id"
        ],
        "type": "object"
      },
      "FivetranRatelimitBackend": {
        "description": "Cross-pod rate-limit budget backend selector for the Fivetran adapter (FR-B Phase 2). See [`FivetranRatelimitConfig`] for the TOML shape.",
        "oneOf": [
          {
            "description": "Per-host file lock (Phase 1 behavior). Default when the block is absent.",
            "enum": [
              "file"
            ],
            "type": "string"
          },
          {
            "description": "Shared Valkey key. Requires the `valkey` Cargo feature.",
            "enum": [
              "valkey"
            ],
            "type": "string"
          }
        ]
      },
      "FivetranRatelimitConfig": {
        "additionalProperties": false,
        "description": "Cross-pod rate-limit budget config for the Fivetran adapter (FR-B Phase 2).\n\nPhase 1 (shipped in v1.37) writes `wake_at_epoch_ms` to a per-host file under `${TMPDIR}/rocky-fivetran-ratelimit/<account_hash>.json`. That works when several `rocky` processes share a host, but a Kubernetes deployment with one rocky-cli per pod sees N independent budgets even though every pod talks to the same Fivetran org. Lifting the budget into Valkey makes a 429 on one pod throttle every other pod for the same `Retry-After` window.\n\n## TOML shape\n\n```toml [adapter.fivetran.ratelimit] backend = \"valkey\" valkey_url = \"rediss://valkey:6379/\" ```\n\n## Fail-open\n\nWhen the configured backend is unreachable the client falls back to the per-host file backend. The cluster regresses to Phase 1 behavior (each host enforces its own budget) rather than blocking traffic.",
        "properties": {
          "backend": {
            "allOf": [
              {
                "$ref": "#/components/schemas/FivetranRatelimitBackend"
              }
            ],
            "default": "file",
            "description": "Which backend to instantiate. Defaults to [`FivetranRatelimitBackend::File`] so a stub block with no `backend` key is well-defined."
          },
          "max_wake_seconds": {
            "description": "Cap (seconds) applied to the Valkey key's `EXPIRE` so an abandoned `wake_at` value never outlives its useful window. Defaults to 600s when unset; ignored for the file backend.",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "valkey_url": {
            "description": "Connection URL for `backend = \"valkey\"`. Standard `redis://` (plain) or `rediss://` (TLS).",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "type": "object"
      },
      "FivetranSchemaConfig": {
        "description": "Schema config payload from `GET /v1/connectors/{id}/schemas`, projected into envelope shape with [`BTreeMap`]s for stable iteration order.",
        "properties": {
          "schemas": {
            "additionalProperties": {
              "$ref": "#/components/schemas/FivetranSchemaEntry"
            },
            "default": {},
            "description": "Map of logical schema key → schema entry.",
            "type": "object"
          }
        },
        "type": "object"
      },
      "FivetranSchemaEntry": {
        "description": "One schema entry inside the per-connector schema config.",
        "properties": {
          "enabled": {
            "default": false,
            "description": "Whether this destination schema is enabled.",
            "type": "boolean"
          },
          "name_in_destination": {
            "description": "Destination schema name when Fivetran has renamed it.",
            "type": [
              "string",
              "null"
            ]
          },
          "tables": {
            "additionalProperties": {
              "$ref": "#/components/schemas/FivetranTableConfig"
            },
            "default": {},
            "description": "Tables in this schema. [`BTreeMap`] for stable iteration order.",
            "type": "object"
          }
        },
        "type": "object"
      },
      "FivetranStampedeBackend": {
        "description": "Distributed cache-stampede lock backend selector for the Fivetran adapter (Layer 1). See [`FivetranStampedeConfig`] for the TOML shape.",
        "oneOf": [
          {
            "description": "No-op lock; every caller is treated as the leader. Default when the block is absent.",
            "enum": [
              "none"
            ],
            "type": "string"
          },
          {
            "description": "Distributed lock via Valkey `SET NX EX`. Requires the `valkey` Cargo feature.",
            "enum": [
              "valkey"
            ],
            "type": "string"
          }
        ]
      },
      "FivetranStampedeConfig": {
        "additionalProperties": false,
        "description": "Distributed cache-stampede protection config for the Fivetran adapter (Layer 1).\n\nOn a cold-start burst, N processes can simultaneously observe the cache miss, fan out N API calls, and write back N copies of the same envelope. The stampede lock elects a single leader (via `SET <key>:lock <id> NX EX <ttl>` against Valkey) to do the API call; followers wait on the cache key with bounded polling until the leader's write becomes visible, then return.\n\n## TOML shape\n\n```toml [adapter.fivetran.stampede] backend = \"valkey\" valkey_url = \"rediss://valkey:6379/\" lock_ttl_seconds = 60 poll_timeout_seconds = 30 ```\n\n## Fail-open\n\nWhen the lock backend is unreachable the client falls through to the direct HTTP path (no stampede protection, but no block).",
        "properties": {
          "backend": {
            "allOf": [
              {
                "$ref": "#/components/schemas/FivetranStampedeBackend"
              }
            ],
            "default": "none",
            "description": "Which backend to instantiate. Defaults to [`FivetranStampedeBackend::None`] so a stub block with no `backend` key is well-defined."
          },
          "lock_ttl_seconds": {
            "description": "Lock TTL applied to the `SET NX EX <ttl>` call. The lock auto-expires after this many seconds so a crashed leader doesn't park every follower forever. Defaults to 60s.",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "poll_timeout_seconds": {
            "description": "Hard cap on how long a follower will poll the cache before falling through to a direct HTTP fetch. Defaults to 30s.",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "valkey_url": {
            "description": "Connection URL for `backend = \"valkey\"`. Standard `redis://` (plain) or `rediss://` (TLS).",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "type": "object"
      },
      "FivetranStateEnvelope": {
        "description": "Canonical Fivetran state envelope written by `rocky discover --emit-fivetran-state-to <PATH>`.\n\nThe envelope is the single contract between Rocky and downstream consumers that want a snapshot of the Fivetran view of a destination — connectors, their sync states, and the per-connector schema config — without re-fetching from Fivetran themselves.\n\n## Construction invariants\n\n- [`Self::connectors`] is sorted ascending by `connector.id` at construction time via [`Self::from_parts`]. - [`Self::schemas`] is a [`BTreeMap`] so iteration order is the ascending lexicographic order of `connector_id` keys.\n\nThese guarantees make the serialized bytes byte-stable across runs (modulo [`Self::fetched_at`]), which is what makes [`envelope_hash`] a useful sentinel for the idempotent emit path.",
        "properties": {
          "connectors": {
            "description": "Connector summaries sorted ascending by `connector.id` — see the construction-invariant note on the type doc.",
            "items": {
              "$ref": "#/components/schemas/FivetranConnectorSummary"
            },
            "type": "array"
          },
          "destination": {
            "$ref": "#/components/schemas/FivetranDestination"
          },
          "fetched_at": {
            "format": "date-time",
            "type": "string"
          },
          "schemas": {
            "additionalProperties": {
              "$ref": "#/components/schemas/FivetranSchemaConfig"
            },
            "description": "Schema config keyed by connector id. [`BTreeMap`] for stable iteration order.",
            "type": "object"
          },
          "version": {
            "$ref": "#/components/schemas/EnvelopeVersion"
          }
        },
        "required": [
          "connectors",
          "destination",
          "fetched_at",
          "schemas",
          "version"
        ],
        "type": "object"
      },
      "FivetranTableConfig": {
        "description": "One table entry inside the per-connector schema config.",
        "properties": {
          "columns": {
            "additionalProperties": {
              "$ref": "#/components/schemas/FivetranColumnConfig"
            },
            "default": {},
            "description": "Per-column config. [`BTreeMap`] for stable iteration order so the envelope hash doesn't drift on HashMap shuffles.",
            "type": "object"
          },
          "enabled": {
            "default": false,
            "description": "Whether this table is enabled for sync.",
            "type": "boolean"
          },
          "name_in_destination": {
            "description": "Destination table name when Fivetran renames it (manual override or the `do_not_alter_` auto-prefix after a breaking schema change). The destination name is what shows up in `information_schema.tables`.",
            "type": [
              "string",
              "null"
            ]
          },
          "sync_mode": {
            "description": "Sync mode (e.g. `\"SOFT_DELETE\"`), when set.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "type": "object"
      },
      "FreshnessConfig": {
        "additionalProperties": false,
        "description": "Freshness check configuration with optional per-schema overrides.",
        "properties": {
          "overrides": {
            "additionalProperties": {
              "format": "uint64",
              "minimum": 0.0,
              "type": "integer"
            },
            "default": {},
            "description": "Per-schema freshness overrides. Key is a schema pattern (e.g., \"raw__us_west__shopify\"), value overrides threshold_seconds for matching schemas.",
            "type": "object"
          },
          "severity": {
            "allOf": [
              {
                "$ref": "#/components/schemas/TestSeverity"
              }
            ],
            "default": "error",
            "description": "Severity reported when freshness lag exceeds the threshold."
          },
          "threshold_seconds": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "threshold_seconds"
        ],
        "type": "object"
      },
      "FreshnessConfigOutput": {
        "description": "Freshness check configuration projected into the discover output.\n\nPer-schema `overrides` from `rocky_core::config::FreshnessConfig` are intentionally not exposed yet — the override-key semantics need to be nailed down before integrations can rely on them.",
        "properties": {
          "threshold_seconds": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "threshold_seconds"
        ],
        "type": "object"
      },
      "GcApplyOutput": {
        "description": "JSON output of `rocky apply <gc-plan>`.\n\nReports the eviction outcome per planned artifact. Deletion is the highest -stakes operation, so the output is exhaustive: what was evicted (with its tombstone), what was refused (with the live failing checks), and what was an idempotent no-op (already evicted by a prior apply).",
        "properties": {
          "already_evicted": {
            "description": "Content hashes of plan entries that were already absent from the ledger (a prior apply evicted them) — idempotent no-ops, not failures.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "bytes_evicted": {
            "description": "Total bytes evicted.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "bytes_refused": {
            "description": "Total bytes refused.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "command": {
            "type": "string"
          },
          "evicted": {
            "description": "Artifacts evicted — tombstone written, ledger row retired.",
            "items": {
              "$ref": "#/components/schemas/GcEvictedOutput"
            },
            "type": "array"
          },
          "evicted_count": {
            "description": "Count of evicted artifacts.",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "notes": {
            "description": "Operator caveats (e.g. physical-reclamation reachability). Each eviction's tombstone records everything `rocky restore <target>` needs to rebuild the artifact and verify it hash-exact.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "plan_id": {
            "type": "string"
          },
          "refused": {
            "description": "Artifacts refused because they were no longer derivable at apply time.",
            "items": {
              "$ref": "#/components/schemas/GcRefusedOutput"
            },
            "type": "array"
          },
          "refused_count": {
            "description": "Count of refused artifacts.",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "already_evicted",
          "bytes_evicted",
          "bytes_refused",
          "command",
          "evicted",
          "evicted_count",
          "notes",
          "plan_id",
          "refused",
          "refused_count",
          "version"
        ],
        "type": "object"
      },
      "GcCandidateOutput": {
        "description": "One reclamation candidate inside a [`GcReportOutput`] — a single content-addressed artifact (identified by its content hash) with its five printed eligibility checks.",
        "properties": {
          "blake3_hash": {
            "description": "Content hash (hex) of the artifact bytes — the reclamation unit.",
            "type": "string"
          },
          "checks": {
            "description": "The five eligibility checks, each with its pass/fail and a human-readable justification. Order is stable: `recipe_recorded`, `replayable`, `unreferenced`, `policy_allows`, `age_threshold`.",
            "items": {
              "$ref": "#/components/schemas/GcCheckOutput"
            },
            "type": "array"
          },
          "derivable": {
            "description": "`true` iff every one of [`Self::checks`] passed.",
            "type": "boolean"
          },
          "input_proof_class": {
            "description": "Input match-strength label carried on the provenance record (`strong` or `heuristic`); `null` when no provenance was found.",
            "type": [
              "string",
              "null"
            ]
          },
          "model_name": {
            "description": "Model that produced the artifact.",
            "type": "string"
          },
          "rebuild_cost": {
            "allOf": [
              {
                "$ref": "#/components/schemas/GcRebuildCostOutput"
              }
            ],
            "description": "Estimated cost to rebuild the artifact via replay."
          },
          "recipe_id": {
            "description": "The recipe-identity key (hex) of the producing execution — the \"what exact program produced this?\" id. `null` when the producing execution predates recipe-identity capture; the provenance record still carries the canonical program.",
            "type": [
              "string",
              "null"
            ]
          },
          "refcount": {
            "description": "How many ledger rows point at these bytes. `1` means Rocky holds a single reference (reclaimable on that axis); `> 1` means the bytes are shared (a branch or replayed run) and must never be evicted.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "run_id": {
            "description": "Run that produced it (joins to the provenance + execution records).",
            "type": "string"
          },
          "size_bytes": {
            "description": "Physical size of the artifact in bytes.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "written_at": {
            "description": "When the artifact was written (RFC 3339). Doubles as the conservative last-access proxy: Rocky has no read-tracking on this adapter, so write-time is the only defensible recency signal.",
            "type": "string"
          }
        },
        "required": [
          "blake3_hash",
          "checks",
          "derivable",
          "model_name",
          "rebuild_cost",
          "refcount",
          "run_id",
          "size_bytes",
          "written_at"
        ],
        "type": "object"
      },
      "GcCheckOutput": {
        "description": "One printed eligibility check inside a [`GcCandidateOutput`].",
        "properties": {
          "check": {
            "description": "Stable check id: `recipe_recorded`, `replayable`, `unreferenced`, `policy_allows`, or `age_threshold`.",
            "type": "string"
          },
          "detail": {
            "description": "Why the check reached that verdict — the auditable justification.",
            "type": "string"
          },
          "passed": {
            "description": "Whether the check passed. A candidate is derivable only when all five are `true`.",
            "type": "boolean"
          }
        },
        "required": [
          "check",
          "detail",
          "passed"
        ],
        "type": "object"
      },
      "GcConfig": {
        "additionalProperties": false,
        "description": "`[gc]` — storage-reclamation settings for `rocky gc` and its review-gated `rocky apply <gc-plan>`.\n\nEviction is **ledger-only**: an approved apply writes the durable tombstone and retires the artifact's ledger row (the eviction of record, always restorable from the recorded recipe). Physical byte-deletion is not performed — reclaiming bytes safely requires a protocol-aware VACUUM (retention windows + TOCTOU-safe deletion), which is future work.",
        "properties": {
          "physical_delete": {
            "default": false,
            "description": "Reserved for a future protocol-aware VACUUM.\n\n`false` (the default): `rocky apply <gc-plan>` tombstones + retires the ledger row and leaves the bytes in place. `true` is currently a **hard error** at apply time — physical reclamation of content-addressed bytes requires a protocol-aware VACUUM (Delta tombstone-retention windows + TOCTOU-safe deletion against concurrent re-adds) that is not yet implemented, so the flag fails loudly rather than silently deleting or silently no-op'ing.",
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "GcEvictedOutput": {
        "description": "One artifact successfully evicted by `rocky apply <gc-plan>` — a tombstone was recorded and the ledger row retired.",
        "properties": {
          "blake3_hash": {
            "type": "string"
          },
          "model_name": {
            "type": "string"
          },
          "physical_reclaimed": {
            "description": "`true` when the bytes were physically deleted through the object-store adapter; `false` when the physical delete was deferred or failed (a safe leaked orphan — the tombstone still records everything `rocky restore <target>` needs to rebuild and verify the artifact).",
            "type": "boolean"
          },
          "physical_status": {
            "description": "Human-readable physical-reclamation outcome (`deleted`, `deferred: …`, or `failed: …`).",
            "type": "string"
          },
          "run_id": {
            "type": "string"
          },
          "size_bytes": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "tombstone_recorded": {
            "description": "Always `true` on this list — the durable restore tombstone was written atomically with the ledger-row retirement before anything else happened.",
            "type": "boolean"
          }
        },
        "required": [
          "blake3_hash",
          "model_name",
          "physical_reclaimed",
          "physical_status",
          "run_id",
          "size_bytes",
          "tombstone_recorded"
        ],
        "type": "object"
      },
      "GcPlanEviction": {
        "description": "One artifact scheduled for eviction inside a persisted `rocky gc` plan ([`GcPlan`]).\n\nCaptures the identity of a single derivable artifact — enough to (a) re-locate its exact ledger row at apply time, (b) re-verify eligibility against the live ledger, and (c) write a complete restore tombstone. The recipe-identity triple is captured at plan time from the producing execution.",
        "properties": {
          "blake3_hash": {
            "description": "Content hash (hex) of the artifact bytes — the eviction unit and the identity a restore would re-compute and compare against.",
            "type": "string"
          },
          "commit_version": {
            "description": "Delta commit version the artifact was attached to.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "env_hash": {
            "description": "Environment hash of the producing execution; `null` when unrecorded.",
            "type": [
              "string",
              "null"
            ]
          },
          "file_path": {
            "description": "Object-store path of the artifact — the byte location a physical reclamation deletes and a restore would re-materialize to.",
            "type": "string"
          },
          "hash_scheme": {
            "description": "Hash-scheme version of the producing execution; `null` when unrecorded.",
            "type": [
              "string",
              "null"
            ]
          },
          "input_hash": {
            "description": "Input-closure hash of the producing execution; `null` when unrecorded.",
            "type": [
              "string",
              "null"
            ]
          },
          "input_proof_class": {
            "description": "Input match-strength (`strong` / `heuristic`); `null` when unrecorded.",
            "type": [
              "string",
              "null"
            ]
          },
          "model_name": {
            "description": "Model that produced the artifact.",
            "type": "string"
          },
          "recipe_hash": {
            "description": "Recipe-identity hash of the producing execution; `null` for a pre-identity build.",
            "type": [
              "string",
              "null"
            ]
          },
          "run_id": {
            "description": "Run that produced it — half of the provenance key a restore would replay from.",
            "type": "string"
          },
          "size_bytes": {
            "description": "Physical size of the artifact in bytes.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "written_at": {
            "description": "When the artifact was written (RFC 3339).",
            "type": "string"
          }
        },
        "required": [
          "blake3_hash",
          "commit_version",
          "file_path",
          "model_name",
          "run_id",
          "size_bytes",
          "written_at"
        ],
        "type": "object"
      },
      "GcPlanOutput": {
        "description": "JSON output of `rocky gc --derivable` (plan mode — no `--dry-run`).\n\nThe plan has been written to the plan store; this reports its id and the scoped proposal. Deletion is symmetric-caution gated: the operator must `rocky review <plan-id> --approve` and then `rocky apply <plan-id>` — the plan itself never deletes.",
        "properties": {
          "command": {
            "type": "string"
          },
          "eviction_count": {
            "description": "Number of derivable artifacts proposed for eviction.",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "evictions": {
            "description": "The proposed evictions.",
            "items": {
              "$ref": "#/components/schemas/GcPlanEviction"
            },
            "type": "array"
          },
          "notes": {
            "description": "Operator caveats (e.g. re-verification at apply, scope). Each eviction records everything `rocky restore <target>` needs to rebuild it hash-exact.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "plan_id": {
            "description": "The persisted plan id — pass to `rocky review` then `rocky apply`.",
            "type": "string"
          },
          "review_required": {
            "description": "Always `true`: a `gc` plan is unconditionally review-gated before apply.",
            "type": "boolean"
          },
          "total_bytes": {
            "description": "Total bytes proposed for reclamation.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "command",
          "eviction_count",
          "evictions",
          "notes",
          "plan_id",
          "review_required",
          "total_bytes",
          "version"
        ],
        "type": "object"
      },
      "GcRebuildCostOutput": {
        "description": "Estimated rebuild cost for a [`GcCandidateOutput`].\n\nDerived from the *recorded* build's metrics via the same cost model `rocky cost` uses — a replay re-runs the recipe, so its original execution's duration and scanned bytes are the honest predictor. Always an estimate ([`Self::estimated`] is always `true`), never a measured rebuild.",
        "properties": {
          "estimated": {
            "description": "Always `true`: this figure is modeled from the recorded build, not measured by re-running it.",
            "type": "boolean"
          },
          "estimated_usd": {
            "description": "Estimated USD to rebuild, priced by `compute_observed_cost_usd` over the recorded metrics; `null` when the config or adapter can't price it (no `rocky.toml`, or a non-billed adapter).",
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          },
          "source_bytes_scanned": {
            "description": "Bytes scanned by the recorded build; `null` when the adapter didn't report a figure (mirrors [`rocky_core::state::ModelExecution::bytes_scanned`]).",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "source_duration_ms": {
            "description": "Duration of the recorded build in milliseconds — the wall-clock a replay would repeat.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "estimated",
          "source_duration_ms"
        ],
        "type": "object"
      },
      "GcRefusedOutput": {
        "description": "One artifact `rocky apply <gc-plan>` **refused** to evict because it was no longer derivable at apply time (the fail-closed re-verification caught ledger drift since plan time — e.g. a new reference appeared).",
        "properties": {
          "blake3_hash": {
            "type": "string"
          },
          "failed_checks": {
            "description": "The eligibility checks as re-evaluated at apply time, so the refusal is auditable rather than asserted.",
            "items": {
              "$ref": "#/components/schemas/GcCheckOutput"
            },
            "type": "array"
          },
          "model_name": {
            "type": "string"
          },
          "reason": {
            "description": "Why the eviction was refused (the failing check summary).",
            "type": "string"
          },
          "run_id": {
            "type": "string"
          },
          "size_bytes": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "blake3_hash",
          "failed_checks",
          "model_name",
          "reason",
          "run_id",
          "size_bytes"
        ],
        "type": "object"
      },
      "GcReportOutput": {
        "description": "JSON output for `rocky gc --derivable --dry-run`.\n\nA read-only inventory of Rocky-managed content-addressed artifacts that are provably rebuildable — *derivable* — and therefore reclamation candidates. Nothing is deleted or planned for deletion: this surface is inventory-only, so the whole product is the report.\n\nThe candidate universe is the content-addressed artifact ledger, grouped by content hash (each distinct hash is one physical artifact; managed bytes count each hash once). An artifact is `derivable` only when **all five** eligibility checks hold — recipe recorded, replayable, unreferenced, policy allows, past the age threshold. Every check fails closed (any doubt keeps the artifact non-derivable) and is reported per candidate, so each verdict is auditable rather than asserted.\n\nScope caveat surfaced in [`Self::notes`]: refcounts see *Rocky's* pointers only. A warehouse-side reference Rocky never recorded (a BI extract, a notebook `SELECT INTO`) is invisible here; the age threshold is the mitigation, and this release measures written-age, not read-recency.",
        "properties": {
          "artifact_count": {
            "description": "Number of distinct content hashes considered.",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "candidates": {
            "description": "One entry per distinct content hash, newest write first.",
            "items": {
              "$ref": "#/components/schemas/GcCandidateOutput"
            },
            "type": "array"
          },
          "command": {
            "type": "string"
          },
          "derivable_bytes": {
            "description": "Physical bytes of the derivable subset (distinct content hashes whose five checks all pass).",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "derivable_count": {
            "description": "How many of those are derivable.",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "derivable_pct": {
            "description": "[`Self::derivable_bytes`] as a percentage of [`Self::managed_bytes`]; `null` when there are no managed bytes (nothing to divide by).",
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          },
          "managed_bytes": {
            "description": "Total physical bytes of Rocky-managed artifacts, counting each distinct content hash once.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "min_age_days": {
            "description": "The minimum written-age (in days) an artifact must reach to pass the age/activity check.",
            "format": "int64",
            "type": "integer"
          },
          "notes": {
            "description": "Report-wide caveats an operator must read before trusting the numbers (Rocky-external references, estimate labeling, unwired policy plane).",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "read_tracking_available": {
            "description": "Whether read-activity tracking backed the age/activity check. Always `false` in this release: the age check is written-age only (see [`GcCheckOutput`]), stated conservatively rather than inferred.",
            "type": "boolean"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "artifact_count",
          "candidates",
          "command",
          "derivable_bytes",
          "derivable_count",
          "managed_bytes",
          "min_age_days",
          "notes",
          "read_tracking_available",
          "version"
        ],
        "type": "object"
      },
      "GovernanceConfig": {
        "additionalProperties": false,
        "description": "Governance settings: auto-creation of catalogs/schemas, tags, isolation, and grants.",
        "properties": {
          "auto_create_catalogs": {
            "default": false,
            "type": "boolean"
          },
          "auto_create_schemas": {
            "default": false,
            "type": "boolean"
          },
          "grants": {
            "default": [],
            "description": "Permissions granted on every managed catalog.",
            "items": {
              "$ref": "#/components/schemas/GrantConfig"
            },
            "type": "array"
          },
          "isolation": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/IsolationConfig"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Workspace isolation settings."
          },
          "schema_grants": {
            "default": [],
            "description": "Permissions granted on every managed schema.",
            "items": {
              "$ref": "#/components/schemas/GrantConfig"
            },
            "type": "array"
          },
          "tag_prefix": {
            "default": null,
            "description": "Optional prefix prepended to auto-generated component tag keys (e.g., `\"ge_\"` turns `client` → `ge_client`). Does not affect keys in `[governance.tags]` — those are used verbatim.",
            "type": [
              "string",
              "null"
            ]
          },
          "tags": {
            "additionalProperties": {
              "type": "string"
            },
            "default": {},
            "description": "Tags applied to every managed catalog and schema.",
            "type": "object"
          }
        },
        "type": "object"
      },
      "GrantConfig": {
        "additionalProperties": false,
        "description": "A permission grant to apply to catalogs or schemas.",
        "properties": {
          "permissions": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "principal": {
            "type": "string"
          }
        },
        "required": [
          "permissions",
          "principal"
        ],
        "type": "object"
      },
      "HealthCheck": {
        "description": "A single health check result.",
        "properties": {
          "details": {
            "items": {
              "maxItems": 2,
              "minItems": 2,
              "prefixItems": [
                {
                  "type": "string"
                },
                {
                  "type": "string"
                }
              ],
              "type": "array"
            },
            "type": "array"
          },
          "duration_ms": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "message": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/HealthStatus"
          }
        },
        "required": [
          "duration_ms",
          "message",
          "name",
          "status"
        ],
        "type": "object"
      },
      "HealthStatus": {
        "description": "Health check status.",
        "enum": [
          "healthy",
          "warning",
          "critical"
        ],
        "type": "string"
      },
      "HistoryOutput": {
        "description": "JSON output for `rocky history` (all runs view).\n\nWhen invoked with `--model <name>`, the dispatch returns `ModelHistoryOutput` instead. The two shapes share the version/command header but differ in their primary collection field, which is why they are separate types rather than one enum.",
        "properties": {
          "command": {
            "type": "string"
          },
          "count": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "runs": {
            "items": {
              "$ref": "#/components/schemas/RunHistoryRecord"
            },
            "type": "array"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "command",
          "count",
          "runs",
          "version"
        ],
        "type": "object"
      },
      "HookConfig": {
        "additionalProperties": false,
        "properties": {
          "command": {
            "description": "Shell command to execute (passed to `sh -c`).",
            "type": "string"
          },
          "env": {
            "additionalProperties": {
              "type": "string"
            },
            "default": {},
            "description": "Extra environment variables passed to the hook process.",
            "type": "object"
          },
          "on_failure": {
            "allOf": [
              {
                "$ref": "#/components/schemas/FailureAction"
              }
            ],
            "default": "warn",
            "description": "What to do if the hook command exits non-zero or times out."
          },
          "timeout_ms": {
            "default": 30000,
            "description": "Maximum time to wait for the hook to complete.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "command"
        ],
        "type": "object"
      },
      "HookConfigOrList": {
        "anyOf": [
          {
            "$ref": "#/components/schemas/HookConfig"
          },
          {
            "items": {
              "$ref": "#/components/schemas/HookConfig"
            },
            "type": "array"
          }
        ],
        "description": "Supports both single-hook and multi-hook syntax per event.\n\nSingle: `[hook.on_pipeline_start]` Multiple: `[[hook.on_after_checks]]`"
      },
      "HookEntry": {
        "properties": {
          "command": {
            "type": "string"
          },
          "env_keys": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "event": {
            "type": "string"
          },
          "on_failure": {
            "type": "string"
          },
          "timeout_ms": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "command",
          "env_keys",
          "event",
          "on_failure",
          "timeout_ms"
        ],
        "type": "object"
      },
      "HooksConfig": {
        "description": "The `[hook]` section from rocky.toml.\n\nEach key is an event name like `on_pipeline_start`. The value can be either a single hook config (TOML table) or an array of hook configs (TOML array of tables).\n\nWebhook entries live under `[hook.webhooks.on_<event>]` or `[[hook.webhooks.on_<event>]]`.",
        "properties": {
          "webhooks": {
            "additionalProperties": {
              "$ref": "#/components/schemas/WebhookConfigOrList"
            },
            "default": {},
            "description": "Webhook configurations keyed by event name.",
            "type": "object"
          }
        },
        "type": "object"
      },
      "HooksListOutput": {
        "description": "JSON output for `rocky hooks list`.",
        "properties": {
          "hooks": {
            "items": {
              "$ref": "#/components/schemas/HookEntry"
            },
            "type": "array"
          },
          "total": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "hooks",
          "total"
        ],
        "type": "object"
      },
      "HooksTestOutput": {
        "description": "JSON output for `rocky hooks test <event>`.",
        "properties": {
          "event": {
            "type": "string"
          },
          "message": {
            "type": [
              "string",
              "null"
            ]
          },
          "result": {
            "description": "Free-form Debug rendering of the hook result, when applicable.",
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "description": "One of \"no_hooks\", \"continue\", \"abort\".",
            "type": "string"
          }
        },
        "required": [
          "event",
          "status"
        ],
        "type": "object"
      },
      "IdempotencyConfig": {
        "additionalProperties": false,
        "description": "Config for `rocky run --idempotency-key` dedup.\n\nAll fields are optional with sensible defaults. Block is present even when the user doesn't set `--idempotency-key`; it's a no-op in that case.",
        "properties": {
          "dedup_on": {
            "allOf": [
              {
                "$ref": "#/components/schemas/DedupPolicy"
              }
            ],
            "default": "success",
            "description": "Which terminal statuses count as \"already processed\" for dedup. See [`DedupPolicy`]. Default [`DedupPolicy::Success`]."
          },
          "in_flight_ttl_hours": {
            "default": 24,
            "description": "Hours after which an `InFlight` entry is treated as a crashed-pod corpse and adopted by a fresh caller. Default 24. Applies only to backends whose in-flight lock does not carry a server-side TTL — Valkey providers set `EX` directly on `SET NX`, so this field is informational for them.",
            "format": "uint32",
            "minimum": 0.0,
            "type": "integer"
          },
          "retention_days": {
            "default": 30,
            "description": "Number of days a `Succeeded` (or `Failed`-under-`any`) stamp is kept before GC. Default 30. GC runs during the state upload sweep.",
            "format": "uint32",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "type": "object"
      },
      "ImportDbtEmission": {
        "description": "Files-on-disk metadata for the runnable Rocky repo emitted by `rocky import-dbt --output-dir <out>`.\n\nConsumers (Dagster, vscode) treat this block as the contract for \"where the importer wrote things.\" Absence of the block on `ImportDbtOutput` means no repo was emitted (e.g. dry-run mode in a follow-up).",
        "properties": {
          "adapter_type": {
            "description": "Rocky adapter type written into `[adapter]` (`duckdb` / `databricks` / …).",
            "type": "string"
          },
          "migration_notes_path": {
            "description": "Path to the generated `MIGRATION-NOTES.md`.",
            "type": "string"
          },
          "models_skipped_count": {
            "description": "Number of dbt model files seen but not translated (failed entries).",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "models_translated_count": {
            "description": "Number of dbt models successfully translated and written under `models/`.",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "original_dbt_adapter_type": {
            "description": "dbt profile `type` value the importer mapped from. Useful when the caller passed `--target-adapter` and we want to surface the original.",
            "type": "string"
          },
          "out_dir": {
            "description": "Resolved output directory.",
            "type": "string"
          },
          "required_env_vars": {
            "description": "Env vars referenced by the emitted `[adapter]` block. Surfaced in `MIGRATION-NOTES.md` under \"Required env vars\".",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "rocky_toml_path": {
            "description": "Path to the generated `rocky.toml`.",
            "type": "string"
          },
          "seeds_copied_count": {
            "description": "Number of files copied from `<dbt_project>/seeds/` into `<out>/seeds/`.",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "adapter_type",
          "migration_notes_path",
          "models_skipped_count",
          "models_translated_count",
          "original_dbt_adapter_type",
          "out_dir",
          "required_env_vars",
          "rocky_toml_path",
          "seeds_copied_count"
        ],
        "type": "object"
      },
      "ImportDbtFailure": {
        "properties": {
          "name": {
            "type": "string"
          },
          "reason": {
            "type": "string"
          }
        },
        "required": [
          "name",
          "reason"
        ],
        "type": "object"
      },
      "ImportDbtHookKind": {
        "description": "Lifecycle hook kind for [`ImportDbtStructuredWarning::DroppedHook`].",
        "enum": [
          "pre",
          "post"
        ],
        "type": "string"
      },
      "ImportDbtOutput": {
        "description": "JSON output for `rocky import-dbt`.\n\nThe `report` field is the per-model migration report from `rocky_compiler::import::report::generate_report`. We hold it as `serde_json::Value` (typed as `any` in JSON Schema) to avoid pulling `JsonSchema` derives all the way through `rocky-compiler::import`. The downstream Pydantic/TS bindings will see it as a free-form object.",
        "properties": {
          "command": {
            "type": "string"
          },
          "constructs_dropped": {
            "default": 0,
            "description": "Number of dbt resources the importer does not translate that were detected and skipped (snapshots, metrics, semantic models, exposures).",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "contracts_dropped": {
            "default": 0,
            "description": "Number of dbt models whose enforced `contract` (column `data_type`s / `constraints`) was dropped on import. Rocky enforces contracts via a `{model}.contract.toml` sidecar the importer does not auto-generate.",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "dbt_version": {
            "type": [
              "string",
              "null"
            ]
          },
          "emission": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/ImportDbtEmission"
              },
              {
                "type": "null"
              }
            ],
            "description": "Metadata about the emitted Rocky repo. Present when `--output-dir` triggered repo emission (the default for `rocky import-dbt`). Absent when emission was skipped or failed before disk writes."
          },
          "failed": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "failed_details": {
            "items": {
              "$ref": "#/components/schemas/ImportDbtFailure"
            },
            "type": "array"
          },
          "import_method": {
            "type": "string"
          },
          "imported": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "imported_models": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "macros_detected": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "project_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "report": {
            "description": "Free-form per-model migration report payload."
          },
          "sources_found": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "sources_mapped": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "structured_warnings": {
            "default": [],
            "description": "Typed structured warnings — payload-carrying variants for dbt config Rocky can't auto-translate (dropped tags, dropped hooks, unresolvable macros, etc.). Coexists with `warning_details` — orchestrators that don't know about this field still see the flat string warnings under `warning_details`.",
            "items": {
              "$ref": "#/components/schemas/ImportDbtStructuredWarning"
            },
            "type": "array"
          },
          "tests_converted": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "tests_converted_custom": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "tests_found": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "tests_skipped": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "unit_tests_converted": {
            "default": 0,
            "description": "Number of dbt unit tests written as Rocky `[[test]]` blocks in per-model sidecar TOML files.",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "unit_tests_found": {
            "default": 0,
            "description": "Total dbt unit-test definitions (`manifest.unit_tests`) seen across all imported manifests.",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "unit_tests_skipped": {
            "default": 0,
            "description": "Number of dbt unit tests dropped — orphan target model, non-`dict` fixture format, or otherwise unsupported shape.",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "version": {
            "type": "string"
          },
          "warning_details": {
            "items": {
              "$ref": "#/components/schemas/ImportDbtWarning"
            },
            "type": "array"
          },
          "warnings": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "command",
          "failed",
          "failed_details",
          "import_method",
          "imported",
          "imported_models",
          "macros_detected",
          "report",
          "sources_found",
          "sources_mapped",
          "tests_converted",
          "tests_converted_custom",
          "tests_found",
          "tests_skipped",
          "version",
          "warning_details",
          "warnings"
        ],
        "type": "object"
      },
      "ImportDbtStructuredWarning": {
        "description": "Typed structured warning for dbt config that Rocky can't translate automatically. Each variant carries the source payload so the downstream UI (Dagster, VS Code) can route specific kinds into per-kind UI affordances without parsing free-form text.",
        "oneOf": [
          {
            "description": "A dbt materialization with no direct Rocky equivalent. The importer fell back to the closest match (typically `full_refresh`).",
            "properties": {
              "action": {
                "type": "string"
              },
              "dbt_materialization": {
                "type": "string"
              },
              "kind": {
                "enum": [
                  "unsupported_materialization"
                ],
                "type": "string"
              },
              "model": {
                "type": "string"
              }
            },
            "required": [
              "action",
              "dbt_materialization",
              "kind",
              "model"
            ],
            "type": "object"
          },
          {
            "description": "`databricks_tags` block dropped — Rocky's `[classification]` block + `rocky-databricks` governance surface covers the same use case but requires manual config.",
            "properties": {
              "kind": {
                "enum": [
                  "dropped_databricks_tags"
                ],
                "type": "string"
              },
              "model": {
                "type": "string"
              },
              "tags": {
                "additionalProperties": {
                  "type": "string"
                },
                "type": "object"
              }
            },
            "required": [
              "kind",
              "model",
              "tags"
            ],
            "type": "object"
          },
          {
            "description": "`pre_hook` or `post_hook` dropped — Rocky supports lifecycle hooks via the `[[hook]]` block in `rocky.toml`.",
            "properties": {
              "hook_kind": {
                "$ref": "#/components/schemas/ImportDbtHookKind"
              },
              "kind": {
                "enum": [
                  "dropped_hook"
                ],
                "type": "string"
              },
              "model": {
                "type": "string"
              },
              "sql": {
                "type": "string"
              }
            },
            "required": [
              "hook_kind",
              "kind",
              "model",
              "sql"
            ],
            "type": "object"
          },
          {
            "description": "`on_schema_change` dropped — Rocky exposes the equivalent via per-pipeline `[drift]` policy.",
            "properties": {
              "dbt_value": {
                "type": "string"
              },
              "kind": {
                "enum": [
                  "dropped_on_schema_change"
                ],
                "type": "string"
              },
              "model": {
                "type": "string"
              },
              "rocky_equivalent": {
                "type": "string"
              }
            },
            "required": [
              "dbt_value",
              "kind",
              "model",
              "rocky_equivalent"
            ],
            "type": "object"
          },
          {
            "description": "A custom Jinja macro call survived `dbt compile` — defined out-of-tree. The user needs to hand-port the macro.",
            "properties": {
              "first_call_site_line": {
                "format": "uint",
                "minimum": 0.0,
                "type": "integer"
              },
              "kind": {
                "enum": [
                  "unresolvable_macro"
                ],
                "type": "string"
              },
              "macro_name": {
                "type": "string"
              },
              "model": {
                "type": "string"
              }
            },
            "required": [
              "first_call_site_line",
              "kind",
              "macro_name",
              "model"
            ],
            "type": "object"
          },
          {
            "description": "A microbatch model is missing the required `event_time` field — the importer fell back to `full_refresh`.",
            "properties": {
              "kind": {
                "enum": [
                  "microbatch_missing_event_time"
                ],
                "type": "string"
              },
              "model": {
                "type": "string"
              }
            },
            "required": [
              "kind",
              "model"
            ],
            "type": "object"
          },
          {
            "description": "A dbt microbatch model was remapped: to an idempotent `merge` (`mapped_to = \"merge\"`) when it has a `unique_key`, or kept append-only (`mapped_to = \"append\"`) when it does not.",
            "properties": {
              "kind": {
                "enum": [
                  "microbatch_mapped"
                ],
                "type": "string"
              },
              "mapped_to": {
                "type": "string"
              },
              "model": {
                "type": "string"
              }
            },
            "required": [
              "kind",
              "mapped_to",
              "model"
            ],
            "type": "object"
          },
          {
            "description": "A dbt construct the importer does not translate was detected and skipped (snapshot, source freshness, grants, meta, metric, semantic model, exposure), surfaced so a migration is never silently lossy.",
            "properties": {
              "construct": {
                "type": "string"
              },
              "detail": {
                "type": "string"
              },
              "kind": {
                "enum": [
                  "dropped_construct"
                ],
                "type": "string"
              },
              "name": {
                "type": "string"
              }
            },
            "required": [
              "construct",
              "detail",
              "kind",
              "name"
            ],
            "type": "object"
          },
          {
            "description": "A dbt model `contract` (`enforced: true`), column `data_type`s, and/or `constraints` were dropped on import. Rocky enforces contracts via a `{model}.contract.toml` sidecar the importer does not auto-generate; the user must hand-author it. Visibility only — no stub generated.",
            "properties": {
              "constraints": {
                "format": "uint",
                "minimum": 0.0,
                "type": "integer"
              },
              "contract_path": {
                "type": "string"
              },
              "kind": {
                "enum": [
                  "dropped_contract"
                ],
                "type": "string"
              },
              "model": {
                "type": "string"
              },
              "typed_columns": {
                "format": "uint",
                "minimum": 0.0,
                "type": "integer"
              }
            },
            "required": [
              "constraints",
              "contract_path",
              "kind",
              "model",
              "typed_columns"
            ],
            "type": "object"
          }
        ]
      },
      "ImportDbtWarning": {
        "properties": {
          "category": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "model": {
            "type": "string"
          },
          "suggestion": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "category",
          "message",
          "model"
        ],
        "type": "object"
      },
      "ImportEntry": {
        "additionalProperties": false,
        "description": "A single imported producer-project snapshot.\n\nDeclared as `[imports.<name>]` in `rocky.toml`. A producer project publishes a serialized snapshot of its compiled project (via `rocky publish-ir`); a consumer project vendors that snapshot file and references it here so `rocky compile` can verify that the columns the consumer reads still exist in the producer's output.\n\n```toml [imports.orders] path = \"vendor/orders\"        # directory holding the vendored snapshots snapshot = \"current.json\"     # the producer's current published snapshot baseline = \"baseline.json\"    # optional prior snapshot used for diffing pin = \"*\"                     # optional recipe-hash pin (\"*\" = trust any) ```\n\n`pin` and `baseline` answer different questions and are complementary, not redundant. `pin` is a whole-project drift tripwire: a concrete recipe hash that, when set, makes `rocky compile` fail (E033) if the vendored snapshot differs at all. `baseline` is the column-level \"before\" image: the only input that lets the breaking-change diff emit the column codes (E030/E031/E032/W030/W031) for changes the consumer actually reads. Leave `pin` at `\"*\"` (or unset) to fail only on changes that touch your reads; set a concrete pin to fail on any drift. Run `rocky imports update` after reviewing a producer change to advance `baseline` to the current snapshot (the explicit accept) — nothing advances it automatically.",
        "properties": {
          "baseline": {
            "default": null,
            "description": "Optional filename of a prior/pinned snapshot used as the diff baseline, relative to `path`. When set, `rocky compile` diffs `baseline` against `snapshot` to detect columns the producer dropped.",
            "type": [
              "string",
              "null"
            ]
          },
          "path": {
            "description": "Directory (relative to `rocky.toml`) holding the vendored snapshot files.",
            "type": "string"
          },
          "pin": {
            "default": null,
            "description": "Optional recipe-hash pin (hex). When set to a concrete hash, the snapshot's recipe hash must match or compilation fails. `\"*\"` (or absent) trusts whatever snapshot is vendored.",
            "type": [
              "string",
              "null"
            ]
          },
          "snapshot": {
            "description": "Filename of the producer's current published snapshot, relative to `path`.",
            "type": "string"
          }
        },
        "required": [
          "path",
          "snapshot"
        ],
        "type": "object"
      },
      "IncrementalityHint": {
        "description": "A hint that a model could benefit from incremental materialization.\n\nReturned by [`infer_incrementality`] when a `full_refresh` model has columns that look monotonic. Surfaced in `rocky compile --output json` as part of each model's detail.",
        "properties": {
          "confidence": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Confidence"
              }
            ],
            "description": "How confident the detector is in this recommendation."
          },
          "is_candidate": {
            "description": "Whether the model is a candidate for incremental materialization.",
            "type": "boolean"
          },
          "recommended_column": {
            "description": "The column recommended as the watermark / timestamp column.",
            "type": "string"
          },
          "signals": {
            "description": "Human-readable reasons why this column was chosen.",
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "confidence",
          "is_candidate",
          "recommended_column",
          "signals"
        ],
        "type": "object"
      },
      "IsolationConfig": {
        "additionalProperties": false,
        "description": "Workspace isolation configuration (Databricks-specific).\n\n```toml [governance.isolation] enabled = true\n\n[[governance.isolation.workspace_ids]] id = 7474656540609532 binding_type = \"READ_WRITE\"\n\n[[governance.isolation.workspace_ids]] id = 7474647537929812 binding_type = \"READ_ONLY\" ```",
        "properties": {
          "enabled": {
            "default": false,
            "type": "boolean"
          },
          "workspace_ids": {
            "default": [],
            "items": {
              "$ref": "#/components/schemas/WorkspaceBindingConfig"
            },
            "type": "array"
          }
        },
        "type": "object"
      },
      "JobKind": {
        "description": "The kind of long-running operation a job wraps.",
        "oneOf": [
          {
            "description": "`rocky run` — materializes tables. Takes the mutation permit.",
            "enum": [
              "run"
            ],
            "type": "string"
          },
          {
            "description": "`rocky plan` — previews and persists a plan. Non-mutating; no permit.",
            "enum": [
              "plan"
            ],
            "type": "string"
          },
          {
            "description": "`rocky apply` — executes a persisted plan. Takes the mutation permit.",
            "enum": [
              "apply"
            ],
            "type": "string"
          }
        ]
      },
      "JobRequest": {
        "description": "Optional JSON body for a job submission. Every field is optional; an empty body runs the verb with its defaults (all pipelines, no filter). Unknown fields are ignored so an embedder on a newer client stays forward-compatible.\n\nDerives `JsonSchema` (crate-visible) so the OpenAPI generator can emit the `POST /api/v1/jobs/{run|plan|apply}` request-body schema from this single source of truth rather than a hand-copied duplicate. It is deliberately not registered in [`super::commands::export_schemas`], so it stays out of the Pydantic/TypeScript codegen cascade.",
        "properties": {
          "filter": {
            "default": null,
            "description": "`--filter <component=value>` for `run`/`plan`.",
            "type": [
              "string",
              "null"
            ]
          },
          "model": {
            "default": null,
            "description": "`--model <name>` for `run`/`plan` (single-model execution).",
            "type": [
              "string",
              "null"
            ]
          },
          "pipeline": {
            "default": null,
            "description": "`--pipeline <name>` for `run`/`plan`.",
            "type": [
              "string",
              "null"
            ]
          },
          "plan_id": {
            "default": null,
            "description": "The positional `<plan_id>` for `apply`.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "type": "object"
      },
      "JobState": {
        "description": "Lifecycle state of a job.",
        "oneOf": [
          {
            "description": "Accepted, not yet started.",
            "enum": [
              "queued"
            ],
            "type": "string"
          },
          {
            "description": "The underlying `rocky <kind>` subprocess is executing.",
            "enum": [
              "running"
            ],
            "type": "string"
          },
          {
            "description": "The subprocess exited 0.",
            "enum": [
              "succeeded"
            ],
            "type": "string"
          },
          {
            "description": "The subprocess exited non-zero, or could not be launched.",
            "enum": [
              "failed"
            ],
            "type": "string"
          }
        ]
      },
      "JobStatus": {
        "description": "Status of a `rocky serve` long-running job (`GET /api/v1/jobs/{id}`).\n\nThe presentation type over `rocky-core`'s `PersistedJob`: it carries the same lifecycle facts with typed [`JobKind`]/[`JobState`] enums for embedder ergonomics, and — once the job is terminal — the canonical `RunOutput` / `PlanOutput` / `ApplyOutput` the underlying subprocess emitted, embedded **verbatim** in [`result`](Self::result). An embedder switches on [`kind`](Self::kind) to know which schema `result` conforms to (the same `run` / `plan` / `apply` schemas the CLI exports).",
        "properties": {
          "error": {
            "description": "Failure detail when [`state`](Self::state) is [`JobState::Failed`], else `null`.",
            "type": [
              "string",
              "null"
            ]
          },
          "finished_at": {
            "description": "When the job reached a terminal state (RFC 3339), or `null` if not yet.",
            "type": [
              "string",
              "null"
            ]
          },
          "job_id": {
            "description": "Opaque job identifier (as returned by `POST /api/v1/jobs/{kind}`).",
            "type": "string"
          },
          "kind": {
            "allOf": [
              {
                "$ref": "#/components/schemas/JobKind"
              }
            ],
            "description": "Which operation the job wraps."
          },
          "principal": {
            "description": "The advisory, spoofable `X-Rocky-Principal` recorded for audit, or `null`. Never an authorization input under the single-shared-secret auth ceiling.",
            "type": [
              "string",
              "null"
            ]
          },
          "result": {
            "description": "The canonical output of the underlying `rocky <kind>` command, embedded verbatim once the job is terminal (`null` while queued/running). Its shape is the `run` / `plan` / `apply` schema selected by [`kind`](Self::kind)."
          },
          "started_at": {
            "description": "When execution started (RFC 3339), or `null` if not yet.",
            "type": [
              "string",
              "null"
            ]
          },
          "state": {
            "allOf": [
              {
                "$ref": "#/components/schemas/JobState"
              }
            ],
            "description": "Current lifecycle state."
          },
          "submitted_at": {
            "description": "When the job was submitted (RFC 3339).",
            "type": "string"
          }
        },
        "required": [
          "job_id",
          "kind",
          "state",
          "submitted_at"
        ],
        "type": "object"
      },
      "LineageColumnChange": {
        "description": "Per-column structural change augmented with downstream consumers.",
        "properties": {
          "change_type": {
            "$ref": "#/components/schemas/ColumnChangeType"
          },
          "column_name": {
            "type": "string"
          },
          "downstream_consumers": {
            "description": "Columns reached by walking the lineage graph downstream from `(model_name, column_name)` on HEAD's compile. Empty when the column no longer exists on HEAD (e.g. for removed columns) or when the trace finds no consumers.",
            "items": {
              "$ref": "#/components/schemas/LineageQualifiedColumn"
            },
            "type": "array"
          },
          "new_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "old_type": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "change_type",
          "column_name"
        ],
        "type": "object"
      },
      "LineageColumnDef": {
        "properties": {
          "data_type": {
            "description": "Inferred column type, rendered as a Rocky type string (e.g. `STRING`, `INT64`, `DECIMAL(10,2)`, `TIMESTAMP`). Omitted when the type could not be inferred — typically a `SELECT *` against an upstream whose schema isn't cached, or a model that did not pass typecheck.",
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "type": "string"
          }
        },
        "required": [
          "name"
        ],
        "type": "object"
      },
      "LineageDiffOutput": {
        "description": "JSON output for `rocky lineage-diff <base_ref>`.\n\nCombines the structural per-column diff produced by `rocky ci-diff` (added/removed/type-changed columns between two git refs) with the downstream blast-radius from `rocky lineage --downstream` (consumers of each changed column, traced through HEAD's semantic graph).\n\nThe markdown payload is rendered server-side and ready to drop into a PR comment — answers \"what does this PR change downstream?\" in one command.\n\nTrace direction is fixed to **downstream from HEAD only** in v1. Removed columns therefore report an empty consumer set (the column no longer exists on HEAD's compile, so its downstream reach can't be walked); the structural diff still surfaces the removal.",
        "properties": {
          "base_ref": {
            "description": "Git ref used as the comparison base (e.g. `main`).",
            "type": "string"
          },
          "command": {
            "type": "string"
          },
          "head_ref": {
            "description": "Git ref for the incoming changes (typically `HEAD`).",
            "type": "string"
          },
          "markdown": {
            "description": "Pre-rendered Markdown suitable for posting as a GitHub PR comment.",
            "type": "string"
          },
          "results": {
            "description": "Per-changed-model entries, each with per-column downstream traces.",
            "items": {
              "$ref": "#/components/schemas/LineageDiffResult"
            },
            "type": "array"
          },
          "summary": {
            "$ref": "#/components/schemas/DiffSummary"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "base_ref",
          "command",
          "head_ref",
          "markdown",
          "results",
          "summary",
          "version"
        ],
        "type": "object"
      },
      "LineageDiffResult": {
        "description": "One model's worth of structural + lineage diff.",
        "properties": {
          "column_changes": {
            "items": {
              "$ref": "#/components/schemas/LineageColumnChange"
            },
            "type": "array"
          },
          "model_name": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/ModelDiffStatus"
          }
        },
        "required": [
          "column_changes",
          "model_name",
          "status"
        ],
        "type": "object"
      },
      "LineageEdgeRecord": {
        "properties": {
          "source": {
            "$ref": "#/components/schemas/LineageQualifiedColumn"
          },
          "target": {
            "$ref": "#/components/schemas/LineageQualifiedColumn"
          },
          "transform": {
            "description": "Transform kind: \"direct\", \"cast\", \"expression\", etc. Stringified from `rocky_sql::lineage::TransformKind` to avoid pulling schemars into rocky-sql.",
            "type": "string"
          }
        },
        "required": [
          "source",
          "target",
          "transform"
        ],
        "type": "object"
      },
      "LineageNodeDef": {
        "description": "Per-node metadata for the lineage graph.\n\nOne entry per distinct model referenced by `LineageOutput.edges` (plus the focal model). Carries cluster keys consumers can use to group nodes without having to parse the qualified node name. Either optional field may be absent — `target_schema` is `None` for nodes that aren't project models, and `source_id` is `None` for nodes that are project models (i.e. the two fields are mutually exclusive in practice).",
        "properties": {
          "model": {
            "description": "Qualified node identifier as it appears in `edges[].source.model` and `edges[].target.model`. Stable across the rest of the payload.",
            "type": "string"
          },
          "source_id": {
            "description": "Source identifier for nodes that represent an external source (i.e. a referenced table outside the project model set). The value mirrors how the SQL referenced the source so consumers can key on it directly. Omitted for project models.",
            "type": [
              "string",
              "null"
            ]
          },
          "target_schema": {
            "description": "Resolved target schema for project models, from the model's declared target config. Omitted for external sources and any node Rocky couldn't resolve to a project model.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "model"
        ],
        "type": "object"
      },
      "LineageOutput": {
        "description": "JSON output for `rocky lineage <model>` (model lineage shape).\n\nWhen invoked with `--column <col>` (or `model.column` syntax), the dispatch returns `ColumnLineageOutput` instead — the two shapes share version/command/model headers but otherwise differ enough to keep separate.",
        "properties": {
          "columns": {
            "items": {
              "$ref": "#/components/schemas/LineageColumnDef"
            },
            "type": "array"
          },
          "command": {
            "type": "string"
          },
          "downstream": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "edges": {
            "items": {
              "$ref": "#/components/schemas/LineageEdgeRecord"
            },
            "type": "array"
          },
          "model": {
            "type": "string"
          },
          "nodes": {
            "description": "Per-node metadata for every model referenced by this lineage view (the focal model plus each endpoint of `edges`). Lets consumers (e.g. the VS Code subgraph drill-in) cluster nodes by their resolved target schema or source identity instead of parsing the qualified node name. Empty when no nodes were resolved; older JSON payloads cached locally may omit the field entirely.",
            "items": {
              "$ref": "#/components/schemas/LineageNodeDef"
            },
            "type": "array"
          },
          "upstream": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "columns",
          "command",
          "downstream",
          "edges",
          "model",
          "upstream",
          "version"
        ],
        "type": "object"
      },
      "LineageQualifiedColumn": {
        "properties": {
          "column": {
            "type": "string"
          },
          "model": {
            "type": "string"
          }
        },
        "required": [
          "column",
          "model"
        ],
        "type": "object"
      },
      "LoadFileFormat": {
        "description": "File format for load pipelines, parsed from TOML.\n\nMirrors `rocky_adapter_sdk::FileFormat` but lives in rocky-core to avoid a hard dependency from config parsing to the adapter SDK.",
        "enum": [
          "csv",
          "parquet",
          "json_lines"
        ],
        "type": "string"
      },
      "LoadFileOutput": {
        "description": "A single file result within `LoadOutput`.",
        "properties": {
          "bytes_read": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "contract": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/ContractResult"
              },
              {
                "type": "null"
              }
            ],
            "description": "Result of the data-contract gate, when the load pipeline declares a contract. `None` for ungated loads. On a failed gate the data was not promoted to the target; the violations explain why."
          },
          "duration_ms": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "file": {
            "type": "string"
          },
          "rows_loaded": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "target": {
            "type": "string"
          }
        },
        "required": [
          "bytes_read",
          "duration_ms",
          "file",
          "rows_loaded",
          "target"
        ],
        "type": "object"
      },
      "LoadOptionsConfig": {
        "additionalProperties": false,
        "description": "Load-specific options parsed from TOML.",
        "properties": {
          "batch_size": {
            "default": 10000,
            "description": "Number of rows per INSERT batch. Default: 10,000.",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "create_table": {
            "default": true,
            "description": "Create the target table if it does not exist. Default: true.",
            "type": "boolean"
          },
          "csv_delimiter": {
            "default": ",",
            "description": "CSV-specific: field delimiter character. Default: `,`.",
            "type": "string"
          },
          "csv_has_header": {
            "default": true,
            "description": "CSV-specific: whether the first row is a header. Default: true.",
            "type": "boolean"
          },
          "truncate_first": {
            "default": false,
            "description": "Truncate the target table before loading. Default: false.",
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "LoadOutput": {
        "description": "JSON output for `rocky load`.",
        "properties": {
          "command": {
            "type": "string"
          },
          "duration_ms": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "files": {
            "items": {
              "$ref": "#/components/schemas/LoadFileOutput"
            },
            "type": "array"
          },
          "files_failed": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "files_loaded": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "format": {
            "type": "string"
          },
          "source_dir": {
            "type": "string"
          },
          "total_bytes": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "total_rows": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "command",
          "duration_ms",
          "files",
          "files_failed",
          "files_loaded",
          "format",
          "source_dir",
          "total_bytes",
          "total_rows",
          "version"
        ],
        "type": "object"
      },
      "LoadPipelineConfig": {
        "description": "Load pipeline configuration -- ingest files (CSV, Parquet, JSONL) into a warehouse.\n\nLoads data from a local directory into a target catalog/schema. The format can be auto-detected from file extensions or set explicitly.\n\n```toml [pipeline.load_data] type = \"load\" source_dir = \"data/\" format = \"csv\"\n\n[pipeline.load_data.target] adapter = \"prod\" catalog = \"warehouse\" schema = \"raw\"\n\n[pipeline.load_data.options] batch_size = 5000 create_table = true truncate_first = false csv_delimiter = \",\" csv_has_header = true ```",
        "properties": {
          "checks": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ChecksConfig"
              }
            ],
            "default": {
              "anomaly_threshold_pct": 0.0,
              "assertions": [],
              "column_match": false,
              "custom": [],
              "enabled": false,
              "fail_on_error": false,
              "freshness": null,
              "null_rate": null,
              "quarantine": null,
              "row_count": false
            },
            "description": "Data quality checks run after loading."
          },
          "contract": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/ContractConfig"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Optional data contract that gates the load. When set, each file is loaded into a staging table, validated against the contract, and promoted to the target only if validation passes. On failure the staging table is dropped and the target is left untouched."
          },
          "depends_on": {
            "default": [],
            "description": "Pipeline dependencies for chaining.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "execution": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ExecutionConfig"
              }
            ],
            "default": {
              "concurrency": "adaptive",
              "error_rate_abort_pct": 50,
              "fail_fast": false,
              "table_retries": 1
            },
            "description": "Execution settings (concurrency, retries, etc.)."
          },
          "format": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/LoadFileFormat"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Explicit file format. When omitted, auto-detected from file extensions."
          },
          "options": {
            "allOf": [
              {
                "$ref": "#/components/schemas/LoadOptionsConfig"
              }
            ],
            "default": {
              "batch_size": 10000,
              "create_table": true,
              "csv_delimiter": ",",
              "csv_has_header": true,
              "truncate_first": false
            },
            "description": "Load options (batch size, create/truncate behavior, CSV settings)."
          },
          "source_dir": {
            "description": "Directory or glob pattern for source files, relative to the config file.",
            "type": "string"
          },
          "target": {
            "allOf": [
              {
                "$ref": "#/components/schemas/LoadTargetConfig"
              }
            ],
            "description": "Target table location."
          }
        },
        "required": [
          "source_dir",
          "target"
        ],
        "type": "object"
      },
      "LoadTargetConfig": {
        "additionalProperties": false,
        "description": "Target configuration for load pipelines.",
        "properties": {
          "adapter": {
            "default": "default",
            "description": "Name of the adapter to use (references a key in `[adapter.*]`).",
            "type": "string"
          },
          "catalog": {
            "description": "Target catalog name.",
            "type": "string"
          },
          "governance": {
            "allOf": [
              {
                "$ref": "#/components/schemas/GovernanceConfig"
              }
            ],
            "default": {
              "auto_create_catalogs": false,
              "auto_create_schemas": false,
              "grants": [],
              "isolation": null,
              "schema_grants": [],
              "tag_prefix": null,
              "tags": {}
            },
            "description": "Governance settings for the target."
          },
          "schema": {
            "description": "Target schema name.",
            "type": "string"
          },
          "table": {
            "default": null,
            "description": "Optional explicit table name. When omitted, derives from the file name (e.g., `orders.csv` -> table `orders`).",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "catalog",
          "schema"
        ],
        "type": "object"
      },
      "MaskAction": {
        "description": "Masking-policy application row in `PlanOutput.mask_actions`.",
        "properties": {
          "column": {
            "description": "Column name the mask will be applied to.",
            "type": "string"
          },
          "model": {
            "description": "Model name the action targets.",
            "type": "string"
          },
          "resolved_strategy": {
            "description": "Wire name of the resolved strategy (`\"hash\"`, `\"redact\"`, `\"partial\"`, `\"none\"`). Matches `MaskStrategy::as_str`.",
            "type": "string"
          },
          "tag": {
            "description": "Classification tag the mask is resolved against.",
            "type": "string"
          }
        },
        "required": [
          "column",
          "model",
          "resolved_strategy",
          "tag"
        ],
        "type": "object"
      },
      "MaskEntry": {
        "anyOf": [
          {
            "allOf": [
              {
                "$ref": "#/components/schemas/MaskStrategy"
              }
            ],
            "description": "Default masking strategy for a classification tag."
          },
          {
            "additionalProperties": {
              "$ref": "#/components/schemas/MaskStrategy"
            },
            "description": "Per-env override map: `[mask.<env>]` section with `<classification> = \"<strategy>\"` pairs.",
            "type": "object"
          }
        ],
        "description": "One entry in the top-level `[mask]` block. A scalar value (`pii = \"hash\"`) binds a classification tag to a default masking strategy; a nested table (`[mask.prod] pii = \"none\"`) overrides strategies for a specific environment.\n\nSerde deserializes the outer `[mask]` map as `BTreeMap<String, MaskEntry>`; scalars are tried first, then the nested table shape. Unknown strategy spellings (e.g., `\"mask\"`) hard-fail at config load time — Rocky never silently accepts something it can't emit SQL for."
      },
      "MaskStrategy": {
        "description": "How a column is masked at apply time.\n\nSerialized in lowercase to match the TOML spelling (`\"hash\"`, `\"redact\"`, `\"partial\"`, `\"none\"`).",
        "oneOf": [
          {
            "description": "SHA-256 hex digest of the column value. Deterministic, one-way.",
            "enum": [
              "hash"
            ],
            "type": "string"
          },
          {
            "description": "Replace the column value with the literal string `'***'`.",
            "enum": [
              "redact"
            ],
            "type": "string"
          },
          {
            "description": "Keep the first and last two characters; replace the middle with `***`. Short values (<5 chars) are fully replaced with `'***'`.",
            "enum": [
              "partial"
            ],
            "type": "string"
          },
          {
            "description": "Explicit identity — no masking applied. Useful as a per-env override to \"unmask\" a column that defaults to masked at the workspace level.",
            "enum": [
              "none"
            ],
            "type": "string"
          }
        ]
      },
      "MaterializationMetadata": {
        "properties": {
          "column_count": {
            "description": "Number of columns in the materialized table's typed schema. Populated for derived models (where the compiler resolved a typed schema); `None` for source-replication tables that inherit their schema from upstream.",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "compile_time_ms": {
            "description": "Compile time in milliseconds for the model that produced this materialization. Populated only for derived models. Mirrors the relevant slice of `PhaseTimings.total_ms`.",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "sql_hash": {
            "description": "Short hash (16 hex chars) of the generated SQL string. Lets orchestrators detect \"what changed?\" between runs without diffing full SQL bodies. Computed via `xxh3_64` of the canonical SQL the engine sent to the warehouse.",
            "type": [
              "string",
              "null"
            ]
          },
          "strategy": {
            "type": "string"
          },
          "target_table_full_name": {
            "description": "Fully-qualified target table identifier in `catalog.schema.table` format. Useful for click-through links to the warehouse UI from the Dagster asset detail page. Always set when the materialization targets a known table.",
            "type": [
              "string",
              "null"
            ]
          },
          "watermark": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "strategy"
        ],
        "type": "object"
      },
      "MaterializationOutput": {
        "properties": {
          "asset_key": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "attempts": {
            "description": "The classified-retry attempt trail for this materialization: one [`AttemptRecord`](rocky_core::state::AttemptRecord) per try. Empty (and omitted from JSON) for a clean first-try success — the run loop only stamps a trail once a retry actually happened, so a non-retried build's output stays byte-identical to before this layer shipped. [`RunOutput::to_run_record`] copies this verbatim onto the persisted `ModelExecution.attempts` — one attempt type on both the wire and the state record.",
            "items": {
              "$ref": "#/components/schemas/AttemptRecord"
            },
            "type": "array"
          },
          "bytes_scanned": {
            "description": "Adapter-reported bytes figure used for cost accounting, summed across all statements executed for this materialization. This is the *billing-relevant* number per adapter, not literal scan volume — so anyone comparing this to a warehouse console should know which column lines up.\n\n- **BigQuery:** `statistics.query.totalBytesBilled` (with the 10 MB per-query minimum already applied) — matches the BigQuery console's \"Bytes billed\" field, **not** \"Bytes processed\". Fed straight into [`rocky_core::cost::compute_observed_cost_usd`] to produce `cost_usd`. - **Databricks:** when populated, byte count from the statement-execution manifest (`total_byte_count`); `None` today until the manifest plumbing lands. - **Snowflake:** `None` — deferred by design (QUERY_HISTORY round-trip cost; Snowflake cost is duration × DBU, not bytes-driven). - **DuckDB:** `None` — no billed-bytes concept.",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "bytes_written": {
            "description": "Adapter-reported bytes-written figure, summed across all statements. Currently `None` on every adapter — BigQuery doesn't expose a bytes-written figure for query jobs, and the Databricks / Snowflake paths haven't wired it yet. Reserved so a future release can populate it without a schema break.",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "cost_usd": {
            "description": "Observed dollar cost of this materialization, computed post-hoc from the adapter-appropriate formula (duration × DBU rate for Databricks/Snowflake, bytes × $/TB for BigQuery, zero for DuckDB). `None` when the warehouse type isn't billed or the adapter didn't report the inputs the formula needs. Populated by the run finalizer via `rocky_core::cost::compute_observed_cost_usd`.",
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          },
          "duration_ms": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "job_ids": {
            "description": "Warehouse-side job identifiers for the SQL statements rocky issued to materialize this output. Populated for adapters whose REST API surfaces a job reference per statement (BigQuery today via `jobReference.jobId`).\n\nUseful for cross-checking rocky's reported figures against the warehouse's own statistics — e.g., feeding a job ID into `bq show -j <id>` and comparing `totalBytesBilled` to [`Self::bytes_scanned`]. Empty `Vec` for adapters that don't surface a job concept (DuckDB) or haven't wired it yet (Databricks, Snowflake).",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "metadata": {
            "$ref": "#/components/schemas/MaterializationMetadata"
          },
          "partition": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/PartitionInfo"
              },
              {
                "type": "null"
              }
            ],
            "description": "Partition window this materialization targeted, present only when the model's strategy is `time_interval`. `None` for unpartitioned strategies (full_refresh, incremental, merge)."
          },
          "rows_copied": {
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "started_at": {
            "description": "Wall-clock timestamp captured at the moment the engine began executing this model. Used by `RunOutput::to_run_record` to build accurate per-model windows on the persisted `ModelExecution` — replaces the prior lossy reconstruction (`finished_at - duration`) that mis-ordered parallel runs. `finished_at` is derived as `started_at + duration_ms`; keeping one source of truth avoids drift between the two.",
            "format": "date-time",
            "type": "string"
          },
          "tenant": {
            "description": "Tenant this materialization is attributed to, taken from the discover-time schema-pattern `{tenant}` component. Present only for replication pipelines whose schema pattern declares a `{tenant}` placeholder; transformation / `time_interval` models and non-tenant patterns leave it `None`. Carried onto the persisted `rocky_core::state::ModelExecution` by [`RunOutput::to_run_record`] so `rocky cost --by tenant` can roll per-model cost up to a tenant dimension. Omitted from JSON when `None` so non-tenant outputs stay byte-identical.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "asset_key",
          "duration_ms",
          "metadata",
          "started_at"
        ],
        "type": "object"
      },
      "MetaOutput": {
        "description": "Feature-detection payload for `GET /api/v1/meta`.\n\nFingerprints the running engine + bound config so an embedder can pin against a build without version-sniffing. Every field is computed at request time — none are baked literals — so `state_schema_version`, `schemas_hash`, and `config_hash` track the live engine and the on-disk config even across a long-running sidecar.",
        "properties": {
          "capabilities": {
            "description": "Feature/capability tokens the build advertises.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "config_hash": {
            "description": "Fingerprint of the resolved `rocky.toml` (contents + path) this sidecar bound, recomputed per request. `null` when no config resolved.",
            "type": [
              "string",
              "null"
            ]
          },
          "engine_version": {
            "description": "Engine release (`CARGO_PKG_VERSION`).",
            "type": "string"
          },
          "routes": {
            "description": "The `/api/v1` routes this build serves.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "schemas_hash": {
            "description": "Fingerprint of the exported JSON-schema set (the `schemas/*.schema.json` contract). Derived from the live registered schemas, so it moves on every codegen; embedders detect an output-shape change without version-sniffing.",
            "type": "string"
          },
          "state_schema_version": {
            "description": "Current state-store schema version, read from the engine's `current_schema_version()` getter at request time (never a literal).",
            "format": "uint32",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "capabilities",
          "engine_version",
          "routes",
          "schemas_hash",
          "state_schema_version"
        ],
        "type": "object"
      },
      "MetadataColumnConfig": {
        "additionalProperties": false,
        "description": "A metadata column added during replication (e.g., `_loaded_by`).",
        "properties": {
          "name": {
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "value": {
            "type": "string"
          }
        },
        "required": [
          "name",
          "type",
          "value"
        ],
        "type": "object"
      },
      "MetricsAlert": {
        "properties": {
          "column": {
            "type": [
              "string",
              "null"
            ]
          },
          "message": {
            "type": "string"
          },
          "run_id": {
            "type": "string"
          },
          "severity": {
            "type": "string"
          },
          "type": {
            "type": "string"
          }
        },
        "required": [
          "message",
          "run_id",
          "severity",
          "type"
        ],
        "type": "object"
      },
      "MetricsOutput": {
        "description": "JSON output for `rocky metrics`.\n\nAll optional fields are present-or-absent depending on the flags (`--trend`, `--alerts`, `--column`). The empty case (no snapshots available) sets `message` and leaves the collections empty.",
        "properties": {
          "alerts": {
            "items": {
              "$ref": "#/components/schemas/MetricsAlert"
            },
            "type": "array"
          },
          "column": {
            "type": [
              "string",
              "null"
            ]
          },
          "column_trend": {
            "items": {
              "$ref": "#/components/schemas/ColumnTrendPoint"
            },
            "type": "array"
          },
          "command": {
            "type": "string"
          },
          "count": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "message": {
            "type": [
              "string",
              "null"
            ]
          },
          "model": {
            "type": "string"
          },
          "snapshots": {
            "items": {
              "$ref": "#/components/schemas/MetricsSnapshotEntry"
            },
            "type": "array"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "command",
          "count",
          "model",
          "snapshots",
          "version"
        ],
        "type": "object"
      },
      "MetricsSnapshot": {
        "description": "Serializable metrics summary for JSON output.",
        "properties": {
          "anomalies_detected": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "error_rate_pct": {
            "format": "double",
            "type": "number"
          },
          "query_duration_max_ms": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "query_duration_p50_ms": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "query_duration_p95_ms": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "retries_attempted": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "retries_succeeded": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "statements_executed": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "table_duration_max_ms": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "table_duration_p50_ms": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "table_duration_p95_ms": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "tables_failed": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "tables_processed": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "anomalies_detected",
          "error_rate_pct",
          "query_duration_max_ms",
          "query_duration_p50_ms",
          "query_duration_p95_ms",
          "retries_attempted",
          "retries_succeeded",
          "statements_executed",
          "table_duration_max_ms",
          "table_duration_p50_ms",
          "table_duration_p95_ms",
          "tables_failed",
          "tables_processed"
        ],
        "type": "object"
      },
      "MetricsSnapshotEntry": {
        "properties": {
          "freshness_lag_seconds": {
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "null_rates": {
            "additionalProperties": {
              "format": "double",
              "type": "number"
            },
            "type": "object"
          },
          "row_count": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "run_id": {
            "type": "string"
          },
          "timestamp": {
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "null_rates",
          "row_count",
          "run_id",
          "timestamp"
        ],
        "type": "object"
      },
      "MismatchKind": {
        "description": "Type of row mismatch.",
        "oneOf": [
          {
            "description": "Row exists in expected but not in actual.",
            "enum": [
              "missing"
            ],
            "type": "string"
          },
          {
            "description": "Row exists in actual but not in expected.",
            "enum": [
              "extra"
            ],
            "type": "string"
          },
          {
            "description": "Row exists in both but values differ.",
            "enum": [
              "value_diff"
            ],
            "type": "string"
          }
        ]
      },
      "ModelCostEntry": {
        "description": "Per-model cost attribution entry inside [`RunCostSummary`].",
        "properties": {
          "asset_key": {
            "description": "Asset key path for the model this entry covers.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "cost_usd": {
            "description": "Observed cost for this materialization. `None` when the adapter type isn't billed or the formula couldn't be computed.",
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          },
          "duration_ms": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "asset_key",
          "duration_ms"
        ],
        "type": "object"
      },
      "ModelDecision": {
        "description": "The per-model build decision the engine made this run — what the skip-gate and content-addressed reuse machinery actually decided.\n\nReporting-only: this never changes build behavior, it surfaces a decision that today only reaches the run log. Populated on [`RunOutput::model_decisions`] only when the `--skip-unchanged` gate is active or `[reuse]` is enabled; a default run (both off) emits an empty list and the field is omitted.",
        "oneOf": [
          {
            "description": "The model was (re-)materialized this run — its SQL ran (or its content-addressed write executed).",
            "enum": [
              "build"
            ],
            "type": "string"
          },
          {
            "description": "The `--skip-unchanged` gate proved the model's logic and upstream data unchanged since the last successful build and skipped re-materialization.",
            "enum": [
              "skip"
            ],
            "type": "string"
          },
          {
            "description": "A content-addressed model pointed-to a prior run's already-written parquet via a zero-copy commit instead of executing its SQL.",
            "enum": [
              "reused"
            ],
            "type": "string"
          }
        ]
      },
      "ModelDecisionOutput": {
        "description": "One per-model decision entry on [`RunOutput::model_decisions`].\n\nSurfaces the skip/build/reuse verdict the engine reached for a single transformation model, plus a short human-readable reason that mirrors the gate's actual decision point (never re-derived — threaded from the evaluation result). Orchestrators (Dagster) and the VS Code extension use it to explain *why* a model ran, was skipped, or was reused.",
        "properties": {
          "decision": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ModelDecision"
              }
            ],
            "description": "What the engine decided for this model."
          },
          "model": {
            "description": "The model's name (matches the model entry in the project DAG).",
            "type": "string"
          },
          "reason": {
            "description": "Short human-readable justification reflecting the exact decision the gate made (e.g. \"logic and upstream data unchanged since last build\", \"not skip-eligible\", \"upstream data may have changed\", \"reused prior run's bytes (strong proof)\").",
            "type": "string"
          }
        },
        "required": [
          "decision",
          "model",
          "reason"
        ],
        "type": "object"
      },
      "ModelDetail": {
        "description": "Per-model summary projected from `rocky_core::models::ModelConfig`.\n\nIntentionally excludes fields that change run-to-run (timings, diagnostics) — those live on the run-level outputs. This is the stable, declarative shape of one compiled model, suitable for orchestrators that build asset definitions ahead of execution.",
        "properties": {
          "contract_source": {
            "description": "How the contract was discovered: `\"auto\"` (sibling `.contract.toml`), `\"explicit\"` (via `--contracts` flag), or absent when no contract.",
            "type": [
              "string",
              "null"
            ]
          },
          "cost_hint": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/CostHint"
              },
              {
                "type": "null"
              }
            ],
            "description": "DAG-propagated cost estimate for this model. Populated at compile time using heuristic cardinality propagation (no warehouse round-trip). `None` when no upstream table statistics are available."
          },
          "depends_on": {
            "description": "Names of models this model directly depends on. Derived from the model's TOML `depends_on` list or auto-resolved from SQL table references. Empty when the model has no upstream dependencies.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "freshness": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/ModelFreshnessConfig"
              },
              {
                "type": "null"
              }
            ],
            "description": "Per-model freshness expectation, when declared in the model's TOML frontmatter. `None` when not configured."
          },
          "incrementality_hint": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/IncrementalityHint"
              },
              {
                "type": "null"
              }
            ],
            "description": "When the model uses `full_refresh` and has columns that look monotonic, this hint suggests switching to incremental materialization. `None` when the model already uses an incremental strategy or no candidates were found."
          },
          "name": {
            "type": "string"
          },
          "strategy": {
            "allOf": [
              {
                "$ref": "#/components/schemas/StrategyConfig"
              }
            ],
            "description": "Materialization strategy as the wire-shape `StrategyConfig` (`{\"type\": \"...\", ...}`)."
          },
          "tags": {
            "additionalProperties": {
              "type": "string"
            },
            "description": "Model-level governance tags — the model's own `[tags]` block merged over any config-group `[tags]` baseline (sidecar > group). Free-form key/value strings describing the model as a whole (`domain`, `tier`, `owner`, …). `dagster-rocky` projects these onto the derived asset's Dagster tags, so a governed fan-out declared once on a config group is visible to the orchestrator end-to-end. Empty when none are declared.",
            "type": "object"
          },
          "target": {
            "allOf": [
              {
                "$ref": "#/components/schemas/TargetConfig"
              }
            ],
            "description": "Target table coordinates."
          }
        },
        "required": [
          "name",
          "strategy",
          "target"
        ],
        "type": "object"
      },
      "ModelDiffStatus": {
        "description": "Status of a single model in the diff.",
        "oneOf": [
          {
            "description": "Model output is identical across both sides.",
            "enum": [
              "unchanged"
            ],
            "type": "string"
          },
          {
            "description": "Model exists on both sides but output differs.",
            "enum": [
              "modified"
            ],
            "type": "string"
          },
          {
            "description": "Model exists only on the incoming (PR) side.",
            "enum": [
              "added"
            ],
            "type": "string"
          },
          {
            "description": "Model exists only on the base (target branch) side.",
            "enum": [
              "removed"
            ],
            "type": "string"
          }
        ]
      },
      "ModelEstimate": {
        "description": "Cost estimate for a single model.",
        "properties": {
          "estimated_bytes_scanned": {
            "description": "Estimated bytes that would be scanned (if available).",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "estimated_cost_usd": {
            "description": "Estimated compute cost in USD (derived from warehouse pricing model).",
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          },
          "estimated_rows": {
            "description": "Estimated number of rows (if available).",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "model_name": {
            "type": "string"
          },
          "raw_explain": {
            "description": "Raw EXPLAIN output from the warehouse.",
            "type": "string"
          }
        },
        "required": [
          "model_name",
          "raw_explain"
        ],
        "type": "object"
      },
      "ModelExecutionRecord": {
        "description": "One model execution from the state store, mirroring `rocky_core::state::ModelExecution`.",
        "properties": {
          "duration_ms": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "recipe_identity": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/RecipeIdentityView"
              },
              {
                "type": "null"
              }
            ],
            "description": "The recipe-identity triple recorded for this execution, when present. See [`RecipeIdentityView`]."
          },
          "rows_affected": {
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "sql_hash": {
            "type": "string"
          },
          "started_at": {
            "format": "date-time",
            "type": "string"
          },
          "status": {
            "type": "string"
          }
        },
        "required": [
          "duration_ms",
          "sql_hash",
          "started_at",
          "status"
        ],
        "type": "object"
      },
      "ModelFreshnessConfig": {
        "description": "Per-model freshness configuration.\n\nDeclares the maximum allowed lag between successive materializations of the model plus the optional timestamp column used by the runtime freshness check.\n\nThe compiler does not enforce the TTL — it's metadata consumed by downstream observability tooling (`dagster-rocky` `FreshnessPolicy`, `rocky doctor --freshness`, etc.). The compiler does however soft-warn (W005) when a model has at least one temporal output column but no `freshness` declaration anywhere in scope (per-model or project-level default).",
        "properties": {
          "max_lag_seconds": {
            "description": "Maximum lag in seconds before the model is considered stale.\n\nAccepts both `max_lag_seconds` (legacy field name, preserved for existing sidecar fixtures + dagster Pydantic + VS Code bindings) and `expected_lag_seconds` (the documented public-facing name matching dbt freshness + SQLMesh defaults). Both deserialize to the same field; the serialized name stays `max_lag_seconds` so existing JSON/codegen consumers keep working unchanged.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "severity": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/TestSeverity"
              },
              {
                "type": "null"
              }
            ],
            "description": "Severity reported when the freshness check trips. Default `warning` keeps the runtime check non-blocking — switch to `error` to fail the pipeline on stale data."
          },
          "time_column": {
            "description": "Optional timestamp column used to evaluate freshness at runtime (`MAX(time_column) < NOW() - INTERVAL max_lag_seconds`). When unset the runtime falls back to the model's last-materialization timestamp from the state store.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "max_lag_seconds"
        ],
        "type": "object"
      },
      "ModelHistoryOutput": {
        "description": "JSON output for `rocky history --model <name>`.",
        "properties": {
          "command": {
            "type": "string"
          },
          "count": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "executions": {
            "items": {
              "$ref": "#/components/schemas/ModelExecutionRecord"
            },
            "type": "array"
          },
          "model": {
            "type": "string"
          },
          "rolling_stats": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/RollingStats"
              },
              {
                "type": "null"
              }
            ],
            "description": "Rolling statistics over the most recent N successful executions. Present only when `--rolling-stats` is passed."
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "command",
          "count",
          "executions",
          "model",
          "version"
        ],
        "type": "object"
      },
      "ModelRetentionStatus": {
        "description": "Per-model retention declaration + (eventually) warehouse-observed value.\n\n- `configured_days` is `None` when the model's sidecar has no `retention` key. - `warehouse_days` is populated by the (v2) `--drift` probe via `SHOW TBLPROPERTIES` on Databricks or `SHOW PARAMETERS ... FOR TABLE` on Snowflake. Always `None` in v1. - `in_sync` is `true` iff `configured_days == warehouse_days`, or both are `None`. A model with no configured retention is never flagged as out-of-sync.",
        "properties": {
          "configured_days": {
            "format": "uint32",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "in_sync": {
            "type": "boolean"
          },
          "model": {
            "type": "string"
          },
          "warehouse_days": {
            "format": "uint32",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "required": [
          "in_sync",
          "model"
        ],
        "type": "object"
      },
      "ModelTestResult": {
        "description": "One per-model outcome from the local model-execution test.\n\n`status` is `\"pass\"` or `\"fail\"`. `error` is set only when `status = \"fail\"`. Mirrors `rocky_engine::test_runner::ModelTestResult` with the status flattened to a string so consumers (Pydantic, TypeScript) get a stable, JSON-Schema-friendly shape.",
        "properties": {
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "model": {
            "type": "string"
          },
          "status": {
            "description": "`\"pass\"` or `\"fail\"`.",
            "type": "string"
          }
        },
        "required": [
          "model",
          "status"
        ],
        "type": "object"
      },
      "ModelValidationOutput": {
        "properties": {
          "compile_ok": {
            "type": "boolean"
          },
          "contracts_generated": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "dbt_description": {
            "type": [
              "string",
              "null"
            ]
          },
          "model": {
            "type": "string"
          },
          "present_in_dbt": {
            "type": "boolean"
          },
          "present_in_rocky": {
            "type": "boolean"
          },
          "rocky_intent": {
            "type": [
              "string",
              "null"
            ]
          },
          "test_count": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "warnings": {
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "compile_ok",
          "contracts_generated",
          "model",
          "present_in_dbt",
          "present_in_rocky",
          "test_count",
          "warnings"
        ],
        "type": "object"
      },
      "NamedStatement": {
        "description": "Named SQL statement (purpose + sql), reused by compact and archive.\n\n`sql` is optional in preparation for the v2 persisted plan format (Cluster 3 C — \"SQL as `.o` files\"). Today's emission path always populates `Some(string)` for both stdout JSON and the persisted plan; the v2 format will write `None` and rely on the apply path to regenerate SQL from the plan's typed IR.",
        "properties": {
          "purpose": {
            "type": "string"
          },
          "sql": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "purpose"
        ],
        "type": "object"
      },
      "NullRateConfig": {
        "additionalProperties": false,
        "description": "Null rate check configuration: columns to check, threshold, and sample size.",
        "properties": {
          "columns": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "sample_percent": {
            "default": 10,
            "format": "uint32",
            "minimum": 0.0,
            "type": "integer"
          },
          "severity": {
            "allOf": [
              {
                "$ref": "#/components/schemas/TestSeverity"
              }
            ],
            "default": "error",
            "description": "Severity reported when a column's null rate exceeds the threshold."
          },
          "threshold": {
            "format": "double",
            "type": "number"
          }
        },
        "required": [
          "columns",
          "threshold"
        ],
        "type": "object"
      },
      "OnCollision": {
        "description": "Action when `rocky discover` detects a cross-source collision — the same external object id mapped to more than one target path.",
        "oneOf": [
          {
            "description": "Collision detection disabled (default — fully backwards compatible).",
            "enum": [
              "off"
            ],
            "type": "string"
          },
          {
            "description": "Report collisions in `collision_candidates` and emit an event, but do not fail the discover.",
            "enum": [
              "warn"
            ],
            "type": "string"
          },
          {
            "description": "Report collisions and fail the discover, so a colliding onboard cannot silently create a catalog/table.",
            "enum": [
              "error"
            ],
            "type": "string"
          }
        ]
      },
      "OptimizeOutput": {
        "description": "JSON output for `rocky optimize`.\n\n`recommendations` is empty when no run history exists; `message` is populated in that case to explain why.",
        "properties": {
          "command": {
            "type": "string"
          },
          "incrementality_note": {
            "description": "Hint pointing users to `rocky compile --output json` for inferred incrementality recommendations on `full_refresh` models. Only populated when the optimize command detects that compile-time analysis could provide additional optimization opportunities.",
            "type": [
              "string",
              "null"
            ]
          },
          "message": {
            "type": [
              "string",
              "null"
            ]
          },
          "recommendations": {
            "items": {
              "$ref": "#/components/schemas/OptimizeRecommendation"
            },
            "type": "array"
          },
          "total_models_analyzed": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "command",
          "recommendations",
          "total_models_analyzed",
          "version"
        ],
        "type": "object"
      },
      "OptimizeRecommendation": {
        "description": "One materialization-strategy recommendation. Mirrors `rocky_core::optimize::MaterializationCost` but lives in the CLI crate so we don't have to derive JsonSchema across the workspace.",
        "properties": {
          "compute_cost_per_run": {
            "description": "Projected per-run compute cost (USD). Populated from `rocky_core::optimize::MaterializationCost::compute_cost_per_run` so Dagster's `checks.py` can surface it as metadata without re-deriving from config.",
            "format": "double",
            "type": "number"
          },
          "current_strategy": {
            "type": "string"
          },
          "downstream_references": {
            "description": "How many downstream models depend on this one. Drives whether the recommendation favours table materialisation (many consumers) vs a view.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "estimated_monthly_savings": {
            "format": "double",
            "type": "number"
          },
          "model_name": {
            "type": "string"
          },
          "reasoning": {
            "type": "string"
          },
          "recommended_strategy": {
            "type": "string"
          },
          "storage_cost_per_month": {
            "description": "Projected monthly storage cost (USD).",
            "format": "double",
            "type": "number"
          }
        },
        "required": [
          "compute_cost_per_run",
          "current_strategy",
          "downstream_references",
          "estimated_monthly_savings",
          "model_name",
          "reasoning",
          "recommended_strategy",
          "storage_cost_per_month"
        ],
        "type": "object"
      },
      "OverrideWarningOutput": {
        "description": "Soft warning surfaced on [`RunOutput::override_warnings`] when an override rule matched no tables this run.\n\nDistinct kinds let orchestrators (Dagster) branch on cause without parsing free-form messages.",
        "properties": {
          "connector": {
            "description": "Echo of `match.connector` from the rule, for cross-reference.",
            "type": [
              "string",
              "null"
            ]
          },
          "kind": {
            "description": "Coarse kind: `\"zero_match\"` (rule matched no pair at all) or `\"connector_match_empty\"` (the connector half of the match resolved nothing).",
            "type": "string"
          },
          "message": {
            "description": "Human-readable explanation, for logs and Dagster UI rendering.",
            "type": "string"
          },
          "rule_index": {
            "description": "Position of the rule in `[[table_overrides]]`, 0-based — same index the validator's error messages use.",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "table": {
            "description": "Echo of `match.table` from the rule, for cross-reference.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "kind",
          "message",
          "rule_index"
        ],
        "type": "object"
      },
      "PartitionInfo": {
        "description": "Partition window information for a single `time_interval` materialization.\n\n`key` is the canonical Rocky partition key (e.g. `\"2026-04-07\"` for daily; `\"2026-04-07T13\"` for hourly). `start` / `end` are the half-open `[start, end)` window the SQL substituted for `@start_date` / `@end_date`. `batched_with` lists any additional partition keys that were merged into this batch when `batch_size > 1` — empty for the default one-partition-per-statement case.",
        "properties": {
          "batched_with": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "end": {
            "format": "date-time",
            "type": "string"
          },
          "key": {
            "type": "string"
          },
          "start": {
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "end",
          "key",
          "start"
        ],
        "type": "object"
      },
      "PartitionShapeOutput": {
        "description": "Partition shape metadata for time-interval nodes.",
        "properties": {
          "first_partition": {
            "description": "First partition key, if declared in the model sidecar.",
            "type": [
              "string",
              "null"
            ]
          },
          "granularity": {
            "description": "Time granularity: `\"daily\"`, `\"hourly\"`, `\"monthly\"`, `\"yearly\"`.",
            "type": "string"
          }
        },
        "required": [
          "granularity"
        ],
        "type": "object"
      },
      "PartitionSummary": {
        "description": "Per-model summary of `time_interval` partition execution.\n\nOne entry per partitioned model touched by the run. Lets dagster-rocky (and other orchestrators) display per-model partition stats without re-counting the per-partition `MaterializationOutput.partition` entries.",
        "properties": {
          "model": {
            "type": "string"
          },
          "partitions_failed": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "partitions_planned": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "partitions_skipped": {
            "description": "Partitions that were already `Computed` in the state store and skipped by the runtime (currently always 0; reserved for the `--missing` change-detection optimization).",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "partitions_succeeded": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "model",
          "partitions_failed",
          "partitions_planned",
          "partitions_succeeded"
        ],
        "type": "object"
      },
      "PerModelBudgetBreachOutput": {
        "description": "One per-model budget breach surfaced on [`PreviewCostOutput::projected_per_model_budget_breaches`].\n\nSame fields as [`BudgetBreachOutput`] plus `model_name`. Kept as a distinct type so the run-level `budget_breaches` shape stays untouched; downstream consumers iterate the two surfaces with separate code paths or merge them deliberately.",
        "properties": {
          "actual": {
            "format": "double",
            "type": "number"
          },
          "limit": {
            "description": "Effective limit applied — i.e. the field-inheritance result of per-model overrides composed against the project-level `[budget]`. Surfaced as the resolved value rather than the raw override so PR readers see the limit they actually crossed.",
            "format": "double",
            "type": "number"
          },
          "limit_type": {
            "description": "Which limit was breached: `\"max_usd\"`, `\"max_duration_ms\"`, or `\"max_bytes_scanned\"`.",
            "type": "string"
          },
          "model_name": {
            "description": "Name of the model whose resolved per-model budget was breached.",
            "type": "string"
          },
          "on_breach": {
            "description": "Resolved on-breach action for this model: `\"warn\"` or `\"error\"`. When `\"error\"`, this breach would fail the run if merged. Defaults from the project-level config when the sidecar omits `on_breach`.",
            "type": "string"
          }
        },
        "required": [
          "actual",
          "limit",
          "limit_type",
          "model_name",
          "on_breach"
        ],
        "type": "object"
      },
      "PerModelCostHistorical": {
        "description": "A single model's cost attribution inside [`CostOutput`].\n\nDistinct from [`ModelCostEntry`] (which lives on [`RunCostSummary`]) because the historical surface carries the richer fields the state store actually persists: model name (not asset-key vector), row/byte counts, and the recorded per-model status.",
        "properties": {
          "bytes_scanned": {
            "description": "Adapter-reported bytes figure used for cost accounting. This is the *billing-relevant* number per adapter, not literal scan volume:\n\n- **BigQuery:** `totalBytesBilled` — includes the 10 MB per-query minimum floor; matches the BigQuery console's \"Bytes billed\" field, **not** \"Bytes processed\". - **Databricks:** when populated, byte count from the statement-execution manifest (`total_byte_count`); `None` today until the manifest plumbing lands. - **Snowflake:** `None` — deferred by design (QUERY_HISTORY round-trip cost; Snowflake cost is duration × DBU, not bytes-driven). - **DuckDB:** `None` — no billed-bytes concept.",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "bytes_written": {
            "description": "Adapter-reported bytes-written figure. Currently `None` on every adapter — BigQuery doesn't expose a bytes-written figure for query jobs, and the Databricks / Snowflake paths haven't wired it yet.",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "cost_usd": {
            "description": "Observed cost for this execution. `None` when the adapter isn't a billed warehouse, the config couldn't be loaded, or the formula inputs were unavailable (e.g. BigQuery without `bytes_scanned`).",
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          },
          "duration_ms": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "model_name": {
            "type": "string"
          },
          "rows_affected": {
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "status": {
            "type": "string"
          },
          "tenant": {
            "description": "Tenant this execution was attributed to, read back from the persisted `rocky_core::state::ModelExecution::tenant`. Present only for replication executions whose source schema declared a `{tenant}` component; `None` (and omitted from JSON) otherwise.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "duration_ms",
          "model_name",
          "status"
        ],
        "type": "object"
      },
      "PermissionSummary": {
        "properties": {
          "catalogs_created": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "grants_added": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "grants_revoked": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "schemas_created": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "catalogs_created",
          "grants_added",
          "grants_revoked",
          "schemas_created"
        ],
        "type": "object"
      },
      "PhaseTimings": {
        "description": "Wall-clock duration of each compile phase.\n\nSurfaced in `CompileResult` so callers (CLI, Dagster, LSP) can attribute compile time to a specific stage instead of treating compile as a black box. `typecheck_join_keys_ms` is a sub-timer of `typecheck_ms` so we can decide whether the cross-model join-key check needs further optimization without re-instrumenting later.",
        "properties": {
          "contracts_ms": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "project_load_ms": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "semantic_graph_ms": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "total_ms": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "typecheck_join_keys_ms": {
            "description": "Portion of `typecheck_ms` spent inside `check_join_keys`.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "typecheck_ms": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "contracts_ms",
          "project_load_ms",
          "semantic_graph_ms",
          "total_ms",
          "typecheck_join_keys_ms",
          "typecheck_ms"
        ],
        "type": "object"
      },
      "PipelineConfig": {
        "anyOf": [
          {
            "allOf": [
              {
                "$ref": "#/components/schemas/ReplicationPipelineConfig"
              },
              {
                "properties": {
                  "type": {
                    "enum": [
                      "replication"
                    ],
                    "type": "string"
                  }
                },
                "type": "object"
              }
            ]
          },
          {
            "allOf": [
              {
                "$ref": "#/components/schemas/TransformationPipelineConfig"
              },
              {
                "properties": {
                  "type": {
                    "enum": [
                      "transformation"
                    ],
                    "type": "string"
                  }
                },
                "required": [
                  "type"
                ],
                "type": "object"
              }
            ]
          },
          {
            "allOf": [
              {
                "$ref": "#/components/schemas/QualityPipelineConfig"
              },
              {
                "properties": {
                  "type": {
                    "enum": [
                      "quality"
                    ],
                    "type": "string"
                  }
                },
                "required": [
                  "type"
                ],
                "type": "object"
              }
            ]
          },
          {
            "allOf": [
              {
                "$ref": "#/components/schemas/SnapshotPipelineConfig"
              },
              {
                "properties": {
                  "type": {
                    "enum": [
                      "snapshot"
                    ],
                    "type": "string"
                  }
                },
                "required": [
                  "type"
                ],
                "type": "object"
              }
            ]
          },
          {
            "allOf": [
              {
                "$ref": "#/components/schemas/LoadPipelineConfig"
              },
              {
                "properties": {
                  "type": {
                    "enum": [
                      "load"
                    ],
                    "type": "string"
                  }
                },
                "required": [
                  "type"
                ],
                "type": "object"
              }
            ]
          }
        ],
        "description": "Pipeline configuration. The `type` field selects one of five variants — `replication` (default when omitted), `transformation`, `quality`, `snapshot`, or `load`. Each variant has its own field set; see the per-variant subschemas in `definitions`."
      },
      "PipelineSourceConfig": {
        "additionalProperties": false,
        "description": "Pipeline source configuration.",
        "properties": {
          "adapter": {
            "default": "default",
            "description": "Name of the adapter to use (references a key in `[adapter.*]`). Defaults to `\"default\"` — resolved against the adapter map in [`normalize_rocky_config`].",
            "type": "string"
          },
          "catalog": {
            "description": "Source catalog name.",
            "type": [
              "string",
              "null"
            ]
          },
          "discovery": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/DiscoveryConfig"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Optional discovery configuration."
          },
          "schema_pattern": {
            "allOf": [
              {
                "$ref": "#/components/schemas/SchemaPatternConfig"
              }
            ],
            "description": "Schema pattern for parsing source schema names."
          }
        },
        "required": [
          "schema_pattern"
        ],
        "type": "object"
      },
      "PipelineTargetConfig": {
        "additionalProperties": false,
        "description": "Pipeline target configuration.",
        "properties": {
          "adapter": {
            "default": "default",
            "description": "Name of the adapter to use (references a key in `[adapter.*]`). Defaults to `\"default\"`.",
            "type": "string"
          },
          "catalog_template": {
            "description": "Template for the target catalog name.",
            "type": "string"
          },
          "governance": {
            "allOf": [
              {
                "$ref": "#/components/schemas/GovernanceConfig"
              }
            ],
            "default": {
              "auto_create_catalogs": false,
              "auto_create_schemas": false,
              "grants": [],
              "isolation": null,
              "schema_grants": [],
              "tag_prefix": null,
              "tags": {}
            },
            "description": "Governance settings for the target."
          },
          "schema_template": {
            "description": "Template for the target schema name.",
            "type": "string"
          },
          "separator": {
            "default": null,
            "description": "Separator for joining variadic components in target templates.\n\nWhen a source pattern uses `\"__\"` as its separator but the target templates use `\"_\"` between placeholders, set this to `\"_\"` so that multi-valued components (e.g., `{hierarchies}`) are joined correctly.\n\nDefaults to the source pattern separator when not set.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "catalog_template",
          "schema_template"
        ],
        "type": "object"
      },
      "PipelinesFieldSchema": {
        "anyOf": [
          {
            "allOf": [
              {
                "$ref": "#/components/schemas/PipelineConfig"
              }
            ],
            "description": "Flat `[pipeline]` block (single pipeline, auto-named `default`)."
          },
          {
            "additionalProperties": {
              "$ref": "#/components/schemas/PipelineConfig"
            },
            "description": "Named `[pipeline.<name>]` blocks.",
            "type": "object"
          }
        ],
        "description": "Schema-only helper mirroring [`AdaptersFieldSchema`] for `[pipeline.*]`.\n\nThe flat-pipeline shorthand exists in [`normalize_toml_shorthands`] but is unused across all committed POCs; including it in the schema is defensive — a user typing `[pipeline] source = ...` shouldn't see a false IDE error. The pipeline payload references [`PipelineConfig`] directly, whose hand-written `JsonSchema` impl emits an `anyOf` of the five pipeline variants."
      },
      "PlanOutput": {
        "description": "JSON output for `rocky plan`.\n\n`statements` enumerates the warehouse SQL Rocky would emit. The three `*_actions` collections are a parallel view of the control-plane governance work `rocky run` would do *after* a successful DAG — the classification / masking / retention reconcile pass. These never show up as SQL; they fire through [`rocky_core::traits::GovernanceAdapter`] methods (e.g. `apply_column_tags`, `apply_masking_policy`, `apply_retention_policy`). Projects without any `[classification]`, `[mask]`, or `retention` config get empty lists — the fields `skip_serializing_if = Vec::is_empty`, so JSON consumers written against the earlier shape are byte-stable.\n\n## Plan-persistence additions\n\n`plan_id`, `plan_kind`, `created_at`, `models`, and `execution_layers` are additive — all have `skip_serializing_if` so existing fixtures and consumers that do not include a compile step remain byte-stable. When `rocky plan` runs against a project with a `models/` directory, these fields are populated and the plan is persisted to `.rocky/plans/`.",
        "properties": {
          "breaking_verdict": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/SemanticPlanVerdict"
              },
              {
                "type": "null"
              }
            ],
            "description": "Semantic change-impact verdict from the typed-IR breaking-change classifier, surfaced as decision-support at plan time. Present only when `--semantic` ran with a usable baseline. See [`SemanticPlanVerdict`] — note its `caveat`: the classifier diffs OUTPUT SCHEMA only and is blind to schema-stable value changes."
          },
          "budget_diagnostics": {
            "description": "Per-model E027 budget-exceeded diagnostics produced at plan time using real catalog statistics. Severity reflects the model's `on_breach` policy (`\"warn\"` or `\"error\"`). Empty when no ceiling was exceeded or no real stats were available.",
            "items": {
              "$ref": "#/components/schemas/Diagnostic"
            },
            "type": "array"
          },
          "classification_actions": {
            "description": "Column-tag applications the governance reconciler would issue via `apply_column_tags`. One row per `(model, column, tag)` triple declared in a model sidecar's `[classification]` block.",
            "items": {
              "$ref": "#/components/schemas/ClassificationAction"
            },
            "type": "array"
          },
          "command": {
            "type": "string"
          },
          "created_at": {
            "description": "UTC timestamp when the plan was persisted. Present when `plan_id` is present.",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "env": {
            "description": "Environment name passed via `--env <name>`. Propagates to `mask_actions` so the preview resolves `[mask.<env>]` overrides on top of the workspace `[mask]` defaults. `None` when the flag is absent — preview resolves against defaults only.",
            "type": [
              "string",
              "null"
            ]
          },
          "execution_layers": {
            "description": "Execution layers (topological order) as a list-of-lists of model names. Models within a layer can execute concurrently. Informational — re-derived at apply time. Empty for replication-only plans.",
            "items": {
              "items": {
                "type": "string"
              },
              "type": "array"
            },
            "type": "array"
          },
          "filter": {
            "type": "string"
          },
          "has_budget_errors": {
            "description": "`true` when at least one entry in `budget_diagnostics` has error-level severity (`on_breach = \"error\"`). Callers can use this flag to fail a pipeline-as-code check without inspecting individual diagnostic severities.",
            "type": "boolean"
          },
          "mask_actions": {
            "description": "Masking-policy applications the governance reconciler would issue via `apply_masking_policy`. One row per `(model, column, tag)` where the tag resolves to a strategy for the active env. Unresolved tags are intentionally omitted — `rocky compliance` is the diagnostic surface for that gap.",
            "items": {
              "$ref": "#/components/schemas/MaskAction"
            },
            "type": "array"
          },
          "models": {
            "description": "Qualified model names that will be executed by `rocky apply`. Empty for replication-only plans. Informational — re-derived at apply time.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "plan_id": {
            "description": "Full 64-char blake3 plan identifier. Present when the plan was persisted to `.rocky/plans/<plan_id>.json`. Apply with: `rocky apply <plan_id>`.",
            "type": [
              "string",
              "null"
            ]
          },
          "plan_kind": {
            "description": "Plan kind wire name (`\"run\"`). Present when `plan_id` is present.",
            "type": [
              "string",
              "null"
            ]
          },
          "retention_actions": {
            "description": "Retention-policy applications the governance reconciler would issue via `apply_retention_policy`. One row per model whose sidecar declares `retention = \"<N>[dy]\"`. `warehouse_preview` shows the warehouse-native SQL that the current adapter would compile the policy to (Databricks / Snowflake); `None` on warehouses without a first-class retention knob.",
            "items": {
              "$ref": "#/components/schemas/RetentionAction"
            },
            "type": "array"
          },
          "statements": {
            "items": {
              "$ref": "#/components/schemas/PlannedStatement"
            },
            "type": "array"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "command",
          "filter",
          "statements",
          "version"
        ],
        "type": "object"
      },
      "PlannedStatement": {
        "properties": {
          "purpose": {
            "type": "string"
          },
          "sql": {
            "type": "string"
          },
          "target": {
            "type": "string"
          }
        },
        "required": [
          "purpose",
          "sql",
          "target"
        ],
        "type": "object"
      },
      "PolicyCapability": {
        "description": "The class of action a policy rule governs.\n\n`read` is always allowed (short-circuit). The mutating verbs (`propose` … `quarantine`) name coarse operations; `schema_change.additive`, `schema_change.breaking`, and `value_change` are *refinements* of the apply/promote verbs — a rule naming a bare verb (`apply`/`promote`) matches those refinements too, but a rule naming a refinement matches only that exact refinement.",
        "oneOf": [
          {
            "description": "Read model output / metadata. Always allowed.",
            "enum": [
              "read"
            ],
            "type": "string"
          },
          {
            "description": "Draft a plan for later review.",
            "enum": [
              "propose"
            ],
            "type": "string"
          },
          {
            "description": "Apply a plan against the warehouse.",
            "enum": [
              "apply"
            ],
            "type": "string"
          },
          {
            "description": "Promote a branch / environment.",
            "enum": [
              "promote"
            ],
            "type": "string"
          },
          {
            "description": "Backfill historical partitions.",
            "enum": [
              "backfill"
            ],
            "type": "string"
          },
          {
            "description": "Garbage-collect / reclaim storage.",
            "enum": [
              "gc"
            ],
            "type": "string"
          },
          {
            "description": "Restore a gc-evicted artifact from its tombstone (rebuild + verify).",
            "enum": [
              "restore"
            ],
            "type": "string"
          },
          {
            "description": "Retry a failed run.",
            "enum": [
              "retry"
            ],
            "type": "string"
          },
          {
            "description": "Quarantine a partition / model.",
            "enum": [
              "quarantine"
            ],
            "type": "string"
          },
          {
            "description": "An additive schema change (refinement of apply/promote).",
            "enum": [
              "schema_change.additive"
            ],
            "type": "string"
          },
          {
            "description": "A breaking schema change (refinement of apply/promote).",
            "enum": [
              "schema_change.breaking"
            ],
            "type": "string"
          },
          {
            "description": "A value-only data change (refinement of apply/promote).",
            "enum": [
              "value_change"
            ],
            "type": "string"
          }
        ]
      },
      "PolicyCheckOutput": {
        "description": "JSON output for `rocky policy check`.\n\nAn explain surface: reports the *base* effect the agent policy plane resolves for a `(principal, capability, model)` triple, the winning rule (if any), and why. The check itself is read-only and static; the same evaluator is enforced at `apply`, `promote`, and the MCP write tools, where active freezes and autonomy-budget burn are also projected and can only tighten the effect reported here.",
        "properties": {
          "capability": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PolicyCapability"
              }
            ],
            "description": "The capability that was checked."
          },
          "command": {
            "type": "string"
          },
          "effect": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PolicyEffect"
              }
            ],
            "description": "Resolved effect: `allow`, `require_review`, or `deny`."
          },
          "matched_rule": {
            "description": "Zero-based index of the winning rule in `[[policy.rules]]`, or `null` when the decision came from a short-circuit (`read`) or the default posture (no rule matched).",
            "format": "uint",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "model": {
            "description": "The model that was checked.",
            "type": "string"
          },
          "model_attributes": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PolicyModelAttributes"
              }
            ],
            "description": "The compiled model attributes the matcher read."
          },
          "principal": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PolicyPrincipal"
              }
            ],
            "description": "The principal that was checked (`human` / `agent`)."
          },
          "reason": {
            "description": "Human-readable explanation of how the effect was reached.",
            "type": "string"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "capability",
          "command",
          "effect",
          "model",
          "model_attributes",
          "principal",
          "reason",
          "version"
        ],
        "type": "object"
      },
      "PolicyConfig": {
        "additionalProperties": false,
        "description": "The `[policy]` block: agent-authority policy for this project.",
        "properties": {
          "default_agent_effect": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PolicyEffect"
              }
            ],
            "default": "require_review",
            "description": "Effect for an `agent` on a mutating capability when no rule matches. Defaults to `require_review` (the safe posture)."
          },
          "rules": {
            "default": [],
            "description": "Ordered list of rules. Evaluated as a set (order only breaks final ties); see [`crate::policy::evaluate`].",
            "items": {
              "$ref": "#/components/schemas/PolicyRule"
            },
            "type": "array"
          },
          "tests": {
            "description": "Scenario assertions run by `rocky policy test`. Each pins the effect the evaluator must resolve for a `(principal, capability, target)` triple, so a policy edit that would silently open a hole fails CI. Never read by any enforcement path — purely a testing surface.",
            "items": {
              "$ref": "#/components/schemas/PolicyTest"
            },
            "type": "array"
          },
          "version": {
            "description": "Schema version. Must be `1`.",
            "format": "uint32",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "version"
        ],
        "type": "object"
      },
      "PolicyEffect": {
        "description": "The verdict a policy rule (or the default posture) yields.\n\nOrdered by restrictiveness for incomparable-rule tie-breaking: `Deny` is a hard override (handled separately), and among non-deny verdicts `RequireReview` is more restrictive than `Allow`.",
        "oneOf": [
          {
            "description": "Permit the action outright.",
            "enum": [
              "allow"
            ],
            "type": "string"
          },
          {
            "description": "Permit only after human review. The safe default posture.",
            "enum": [
              "require_review"
            ],
            "type": "string"
          },
          {
            "description": "Refuse the action. A hard override — no `allow` overturns it.",
            "enum": [
              "deny"
            ],
            "type": "string"
          }
        ]
      },
      "PolicyFreezeEntry": {
        "description": "One principal's freeze/unfreeze record inside a [`PolicyFreezeOutput`].",
        "properties": {
          "decision_ref": {
            "description": "Composite ledger key citation (`\"{timestamp}|{plan_id}|{scope}\"`).",
            "type": "string"
          },
          "effect": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PolicyEffect"
              }
            ],
            "description": "The recorded effect (`deny` for a freeze, `allow` for an unfreeze)."
          },
          "plan_id": {
            "description": "The freeze decision's `plan_id`.",
            "type": "string"
          },
          "principal": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PolicyPrincipal"
              }
            ],
            "description": "The principal whose actions were frozen/unfrozen."
          },
          "reason": {
            "description": "Human-readable description of the freeze/unfreeze.",
            "type": "string"
          }
        },
        "required": [
          "decision_ref",
          "effect",
          "plan_id",
          "principal",
          "reason"
        ],
        "type": "object"
      },
      "PolicyFreezeOutput": {
        "description": "JSON output for `rocky policy freeze` / `rocky policy unfreeze` — the kill switch.\n\nA freeze records a decision entry per matched principal into the existing policy-decision ledger; at the enforcement seam an active freeze forces `deny`. `unfreeze` records a superseding entry that lifts it. No config file is rewritten and no new table is created.",
        "properties": {
          "command": {
            "description": "`policy_freeze` or `policy_unfreeze`.",
            "type": "string"
          },
          "entries": {
            "description": "One entry per affected principal.",
            "items": {
              "$ref": "#/components/schemas/PolicyFreezeEntry"
            },
            "type": "array"
          },
          "lifted": {
            "description": "`true` when this call lifted a freeze rather than establishing one.",
            "type": "boolean"
          },
          "notes": {
            "description": "Advisory notes about enforcement status — e.g. a warning that the freeze was recorded but is inert because the project has no `[policy]` block. Empty when the freeze is enforceable.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "recorded_at": {
            "description": "RFC 3339 wall clock when the freeze/unfreeze was recorded.",
            "type": "string"
          },
          "scope": {
            "description": "The scope selector the freeze targets (`any`, `layer=…`, `tag=…`, …).",
            "type": "string"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "command",
          "entries",
          "lifted",
          "recorded_at",
          "scope",
          "version"
        ],
        "type": "object"
      },
      "PolicyModelAttributes": {
        "description": "The compiled attributes of the checked model, echoed back so the explain output is self-contained.",
        "properties": {
          "classifications": {
            "description": "Distinct column-classification values present on the model.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "contracted": {
            "description": "Whether the model sits behind a contract (best-effort: a sibling `.contract.toml` exists).",
            "type": "boolean"
          },
          "downstreams": {
            "description": "Direct downstream-consumer count (models that `depends_on` this one). Informational — the `max_downstreams` ceiling reads [`Self::reachable_downstreams`].",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "layer": {
            "description": "Medallion/semantic layer (the model's `layer` tag), if any.",
            "type": [
              "string",
              "null"
            ]
          },
          "reachable_downstreams": {
            "description": "Transitive downstream reachability — the full blast radius (direct + indirect), excluding the model itself. This is what a rule's `max_downstreams` ceiling is compared against. `null` when the blast radius could not be computed (the ceiling then fails closed).",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "tags": {
            "additionalProperties": {
              "type": "string"
            },
            "description": "Model-level governance tags.",
            "type": "object"
          }
        },
        "required": [
          "classifications",
          "contracted",
          "downstreams",
          "tags"
        ],
        "type": "object"
      },
      "PolicyPrincipal": {
        "description": "Who is attempting an action.\n\n`agent` is a non-human caller (an AI harness authoring, applying, or remediating). `human` is a person. In v0 the principal is supplied explicitly (`rocky policy check --principal …`); auto-detection is a later phase.",
        "oneOf": [
          {
            "description": "A person.",
            "enum": [
              "human"
            ],
            "type": "string"
          },
          {
            "description": "A non-human caller (AI agent / automation).",
            "enum": [
              "agent"
            ],
            "type": "string"
          }
        ]
      },
      "PolicyRule": {
        "additionalProperties": false,
        "description": "One `[[policy.rules]]` entry: `(principal, capability, scope) → effect`.",
        "properties": {
          "autonomy_budget": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/AutonomyBudget"
              },
              {
                "type": "null"
              }
            ],
            "description": "Optional autonomy budget: a rolling failure ceiling that degrades this rule to `require_review` when its verify-after failures exhaust it. See [`AutonomyBudget`]. Absent ⇒ the rule's effect is never budget-degraded."
          },
          "capability": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PolicyCapability"
              }
            ],
            "description": "Which capability this rule governs."
          },
          "conditions": {
            "description": "Optional v1 conditional refinements not yet promoted to typed fields. **Parsed and ignored** — captured as opaque JSON so a config authored against a later version still loads."
          },
          "effect": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PolicyEffect"
              }
            ],
            "description": "The verdict when this rule matches."
          },
          "principal": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PolicyPrincipal"
              }
            ],
            "description": "Who this rule applies to."
          },
          "scope": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PolicyScope"
              }
            ],
            "default": {
              "any": false,
              "classifications": [],
              "exclude_classifications": [],
              "models": [],
              "tags": {}
            },
            "description": "The models this rule covers. Defaults to the empty scope, which is *not* `any` — an all-default scope with no `any = true` matches nothing and is rejected at validation."
          },
          "verify_after": {
            "description": "Post-apply verification: named checks that must pass after a mutation governed by this rule lands. Once the apply's run completes, each named check is confirmed against the run's executed checks; a failing **or absent** named check halts the apply (fail closed) and raises an alert. Auto-rollback runs only where a rollback substrate exists; today none does, so a failure is halt-only and the mutation stands until a human reverts it. Empty ⇒ no post-apply gate.",
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "capability",
          "effect",
          "principal"
        ],
        "type": "object"
      },
      "PolicyScope": {
        "additionalProperties": false,
        "description": "Scope of a policy rule — the AND of every present predicate. A model matches the scope only when it satisfies *all* set keys.\n\n`any = true` is the empty scope (matches every model, zero constraints) and is mutually exclusive with every other key.",
        "properties": {
          "any": {
            "default": false,
            "description": "Match every model. Mutually exclusive with all other keys; carries zero constraints, so any rule with a real predicate outranks it.",
            "type": "boolean"
          },
          "classifications": {
            "default": [],
            "description": "Classification guard (positive). Satisfied when the model has at least one column classified with any listed value (e.g. `[\"pii\"]`).",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "contracted": {
            "description": "Contract-boundary guard. Satisfied when the model's contracted status equals this value. (v0 reads contracted status best-effort from a sibling `.contract.toml`; see [`crate::policy`].)",
            "type": [
              "boolean",
              "null"
            ]
          },
          "exclude_classifications": {
            "default": [],
            "description": "Classification guard (negative). Satisfied when the model has *no* column classified with any listed value — e.g. `exclude_classifications = [\"pii\"]` matches only non-PII models.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "layer": {
            "description": "Medallion/semantic layer guard. Satisfied when the model's `layer` tag equals this value (v0 reads layer from the model's `layer` tag).",
            "type": [
              "string",
              "null"
            ]
          },
          "max_downstreams": {
            "description": "Blast-radius guard: maximum transitive downstream count. Enforced as a **post-match ceiling on `allow`**, not a scope predicate: the rule still matches, and when its effect is `allow` and the target's transitive downstream reachability exceeds the ceiling — or cannot be computed — the effect degrades to `require_review` (fail-closed). `deny` / `require_review` rules are unaffected.",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "models": {
            "default": [],
            "description": "Glob selectors over the model name (`*`/`?`). Satisfied when the model name matches at least one pattern.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "tags": {
            "additionalProperties": {
              "type": "string"
            },
            "default": {},
            "description": "Required model tags (AND of `key = value` pairs). Satisfied when the model carries every listed tag with the exact value.",
            "type": "object"
          }
        },
        "type": "object"
      },
      "PolicyTest": {
        "additionalProperties": false,
        "description": "One `[[policy.tests]]` scenario: a self-contained assertion over the policy evaluator.\n\nA scenario names a `principal`, a `capability`, a synthetic target model (its attributes spelled out inline), and the `expect`ed effect. The `rocky policy test` runner constructs a [`crate::policy::ModelAttributes`] from these fields *verbatim* — the same value the evaluator receives at a real enforcement seam — feeds it to [`crate::policy::evaluate`], and asserts the resolved effect equals `expect`. Because the attributes are declared, not compiled, a scenario is stable regardless of the current project graph: it pins the *policy's* behaviour, which is exactly what a policy edit must not silently change.",
        "properties": {
          "capability": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PolicyCapability"
              }
            ],
            "description": "The capability being attempted."
          },
          "classifications": {
            "default": [],
            "description": "Synthetic column classifications present on the model (matched against `scope.classifications` / `scope.exclude_classifications`).",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "contracted": {
            "default": false,
            "description": "Whether the synthetic model sits behind a contract (matched against `scope.contracted`).",
            "type": "boolean"
          },
          "downstreams": {
            "default": 0,
            "description": "Synthetic direct downstream count. Informational — the `max_downstreams` ceiling reads `reachable_downstreams`.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "expect": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PolicyEffect"
              }
            ],
            "description": "The effect the evaluator must resolve for this scenario. A mismatch fails the scenario (and the `rocky policy test` run)."
          },
          "layer": {
            "description": "Synthetic medallion/semantic layer (matched against `scope.layer`). When omitted, the runner derives it from `tags[\"layer\"]`, mirroring how a real enforcement seam reads the model's `layer` tag — so a `tags = { layer = ... }` scenario matches a `scope.layer` rule without restating the value. An explicit value here always wins.",
            "type": [
              "string",
              "null"
            ]
          },
          "model": {
            "default": "",
            "description": "Synthetic model name, matched against rule `scope.models` globs. Empty (the default) matches no name-scoped rule — only `any`/attribute rules apply.",
            "type": "string"
          },
          "name": {
            "description": "Human-readable name for the scenario, echoed in the pass/fail report.",
            "type": "string"
          },
          "principal": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PolicyPrincipal"
              }
            ],
            "description": "The principal attempting the action."
          },
          "reachable_downstreams": {
            "description": "Synthetic transitive blast radius, compared against a rule's `max_downstreams` ceiling. Omit (the default `null`) to model an **uncomputable** blast radius — the ceiling then fails closed, exactly as at a real seam where the graph did not compile.",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "tags": {
            "additionalProperties": {
              "type": "string"
            },
            "default": {},
            "description": "Synthetic model-level tags (matched against rule `scope.tags`).",
            "type": "object"
          }
        },
        "required": [
          "capability",
          "expect",
          "name",
          "principal"
        ],
        "type": "object"
      },
      "PolicyTestOutput": {
        "description": "JSON output for `rocky policy test` — the scenario-assertion runner.\n\nRuns every `[[policy.tests]]` scenario through the real policy evaluator and reports, per scenario, whether the resolved effect matched the expectation. A non-empty `failed` count fails the command (non-zero exit), so a policy edit that would silently open a hole is caught in CI.",
        "properties": {
          "command": {
            "type": "string"
          },
          "failed": {
            "description": "How many scenarios resolved to a different effect than expected.",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "passed": {
            "description": "How many scenarios resolved to their expected effect.",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "results": {
            "description": "Per-scenario results, in declaration order.",
            "items": {
              "$ref": "#/components/schemas/PolicyTestResult"
            },
            "type": "array"
          },
          "total": {
            "description": "Total scenarios asserted.",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "command",
          "failed",
          "passed",
          "results",
          "total",
          "version"
        ],
        "type": "object"
      },
      "PolicyTestResult": {
        "description": "The outcome of one `[[policy.tests]]` scenario.",
        "properties": {
          "actual": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PolicyEffect"
              }
            ],
            "description": "The effect the evaluator actually resolved."
          },
          "capability": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PolicyCapability"
              }
            ],
            "description": "The capability that was checked."
          },
          "expected": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PolicyEffect"
              }
            ],
            "description": "The effect the scenario expected."
          },
          "matched_rule": {
            "description": "Zero-based index of the rule that decided `actual`, or `null` when the decision came from a short-circuit (`read`) or the default posture.",
            "format": "uint",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "model": {
            "description": "The synthetic model name the scenario targeted.",
            "type": "string"
          },
          "name": {
            "description": "The scenario's name.",
            "type": "string"
          },
          "passed": {
            "description": "`true` when the resolved effect equalled `expected`.",
            "type": "boolean"
          },
          "principal": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PolicyPrincipal"
              }
            ],
            "description": "The principal that was checked."
          },
          "reason": {
            "description": "The evaluator's explanation of how `actual` was reached — the decisive context on a failure.",
            "type": "string"
          }
        },
        "required": [
          "actual",
          "capability",
          "expected",
          "model",
          "name",
          "passed",
          "principal",
          "reason"
        ],
        "type": "object"
      },
      "PortabilityConfig": {
        "additionalProperties": false,
        "description": "Project-wide dialect portability configuration.\n\nLives at the top level because a Rocky project targets one warehouse; per-pipeline overrides aren't supported yet (no demand signal). The `allow` list applies to every model — a per-model override is the `-- rocky-allow: <constructs>` pragma in the model SQL itself, parsed by [`rocky_sql::pragma`].",
        "properties": {
          "allow": {
            "default": [],
            "description": "Project-wide allow-list of construct labels (case-insensitive, matched against `PortabilityIssue::construct`). Useful for blanket exemptions like `allow = [\"QUALIFY\"]` when a project standardizes on a specific extension. For per-model exemptions prefer the `-- rocky-allow: <construct>` pragma over expanding this list.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "target_dialect": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Dialect"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Target dialect for the portability lint. When unset, no lint runs (matches the wave-1 \"flag opt-in\" behavior). The CLI flag overrides this if both are present."
          }
        },
        "type": "object"
      },
      "PreviewBisectionRowDiff": {
        "description": "Row-level diff produced by the checksum-bisection algorithm. All counts are exhaustive over the diffed table — no sampling window.",
        "properties": {
          "rows_added": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "rows_changed": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "rows_removed": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "samples": {
            "description": "Up to `--max-samples` (default 5) representative changed rows surfaced from the leaves. Bisection samples only carry the primary key — column-level diffs are not retained on the kernel's leaf record. Empty when no rows differ.",
            "items": {
              "$ref": "#/components/schemas/PreviewRowSample"
            },
            "type": "array"
          }
        },
        "required": [
          "rows_added",
          "rows_changed",
          "rows_removed"
        ],
        "type": "object"
      },
      "PreviewColumnTypeChange": {
        "description": "One column-type change entry.",
        "properties": {
          "from": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "to": {
            "type": "string"
          }
        },
        "required": [
          "from",
          "name",
          "to"
        ],
        "type": "object"
      },
      "PreviewCopiedModel": {
        "description": "One entry in [`PreviewCreateOutput::copy_set`].\n\n`copy_strategy` reports `\"ctas\"` for every successful copy regardless of which SQL primitive the adapter emitted under the hood. Databricks uses `SHALLOW CLONE` and BigQuery uses `CREATE TABLE ... COPY` (both metadata-only); DuckDB and Snowflake fall through to the portable CTAS default. Surfacing the per-adapter strategy in the wire output is a follow-up.",
        "properties": {
          "copy_strategy": {
            "type": "string"
          },
          "model_name": {
            "type": "string"
          },
          "source_schema": {
            "type": "string"
          },
          "target_schema": {
            "type": "string"
          }
        },
        "required": [
          "copy_strategy",
          "model_name",
          "source_schema",
          "target_schema"
        ],
        "type": "object"
      },
      "PreviewCostOutput": {
        "description": "JSON output for `rocky preview cost`.\n\nDiff layer over `rocky cost latest`'s machinery. Per-model deltas are computed by looking up the latest base-schema `RunRecord` and the branch run's `RunRecord` and subtracting field-by-field.\n\n`models_skipped_via_copy` is the prune-set complement: copied models did not run on the branch, so their delta is `None` and their savings accrue to `savings_from_copy_usd`.",
        "properties": {
          "base_run_id": {
            "description": "`RunRecord` id for the latest base-schema run that the branch is being compared against. `None` when no base run exists.",
            "type": [
              "string",
              "null"
            ]
          },
          "branch_name": {
            "type": "string"
          },
          "branch_run_id": {
            "description": "`RunRecord` id for the branch execution. Empty when the branch run was a no-op (every changed model pruned to zero downstream).",
            "type": "string"
          },
          "command": {
            "type": "string"
          },
          "markdown": {
            "type": "string"
          },
          "per_model": {
            "items": {
              "$ref": "#/components/schemas/PreviewModelCostDelta"
            },
            "type": "array"
          },
          "projected_budget_breaches": {
            "description": "Budget breaches projected against the branch totals — populated only when the project declares a `[budget]` block. Lets a PR reviewer (and a CI gate) see \"this PR would breach the run-level `max_usd` / `max_duration_ms` / `max_bytes_scanned` if merged\" before the merge actually happens. Empty when no budget is configured or the projected totals stay within every limit. Mirrors the `RunOutput.budget_breaches` shape so the same downstream consumers (PR-comment templates, JSON listeners) can process both with one code path.",
            "items": {
              "$ref": "#/components/schemas/BudgetBreachOutput"
            },
            "type": "array"
          },
          "projected_per_model_budget_breaches": {
            "description": "Budget breaches projected against per-model branch totals — populated only when at least one model's resolved budget (its sidecar `[budget]` composed against the project-level config) is breached. Each entry carries `model_name` plus the same `limit_type` / `limit` / `actual` triple as [`Self::projected_budget_breaches`], with the resolved `on_breach` so downstream consumers can render advisory-vs-blocking per row. Empty when no per-model breach is projected. Strictly additive — kept on a separate field so existing consumers of `projected_budget_breaches` (which continues to surface only project-level breaches) are unaffected.",
            "items": {
              "$ref": "#/components/schemas/PerModelBudgetBreachOutput"
            },
            "type": "array"
          },
          "summary": {
            "$ref": "#/components/schemas/PreviewCostSummary"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "branch_name",
          "branch_run_id",
          "command",
          "markdown",
          "per_model",
          "summary",
          "version"
        ],
        "type": "object"
      },
      "PreviewCostSummary": {
        "description": "Aggregate cost rollup for [`PreviewCostOutput`].",
        "properties": {
          "delta_usd": {
            "description": "`total_branch_cost_usd - total_base_cost_usd`. Positive = the PR costs more to run than `main`.",
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          },
          "models_skipped_via_copy": {
            "description": "Number of models that did not run on the branch because they were copied from base. Their savings show up below.",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "savings_from_copy_usd": {
            "description": "Sum of `base_cost_usd` for every copied model — the cost the PR avoided by copying instead of re-running. `None` when no base costs are available for the copied models.",
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          },
          "total_base_cost_usd": {
            "description": "Sum of every per-model `base_cost_usd` (limited to models in the prune set — copied models contribute 0 here, accounted for in `savings_from_copy_usd`).",
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          },
          "total_branch_bytes_scanned": {
            "description": "Sum of every per-model `branch_bytes_scanned` that produced a number. `None` when no branch model reported a byte count (mirrors `RunOutput.cost.total_bytes_scanned` semantics — the non-BigQuery adapters today still inherit the default stub on `WarehouseAdapter::execute_statement_with_stats`). Used to project the `[budget]` `max_bytes_scanned` limit at preview time.",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "total_branch_cost_usd": {
            "description": "Sum of every per-model `branch_cost_usd` that produced a number.",
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          },
          "total_branch_duration_ms": {
            "description": "Sum of every per-model `branch_duration_ms`. Used to project the `[budget]` `max_duration_ms` limit at preview time.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "models_skipped_via_copy",
          "total_branch_duration_ms"
        ],
        "type": "object"
      },
      "PreviewCreateOutput": {
        "description": "JSON output for `rocky preview create`.\n\nRecords the prune-and-copy decision plus the run summary for the branch execution. The `prune_set` lists models that re-executed; the `copy_set` lists models pre-populated from the base schema instead of being re-run. `skipped_set` is the empty-cost residue — neither changed nor downstream of a change.",
        "properties": {
          "base_ref": {
            "description": "Git ref the change set was computed against. Mirrors `--base`.",
            "type": "string"
          },
          "branch_name": {
            "description": "Branch name registered in the state store. Mirrors the `name` from `rocky branch create`.",
            "type": "string"
          },
          "branch_schema": {
            "description": "Schema prefix the branch run wrote into (e.g. `branch__fix-price`).",
            "type": "string"
          },
          "command": {
            "type": "string"
          },
          "copy_set": {
            "description": "Models pre-populated from the base schema instead of re-running. Empty in Phase 1 if no models could be safely copied.",
            "items": {
              "$ref": "#/components/schemas/PreviewCopiedModel"
            },
            "type": "array"
          },
          "duration_ms": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "head_ref": {
            "description": "Git SHA the diff was computed from (typically `HEAD`).",
            "type": "string"
          },
          "prune_set": {
            "description": "Models that re-executed against the branch.",
            "items": {
              "$ref": "#/components/schemas/PreviewPrunedModel"
            },
            "type": "array"
          },
          "run_id": {
            "description": "`RunRecord` id for the branch execution. Empty when no models re-executed (a no-op preview where every changed model pruned to zero downstream).",
            "type": "string"
          },
          "run_status": {
            "description": "`succeeded`, `partial`, or `failed` — mirrors `RunRecord.status`.",
            "type": "string"
          },
          "skipped_set": {
            "description": "Models neither in the prune set nor copied — the column-level pruner determined they are unaffected and not depended on by any pruned model. Names only; rationale lives in the diff layer.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "base_ref",
          "branch_name",
          "branch_schema",
          "command",
          "copy_set",
          "duration_ms",
          "head_ref",
          "prune_set",
          "run_id",
          "run_status",
          "skipped_set",
          "version"
        ],
        "type": "object"
      },
      "PreviewDiffOutput": {
        "description": "JSON output for `rocky preview diff`.\n\nCombines the structural diff (column added/removed/type-changed) from the existing `rocky_core::ci_diff` machinery with a row-level diff produced by either the sampled or the checksum-bisection algorithm. Each per-model entry's `algorithm` field carries a `kind` discriminator (`\"sampled\"` or `\"bisection\"`) plus the matching payload.\n\n**Sampled correctness ceiling.** The sampled algorithm reads `LIMIT N` rows ordered by primary key (or first column). Changes outside that window appear as no-change unless the row count itself differs; `sampling_window.coverage_warning = true` flags that risk. **Bisection** walks the chunk lattice exhaustively over a single-column primary key; `bisection_stats.depth_capped = true` flags the rare case where the recursion bottomed out at the depth cap before reaching leaf size. `summary.any_coverage_warning` rolls both signals up.",
        "properties": {
          "base_ref": {
            "type": "string"
          },
          "branch_name": {
            "type": "string"
          },
          "command": {
            "type": "string"
          },
          "markdown": {
            "description": "Pre-rendered Markdown suitable for posting as a GitHub PR comment.",
            "type": "string"
          },
          "models": {
            "items": {
              "$ref": "#/components/schemas/PreviewModelDiff"
            },
            "type": "array"
          },
          "summary": {
            "$ref": "#/components/schemas/PreviewDiffSummary"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "base_ref",
          "branch_name",
          "command",
          "markdown",
          "models",
          "summary",
          "version"
        ],
        "type": "object"
      },
      "PreviewDiffSummary": {
        "description": "Aggregate diff counts for [`PreviewDiffOutput`].",
        "properties": {
          "any_coverage_warning": {
            "description": "`true` if **any** per-model diff is either a sampled diff with `sampling_window.coverage_warning = true` or a bisection diff with `bisection_stats.depth_capped = true`. Both conditions indicate the row-level findings might be incomplete and a reviewer shouldn't infer \"no change\" from a clean result.",
            "type": "boolean"
          },
          "models_unchanged": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "models_with_changes": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "total_rows_added": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "total_rows_changed": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "total_rows_removed": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "any_coverage_warning",
          "models_unchanged",
          "models_with_changes",
          "total_rows_added",
          "total_rows_changed",
          "total_rows_removed"
        ],
        "type": "object"
      },
      "PreviewModelCostDelta": {
        "description": "Per-model cost delta.",
        "properties": {
          "base_bytes_scanned": {
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "base_cost_usd": {
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          },
          "base_duration_ms": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "branch_bytes_scanned": {
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "branch_cost_usd": {
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          },
          "branch_duration_ms": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "delta_usd": {
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          },
          "model_name": {
            "type": "string"
          },
          "skipped_via_copy": {
            "description": "`true` if this model was copied from base instead of re-run. When true, `branch_*` fields are `None` / `0` and the savings for this row roll into [`PreviewCostSummary::savings_from_copy_usd`].",
            "type": "boolean"
          }
        },
        "required": [
          "base_duration_ms",
          "branch_duration_ms",
          "model_name",
          "skipped_via_copy"
        ],
        "type": "object"
      },
      "PreviewModelDiff": {
        "description": "Per-model diff. Combines a structural (column-level) delta with a row-level diff that was produced by either the sampled or the checksum-bisection algorithm. The active algorithm is encoded by the `kind` discriminator on [`PreviewModelDiffAlgorithm`].",
        "properties": {
          "algorithm": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PreviewModelDiffAlgorithm"
              }
            ],
            "description": "Row-level diff payload, tagged by which algorithm produced it. Different models in the same run can carry different variants: a model with a single-column integer primary key runs through the bisection kernel, while a model that lacks a usable PK falls back to the sampled algorithm."
          },
          "model_name": {
            "type": "string"
          },
          "structural": {
            "$ref": "#/components/schemas/PreviewStructuralDiff"
          }
        },
        "required": [
          "algorithm",
          "model_name",
          "structural"
        ],
        "type": "object"
      },
      "PreviewModelDiffAlgorithm": {
        "description": "Row-level diff payload tagged by which algorithm produced it. The `kind` discriminator (`\"sampled\"` or `\"bisection\"`) lets consumers reading the JSON pick the right shape without having to check optional fields.",
        "oneOf": [
          {
            "description": "Sampled diff — `LIMIT N` rows ordered by primary key (or first column). Carries a coverage warning when changes outside the sampling window are not surfaced.",
            "properties": {
              "kind": {
                "enum": [
                  "sampled"
                ],
                "type": "string"
              },
              "sampled": {
                "$ref": "#/components/schemas/PreviewSampledRowDiff"
              },
              "sampling_window": {
                "$ref": "#/components/schemas/PreviewSamplingWindow"
              }
            },
            "required": [
              "kind",
              "sampled",
              "sampling_window"
            ],
            "type": "object"
          },
          {
            "description": "Checksum-bisection exhaustive diff. Walks the chunk lattice and materializes leaves for row-by-row compare. Carries [`BisectionStatsOutput`] so consumers can audit the recursion trace.",
            "properties": {
              "bisection_stats": {
                "$ref": "#/components/schemas/BisectionStatsOutput"
              },
              "diff": {
                "$ref": "#/components/schemas/PreviewBisectionRowDiff"
              },
              "kind": {
                "enum": [
                  "bisection"
                ],
                "type": "string"
              }
            },
            "required": [
              "bisection_stats",
              "diff",
              "kind"
            ],
            "type": "object"
          }
        ]
      },
      "PreviewPrunedModel": {
        "description": "One entry in [`PreviewCreateOutput::prune_set`].",
        "properties": {
          "changed_columns": {
            "description": "Columns the diff reports as changed on this model. Only populated when `reason = \"changed\"`.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "model_name": {
            "type": "string"
          },
          "reason": {
            "description": "`\"changed\"` for models the diff identified directly, or `\"downstream_of_changed\"` for models pulled in by column-level lineage from a changed model.",
            "type": "string"
          }
        },
        "required": [
          "model_name",
          "reason"
        ],
        "type": "object"
      },
      "PreviewRowSample": {
        "description": "One representative changed row in [`PreviewSampledRowDiff::samples`].",
        "properties": {
          "changes": {
            "description": "Pairs of `(column_name, base_value, branch_value)` for every column that differs on this row.",
            "items": {
              "$ref": "#/components/schemas/PreviewRowSampleChange"
            },
            "type": "array"
          },
          "primary_key": {
            "type": "string"
          }
        },
        "required": [
          "changes",
          "primary_key"
        ],
        "type": "object"
      },
      "PreviewRowSampleChange": {
        "description": "One `(column, base, branch)` triple in [`PreviewRowSample::changes`].",
        "properties": {
          "base_value": {
            "type": "string"
          },
          "branch_value": {
            "type": "string"
          },
          "column": {
            "type": "string"
          }
        },
        "required": [
          "base_value",
          "branch_value",
          "column"
        ],
        "type": "object"
      },
      "PreviewRowsOutput": {
        "description": "JSON output for `rocky preview rows`.\n\nA sample of result rows for a single transformation model (or one of its CTEs), executed against the pipeline's configured adapter. Classified columns are masked inline before execution, so the rows match what the materialized target would expose. `truncated` is `true` when the model produced at least `limit_applied` rows.",
        "properties": {
          "adapter_kind": {
            "description": "Adapter the preview ran against (e.g. `\"duckdb\"`, `\"databricks\"`).",
            "type": "string"
          },
          "columns": {
            "description": "Output column names, in order.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "command": {
            "type": "string"
          },
          "cte": {
            "description": "CTE within the model that was isolated, if `--cte` was given.",
            "type": [
              "string",
              "null"
            ]
          },
          "duration_ms": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "executed_sql": {
            "description": "The exact SQL executed (carries no credentials). Useful for debugging the masking projection and CTE isolation.",
            "type": "string"
          },
          "limit_applied": {
            "description": "The `LIMIT` applied to the preview query.",
            "format": "uint32",
            "minimum": 0.0,
            "type": "integer"
          },
          "model": {
            "description": "Model whose output was sampled.",
            "type": "string"
          },
          "row_count": {
            "description": "Number of rows returned (≤ `limit_applied`).",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "rows": {
            "description": "Sampled rows; each row is a list of JSON-encoded cell values aligned to `columns`.",
            "items": {
              "items": true,
              "type": "array"
            },
            "type": "array"
          },
          "truncated": {
            "description": "`true` when `row_count` reached `limit_applied` — more rows may exist.",
            "type": "boolean"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "adapter_kind",
          "columns",
          "command",
          "duration_ms",
          "executed_sql",
          "limit_applied",
          "model",
          "row_count",
          "rows",
          "truncated",
          "version"
        ],
        "type": "object"
      },
      "PreviewSampledRowDiff": {
        "description": "Sampled row-level diff. All counts are over the sampling window.",
        "properties": {
          "rows_added": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "rows_changed": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "rows_removed": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "samples": {
            "description": "Up to `--max-samples` (default 5) representative changed rows for human review. Pure noise when sampling found no change.",
            "items": {
              "$ref": "#/components/schemas/PreviewRowSample"
            },
            "type": "array"
          }
        },
        "required": [
          "rows_added",
          "rows_changed",
          "rows_removed"
        ],
        "type": "object"
      },
      "PreviewSamplingWindow": {
        "description": "Sampling-window metadata surfaced verbatim in the PR comment.\n\n`coverage_warning = true` flags that the row count outside the sampling window is non-trivial; reviewers should not infer \"no change\" from a clean sample if this flag is set.",
        "properties": {
          "coverage": {
            "description": "Sampling-strategy tag. `\"first_n_by_order\"` is the standard PK-ordered window; `\"not_yet_sampled\"` is a placeholder used when the sampling layer didn't run (e.g. the structural-only fallback path).",
            "type": "string"
          },
          "coverage_warning": {
            "type": "boolean"
          },
          "limit": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "ordered_by": {
            "type": "string"
          }
        },
        "required": [
          "coverage",
          "coverage_warning",
          "limit",
          "ordered_by"
        ],
        "type": "object"
      },
      "PreviewStructuralDiff": {
        "description": "Column-level structural diff. Mirrors the shape produced by `rocky ci-diff` at the column granularity.",
        "properties": {
          "added_columns": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "removed_columns": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "type_changes": {
            "description": "One entry per column whose type changed. Each carries `name`, `from`, `to`.",
            "items": {
              "$ref": "#/components/schemas/PreviewColumnTypeChange"
            },
            "type": "array"
          }
        },
        "type": "object"
      },
      "ProfileColumnStats": {
        "description": "Observed data profile for one column.",
        "properties": {
          "distinct": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "max": {
            "type": [
              "string",
              "null"
            ]
          },
          "min": {
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "type": "string"
          },
          "null_rate": {
            "format": "double",
            "type": "number"
          },
          "nulls": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "observed_values": {
            "description": "Observed low-cardinality domain (empty above the cardinality cap).",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "rows": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "type": {
            "description": "Inferred Rocky type name.",
            "type": "string"
          }
        },
        "required": [
          "distinct",
          "name",
          "null_rate",
          "nulls",
          "observed_values",
          "rows",
          "type"
        ],
        "type": "object"
      },
      "ProfileOutput": {
        "description": "JSON output for `rocky profile <model> [--column <col>]`.\n\nA per-column data profile (row / null / distinct counts, min / max, and the low-cardinality domain) for a model's target table, computed by a single aggregate query per column. DuckDB-only this release; a non-DuckDB target reports `unavailable` with an empty `columns` list rather than erroring.\n\nWhen the model's declared target isn't materialized (the agentic authoring loop pre-`rocky run`, or a replication-pipeline POC that doesn't run the transformation models), profile falls back to the model's first resolvable source table. The fallback is labelled via `profiled_table` (the table actually queried) and `fell_back_from` (the missing target), so consumers can surface \"source preview, not model output\" in the UI.",
        "properties": {
          "columns": {
            "description": "One entry per profiled column.",
            "items": {
              "$ref": "#/components/schemas/ProfileColumnStats"
            },
            "type": "array"
          },
          "command": {
            "type": "string"
          },
          "fell_back_from": {
            "description": "When set, profile fell back to a source because the model's declared target wasn't materialized; carries the declared-target FQN that was missing. `None` when the declared target was profiled directly.",
            "type": [
              "string",
              "null"
            ]
          },
          "model": {
            "type": "string"
          },
          "profiled_table": {
            "description": "Fully-qualified table actually profiled (e.g. `staging.raw_orders` or `raw__orders.orders`). When `fell_back_from` is set, this differs from the model's declared target. Omitted when `unavailable` is set.",
            "type": [
              "string",
              "null"
            ]
          },
          "unavailable": {
            "description": "Set when profiling could not run (e.g. a non-DuckDB target this release).",
            "type": [
              "string",
              "null"
            ]
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "columns",
          "command",
          "model",
          "version"
        ],
        "type": "object"
      },
      "ProfileStorageOutput": {
        "description": "JSON output for `rocky profile-storage`.",
        "properties": {
          "command": {
            "type": "string"
          },
          "model": {
            "type": "string"
          },
          "profile_sql": {
            "type": "string"
          },
          "recommendations": {
            "items": {
              "$ref": "#/components/schemas/EncodingRecommendationOutput"
            },
            "type": "array"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "command",
          "model",
          "profile_sql",
          "recommendations",
          "version"
        ],
        "type": "object"
      },
      "ProjectFreshnessConfig": {
        "additionalProperties": false,
        "description": "Project-level freshness defaults.\n\nTop-level `[freshness]` block on `rocky.toml`. Provides defaults inherited by per-model [`crate::models::ModelFreshnessConfig`] declarations that omit one or more fields. Independent of the [`ChecksConfig::freshness`](FreshnessConfig) check (which lives under `[checks.freshness]` and feeds the data-quality test pipeline).\n\nAll fields are optional. A project-level `[freshness]` with no `expected_lag_seconds` is treated as \"no project default\" for the W005 soft-warn — the suppression still requires a concrete TTL.",
        "properties": {
          "expected_lag_seconds": {
            "description": "Default maximum lag in seconds before models are considered stale. When set, every model without its own `freshness` block inherits this value (plus the other fields). When `None`, no project-level default applies — per-model declarations are the only source of freshness metadata.",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "severity": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/TestSeverity"
              },
              {
                "type": "null"
              }
            ],
            "description": "Default severity reported when the freshness check trips."
          },
          "time_column": {
            "description": "Default timestamp column used to evaluate freshness at runtime. Inherited by per-model freshness blocks that don't specify their own `time_column`.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "type": "object"
      },
      "PromotePlan": {
        "description": "Persisted payload for a `rocky plan promote` run plan.\n\nWritten to `.rocky/plans/<plan_id>.json` by `rocky plan promote` and read back by `rocky apply <plan_id>` (dispatched as `PlanKind::Promote`).\n\n## What is persisted vs re-derived\n\n- **Persisted**: branch name + refs, state hash at plan time, approval artifacts used/rejected, breaking-change findings, per-target SQL statements, and the plan-time audit events. - **Re-derived at apply time**: nothing related to approvals or breaking-change gate — those gates ran at plan time and their outcomes are captured here. The warehouse adapter is resolved at apply time (to call `execute_statement`), but discovery is NOT re-run.\n\n## Branch state drift at apply time\n\n`branch_state_hash` reflects the branch metadata + config bytes at plan time. If the branch's config or metadata changes between `rocky plan promote` and `rocky apply`, the persisted hash differs from the live hash. By default, `rocky apply` does **not** re-check the hash — the gates already ran at plan time, and that is the point of the plan/apply split. Operators who need a strict re-check can re-run `rocky plan promote` to produce a fresh plan.\n\nWarehouse table contents are not covered by `branch_state_hash` in v1 — if a branch's tables are mutated between plan and apply, `rocky apply` will promote whatever data is in the branch schema at apply time.",
        "properties": {
          "allow_breaking": {
            "default": false,
            "description": "Whether `--allow-breaking` was set at plan time.",
            "type": "boolean"
          },
          "approvals_rejected": {
            "description": "Approval artifacts loaded from disk that failed verification at plan time.",
            "items": {
              "$ref": "#/components/schemas/RejectedApproval"
            },
            "type": "array"
          },
          "approvals_used": {
            "description": "Approval artifacts that satisfied the gate at plan time.",
            "items": {
              "$ref": "#/components/schemas/ApprovalArtifact"
            },
            "type": "array"
          },
          "base_ref": {
            "description": "Git ref that `base_ref` was resolved to at plan time (e.g. `\"main\"`).",
            "type": "string"
          },
          "branch_name": {
            "description": "Branch name being promoted.",
            "type": "string"
          },
          "branch_state_hash": {
            "description": "Content-addressed hash of the branch metadata + config bytes at plan time. Stored for audit purposes; not re-validated at apply time.",
            "type": "string"
          },
          "breaking_changes": {
            "description": "Semantic breaking-change findings produced by the pre-promote gate at plan time. Empty when the gate ran and found no breaking changes; absent when the gate was skipped (compile failure on either side).",
            "items": {
              "$ref": "#/components/schemas/BreakingFinding"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "created_at": {
            "description": "When this plan was persisted.",
            "format": "date-time",
            "type": "string"
          },
          "head_ref": {
            "description": "Git HEAD SHA at plan time — informational for audit purposes.",
            "type": "string"
          },
          "plan_audit": {
            "description": "Plan-time audit events (approvals gate + breaking-change gate outcomes). Apply-time events (`PromoteStarted`, `PromoteCompleted`, `PromoteFailed`) are appended in `BranchPromoteOutput.audit` at apply time.",
            "items": {
              "$ref": "#/components/schemas/AuditEvent"
            },
            "type": "array"
          },
          "targets": {
            "description": "Per-model SQL plan, in dispatch order. SQL is persisted verbatim so `rocky apply` executes the exact statements generated at plan time.",
            "items": {
              "$ref": "#/components/schemas/PromoteTargetPlan"
            },
            "type": "array"
          }
        },
        "required": [
          "approvals_rejected",
          "approvals_used",
          "base_ref",
          "branch_name",
          "branch_state_hash",
          "created_at",
          "head_ref",
          "plan_audit",
          "targets"
        ],
        "type": "object"
      },
      "PromoteTarget": {
        "description": "One per-target promote step in [`BranchPromoteOutput::targets`].",
        "properties": {
          "error": {
            "description": "Adapter / SQL error text when `succeeded` is `false`.",
            "type": [
              "string",
              "null"
            ]
          },
          "source": {
            "description": "Fully-qualified branch source the promote read from (catalog.branch_schema.table).",
            "type": "string"
          },
          "statement": {
            "description": "SQL statement dispatched to the adapter for this target.",
            "type": "string"
          },
          "succeeded": {
            "description": "Whether the per-target SQL succeeded. Failures abort the run; on a failure this is `false` for the failing target and absent for any targets that never started.",
            "type": "boolean"
          },
          "target": {
            "description": "Fully-qualified production target (catalog.schema.table).",
            "type": "string"
          }
        },
        "required": [
          "source",
          "statement",
          "succeeded",
          "target"
        ],
        "type": "object"
      },
      "PromoteTargetPlan": {
        "description": "Per-model promote step captured in a [`PromotePlan`].\n\nMirrors the shape of [`PromoteTarget`] but contains only plan-time fields (`target`, `source`, `statement`). Execution outcome (`succeeded`, `error`) is added at apply time and lives on [`PromoteTarget`].\n\n`statement` is persisted verbatim so `rocky apply` executes the **exact** SQL generated at plan time — skipping re-discovery and ensuring the `plan_id` digest is invalidated if the SQL would differ.",
        "properties": {
          "source": {
            "description": "Fully-qualified branch source the promote will read from (catalog.branch_schema.table).",
            "type": "string"
          },
          "statement": {
            "description": "`CREATE OR REPLACE TABLE <target> AS SELECT * FROM <source>` SQL, dialect-quoted at plan time.",
            "type": "string"
          },
          "target": {
            "description": "Fully-qualified production target (catalog.schema.table).",
            "type": "string"
          }
        },
        "required": [
          "source",
          "statement",
          "target"
        ],
        "type": "object"
      },
      "QualityAssertion": {
        "description": "A single row-level assertion attached to a quality pipeline, scoped to one table in the pipeline's `tables` list.\n\n```toml [[pipeline.nightly_dq.checks.assertions]] name     = \"orders_customer_id_not_null\"  # optional table    = \"orders\" type     = \"not_null\" column   = \"customer_id\" severity = \"error\" ```\n\nThe `type`-specific fields (and `column`, `severity`) are flattened from `TestDecl` — the same surface used by declarative model tests.",
        "oneOf": [
          {
            "description": "Assert that a column contains no NULL values.",
            "properties": {
              "type": {
                "enum": [
                  "not_null"
                ],
                "type": "string"
              }
            },
            "required": [
              "type"
            ],
            "type": "object"
          },
          {
            "description": "Assert that a column contains only unique values.",
            "properties": {
              "type": {
                "enum": [
                  "unique"
                ],
                "type": "string"
              }
            },
            "required": [
              "type"
            ],
            "type": "object"
          },
          {
            "description": "Assert that a column contains only values from a fixed set.",
            "properties": {
              "type": {
                "enum": [
                  "accepted_values"
                ],
                "type": "string"
              },
              "values": {
                "description": "The allowed values. Compared as string literals.",
                "items": {
                  "type": "string"
                },
                "type": "array"
              }
            },
            "required": [
              "type",
              "values"
            ],
            "type": "object"
          },
          {
            "description": "Assert that every non-NULL value in a column exists in a referenced table's column (referential integrity).",
            "properties": {
              "to_column": {
                "description": "Column in the target table to join against.",
                "type": "string"
              },
              "to_table": {
                "description": "Fully-qualified target table (`catalog.schema.table`).",
                "type": "string"
              },
              "type": {
                "enum": [
                  "relationships"
                ],
                "type": "string"
              }
            },
            "required": [
              "to_column",
              "to_table",
              "type"
            ],
            "type": "object"
          },
          {
            "description": "Assert that a custom SQL expression holds for every row.",
            "properties": {
              "expression": {
                "description": "A SQL boolean expression. Rows where `NOT (expression)` are failures.",
                "type": "string"
              },
              "type": {
                "enum": [
                  "expression"
                ],
                "type": "string"
              }
            },
            "required": [
              "expression",
              "type"
            ],
            "type": "object"
          },
          {
            "description": "Assert that the table's row count falls within an inclusive range.",
            "properties": {
              "max": {
                "default": null,
                "description": "Maximum row count (inclusive). `None` means no upper bound.",
                "format": "uint64",
                "minimum": 0.0,
                "type": [
                  "integer",
                  "null"
                ]
              },
              "min": {
                "default": null,
                "description": "Minimum row count (inclusive). `None` means no lower bound.",
                "format": "uint64",
                "minimum": 0.0,
                "type": [
                  "integer",
                  "null"
                ]
              },
              "type": {
                "enum": [
                  "row_count_range"
                ],
                "type": "string"
              }
            },
            "required": [
              "type"
            ],
            "type": "object"
          },
          {
            "description": "Assert that a column's values fall within a numeric range (inclusive, half-open either side). `in_range` supports numeric bounds only — use `time_window` or `expression` for temporal comparisons.\n\nNULL column values pass (consistent with existing `NOT IN` / `NOT (expr)` semantics).",
            "properties": {
              "max": {
                "default": null,
                "description": "Maximum value (inclusive). `None` means no upper bound.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "min": {
                "default": null,
                "description": "Minimum value (inclusive). `None` means no lower bound.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "type": {
                "enum": [
                  "in_range"
                ],
                "type": "string"
              }
            },
            "required": [
              "type"
            ],
            "type": "object"
          },
          {
            "description": "Assert that a column's values match a regular expression.\n\nDialect-specific operator (`REGEXP` / `RLIKE` / `REGEXP_LIKE` / `REGEXP_CONTAINS`) is produced by [`SqlDialect::regex_match_predicate`]. Patterns are validated against a strict allowlist that rejects single quotes, backticks, and semicolons.\n\nNULL column values pass.",
            "properties": {
              "pattern": {
                "description": "The regex pattern. Dialect-specific syntax — stick to the portable subset (character classes, anchors, quantifiers).",
                "type": "string"
              },
              "type": {
                "enum": [
                  "regex_match"
                ],
                "type": "string"
              }
            },
            "required": [
              "pattern",
              "type"
            ],
            "type": "object"
          },
          {
            "description": "Assert that an aggregate (`sum`, `count`, `avg`, `min`, `max`) of a column satisfies a comparison against a numeric threshold. The comparison is what **passes** — e.g. `op = \"sum\", cmp = \"gt\", value = \"0\"` means \"sum(col) > 0 passes; otherwise fails\".\n\nTable-level; not quarantinable. Use `count` (no column) for the row-count-based variants that `row_count_range` can't express (equality, strict inequalities).",
            "properties": {
              "cmp": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/AggregateCmp"
                  }
                ],
                "description": "Comparison between `op(column)` and `value`. The check passes when the comparison is true."
              },
              "op": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/AggregateOp"
                  }
                ],
                "description": "Aggregate operator."
              },
              "type": {
                "enum": [
                  "aggregate"
                ],
                "type": "string"
              },
              "value": {
                "description": "Threshold to compare against. Parsed as `f64`.",
                "type": "string"
              }
            },
            "required": [
              "cmp",
              "op",
              "type",
              "value"
            ],
            "type": "object"
          },
          {
            "description": "Assert that a combination of columns is unique across all rows. Extends [`TestType::Unique`] to composite keys.\n\nSet-based; not quarantinable.",
            "properties": {
              "columns": {
                "description": "Columns that together form the key. Must have at least two entries (single-column uniqueness is covered by `Unique`).",
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "kind": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/CompositeKind"
                  }
                ],
                "description": "The kind of composite assertion. Currently `unique` only — kept as an enum to leave room for `not_null_any` / `not_null_all` in a later phase without another TestType."
              },
              "type": {
                "enum": [
                  "composite"
                ],
                "type": "string"
              }
            },
            "required": [
              "columns",
              "kind",
              "type"
            ],
            "type": "object"
          },
          {
            "description": "Assert that a derived **key expression** is unique across all rows — the `GROUP BY <expr> HAVING COUNT(*) > 1` form that neither `Unique` (single column) nor `Composite` (column tuple) can express. The meaningful identity is a *computed* value (e.g. a surrogate built to be stable across a multi-tenant union), not any stored column.\n\n`key_expr` is a SQL scalar expression evaluated against the target (e.g. `md5(databasename || '-' || id)`). It is passed through **verbatim** — the same trusted-config contract as [`TestType::Expression`] — so the caller is responsible for sandboxing execution.\n\nSet-based; not quarantinable. Mirrors `Unique`'s NULL handling (NULL keys are not excluded; use `filter` to scope them out).",
            "properties": {
              "key_expr": {
                "description": "SQL scalar expression whose value must be unique across rows.",
                "type": "string"
              },
              "type": {
                "enum": [
                  "unique_expr"
                ],
                "type": "string"
              }
            },
            "required": [
              "key_expr",
              "type"
            ],
            "type": "object"
          },
          {
            "description": "First-class sugar for `col <= CURRENT_TIMESTAMP()` — no timestamp in the future. Row-level; quarantinable (NULL column values pass).",
            "properties": {
              "type": {
                "enum": [
                  "not_in_future"
                ],
                "type": "string"
              }
            },
            "required": [
              "type"
            ],
            "type": "object"
          },
          {
            "description": "First-class sugar for `col <= CURRENT_DATE - N days` — every row's timestamp must be at least `days` days old. Row-level; quarantinable (NULL column values pass). Dialect-specific: uses [`SqlDialect::date_minus_days_expr`].",
            "properties": {
              "days": {
                "description": "N — days in the past. Must be > 0.",
                "format": "uint32",
                "minimum": 0.0,
                "type": "integer"
              },
              "type": {
                "enum": [
                  "older_than_n_days"
                ],
                "type": "string"
              }
            },
            "required": [
              "days",
              "type"
            ],
            "type": "object"
          }
        ],
        "properties": {
          "column": {
            "default": null,
            "description": "Column under test. Required for `not_null`, `unique`, `accepted_values`, `relationships`, `in_range`, `regex_match`. Ignored for `expression` and `row_count_range`.",
            "type": [
              "string",
              "null"
            ]
          },
          "filter": {
            "default": null,
            "description": "Optional SQL boolean predicate that scopes the assertion to a subset of rows. When set, only rows where `(filter)` evaluates to `TRUE` are subject to the assertion — rows where the filter is `FALSE` or `NULL` pass unconditionally.\n\nFilter is user-supplied SQL; the caller is responsible for sandboxing execution (same contract as `expression`).\n\nExample: `filter = \"created_at > current_date - interval 30 day\"` restricts a `not_null` check to rows created in the last 30 days.",
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "default": null,
            "description": "Optional identifier used as the `CheckResult.name` in the JSON output. When unset, a synthesized `\"{kind}:{column}\"` name is used — which can collide if multiple assertions share the same table, kind, and column. Set `name` explicitly to disambiguate.",
            "type": [
              "string",
              "null"
            ]
          },
          "severity": {
            "allOf": [
              {
                "$ref": "#/components/schemas/TestSeverity"
              }
            ],
            "default": "error",
            "description": "Severity of failure. Defaults to `error`."
          },
          "table": {
            "description": "Table name this assertion applies to. Must match a table discovered from one of the pipeline's `[[tables]]` entries (by unqualified table name).",
            "type": "string"
          }
        },
        "required": [
          "table"
        ],
        "type": "object"
      },
      "QualityPipelineConfig": {
        "description": "Quality pipeline configuration — standalone data quality checks.\n\nRuns checks against existing tables without any data movement.\n\n```toml [pipeline.nightly_dq] type = \"quality\"\n\n[pipeline.nightly_dq.target] adapter = \"databricks_prod\"\n\n[[pipeline.nightly_dq.tables]] catalog = \"acme_warehouse\" schema = \"raw__us_west__shopify\"\n\n[pipeline.nightly_dq.checks] enabled = true freshness = { threshold_seconds = 86400 } ```",
        "properties": {
          "checks": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ChecksConfig"
              }
            ],
            "description": "Data quality checks to run."
          },
          "depends_on": {
            "default": [],
            "description": "Pipeline dependencies for chaining.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "execution": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ExecutionConfig"
              }
            ],
            "default": {
              "concurrency": "adaptive",
              "error_rate_abort_pct": 50,
              "fail_fast": false,
              "table_retries": 1
            },
            "description": "Execution settings (concurrency, retries, etc.)."
          },
          "tables": {
            "default": [],
            "description": "Tables to check. Each entry specifies catalog + schema, and optionally a specific table (omit for all tables in the schema).",
            "items": {
              "$ref": "#/components/schemas/TableRef"
            },
            "type": "array"
          },
          "target": {
            "allOf": [
              {
                "$ref": "#/components/schemas/QualityTargetConfig"
              }
            ],
            "description": "Target adapter for running check queries."
          }
        },
        "required": [
          "checks",
          "target"
        ],
        "type": "object"
      },
      "QualityTargetConfig": {
        "additionalProperties": false,
        "description": "Target configuration for quality pipelines (adapter reference only).",
        "properties": {
          "adapter": {
            "default": "default",
            "description": "Name of the adapter to use (references a key in `[adapter.*]`).",
            "type": "string"
          }
        },
        "type": "object"
      },
      "QuarantineConfig": {
        "additionalProperties": false,
        "description": "Row quarantine configuration. When enabled, error-severity row-level assertions (`not_null`, `accepted_values`, `expression`) on a given table are compiled into a single boolean row predicate. Rows matching the predicate go to the `__valid` table; rows that don't go to the `__quarantine` table (or are dropped / tagged, per `mode`).\n\nAggregate / set-based assertions (`unique`, `relationships`, `row_count_range`) are **not** lowered — they stay observational and produce `CheckResult` entries as before.\n\n```toml [pipeline.nightly_dq.checks.quarantine] enabled = true mode    = \"split\"          # \"split\" (default) | \"tag\" | \"drop\" # suffix_valid       = \"__valid\" # suffix_quarantine  = \"__quarantine\" ```",
        "properties": {
          "enabled": {
            "default": false,
            "description": "Enable quarantine. Default: `false`.",
            "type": "boolean"
          },
          "mode": {
            "allOf": [
              {
                "$ref": "#/components/schemas/QuarantineMode"
              }
            ],
            "default": "split",
            "description": "How to split rows — see [`QuarantineMode`]."
          },
          "suffix_quarantine": {
            "default": "__quarantine",
            "description": "Table-name suffix for the failing-rows table. Default `\"__quarantine\"`.",
            "type": "string"
          },
          "suffix_valid": {
            "default": "__valid",
            "description": "Table-name suffix for the passing-rows table. Default `\"__valid\"`.",
            "type": "string"
          }
        },
        "type": "object"
      },
      "QuarantineMode": {
        "description": "How to handle rows that fail error-severity row-level assertions.",
        "oneOf": [
          {
            "description": "Write a `<table>__valid` table with passing rows and a `<table>__quarantine` table with failing rows (plus per-assertion `_error_<name>` label columns). Original table untouched. Default.",
            "enum": [
              "split"
            ],
            "type": "string"
          },
          {
            "description": "Replace `<table>` in place with a copy that has per-assertion `_error_<name>` label columns set for failing rows. Rewrites the source — use with care; not recommended when the source is a raw replication target.",
            "enum": [
              "tag"
            ],
            "type": "string"
          },
          {
            "description": "Write only the `<table>__valid` half. Failing rows are discarded. Irreversible at run-time.",
            "enum": [
              "drop"
            ],
            "type": "string"
          }
        ]
      },
      "QuarantineOutput": {
        "description": "Row-quarantine outcome for a single table processed by the quality pipeline. Emitted when `[pipeline.x.checks.quarantine]` is enabled and the table has at least one error-severity row-level assertion that lowers to a boolean predicate.\n\nRow counts are reported when the warehouse adapter supplies them. Adapters that cannot count rows written by a `CREATE OR REPLACE TABLE` leave the counts as `None`.",
        "properties": {
          "asset_key": {
            "description": "Dagster-style asset key path (`[catalog, schema, table]`) of the source table the quarantine acted on.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "error": {
            "description": "Error message from the first failing statement, if any.",
            "type": [
              "string",
              "null"
            ]
          },
          "mode": {
            "description": "Quarantine mode that was applied. One of `\"split\"`, `\"tag\"`, `\"drop\"` (matches [`rocky_core::config::QuarantineMode`]).",
            "type": "string"
          },
          "ok": {
            "description": "`true` when every quarantine statement executed successfully. `false` means a partial failure — inspect `error` for details.",
            "type": "boolean"
          },
          "quarantine_table": {
            "description": "Fully-qualified name of the `__quarantine` output table. Empty for `mode = \"drop\"` (failing rows discarded) and `mode = \"tag\"`.",
            "type": "string"
          },
          "quarantined_rows": {
            "description": "Number of rows in the `__quarantine` output, when the adapter can report it.",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "valid_rows": {
            "description": "Number of rows in the `__valid` output, when the adapter can report it.",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "valid_table": {
            "description": "Fully-qualified `catalog.schema.table` name of the `__valid` output table. Empty for `mode = \"tag\"` (source is rewritten in place).",
            "type": "string"
          }
        },
        "required": [
          "asset_key",
          "mode",
          "ok"
        ],
        "type": "object"
      },
      "RecipeExecutionRecord": {
        "description": "One execution of a given `recipe_hash`, embedded in [`RecipeHistoryOutput`].",
        "properties": {
          "duration_ms": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "model_name": {
            "description": "Model name as recorded in the run.",
            "type": "string"
          },
          "recipe_identity": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/RecipeIdentityView"
              },
              {
                "type": "null"
              }
            ],
            "description": "The recipe-identity triple for this execution. Its `recipe_hash` matches the top-level filter; `input_hash` / `env_hash` can differ run-to-run for the same program (different inputs, different engine version)."
          },
          "rows_affected": {
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "run_id": {
            "description": "The run this execution belonged to.",
            "type": "string"
          },
          "sql_hash": {
            "type": "string"
          },
          "started_at": {
            "format": "date-time",
            "type": "string"
          },
          "status": {
            "type": "string"
          }
        },
        "required": [
          "duration_ms",
          "model_name",
          "run_id",
          "sql_hash",
          "started_at",
          "status"
        ],
        "type": "object"
      },
      "RecipeHistoryOutput": {
        "description": "JSON output for `rocky history --recipe <hash>` — every recorded execution of one exact program (`recipe_hash`), across all runs.\n\nThe \"what produced this?\" query: given a `recipe_hash` (read from any model record's [`RecipeIdentityView`]), list every time that exact program ran — newest run first — with the run it belonged to and its per-execution input / environment identity.",
        "properties": {
          "command": {
            "type": "string"
          },
          "count": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "executions": {
            "items": {
              "$ref": "#/components/schemas/RecipeExecutionRecord"
            },
            "type": "array"
          },
          "recipe_hash": {
            "description": "The `recipe_hash` the history was filtered on, echoed back.",
            "type": "string"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "command",
          "count",
          "executions",
          "recipe_hash",
          "version"
        ],
        "type": "object"
      },
      "RecipeIdentityView": {
        "description": "The recipe-identity triple surfaced on a model record — the answer to \"what exact program, over what inputs, in what environment produced this?\".\n\nRead back from the persisted [`rocky_core::state::ModelExecution`]. Every field is optional: a record written before the triple was captured (state schema predating it) or a failed execution carries none of them, and the input side is absent on the default run path (which observes no inputs). The whole object is omitted from JSON when nothing was recorded — see [`Self::from_execution`] — so output for pre-triple records is unchanged.",
        "properties": {
          "env_hash": {
            "description": "The **environment** key: blake3 (hex) over the engine version and the adapter / dialect identity. Excludes the hostname by construction.",
            "type": [
              "string",
              "null"
            ]
          },
          "hash_scheme": {
            "description": "The hash-scheme tag (`\"v1\"`) in force when the triple was computed, so a future canonicalisation change is an explicit new scheme rather than a silent history fork.",
            "type": [
              "string",
              "null"
            ]
          },
          "input_hash": {
            "description": "The **input** key: blake3 (hex) over the run's observed input identities. Present only when the run actually observed inputs (the `--skip-unchanged` gate's upstream freshness signatures, or the content-addressed reuse spine); absent on the default run path.",
            "type": [
              "string",
              "null"
            ]
          },
          "input_proof_class": {
            "description": "Strength of [`Self::input_hash`]: `\"strong\"` (every observed upstream is a content hash — offline byte-verifiable) or `\"heuristic\"` (at least one is a freshness signature, attesting freshness rather than byte-identity). Carried so a weak input hash is never presented as a content claim. `None` whenever [`Self::input_hash`] is `None`.",
            "type": [
              "string",
              "null"
            ]
          },
          "recipe_hash": {
            "description": "The program **identity** key: blake3 (hex) of the canonical `ModelIr` JSON. Stable across environments and engine versions for the same program text. The value `rocky history --recipe <hash>` filters on.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "type": "object"
      },
      "RedactedString": {
        "type": "string"
      },
      "RejectedApproval": {
        "description": "An approval artifact that was loaded from disk but rejected.",
        "properties": {
          "approval_id": {
            "type": "string"
          },
          "detail": {
            "type": "string"
          },
          "reason": {
            "description": "One of: `bad_signature`, `state_hash_mismatch`, `expired`, `signer_not_allowed`, `parse_error`.",
            "type": "string"
          }
        },
        "required": [
          "approval_id",
          "detail",
          "reason"
        ],
        "type": "object"
      },
      "ReplayCheckInputOutput": {
        "description": "Resolvability of one declared upstream inside a [`ReplayCheckModelOutput`].",
        "properties": {
          "kind": {
            "description": "`content` (a recorded blake3 in the artifact ledger) or `watermark` (a freshness signal over a mutable source).",
            "type": "string"
          },
          "reason": {
            "description": "Why the input does not resolve; `null` when resolvable.",
            "type": [
              "string",
              "null"
            ]
          },
          "resolvable": {
            "description": "Whether this input resolves from the ledger for a deterministic replay.",
            "type": "boolean"
          },
          "upstream_key": {
            "description": "Fully-qualified `catalog.schema.table` identity of the upstream.",
            "type": "string"
          }
        },
        "required": [
          "kind",
          "resolvable",
          "upstream_key"
        ],
        "type": "object"
      },
      "ReplayCheckModelOutput": {
        "description": "Per-model replayability verdict inside a [`ReplayCheckOutput`].",
        "properties": {
          "has_provenance": {
            "description": "Whether a provenance record was found for this `(run, model)`.",
            "type": "boolean"
          },
          "inputs": {
            "description": "Per-input resolvability, one entry per declared upstream.",
            "items": {
              "$ref": "#/components/schemas/ReplayCheckInputOutput"
            },
            "type": "array"
          },
          "ir_parseable": {
            "description": "Whether the embedded canonical `ModelIr` deserialized under the current engine. `false` both when there is no provenance and when a provenance record's IR failed to parse (an IR forward-compat break).",
            "type": "boolean"
          },
          "model_name": {
            "type": "string"
          },
          "nondeterministic": {
            "description": "Static-scan non-determinism flag (via `rocky_sql::determinism`): the model's SQL contains a volatile construct (`now()`, `current_timestamp`, `random()`, an unresolved function, ...). Orthogonal to `verdict`.",
            "type": "boolean"
          },
          "proof_class": {
            "description": "Match-strength label carried on the provenance record (`strong` or `heuristic`); `null` when no provenance was found.",
            "type": [
              "string",
              "null"
            ]
          },
          "reasons": {
            "description": "Human-readable reasons the model is not replayable; empty when replayable.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "verdict": {
            "description": "`replayable` or `non_replayable`.",
            "type": "string"
          }
        },
        "required": [
          "has_provenance",
          "inputs",
          "ir_parseable",
          "model_name",
          "nondeterministic",
          "reasons",
          "verdict"
        ],
        "type": "object"
      },
      "ReplayCheckOutput": {
        "description": "JSON output for `rocky replay --at <run_id> --check`.\n\nRead-only replayability audit: classifies every model in a recorded run as replayable or not, using only the state-store ledger — the model's [`rocky_core::state::ProvenanceRecord`] (which embeds the canonical `ModelIr`) and the content-addressed artifact rows. Nothing is executed and the working tree is never read: the recording is the source of truth.\n\nA model is `replayable` when its provenance record exists, its embedded IR parses under the current engine, and every declared input resolves from the ledger. The `nondeterministic` flag on each model is orthogonal to the verdict — a nondeterministic model is still replayable, but a future re-execution may legitimately diverge.",
        "properties": {
          "command": {
            "type": "string"
          },
          "model_count": {
            "description": "Total number of models considered (after any `--model` filter).",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "models": {
            "items": {
              "$ref": "#/components/schemas/ReplayCheckModelOutput"
            },
            "type": "array"
          },
          "replayable": {
            "description": "`true` iff every model in the run is replayable.",
            "type": "boolean"
          },
          "replayable_count": {
            "description": "Number of those models classified replayable.",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "run_id": {
            "type": "string"
          },
          "status": {
            "description": "Recorded run status (`success`, `partial_failure`, ...).",
            "type": "string"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "command",
          "model_count",
          "models",
          "replayable",
          "replayable_count",
          "run_id",
          "status",
          "version"
        ],
        "type": "object"
      },
      "ReplayExecuteModelOutput": {
        "description": "Per-model re-execution verdict inside a [`ReplayExecuteOutput`].",
        "properties": {
          "computed_hash": {
            "description": "The blake3 re-derived by this replay execution; `null` when the recipe was not executed (a `non_replayable` verdict).",
            "type": [
              "string",
              "null"
            ]
          },
          "model_name": {
            "type": "string"
          },
          "nondeterministic": {
            "description": "Static-scan non-determinism flag (via `rocky_sql::determinism`). When `true`, a `diverged` verdict is expected rather than a failure.",
            "type": "boolean"
          },
          "reasons": {
            "description": "Human-readable reasons for a `non_replayable` verdict, or a note on an expected `diverged`; empty on a clean `bit_exact`.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "recorded_hash": {
            "description": "The recorded output blake3 carried on the provenance record; `null` when the record held no output hash.",
            "type": [
              "string",
              "null"
            ]
          },
          "rows": {
            "description": "Rows produced by the re-execution; `null` when not executed.",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "verdict": {
            "description": "The verdict for this model. One of:\n\n- `bit_exact` — re-execution reproduced the recorded output blake3 (reachable only through a successful re-execution whose digest matched the recording); - `diverged` — re-execution succeeded but the blake3 differs (expected when `nondeterministic` is set; a genuine reproducibility gap otherwise); - `executed` — re-executed without `--verify`, so no comparison was made; - `non_replayable` — the recording alone was insufficient to re-execute the model (see `reasons`).\n\nA `value_equal_order_diff` verdict is reserved for a later order-insensitive refinement; v1 compares the raw blake3 only, so a row-order difference reports `diverged` (over-sensitivity fails safe).",
            "type": "string"
          }
        },
        "required": [
          "model_name",
          "nondeterministic",
          "reasons",
          "verdict"
        ],
        "type": "object"
      },
      "ReplayExecuteOutput": {
        "description": "JSON output for `rocky replay --at <run_id> --execute [--verify]`.\n\nRe-execution surface. Each model's recipe is reconstructed from its recorded [`rocky_core::state::ProvenanceRecord`] — never the working tree — re-executed, and its output artifact's blake3 re-derived. With `--verify` that digest is compared against the recorded output hash and a per-model verdict is emitted. Execution runs on an in-memory DuckDB engine by default; with `--warehouse` it runs on the configured live warehouse instead, materializing replayed outputs into an isolated replay schema (see `replay_schema`) and encoding the recomputed artifact with the live table's physical column mapping so the digest is directly comparable to what the content-addressed writer recorded.\n\nTwo modes share this shape:\n\n- `--model <m>` re-executes a single, *self-contained* recipe on a throwaway engine. A recipe with recorded content upstreams is `non_replayable` in this mode — resolving those inputs is the DAG mode below. - no `--model` re-executes the *whole run* in topological order on one shared engine, materializing each upstream's **replayed** output so a downstream `SELECT` reads the replayed bytes (never the recorded object-store bytes, never production). A downstream whose in-run upstream could not be replayed is `non_replayable` (fail-closed cascade); an upstream not produced by any model in this run, or a mutable-source watermark, is likewise `non_replayable` rather than substituted.\n\nNo production identity is ever touched in either mode: the local path's entire in-memory engine is an ephemeral replay namespace discarded after the run, and the warehouse path writes only into the isolated `replay_schema`, dropped after the run unless `--keep` is passed.",
        "properties": {
          "bit_exact_count": {
            "description": "Number of models whose re-execution reproduced the recorded output byte-for-byte. Always `0` when `--verify` was not requested.",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "command": {
            "type": "string"
          },
          "model_count": {
            "description": "Total number of models considered (after any `--model` filter).",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "models": {
            "items": {
              "$ref": "#/components/schemas/ReplayExecuteModelOutput"
            },
            "type": "array"
          },
          "replay_schema": {
            "description": "Isolated warehouse schema the replayed outputs were materialized into (`--warehouse` only; absent on the local re-execution path). Replay never writes to the production location of any recorded target — every replayed table lands in this namespace, one schema per catalog touched.",
            "type": [
              "string",
              "null"
            ]
          },
          "replay_schema_dropped": {
            "description": "Whether the replay namespace was removed after the run (`--warehouse` only). `true` when no replay schema remains on the warehouse; `false` when `--keep` was passed or a drop failed.",
            "type": [
              "boolean",
              "null"
            ]
          },
          "run_id": {
            "type": "string"
          },
          "status": {
            "description": "Recorded run status (`success`, `partial_failure`, ...).",
            "type": "string"
          },
          "verified": {
            "description": "Whether `--verify` was requested (blake3 comparison performed).",
            "type": "boolean"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "bit_exact_count",
          "command",
          "model_count",
          "models",
          "run_id",
          "status",
          "verified",
          "version"
        ],
        "type": "object"
      },
      "ReplayModelOutput": {
        "description": "A single model execution inside a replayed run.",
        "properties": {
          "bytes_scanned": {
            "description": "Adapter-reported bytes figure used for cost accounting. This is the *billing-relevant* number per adapter, not literal scan volume:\n\n- **BigQuery:** `totalBytesBilled` — includes the 10 MB per-query minimum floor; matches the BigQuery console's \"Bytes billed\" field, **not** \"Bytes processed\". - **Databricks:** when populated, byte count from the statement-execution manifest (`total_byte_count`); `None` today until the manifest plumbing lands. - **Snowflake:** `None` — deferred by design (QUERY_HISTORY round-trip cost; Snowflake cost is duration × DBU, not bytes-driven). - **DuckDB:** `None` — no billed-bytes concept.",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "bytes_written": {
            "description": "Adapter-reported bytes-written figure. Currently `None` on every adapter — BigQuery doesn't expose a bytes-written figure for query jobs, and the Databricks / Snowflake paths haven't wired it yet.",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "duration_ms": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "finished_at": {
            "type": "string"
          },
          "model_name": {
            "type": "string"
          },
          "rows_affected": {
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "sql_hash": {
            "type": "string"
          },
          "started_at": {
            "type": "string"
          },
          "status": {
            "type": "string"
          }
        },
        "required": [
          "duration_ms",
          "finished_at",
          "model_name",
          "sql_hash",
          "started_at",
          "status"
        ],
        "type": "object"
      },
      "ReplayOutput": {
        "description": "JSON output for `rocky replay <run_id|latest>`.\n\nInspection-only surface over the state store's [`RunRecord`]: shows every model that ran, with the SQL hash, row counts, bytes, and timings captured at the time. Re-execution is the separate `rocky replay --execute` surface ([`ReplayExecuteOutput`]), which re-runs recorded models on an ephemeral engine and byte-compares to the recording.",
        "properties": {
          "command": {
            "type": "string"
          },
          "config_hash": {
            "type": "string"
          },
          "finished_at": {
            "type": "string"
          },
          "models": {
            "items": {
              "$ref": "#/components/schemas/ReplayModelOutput"
            },
            "type": "array"
          },
          "run_id": {
            "type": "string"
          },
          "started_at": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "trigger": {
            "type": "string"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "command",
          "config_hash",
          "finished_at",
          "models",
          "run_id",
          "started_at",
          "status",
          "trigger",
          "version"
        ],
        "type": "object"
      },
      "ReplicationPipelineConfig": {
        "description": "Replication pipeline configuration.\n\nCopies tables from a source to a target using schema pattern discovery, with optional incremental strategy, metadata columns, governance, and data quality checks.",
        "properties": {
          "checks": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ChecksConfig"
              }
            ],
            "default": {
              "anomaly_threshold_pct": 0.0,
              "assertions": [],
              "column_match": false,
              "custom": [],
              "enabled": false,
              "fail_on_error": false,
              "freshness": null,
              "null_rate": null,
              "quarantine": null,
              "row_count": false
            },
            "description": "Data quality checks."
          },
          "depends_on": {
            "default": [],
            "description": "Pipeline dependencies for chaining.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "execution": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ExecutionConfig"
              }
            ],
            "default": {
              "concurrency": "adaptive",
              "error_rate_abort_pct": 50,
              "fail_fast": false,
              "table_retries": 1
            },
            "description": "Execution settings (concurrency, retries, etc.)."
          },
          "merge_keys": {
            "description": "Unique-key columns for `strategy = \"merge\"`. The `MERGE` statement joins source rows onto the target on these columns; matched rows are updated, unmatched rows are inserted.\n\nRequired when `strategy = \"merge\"` and `merge_keys_fallback` is absent. Ignored for other strategies.",
            "items": {
              "type": "string"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "merge_keys_fallback": {
            "description": "Fallback unique-key columns used when `merge_keys` is not configured. Provides a single source of merge-key defaults for callers that derive keys from another source (e.g. the discovery adapter) but still want pipeline-level control.",
            "items": {
              "type": "string"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "metadata_columns": {
            "default": [],
            "description": "Metadata columns added during replication.",
            "items": {
              "$ref": "#/components/schemas/MetadataColumnConfig"
            },
            "type": "array"
          },
          "prune_unchanged": {
            "default": false,
            "description": "When `true`, the replication runner skips (\"prunes\") any table whose source is provably unchanged since the last successful copy — detected via the adapter's `source_change_marker` (a `DESCRIBE DETAIL`-derived marker on Databricks; adapters without a cheap change signal always copy). A pruned table runs no copy and no data checks: the runner emits no materialization and records it under `excluded_tables` with reason `\"unchanged_since_last_copy\"`.\n\nDownstream continuity — and preserving the table's prior check results — is then the orchestrator's job. The Dagster integration treats a pruned, unmaterialized key as unchanged via `satisfy_empty_outputs`; enabling pruning without an orchestrator that handles unmaterialized keys drops the table from the run.\n\nDefaults to `false` — opt in per pipeline, since silently skipping copies is a behavior change. The marker is compared against the target's recorded last-copied value (never wall-clock), so a failed prior run cannot cause a false skip. Pass `--no-prune` to `rocky run` to force a full pass (e.g. after a manual target-side mutation).",
            "type": "boolean"
          },
          "source": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PipelineSourceConfig"
              }
            ],
            "description": "Source configuration."
          },
          "strategy": {
            "default": "incremental",
            "description": "Replication strategy. Accepted values:\n\n- `\"incremental\"` — append rows past the source-side watermark. - `\"full_refresh\"` — `CREATE OR REPLACE TABLE … AS SELECT …`. - `\"merge\"` — upserts the watermarked delta into the target via `MERGE INTO … USING (delta) ON merge_keys WHEN MATCHED UPDATE SET * WHEN NOT MATCHED INSERT *`. Requires `merge_keys` (or `merge_keys_fallback`) to be set. - `\"view\"` — emits a `CREATE OR REPLACE VIEW` over the source. Every read against the target re-runs the SELECT; no row movement. - `\"materialized_view\"` — warehouse-managed materialized view. Supported on Databricks, Snowflake, and BigQuery; DuckDB/Trino surface an unsupported-dialect error. - `\"dynamic_table\"` — Snowflake-only. Not configurable at the pipeline level today (the strategy needs a `target_lag` specifier that the pipeline block doesn't expose); declare it on a transformation model's sidecar TOML instead.",
            "type": "string"
          },
          "table_overrides": {
            "description": "Per-`(connector, table)` overrides applied on top of the pipeline defaults. Each rule matches against a discovered connector + table pair and, when matched, replaces selected pipeline-level fields with override-supplied values.\n\nResolution is **per-field most-specific-match-wins**: for each overrideable field, the most specific matching rule that explicitly sets that field wins; less-specific rules contribute values only for fields they actually set. Specificity ranking (highest to lowest):\n\n1. Both `match.connector` AND `match.table` set, with `match.table` a literal (no glob). 2. Both set, with `match.table` containing a `*` or `?` glob. 3. Only `match.connector` set. 4. Only `match.table` set.\n\nTies at the same specificity tier for a `(connector, table)` pair are rejected at parse time as ambiguous.\n\nEmpty by default — projects that don't need per-table tweaking pay nothing in JSON output, schema surface area, or runtime cost.",
            "items": {
              "$ref": "#/components/schemas/TableOverride"
            },
            "type": "array"
          },
          "target": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PipelineTargetConfig"
              }
            ],
            "description": "Target configuration."
          },
          "timestamp_column": {
            "default": "_fivetran_synced",
            "description": "Timestamp column for incremental strategy.",
            "type": "string"
          }
        },
        "required": [
          "source",
          "target"
        ],
        "type": "object"
      },
      "RequiredColumn": {
        "description": "A column that must exist in the source with a specific type.",
        "properties": {
          "name": {
            "type": "string"
          },
          "nullable": {
            "default": true,
            "type": "boolean"
          },
          "type": {
            "description": "Expected type, written in warehouse vocabulary (e.g. `BIGINT`, `VARCHAR`, `NUMBER(38,0)`). It is normalized to a portable Rocky type before comparison, so the same contract ports across warehouses (DuckDB `VARCHAR` and Snowflake `STRING` both match). A type the normalizer doesn't recognize is treated as unknown and never fails the type check — presence and nullability still apply.",
            "type": "string"
          }
        },
        "required": [
          "name",
          "type"
        ],
        "type": "object"
      },
      "ResilienceConfig": {
        "additionalProperties": false,
        "description": "`[resilience]` — the run loop's classified-retry policy.\n\nThis is a **distinct layer** from the per-adapter `[adapter.*.retry]` (which retries individual statements inside a connector) and from the run-level `[retry]` budget ([`RunRetryConfig`], which caps connector retries). `[resilience]` governs whether the run loop re-runs a whole *model* whose materialization failed, classifying the failure via [`crate::failure_class::FailureClass`] and retrying only a *proven* transient one.\n\n# Not default-OFF, but conservative\n\nUnlike the skip / reuse gates, this layer is **on by default** — a model that fails transiently today *will* be retried once this ships. The lever that keeps that safe is [`Self::transient_max_retries`] (default `2`): a small, bounded budget with capped exponential backoff. Set `transient_max_retries = 0` (or `enabled = false`) to restore the prior single-attempt behaviour, e.g. in CI where a fast-fail is preferred.\n\nPermanent and Unknown failures are **never** retried regardless of these settings — that is a property of the classifier, not of this config.",
        "properties": {
          "auto_apply_additive_drift": {
            "default": false,
            "description": "Opt in to policy-governed auto-apply of **additive** source drift. Default `false` — a run detecting a new nullable upstream column evolves the target exactly as it does today (unconditionally), with no policy gate. When `true`, any drift mutation must first clear the policy plane: only a *provably additive* change with an `allow` verdict for the `schema_change.additive` capability is auto-applied; anything else (a drop, retype, narrowing, or a scope without the grant) is refused and left for review rather than mutated. Has no effect unless a `[policy]` block grants the capability, so both the opt-in **and** a policy rule are required to change behaviour.",
            "type": "boolean"
          },
          "backoff_multiplier": {
            "default": 2.0,
            "description": "Multiplier applied to the backoff after each retry.",
            "format": "double",
            "type": "number"
          },
          "circuit_breaker_threshold": {
            "default": 3,
            "description": "Trip the run-loop breaker after this many *consecutive* transient model failures; once tripped, no further model is retried for the rest of the run (they still get their one attempt). Default: `3`. `0` disables the breaker.",
            "format": "uint32",
            "minimum": 0.0,
            "type": "integer"
          },
          "contain_failures": {
            "default": false,
            "description": "Continue disjoint subgraphs when a model fails, instead of aborting the whole run at the first failure. Default `false` (fail-fast — the run stops at the first failing model, exactly as before this knob existed).\n\nWhen `true`, a failed model and its downstream closure are *withheld* (never built on the failure's stale/missing output), while unrelated subtrees still materialize; the run reports `PartialFailure` with a containment manifest naming what failed and its blast radius. This changes no data semantics — everything withheld is already withheld by today's fail-fast run; it only *narrows* the withholding to the actual blast radius. The closure is conservative (computed from the resolved dependency graph, contain-more on any doubt).",
            "type": "boolean"
          },
          "enabled": {
            "default": true,
            "description": "Master switch for run-loop classified retry. Default `true`. When `false`, every model is attempted exactly once (no classification, no backoff) — the behaviour before this layer existed.",
            "type": "boolean"
          },
          "initial_backoff_ms": {
            "default": 500,
            "description": "Initial backoff (ms) before the first retry.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "jitter": {
            "default": true,
            "description": "Add ±25 % jitter so concurrent runs don't retry in lockstep.",
            "type": "boolean"
          },
          "max_backoff_ms": {
            "default": 30000,
            "description": "Maximum backoff (ms) — caps the exponential growth.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "max_retries_per_run": {
            "default": 8,
            "description": "Optional ceiling on the *total* number of retries across all models in one run — a global budget separate from the per-adapter one. Default `Some(8)`: a conservative cap so one flaky layer can't spin the whole run. `None` removes the ceiling (per-model `transient_max_retries` is then the only bound); `Some(0)` forbids all retries.",
            "format": "uint32",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "transient_max_retries": {
            "default": 2,
            "description": "Maximum re-runs of a model that failed with a *transient* class. Conservative default: `2` (so at most three attempts total). `0` disables retry while leaving the classifier active for observability.",
            "format": "uint32",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "type": "object"
      },
      "ResolvedCheckNameOutput": {
        "description": "A resolved check name `rocky discover` projects so a consumer can pre-declare a matching check spec. `name` byte-matches the `CheckResult.name` the runner emits at run time. `candidate` is `true` for names whose existence depends on runtime-discovered siblings (`cross_source_overlap`) and so may not be emitted on every run.",
        "properties": {
          "candidate": {
            "type": "boolean"
          },
          "kind": {
            "description": "The check-kind tag: `custom` | `assertion` | `null_rate` | `cross_source_overlap`.",
            "type": "string"
          },
          "name": {
            "type": "string"
          }
        },
        "required": [
          "kind",
          "name"
        ],
        "type": "object"
      },
      "RestoreApplyOutput": {
        "description": "JSON output of `rocky apply <restore-plan>`.\n\nReports the restoration outcome per planned tombstone: restored (rebuilt hash-exact, or verified already-present), refused (with the exact fail-closed reason), or already restored (idempotent no-op verified against the live bytes).",
        "properties": {
          "already_restored": {
            "description": "Content hashes of plan entries whose tombstone was already consumed by a prior restore and whose live bytes re-verified — idempotent no-ops.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "bytes_restored": {
            "description": "Total bytes restored (including verified-already-present bytes).",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "command": {
            "type": "string"
          },
          "notes": {
            "description": "Operator caveats (re-derivation engine, verification order, overwrite rule).",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "plan_id": {
            "type": "string"
          },
          "refused": {
            "description": "Artifacts refused by a fail-closed guard; nothing was written for them.",
            "items": {
              "$ref": "#/components/schemas/RestoreRefusedOutput"
            },
            "type": "array"
          },
          "refused_count": {
            "description": "Count of refused artifacts.",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "restored": {
            "description": "Artifacts restored — hash-verified bytes at the tombstoned path and a reinstated ledger row.",
            "items": {
              "$ref": "#/components/schemas/RestoredOutput"
            },
            "type": "array"
          },
          "restored_count": {
            "description": "Count of restored artifacts.",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "already_restored",
          "bytes_restored",
          "command",
          "notes",
          "plan_id",
          "refused",
          "refused_count",
          "restored",
          "restored_count",
          "version"
        ],
        "type": "object"
      },
      "RestorePlanOutput": {
        "description": "JSON output of `rocky restore <target>` (plan mode).\n\nThe plan has been written to the plan store; this reports its id and the resolved tombstone(s). Restoration is symmetric-caution gated like gc: the operator must `rocky review <plan-id> --approve` and then `rocky apply <plan-id>` — the plan itself writes nothing.",
        "properties": {
          "command": {
            "type": "string"
          },
          "notes": {
            "description": "Operator caveats (re-derivation source, hash-exactness gate, no-overwrite rule).",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "plan_id": {
            "description": "The persisted plan id — pass to `rocky review` then `rocky apply`.",
            "type": "string"
          },
          "restoration_count": {
            "description": "Number of tombstoned artifacts the plan restores.",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "restorations": {
            "description": "The resolved restorations.",
            "items": {
              "$ref": "#/components/schemas/RestorePlanRestoration"
            },
            "type": "array"
          },
          "review_required": {
            "description": "Always `true`: a restore plan is unconditionally review-gated.",
            "type": "boolean"
          },
          "target": {
            "description": "The user-supplied target the resolution matched.",
            "type": "string"
          },
          "total_bytes": {
            "description": "Total tombstoned bytes the plan restores.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "command",
          "notes",
          "plan_id",
          "restoration_count",
          "restorations",
          "review_required",
          "target",
          "total_bytes",
          "version"
        ],
        "type": "object"
      },
      "RestorePlanRestoration": {
        "description": "One tombstoned artifact scheduled for restoration inside a persisted `rocky restore` plan ([`RestorePlan`]).\n\nCaptures the tombstone's full identity — enough to (a) re-locate the exact live tombstone at apply time (the plan is advisory; apply re-resolves against the live custody ledger), and (b) report the restoration to the reviewer. Identity is always the full `(run, model, path, hash)` tuple.",
        "properties": {
          "blake3_hash": {
            "description": "Content hash (hex) of the evicted bytes — the identity the restore re-computes and asserts equality against **before any write**.",
            "type": "string"
          },
          "commit_version": {
            "description": "Delta commit version the artifact was attached to, for correlation.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "evicted_at": {
            "description": "When the artifact was evicted (RFC 3339) — with the hash, this is the tombstone's ledger key.",
            "type": "string"
          },
          "file_path": {
            "description": "Object-store path the restore re-materializes to (the tombstoned location).",
            "type": "string"
          },
          "gc_plan_id": {
            "description": "The `rocky gc` plan that authorized the eviction (custody back-link).",
            "type": "string"
          },
          "input_hash": {
            "description": "Input-closure hash captured on the tombstone; `null` when unrecorded.",
            "type": [
              "string",
              "null"
            ]
          },
          "input_proof_class": {
            "description": "Input match-strength (`strong` / `heuristic`); `null` when unrecorded.",
            "type": [
              "string",
              "null"
            ]
          },
          "model_name": {
            "description": "Model that produced the evicted artifact.",
            "type": "string"
          },
          "recipe_hash": {
            "description": "Recipe-identity hash captured on the tombstone; `null` when unrecorded.",
            "type": [
              "string",
              "null"
            ]
          },
          "run_id": {
            "description": "Run that produced it — half of the provenance key the restore replays from.",
            "type": "string"
          },
          "size_bytes": {
            "description": "Physical size of the evicted artifact in bytes.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "blake3_hash",
          "commit_version",
          "evicted_at",
          "file_path",
          "gc_plan_id",
          "model_name",
          "run_id",
          "size_bytes"
        ],
        "type": "object"
      },
      "RestoreRefusedOutput": {
        "description": "One artifact `rocky apply <restore-plan>` **refused** to restore — the fail-closed verification caught a mismatch, so nothing was written for it.",
        "properties": {
          "blake3_hash": {
            "type": "string"
          },
          "model_name": {
            "type": "string"
          },
          "reason": {
            "description": "Why the restoration was refused. A recomputed-hash mismatch is surfaced honestly — it means the tombstone's rebuild claim failed, which is itself a finding, never papered over.",
            "type": "string"
          },
          "run_id": {
            "type": "string"
          },
          "size_bytes": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "blake3_hash",
          "model_name",
          "reason",
          "run_id",
          "size_bytes"
        ],
        "type": "object"
      },
      "RestoredOutput": {
        "description": "One artifact successfully restored by `rocky apply <restore-plan>` — the recomputed blake3 matched the tombstoned hash, the bytes are present at the tombstoned path, and the ledger row was reinstated.",
        "properties": {
          "blake3_hash": {
            "type": "string"
          },
          "bytes_written": {
            "description": "`true` when the restore physically wrote the rebuilt bytes to the tombstoned path; `false` when verified bytes were already present there (an idempotent \"already present, verified\" restore — the ledger row was still reinstated if missing).",
            "type": "boolean"
          },
          "file_path": {
            "type": "string"
          },
          "hash_verified": {
            "description": "Always `true` on this list: the rebuilt bytes' blake3 equalled the tombstoned hash **before** any write became visible.",
            "type": "boolean"
          },
          "model_name": {
            "type": "string"
          },
          "run_id": {
            "type": "string"
          },
          "size_bytes": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "status": {
            "description": "Human-readable restoration outcome.",
            "type": "string"
          }
        },
        "required": [
          "blake3_hash",
          "bytes_written",
          "file_path",
          "hash_verified",
          "model_name",
          "run_id",
          "size_bytes",
          "status"
        ],
        "type": "object"
      },
      "RetentionAction": {
        "description": "Retention-policy application row in `PlanOutput.retention_actions`.",
        "properties": {
          "duration_days": {
            "description": "Retention duration parsed from the sidecar (`\"90d\"` → 90, `\"1y\"` → 365). Flat day count — no leap-year semantics.",
            "format": "uint32",
            "minimum": 0.0,
            "type": "integer"
          },
          "model": {
            "description": "Model name the action targets.",
            "type": "string"
          },
          "warehouse_preview": {
            "description": "Warehouse-native preview of the SQL / TBLPROPERTIES Rocky would issue for this model on the active adapter. `None` on warehouses that don't support a first-class retention knob (BigQuery, DuckDB).",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "duration_days",
          "model"
        ],
        "type": "object"
      },
      "RetentionStatusOutput": {
        "description": "JSON output for `rocky retention-status`.\n\nReports which models declare a `retention = \"<N>[dy]\"` sidecar value and — when `--drift` is set in a future release — whether the warehouse's current retention matches. Today `warehouse_days` is always `None` because the probe is deferred to v2; the schema is stable so v2 can populate the field without a JSON shape break.",
        "properties": {
          "command": {
            "type": "string"
          },
          "models": {
            "items": {
              "$ref": "#/components/schemas/ModelRetentionStatus"
            },
            "type": "array"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "command",
          "models",
          "version"
        ],
        "type": "object"
      },
      "RetentionSweepOutput": {
        "description": "JSON output for `rocky state retention sweep`.\n\nMirrors [`rocky_core::retention::SweepReport`] with the version/command envelope every CLI JSON output carries. `dry_run = true` reports counts that *would* result without touching the state store.",
        "properties": {
          "audit_deleted": {
            "description": "Quality snapshots (`quality_history`) deleted, or that would be deleted.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "audit_kept": {
            "description": "Quality snapshots remaining.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "command": {
            "type": "string"
          },
          "domains": {
            "description": "Domains that were considered in this sweep, as the canonical lowercase strings (`\"history\"`, `\"lineage\"`, `\"audit\"`).",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "dry_run": {
            "description": "`true` when `--dry-run` was set; the state store was left untouched.",
            "type": "boolean"
          },
          "duration_ms": {
            "description": "Wall-clock duration of the sweep in milliseconds.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "lineage_deleted": {
            "description": "DAG snapshots (`dag_snapshots`) deleted, or that would be deleted.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "lineage_kept": {
            "description": "DAG snapshots remaining.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "max_age_days": {
            "description": "Configured retention window in days at the time of the sweep.",
            "format": "uint32",
            "minimum": 0.0,
            "type": "integer"
          },
          "min_runs_kept": {
            "description": "Configured per-domain floor at the time of the sweep.",
            "format": "uint32",
            "minimum": 0.0,
            "type": "integer"
          },
          "runs_deleted": {
            "description": "Run records (`run_history`) deleted, or that would be deleted in dry-run mode.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "runs_kept": {
            "description": "Run records remaining after the sweep.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "traces_deleted": {
            "description": "JSONL trace files removed by the last-N-by-mtime sweep. Always zero for the explicit `rocky state retention sweep` command — the trace sweep is only invoked from `rocky run`'s end-of-run auto-sweep.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "audit_deleted",
          "audit_kept",
          "command",
          "domains",
          "dry_run",
          "duration_ms",
          "lineage_deleted",
          "lineage_kept",
          "max_age_days",
          "min_runs_kept",
          "runs_deleted",
          "runs_kept",
          "traces_deleted",
          "version"
        ],
        "type": "object"
      },
      "RetryConfig": {
        "additionalProperties": false,
        "description": "Retry policy for transient warehouse errors (HTTP 429/503, rate limits, timeouts).\n\nRocky retries transient errors with exponential backoff and optional jitter to prevent thundering herd across concurrent runs.",
        "properties": {
          "backoff_multiplier": {
            "default": 2.0,
            "description": "Backoff multiplier applied after each retry (e.g. 2.0 = double each time).",
            "format": "double",
            "type": "number"
          },
          "circuit_breaker_recovery_timeout_secs": {
            "default": null,
            "description": "Seconds the breaker will stay `Open` before a single trial request is allowed through (half-open state). On trial success the breaker closes and resumes normal traffic; on trial failure it re-opens immediately. `None` preserves the pre-Arc-3 \"manual-reset-only\" behaviour — a tripped breaker stays tripped for the rest of the run.",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "circuit_breaker_threshold": {
            "default": 5,
            "description": "Circuit breaker: trip after this many consecutive transient failures across statements. Once tripped, all subsequent statements fail immediately without attempting execution. Default: 5. Set to 0 to disable.",
            "format": "uint32",
            "minimum": 0.0,
            "type": "integer"
          },
          "initial_backoff_ms": {
            "default": 1000,
            "description": "Initial backoff duration in milliseconds before the first retry.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "jitter": {
            "default": true,
            "description": "Add random jitter to prevent concurrent runs from retrying in lockstep.",
            "type": "boolean"
          },
          "max_backoff_ms": {
            "default": 30000,
            "description": "Maximum backoff duration in milliseconds (caps exponential growth).",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "max_retries": {
            "default": 3,
            "description": "Maximum number of retry attempts. Set to 0 to disable retries (e.g. for CI).\n\nWhen the Fivetran shared circuit breaker is enabled (`[adapter.fivetran.circuit_breaker]` with a non-default backend), keep `max_retries` ≤ 4 so a single 429-storm bursts at most ~5 attempts (`max_retries + 1`) per envelope-fetch before voting `Remote` to the breaker. Higher values lengthen the storm without changing the outcome — the breaker still trips after `failure_threshold` envelope-fetches exhaust their retry budget, and the orchestrator only sees the result after that.",
            "format": "uint32",
            "minimum": 0.0,
            "type": "integer"
          },
          "max_retries_per_run": {
            "default": null,
            "description": "Cross-statement retry budget for a single run (§P2.7). When set, adapters construct a [`crate::retry_budget::RetryBudget`] from this value and decrement it on every retry; once exhausted, remaining statements fail fast with adapter-specific `RetryBudgetExhausted` errors instead of burning the warehouse's rate-limit quota.\n\n`None` (default) keeps legacy behaviour — per-statement [`RetryConfig::max_retries`] is the only bound. `Some(0)` means no retries are allowed for the whole run.",
            "format": "uint32",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "type": "object"
      },
      "ReuseConfig": {
        "additionalProperties": false,
        "description": "`[reuse]` — auditable reuse for content-addressed models.\n\nWhen `enabled = true`, a successful run records, per model, an input-match index entry and an offline-verifiable provenance record (the model's logic key, upstream input identities, output blake3(s), and proof class). That spine is the *input* side; on a later run, an eligible model whose recomputed `input_hash` hits the index for a prior **strong** run may **point-to** that run's already-written parquet — a zero-copy commit that skips the SQL — provided every clause of the runner's fail-closed reuse decision holds. Any doubt builds.\n\n**`enabled` is default-OFF.** `enabled = false` (the default) writes no input-match spine: no extra normalize+hash work, no extra state write, no point-to decision. The point-to path is strong (byte-identical) only on the content-addressed/UniForm write path. Column-level skip is a separate, orthogonal knob — [`ReuseConfig::column_level`], **default-ON** — that engages only on that same content-addressed path.",
        "properties": {
          "column_level": {
            "default": true,
            "description": "**Column-level skip** for content-addressed models. When `true`, an unpartitioned content-addressed model whose logic, environment, and every provably-consumed upstream column are unchanged since its last successful build is **skipped** — its SQL does not run and no new commit is written; the prior output stays authoritative. Skipping on a value change to a consumed column is precisely the silent-staleness bug this gate is built to avoid, so the decision is fail-closed: any unproven input (a non-deterministic model, a changed recipe/env, an un-enumerable consumed set, a missing or moved column hash) forces a build.\n\n**Default-ON.** `true` (the default) lets a content-addressed model skip when its consumed inputs are provably unchanged; set `column_level = false` to restore the always-build behavior. Independent of [`Self::enabled`] (byte-level point-to reuse) — the two are orthogonal on the content-addressed path, and off that path the feature does not apply (so on the common non-content-addressed run this default is inert).",
            "type": "boolean"
          },
          "enabled": {
            "default": false,
            "description": "Master switch for auditable reuse: populates the input-match spine and arms the point-to decision. `false` (default) ⇒ the spine is never written, no per-model hashing cost is paid, and no model reuses.",
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "ReviewOutput": {
        "description": "JSON output for `rocky review <plan-id>`.\n\n`rocky review` is the human sign-off gate for an AI-authored plan. It compares the working-tree models against `base_ref`, classifies the semantic delta, and either reports the findings (dry run) or — with `--approve` — writes a review marker that unblocks `rocky apply`.\n\nThe marker is written even when breaking changes exist, on the premise that the human approving has seen this report and is signing off on them explicitly. `breaking_changes` therefore always lists the full classified delta so the approval is informed.",
        "properties": {
          "approved": {
            "description": "True when this invocation wrote the approval marker (i.e. `--approve` was set). When false, the review was a dry run and `rocky apply` stays blocked.",
            "type": "boolean"
          },
          "base_ref": {
            "description": "Git ref the working tree was compared against (default `HEAD`).",
            "type": "string"
          },
          "breaking_changes": {
            "description": "Semantic breaking-change findings between `base_ref` and the working tree. Empty when the classifier ran and found no breaking changes; absent when the gate was skipped (compile failure on either side, or the models directory was unavailable).",
            "items": {
              "$ref": "#/components/schemas/BreakingFinding"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "command": {
            "type": "string"
          },
          "marker_written": {
            "description": "True when the approval marker is now present on disk as a result of this invocation. Mirrors `approved` today; kept distinct so callers reading the JSON do not have to infer marker state from the flag.",
            "type": "boolean"
          },
          "message": {
            "description": "Human-readable summary of the review outcome.",
            "type": [
              "string",
              "null"
            ]
          },
          "plan_id": {
            "description": "The AI-authored plan being reviewed (64-char blake3 hex).",
            "type": "string"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "approved",
          "base_ref",
          "command",
          "marker_written",
          "plan_id",
          "version"
        ],
        "type": "object"
      },
      "ReviewQueueEntry": {
        "description": "One pending `require_review` escalation inside [`ReviewQueueOutput`].",
        "properties": {
          "approve_command": {
            "description": "The exact command that clears this escalation.",
            "type": "string"
          },
          "blast_radius": {
            "description": "Count of models that transitively consume this one — the blast radius the ranking weighs. `null` when the model could not be located in the compiled graph (e.g. it was removed), in which case the ranking treats the blast radius as zero and this entry says so.",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "capability": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PolicyCapability"
              }
            ],
            "description": "The capability that was evaluated (its `schema_change.*` refinement is the change class the ranking weighs)."
          },
          "classification_weight": {
            "description": "The change-class weight the ranking used (breaking > bare verb > additive / value-only).",
            "format": "uint32",
            "minimum": 0.0,
            "type": "integer"
          },
          "decision_ref": {
            "description": "Composite ledger key (`\"{timestamp}|{plan_id}|{model}\"`) — the stable identity a governor drills into via `rocky audit --for`.",
            "type": "string"
          },
          "model": {
            "description": "The model the escalation is about.",
            "type": "string"
          },
          "plan_id": {
            "description": "The plan whose approval clears this escalation.",
            "type": "string"
          },
          "principal": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PolicyPrincipal"
              }
            ],
            "description": "Who authored the change (`human` / `agent`)."
          },
          "reason": {
            "description": "Human-readable explanation of how `require_review` was reached.",
            "type": "string"
          },
          "rule_id": {
            "description": "Index of the winning `[[policy.rules]]` entry, or `null` when the escalation came from the default posture.",
            "format": "uint",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "score": {
            "description": "The composite priority score. Higher sorts first. Reported so a consumer can re-rank or explain the ordering without recomputing it.",
            "format": "double",
            "type": "number"
          },
          "staleness_seconds": {
            "description": "How long the escalation has waited, in whole seconds.",
            "format": "int64",
            "type": "integer"
          },
          "timestamp": {
            "description": "RFC 3339 timestamp when the decision was recorded.",
            "type": "string"
          }
        },
        "required": [
          "approve_command",
          "capability",
          "classification_weight",
          "decision_ref",
          "model",
          "plan_id",
          "principal",
          "reason",
          "score",
          "staleness_seconds",
          "timestamp"
        ],
        "type": "object"
      },
      "ReviewQueueOutput": {
        "description": "JSON output for `rocky review --queue` — the pending-review work queue.\n\nLists every `require_review` policy decision that has not yet been satisfied by a sign-off marker, ranked so the change most in need of a human floats to the top. The rank blends three signals: how much sits downstream of the model (blast radius), how disruptive the change class is (a breaking schema change outranks an additive one), and how long the escalation has waited (staleness). Each entry carries the exact `rocky review <plan_id> --approve` invocation that clears it — the queue is a triage view; the approval path is unchanged.",
        "properties": {
          "command": {
            "type": "string"
          },
          "excluded_non_plan_rows": {
            "description": "Count of outstanding `require_review` ledger rows excluded from the queue because their `plan_id` resolves to no persisted plan file — decision-only custody rows (e.g. refused drafts, auto-apply evaluations) that nothing could approve. They stay in the audit ledger; they are just not approvable queue items.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "pending": {
            "description": "The pending escalations, highest-priority first.",
            "items": {
              "$ref": "#/components/schemas/ReviewQueueEntry"
            },
            "type": "array"
          },
          "ranking": {
            "description": "Human-readable description of the ordering, e.g. `\"blast_radius × classification × staleness\"`.",
            "type": "string"
          },
          "total": {
            "description": "Count of pending escalations in the queue.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "command",
          "excluded_non_plan_rows",
          "pending",
          "ranking",
          "total",
          "version"
        ],
        "type": "object"
      },
      "RockyConfig": {
        "additionalProperties": false,
        "description": "Top-level Rocky configuration (v2 format).\n\nUses named adapters and named pipelines: ```toml [adapter.databricks_prod] type = \"databricks\" ...\n\n[pipeline.raw_replication] type = \"replication\" ... ```",
        "properties": {
          "adapter": {
            "allOf": [
              {
                "$ref": "#/components/schemas/AdaptersFieldSchema"
              }
            ],
            "default": {},
            "description": "Named adapter configurations (keyed by adapter name)."
          },
          "ai": {
            "allOf": [
              {
                "$ref": "#/components/schemas/AiSection"
              }
            ],
            "default": {
              "max_tokens": 4096
            },
            "description": "AI intent layer configuration. Currently scopes the per-request and cumulative-retry token budget for `rocky ai` / `ai-explain` / `ai-sync` / `ai-test`. See [`AiSection`]."
          },
          "branch": {
            "allOf": [
              {
                "$ref": "#/components/schemas/BranchSection"
              }
            ],
            "default": {
              "approval": {
                "allowed_signers": [],
                "max_age_seconds": 86400,
                "min_approvers": 1,
                "required": false
              }
            },
            "description": "Branch-level configuration. Currently scopes the optional approval gate consumed by `rocky branch promote`. See [`BranchSection`]."
          },
          "budget": {
            "allOf": [
              {
                "$ref": "#/components/schemas/BudgetConfig"
              }
            ],
            "default": {
              "max_bytes_scanned": null,
              "max_duration_ms": null,
              "max_usd": null,
              "on_breach": "warn"
            },
            "description": "Declarative run-level budget. See [`BudgetConfig`] for the semantics of each limit and the breach action."
          },
          "cache": {
            "allOf": [
              {
                "$ref": "#/components/schemas/CacheConfig"
              }
            ],
            "default": {
              "schemas": {
                "enabled": true,
                "replicate": false,
                "ttl_seconds": 86400
              }
            },
            "description": "Project-level cache configuration. Today this is just `[cache.schemas]` (schema cache for `DESCRIBE TABLE` results); future cache surfaces live as sibling fields under [`CacheConfig`]."
          },
          "classifications": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ClassificationsConfig"
              }
            ],
            "default": {
              "allow_unmasked": []
            },
            "description": "Advisory settings for column classification — currently just the `allow_unmasked` list that suppresses W004 warnings."
          },
          "cost": {
            "allOf": [
              {
                "$ref": "#/components/schemas/CostSection"
              }
            ],
            "default": {
              "compute_cost_per_dbu": 0.4,
              "min_history_runs": 5,
              "storage_cost_per_gb_month": 0.023,
              "warehouse_size": "Medium"
            },
            "description": "Cost estimation configuration."
          },
          "freshness": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ProjectFreshnessConfig"
              }
            ],
            "default": {},
            "description": "Project-level freshness defaults inherited by per-model [`crate::models::ModelFreshnessConfig`] declarations that omit individual fields. See [`ProjectFreshnessConfig`] for the TOML shape:\n\n```toml [freshness] expected_lag_seconds = 3600 time_column = \"updated_at\" severity = \"warning\" ```\n\nInheritance is field-by-field: a per-model `[freshness]` table always wins for the fields it sets; absent fields fall through to the project-level default. Models with no per-model `[freshness]` at all inherit the project default when it carries an `expected_lag_seconds` value (the required field)."
          },
          "gc": {
            "allOf": [
              {
                "$ref": "#/components/schemas/GcConfig"
              }
            ],
            "default": {
              "physical_delete": false
            },
            "description": "`[gc]` — storage-reclamation settings for `rocky gc` / `rocky apply <gc-plan>`. Default: physical byte-deletion stays disarmed; an applied eviction is recorded as tombstone + retired ledger row only. See [`GcConfig`]."
          },
          "hook": {
            "allOf": [
              {
                "$ref": "#/components/schemas/HooksConfig"
              }
            ],
            "default": {
              "webhooks": {}
            },
            "description": "Shell hooks configuration."
          },
          "imports": {
            "additionalProperties": {
              "$ref": "#/components/schemas/ImportEntry"
            },
            "default": {},
            "description": "Imported producer-project snapshots, keyed by import name.\n\nEach `[imports.<name>]` block points at a vendored snapshot of a producer project's compiled IR. During `rocky compile`, the consumer's column references are checked against the producer's published schema: a column the producer dropped but the consumer still reads surfaces as an error (E030), and a recipe-hash mismatch against a configured `pin` surfaces as E033. Empty by default — a project with no imports incurs no extra work.",
            "type": "object"
          },
          "mask": {
            "additionalProperties": {
              "$ref": "#/components/schemas/MaskEntry"
            },
            "default": {},
            "description": "Workspace-default column-masking strategies plus optional per-env overrides. See [`MaskEntry`] for the TOML shape:\n\n```toml [mask] pii = \"hash\"            # default strategy for \"pii\" classification confidential = \"redact\" # default strategy for \"confidential\"\n\n[mask.prod] pii = \"none\"            # prod override: do not mask pii confidential = \"partial\" ```\n\nResolved per model at apply time via [`RockyConfig::resolve_mask_for_env`].",
            "type": "object"
          },
          "pipeline": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PipelinesFieldSchema"
              }
            ],
            "default": {},
            "description": "Named pipeline configurations (keyed by pipeline name)."
          },
          "policy": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/PolicyConfig"
              },
              {
                "type": "null"
              }
            ],
            "description": "Agent-authority policy plane. Declares, per `(principal, capability, scope)`, whether an action is allowed, requires human review, or is denied; enforced at `apply`, `promote`, and the MCP write tools, with every decision recorded to the ledger. Absent `[policy]` block ⇒ no rules and the default posture applies (agents on mutating actions fall to `default_agent_effect`, humans are never gated). See [`PolicyConfig`] and [`crate::policy`] for the evaluator."
          },
          "portability": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PortabilityConfig"
              }
            ],
            "default": {
              "allow": [],
              "target_dialect": null
            },
            "description": "Dialect-portability lint configuration. Consumed by `rocky compile` to drive P001 (and, when wired, future) diagnostics. The CLI's `--target-dialect` flag, when set, takes precedence over [`PortabilityConfig::target_dialect`]."
          },
          "resilience": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ResilienceConfig"
              }
            ],
            "default": {
              "auto_apply_additive_drift": false,
              "backoff_multiplier": 2.0,
              "circuit_breaker_threshold": 3,
              "contain_failures": false,
              "enabled": true,
              "initial_backoff_ms": 500,
              "jitter": true,
              "max_backoff_ms": 30000,
              "max_retries_per_run": 8,
              "transient_max_retries": 2
            },
            "description": "`[resilience]` — the run loop's classified-retry policy. Governs whether a model whose materialization fails *transiently* is re-run, with a conservative bounded budget and capped backoff. On by default (a transient failure is retried), but every knob is small and explicit; see [`ResilienceConfig`]. Permanent / Unknown failures never retry."
          },
          "retry": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/RunRetryConfig"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Run-level retry budget shared across every adapter for this run.\n\nWhen set, `rocky run` builds a single [`crate::retry_budget::RetryBudget`] from [`RunRetryConfig::max_retries_per_run`] and passes it to every connector via `with_retry_budget(...)`. One bad table that burns through retries on adapter A then has less budget available for adapter B's retries — the protection §P2.7 added within a single adapter now extends across the whole run.\n\nUnset (the default) preserves per-adapter semantics: each adapter still honours its own `retry.max_retries_per_run` independently. That's the backward-compatible path and stays the right choice when adapters have wildly different rate limits."
          },
          "reuse": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ReuseConfig"
              }
            ],
            "default": {
              "column_level": true,
              "enabled": false
            },
            "description": "Auditable reuse for content-addressed models — two orthogonal knobs. `enabled` (byte-level point-to reuse) is **default-OFF**: an absent `[reuse]` block writes no input-match spine and pays no per-model hashing for it. `column_level` (column-level skip) is **default-ON** but engages only on the content-addressed path, so on the common non-content-addressed run it is inert. When `enabled`, an eligible content-addressed model whose inputs match a prior strong run may **point-to** that run's parquet (zero-copy) instead of re-executing its SQL — fail-closed to BUILD on any doubt. See [`ReuseConfig`]."
          },
          "role": {
            "additionalProperties": {
              "$ref": "#/components/schemas/RoleConfig"
            },
            "default": {},
            "description": "Hierarchical role declarations reconciled against the warehouse's native role/group system.\n\nSee [`RoleConfig`] for the TOML shape and [`crate::role_graph::flatten_role_graph`] for the inheritance resolution semantics (DAG walk with cycle detection).",
            "type": "object"
          },
          "run": {
            "allOf": [
              {
                "$ref": "#/components/schemas/RunConfig"
              }
            ],
            "default": {
              "lag_tolerance_seconds": 0,
              "skip_rowcount_fallback": false,
              "skip_unchanged": false
            },
            "description": "Opt-in run-execution tuning for the `--skip-unchanged` model-skip gate. Default-OFF: an absent `[run]` block (or one that leaves every field at its default) keeps `rocky run`'s behavior byte-identical to before the gate existed. See [`RunConfig`]."
          },
          "schema_evolution": {
            "allOf": [
              {
                "$ref": "#/components/schemas/SchemaEvolutionConfig"
              }
            ],
            "default": {
              "grace_period_days": 7
            },
            "description": "Schema evolution configuration (grace-period column drops)."
          },
          "state": {
            "allOf": [
              {
                "$ref": "#/components/schemas/StateConfig"
              }
            ],
            "default": {
              "backend": "local",
              "gcs_bucket": null,
              "gcs_prefix": null,
              "idempotency": {
                "dedup_on": "success",
                "in_flight_ttl_hours": 24,
                "retention_days": 30
              },
              "namespacing": "none",
              "on_schema_mismatch": "recreate",
              "on_upload_failure": "skip",
              "retention": {
                "applies_to": [
                  "history",
                  "lineage",
                  "audit"
                ],
                "max_age_days": 365,
                "min_runs_kept": 100,
                "sweep_budget_ms": 5000,
                "sweep_interval_seconds": 3600
              },
              "retry": {
                "backoff_multiplier": 2.0,
                "circuit_breaker_recovery_timeout_secs": null,
                "circuit_breaker_threshold": 5,
                "initial_backoff_ms": 1000,
                "jitter": true,
                "max_backoff_ms": 30000,
                "max_retries": 3,
                "max_retries_per_run": null
              },
              "s3_bucket": null,
              "s3_prefix": null,
              "transfer_timeout_seconds": 300,
              "valkey_prefix": null,
              "valkey_url": null
            },
            "description": "Global state persistence configuration."
          }
        },
        "type": "object"
      },
      "RoleConfig": {
        "additionalProperties": false,
        "description": "A single entry in the top-level `[role.*]` block, declaring a hierarchical role with optional inheritance and a list of permissions.\n\n```toml [role.reader] permissions = [\"SELECT\", \"USE CATALOG\", \"USE SCHEMA\"]\n\n[role.analytics_engineer] inherits = [\"reader\"] permissions = [\"MODIFY\"]\n\n[role.admin] inherits = [\"analytics_engineer\"] permissions = [\"MANAGE\"] ```\n\nResolution happens at reconcile time via [`crate::role_graph::flatten_role_graph`], which walks the `inherits` DAG and unions permissions from the role and every transitive ancestor. Cycles and unknown parents are caught as structured [`crate::role_graph::RoleGraphError`] values.\n\nPermission strings must match the canonical uppercase spellings of [`rocky_ir::Permission`] (`\"SELECT\"`, `\"USE CATALOG\"`, ...).",
        "properties": {
          "inherits": {
            "default": [],
            "description": "Immediate parent role names. Rocky walks these transitively at reconcile time; cycles are rejected. Defaults to `[]` when omitted.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "permissions": {
            "default": [],
            "description": "Permissions this role grants. Rocky unions these with every ancestor's permissions before passing the flattened set to the governance adapter. Defaults to `[]` (permissionless grouping roles are legal — they exist only for inheritance).",
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "type": "object"
      },
      "RollingDimension": {
        "description": "Per-dimension rolling statistics (mean, std dev, latest z-score).",
        "properties": {
          "latest_z_score": {
            "description": "Z-score of the most recent execution relative to the window.\n\n`None` when fewer than 2 samples are available or when `std_dev` is exactly 0 (all samples are equal — no meaningful deviation).",
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          },
          "mean": {
            "description": "Population mean over the sample window.",
            "format": "double",
            "type": "number"
          },
          "std_dev": {
            "description": "Population standard deviation (÷N) over the sample window.",
            "format": "double",
            "type": "number"
          }
        },
        "required": [
          "mean",
          "std_dev"
        ],
        "type": "object"
      },
      "RollingStats": {
        "description": "Rolling statistics computed over the most recent N successful executions of a model. Populated by `rocky history --model <name> --rolling-stats`.\n\nStatistics use population standard deviation (divided by N, not N-1), so `std_dev` is exactly 0 when all samples are equal.",
        "properties": {
          "duration_ms": {
            "allOf": [
              {
                "$ref": "#/components/schemas/RollingDimension"
              }
            ],
            "description": "Rolling statistics for the `duration_ms` dimension."
          },
          "health_score": {
            "description": "Composite health score in `[0.0, 1.0]`.\n\nComputed as `1.0 - clamp((max(|z_rows|, |z_duration|) - 2.0) / 4.0, 0.0, 1.0)`. A score of `1.0` means both z-scores are within 2σ of the mean; `0.0` means at least one z-score is 6σ or more.",
            "format": "double",
            "type": "number"
          },
          "rows_affected": {
            "allOf": [
              {
                "$ref": "#/components/schemas/RollingDimension"
              }
            ],
            "description": "Rolling statistics for the `rows_affected` dimension. Computed only over executions where `rows_affected` is not null."
          },
          "samples": {
            "description": "Actual number of successful executions used (≤ window; may be smaller when model history is shorter than the requested window).",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "window": {
            "description": "Maximum number of executions requested for the rolling window.",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "duration_ms",
          "health_score",
          "rows_affected",
          "samples",
          "window"
        ],
        "type": "object"
      },
      "RowMismatch": {
        "description": "A single row mismatch between expected and actual output.",
        "properties": {
          "actual": {
            "type": [
              "string",
              "null"
            ]
          },
          "expected": {
            "type": "string"
          },
          "kind": {
            "$ref": "#/components/schemas/MismatchKind"
          },
          "row_index": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "expected",
          "kind",
          "row_index"
        ],
        "type": "object"
      },
      "RunConfig": {
        "additionalProperties": false,
        "description": "`[run]` — opt-in tuning for the model-skip gate.\n\nThe gate lets `rocky run` skip re-materializing a model whose logic and upstream data both *appear* unchanged since the last successful build. It is a best-effort optimization, **not** a guarantee of result- equivalence: non-deterministic SQL is excluded, and any ambiguity rebuilds. Every field defaults to the safe (no-skip) choice — the whole feature is off unless `skip_unchanged = true` (or the `--skip-unchanged` flag) is set.",
        "properties": {
          "lag_tolerance_seconds": {
            "default": 0,
            "description": "Treat an upstream `MAX(ts)` that moved by fewer than this many seconds as unchanged for the B3 freshness comparison — the late-arriving-but- irrelevant micro-update analog of a freshness SLA threshold. Default `0`: any movement at all forces a rebuild.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "skip_rowcount_fallback": {
            "default": false,
            "description": "Allow a rowcount-only data-stability signal (`COUNT(*)`) when an upstream has no tracked timestamp column. Default `false`: without an explicit opt-in, a model whose upstreams are not watermarkable is not skip-eligible. Rowcount equality is weaker than a watermark: it can miss a same-size in-place `UPDATE` (or a matched insert+delete) that mutates values without changing the row count, so it stays behind this switch.",
            "type": "boolean"
          },
          "skip_unchanged": {
            "default": false,
            "description": "Master switch for the model-skip gate. `false` (default) ⇒ every selected model always builds, exactly as before. The `--skip-unchanged` CLI flag turns the gate on for a single invocation regardless of this value.",
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "RunCostSummary": {
        "description": "Aggregate cost attribution for a `rocky run` invocation.\n\nThe run finalizer walks every [`MaterializationOutput`] in [`RunOutput::materializations`], computes a per-model dollar cost using [`rocky_core::cost::compute_observed_cost_usd`], and stuffs the totals here. Consumers (Dagster, custom dashboards) can then read either the per-model breakdown or the run total without re-deriving the cost model themselves.",
        "properties": {
          "adapter_type": {
            "description": "Adapter type the cost formula was parameterised against, for audit. Values mirror `AdapterConfig.type`: \"databricks\", \"snowflake\", \"bigquery\", \"duckdb\".",
            "type": "string"
          },
          "per_model": {
            "description": "Per-model cost attribution, ordered as in [`RunOutput::materializations`].",
            "items": {
              "$ref": "#/components/schemas/ModelCostEntry"
            },
            "type": "array"
          },
          "total_bytes_scanned": {
            "description": "Sum of every per-model `bytes_scanned` that produced a number. `None` when no materialization reported a byte count (the non-BigQuery adapters today, which still inherit the default stub on `WarehouseAdapter::execute_statement_with_stats`). Surfaced so consumers — and the `[budget]` `max_bytes_scanned` gate — can read scan volume without re-walking [`RunOutput::materializations`].",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "total_cost_usd": {
            "description": "Sum of every per-model `cost_usd` that produced a number. `None` when no materialization produced a cost.",
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          },
          "total_duration_ms": {
            "description": "Wall-clock time summed across every materialization. Separate from [`RunOutput::duration_ms`] (which includes setup / governance overhead) so budget enforcement can target model-compute time specifically.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "adapter_type",
          "per_model",
          "total_duration_ms"
        ],
        "type": "object"
      },
      "RunHistoryRecord": {
        "description": "One run from the state store, mirroring `rocky_core::state::RunRecord` with serializable field types (no enums or non-JSON-friendly Rust types).\n\nThe governance audit fields (`triggering_identity`, `session_source`, `git_commit`, `git_branch`, `idempotency_key`, `target_catalog`, `hostname`, `rocky_version`) are only populated in the `rocky history --audit` shape; the default shape continues to emit just the existing 7 fields. The `#[serde(skip_serializing_if = \"…\")]` attributes keep the default payload byte-identical to schema v5 unless `--audit` flips the fields on.",
        "properties": {
          "duration_ms": {
            "description": "Duration in milliseconds (`finished_at - started_at`).",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "git_branch": {
            "description": "`git symbolic-ref --short HEAD` at claim time, or `None` on detached HEAD.",
            "type": [
              "string",
              "null"
            ]
          },
          "git_commit": {
            "description": "`git rev-parse HEAD` at claim time.",
            "type": [
              "string",
              "null"
            ]
          },
          "hostname": {
            "description": "Machine hostname that produced the run.",
            "type": [
              "string",
              "null"
            ]
          },
          "idempotency_key": {
            "description": "The `--idempotency-key` value supplied to `rocky run`, if any.",
            "type": [
              "string",
              "null"
            ]
          },
          "models": {
            "description": "Per-model execution details for this run.",
            "items": {
              "$ref": "#/components/schemas/RunModelRecord"
            },
            "type": "array"
          },
          "models_executed": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "rocky_version": {
            "description": "`CARGO_PKG_VERSION` of the `rocky` binary, or `\"<pre-audit>\"` on schema-v5 rows that predate the audit trail.",
            "type": [
              "string",
              "null"
            ]
          },
          "run_id": {
            "type": "string"
          },
          "session_source": {
            "description": "Session origin — `\"cli\"`, `\"dagster\"`, `\"lsp\"`, or `\"http_api\"`. Emitted as the lowercase variant string so JSON consumers can match on it without knowing the Rust enum shape.",
            "type": [
              "string",
              "null"
            ]
          },
          "started_at": {
            "format": "date-time",
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "target_catalog": {
            "description": "Best-available target catalog — the `catalog_template` for replication pipelines, the literal `target.catalog` for transformation/quality/snapshot/load.",
            "type": [
              "string",
              "null"
            ]
          },
          "trigger": {
            "type": "string"
          },
          "triggering_identity": {
            "description": "Resolved caller identity (Unix `$USER` / Windows `$USERNAME`). `None` in CI / container contexts where no human caller is discernible.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "duration_ms",
          "models",
          "models_executed",
          "run_id",
          "started_at",
          "status",
          "trigger"
        ],
        "type": "object"
      },
      "RunModelRecord": {
        "description": "Per-model execution record embedded in [`RunHistoryRecord`].",
        "properties": {
          "duration_ms": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "model_name": {
            "type": "string"
          },
          "recipe_identity": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/RecipeIdentityView"
              },
              {
                "type": "null"
              }
            ],
            "description": "The recipe-identity triple recorded for this execution, when present. See [`RecipeIdentityView`]."
          },
          "rows_affected": {
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "status": {
            "type": "string"
          }
        },
        "required": [
          "duration_ms",
          "model_name",
          "status"
        ],
        "type": "object"
      },
      "RunOutput": {
        "description": "JSON output for `rocky run`.",
        "properties": {
          "anomalies": {
            "items": {
              "$ref": "#/components/schemas/AnomalyOutput"
            },
            "type": "array"
          },
          "budget_breaches": {
            "description": "Budget breaches detected at end of run. Empty when no `[budget]` block is configured or all configured limits were respected. Each breach is also emitted as a `budget_breach` [`rocky_observe::events::PipelineEvent`] and fires the `on_budget_breach` hook so subscribers see them live.",
            "items": {
              "$ref": "#/components/schemas/BudgetBreachOutput"
            },
            "type": "array"
          },
          "check_results": {
            "items": {
              "$ref": "#/components/schemas/TableCheckOutput"
            },
            "type": "array"
          },
          "command": {
            "type": "string"
          },
          "contained": {
            "description": "Models withheld this run because an upstream failed (or was itself withheld) and `[resilience] contain_failures` continued the disjoint subgraphs — the blast radius of the failures named in `errors[]`. Empty (and omitted) for a run that did not withhold anything: the default fail-fast run, and any successful run, record nothing here.",
            "items": {
              "$ref": "#/components/schemas/ContainedModelOutput"
            },
            "type": "array"
          },
          "cost_summary": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/RunCostSummary"
              },
              {
                "type": "null"
              }
            ],
            "description": "Aggregate cost attribution across every materialization in this run (per-model entries + run totals). `None` for DuckDB-only pipelines or when no materializations produced a cost number."
          },
          "drift": {
            "$ref": "#/components/schemas/DriftSummary"
          },
          "duration_ms": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "errors": {
            "items": {
              "$ref": "#/components/schemas/TableErrorOutput"
            },
            "type": "array"
          },
          "excluded_tables": {
            "description": "Tables that the discovery adapter reported as enabled but that do not exist in the source warehouse (e.g. Fivetran has them configured but hasn't synced them, or they carry the `do_not_alter__` broken-table marker). Surfaced so orchestrators can flag the gap in their UIs instead of silently dropping the assets.",
            "items": {
              "$ref": "#/components/schemas/ExcludedTableOutput"
            },
            "type": "array"
          },
          "execution": {
            "$ref": "#/components/schemas/ExecutionSummary"
          },
          "filter": {
            "type": "string"
          },
          "idempotency_key": {
            "description": "The `--idempotency-key` value this run was invoked with, echoed back for operator cross-reference in logs and `rocky history`. `None` for runs that didn't pass the flag.",
            "type": [
              "string",
              "null"
            ]
          },
          "interrupted": {
            "description": "`true` when the run was cancelled by a SIGINT (Ctrl-C). Surfaced so orchestrators can distinguish \"user interrupted\" from \"run failed\". Tables that hadn't reached `Success` or `Failed` at interrupt time are recorded as `TableStatus::Interrupted` in the state store. Always serialised (even when `false`) so consumers don't have to treat its absence specially.",
            "type": "boolean"
          },
          "materializations": {
            "items": {
              "$ref": "#/components/schemas/MaterializationOutput"
            },
            "type": "array"
          },
          "metrics": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/MetricsSnapshot"
              },
              {
                "type": "null"
              }
            ]
          },
          "model_decisions": {
            "description": "Per-model build/skip/reuse decision + reason, surfaced for transformation runs so orchestrators can explain *why* each model ran, was skipped, or was reused. Populated only when the `--skip-unchanged` gate is active or `[reuse]` is enabled; empty (and omitted) for a default run, which stays byte-identical.",
            "items": {
              "$ref": "#/components/schemas/ModelDecisionOutput"
            },
            "type": "array"
          },
          "override_warnings": {
            "description": "Soft warnings raised by the per-table override resolver — one entry per `[[table_overrides]]` rule that matched zero `(connector, table)` pairs this run, or whose connector half resolved nothing. Discovery-time-only — the pipeline runs to completion regardless. Empty for runs whose pipeline declares no overrides.",
            "items": {
              "$ref": "#/components/schemas/OverrideWarningOutput"
            },
            "type": "array"
          },
          "partition_summaries": {
            "description": "Per-model partition execution summaries, present only when the run touched one or more `time_interval` models. Empty for runs that didn't execute any partitioned models.",
            "items": {
              "$ref": "#/components/schemas/PartitionSummary"
            },
            "type": "array"
          },
          "permissions": {
            "$ref": "#/components/schemas/PermissionSummary"
          },
          "pipeline_type": {
            "description": "Pipeline type that was executed (e.g., \"replication\").",
            "type": [
              "string",
              "null"
            ]
          },
          "quarantine": {
            "description": "Row-quarantine outcomes — one entry per table the quality pipeline quarantined. Empty for runs that did not use `[pipeline.x.checks.quarantine]`.",
            "items": {
              "$ref": "#/components/schemas/QuarantineOutput"
            },
            "type": "array"
          },
          "resumed_from": {
            "type": [
              "string",
              "null"
            ]
          },
          "shadow": {
            "description": "True when running in shadow mode (targets rewritten).",
            "type": "boolean"
          },
          "skipped_by_run_id": {
            "description": "Prior run whose idempotency key deflected this call, or the run currently holding the in-flight claim. Populated only when `status` is `skipped_idempotent` or `skipped_in_flight`.",
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "allOf": [
              {
                "$ref": "#/components/schemas/RunStatus"
              }
            ],
            "description": "Terminal status of the run. `success` / `partial_failure` / `failure` match the lifecycle semantics callers already understand; the two `skipped_*` variants short-circuit via the idempotency key (see `idempotency_key` below). Always populated — non-skipped runs derive this field from `tables_failed` / materialization counts, so JSON consumers no longer need to re-derive status from counts themselves."
          },
          "tables_copied": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "tables_failed": {
            "description": "Total number of failed tables/models for the run, **including** pre-execution compile failures: a model that fails to type-check is counted here even though it never reached the execution phase. This is the authoritative failure count — consumers should key overall pass/fail on the top-level `tables_failed` / `status` / `errors`, not on `execution.tables_failed`, which counts only execution-phase (copy / runtime) failures and excludes models excluded before execution.",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "tables_skipped": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "check_results",
          "command",
          "drift",
          "duration_ms",
          "execution",
          "filter",
          "interrupted",
          "materializations",
          "permissions",
          "status",
          "tables_copied",
          "tables_failed",
          "version"
        ],
        "type": "object"
      },
      "RunRetryConfig": {
        "additionalProperties": false,
        "description": "Top-level retry configuration applied across every adapter for this run. See [`RockyConfig::retry`] for the cross-adapter semantics this unlocks.",
        "properties": {
          "max_retries_per_run": {
            "default": null,
            "description": "Total number of retries allowed across every adapter for this run. `None` means no cross-adapter cap (each adapter's own `retry.max_retries_per_run` still applies in isolation).",
            "format": "uint32",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "type": "object"
      },
      "RunStatus": {
        "description": "Status of a pipeline run.\n\n`Success` / `PartialFailure` / `Failure` cover the terminal outcomes of a run that actually executed. `SkippedIdempotent` / `SkippedInFlight` are the short-circuit outcomes of `rocky run --idempotency-key` — see [`crate::idempotency`].",
        "oneOf": [
          {
            "enum": [
              "Success",
              "PartialFailure",
              "Failure"
            ],
            "type": "string"
          },
          {
            "description": "The run was short-circuited because an idempotency key already mapped to a prior completed run. No work was done.",
            "enum": [
              "SkippedIdempotent"
            ],
            "type": "string"
          },
          {
            "description": "The run was short-circuited because another caller held the idempotency key's in-flight claim. No work was done.",
            "enum": [
              "SkippedInFlight"
            ],
            "type": "string"
          }
        ]
      },
      "SchemaCacheConfig": {
        "additionalProperties": false,
        "description": "`[cache.schemas]` — schema cache configuration.\n\nControls the DESCRIBE-result cache. Defaults are chosen so the feature is useful out of the box: the cache is on, entries live for 24 hours, and nothing replicates off-machine until the user opts in.",
        "properties": {
          "enabled": {
            "default": true,
            "description": "Enable schema cache reads + writes. Defaults to `true`. Set to `false` for strict CI where every typecheck should resolve against the current warehouse and never fall back to a cached entry.",
            "type": "boolean"
          },
          "replicate": {
            "default": false,
            "description": "Replicate the schema cache via `state_sync` to the remote backend. Defaults to `false`: a dev on a fresh clone should not inherit another machine's stale type stamps. Opt in to `true` for teams that want cross-machine cache warm-up via a shared state backend.",
            "type": "boolean"
          },
          "ttl_seconds": {
            "default": 86400,
            "description": "TTL for cache entries in seconds. Defaults to 86400 (24 hours). Lower it for high-DDL-churn teams; raise it for projects whose sources change on a weekly or slower cadence.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "type": "object"
      },
      "SchemaEvolutionConfig": {
        "additionalProperties": false,
        "description": "Schema evolution configuration.\n\nControls how Rocky handles columns that disappear from the source but still exist in the target table. Instead of immediately dropping them, Rocky can keep them for a grace period (filling with NULL) so downstream consumers have time to adapt.",
        "properties": {
          "grace_period_days": {
            "default": 7,
            "description": "Number of days to keep a dropped column before removing it from the target table. During this window the column is filled with NULL for new rows and a warning is emitted on every run. Default: 7.",
            "format": "uint32",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "type": "object"
      },
      "SchemaMismatchPolicy": {
        "description": "Policy applied when the engine opens a state store whose schema version is **newer** than the binary supports (a *forward*-incompatibility).\n\nThis is the deploy-safety knob for rolling upgrades that cross a redb schema version. During a rolling bump, pods on the *old* binary can read state written by *already-upgraded* pods through a shared tiered backend. The default ([`Recreate`][Self::Recreate]) degrades that window instead of breaking it: the old pod does one full-refresh run and succeeds, rather than stranding the orchestrated run in an hour-long retry spiral.\n\nOnly the *forward* case (on-disk newer than binary) is governed here. *Backward*-compatibility — a newer binary reading older state — always auto-migrates forward as before; there is no knob for it.",
        "oneOf": [
          {
            "description": "Treat a forward-incompatible store as cold: log a single `WARN`, bootstrap a fresh local state, and **never write the downgraded state back** to the shared tier (so the newer state upgraded pods depend on is left intact). The run proceeds as a full refresh. Default — it turns a hard, run-stranding failure into a graceful one-time full refresh during the mixed-version window of a schema-changing upgrade.",
            "enum": [
              "recreate"
            ],
            "type": "string"
          },
          {
            "description": "Abort the open with a clear error (the historical behaviour). Appropriate when an operator would rather an incompatible pod fail loudly than do a full refresh against newer shared state.",
            "enum": [
              "fail"
            ],
            "type": "string"
          }
        ]
      },
      "SchemaPatternConfig": {
        "additionalProperties": false,
        "description": "Schema pattern configuration from TOML, converted to [`SchemaPattern`] at runtime.",
        "properties": {
          "components": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "prefix": {
            "type": "string"
          },
          "separator": {
            "type": "string"
          }
        },
        "required": [
          "components",
          "prefix",
          "separator"
        ],
        "type": "object"
      },
      "ScorecardDimension": {
        "description": "The dimension a scorecard groups policy decisions by.\n\nEach maps to a field the ledger persists on every decision: `principal` groups by who acted (`agent` / `human`), `rule` by the winning `[[policy.rules]]` index, and `scope` by the concrete model the decision was about (the ledger records the matched model, not the rule's scope predicates).",
        "oneOf": [
          {
            "description": "Group by who acted (`agent` / `human`).",
            "enum": [
              "principal"
            ],
            "type": "string"
          },
          {
            "description": "Group by the winning `[[policy.rules]]` index (or the default posture).",
            "enum": [
              "rule"
            ],
            "type": "string"
          },
          {
            "description": "Group by the concrete model the decision was about.",
            "enum": [
              "scope"
            ],
            "type": "string"
          }
        ]
      },
      "ScorecardGroup": {
        "description": "One group's decision aggregate in an [`AuditScorecardOutput`].\n\nEvery rate is a ratio over [`Self::total`] and is independently recomputable from [`Self::decision_refs`], which lists the exact ledger keys (`timestamp|plan_id|model`) that composed the group — so the aggregate summarizes citable rows, never an unverifiable number.",
        "properties": {
          "acceptance_rate": {
            "description": "`allow / total` — the proposal acceptance rate.",
            "format": "double",
            "type": "number"
          },
          "allow": {
            "description": "Decisions the plane allowed outright.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "decision_refs": {
            "description": "The ledger keys that composed this group (`timestamp|plan_id|model`), newest first — the citations backing every count above.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "denial_rate": {
            "description": "`deny / total` — the denial rate.",
            "format": "double",
            "type": "number"
          },
          "deny": {
            "description": "Decisions the plane denied.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "key": {
            "description": "The group label: a principal (`agent` / `human`), a rule (`rule 3` / `default posture`), or a model/scope name — per the scorecard's `by`.",
            "type": "string"
          },
          "require_review": {
            "description": "Decisions the plane escalated to human review.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "review_rate": {
            "description": "`require_review / total` — the escalation rate.",
            "format": "double",
            "type": "number"
          },
          "total": {
            "description": "Decisions in this group within the window.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "acceptance_rate",
          "allow",
          "decision_refs",
          "denial_rate",
          "deny",
          "key",
          "require_review",
          "review_rate",
          "total"
        ],
        "type": "object"
      },
      "ScorecardUnavailableMetric": {
        "description": "A metric the scorecard cannot compute from the persisted ledger for this window.\n\nDeclared explicitly (not silently omitted) so the honesty is machine-readable: a consumer sees the metric name, that it is `unavailable`, and exactly why.",
        "properties": {
          "availability": {
            "allOf": [
              {
                "$ref": "#/components/schemas/SectionAvailability"
              }
            ],
            "description": "Always [`SectionAvailability::Unavailable`]."
          },
          "metric": {
            "description": "The metric identifier (`reverts`, `escalation_latency`, or `verify_after_pass_rate` when no verification row falls in the window).",
            "type": "string"
          },
          "note": {
            "description": "Why the metric cannot be derived from the persisted ledger.",
            "type": "string"
          }
        },
        "required": [
          "availability",
          "metric",
          "note"
        ],
        "type": "object"
      },
      "ScorecardVerifyAfter": {
        "description": "The verify-after aggregate inside an [`AuditScorecardOutput`]: pass/fail counts over the post-apply verification custody rows in the window.\n\nEach row's aggregate verdict is its recorded `effect` (`allow` = every named check passed, `deny` = a check failed or was absent and the apply halted), so the pass rate summarizes exactly what the ledger persists.",
        "properties": {
          "failed": {
            "description": "Rows whose verdict was `deny` (a named check failed or was absent).",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "pass_rate": {
            "description": "`passed / total`.",
            "format": "double",
            "type": "number"
          },
          "passed": {
            "description": "Rows whose verdict was `allow` (every named check passed).",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "total": {
            "description": "Verification custody rows in the window.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "failed",
          "pass_rate",
          "passed",
          "total"
        ],
        "type": "object"
      },
      "SectionAvailability": {
        "description": "Whether a brief section's underlying query succeeded and had data.\n\nThe digest is composed section-by-section from independent typed queries over the state store and the decision ledger. Each section fails closed: a query that returns nothing renders as [`SectionAvailability::NoData`], and a source that is not wired into the state store at all renders as [`SectionAvailability::Unavailable`] with a note — never as a smoothed-over \"all clear\". A brief that claims more than the ledger holds poisons the whole surface, so the marker is machine-readable, not prose.",
        "oneOf": [
          {
            "description": "The query ran and the section carries data for the window.",
            "enum": [
              "available"
            ],
            "type": "string"
          },
          {
            "description": "The query ran cleanly but nothing fell inside the window.",
            "enum": [
              "no_data"
            ],
            "type": "string"
          },
          {
            "description": "The underlying signal is not recorded in the state store, so the section cannot be composed. Accompanied by a `note` explaining why.",
            "enum": [
              "unavailable"
            ],
            "type": "string"
          }
        ]
      },
      "SeedOutput": {
        "description": "JSON output for `rocky seed`.",
        "properties": {
          "command": {
            "type": "string"
          },
          "duration_ms": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "seeds_dir": {
            "type": "string"
          },
          "tables": {
            "items": {
              "$ref": "#/components/schemas/SeedTableOutput"
            },
            "type": "array"
          },
          "tables_failed": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "tables_loaded": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "command",
          "duration_ms",
          "seeds_dir",
          "tables",
          "tables_failed",
          "tables_loaded",
          "version"
        ],
        "type": "object"
      },
      "SeedTableOutput": {
        "description": "A single seed table result within `SeedOutput`.",
        "properties": {
          "columns": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "duration_ms": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "type": "string"
          },
          "rows": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "target": {
            "type": "string"
          }
        },
        "required": [
          "columns",
          "duration_ms",
          "name",
          "rows",
          "target"
        ],
        "type": "object"
      },
      "SemanticPlanVerdict": {
        "description": "Decision-support verdict from the typed-IR breaking-change classifier, attached to `PlanOutput` when `rocky plan --semantic` runs against a usable baseline.\n\n# Scope and blindness\n\nThe classifier diffs the typed **output schema** of each model between the baseline git ref and the working tree (column drop/add/type narrowing, nullability, materialization keys, masks, target rename). It is **blind to schema-stable value changes**: a `WHERE` / `JOIN`-key / `CASE` rewrite that changes every output row but leaves the column list and types untouched produces **no finding**. An empty `findings` list therefore means \"no output-schema change was detected\" — it is **not** a completeness or safety signal that the data is unchanged. The [`Self::caveat`] field carries this statement verbatim so a consumer that only reads the JSON cannot miss it.\n\n# Reporting-only\n\nThis verdict never gates the plan. Even a `breaking`-severity finding leaves the planned statements and the process exit code unchanged. The hard gate (which blocks on `breaking`) lives on `rocky plan promote`.",
        "properties": {
          "base_ref": {
            "description": "The git ref the working tree was diffed against (the `--base` value, default `\"main\"`).",
            "type": "string"
          },
          "caveat": {
            "description": "Verbatim statement of what the classifier does and does not see. Always populated — present even when `findings` is empty, because \"no findings\" is the case most easily misread as \"safe\". See the type-level docs for why this is load-bearing.",
            "type": "string"
          },
          "findings": {
            "description": "Classified output-schema findings (one per detected change), including `info`-severity entries. Each finding carries its own `model`, tagged `change.kind`, and `severity` (`breaking` / `warning` / `info`). Empty when no output-schema change was detected — which, per `caveat`, is not a safety signal.",
            "items": {
              "$ref": "#/components/schemas/BreakingFinding"
            },
            "type": "array"
          }
        },
        "required": [
          "base_ref",
          "caveat",
          "findings"
        ],
        "type": "object"
      },
      "Severity": {
        "description": "Severity level of a diagnostic.\n\nSerialized in PascalCase (`\"Error\"`, `\"Warning\"`, `\"Info\"`) to stay compatible with existing dagster fixtures and the hand-written `Severity` StrEnum in `integrations/dagster/src/dagster_rocky/types.py`.",
        "enum": [
          "Error",
          "Warning",
          "Info"
        ],
        "type": "string"
      },
      "SignatureAlgorithm": {
        "description": "Algorithm tag for an [`ApprovalSignature`].\n\nThe discriminator is on disk from day one so future \"real\" cryptographic signing variants slot in without migrating existing artifacts.",
        "oneOf": [
          {
            "description": "blake3 over a canonical-JSON encoding of the artifact payload, with the approver's git identity bound into the hashed bytes. Detects tamper-after-write but is not asymmetric crypto — see the approval-flow docs for the security model.",
            "enum": [
              "blake3_canonical_json"
            ],
            "type": "string"
          }
        ]
      },
      "SnapshotPipelineConfig": {
        "description": "Snapshot pipeline configuration — SCD Type 2 slowly-changing dimension capture.\n\nTracks historical changes to a source table by maintaining `valid_from` / `valid_to` columns in the target history table.\n\n```toml [pipeline.customer_history] type = \"snapshot\" unique_key = [\"customer_id\"] updated_at = \"updated_at\"\n\n[pipeline.customer_history.source] adapter = \"databricks_prod\" catalog = \"raw_catalog\" schema = \"raw__us_west__shopify\" table = \"customers\"\n\n[pipeline.customer_history.target] adapter = \"databricks_prod\" catalog = \"acme_warehouse\" schema = \"silver__scd\" table = \"customers_history\" ```",
        "properties": {
          "checks": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ChecksConfig"
              }
            ],
            "default": {
              "anomaly_threshold_pct": 0.0,
              "assertions": [],
              "column_match": false,
              "custom": [],
              "enabled": false,
              "fail_on_error": false,
              "freshness": null,
              "null_rate": null,
              "quarantine": null,
              "row_count": false
            },
            "description": "Data quality checks run after snapshot."
          },
          "depends_on": {
            "default": [],
            "description": "Pipeline dependencies for chaining.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "execution": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ExecutionConfig"
              }
            ],
            "default": {
              "concurrency": "adaptive",
              "error_rate_abort_pct": 50,
              "fail_fast": false,
              "table_retries": 1
            },
            "description": "Execution settings."
          },
          "invalidate_hard_deletes": {
            "default": false,
            "description": "When true, rows deleted from the source get their `valid_to` set to the current timestamp in the target. Default: false.",
            "type": "boolean"
          },
          "source": {
            "allOf": [
              {
                "$ref": "#/components/schemas/SnapshotSourceConfig"
              }
            ],
            "description": "Source table reference (single table, not pattern-based discovery)."
          },
          "target": {
            "allOf": [
              {
                "$ref": "#/components/schemas/SnapshotTargetConfig"
              }
            ],
            "description": "Target history table."
          },
          "unique_key": {
            "description": "Column(s) that uniquely identify a row in the source table.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "updated_at": {
            "description": "Column used to detect changes (compared between runs).",
            "type": "string"
          }
        },
        "required": [
          "source",
          "target",
          "unique_key",
          "updated_at"
        ],
        "type": "object"
      },
      "SnapshotSourceConfig": {
        "additionalProperties": false,
        "description": "Source table for a snapshot pipeline (explicit single-table reference).",
        "properties": {
          "adapter": {
            "default": "default",
            "description": "Name of the adapter to use (references a key in `[adapter.*]`).",
            "type": "string"
          },
          "catalog": {
            "type": "string"
          },
          "schema": {
            "type": "string"
          },
          "table": {
            "type": "string"
          }
        },
        "required": [
          "catalog",
          "schema",
          "table"
        ],
        "type": "object"
      },
      "SnapshotTargetConfig": {
        "additionalProperties": false,
        "description": "Target table for a snapshot pipeline (explicit single-table reference + governance).",
        "properties": {
          "adapter": {
            "default": "default",
            "description": "Name of the adapter to use (references a key in `[adapter.*]`).",
            "type": "string"
          },
          "catalog": {
            "type": "string"
          },
          "governance": {
            "allOf": [
              {
                "$ref": "#/components/schemas/GovernanceConfig"
              }
            ],
            "default": {
              "auto_create_catalogs": false,
              "auto_create_schemas": false,
              "grants": [],
              "isolation": null,
              "schema_grants": [],
              "tag_prefix": null,
              "tags": {}
            }
          },
          "schema": {
            "type": "string"
          },
          "table": {
            "type": "string"
          }
        },
        "required": [
          "catalog",
          "schema",
          "table"
        ],
        "type": "object"
      },
      "SourceOutput": {
        "description": "A discovered source (connector, schema, integration — terminology varies by adapter).",
        "properties": {
          "components": {
            "additionalProperties": true,
            "description": "Schema pattern components parsed from the source schema name. Keys are the component names from the schema pattern config. e.g., {\"tenant\": \"acme\", \"regions\": [\"us_west\"], \"source\": \"shopify\"}",
            "type": "object"
          },
          "id": {
            "type": "string"
          },
          "last_sync_at": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "metadata": {
            "additionalProperties": true,
            "description": "Adapter-namespaced metadata surfaced by the discovery adapter.\n\nKeys are conventionally prefixed with the adapter kind (e.g. `fivetran.service`, `fivetran.connector_id`, `fivetran.custom_reports`, `fivetran.custom_tables`, `fivetran.schema_prefix`) so entries from different adapters don't collide when an orchestrator folds them into an asset graph. Values are opaque `serde_json::Value` — Rocky relays service-specific payloads rather than modelling them.\n\nEmpty for adapters that haven't opted in; the field is skipped from the wire format in that case so existing fixtures stay byte-stable. Populated per-adapter in the discover command — see [`rocky_core::source::DiscoveredConnector::metadata`].",
            "type": "object"
          },
          "source_type": {
            "type": "string"
          },
          "tables": {
            "items": {
              "$ref": "#/components/schemas/TableOutput"
            },
            "type": "array"
          }
        },
        "required": [
          "components",
          "id",
          "source_type",
          "tables"
        ],
        "type": "object"
      },
      "SourceSpan": {
        "description": "Location in a source file.",
        "properties": {
          "col": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "file": {
            "type": "string"
          },
          "line": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "col",
          "file",
          "line"
        ],
        "type": "object"
      },
      "StateBackend": {
        "description": "State storage backend variants.",
        "oneOf": [
          {
            "description": "State stored on local disk (default). No sync needed.",
            "enum": [
              "local"
            ],
            "type": "string"
          },
          {
            "description": "State synced to/from an S3 bucket via the `object_store` crate.",
            "enum": [
              "s3"
            ],
            "type": "string"
          },
          {
            "description": "State synced to/from a Google Cloud Storage bucket.",
            "enum": [
              "gcs"
            ],
            "type": "string"
          },
          {
            "description": "State synced to/from a Valkey/Redis instance.",
            "enum": [
              "valkey"
            ],
            "type": "string"
          },
          {
            "description": "Tiered: Valkey (fast) with S3 fallback (durable).",
            "enum": [
              "tiered"
            ],
            "type": "string"
          }
        ]
      },
      "StateConfig": {
        "additionalProperties": false,
        "description": "State persistence configuration.\n\nControls where Rocky stores watermarks and anomaly history between runs. On ephemeral environments (EKS pods), use S3, GCS, or Valkey for persistence.\n\nWhen both S3 and Valkey are configured (`backend = \"tiered\"`): - Download: Valkey first (fast), S3 fallback (durable) - Upload: write to both Valkey + S3",
        "properties": {
          "backend": {
            "allOf": [
              {
                "$ref": "#/components/schemas/StateBackend"
              }
            ],
            "default": "local",
            "description": "Storage backend: local (default), s3, gcs, valkey, or tiered (valkey + s3 fallback)"
          },
          "gcs_bucket": {
            "description": "GCS bucket for state persistence",
            "type": [
              "string",
              "null"
            ]
          },
          "gcs_prefix": {
            "description": "GCS key prefix (default: \"rocky/state/\")",
            "type": [
              "string",
              "null"
            ]
          },
          "idempotency": {
            "allOf": [
              {
                "$ref": "#/components/schemas/IdempotencyConfig"
              }
            ],
            "default": {
              "dedup_on": "success",
              "in_flight_ttl_hours": 24,
              "retention_days": 30
            },
            "description": "Per-run idempotency-key policy (`rocky run --idempotency-key`). Controls retention of stamped keys, what terminal statuses count as \"deduplicated\", and how long an `InFlight` entry survives before it's treated as a crashed-pod corpse and adopted by a fresh caller. See [`IdempotencyConfig`]."
          },
          "namespacing": {
            "allOf": [
              {
                "$ref": "#/components/schemas/StateNamespacing"
              }
            ],
            "default": "none",
            "description": "Per-pipeline / per-client state-file namespacing. Defaults to [`StateNamespacing::None`] (one global state file — byte-identical to a project that omits this key). Set `namespacing = \"pipeline\"` to give each pipeline its own `<models>/.rocky-state/<pipeline>.redb` so independent fan-out runs don't serialize on one advisory lock. The per-invocation `--state-namespace <key>` flag overrides this; an explicit `--state-path` disables namespacing for that run."
          },
          "on_schema_mismatch": {
            "allOf": [
              {
                "$ref": "#/components/schemas/SchemaMismatchPolicy"
              }
            ],
            "default": "recreate",
            "description": "What to do when the engine opens a state store whose schema version is **newer** than this binary supports (a forward-incompatibility, which happens during a rolling upgrade that crosses a redb schema version). Defaults to [`SchemaMismatchPolicy::Recreate`] — the old pod bootstraps fresh, does one full-refresh run, and never clobbers the newer shared state. Set to `fail` to keep the historical hard-abort behaviour. Only the run path honours this; inspection/branch commands still hard-fail on a forward-incompatible store. See [`SchemaMismatchPolicy`]."
          },
          "on_upload_failure": {
            "allOf": [
              {
                "$ref": "#/components/schemas/StateUploadFailureMode"
              }
            ],
            "default": "skip",
            "description": "What to do when state upload exhausts retries + circuit-breaker. Defaults to `skip` — rocky continues the run and the next run re-derives state from target-table metadata. See [`StateUploadFailureMode`]."
          },
          "retention": {
            "allOf": [
              {
                "$ref": "#/components/schemas/StateRetentionConfig"
              }
            ],
            "default": {
              "applies_to": [
                "history",
                "lineage",
                "audit"
              ],
              "max_age_days": 365,
              "min_runs_kept": 100,
              "sweep_budget_ms": 5000,
              "sweep_interval_seconds": 3600
            },
            "description": "Retention policy applied to Rocky's own `state.redb` tables (run history, DAG snapshots, quality snapshots). Bounds the size of the control-plane store; operational tables (schema cache, watermarks, partition records) are never swept by this policy. See [`crate::retention::StateRetentionConfig`]."
          },
          "retry": {
            "allOf": [
              {
                "$ref": "#/components/schemas/RetryConfig"
              }
            ],
            "default": {
              "backoff_multiplier": 2.0,
              "circuit_breaker_recovery_timeout_secs": null,
              "circuit_breaker_threshold": 5,
              "initial_backoff_ms": 1000,
              "jitter": true,
              "max_backoff_ms": 30000,
              "max_retries": 3,
              "max_retries_per_run": null
            },
            "description": "Retry policy applied to transient state-transfer failures (network hiccups, hung endpoints that hit the per-request HTTP timeout, transient 5xx, etc.). Shares the same shape as the adapter retry config so operators can reason about both with a single mental model. Retries share the outer `transfer_timeout_seconds` budget, so the total wall-clock ceiling is unchanged."
          },
          "s3_bucket": {
            "description": "S3 bucket for state persistence",
            "type": [
              "string",
              "null"
            ]
          },
          "s3_prefix": {
            "description": "S3 key prefix (default: \"rocky/state/\")",
            "type": [
              "string",
              "null"
            ]
          },
          "transfer_timeout_seconds": {
            "default": 300,
            "description": "Wall-clock budget (seconds) for each state transfer operation (download or upload). Catches stuck SDK retry loops, DNS, TLS, and hung endpoints that the per-request HTTP timeout does not see. Defaults to 300s; raise for large state or slow networks.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "valkey_prefix": {
            "description": "Valkey key prefix (default: \"rocky:state:\")",
            "type": [
              "string",
              "null"
            ]
          },
          "valkey_url": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/RedactedString"
              },
              {
                "type": "null"
              }
            ],
            "description": "Valkey/Redis URL for state persistence. May embed credentials, so the value is redacted in serialized config and logs."
          }
        },
        "type": "object"
      },
      "StateNamespacing": {
        "description": "Per-pipeline / per-client state-file namespacing policy.\n\nredb permits one writer per file, so fanning out one `rocky run` process per pipeline or client against the single global state file forces those independent runs to serialize on one advisory lock. Namespacing gives each namespace its own `<models>/.rocky-state/<namespace>.redb` file (its own lock, its own redb handle, its own remote object key), so runs on distinct namespaces proceed concurrently with zero shared corruption surface.\n\nDefault is [`StateNamespacing::None`] — behavior is **byte-identical** to a project that never sets this key. Namespacing is purely opt-in.",
        "oneOf": [
          {
            "description": "One global `<models>/.rocky-state.redb` for the whole project (default). Identical to today's behavior.",
            "enum": [
              "none"
            ],
            "type": "string"
          },
          {
            "description": "One state file per pipeline, under `<models>/.rocky-state/<pipeline>.redb`. The per-invocation `--state-namespace <key>` flag takes precedence over this config and is the explicit way to fan out by client/tenant rather than by pipeline name. An explicit `--state-path` disables namespacing entirely for that invocation.",
            "enum": [
              "pipeline"
            ],
            "type": "string"
          }
        ]
      },
      "StateOutput": {
        "description": "JSON output for `rocky state show`.",
        "properties": {
          "command": {
            "type": "string"
          },
          "schema_version_on_disk": {
            "description": "redb state-schema version stamped in the on-disk state file, or `null` when no state file exists yet or it predates schema versioning.",
            "format": "uint32",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "schema_version_supported": {
            "description": "redb state-schema version this binary supports. Pair with `schema_version_on_disk` to make a compatibility decision without parsing a human error string: `on_disk > supported` means the file was written by a newer engine (forward-incompatible). See the state-schema-deploy-safety contract.",
            "format": "uint32",
            "minimum": 0.0,
            "type": "integer"
          },
          "version": {
            "type": "string"
          },
          "watermarks": {
            "items": {
              "$ref": "#/components/schemas/WatermarkEntry"
            },
            "type": "array"
          }
        },
        "required": [
          "command",
          "schema_version_supported",
          "version",
          "watermarks"
        ],
        "type": "object"
      },
      "StateRetentionConfig": {
        "additionalProperties": false,
        "description": "State-store retention policy.\n\nBounds the size of Rocky's `state.redb` by sweeping rows older than `max_age_days` from the run-history, DAG-snapshot, and quality-snapshot tables. The most recent `min_runs_kept` rows in each domain are always preserved, so a project that has not run in months still has its last good baseline available for `rocky history` and `rocky compare`.\n\nOperational state — schema cache, watermarks, partition records — is never swept by this policy: those tables hold live correctness data (without them, the next run cannot resume), not history.",
        "properties": {
          "applies_to": {
            "default": [
              "history",
              "lineage",
              "audit"
            ],
            "description": "Domains to sweep. Defaults to `[\"history\", \"lineage\", \"audit\"]`. Setting `applies_to = []` disables the sweep entirely without removing the config block — useful for staged rollouts.",
            "items": {
              "$ref": "#/components/schemas/StateRetentionDomain"
            },
            "type": "array"
          },
          "max_age_days": {
            "default": 365,
            "description": "Drop rows whose timestamp is older than this many days. Counted from the row's recorded `started_at` (history), `timestamp` (lineage / audit) — not the file mtime. Defaults to [`DEFAULT_STATE_RETENTION_MAX_AGE_DAYS`].",
            "format": "uint32",
            "minimum": 0.0,
            "type": "integer"
          },
          "min_runs_kept": {
            "default": 100,
            "description": "Always preserve at least this many rows in each domain, even if every row is older than `max_age_days`. Applied per domain (last N runs, last N DAG snapshots, last N quality snapshots). Defaults to [`DEFAULT_STATE_RETENTION_MIN_RUNS_KEPT`].",
            "format": "uint32",
            "minimum": 0.0,
            "type": "integer"
          },
          "sweep_budget_ms": {
            "default": 5000,
            "description": "Wall-clock budget (in milliseconds) the auto-sweep measures itself against at end-of-run. The sweep always runs to completion; exceeding the budget flips the per-run log line from `tracing::debug` to `tracing::warn` so operators can spot a state store that has grown large enough to warrant manual intervention. Defaults to [`DEFAULT_STATE_RETENTION_SWEEP_BUDGET_MS`].",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "sweep_interval_seconds": {
            "default": 3600,
            "description": "Minimum number of seconds between two automatic end-of-run sweeps. The `rocky run` end-of-run hook reads `last_retention_sweep_at` from the state store's metadata table and skips the sweep when `now - last < sweep_interval_seconds`. The manual `rocky state retention sweep` subcommand is unaffected — it always runs. Defaults to [`DEFAULT_STATE_RETENTION_SWEEP_INTERVAL_SECONDS`].",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "type": "object"
      },
      "StateRetentionDomain": {
        "description": "Domain identifier for the state-store retention sweep.\n\nThe wire format is a lowercase string in `applies_to` — the [`Display`] impl produces the canonical spelling, and the `from_str` impl accepts only that spelling (no aliases).\n\n[`Display`]: std::fmt::Display",
        "oneOf": [
          {
            "description": "Pipeline run records (`run_history` table). Each row carries `started_at`/`finished_at` and the governance audit trail.",
            "enum": [
              "history"
            ],
            "type": "string"
          },
          {
            "description": "DAG snapshots (`dag_snapshots` table). Each entry carries the graph hash and the diff against the prior snapshot.",
            "enum": [
              "lineage"
            ],
            "type": "string"
          },
          {
            "description": "Per-model quality snapshots (`quality_history` table). Each entry carries `timestamp`, `run_id`, and the metric blob.",
            "enum": [
              "audit"
            ],
            "type": "string"
          }
        ]
      },
      "StateUploadFailureMode": {
        "description": "Policy applied when state upload fails after retries + circuit-breaker are exhausted. See [`StateConfig::on_upload_failure`].",
        "oneOf": [
          {
            "description": "Log a warning and continue the run successfully. State becomes stale until the next healthy upload; the next run's `discover` re-derives watermarks from target-table metadata. Trades state durability for run liveness — matches the de-facto behaviour of the pre-retry callers in `rocky run`, which already `warn + continue` on a failed upload.",
            "enum": [
              "skip"
            ],
            "type": "string"
          },
          {
            "description": "Propagate the error to the caller. Appropriate for strict environments where state durability matters more than liveness (e.g. long-running backfills where re-deriving watermarks is prohibitively expensive).",
            "enum": [
              "fail"
            ],
            "type": "string"
          }
        ]
      },
      "StatementResult": {
        "description": "Result of executing one SQL statement during `rocky compact apply` or `rocky archive apply`.",
        "properties": {
          "duration_ms": {
            "description": "Execution duration in milliseconds. Zero when execution was skipped due to a prior statement failure in the same apply run.",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "error": {
            "description": "Adapter error message. `None` when `success == true`.",
            "type": [
              "string",
              "null"
            ]
          },
          "purpose": {
            "description": "Human-readable purpose label (mirrors `NamedStatement.purpose`).",
            "type": "string"
          },
          "sql": {
            "description": "The SQL that was executed.",
            "type": "string"
          },
          "success": {
            "description": "Whether the adapter accepted the statement without error.",
            "type": "boolean"
          }
        },
        "required": [
          "duration_ms",
          "purpose",
          "sql",
          "success"
        ],
        "type": "object"
      },
      "StrategyConfig": {
        "description": "Materialization strategy for a model, defaulting to full refresh.",
        "oneOf": [
          {
            "properties": {
              "type": {
                "enum": [
                  "full_refresh"
                ],
                "type": "string"
              }
            },
            "required": [
              "type"
            ],
            "type": "object"
          },
          {
            "properties": {
              "timestamp_column": {
                "type": "string"
              },
              "type": {
                "enum": [
                  "incremental"
                ],
                "type": "string"
              }
            },
            "required": [
              "timestamp_column",
              "type"
            ],
            "type": "object"
          },
          {
            "properties": {
              "type": {
                "enum": [
                  "merge"
                ],
                "type": "string"
              },
              "unique_key": {
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "update_columns": {
                "default": null,
                "items": {
                  "type": "string"
                },
                "type": [
                  "array",
                  "null"
                ]
              }
            },
            "required": [
              "type",
              "unique_key"
            ],
            "type": "object"
          },
          {
            "description": "Partition-keyed materialization. Each run targets specific partitions rather than appending past a watermark. Idempotent and backfill-friendly.\n\nThe model SQL must reference `@start_date` and `@end_date` placeholders, which the runtime substitutes per partition with quoted timestamp literals.",
            "properties": {
              "batch_size": {
                "default": 1,
                "description": "Combine N consecutive partitions into one SQL statement when backfilling. Defaults to 1 (atomic per-partition replacement).",
                "format": "uint32",
                "minimum": 1.0,
                "type": "integer"
              },
              "first_partition": {
                "default": null,
                "description": "Lower bound for `--missing` discovery, in canonical key format (e.g., `\"2024-01-01\"` for daily). Required when `--missing` is used.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "granularity": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/TimeGrain"
                  }
                ],
                "description": "Partition granularity (`hour`, `day`, `month`, `year`)."
              },
              "lookback": {
                "default": 0,
                "description": "Recompute the previous N partitions on each run, in addition to whatever the CLI selected. Standard handling for late-arriving data.",
                "format": "uint32",
                "minimum": 0.0,
                "type": "integer"
              },
              "time_column": {
                "description": "Column on the model output that holds the partition value. Must be a non-nullable date or timestamp column. Validated by the compiler against the typed output schema.",
                "type": "string"
              },
              "type": {
                "enum": [
                  "time_interval"
                ],
                "type": "string"
              }
            },
            "required": [
              "granularity",
              "time_column",
              "type"
            ],
            "type": "object"
          },
          {
            "description": "Ephemeral model — inlined as CTE in downstream queries, no table created.",
            "properties": {
              "type": {
                "enum": [
                  "ephemeral"
                ],
                "type": "string"
              }
            },
            "required": [
              "type"
            ],
            "type": "object"
          },
          {
            "description": "Delete+Insert: delete matching rows by partition key, then insert.",
            "properties": {
              "partition_by": {
                "description": "Column(s) used to identify the partition to delete.",
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "type": {
                "enum": [
                  "delete_insert"
                ],
                "type": "string"
              }
            },
            "required": [
              "partition_by",
              "type"
            ],
            "type": "object"
          },
          {
            "description": "Microbatch: alias for time_interval with hourly defaults. dbt-compatible naming for partition-based incremental processing.",
            "properties": {
              "granularity": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/TimeGrain"
                  }
                ],
                "default": "hour",
                "description": "Batch granularity (default: Hour)."
              },
              "timestamp_column": {
                "description": "Timestamp column for micro-batch boundaries.",
                "type": "string"
              },
              "type": {
                "enum": [
                  "microbatch"
                ],
                "type": "string"
              }
            },
            "required": [
              "timestamp_column",
              "type"
            ],
            "type": "object"
          },
          {
            "description": "SQL view — no physical storage. Every read against the target re-runs the model SELECT. Supported on every Rocky-targeted warehouse.",
            "properties": {
              "type": {
                "enum": [
                  "view"
                ],
                "type": "string"
              }
            },
            "required": [
              "type"
            ],
            "type": "object"
          },
          {
            "description": "Materialized view — warehouse manages refresh. Supported on Databricks, Snowflake, and BigQuery. DuckDB / Trino surface a \"not supported\" error at SQL-gen time.",
            "properties": {
              "type": {
                "enum": [
                  "materialized_view"
                ],
                "type": "string"
              }
            },
            "required": [
              "type"
            ],
            "type": "object"
          },
          {
            "description": "Snowflake dynamic table — warehouse manages lag-based refresh. `target_lag` is a Snowflake lag specifier (e.g. `\"1 minute\"`, `\"5 hours\"`, `\"downstream\"`). Non-Snowflake adapters surface a \"not supported\" error at SQL-gen time.",
            "properties": {
              "target_lag": {
                "description": "Snowflake lag specifier — alphanumeric + space only. Examples: `\"1 minute\"`, `\"5 hours\"`, `\"downstream\"`.",
                "type": "string"
              },
              "type": {
                "enum": [
                  "dynamic_table"
                ],
                "type": "string"
              }
            },
            "required": [
              "target_lag",
              "type"
            ],
            "type": "object"
          },
          {
            "description": "Content-addressed write to a Delta UniForm table via the `rocky-iceberg` writer. The runtime executes the model SQL, converts the result to Arrow, blake3-hashes the Parquet bytes, uploads to `storage_prefix`, and emits a Delta log commit. Cross-engine reads (DuckDB iceberg_scan, etc.) require MSCK REPAIR after each commit; the runtime issues this automatically.",
            "properties": {
              "partition_columns": {
                "default": [],
                "description": "Logical partition column names. Empty for unpartitioned tables. The runtime asserts this matches the table's declared partition columns at materialization time.",
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "storage_prefix": {
                "description": "Object-store key prefix that holds `_delta_log/` + Parquet files for the target table. Typically `s3://<bucket>/<path>/<table>` for AWS-backed deployments.",
                "type": "string"
              },
              "type": {
                "enum": [
                  "content_addressed"
                ],
                "type": "string"
              }
            },
            "required": [
              "storage_prefix",
              "type"
            ],
            "type": "object"
          }
        ]
      },
      "TableCheckOutput": {
        "properties": {
          "asset_key": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "checks": {
            "items": {
              "$ref": "#/components/schemas/CheckResult"
            },
            "type": "array"
          }
        },
        "required": [
          "asset_key",
          "checks"
        ],
        "type": "object"
      },
      "TableCompareResult": {
        "properties": {
          "production_count": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "production_table": {
            "type": "string"
          },
          "row_count_diff_pct": {
            "format": "double",
            "type": "number"
          },
          "row_count_match": {
            "type": "boolean"
          },
          "schema_diffs": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "schema_match": {
            "type": "boolean"
          },
          "shadow_count": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "shadow_table": {
            "type": "string"
          },
          "verdict": {
            "type": "string"
          }
        },
        "required": [
          "production_count",
          "production_table",
          "row_count_diff_pct",
          "row_count_match",
          "schema_diffs",
          "schema_match",
          "shadow_count",
          "shadow_table",
          "verdict"
        ],
        "type": "object"
      },
      "TableDedupContribution": {
        "description": "Per-table contribution to the overall duplicate set.",
        "properties": {
          "contribution_pct": {
            "description": "0.0..=100.0. Share of this table's partitions that are duplicates of some other table's partitions.",
            "format": "double",
            "type": "number"
          },
          "partitions": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "partitions_shared_with_others": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "table": {
            "type": "string"
          }
        },
        "required": [
          "contribution_pct",
          "partitions",
          "partitions_shared_with_others",
          "table"
        ],
        "type": "object"
      },
      "TableErrorOutput": {
        "description": "Error from a table that failed during parallel processing.",
        "properties": {
          "asset_key": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "cooldown_seconds": {
            "description": "Backoff hint in whole seconds. Populated by adapters whose failure mode carries a known cooldown — currently only the Databricks and Snowflake warehouse adapters when their shared circuit breaker trips (and the breaker was configured with `retry.circuit_breaker_recovery_timeout_secs`). Orchestrators (Dagster, etc.) use it as a `retry_after` hint when scheduling a delayed re-run. Mirrors the source-side shape on [`FailedSourceOutput::cooldown_seconds`]. Absent for failure classes without an engine-supplied hint.",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "error": {
            "type": "string"
          },
          "failure_kind": {
            "allOf": [
              {
                "$ref": "#/components/schemas/FailureKind"
              }
            ],
            "default": "unknown",
            "description": "Coarse classification of the failure so orchestrators can branch on kind (retry, page, surface) without parsing `error`. Defaults to [`FailureKind::Unknown`] for errors that reached the output layer type-erased."
          }
        },
        "required": [
          "asset_key",
          "error"
        ],
        "type": "object"
      },
      "TableMatch": {
        "additionalProperties": false,
        "description": "Match criteria for a [`TableOverride`].\n\nAt least one of `connector` or `table` must be set (`None` for both is rejected at parse time as redundant — use pipeline-level fields to change defaults globally).\n\n`connector` matches against either [`crate::source::DiscoveredConnector::id`] (the stable adapter-side identifier — e.g. a Fivetran connector_id) **or** [`crate::source::DiscoveredConnector::schema`] (the human-meaningful source schema name — e.g. `src__acme__shopify`). String equality on either match wins.\n\n`table` matches against [`crate::source::DiscoveredTable::name`]. Supports `*` (zero or more characters) and `?` (exactly one character) glob wildcards; presence of either character switches the value from literal to glob.\n\n# Reserved\n\n`table` and `id` are reserved as schema-pattern component names — configs that use either as a component in [`SchemaPatternConfig::components`] are rejected at parse time to avoid colliding with `--filter table=` and `--filter id=`.",
        "properties": {
          "connector": {
            "description": "Connector match — equals either `DiscoveredConnector.id` or `DiscoveredConnector.schema`. `None` matches every connector.",
            "type": [
              "string",
              "null"
            ]
          },
          "table": {
            "description": "Table-name match. Literal when no `*`/`?`; glob otherwise. `None` matches every table.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "type": "object"
      },
      "TableOutput": {
        "properties": {
          "name": {
            "type": "string"
          },
          "row_count": {
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "required": [
          "name"
        ],
        "type": "object"
      },
      "TableOverride": {
        "additionalProperties": false,
        "description": "A per-`(connector, table)` override rule on a replication pipeline.\n\nAuthored as one TOML array entry under `[[pipeline.<name>.table_overrides]]`. Each field except `match_` is optional — `None` means \"inherit from the pipeline-level default.\" The resolver applies overrides with per-field most-specific-wins semantics; see [`ReplicationPipelineConfig::table_overrides`] for the ranking.\n\n# Example\n\n```toml [[pipeline.raw.table_overrides]] match.connector = \"stripe_main\" match.table     = \"pii_users\" merge_keys      = [\"user_id\", \"tenant_id\"] ```",
        "properties": {
          "enabled": {
            "description": "When `Some(false)`, the matched `(connector, table)` pairs are dropped from the run and surfaced under `RunOutput.excluded_tables` with `reason = \"table_override_disabled\"`. Net-new field with no pipeline-level counterpart — disabling a whole pipeline is already covered by removing the pipeline block.",
            "type": [
              "boolean",
              "null"
            ]
          },
          "match": {
            "allOf": [
              {
                "$ref": "#/components/schemas/TableMatch"
              }
            ],
            "default": {},
            "description": "Match criteria — at least one of `connector` or `table` must be set. A rule with neither (or a `match` block omitted entirely) is rejected at parse time (use pipeline-level fields to change defaults for all tables). `default` lets serde deserialize an override missing the whole `match` block; the validator catches the resulting empty match."
          },
          "merge_keys": {
            "description": "Override for [`ReplicationPipelineConfig::merge_keys`]. `None` inherits the pipeline default.",
            "items": {
              "type": "string"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "merge_keys_fallback": {
            "description": "Override for [`ReplicationPipelineConfig::merge_keys_fallback`]. `None` inherits the pipeline default.",
            "items": {
              "type": "string"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "strategy": {
            "description": "Override for [`ReplicationPipelineConfig::strategy`]. `None` inherits the pipeline default.",
            "type": [
              "string",
              "null"
            ]
          },
          "timestamp_column": {
            "description": "Override for [`ReplicationPipelineConfig::timestamp_column`]. `None` inherits the pipeline default.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "type": "object"
      },
      "TableRef": {
        "additionalProperties": false,
        "description": "A reference to a specific catalog/schema/table for quality checks and snapshot pipelines.",
        "properties": {
          "catalog": {
            "type": "string"
          },
          "schema": {
            "type": "string"
          },
          "table": {
            "default": null,
            "description": "Specific table name. When `None`, all tables in the schema are checked.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "catalog",
          "schema"
        ],
        "type": "object"
      },
      "TargetConfig": {
        "description": "Target table coordinates for a model.",
        "properties": {
          "catalog": {
            "type": "string"
          },
          "schema": {
            "type": "string"
          },
          "table": {
            "type": "string"
          }
        },
        "required": [
          "catalog",
          "schema",
          "table"
        ],
        "type": "object"
      },
      "TestAdapterOutput": {
        "description": "JSON output for `rocky test-adapter`.\n\nMirrors `rocky_adapter_sdk::conformance::ConformanceResult` with the status/category enums stringified, so we don't have to add `JsonSchema` to the SDK crate.",
        "properties": {
          "adapter": {
            "type": "string"
          },
          "results": {
            "items": {
              "$ref": "#/components/schemas/TestAdapterTestResult"
            },
            "type": "array"
          },
          "sdk_version": {
            "type": "string"
          },
          "tests_failed": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "tests_passed": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "tests_run": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "tests_skipped": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "adapter",
          "results",
          "sdk_version",
          "tests_failed",
          "tests_passed",
          "tests_run",
          "tests_skipped"
        ],
        "type": "object"
      },
      "TestAdapterTestResult": {
        "properties": {
          "category": {
            "description": "Stringified `TestCategory`: \"connection\", \"ddl\", \"dml\", \"query\", \"types\", \"dialect\", \"governance\", \"discovery\", \"batch_checks\".",
            "type": "string"
          },
          "duration_ms": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "message": {
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "type": "string"
          },
          "status": {
            "description": "Stringified `TestStatus`: \"passed\", \"failed\", \"skipped\".",
            "type": "string"
          }
        },
        "required": [
          "category",
          "duration_ms",
          "name",
          "status"
        ],
        "type": "object"
      },
      "TestFailure": {
        "description": "One failed test, mirroring the (name, error) tuple in `rocky_engine::test_runner::TestResult::failures` but with named fields because schemars/JSON Schema can't represent positional tuples cleanly.",
        "properties": {
          "error": {
            "type": "string"
          },
          "name": {
            "type": "string"
          }
        },
        "required": [
          "error",
          "name"
        ],
        "type": "object"
      },
      "TestOutput": {
        "description": "JSON output for `rocky test`.",
        "properties": {
          "command": {
            "type": "string"
          },
          "declarative": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/DeclarativeTestSummary"
              },
              {
                "type": "null"
              }
            ],
            "description": "Results from declarative `[[tests]]` in model sidecars. Present only when `--declarative` is used."
          },
          "failed": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "failures": {
            "items": {
              "$ref": "#/components/schemas/TestFailure"
            },
            "type": "array"
          },
          "model_results": {
            "default": [],
            "description": "Per-model outcomes for the (DuckDB-backed) model-execution test — passes too, not just failures. Lets the VS Code Inspector Tests tab and the dagster integration render \"good_mart: pass\" without inferring it from `total - failures`. Empty when only declarative tests ran. Filtered to `--model` when that flag is set.",
            "items": {
              "$ref": "#/components/schemas/ModelTestResult"
            },
            "type": "array"
          },
          "passed": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "total": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "unit_tests": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/UnitTestSummary"
              },
              {
                "type": "null"
              }
            ],
            "description": "Results from fixture-driven `[[test]]` unit tests in model sidecars. Present only when at least one model declares a `[[test]]` block."
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "command",
          "failed",
          "failures",
          "passed",
          "total",
          "version"
        ],
        "type": "object"
      },
      "TestSeverity": {
        "description": "Severity of a test failure.",
        "oneOf": [
          {
            "description": "Test failure is a hard error — pipeline fails.",
            "enum": [
              "error"
            ],
            "type": "string"
          },
          {
            "description": "Test failure is a warning — pipeline continues.",
            "enum": [
              "warning"
            ],
            "type": "string"
          }
        ]
      },
      "TimeGrain": {
        "description": "Partition granularity for `time_interval` materialization.\n\nThe granularity determines: - The canonical partition key format (see [`TimeGrain::format_str`]). - How `@start_date` / `@end_date` placeholders are computed per partition. - What column types are valid (`hour` requires TIMESTAMP; others accept DATE).",
        "enum": [
          "hour",
          "day",
          "month",
          "year"
        ],
        "type": "string"
      },
      "TraceModelEntry": {
        "description": "One model execution entry inside [`TraceOutput`]. `start_offset_ms` is the wall-clock offset from the run start; `lane` identifies the concurrency lane for Gantt rendering (entries on the same lane never overlap in time).",
        "properties": {
          "bytes_scanned": {
            "description": "Adapter-reported bytes figure used for cost accounting. This is the *billing-relevant* number per adapter, not literal scan volume:\n\n- **BigQuery:** `totalBytesBilled` — includes the 10 MB per-query minimum floor; matches the BigQuery console's \"Bytes billed\" field, **not** \"Bytes processed\". - **Databricks:** when populated, byte count from the statement-execution manifest (`total_byte_count`); `None` today until the manifest plumbing lands. - **Snowflake:** `None` — deferred by design (QUERY_HISTORY round-trip cost; Snowflake cost is duration × DBU, not bytes-driven). - **DuckDB:** `None` — no billed-bytes concept.",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "bytes_written": {
            "description": "Adapter-reported bytes-written figure. Currently `None` on every adapter — BigQuery doesn't expose a bytes-written figure for query jobs, and the Databricks / Snowflake paths haven't wired it yet.",
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "duration_ms": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "lane": {
            "default": 0,
            "description": "Greedy first-fit concurrency lane. Populated by the renderer; deserializing clients don't need to supply it.",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "model_name": {
            "type": "string"
          },
          "recipe_identity": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/RecipeIdentityView"
              },
              {
                "type": "null"
              }
            ],
            "description": "The recipe-identity triple recorded for this execution, when present. See [`RecipeIdentityView`]."
          },
          "rows_affected": {
            "format": "uint64",
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ]
          },
          "sql_hash": {
            "type": "string"
          },
          "start_offset_ms": {
            "format": "int64",
            "type": "integer"
          },
          "status": {
            "type": "string"
          }
        },
        "required": [
          "duration_ms",
          "model_name",
          "sql_hash",
          "start_offset_ms",
          "status"
        ],
        "type": "object"
      },
      "TraceOutput": {
        "description": "JSON output for `rocky trace <run_id|latest>`.\n\nSibling to [`ReplayOutput`] but with offset-relative timings so downstream consumers (Dagster asset Gantt, custom dashboards) can render the run as a timeline without re-deriving the run start.",
        "properties": {
          "command": {
            "type": "string"
          },
          "finished_at": {
            "type": "string"
          },
          "lane_count": {
            "description": "Number of concurrent lanes the scheduler used during this run. `1` for fully sequential pipelines, `>1` when the DAG had independent models that the executor materialized in parallel.",
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "models": {
            "items": {
              "$ref": "#/components/schemas/TraceModelEntry"
            },
            "type": "array"
          },
          "run_duration_ms": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "run_id": {
            "type": "string"
          },
          "started_at": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "trigger": {
            "type": "string"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "command",
          "finished_at",
          "lane_count",
          "models",
          "run_duration_ms",
          "run_id",
          "started_at",
          "status",
          "trigger",
          "version"
        ],
        "type": "object"
      },
      "TransformationPipelineConfig": {
        "description": "Transformation pipeline configuration.\n\nOrchestrates `.sql` / `.rocky` model compilation and execution as a first-class pipeline, with its own execution, checks, and governance settings. Model-level strategy (incremental, merge, time_interval, etc.) is defined in each model's sidecar TOML, not at the pipeline level.\n\n```toml [pipeline.silver] type = \"transformation\" models = \"models/**\"\n\n[pipeline.silver.target] adapter = \"databricks_prod\" [pipeline.silver.target.governance] auto_create_schemas = true\n\n[pipeline.silver.execution] concurrency = 8 ```",
        "properties": {
          "checks": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ChecksConfig"
              }
            ],
            "default": {
              "anomaly_threshold_pct": 0.0,
              "assertions": [],
              "column_match": false,
              "custom": [],
              "enabled": false,
              "fail_on_error": false,
              "freshness": null,
              "null_rate": null,
              "quarantine": null,
              "row_count": false
            },
            "description": "Data quality checks run after model execution."
          },
          "depends_on": {
            "default": [],
            "description": "Pipeline dependencies for chaining.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "execution": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ExecutionConfig"
              }
            ],
            "default": {
              "concurrency": "adaptive",
              "error_rate_abort_pct": 50,
              "fail_fast": false,
              "table_retries": 1
            },
            "description": "Execution settings (concurrency, retries, etc.)."
          },
          "models": {
            "default": "models/**",
            "description": "Glob pattern for model files, relative to the config file directory. Default: `\"models/**\"`.",
            "type": "string"
          },
          "target": {
            "allOf": [
              {
                "$ref": "#/components/schemas/TransformationTargetConfig"
              }
            ],
            "description": "Target configuration (adapter + governance)."
          }
        },
        "required": [
          "target"
        ],
        "type": "object"
      },
      "TransformationTargetConfig": {
        "additionalProperties": false,
        "description": "Target configuration for transformation pipelines.\n\nUnlike replication targets (which use `catalog_template` / `schema_template` for dynamic routing), transformation targets only need an adapter reference and optional governance — the actual catalog/schema/table is defined per-model in sidecar TOML files.",
        "properties": {
          "adapter": {
            "default": "default",
            "description": "Name of the adapter to use (references a key in `[adapter.*]`). Defaults to `\"default\"`.",
            "type": "string"
          },
          "governance": {
            "allOf": [
              {
                "$ref": "#/components/schemas/GovernanceConfig"
              }
            ],
            "default": {
              "auto_create_catalogs": false,
              "auto_create_schemas": false,
              "grants": [],
              "isolation": null,
              "schema_grants": [],
              "tag_prefix": null,
              "tags": {}
            },
            "description": "Governance settings for the target."
          }
        },
        "type": "object"
      },
      "UnitTestResult": {
        "description": "Result of running a single unit test.",
        "properties": {
          "error": {
            "default": null,
            "description": "Error message if failed.",
            "type": [
              "string",
              "null"
            ]
          },
          "mismatches": {
            "default": [],
            "description": "Mismatched rows (for diagnostics).",
            "items": {
              "$ref": "#/components/schemas/RowMismatch"
            },
            "type": "array"
          },
          "model": {
            "description": "Model name.",
            "type": "string"
          },
          "passed": {
            "description": "Whether the test passed.",
            "type": "boolean"
          },
          "test": {
            "description": "Test name.",
            "type": "string"
          }
        },
        "required": [
          "model",
          "passed",
          "test"
        ],
        "type": "object"
      },
      "UnitTestSummary": {
        "description": "Summary of fixture-driven unit-test execution (from `[[test]]` blocks in model sidecars). The per-test `results` reuse the engine's [`rocky_core::unit_test::UnitTestResult`] shape (model, test, passed, error, and row-level mismatches).",
        "properties": {
          "failed": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "passed": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "results": {
            "items": {
              "$ref": "#/components/schemas/UnitTestResult"
            },
            "type": "array"
          },
          "total": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "failed",
          "passed",
          "results",
          "total"
        ],
        "type": "object"
      },
      "ValidateAdapterStatus": {
        "description": "Status of a single adapter in the validate output.",
        "properties": {
          "adapter_type": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "ok": {
            "type": "boolean"
          }
        },
        "required": [
          "adapter_type",
          "name",
          "ok"
        ],
        "type": "object"
      },
      "ValidateMessage": {
        "description": "A structured diagnostic message from `rocky validate`.",
        "properties": {
          "code": {
            "description": "Machine-readable code (e.g. \"V001\", \"L001\").",
            "type": "string"
          },
          "field": {
            "description": "Config field path associated with this message, if applicable.",
            "type": [
              "string",
              "null"
            ]
          },
          "file": {
            "description": "File path associated with this message, if applicable.",
            "type": [
              "string",
              "null"
            ]
          },
          "message": {
            "type": "string"
          },
          "severity": {
            "description": "One of \"ok\", \"warn\", \"error\", \"lint\".",
            "type": "string"
          }
        },
        "required": [
          "code",
          "message",
          "severity"
        ],
        "type": "object"
      },
      "ValidateMigrationOutput": {
        "description": "JSON output for `rocky validate-migration`.",
        "properties": {
          "command": {
            "type": "string"
          },
          "dbt_version": {
            "type": [
              "string",
              "null"
            ]
          },
          "models_failed": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "models_imported": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "project_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "total_contracts": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "total_tests": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "total_warnings": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "validations": {
            "items": {
              "$ref": "#/components/schemas/ModelValidationOutput"
            },
            "type": "array"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "command",
          "models_failed",
          "models_imported",
          "total_contracts",
          "total_tests",
          "total_warnings",
          "validations",
          "version"
        ],
        "type": "object"
      },
      "ValidateModelsStatus": {
        "description": "Status of the models directory in the validate output.",
        "properties": {
          "count": {
            "format": "uint",
            "minimum": 0.0,
            "type": "integer"
          },
          "dag_valid": {
            "type": "boolean"
          },
          "found": {
            "type": "boolean"
          }
        },
        "required": [
          "count",
          "dag_valid",
          "found"
        ],
        "type": "object"
      },
      "ValidateOutput": {
        "description": "JSON output for `rocky validate`.",
        "properties": {
          "adapters": {
            "items": {
              "$ref": "#/components/schemas/ValidateAdapterStatus"
            },
            "type": "array"
          },
          "command": {
            "type": "string"
          },
          "messages": {
            "items": {
              "$ref": "#/components/schemas/ValidateMessage"
            },
            "type": "array"
          },
          "models": {
            "$ref": "#/components/schemas/ValidateModelsStatus"
          },
          "pipelines": {
            "items": {
              "$ref": "#/components/schemas/ValidatePipelineStatus"
            },
            "type": "array"
          },
          "valid": {
            "type": "boolean"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "adapters",
          "command",
          "messages",
          "models",
          "pipelines",
          "valid",
          "version"
        ],
        "type": "object"
      },
      "ValidatePipelineStatus": {
        "description": "Status of a single pipeline in the validate output.",
        "properties": {
          "catalog_template": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "ok": {
            "type": "boolean"
          },
          "pipeline_type": {
            "type": "string"
          },
          "schema_template": {
            "type": "string"
          },
          "strategy": {
            "type": "string"
          }
        },
        "required": [
          "catalog_template",
          "name",
          "ok",
          "pipeline_type",
          "schema_template",
          "strategy"
        ],
        "type": "object"
      },
      "WatermarkEntry": {
        "properties": {
          "last_value": {
            "format": "date-time",
            "type": "string"
          },
          "table": {
            "type": "string"
          },
          "updated_at": {
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "last_value",
          "table",
          "updated_at"
        ],
        "type": "object"
      },
      "WebhookConfig": {
        "additionalProperties": false,
        "description": "Configuration for a single webhook endpoint.",
        "properties": {
          "async": {
            "default": false,
            "description": "If true, fire-and-forget (spawn task, don't await). Default: false.",
            "type": "boolean"
          },
          "body_template": {
            "description": "Mustache-style body template. If None, the full HookContext is serialized as JSON.",
            "type": [
              "string",
              "null"
            ]
          },
          "headers": {
            "additionalProperties": {
              "type": "string"
            },
            "default": {},
            "description": "Additional HTTP headers.",
            "type": "object"
          },
          "method": {
            "default": "POST",
            "description": "HTTP method (default: POST).",
            "type": "string"
          },
          "on_failure": {
            "allOf": [
              {
                "$ref": "#/components/schemas/FailureAction"
              }
            ],
            "default": "warn",
            "description": "What to do when the webhook fails."
          },
          "preset": {
            "description": "Optional preset name (e.g., \"slack\", \"pagerduty\"). When set, preset defaults are merged before execution.",
            "type": [
              "string",
              "null"
            ]
          },
          "retry_count": {
            "default": 0,
            "description": "Number of retry attempts on failure (default: 0).",
            "format": "uint32",
            "minimum": 0.0,
            "type": "integer"
          },
          "retry_delay_ms": {
            "default": 1000,
            "description": "Delay between retries in milliseconds (default: 1000).",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "secret": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/RedactedString"
              },
              {
                "type": "null"
              }
            ],
            "description": "HMAC-SHA256 signing secret. When set, adds `X-Rocky-Signature: sha256=<hex>` header. Wrapped in [`RedactedString`] so a stray `Debug` print of the hook config doesn't leak the secret into logs."
          },
          "timeout_ms": {
            "default": 10000,
            "description": "Request timeout in milliseconds (default: 10000).",
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          },
          "url": {
            "description": "Target URL for the webhook request.",
            "type": "string"
          }
        },
        "required": [
          "url"
        ],
        "type": "object"
      },
      "WebhookConfigOrList": {
        "anyOf": [
          {
            "$ref": "#/components/schemas/WebhookConfig"
          },
          {
            "items": {
              "$ref": "#/components/schemas/WebhookConfig"
            },
            "type": "array"
          }
        ],
        "description": "Supports both single-webhook and multi-webhook syntax per event.\n\nSingle: `[hook.webhooks.on_pipeline_start]` Multiple: `[[hook.webhooks.on_pipeline_start]]`"
      },
      "WorkspaceBindingConfig": {
        "additionalProperties": false,
        "description": "A workspace binding with ID and access level.",
        "properties": {
          "binding_type": {
            "allOf": [
              {
                "$ref": "#/components/schemas/BindingType"
              }
            ],
            "default": "READ_WRITE"
          },
          "id": {
            "format": "uint64",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "id"
        ],
        "type": "object"
      }
    },
    "securitySchemes": {
      "bearerAuth": {
        "description": "Optional shared-secret bearer token. Required on every route except `GET /api/v1/health` when the server is started with a token (mandatory for a non-loopback bind).",
        "scheme": "bearer",
        "type": "http"
      }
    }
  },
  "externalDocs": {
    "description": "Embedding Rocky guide",
    "url": "https://rocky-data.dev/guides/embedding/"
  },
  "info": {
    "description": "HTTP surface exposed by `rocky serve` under `/api/v1`. The canonical read routes return the same typed payloads as the corresponding `rocky <verb> --output json` command, byte for byte. Mutating work (`run`, `plan`, `apply`) is submitted as a job and polled. This document describes the `v1` contract shape; read the live engine version, schema-set hash, and available routes at runtime from `GET /api/v1/meta`.",
    "license": {
      "identifier": "Apache-2.0",
      "name": "Apache-2.0"
    },
    "title": "Rocky Engine API",
    "version": "1.0.0"
  },
  "openapi": "3.1.0",
  "paths": {
    "/api/v1/compile": {
      "get": {
        "description": "Canonical compile result. Byte-identical to `rocky compile --output json`, except the wall-clock `compile_timings`, which is inherently non-deterministic.",
        "operationId": "getCompile",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CompileOutput"
                }
              }
            },
            "description": "The full compile result."
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "The engine has no compile result yet, or the state store is locked by a running job (retryable)."
          }
        },
        "summary": "Full compile result",
        "tags": [
          "compile"
        ]
      },
      "post": {
        "description": "Server-lifecycle recompile trigger. Outside the `/api/v1` value contract.",
        "operationId": "triggerCompile",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "additionalProperties": true,
                  "description": "Ad-hoc body outside the `/api/v1` value contract; shape is not pinned and may change without a version bump.",
                  "type": "object"
                }
              }
            },
            "description": "Recompilation was triggered."
          }
        },
        "summary": "Trigger recompilation",
        "tags": [
          "compile"
        ]
      }
    },
    "/api/v1/dag": {
      "get": {
        "description": "Canonical unified DAG. Byte-identical to `rocky dag --output json`.",
        "operationId": "getDag",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DagOutput"
                }
              }
            },
            "description": "The full unified DAG."
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "The engine has no compile result yet, or the state store is locked by a running job (retryable)."
          }
        },
        "summary": "Full unified DAG",
        "tags": [
          "dag"
        ]
      }
    },
    "/api/v1/dag/layers": {
      "get": {
        "description": "Dashboard-only execution layers. Outside the `/api/v1` value contract.",
        "operationId": "getDagLayers",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "additionalProperties": true,
                  "description": "Ad-hoc body outside the `/api/v1` value contract; shape is not pinned and may change without a version bump.",
                  "type": "object"
                }
              }
            },
            "description": "The execution layers."
          }
        },
        "summary": "Execution layers (dashboard)",
        "tags": [
          "dag"
        ]
      }
    },
    "/api/v1/dag/status": {
      "get": {
        "description": "Latest recorded DAG execution status. Server-only, outside the `/api/v1` value contract.",
        "operationId": "getDagStatus",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "additionalProperties": true,
                  "description": "Ad-hoc body outside the `/api/v1` value contract; shape is not pinned and may change without a version bump.",
                  "type": "object"
                }
              }
            },
            "description": "The latest DAG run status."
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "No DAG run has been recorded yet."
          }
        },
        "summary": "Latest DAG run status (server)",
        "tags": [
          "dag"
        ]
      }
    },
    "/api/v1/health": {
      "get": {
        "description": "Auth-exempt liveness probe. Server-lifecycle, outside the `/api/v1` value contract.",
        "operationId": "getHealth",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "additionalProperties": true,
                  "description": "Ad-hoc body outside the `/api/v1` value contract; shape is not pinned and may change without a version bump.",
                  "type": "object"
                }
              }
            },
            "description": "The server is alive."
          }
        },
        "security": [],
        "summary": "Liveness probe",
        "tags": [
          "meta"
        ]
      }
    },
    "/api/v1/jobs/apply": {
      "post": {
        "description": "Submit a mutating `apply` job. Takes the single-mutating-job permit; a second run/apply while one is held returns 409.",
        "operationId": "submitApplyJob",
        "parameters": [
          {
            "description": "Advisory principal recorded for audit only. Spoofable under shared-secret auth; never an authorization input.",
            "in": "header",
            "name": "X-Rocky-Principal",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JobRequest"
              }
            }
          },
          "description": "Optional job parameters. An empty body runs the verb with its defaults.",
          "required": false
        },
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "job_id": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "job_id"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Job accepted. Poll `GET /api/v1/jobs/{id}` for status."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "The request body or a header could not be parsed."
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Another run/apply job already holds the single-mutating-job permit. The holder's id is carried in `running_job_id`."
          }
        },
        "summary": "Submit an apply job",
        "tags": [
          "jobs"
        ]
      }
    },
    "/api/v1/jobs/plan": {
      "post": {
        "description": "Submit a non-mutating `plan` job. Does not take the mutating permit, so it is never blocked by a running run/apply.",
        "operationId": "submitPlanJob",
        "parameters": [
          {
            "description": "Advisory principal recorded for audit only. Spoofable under shared-secret auth; never an authorization input.",
            "in": "header",
            "name": "X-Rocky-Principal",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JobRequest"
              }
            }
          },
          "description": "Optional job parameters. An empty body runs the verb with its defaults.",
          "required": false
        },
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "job_id": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "job_id"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Job accepted. Poll `GET /api/v1/jobs/{id}` for status."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "The request body or a header could not be parsed."
          }
        },
        "summary": "Submit a plan job",
        "tags": [
          "jobs"
        ]
      }
    },
    "/api/v1/jobs/run": {
      "post": {
        "description": "Submit a mutating `run` job. Takes the single-mutating-job permit; a second run/apply while one is held returns 409.",
        "operationId": "submitRunJob",
        "parameters": [
          {
            "description": "Advisory principal recorded for audit only. Spoofable under shared-secret auth; never an authorization input.",
            "in": "header",
            "name": "X-Rocky-Principal",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JobRequest"
              }
            }
          },
          "description": "Optional job parameters. An empty body runs the verb with its defaults.",
          "required": false
        },
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "job_id": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "job_id"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Job accepted. Poll `GET /api/v1/jobs/{id}` for status."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "The request body or a header could not be parsed."
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Another run/apply job already holds the single-mutating-job permit. The holder's id is carried in `running_job_id`."
          }
        },
        "summary": "Submit a run job",
        "tags": [
          "jobs"
        ]
      }
    },
    "/api/v1/jobs/{id}": {
      "get": {
        "description": "Job status, with the embedded canonical result once terminal. Falls back to the durable job table after a sidecar restart.",
        "operationId": "getJob",
        "parameters": [
          {
            "description": "Path parameter `id`.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobStatus"
                }
              }
            },
            "description": "The job status (with embedded result when done)."
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "No job with this id (neither in-memory nor persisted)."
          }
        },
        "summary": "Job status",
        "tags": [
          "jobs"
        ]
      }
    },
    "/api/v1/meta": {
      "get": {
        "description": "Feature-detection endpoint. Every field is computed per request (engine version, state-schema version, schema-set hash, config hash, capabilities, routes).",
        "operationId": "getMeta",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MetaOutput"
                }
              }
            },
            "description": "The engine and config fingerprint."
          }
        },
        "summary": "Engine and config fingerprint",
        "tags": [
          "meta"
        ]
      }
    },
    "/api/v1/models": {
      "get": {
        "description": "Dashboard-only model list. Outside the `/api/v1` value contract (no canonical CLI counterpart).",
        "operationId": "listModels",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "additionalProperties": true,
                  "description": "Ad-hoc body outside the `/api/v1` value contract; shape is not pinned and may change without a version bump.",
                  "type": "object"
                }
              }
            },
            "description": "The model list."
          }
        },
        "summary": "List models (dashboard)",
        "tags": [
          "models"
        ]
      }
    },
    "/api/v1/models/{name}": {
      "get": {
        "description": "Dashboard-only model detail. Outside the `/api/v1` value contract.",
        "operationId": "getModel",
        "parameters": [
          {
            "description": "Path parameter `name`.",
            "in": "path",
            "name": "name",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "additionalProperties": true,
                  "description": "Ad-hoc body outside the `/api/v1` value contract; shape is not pinned and may change without a version bump.",
                  "type": "object"
                }
              }
            },
            "description": "The model detail."
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "The named model is not in the compiled graph."
          }
        },
        "summary": "Model detail (dashboard)",
        "tags": [
          "models"
        ]
      }
    },
    "/api/v1/models/{name}/history": {
      "get": {
        "description": "Canonical per-model run history. Byte-identical to `rocky history --model <name> --output json`.",
        "operationId": "getModelHistory",
        "parameters": [
          {
            "description": "Path parameter `name`.",
            "in": "path",
            "name": "name",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModelHistoryOutput"
                }
              }
            },
            "description": "The model's run history."
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "The engine has no compile result yet, or the state store is locked by a running job (retryable)."
          }
        },
        "summary": "Per-model run history",
        "tags": [
          "history"
        ]
      }
    },
    "/api/v1/models/{name}/lineage": {
      "get": {
        "description": "Canonical lineage for a model. Byte-identical to `rocky lineage <name> --output json`.",
        "operationId": "getModelLineage",
        "parameters": [
          {
            "description": "Path parameter `name`.",
            "in": "path",
            "name": "name",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LineageOutput"
                }
              }
            },
            "description": "The model's column-level lineage."
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "The named model is not in the compiled graph."
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "The engine has no compile result yet, or the state store is locked by a running job (retryable)."
          }
        },
        "summary": "Column-level lineage",
        "tags": [
          "models"
        ]
      }
    },
    "/api/v1/models/{name}/lineage/{column}": {
      "get": {
        "description": "Canonical upstream trace for one column. Byte-identical to `rocky lineage <name> --column <column> --output json`.",
        "operationId": "traceColumnLineage",
        "parameters": [
          {
            "description": "Path parameter `name`.",
            "in": "path",
            "name": "name",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Path parameter `column`.",
            "in": "path",
            "name": "column",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ColumnLineageOutput"
                }
              }
            },
            "description": "The column's upstream lineage trace."
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "The named model is not in the compiled graph."
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "The engine has no compile result yet, or the state store is locked by a running job (retryable)."
          }
        },
        "summary": "Trace a single column",
        "tags": [
          "models"
        ]
      }
    },
    "/api/v1/models/{name}/metrics": {
      "get": {
        "description": "Canonical per-model quality snapshots. Byte-identical to `rocky metrics <name> --output json`.",
        "operationId": "getModelMetrics",
        "parameters": [
          {
            "description": "Path parameter `name`.",
            "in": "path",
            "name": "name",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MetricsOutput"
                }
              }
            },
            "description": "The model's quality snapshots."
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "The engine has no compile result yet, or the state store is locked by a running job (retryable)."
          }
        },
        "summary": "Per-model quality snapshots",
        "tags": [
          "history"
        ]
      }
    },
    "/api/v1/runs": {
      "get": {
        "description": "Canonical project run history. Byte-identical to `rocky history --output json`.",
        "operationId": "listRuns",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HistoryOutput"
                }
              }
            },
            "description": "The project run history."
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "The engine has no compile result yet, or the state store is locked by a running job (retryable)."
          }
        },
        "summary": "Project run history",
        "tags": [
          "history"
        ]
      }
    }
  },
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "servers": [
    {
      "description": "Default `rocky serve` bind (loopback only). A non-loopback host requires a bearer token.",
      "url": "http://127.0.0.1:8080"
    }
  ]
}
