Skip to main content

runx_contracts/
output.rs

1//! Skill output declaration types: the value shape of the `runx.ai/spec`
2//! output map (a field is either a bare type name or a typed field spec).
3//!
4//! The standalone `output.schema.json` document is a top-level open map carrying
5//! a bare `$id`; it is modeled here as the transparent map newtype [`Output`],
6//! whose `RunxSchema` derive emits the committed `patternProperties` shape. The
7//! same `BTreeMap<String, OutputField>` is embedded by the agent-context
8//! envelope's `output` field.
9use std::collections::BTreeMap;
10
11use serde::{Deserialize, Serialize};
12
13use crate::schema::{NonEmptyString, RunxSchema};
14
15/// The diagnostic/base fields a step projection always injects into its `outputs`
16/// map for receipts, effect replay, and debugging. They are NOT part of a step's
17/// addressable contract: a graph context edge may bind only to declared outputs and
18/// artifact packets, never to these. This is the single source of truth shared by the
19/// runtime projection/resolver and the parser's parse-time context-edge validation, so
20/// the addressable surface cannot drift between the two layers.
21pub const BASE_OUTPUT_FIELDS: &[&str] = &["raw", "skill_claim", "stdout", "stderr", "status"];
22
23/// A declared output value type.
24#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
25#[serde(rename_all = "lowercase")]
26pub enum OutputType {
27    String,
28    Number,
29    Integer,
30    Boolean,
31    Array,
32    Object,
33    Null,
34}
35
36/// The expanded form of an output field declaration. Committed with
37/// `additionalProperties: false` and `minProperties: 1` (the latter is a
38/// numeric bound the emitter does not express).
39#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
40#[serde(deny_unknown_fields)]
41pub struct OutputFieldSpec {
42    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
43    pub field_type: Option<OutputType>,
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub description: Option<String>,
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub required: Option<bool>,
48    #[serde(skip_serializing_if = "Option::is_none")]
49    pub wrap_as: Option<NonEmptyString>,
50    #[serde(rename = "enum", skip_serializing_if = "Option::is_none")]
51    pub enum_values: Option<Vec<String>>,
52}
53
54/// A single output field declaration: either a bare type name or a typed spec.
55#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
56#[serde(untagged)]
57pub enum OutputField {
58    Type(OutputType),
59    Spec(OutputFieldSpec),
60}
61
62/// The standalone `output.schema.json` document: a top-level open map of field
63/// name to [`OutputField`], carrying the bare `runx.ai/spec` `$id`.
64#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
65#[serde(transparent)]
66#[runx_schema(spec_id = "https://runx.ai/spec/output.schema.json")]
67pub struct Output(pub BTreeMap<String, OutputField>);