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}