spec_driven_docs/plan.rs
1//! One typed document that every landing write comes from.
2//!
3//! Three verbs used to walk one projection on three paths, which is three
4//! places for a rule to drift. A plan is one place: the engine computes it,
5//! an operator reads and approves it, and an apply executes exactly its
6//! operations or refuses because its inputs moved.
7//!
8//! The document keeps five kinds apart, and an addition inside one kind is
9//! additive rather than a new shape:
10//!
11//! - Evidence is what was observed.
12//! - Analysis is what the engine derived: findings, operations.
13//! - Policy is the requirement each precondition carries.
14//! - Decisions are workflow state the operator owns.
15//! - Postconditions are what proves completion.
16//!
17//! The planner is pure. Observation, resolution, and the clock are inputs,
18//! so the same inputs produce the same plan and the same fingerprint.
19
20pub mod apply;
21pub mod classify;
22pub mod compatibility;
23pub mod decision;
24pub mod derive;
25pub mod evidence;
26pub mod finding;
27pub mod fingerprint;
28pub mod guidance;
29pub mod observe;
30pub mod operation;
31pub mod planner;
32pub mod readiness;
33pub mod session;
34pub mod store;
35
36use serde::{Deserialize, Serialize};
37
38use crate::domain::ownership::Sha256;
39use crate::domain::profile::ProfileId;
40use crate::plan::classify::Classification;
41use crate::plan::decision::Decision;
42use crate::plan::evidence::Evidence;
43use crate::plan::finding::{Finding, StyleCandidate};
44use crate::plan::observe::{Corpus, Host, Installation, Repository};
45use crate::plan::operation::Operation;
46use crate::plan::readiness::{Precondition, Readiness};
47
48/// The machine schema a plan declares.
49///
50/// Independent of the status, record, payload, and manifest schemas. They
51/// version different things on different dates.
52pub const PLAN_SCHEMA: &str = "sdd.plan/1";
53
54/// Which plan this is, and what produced it.
55#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
56pub struct Identity {
57 /// The machine schema of this object.
58 pub schema: String,
59 /// The plan's own identifier, which is its fingerprint.
60 pub plan_id: String,
61 /// When it was computed, as the caller's clock reported it.
62 pub created_at: String,
63 /// The engine that computed it.
64 pub engine_version: String,
65}
66
67/// What the plan is aiming at.
68#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
69pub struct DesiredState {
70 /// What the caller asked for, before resolution.
71 pub selector: String,
72 /// The exact release the selector resolved to.
73 pub release: String,
74 /// The digest over that release's content.
75 pub release_sha256: Sha256,
76 /// The profile the target takes.
77 pub profile: Option<ProfileId>,
78 /// Paths no delivered gate judges, as the caller reserved them.
79 ///
80 /// Carried so an apply recomputes the same landing. The bytes they
81 /// change are already in the operations, so the fingerprint covers
82 /// them there rather than twice.
83 #[serde(default)]
84 pub reserved: Vec<String>,
85 /// What that release declares it lands, as counts a reader can check.
86 pub declared: Declared,
87}
88
89/// How much one release's declaration covers.
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
91pub struct Declared {
92 /// The protocol version between the engine and the bundle.
93 pub payload_schema: u32,
94 /// How many byte projections the canon keeps owning.
95 pub managed: usize,
96 /// How many seeds the instance owns from the moment they land.
97 pub adopted: usize,
98 /// How many sentinels the release authorizes.
99 pub sentinels: usize,
100}
101
102/// What the plan found.
103#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
104pub struct ObservedState {
105 /// The repository itself.
106 pub repository: Repository,
107 /// What is installed there, where anything is.
108 pub installation: Option<Installation>,
109 /// The host this command ran on.
110 pub host: Host,
111 /// What the corpus looks like.
112 pub corpus: Corpus,
113 /// What each of the above rests on.
114 pub evidence_refs: Vec<String>,
115}
116
117/// Where the release came from, and whether it was verified.
118#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
119pub struct ReleaseSource {
120 /// The release's version.
121 pub version: String,
122 /// Where its facts came from.
123 pub provenance: String,
124 /// The registry checksum, where a registry served it.
125 #[serde(default, skip_serializing_if = "Option::is_none")]
126 pub registry_checksum: Option<Sha256>,
127 /// Whether the registry marks it yanked.
128 pub yanked: bool,
129 /// The lowest engine this release declares it needs.
130 #[serde(default, skip_serializing_if = "Option::is_none")]
131 pub minimum_engine: Option<String>,
132 /// How much of the interval the guidance ledger covers.
133 #[serde(default, skip_serializing_if = "Option::is_none")]
134 pub guidance_coverage: Option<crate::plan::guidance::Coverage>,
135 /// Every guidance step that reaches this target.
136 #[serde(default, skip_serializing_if = "Vec::is_empty")]
137 pub guidance_steps: Vec<String>,
138 /// How many steps this target's destinations excluded.
139 #[serde(default)]
140 pub guidance_excluded: usize,
141 /// What each of the above rests on.
142 pub evidence_refs: Vec<String>,
143}
144
145/// One typed check the apply runs at the end and reports.
146#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
147pub struct Postcondition {
148 /// A stable identifier.
149 pub id: String,
150 /// What must be true once the apply has finished.
151 pub statement: String,
152}
153
154/// The whole document.
155#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
156pub struct Plan {
157 /// Which plan this is.
158 pub identity: Identity,
159 /// What the target is.
160 pub classification: Classification,
161 /// What the corpus shows.
162 pub findings: Vec<Finding>,
163 /// Documents written before the instance, named and never judged.
164 pub style_candidates: Vec<StyleCandidate>,
165 /// What the plan is aiming at.
166 pub desired_state: DesiredState,
167 /// What the plan found.
168 pub observed_state: ObservedState,
169 /// Where the release came from.
170 pub release: ReleaseSource,
171 /// Every write the apply will make, in apply order.
172 pub operations: Vec<Operation>,
173 /// Everything that must hold first.
174 pub preconditions: Vec<Precondition>,
175 /// Everything the operator decides.
176 pub decisions: Vec<Decision>,
177 /// Everything the apply proves at the end.
178 pub postconditions: Vec<Postcondition>,
179 /// What was observed, and how.
180 pub evidence: Vec<Evidence>,
181 /// Whether the plan may be applied.
182 pub readiness: Readiness,
183 /// The digest over the semantic inputs an approval binds to.
184 pub input_fingerprint: Sha256,
185}
186
187impl Plan {
188 /// Whether an apply may proceed.
189 #[must_use]
190 pub const fn is_ready(&self) -> bool {
191 matches!(self.readiness, Readiness::Ready)
192 }
193
194 /// Every finding of one kind.
195 #[must_use]
196 pub fn findings_of(&self, kind: finding::FindingKind) -> Vec<&Finding> {
197 self.findings
198 .iter()
199 .filter(|found| found.kind == kind)
200 .collect()
201 }
202
203 /// How many structural findings the corpus shows.
204 #[must_use]
205 pub fn structural_count(&self) -> usize {
206 self.findings_of(finding::FindingKind::Structural).len()
207 }
208}