Skip to main content

pointlock_ir/
flow.rs

1//! The flow root type and its contract declarations (spine §3, 02 §2–§3).
2
3use std::collections::BTreeMap;
4
5use schemars::{JsonSchema, Schema, SchemaGenerator, json_schema};
6use serde::{Deserialize, Serialize};
7
8use crate::expr::Expr;
9use crate::handler::HandlerBinding;
10use crate::primitives::{
11    FeatureId, FlowId, Hash, Identifier, IrVersion, JsonSchemaDocument, ProviderName,
12};
13use crate::source_map::SourceMapEntry;
14use crate::step::StepIR;
15use crate::vocab::VerdictPolicy;
16
17/// Pointlock Typed IR v0.1 — the sole input accepted by `pointlock-runner` and
18/// the sole output of the `pointlock-compiler` seal phase.
19///
20/// Closed vocabulary per spine Appendix A. All objects are closed except the
21/// three documented exemption classes (02 §2.2): embedded JSON Schema
22/// documents, identifier-keyed maps, and `StepBase` (composed into variants).
23#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
24#[serde(rename_all = "camelCase", deny_unknown_fields)]
25pub struct FlowIR {
26    /// IR semantic-generation number, const `1` in v0.1.
27    pub ir_version: IrVersion,
28    /// The flow's name-identity.
29    pub flow_id: FlowId,
30    /// Canonical whole-tree hash (excluding `irHash` itself and `sourceMap`;
31    /// covers callee irHashes via `subflows` — the link-closure property,
32    /// 02 §12.2).
33    pub ir_hash: Hash,
34    /// The provider this flow was compiled against.
35    pub provider: ProviderRef,
36    /// Union of features required by the whole flow; fed into
37    /// `FeatureOffer.required` at session open (free enforcement).
38    /// Set semantics — serialized in lexicographic order.
39    pub required_features: std::collections::BTreeSet<FeatureId>,
40    /// Digest of the `CapabilityLockfile` used at bind time; attestation
41    /// mismatch at runtime is `capability_drift`, refuse to run.
42    pub lockfile_digest: Hash,
43    /// Input contract.
44    pub params: Vec<ParamDecl>,
45    /// Output contract.
46    pub outputs: Vec<OutputDecl>,
47    /// The step body (≥ 1 step).
48    #[schemars(length(min = 1))]
49    pub body: Vec<StepIR>,
50    /// Flow-level handler hooks.
51    #[serde(skip_serializing_if = "Option::is_none")]
52    #[schemars(length(min = 1))]
53    pub handlers: Option<Vec<HandlerBinding>>,
54    /// Verdict folding policy (`strict` folds degraded pass to unknown).
55    pub verdict_policy: VerdictPolicy,
56    /// IR path → YAML span mapping, plus macro origin traces. Pure
57    /// diagnostics: excluded from `irHash` (02 §12.2).
58    pub source_map: Vec<SourceMapEntry>,
59    /// Subflow registry: reference, not inline — callees are independent
60    /// artifacts pinned by `irHash` (02 §6).
61    #[schemars(schema_with = "subflows_schema")]
62    pub subflows: BTreeMap<FlowId, FlowRef>,
63}
64
65fn subflows_schema(generator: &mut SchemaGenerator) -> Schema {
66    json_schema!({
67        "type": "object",
68        "propertyNames": { "pattern": "^[A-Za-z_][A-Za-z0-9_.-]*$" },
69        "additionalProperties": generator.subschema_for::<FlowRef>()
70    })
71}
72
73/// The provider a flow is bound to (inline object in the baseline).
74#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
75#[serde(rename_all = "camelCase", deny_unknown_fields)]
76#[schemars(inline)]
77pub struct ProviderRef {
78    /// Const `"devicerail"` — the only provider of v0.1.
79    pub name: ProviderName,
80    /// Provider package version the manifest came from.
81    #[schemars(length(min = 1))]
82    pub version: String,
83}
84
85/// One declared flow parameter.
86#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
87#[serde(rename_all = "camelCase", deny_unknown_fields)]
88pub struct ParamDecl {
89    /// Parameter name.
90    pub name: Identifier,
91    /// JSON Schema contract of the value.
92    pub schema: JsonSchemaDocument,
93    /// Whether the run must supply this parameter.
94    pub required: bool,
95    /// Default value (any JSON). Note: an explicit JSON `null` default does
96    /// not survive a serde round-trip (absence-by-omission rule, 02 §2.4);
97    /// the compiler never emits one.
98    #[serde(skip_serializing_if = "Option::is_none")]
99    pub default: Option<serde_json::Value>,
100}
101
102/// One declared flow output.
103#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
104#[serde(rename_all = "camelCase", deny_unknown_fields)]
105pub struct OutputDecl {
106    /// Output name.
107    pub name: Identifier,
108    /// JSON Schema contract of the value.
109    pub schema: JsonSchemaDocument,
110    /// Projection expression producing the value.
111    pub from: Expr,
112}
113
114/// Content-pinned reference to a compiled flow artifact.
115#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
116#[serde(rename_all = "camelCase", deny_unknown_fields)]
117pub struct FlowRef {
118    /// The callee's flow id.
119    pub flow_id: FlowId,
120    /// The callee's content hash (integrity pin; runner verifies on load).
121    pub ir_hash: Hash,
122}