use runx_contracts::{JsonObject, JsonValue};
use crate::ValidationError;
use super::{
FIELDS, SkillGovernance, SkillRunnerDefinition, field_value, first_value, nested_value,
validate_allowed_tools, validate_artifact_contract, validate_execution_semantics,
validate_idempotency, validate_inputs, validate_mutating, validate_retry, validate_source,
validate_source_fields,
};
const RUNNER_FIELDS: &[&str] = &[
"act",
"agent",
"agent_card_url",
"agent_identity",
"allowed_tools",
"args",
"arguments",
"artifacts",
"auth",
"catalog_ref",
"command",
"context",
"context_skills",
"cwd",
"default",
"execution",
"external_adapter",
"external_adapter_manifest",
"external_adapter_manifest_path",
"graph",
"headers",
"hook",
"http",
"idempotency",
"input_mode",
"inputs",
"instructions",
"invocation_id",
"method",
"mutating",
"outputs",
"policy",
"retry",
"risk",
"run_id",
"runx",
"runtime",
"sandbox",
"server",
"skill_ref",
"scopes",
"source",
"task",
"timeout_seconds",
"tool",
"type",
"url",
"allow_private_network",
];
pub(crate) fn validate_runner_definition(
name: &str,
runner: JsonObject,
) -> Result<SkillRunnerDefinition, ValidationError> {
FIELDS.reject_unknown_fields(&runner, &format!("runners.{name}"), RUNNER_FIELDS)?;
let runx = FIELDS.optional_object(runner.get("runx"), &format!("runners.{name}.runx"))?;
crate::runner::resolve_post_run_reflect_policy(runx.as_ref(), &format!("runners.{name}.runx"))?;
let source_record =
match FIELDS.optional_object(runner.get("source"), &format!("runners.{name}.source"))? {
Some(source) => {
validate_source_fields(&source, &format!("runners.{name}.source"))?;
source
}
None => runner.clone(),
};
let risk = runner.get("risk").cloned();
let governance = validate_runner_governance(name, &runner, runx.as_ref(), risk.as_ref())?;
Ok(SkillRunnerDefinition {
name: name.to_owned(),
default: FIELDS
.optional_bool(runner.get("default"), &format!("runners.{name}.default"))?
.unwrap_or(false),
source: validate_source(&source_record, runx.as_ref())?,
inputs: validate_inputs(
FIELDS
.optional_object(runner.get("inputs"), &format!("runners.{name}.inputs"))?
.unwrap_or_default(),
)?,
auth: runner.get("auth").cloned(),
risk: risk.clone(),
runtime: runner.get("runtime").cloned(),
retry: governance.retry,
idempotency: governance.idempotency,
mutating: governance.mutating,
artifacts: governance.artifacts,
allowed_tools: governance.allowed_tools,
execution: governance.execution,
runx,
raw: runner,
})
}
fn validate_runner_governance(
name: &str,
runner: &JsonObject,
runx: Option<&JsonObject>,
risk: Option<&JsonValue>,
) -> Result<SkillGovernance, ValidationError> {
Ok(SkillGovernance {
retry: validate_retry(
first_value(runner.get("retry"), field_value(runx, "retry")),
&format!("runners.{name}.retry"),
)?,
idempotency: validate_idempotency(
first_value(runner.get("idempotency"), field_value(runx, "idempotency")),
&format!("runners.{name}.idempotency"),
)?,
mutating: validate_mutating(
first_value(
first_value(runner.get("mutating"), nested_value(risk, "mutating")),
field_value(runx, "mutating"),
),
&format!("runners.{name}.mutating"),
)?,
artifacts: validate_artifact_contract(
first_value(runner.get("artifacts"), field_value(runx, "artifacts")),
&format!("runners.{name}.artifacts"),
)?,
allowed_tools: validate_allowed_tools(
field_value(runx, "allowed_tools"),
&format!("runners.{name}.runx.allowed_tools"),
)?,
execution: validate_execution_semantics(
first_value(runner.get("execution"), field_value(runx, "execution")),
&format!("runners.{name}.execution"),
)?,
})
}