//! Typed contracts for data-driven workflow primitive definitions.

use std::collections::{BTreeMap, HashMap};

use serde::{Deserialize, Serialize};

use super::{Plane, RuntimeJsonContract, RuntimeJsonError, validate_safe_identifier};

const SCHEMA_VERSION: &str = "1.0";

pub type WorkflowPrimitiveId = String;
pub type WorkItemFamilyId = String;
pub type DocumentAdapterId = String;
pub type QueueClaimPolicyId = String;
pub type TerminalActionId = String;
pub type LifecycleMutationPlanId = String;
pub type RuntimeEffectHandlerId = String;
pub type RuntimeEffectOperationId = String;
pub type RuntimeEffectOperationRunnerId = String;
pub type RuntimeEffectStoreId = String;
pub type RuntimeEffectValidatorId = String;
pub type RuntimeEffectRuleId = String;
pub type RuntimeEffectHandlerCapabilityId = String;
pub type RequestContextProfileId = String;
pub type RequestContextProviderId = String;
pub type RequestContextRenderPlanId = String;
pub type ArtifactContractId = String;

macro_rules! impl_primitive_contract {
    ($type:ty, $artifact:literal, $kind:literal, $id_field:ident) => {
        impl RuntimeJsonContract for $type {
            const ARTIFACT: &'static str = $artifact;

            fn validate_contract(&mut self) -> Result<(), RuntimeJsonError> {
                validate_literal("schema_version", &self.schema_version, SCHEMA_VERSION)?;
                validate_literal("kind", &self.kind, $kind)?;
                validate_safe_identifier(&self.$id_field, stringify!($id_field))?;
                self.validate_definition()
            }
        }

        impl $type {
            /// Deserializes and validates this workflow primitive definition.
            pub fn from_json_value(value: serde_json::Value) -> Result<Self, RuntimeJsonError> {
                <Self as RuntimeJsonContract>::from_json_value(value)
            }
        }
    };
}

/// Artifact payload formats used by filename adapters.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ArtifactFormat {
    Json,
    Markdown,
    Text,
    Directory,
}

/// Runtime-effect mutation phase categories used by failure policies.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RuntimeEffectMutationPhase {
    PreMutation,
    PartialMutation,
    Unknown,
}

/// Parser/renderer binding for one artifact filename.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ArtifactFilenameAdapterDefinition {
    pub filename: String,
    pub format: ArtifactFormat,
    pub parser_id: String,
    #[serde(default)]
    pub renderer_id: Option<String>,
}

impl ArtifactFilenameAdapterDefinition {
    fn validate(&self) -> Result<(), RuntimeJsonError> {
        validate_filename("filename", &self.filename)?;
        validate_safe_identifier(&self.parser_id, "parser_id")?;
        validate_optional_id("renderer_id", &self.renderer_id)
    }
}

/// Artifact contract registry definition.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ArtifactContractDefinition {
    #[serde(default = "default_schema_version")]
    pub schema_version: String,
    #[serde(default = "default_artifact_contract_kind")]
    pub kind: String,
    pub artifact_id: ArtifactContractId,
    pub canonical_filename: String,
    #[serde(default)]
    pub accepted_filenames: Vec<String>,
    pub preferred_format: ArtifactFormat,
    pub schema_id: String,
    pub filename_adapters: Vec<ArtifactFilenameAdapterDefinition>,
    #[serde(default)]
    pub producer_stage_kind_ids: Vec<String>,
    #[serde(default)]
    pub consumer_handler_ids: Vec<RuntimeEffectHandlerId>,
    #[serde(default)]
    pub consumer_operation_ids: Vec<RuntimeEffectOperationId>,
    #[serde(default)]
    pub destination_family_id: Option<WorkItemFamilyId>,
}

impl ArtifactContractDefinition {
    fn validate_definition(&self) -> Result<(), RuntimeJsonError> {
        validate_filename("canonical_filename", &self.canonical_filename)?;
        validate_id_list("accepted_filenames", &self.accepted_filenames, false, false)?;
        validate_safe_identifier(&self.schema_id, "schema_id")?;
        validate_id_list(
            "producer_stage_kind_ids",
            &self.producer_stage_kind_ids,
            false,
            true,
        )?;
        validate_id_list(
            "consumer_handler_ids",
            &self.consumer_handler_ids,
            false,
            true,
        )?;
        validate_id_list(
            "consumer_operation_ids",
            &self.consumer_operation_ids,
            false,
            true,
        )?;
        validate_optional_id("destination_family_id", &self.destination_family_id)?;
        if self.filename_adapters.is_empty() {
            return invalid_field("filename_adapters", "must not be empty");
        }
        for adapter in &self.filename_adapters {
            adapter.validate()?;
        }
        let mut filenames = vec![self.canonical_filename.as_str()];
        filenames.extend(self.accepted_filenames.iter().map(String::as_str));
        let adapter_names: Vec<_> = self
            .filename_adapters
            .iter()
            .map(|adapter| adapter.filename.as_str())
            .collect();
        for filename in filenames {
            if !adapter_names.contains(&filename) {
                return invalid_document("filename_adapters must cover every declared filename");
            }
        }
        Ok(())
    }
}

impl_primitive_contract!(
    ArtifactContractDefinition,
    "artifact_contract",
    "artifact_contract",
    artifact_id
);

/// Queue directories for one work-item family.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct WorkItemQueueDirs {
    pub queue: String,
    pub active: String,
    pub done: String,
    pub blocked: String,
    #[serde(default)]
    pub canceled: Option<String>,
    #[serde(default)]
    pub superseded: Option<String>,
}

impl WorkItemQueueDirs {
    fn validate(&self) -> Result<(), RuntimeJsonError> {
        for (field, value) in [
            ("queue", &self.queue),
            ("active", &self.active),
            ("done", &self.done),
            ("blocked", &self.blocked),
        ] {
            validate_runtime_relative_path(field, value)?;
        }
        validate_optional_runtime_path("canceled", &self.canceled)?;
        validate_optional_runtime_path("superseded", &self.superseded)
    }
}

/// Work-item family registry definition.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct WorkItemFamilyDefinition {
    #[serde(default = "default_schema_version")]
    pub schema_version: String,
    #[serde(default = "default_work_item_family_kind")]
    pub kind: String,
    pub family_id: WorkItemFamilyId,
    pub plane: Plane,
    pub entry_key: String,
    pub display_name: String,
    pub document_kind: String,
    pub runtime_relative_dir: String,
    #[serde(default = "default_json_extension")]
    pub file_extension: String,
    pub schema_id: String,
    pub document_adapter_id: DocumentAdapterId,
    pub queue_dirs: WorkItemQueueDirs,
    pub lifecycle_states: Vec<String>,
    #[serde(default = "default_queued")]
    pub claimable_state: String,
    #[serde(default = "default_active")]
    pub active_state: String,
    #[serde(default = "default_done")]
    pub done_state: String,
    #[serde(default = "default_blocked")]
    pub blocked_state: String,
    #[serde(default)]
    pub canceled_state: Option<String>,
    #[serde(default)]
    pub closure_blocking_states: Vec<String>,
    #[serde(default)]
    pub default_entry_key: Option<String>,
    #[serde(default)]
    pub id_field: Option<String>,
    #[serde(default = "default_created_at_field")]
    pub created_at_field: String,
    #[serde(default)]
    pub lineage_fields: Vec<String>,
    #[serde(default)]
    pub dependency_field: Option<String>,
    #[serde(default = "default_plane_scope")]
    pub one_active_policy: String,
    #[serde(default = "default_fail_policy")]
    pub duplicate_policy: String,
    #[serde(default = "default_block_source_policy")]
    pub invalid_artifact_policy: String,
    #[serde(default = "default_created_at_asc")]
    pub sort_policy: String,
    #[serde(default)]
    pub operator_capabilities: Vec<String>,
}

impl WorkItemFamilyDefinition {
    fn validate_definition(&self) -> Result<(), RuntimeJsonError> {
        validate_safe_identifier(&self.entry_key, "entry_key")?;
        require_non_blank("display_name", &self.display_name)?;
        validate_safe_identifier(&self.document_kind, "document_kind")?;
        validate_runtime_relative_path("runtime_relative_dir", &self.runtime_relative_dir)?;
        validate_file_extension("file_extension", &self.file_extension)?;
        validate_safe_identifier(&self.schema_id, "schema_id")?;
        validate_safe_identifier(&self.document_adapter_id, "document_adapter_id")?;
        self.queue_dirs.validate()?;
        validate_id_list("lifecycle_states", &self.lifecycle_states, true, true)?;
        for (field, state) in [
            ("claimable_state", &self.claimable_state),
            ("active_state", &self.active_state),
            ("done_state", &self.done_state),
            ("blocked_state", &self.blocked_state),
        ] {
            validate_safe_identifier(state, field)?;
            if !self.lifecycle_states.contains(state) {
                return invalid_field(field, "must be declared in lifecycle_states");
            }
        }
        validate_optional_id("canceled_state", &self.canceled_state)?;
        validate_id_list(
            "closure_blocking_states",
            &self.closure_blocking_states,
            false,
            true,
        )?;
        validate_optional_id("default_entry_key", &self.default_entry_key)?;
        validate_optional_id("id_field", &self.id_field)?;
        validate_safe_identifier(&self.created_at_field, "created_at_field")?;
        validate_id_list("lineage_fields", &self.lineage_fields, false, true)?;
        validate_optional_id("dependency_field", &self.dependency_field)?;
        validate_id_list(
            "operator_capabilities",
            &self.operator_capabilities,
            false,
            true,
        )
    }
}

impl_primitive_contract!(
    WorkItemFamilyDefinition,
    "work_item_family",
    "work_item_family",
    family_id
);

/// Work-item document adapter registry definition.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct WorkItemDocumentAdapterDefinition {
    #[serde(default = "default_schema_version")]
    pub schema_version: String,
    #[serde(default = "default_document_adapter_kind")]
    pub kind: String,
    pub adapter_id: DocumentAdapterId,
    pub schema_id: String,
    pub supported_file_extensions: Vec<String>,
    pub family_ids: Vec<WorkItemFamilyId>,
    pub can_parse: bool,
    pub can_render: bool,
    pub can_summarize: bool,
    pub supports_dependencies: bool,
    pub supports_lineage: bool,
}

impl WorkItemDocumentAdapterDefinition {
    fn validate_definition(&self) -> Result<(), RuntimeJsonError> {
        validate_safe_identifier(&self.schema_id, "schema_id")?;
        if self.supported_file_extensions.is_empty() {
            return invalid_field("supported_file_extensions", "must not be empty");
        }
        for extension in &self.supported_file_extensions {
            validate_file_extension("supported_file_extensions", extension)?;
        }
        validate_id_list("family_ids", &self.family_ids, true, true)?;
        if !(self.can_parse || self.can_render || self.can_summarize) {
            return invalid_document("document adapter must support at least one operation");
        }
        Ok(())
    }
}

impl_primitive_contract!(
    WorkItemDocumentAdapterDefinition,
    "work_item_document_adapter",
    "work_item_document_adapter",
    adapter_id
);

/// Work-item partition selector registry definition.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct WorkItemPartitionSelectorDefinition {
    #[serde(default = "default_schema_version")]
    pub schema_version: String,
    #[serde(default = "default_partition_selector_kind")]
    pub kind: String,
    pub selector_id: String,
    pub family_id: WorkItemFamilyId,
    pub output_kind: String,
    pub supports_static_compile_check: bool,
}

impl WorkItemPartitionSelectorDefinition {
    fn validate_definition(&self) -> Result<(), RuntimeJsonError> {
        validate_safe_identifier(&self.family_id, "family_id")?;
        validate_safe_identifier(&self.output_kind, "output_kind")?;
        Ok(())
    }
}

impl_primitive_contract!(
    WorkItemPartitionSelectorDefinition,
    "work_item_partition_selector",
    "work_item_partition_selector",
    selector_id
);

/// Queue claim policy for a runtime plane.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PlaneQueueClaimPolicyDefinition {
    #[serde(default = "default_schema_version")]
    pub schema_version: String,
    #[serde(default = "default_queue_claim_policy_kind")]
    pub kind: String,
    pub policy_id: QueueClaimPolicyId,
    pub plane: Plane,
    #[serde(default)]
    pub family_order: Vec<WorkItemFamilyId>,
    #[serde(default = "default_defer_unrelated")]
    pub closure_lineage_policy: String,
    #[serde(default = "default_idle")]
    pub empty_behavior: String,
}

impl PlaneQueueClaimPolicyDefinition {
    fn validate_definition(&self) -> Result<(), RuntimeJsonError> {
        validate_id_list("family_order", &self.family_order, false, true)
    }
}

impl_primitive_contract!(
    PlaneQueueClaimPolicyDefinition,
    "plane_queue_claim_policy",
    "plane_queue_claim_policy",
    policy_id
);

/// Scheduler lane definition.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct WorkflowLaneDefinition {
    #[serde(default = "default_schema_version")]
    pub schema_version: String,
    #[serde(default = "default_workflow_lane_kind")]
    pub kind: String,
    pub lane_id: String,
    pub plane: Plane,
    #[serde(alias = "accepted_family_ids")]
    pub allowed_family_ids: Vec<WorkItemFamilyId>,
    pub claim_policy_id: QueueClaimPolicyId,
    #[serde(default = "default_one")]
    pub max_active_runs: u64,
    #[serde(default = "default_plane_scope")]
    pub one_active_scope: String,
    #[serde(default)]
    pub partition_selector_id: Option<String>,
    #[serde(default = "default_plane_scope")]
    pub mutation_lock_scope: String,
    #[serde(default = "default_single_writer_serialized")]
    pub result_application_policy: String,
    #[serde(default)]
    pub conflict_policy_id: Option<String>,
}

impl WorkflowLaneDefinition {
    fn validate_definition(&self) -> Result<(), RuntimeJsonError> {
        validate_safe_identifier(&self.lane_id, "lane_id")?;
        validate_id_list("allowed_family_ids", &self.allowed_family_ids, true, true)?;
        validate_safe_identifier(&self.claim_policy_id, "claim_policy_id")?;
        validate_optional_id("partition_selector_id", &self.partition_selector_id)?;
        validate_optional_id("conflict_policy_id", &self.conflict_policy_id)?;
        if self.max_active_runs == 0 {
            return invalid_field("max_active_runs", "must be >= 1");
        }
        Ok(())
    }
}

impl_primitive_contract!(
    WorkflowLaneDefinition,
    "workflow_lane",
    "workflow_lane",
    lane_id
);

/// Lane conflict policy definition.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct LaneConflictPolicyDefinition {
    #[serde(default = "default_schema_version")]
    pub schema_version: String,
    #[serde(default = "default_lane_conflict_policy_kind")]
    pub kind: String,
    pub policy_id: String,
    #[serde(default)]
    pub lane_ids: Vec<String>,
    #[serde(default)]
    pub concurrent_with_lane_ids: Vec<String>,
    pub conflict_scopes: Vec<String>,
    #[serde(default)]
    pub lock_acquisition_order: Vec<String>,
    #[serde(default = "default_after_result_application")]
    pub release_policy: String,
    #[serde(default = "default_reject_compile")]
    pub missing_lock_policy: String,
}

impl LaneConflictPolicyDefinition {
    fn validate_definition(&self) -> Result<(), RuntimeJsonError> {
        validate_id_list("lane_ids", &self.lane_ids, true, true)?;
        validate_id_list(
            "concurrent_with_lane_ids",
            &self.concurrent_with_lane_ids,
            true,
            true,
        )?;
        validate_id_list("conflict_scopes", &self.conflict_scopes, true, true)?;
        validate_id_list(
            "lock_acquisition_order",
            &self.lock_acquisition_order,
            false,
            true,
        )
    }
}

impl_primitive_contract!(
    LaneConflictPolicyDefinition,
    "lane_conflict_policy",
    "lane_conflict_policy",
    policy_id
);

/// Terminal action definition.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TerminalActionDefinition {
    #[serde(default = "default_schema_version")]
    pub schema_version: String,
    #[serde(default = "default_terminal_action_kind")]
    pub kind: String,
    pub terminal_action_id: TerminalActionId,
    pub terminal_class: String,
    #[serde(default)]
    pub lifecycle_mutation_plan_id: Option<LifecycleMutationPlanId>,
    #[serde(default)]
    pub effect_rule_ids: Vec<RuntimeEffectRuleId>,
    #[serde(default)]
    pub status_marker: Option<String>,
    #[serde(default)]
    pub operator_summary_template: Option<String>,
    #[serde(default)]
    pub non_mutating: bool,
}

impl TerminalActionDefinition {
    fn validate_definition(&self) -> Result<(), RuntimeJsonError> {
        validate_safe_identifier(&self.terminal_class, "terminal_class")?;
        validate_optional_id(
            "lifecycle_mutation_plan_id",
            &self.lifecycle_mutation_plan_id,
        )?;
        validate_id_list("effect_rule_ids", &self.effect_rule_ids, false, true)?;
        validate_optional_non_blank("status_marker", &self.status_marker)?;
        validate_optional_non_blank("operator_summary_template", &self.operator_summary_template)?;
        if !self.non_mutating && self.lifecycle_mutation_plan_id.is_none() {
            return invalid_document(
                "lifecycle_mutation_plan_id is required for mutating terminal actions",
            );
        }
        Ok(())
    }
}

impl_primitive_contract!(
    TerminalActionDefinition,
    "terminal_action",
    "terminal_action",
    terminal_action_id
);

/// Lifecycle mutation plan definition.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct LifecycleMutationPlanDefinition {
    #[serde(default = "default_schema_version")]
    pub schema_version: String,
    #[serde(default = "default_lifecycle_mutation_plan_kind")]
    pub kind: String,
    pub plan_id: LifecycleMutationPlanId,
    pub source_node_id: String,
    pub outcome_id: String,
    pub source_family_id: WorkItemFamilyId,
    pub owner: String,
    pub source_from_state: String,
    #[serde(default)]
    pub source_to_state: Option<String>,
    pub ordering: String,
    #[serde(default)]
    pub lifecycle_action_id: Option<String>,
}

impl LifecycleMutationPlanDefinition {
    fn validate_definition(&self) -> Result<(), RuntimeJsonError> {
        validate_safe_identifier(&self.source_node_id, "source_node_id")?;
        validate_safe_identifier(&self.outcome_id, "outcome_id")?;
        validate_safe_identifier(&self.source_family_id, "source_family_id")?;
        validate_safe_identifier(&self.owner, "owner")?;
        validate_safe_identifier(&self.source_from_state, "source_from_state")?;
        validate_optional_id("source_to_state", &self.source_to_state)?;
        validate_safe_identifier(&self.ordering, "ordering")?;
        validate_optional_id("lifecycle_action_id", &self.lifecycle_action_id)
    }
}

impl_primitive_contract!(
    LifecycleMutationPlanDefinition,
    "lifecycle_mutation_plan",
    "lifecycle_mutation_plan",
    plan_id
);

/// Runtime effect handler definition.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RuntimeEffectHandlerDefinition {
    #[serde(default = "default_schema_version")]
    pub schema_version: String,
    #[serde(default = "default_runtime_effect_handler_kind")]
    pub kind: String,
    pub handler_id: RuntimeEffectHandlerId,
    pub source_planes: Vec<Plane>,
    #[serde(default)]
    pub allowed_source_families: Vec<WorkItemFamilyId>,
    #[serde(default)]
    pub destination_kinds: Vec<String>,
    #[serde(default)]
    pub required_artifacts: Vec<String>,
    #[serde(default)]
    pub optional_artifacts: Vec<String>,
    pub returns_source_lifecycle_intent: bool,
    pub requires_lifecycle_mutation_plan: bool,
    pub creates_work_items: bool,
    #[serde(default)]
    pub creates_incidents: bool,
    #[serde(default)]
    pub creates_closure_targets: bool,
    #[serde(default)]
    pub declared_capabilities: Vec<RuntimeEffectHandlerCapabilityId>,
    pub failure_classes: Vec<String>,
}

impl RuntimeEffectHandlerDefinition {
    fn validate_definition(&self) -> Result<(), RuntimeJsonError> {
        if self.source_planes.is_empty() {
            return invalid_field("source_planes", "must not be empty");
        }
        validate_id_list(
            "allowed_source_families",
            &self.allowed_source_families,
            false,
            true,
        )?;
        validate_id_list("destination_kinds", &self.destination_kinds, false, true)?;
        validate_id_list("required_artifacts", &self.required_artifacts, false, true)?;
        validate_id_list("optional_artifacts", &self.optional_artifacts, false, true)?;
        validate_id_list(
            "declared_capabilities",
            &self.declared_capabilities,
            false,
            true,
        )?;
        validate_id_list("failure_classes", &self.failure_classes, true, true)?;
        if self.returns_source_lifecycle_intent && !self.requires_lifecycle_mutation_plan {
            return invalid_document(
                "requires_lifecycle_mutation_plan must be true when lifecycle intent is returned",
            );
        }
        if self.creates_work_items && self.destination_kinds.is_empty() {
            return invalid_field(
                "destination_kinds",
                "required when creates_work_items is true",
            );
        }
        Ok(())
    }
}

impl_primitive_contract!(
    RuntimeEffectHandlerDefinition,
    "runtime_effect_handler",
    "runtime_effect_handler",
    handler_id
);

/// Runtime effect operation-runner definition.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RuntimeEffectOperationRunnerDefinition {
    #[serde(default = "default_schema_version")]
    pub schema_version: String,
    #[serde(default = "default_runtime_effect_runner_kind")]
    pub kind: String,
    pub runner_id: RuntimeEffectOperationRunnerId,
    pub operation_ids: Vec<RuntimeEffectOperationId>,
    #[serde(default)]
    pub required_runtime_capabilities: Vec<RuntimeEffectHandlerCapabilityId>,
    #[serde(default)]
    pub required_runtime_capabilities_by_operation_id:
        BTreeMap<RuntimeEffectOperationId, Vec<RuntimeEffectHandlerCapabilityId>>,
    #[serde(default)]
    pub legacy_handler_ids: Vec<RuntimeEffectHandlerId>,
    #[serde(default)]
    pub legacy_handler_operation_ids: BTreeMap<RuntimeEffectHandlerId, RuntimeEffectOperationId>,
    #[serde(default)]
    pub result_display_aliases: BTreeMap<RuntimeEffectOperationId, RuntimeEffectHandlerId>,
}

impl RuntimeEffectOperationRunnerDefinition {
    fn validate_definition(&self) -> Result<(), RuntimeJsonError> {
        validate_id_list("operation_ids", &self.operation_ids, true, true)?;
        validate_id_list(
            "required_runtime_capabilities",
            &self.required_runtime_capabilities,
            false,
            true,
        )?;
        validate_id_list("legacy_handler_ids", &self.legacy_handler_ids, false, true)?;

        for (operation_id, capabilities) in &self.required_runtime_capabilities_by_operation_id {
            validate_safe_identifier(
                operation_id,
                "required_runtime_capabilities_by_operation_id key",
            )?;
            validate_id_list(
                "required_runtime_capabilities_by_operation_id",
                capabilities,
                false,
                true,
            )?;
        }
        for (handler_id, operation_id) in &self.legacy_handler_operation_ids {
            validate_safe_identifier(handler_id, "legacy_handler_operation_ids key")?;
            validate_safe_identifier(operation_id, "legacy_handler_operation_ids value")?;
        }
        for (operation_id, handler_id) in &self.result_display_aliases {
            validate_safe_identifier(operation_id, "result_display_aliases key")?;
            validate_safe_identifier(handler_id, "result_display_aliases value")?;
        }

        let operation_ids: std::collections::HashSet<_> =
            self.operation_ids.iter().map(String::as_str).collect();
        let legacy_handler_ids: std::collections::HashSet<_> =
            self.legacy_handler_ids.iter().map(String::as_str).collect();
        let mapped_handlers: std::collections::HashSet<_> = self
            .legacy_handler_operation_ids
            .keys()
            .map(String::as_str)
            .collect();

        if !mapped_handlers.is_subset(&legacy_handler_ids) {
            return invalid_document(
                "legacy_handler_operation_ids keys must be declared in legacy_handler_ids",
            );
        }
        if !self
            .required_runtime_capabilities_by_operation_id
            .keys()
            .all(|operation_id| operation_ids.contains(operation_id.as_str()))
        {
            return invalid_document(
                "required_runtime_capabilities_by_operation_id keys must be declared in operation_ids",
            );
        }
        if !self
            .legacy_handler_operation_ids
            .values()
            .all(|operation_id| operation_ids.contains(operation_id.as_str()))
        {
            return invalid_document(
                "legacy_handler_operation_ids values must be declared in operation_ids",
            );
        }
        if self.operation_ids.len() > 1 && mapped_handlers != legacy_handler_ids {
            return invalid_document(
                "legacy_handler_operation_ids must map every legacy_handler_id when a runner supports multiple operations",
            );
        }
        if !self
            .result_display_aliases
            .keys()
            .all(|operation_id| operation_ids.contains(operation_id.as_str()))
        {
            return invalid_document(
                "result_display_aliases keys must be declared in operation_ids",
            );
        }
        if !self
            .result_display_aliases
            .values()
            .all(|handler_id| legacy_handler_ids.contains(handler_id.as_str()))
        {
            return invalid_document(
                "result_display_aliases values must be declared in legacy_handler_ids",
            );
        }
        Ok(())
    }

    #[must_use]
    pub fn operation_id_for_legacy_handler(
        &self,
        handler_id: &str,
    ) -> Option<RuntimeEffectOperationId> {
        if !self.legacy_handler_ids.iter().any(|id| id == handler_id) {
            return None;
        }
        if let Some(operation_id) = self.legacy_handler_operation_ids.get(handler_id) {
            return Some(operation_id.clone());
        }
        if self.operation_ids.len() == 1 {
            return self.operation_ids.first().cloned();
        }
        None
    }

    #[must_use]
    pub fn runtime_capabilities_for_operation(
        &self,
        operation_id: &str,
    ) -> &[RuntimeEffectHandlerCapabilityId] {
        self.required_runtime_capabilities_by_operation_id
            .get(operation_id)
            .map(Vec::as_slice)
            .unwrap_or(self.required_runtime_capabilities.as_slice())
    }
}

impl_primitive_contract!(
    RuntimeEffectOperationRunnerDefinition,
    "runtime_effect_runner",
    "runtime_effect_runner",
    runner_id
);

/// Runtime effect store definition.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RuntimeEffectStoreDefinition {
    #[serde(default = "default_schema_version")]
    pub schema_version: String,
    #[serde(default = "default_runtime_effect_store_kind")]
    pub kind: String,
    pub store_id: RuntimeEffectStoreId,
    pub store_type: String,
    pub runtime_relative_root: String,
    #[serde(default)]
    pub path_template: Option<String>,
    #[serde(default = "default_runtime_effect_store_owner")]
    pub owner: String,
    #[serde(default = "default_runtime_effect_store_write_policy")]
    pub write_policy: String,
}

impl RuntimeEffectStoreDefinition {
    fn validate_definition(&self) -> Result<(), RuntimeJsonError> {
        validate_safe_identifier(&self.store_type, "store_type")?;
        validate_runtime_relative_path("runtime_relative_root", &self.runtime_relative_root)?;
        validate_optional_runtime_path("path_template", &self.path_template)?;
        validate_safe_identifier(&self.owner, "owner")?;
        validate_safe_identifier(&self.write_policy, "write_policy")?;
        Ok(())
    }
}

impl_primitive_contract!(
    RuntimeEffectStoreDefinition,
    "runtime_effect_store",
    "runtime_effect_store",
    store_id
);

/// Runtime effect validator definition.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RuntimeEffectValidatorDefinition {
    #[serde(default = "default_schema_version")]
    pub schema_version: String,
    #[serde(default = "default_runtime_effect_validator_kind")]
    pub kind: String,
    pub validator_id: RuntimeEffectValidatorId,
    pub primitive_id: String,
    #[serde(default)]
    pub input_artifact_ids: Vec<ArtifactContractId>,
    #[serde(default)]
    pub store_ids: Vec<RuntimeEffectStoreId>,
    pub failure_class: String,
    #[serde(default = "default_runtime_effect_mutation_phase")]
    pub mutation_phase: RuntimeEffectMutationPhase,
}

impl RuntimeEffectValidatorDefinition {
    fn validate_definition(&self) -> Result<(), RuntimeJsonError> {
        validate_safe_identifier(&self.primitive_id, "primitive_id")?;
        validate_id_list("input_artifact_ids", &self.input_artifact_ids, false, true)?;
        validate_id_list("store_ids", &self.store_ids, false, true)?;
        validate_safe_identifier(&self.failure_class, "failure_class")?;
        Ok(())
    }
}

impl_primitive_contract!(
    RuntimeEffectValidatorDefinition,
    "runtime_effect_validator",
    "runtime_effect_validator",
    validator_id
);

/// One operation step in a declarative runtime effect operation.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RuntimeEffectOperationStepDefinition {
    pub step_id: String,
    pub primitive_id: String,
    #[serde(default = "default_runtime_effect_mutation_phase")]
    pub mutation_phase: RuntimeEffectMutationPhase,
    #[serde(default)]
    pub reads_artifact_ids: Vec<ArtifactContractId>,
    #[serde(default)]
    pub store_id: Option<RuntimeEffectStoreId>,
    #[serde(default)]
    pub validator_ids: Vec<RuntimeEffectValidatorId>,
    #[serde(default)]
    pub writes_store: bool,
    #[serde(default)]
    pub journal_event_type: Option<String>,
}

impl RuntimeEffectOperationStepDefinition {
    fn validate(&self) -> Result<(), RuntimeJsonError> {
        validate_safe_identifier(&self.step_id, "step_id")?;
        validate_safe_identifier(&self.primitive_id, "primitive_id")?;
        validate_id_list("reads_artifact_ids", &self.reads_artifact_ids, false, true)?;
        validate_optional_id("store_id", &self.store_id)?;
        validate_id_list("validator_ids", &self.validator_ids, false, true)?;
        validate_optional_id("journal_event_type", &self.journal_event_type)?;
        if self.writes_store && self.store_id.is_none() {
            return invalid_document("store_id is required when writes_store is true");
        }
        Ok(())
    }
}

/// One failure-class mapping for an operation.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RuntimeEffectFailureMappingDefinition {
    pub failure_class: String,
    pub mutation_phase: RuntimeEffectMutationPhase,
    #[serde(default)]
    pub validator_id: Option<RuntimeEffectValidatorId>,
    #[serde(default)]
    pub retryable: bool,
}

impl RuntimeEffectFailureMappingDefinition {
    fn validate(&self) -> Result<(), RuntimeJsonError> {
        validate_safe_identifier(&self.failure_class, "failure_class")?;
        validate_optional_id("validator_id", &self.validator_id)
    }
}

/// Repair-closure contract for one operation failure class.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RuntimeEffectRepairClosureContractDefinition {
    pub failure_class: String,
    pub repair_operation_id: RuntimeEffectOperationId,
    pub target_node_id: String,
    pub target_terminal_outcome: String,
    pub required_repair_evidence_artifact_ids: Vec<ArtifactContractId>,
    pub affected_source_family_id: WorkItemFamilyId,
    pub source_lifecycle_behavior_on_repair_success: String,
    pub source_lifecycle_behavior_on_repair_failure: String,
    #[serde(default)]
    pub supports_partial_mutation: bool,
    #[serde(default = "default_true")]
    pub requires_resume_guard: bool,
}

impl RuntimeEffectRepairClosureContractDefinition {
    fn validate(&self) -> Result<(), RuntimeJsonError> {
        validate_safe_identifier(&self.failure_class, "failure_class")?;
        validate_safe_identifier(&self.repair_operation_id, "repair_operation_id")?;
        validate_safe_identifier(&self.target_node_id, "target_node_id")?;
        validate_safe_identifier(&self.target_terminal_outcome, "target_terminal_outcome")?;
        validate_id_list(
            "required_repair_evidence_artifact_ids",
            &self.required_repair_evidence_artifact_ids,
            true,
            true,
        )?;
        validate_safe_identifier(&self.affected_source_family_id, "affected_source_family_id")?;
        validate_safe_identifier(
            &self.source_lifecycle_behavior_on_repair_success,
            "source_lifecycle_behavior_on_repair_success",
        )?;
        validate_safe_identifier(
            &self.source_lifecycle_behavior_on_repair_failure,
            "source_lifecycle_behavior_on_repair_failure",
        )?;
        Ok(())
    }
}

/// Idempotency contract for a runtime effect operation.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RuntimeEffectIdempotencyDefinition {
    pub duplicate_policy: String,
    pub replay_policy: String,
    #[serde(default)]
    pub equivalence_validator_ids: Vec<RuntimeEffectValidatorId>,
}

impl RuntimeEffectIdempotencyDefinition {
    fn validate(&self) -> Result<(), RuntimeJsonError> {
        validate_safe_identifier(&self.duplicate_policy, "duplicate_policy")?;
        validate_safe_identifier(&self.replay_policy, "replay_policy")?;
        validate_id_list(
            "equivalence_validator_ids",
            &self.equivalence_validator_ids,
            false,
            true,
        )
    }
}

/// Mutation journal contract for a runtime effect operation.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RuntimeEffectMutationJournalDefinition {
    #[serde(default = "default_runtime_effect_mutation_journal_schema_id")]
    pub schema_id: String,
    pub entry_id_template: String,
    pub required_fields: Vec<String>,
    #[serde(default)]
    pub record_step_ids: Vec<String>,
}

impl RuntimeEffectMutationJournalDefinition {
    fn validate(&self) -> Result<(), RuntimeJsonError> {
        require_non_blank("schema_id", &self.schema_id)?;
        require_non_blank("entry_id_template", &self.entry_id_template)?;
        validate_id_list("required_fields", &self.required_fields, true, true)?;
        validate_id_list("record_step_ids", &self.record_step_ids, false, true)
    }
}

/// Declarative runtime effect operation definition.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RuntimeEffectOperationDefinition {
    #[serde(default = "default_schema_version")]
    pub schema_version: String,
    #[serde(default = "default_runtime_effect_operation_kind")]
    pub kind: String,
    pub operation_id: RuntimeEffectOperationId,
    pub display_name: String,
    #[serde(default)]
    pub legacy_handler_ids: Vec<RuntimeEffectHandlerId>,
    #[serde(default)]
    pub required_artifacts: Vec<ArtifactContractId>,
    #[serde(default)]
    pub produced_artifacts: Vec<ArtifactContractId>,
    pub steps: Vec<RuntimeEffectOperationStepDefinition>,
    pub idempotency: RuntimeEffectIdempotencyDefinition,
    pub failure_mappings: Vec<RuntimeEffectFailureMappingDefinition>,
    #[serde(default)]
    pub repair_closure_contracts: Vec<RuntimeEffectRepairClosureContractDefinition>,
    pub mutation_journal: RuntimeEffectMutationJournalDefinition,
    #[serde(default)]
    pub partial_commit_policy: Option<String>,
}

impl RuntimeEffectOperationDefinition {
    fn validate_definition(&self) -> Result<(), RuntimeJsonError> {
        require_non_blank("display_name", &self.display_name)?;
        validate_id_list("legacy_handler_ids", &self.legacy_handler_ids, false, true)?;
        validate_id_list("required_artifacts", &self.required_artifacts, false, true)?;
        validate_id_list("produced_artifacts", &self.produced_artifacts, false, true)?;
        if self.steps.is_empty() {
            return invalid_field("steps", "must not be empty");
        }
        self.idempotency.validate()?;
        if self.failure_mappings.is_empty() {
            return invalid_field("failure_mappings", "must not be empty");
        }
        self.mutation_journal.validate()?;
        validate_optional_id("partial_commit_policy", &self.partial_commit_policy)?;

        let mut step_ids = std::collections::HashSet::new();
        for step in &self.steps {
            step.validate()?;
            if !step_ids.insert(step.step_id.as_str()) {
                return invalid_document(
                    "operation steps may not contain duplicate step_id values",
                );
            }
        }
        for mapping in &self.failure_mappings {
            mapping.validate()?;
        }
        let failure_classes: std::collections::HashSet<_> = self
            .failure_mappings
            .iter()
            .map(|mapping| mapping.failure_class.as_str())
            .collect();
        let mut seen_repair_failure_classes = std::collections::HashSet::new();
        for contract in &self.repair_closure_contracts {
            contract.validate()?;
            if !seen_repair_failure_classes.insert(contract.failure_class.as_str()) {
                return invalid_document(
                    "repair_closure_contracts may not contain duplicate failure_class values",
                );
            }
            if !failure_classes.contains(contract.failure_class.as_str()) {
                return invalid_document(
                    "repair_closure_contracts failure_class values must be declared in failure_mappings",
                );
            }
        }
        for step_id in &self.mutation_journal.record_step_ids {
            if !step_ids.contains(step_id.as_str()) {
                return invalid_document(
                    "mutation_journal record_step_ids must reference operation steps",
                );
            }
        }
        Ok(())
    }
}

impl_primitive_contract!(
    RuntimeEffectOperationDefinition,
    "runtime_effect_operation",
    "runtime_effect_operation",
    operation_id
);

/// Runtime effect rule definition.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RuntimeEffectRuleDefinition {
    #[serde(default = "default_schema_version")]
    pub schema_version: String,
    #[serde(default = "default_runtime_effect_rule_kind")]
    pub kind: String,
    pub rule_id: RuntimeEffectRuleId,
    pub effect_operation_id: String,
    pub source_node_id: String,
    pub on_outcomes: Vec<String>,
    #[serde(default)]
    pub handler_id: Option<RuntimeEffectHandlerId>,
    #[serde(default)]
    pub required_run_artifacts: Vec<String>,
    #[serde(default)]
    pub destination_family_id: Option<WorkItemFamilyId>,
    #[serde(default)]
    pub creates_work_items: bool,
    #[serde(default)]
    pub required_handler_capabilities: Vec<RuntimeEffectHandlerCapabilityId>,
    pub duplicate_policy: String,
    pub partial_commit_policy: String,
    pub replay_policy: String,
    pub lineage_policy: String,
    pub applies_before_route: bool,
    #[serde(default)]
    pub lifecycle_mutation_plan_id: Option<LifecycleMutationPlanId>,
}

impl RuntimeEffectRuleDefinition {
    fn validate_definition(&self) -> Result<(), RuntimeJsonError> {
        validate_safe_identifier(&self.effect_operation_id, "effect_operation_id")?;
        validate_safe_identifier(&self.source_node_id, "source_node_id")?;
        validate_id_list("on_outcomes", &self.on_outcomes, true, true)?;
        validate_optional_id("handler_id", &self.handler_id)?;
        validate_id_list(
            "required_run_artifacts",
            &self.required_run_artifacts,
            false,
            true,
        )?;
        validate_optional_id("destination_family_id", &self.destination_family_id)?;
        validate_id_list(
            "required_handler_capabilities",
            &self.required_handler_capabilities,
            false,
            true,
        )?;
        validate_safe_identifier(&self.duplicate_policy, "duplicate_policy")?;
        validate_safe_identifier(&self.partial_commit_policy, "partial_commit_policy")?;
        validate_safe_identifier(&self.replay_policy, "replay_policy")?;
        validate_safe_identifier(&self.lineage_policy, "lineage_policy")?;
        validate_optional_id(
            "lifecycle_mutation_plan_id",
            &self.lifecycle_mutation_plan_id,
        )?;
        if self.creates_work_items && self.destination_family_id.is_none() {
            return invalid_document(
                "destination_family_id is required when creates_work_items is true",
            );
        }
        Ok(())
    }
}

impl_primitive_contract!(
    RuntimeEffectRuleDefinition,
    "runtime_effect_rule",
    "runtime_effect_rule",
    rule_id
);

/// Outcome artifact declaration.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct OutcomeArtifactDefinition {
    #[serde(default = "default_schema_version")]
    pub schema_version: String,
    #[serde(default = "default_outcome_artifact_kind")]
    pub kind: String,
    pub outcome_id: String,
    #[serde(default)]
    pub required_artifacts: Vec<String>,
    #[serde(default)]
    pub optional_artifacts: Vec<String>,
}

impl OutcomeArtifactDefinition {
    fn validate_definition(&self) -> Result<(), RuntimeJsonError> {
        validate_id_list("required_artifacts", &self.required_artifacts, false, true)?;
        validate_id_list("optional_artifacts", &self.optional_artifacts, false, true)
    }
}

impl_primitive_contract!(
    OutcomeArtifactDefinition,
    "outcome_artifact",
    "outcome_artifact",
    outcome_id
);

/// Request-context provider definition.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RequestContextProviderDefinition {
    #[serde(default = "default_schema_version")]
    pub schema_version: String,
    #[serde(default = "default_request_context_provider_kind")]
    pub kind: String,
    pub provider_id: RequestContextProviderId,
    pub python_registry_id: String,
    pub supported_request_kinds: Vec<String>,
    pub supported_planes: Vec<Plane>,
    pub capabilities: Vec<String>,
    #[serde(default)]
    pub required_workspace_data_surfaces: Vec<String>,
}

impl RequestContextProviderDefinition {
    fn validate_definition(&self) -> Result<(), RuntimeJsonError> {
        validate_safe_identifier(&self.python_registry_id, "python_registry_id")?;
        validate_id_list(
            "supported_request_kinds",
            &self.supported_request_kinds,
            true,
            true,
        )?;
        if self.supported_planes.is_empty() {
            return invalid_field("supported_planes", "must not be empty");
        }
        let mut seen_planes = std::collections::HashSet::new();
        for plane in &self.supported_planes {
            if !seen_planes.insert(*plane) {
                return invalid_field("supported_planes", "must not contain duplicates");
            }
        }
        validate_id_list("capabilities", &self.capabilities, true, true)?;
        validate_id_list(
            "required_workspace_data_surfaces",
            &self.required_workspace_data_surfaces,
            false,
            true,
        )?;
        Ok(())
    }
}

impl_primitive_contract!(
    RequestContextProviderDefinition,
    "request_context_provider",
    "request_context_provider",
    provider_id
);

/// Request-context profile definition.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RequestContextProfileDefinition {
    #[serde(default = "default_schema_version")]
    pub schema_version: String,
    #[serde(default = "default_request_context_profile_kind")]
    pub kind: String,
    pub profile_id: RequestContextProfileId,
    pub request_kind: String,
    pub provider_id: RequestContextProviderId,
    pub primary_render_plan_id: RequestContextRenderPlanId,
    #[serde(default)]
    pub allow_render_plan_override: bool,
    #[serde(default)]
    pub required_providers: Vec<String>,
    #[serde(default)]
    pub optional_providers: Vec<String>,
    #[serde(default)]
    pub output_path_preferences: BTreeMap<String, String>,
    pub visibility_policy: String,
}

impl RequestContextProfileDefinition {
    fn validate_definition(&self) -> Result<(), RuntimeJsonError> {
        validate_safe_identifier(&self.request_kind, "request_kind")?;
        validate_safe_identifier(&self.provider_id, "provider_id")?;
        validate_safe_identifier(&self.primary_render_plan_id, "primary_render_plan_id")?;
        validate_id_list("required_providers", &self.required_providers, false, true)?;
        validate_id_list("optional_providers", &self.optional_providers, false, true)?;
        for (key, path) in &self.output_path_preferences {
            validate_safe_identifier(key, "output_path_preferences key")?;
            validate_runtime_relative_path("output_path_preferences", path)?;
        }
        validate_safe_identifier(&self.visibility_policy, "visibility_policy")?;
        Ok(())
    }
}

impl_primitive_contract!(
    RequestContextProfileDefinition,
    "request_context_profile",
    "request_context_profile",
    profile_id
);

/// Request-context render plan definition.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RequestContextRenderPlan {
    #[serde(default = "default_schema_version")]
    pub schema_version: String,
    #[serde(default = "default_request_context_render_plan_kind")]
    pub kind: String,
    pub render_plan_id: RequestContextRenderPlanId,
    #[serde(default)]
    pub profile_id: Option<RequestContextProfileId>,
    pub bundle_schema_version: String,
    #[serde(default, alias = "section_order")]
    pub included_sections: Vec<String>,
    #[serde(default)]
    pub required_provider_capabilities: Vec<String>,
    pub artifact_ref_policy: String,
    pub prompt_rendering_behavior: String,
    pub redaction_policy_id: String,
    #[serde(default)]
    pub max_inline_bytes_by_role: BTreeMap<String, u64>,
    pub missing_optional_provider_policy: String,
}

impl RequestContextRenderPlan {
    fn validate_definition(&self) -> Result<(), RuntimeJsonError> {
        validate_optional_id("profile_id", &self.profile_id)?;
        require_non_blank("bundle_schema_version", &self.bundle_schema_version)?;
        validate_id_list("included_sections", &self.included_sections, true, true)?;
        validate_id_list(
            "required_provider_capabilities",
            &self.required_provider_capabilities,
            false,
            true,
        )?;
        validate_safe_identifier(&self.artifact_ref_policy, "artifact_ref_policy")?;
        validate_safe_identifier(&self.prompt_rendering_behavior, "prompt_rendering_behavior")?;
        validate_safe_identifier(&self.redaction_policy_id, "redaction_policy_id")?;
        for key in self.max_inline_bytes_by_role.keys() {
            validate_safe_identifier(key, "max_inline_bytes_by_role key")?;
        }
        validate_safe_identifier(
            &self.missing_optional_provider_policy,
            "missing_optional_provider_policy",
        )?;
        Ok(())
    }
}

impl_primitive_contract!(
    RequestContextRenderPlan,
    "request_context_render_plan",
    "request_context_render_plan",
    render_plan_id
);

/// Workflow completion behavior definition.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct WorkflowCompletionBehaviorDefinition {
    #[serde(default = "default_schema_version")]
    pub schema_version: String,
    #[serde(default = "default_completion_behavior_kind")]
    pub kind: String,
    pub behavior_id: String,
    pub plane: Plane,
    #[serde(default = "default_backlog_drained")]
    pub trigger: String,
    pub target_scope: String,
    pub readiness_handler_ids: Vec<String>,
    pub target_entry_key: String,
    pub target_node_id: String,
    pub request_context_profile_id: RequestContextProfileId,
    pub target_selector: String,
    pub backpressure_policy: String,
    #[serde(default = "default_true")]
    pub skip_if_already_closed: bool,
    pub terminal_action_by_outcome: BTreeMap<String, TerminalActionId>,
}

impl WorkflowCompletionBehaviorDefinition {
    fn validate_definition(&self) -> Result<(), RuntimeJsonError> {
        validate_safe_identifier(&self.trigger, "trigger")?;
        validate_safe_identifier(&self.target_scope, "target_scope")?;
        validate_id_list(
            "readiness_handler_ids",
            &self.readiness_handler_ids,
            true,
            true,
        )?;
        validate_safe_identifier(&self.target_entry_key, "target_entry_key")?;
        validate_safe_identifier(&self.target_node_id, "target_node_id")?;
        validate_safe_identifier(
            &self.request_context_profile_id,
            "request_context_profile_id",
        )?;
        validate_safe_identifier(&self.target_selector, "target_selector")?;
        validate_safe_identifier(&self.backpressure_policy, "backpressure_policy")?;
        if self.terminal_action_by_outcome.is_empty() {
            return invalid_field("terminal_action_by_outcome", "must not be empty");
        }
        for (outcome, action) in &self.terminal_action_by_outcome {
            validate_safe_identifier(outcome, "terminal_action_by_outcome outcome")?;
            validate_safe_identifier(action, "terminal_action_by_outcome action")?;
        }
        Ok(())
    }
}

impl_primitive_contract!(
    WorkflowCompletionBehaviorDefinition,
    "workflow_completion_behavior",
    "workflow_completion_behavior",
    behavior_id
);

/// Workflow recovery policy definition.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct WorkflowRecoveryPolicyDefinition {
    #[serde(default = "default_schema_version")]
    pub schema_version: String,
    #[serde(default = "default_recovery_policy_kind")]
    pub kind: String,
    pub policy_id: String,
    pub source_node_ids: Vec<String>,
    pub on_outcomes: Vec<String>,
    pub counter_name: String,
    pub threshold: u64,
    #[serde(default)]
    pub retry_target_node_id: Option<String>,
    #[serde(default)]
    pub exhausted_target_node_id: Option<String>,
    #[serde(default)]
    pub exhausted_terminal_state_id: Option<String>,
    pub failure_class_template: String,
}

impl WorkflowRecoveryPolicyDefinition {
    fn validate_definition(&self) -> Result<(), RuntimeJsonError> {
        validate_id_list("source_node_ids", &self.source_node_ids, true, true)?;
        validate_id_list("on_outcomes", &self.on_outcomes, true, true)?;
        validate_safe_identifier(&self.counter_name, "counter_name")?;
        if self.threshold == 0 {
            return invalid_field("threshold", "must be >= 1");
        }
        validate_optional_id("retry_target_node_id", &self.retry_target_node_id)?;
        validate_optional_id("exhausted_target_node_id", &self.exhausted_target_node_id)?;
        validate_optional_id(
            "exhausted_terminal_state_id",
            &self.exhausted_terminal_state_id,
        )?;
        validate_safe_identifier(&self.failure_class_template, "failure_class_template")?;
        if self.exhausted_target_node_id.is_some() == self.exhausted_terminal_state_id.is_some() {
            return invalid_document("recovery policy must declare exactly one exhausted target");
        }
        Ok(())
    }
}

impl_primitive_contract!(
    WorkflowRecoveryPolicyDefinition,
    "workflow_recovery_policy",
    "workflow_recovery_policy",
    policy_id
);

/// Repair-closure mapping entry for runtime failure route policies.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RuntimeFailurePolicyRepairClosureMappingDefinition {
    pub source_operation_id: RuntimeEffectOperationId,
    pub failure_class: String,
    pub repair_operation_id: RuntimeEffectOperationId,
    pub target_node_id: String,
    pub target_terminal_outcome: String,
    pub required_repair_evidence_artifact_ids: Vec<ArtifactContractId>,
    pub affected_source_family_id: WorkItemFamilyId,
    pub source_lifecycle_behavior_on_repair_success: String,
    pub source_lifecycle_behavior_on_repair_failure: String,
    #[serde(default)]
    pub supports_partial_mutation: bool,
    #[serde(default = "default_true")]
    pub requires_resume_guard: bool,
}

impl RuntimeFailurePolicyRepairClosureMappingDefinition {
    fn validate(&self) -> Result<(), RuntimeJsonError> {
        validate_safe_identifier(&self.source_operation_id, "source_operation_id")?;
        validate_safe_identifier(&self.failure_class, "failure_class")?;
        validate_safe_identifier(&self.repair_operation_id, "repair_operation_id")?;
        validate_safe_identifier(&self.target_node_id, "target_node_id")?;
        validate_safe_identifier(&self.target_terminal_outcome, "target_terminal_outcome")?;
        validate_id_list(
            "required_repair_evidence_artifact_ids",
            &self.required_repair_evidence_artifact_ids,
            true,
            true,
        )?;
        validate_safe_identifier(&self.affected_source_family_id, "affected_source_family_id")?;
        validate_safe_identifier(
            &self.source_lifecycle_behavior_on_repair_success,
            "source_lifecycle_behavior_on_repair_success",
        )?;
        validate_safe_identifier(
            &self.source_lifecycle_behavior_on_repair_failure,
            "source_lifecycle_behavior_on_repair_failure",
        )?;
        Ok(())
    }
}

/// Runtime failure policy definition.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RuntimeFailurePolicyDefinition {
    #[serde(default = "default_schema_version")]
    pub schema_version: String,
    #[serde(default = "default_runtime_failure_policy_kind")]
    pub kind: String,
    pub policy_id: String,
    pub applies_to_origins: Vec<String>,
    pub applies_to_planes: Vec<Plane>,
    #[serde(default)]
    pub applies_to_families: Vec<WorkItemFamilyId>,
    #[serde(default)]
    pub applies_to_failure_classes: Vec<String>,
    #[serde(default)]
    pub applies_to_mutation_phases: Vec<RuntimeEffectMutationPhase>,
    #[serde(default)]
    pub applies_to_operation_ids: Vec<RuntimeEffectOperationId>,
    #[serde(default)]
    pub applies_to_handler_ids: Vec<RuntimeEffectHandlerId>,
    #[serde(default)]
    pub applies_to_source_node_ids: Vec<String>,
    #[serde(default)]
    pub applies_to_source_terminal_state_ids: Vec<String>,
    pub action: String,
    #[serde(default)]
    pub threshold: Option<u64>,
    #[serde(default)]
    pub counter_name: Option<String>,
    pub failure_class_template: String,
    #[serde(default)]
    pub repair_closure_mappings: Vec<RuntimeFailurePolicyRepairClosureMappingDefinition>,
    #[serde(default)]
    pub recovery_node_id: Option<String>,
    #[serde(default)]
    pub target_node_id: Option<String>,
    #[serde(default)]
    pub target_terminal_state_id: Option<String>,
    #[serde(default)]
    pub max_attempts: Option<u64>,
    #[serde(default)]
    pub incident_severity: Option<String>,
}

impl RuntimeFailurePolicyDefinition {
    fn validate_definition(&self) -> Result<(), RuntimeJsonError> {
        validate_id_list("applies_to_origins", &self.applies_to_origins, true, true)?;
        if self.applies_to_planes.is_empty() {
            return invalid_field("applies_to_planes", "must not be empty");
        }
        validate_id_list(
            "applies_to_families",
            &self.applies_to_families,
            false,
            true,
        )?;
        validate_id_list(
            "applies_to_failure_classes",
            &self.applies_to_failure_classes,
            false,
            true,
        )?;
        validate_id_list(
            "applies_to_handler_ids",
            &self.applies_to_handler_ids,
            false,
            true,
        )?;
        validate_id_list(
            "applies_to_operation_ids",
            &self.applies_to_operation_ids,
            false,
            true,
        )?;
        validate_id_list(
            "applies_to_source_node_ids",
            &self.applies_to_source_node_ids,
            false,
            true,
        )?;
        validate_id_list(
            "applies_to_source_terminal_state_ids",
            &self.applies_to_source_terminal_state_ids,
            false,
            true,
        )?;
        validate_safe_identifier(&self.action, "action")?;
        validate_optional_id("counter_name", &self.counter_name)?;
        validate_safe_identifier(&self.failure_class_template, "failure_class_template")?;
        for mapping in &self.repair_closure_mappings {
            mapping.validate()?;
        }
        validate_optional_id("recovery_node_id", &self.recovery_node_id)?;
        validate_optional_id("target_node_id", &self.target_node_id)?;
        validate_optional_id("target_terminal_state_id", &self.target_terminal_state_id)?;
        validate_optional_id("incident_severity", &self.incident_severity)?;
        if self.threshold.is_some() != self.counter_name.is_some() {
            return invalid_document("threshold and counter_name must be declared together");
        }
        if !self.repair_closure_mappings.is_empty() && self.action != "route_to_node" {
            return invalid_document(
                "repair_closure_mappings are only valid for action=route_to_node",
            );
        }
        let mut seen_pairs = std::collections::HashSet::new();
        for mapping in &self.repair_closure_mappings {
            let pair = (
                mapping.source_operation_id.as_str(),
                mapping.failure_class.as_str(),
            );
            if !seen_pairs.insert(pair) {
                return invalid_document(
                    "repair_closure_mappings may not contain duplicate source_operation_id + failure_class pairs",
                );
            }
            if let Some(target_node_id) = &self.target_node_id {
                if mapping.target_node_id != *target_node_id {
                    return invalid_document(
                        "repair_closure_mappings target_node_id must match policy target_node_id",
                    );
                }
            }
        }
        if !self.applies_to_failure_classes.is_empty()
            && self.repair_closure_mappings.iter().any(|mapping| {
                !self
                    .applies_to_failure_classes
                    .contains(&mapping.failure_class)
            })
        {
            return invalid_document(
                "repair_closure_mappings failure_class values must be included in applies_to_failure_classes",
            );
        }
        if !self.applies_to_operation_ids.is_empty()
            && self.repair_closure_mappings.iter().any(|mapping| {
                !self
                    .applies_to_operation_ids
                    .contains(&mapping.source_operation_id)
            })
        {
            return invalid_document(
                "repair_closure_mappings source_operation_id values must be included in applies_to_operation_ids",
            );
        }
        if !self.applies_to_families.is_empty()
            && self.repair_closure_mappings.iter().any(|mapping| {
                !self
                    .applies_to_families
                    .contains(&mapping.affected_source_family_id)
            })
        {
            return invalid_document(
                "repair_closure_mappings affected_source_family_id values must be included in applies_to_families",
            );
        }
        Ok(())
    }
}

impl_primitive_contract!(
    RuntimeFailurePolicyDefinition,
    "runtime_failure_policy",
    "runtime_failure_policy",
    policy_id
);

/// Scheduler policy for workflow planes and lanes.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct WorkflowPlaneSchedulerPolicyDefinition {
    #[serde(default = "default_schema_version")]
    pub schema_version: String,
    #[serde(default = "default_scheduler_policy_kind")]
    pub kind: String,
    pub policy_id: String,
    pub plane_order: Vec<Plane>,
    #[serde(default)]
    pub concurrency_policy_id: Option<String>,
    pub lanes: Vec<WorkflowLaneDefinition>,
    pub claim_policies_by_plane: HashMap<Plane, PlaneQueueClaimPolicyDefinition>,
    #[serde(default)]
    pub completion_check_order: Vec<Plane>,
    #[serde(default)]
    pub experimental_multi_lane: bool,
    #[serde(default)]
    pub lane_conflict_policies: Vec<LaneConflictPolicyDefinition>,
}

impl WorkflowPlaneSchedulerPolicyDefinition {
    fn validate_definition(&self) -> Result<(), RuntimeJsonError> {
        if self.plane_order.is_empty() {
            return invalid_field("plane_order", "must not be empty");
        }
        validate_optional_id("concurrency_policy_id", &self.concurrency_policy_id)?;
        if self.lanes.is_empty() {
            return invalid_field("lanes", "must not be empty");
        }
        for lane in &self.lanes {
            let mut lane = lane.clone();
            lane.validate_contract()?;
        }
        for (plane, policy) in &self.claim_policies_by_plane {
            if policy.plane != *plane {
                return invalid_document("claim_policies_by_plane keys must match policy plane");
            }
        }
        for policy in &self.lane_conflict_policies {
            let mut policy = policy.clone();
            policy.validate_contract()?;
        }
        Ok(())
    }
}

impl_primitive_contract!(
    WorkflowPlaneSchedulerPolicyDefinition,
    "workflow_plane_scheduler_policy",
    "workflow_plane_scheduler_policy",
    policy_id
);

/// Operator control capability definition.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct OperatorControlCapabilityDefinition {
    #[serde(default = "default_schema_version")]
    pub schema_version: String,
    #[serde(default = "default_operator_control_capability_kind")]
    pub kind: String,
    pub capability_id: String,
    pub action: String,
    pub target_type: String,
    #[serde(default)]
    pub plane: Option<Plane>,
    #[serde(default)]
    pub family_ids: Vec<WorkItemFamilyId>,
    #[serde(default)]
    pub lane_ids: Vec<String>,
    #[serde(default)]
    pub allowed_lifecycle_states: Vec<String>,
}

impl OperatorControlCapabilityDefinition {
    fn validate_definition(&self) -> Result<(), RuntimeJsonError> {
        validate_safe_identifier(&self.action, "action")?;
        validate_safe_identifier(&self.target_type, "target_type")?;
        validate_id_list("family_ids", &self.family_ids, false, true)?;
        validate_id_list("lane_ids", &self.lane_ids, false, true)?;
        validate_id_list(
            "allowed_lifecycle_states",
            &self.allowed_lifecycle_states,
            false,
            true,
        )?;
        Ok(())
    }
}

impl_primitive_contract!(
    OperatorControlCapabilityDefinition,
    "operator_control_capability",
    "operator_control_capability",
    capability_id
);

/// Workspace schema epoch definition.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct WorkspaceSchemaEpochDefinition {
    #[serde(default = "default_schema_version")]
    pub schema_version: String,
    #[serde(default = "default_workspace_schema_epoch_kind")]
    pub kind: String,
    pub epoch_id: String,
    pub minimum_supported_epoch_id: String,
    #[serde(default)]
    pub archive_required_from_epoch_ids: Vec<String>,
    pub reset_command: String,
    #[serde(default)]
    pub compatibility_notes: Vec<String>,
}

impl WorkspaceSchemaEpochDefinition {
    fn validate_definition(&self) -> Result<(), RuntimeJsonError> {
        validate_safe_identifier(
            &self.minimum_supported_epoch_id,
            "minimum_supported_epoch_id",
        )?;
        validate_id_list(
            "archive_required_from_epoch_ids",
            &self.archive_required_from_epoch_ids,
            false,
            true,
        )?;
        require_non_blank("reset_command", &self.reset_command)?;
        validate_non_empty_entries("compatibility_notes", &self.compatibility_notes)
    }
}

impl_primitive_contract!(
    WorkspaceSchemaEpochDefinition,
    "workspace_schema_epoch",
    "workspace_schema_epoch",
    epoch_id
);

/// Discovered built-in workflow primitive bundle.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct WorkflowPrimitiveBundle {
    pub artifact_contracts: Vec<ArtifactContractDefinition>,
    pub request_context_profiles: Vec<RequestContextProfileDefinition>,
    pub request_context_providers: Vec<RequestContextProviderDefinition>,
    pub request_context_render_plans: Vec<RequestContextRenderPlan>,
    pub work_item_families: Vec<WorkItemFamilyDefinition>,
    pub document_adapters: Vec<WorkItemDocumentAdapterDefinition>,
    pub queue_claim_policies: Vec<PlaneQueueClaimPolicyDefinition>,
    pub terminal_actions: Vec<TerminalActionDefinition>,
    pub lifecycle_mutation_plans: Vec<LifecycleMutationPlanDefinition>,
    pub runtime_effect_handlers: Vec<RuntimeEffectHandlerDefinition>,
    pub runtime_effect_runners: Vec<RuntimeEffectOperationRunnerDefinition>,
    pub runtime_effect_rules: Vec<RuntimeEffectRuleDefinition>,
    pub effect_stores: Vec<RuntimeEffectStoreDefinition>,
    pub effect_validators: Vec<RuntimeEffectValidatorDefinition>,
    pub runtime_effect_operations: Vec<RuntimeEffectOperationDefinition>,
    pub recovery_policies: Vec<WorkflowRecoveryPolicyDefinition>,
    pub runtime_failure_policies: Vec<RuntimeFailurePolicyDefinition>,
    pub workspace_schema_epoch: Option<WorkspaceSchemaEpochDefinition>,
}

fn default_schema_version() -> String {
    SCHEMA_VERSION.to_owned()
}

fn default_artifact_contract_kind() -> String {
    "artifact_contract".to_owned()
}
fn default_work_item_family_kind() -> String {
    "work_item_family".to_owned()
}
fn default_document_adapter_kind() -> String {
    "work_item_document_adapter".to_owned()
}
fn default_partition_selector_kind() -> String {
    "work_item_partition_selector".to_owned()
}
fn default_queue_claim_policy_kind() -> String {
    "plane_queue_claim_policy".to_owned()
}
fn default_workflow_lane_kind() -> String {
    "workflow_lane".to_owned()
}
fn default_lane_conflict_policy_kind() -> String {
    "lane_conflict_policy".to_owned()
}
fn default_terminal_action_kind() -> String {
    "terminal_action".to_owned()
}
fn default_lifecycle_mutation_plan_kind() -> String {
    "lifecycle_mutation_plan".to_owned()
}
fn default_runtime_effect_handler_kind() -> String {
    "runtime_effect_handler".to_owned()
}
fn default_runtime_effect_runner_kind() -> String {
    "runtime_effect_runner".to_owned()
}
fn default_runtime_effect_store_kind() -> String {
    "runtime_effect_store".to_owned()
}
fn default_runtime_effect_validator_kind() -> String {
    "runtime_effect_validator".to_owned()
}
fn default_runtime_effect_operation_kind() -> String {
    "runtime_effect_operation".to_owned()
}
fn default_runtime_effect_rule_kind() -> String {
    "runtime_effect_rule".to_owned()
}
fn default_outcome_artifact_kind() -> String {
    "outcome_artifact".to_owned()
}
fn default_request_context_profile_kind() -> String {
    "request_context_profile".to_owned()
}
fn default_request_context_provider_kind() -> String {
    "request_context_provider".to_owned()
}
fn default_request_context_render_plan_kind() -> String {
    "request_context_render_plan".to_owned()
}
fn default_completion_behavior_kind() -> String {
    "workflow_completion_behavior".to_owned()
}
fn default_recovery_policy_kind() -> String {
    "workflow_recovery_policy".to_owned()
}
fn default_runtime_failure_policy_kind() -> String {
    "runtime_failure_policy".to_owned()
}
fn default_scheduler_policy_kind() -> String {
    "workflow_plane_scheduler_policy".to_owned()
}
fn default_operator_control_capability_kind() -> String {
    "operator_control_capability".to_owned()
}
fn default_workspace_schema_epoch_kind() -> String {
    "workspace_schema_epoch".to_owned()
}

fn default_json_extension() -> String {
    ".json".to_owned()
}
fn default_queued() -> String {
    "queued".to_owned()
}
fn default_active() -> String {
    "active".to_owned()
}
fn default_done() -> String {
    "done".to_owned()
}
fn default_blocked() -> String {
    "blocked".to_owned()
}
fn default_created_at_field() -> String {
    "created_at".to_owned()
}
fn default_plane_scope() -> String {
    "plane".to_owned()
}
fn default_fail_policy() -> String {
    "fail".to_owned()
}
fn default_block_source_policy() -> String {
    "block_source".to_owned()
}
fn default_created_at_asc() -> String {
    "created_at_asc".to_owned()
}
fn default_defer_unrelated() -> String {
    "defer_unrelated".to_owned()
}
fn default_idle() -> String {
    "idle".to_owned()
}
const fn default_one() -> u64 {
    1
}
fn default_single_writer_serialized() -> String {
    "single_writer_serialized".to_owned()
}
fn default_after_result_application() -> String {
    "after_result_application".to_owned()
}
fn default_reject_compile() -> String {
    "reject_compile".to_owned()
}
fn default_backlog_drained() -> String {
    "backlog_drained".to_owned()
}
fn default_runtime_effect_store_owner() -> String {
    "runtime".to_owned()
}
fn default_runtime_effect_store_write_policy() -> String {
    "single_write".to_owned()
}
const fn default_runtime_effect_mutation_phase() -> RuntimeEffectMutationPhase {
    RuntimeEffectMutationPhase::PreMutation
}
fn default_runtime_effect_mutation_journal_schema_id() -> String {
    "runtime_effect_mutation_journal_v1".to_owned()
}
const fn default_true() -> bool {
    true
}

fn validate_literal(
    field_name: &'static str,
    actual: &str,
    expected: &'static str,
) -> Result<(), RuntimeJsonError> {
    if actual == expected {
        Ok(())
    } else {
        Err(RuntimeJsonError::InvalidLiteral {
            field_name,
            expected,
            actual: actual.to_owned(),
        })
    }
}

fn validate_optional_id(
    field_name: &'static str,
    value: &Option<String>,
) -> Result<(), RuntimeJsonError> {
    if let Some(value) = value {
        validate_safe_identifier(value, field_name)?;
    }
    Ok(())
}

fn validate_optional_non_blank(
    field_name: &'static str,
    value: &Option<String>,
) -> Result<(), RuntimeJsonError> {
    if let Some(value) = value {
        require_non_blank(field_name, value)?;
    }
    Ok(())
}

fn validate_id_list(
    field_name: &'static str,
    values: &[String],
    require_non_empty: bool,
    check_safe_id: bool,
) -> Result<(), RuntimeJsonError> {
    if require_non_empty && values.is_empty() {
        return invalid_field(field_name, "must not be empty");
    }
    let mut seen = std::collections::HashSet::new();
    for value in values {
        if check_safe_id {
            validate_safe_identifier(value, field_name)?;
        } else {
            require_non_blank(field_name, value)?;
        }
        if !seen.insert(value) {
            return invalid_field(field_name, "must not contain duplicates");
        }
    }
    Ok(())
}

fn require_non_blank(field_name: &'static str, value: &str) -> Result<(), RuntimeJsonError> {
    if value.trim().is_empty() {
        invalid_field(field_name, "must not be empty")
    } else {
        Ok(())
    }
}

fn validate_non_empty_entries(
    field_name: &'static str,
    values: &[String],
) -> Result<(), RuntimeJsonError> {
    for value in values {
        require_non_blank(field_name, value)?;
    }
    Ok(())
}

fn validate_filename(field_name: &'static str, value: &str) -> Result<(), RuntimeJsonError> {
    require_non_blank(field_name, value)?;
    if value.contains('/') || value.contains('\\') || value == "." || value == ".." {
        return invalid_field(field_name, "must be a filename, not a path");
    }
    Ok(())
}

fn validate_file_extension(field_name: &'static str, value: &str) -> Result<(), RuntimeJsonError> {
    if value.starts_with('.') && value.len() > 1 && !value.contains('/') && !value.contains('\\') {
        Ok(())
    } else {
        invalid_field(field_name, "must be a file extension such as `.md`")
    }
}

fn validate_runtime_relative_path(
    field_name: &'static str,
    value: &str,
) -> Result<(), RuntimeJsonError> {
    require_non_blank(field_name, value)?;
    if value.starts_with('/') || value.split('/').any(|part| part == "..") {
        return invalid_field(field_name, "must be a runtime-relative path");
    }
    Ok(())
}

fn validate_optional_runtime_path(
    field_name: &'static str,
    value: &Option<String>,
) -> Result<(), RuntimeJsonError> {
    if let Some(value) = value {
        validate_runtime_relative_path(field_name, value)?;
    }
    Ok(())
}

fn invalid_field<T>(field_name: &'static str, message: &str) -> Result<T, RuntimeJsonError> {
    Err(RuntimeJsonError::InvalidField {
        field_name,
        message: message.to_owned(),
    })
}

fn invalid_document<T>(message: &str) -> Result<T, RuntimeJsonError> {
    Err(RuntimeJsonError::InvalidDocument {
        message: message.to_owned(),
    })
}
