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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
//! PMAT-161 (PMAT-175): how a stamp records the config it was applied from,
//! and how that record is compared with the config in hand.
//!
//! Split out of the parent for the 500-line ceiling, and the split is also the
//! honest boundary: everything here is about PATHS, and the parent is about
//! stacks. The one rule the whole file exists to state is that the two sides of
//! the comparison are normalised THE SAME WAY — a recorded path is whatever
//! [`stamped_config_file`] wrote, so the question "is this the same file?" is
//! answered by writing it again and comparing.
//!
//! PMAT-183: ONE comparison, and it is that one. PMAT-175 tightened the reader
//! that deletes on a match and left a looser sibling for the reader that only
//! warns ([`super::stack_conflict`], which was called without a state dir) —
//! documented as an accepted limit. It was not one: `../forjar.yaml` still
//! matched every `machines/<m>/forjar.yaml`, so the wrong-stack guard was
//! silent on the same name applied from a different file, which is the case it
//! exists for. Every reader supplies the state dir now, so the tail comparison
//! is gone rather than narrowed.
use StackStamp;
use ;
/// Canonicalise a path, falling back to the path as given.
///
/// The config may have moved since the apply that recorded it; comparing two
/// uncanonicalised paths still beats claiming they differ.
/// The path relative to `base`, or `None` when the two share no prefix.
/// How a config path is stored in a stamp: relative to the state dir when both
/// sit in one tree, else the absolute canonical path.
/// Does a recorded stamp path name the config now being applied?
///
/// PMAT-175: EXACT, in the one frame both sides are written in. The recorded
/// string is whatever [`stamped_config_file`] produced for this state dir, so
/// the question "is this the same file?" is answered by producing it again for
/// the config in hand and comparing the two strings. Nothing is skipped and
/// nothing is matched by suffix.
///
/// It was a suffix match — the `..` components were dropped and the current
/// path was asked whether it ENDS WITH the rest — and that is not a weaker
/// version of the same test, it is a different one. `../forjar.yaml` matched
/// EVERY `machines/<m>/forjar.yaml` in the paiml/infra layout, so the first
/// machine manifest applied into the root stack's dir was read as that stack
/// renamed and [`rename::retire_renamed`] DELETED the root stack's stamp,
/// machines and output keys included (executed reproducer, review lane).
///
/// PMAT-183: and there is no second comparison left to fall back to.
/// `state_dir` used to be an option, `None` meaning "the caller does not know
/// which dir the lock came from" — which was true of exactly one reader,
/// [`super::stack_conflict`], and answered with a match by the tail of the
/// path. So the guard whose job is "one of `-f`/`--state-dir` points at the
/// wrong stack" kept the very defect the rename path had been cured of, and
/// kept it SILENTLY, because a loose match there exempts a warning rather than
/// deleting anything. Both of that guard's callers hold the dir already, so it
/// is a `&Path` here and a future reader cannot ask the question without
/// saying which dir it is about.
///
/// A stamp with NO recorded file — the 1.0 migration's one-apply window — is
/// answered before this is reached: [`records_config_file`] and
/// [`super::stack_written_from_other_file`] both return early on `file: None`,
/// so "origin unknown" is still not evidence of anything.
pub
/// Does this stamp record the config file now being applied?
///
/// A stamp with NO recorded file never matches: "origin unknown" is not
/// evidence of anything, and the one caller that acts destructively on a match
/// ([`rename::retire_renamed`]) would otherwise retire a 1.0 migration's stamp.
pub