1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
//! Replanning — a run changing its mind, on the record.
//!
//! # Versioned, never mutative
//!
//! A replan produces `PlanIR v2` carrying `derived_from: v1`, rather than
//! editing the plan in place. What the run *intended* before it changed its mind
//! is usually the interesting part of an incident, and it is structurally absent
//! from any system that edits a plan. Both versions stay in the journal.
//!
//! # Why replanning is refused once untrusted data is in play
//!
//! The frozen plan is an authorization graph: it is compiled from trusted input
//! only, and every argument is bound to a declared provenance. That property is
//! what makes it safe to check the journal against the plan afterwards.
//!
//! A replan *changes the graph*. If untrusted data has already reached working
//! memory, then anything influencing the new plan may be attacker-shaped — and
//! the attacker gets to choose the authorization graph, which is the whole game.
//! So the `plan-then-execute` rule is enforced structurally: once any
//! `Untrusted` value is in the run's outputs, `Outcome::Replan` is refused.
//!
//! This is the one place where refusing looks unhelpful and is not. A run that
//! wants a different plan after reading untrusted input is describing exactly
//! the attack.
use Debug;
use async_trait;
use crate;
/// Produces a new plan version for a run that asked for one.
///
/// Deliberately a seam rather than a built-in: where a new plan comes from —
/// a router, a rules table, a model call — is a deployment decision, and the
/// runtime's job is to make whichever choice auditable rather than to make it.
/// Why a new plan could not be produced.