Skip to main content

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}