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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
//! forjar#360: which fields of an observation `lifecycle.ignore_drift` suppresses.
//!
//! # Why a mask over the stdout and not a per-field lock schema
//!
//! The issue asks for `ResourceLock.observed` to become a map of field to
//! observed value — a lock schema change, a migration, and a change to every
//! per-resource state-query generator. It is not needed. The generators ALREADY
//! emit field-shaped output (`file`: `owner=noah group=noah mode=644 size=19`;
//! `service`: `active=`/`enabled=`), and the digest is taken over that text. So
//! the field list can be honoured by dropping the named `key=value` tokens
//! BEFORE hashing, leaving the observation a digest exactly as it is today.
//! No schema bump — which also avoids `cli::lock_core`'s two hard-coded
//! `schema != "1" && schema != "1.0"` checks.
//!
//! # The mask has to be applied at every writer, not just the reader
//!
//! There are THREE places that hash a state query's stdout into the observed
//! state, and they must agree or the comparison is between two different
//! questions:
//!
//! 1. `core::executor::resource_ops::record_success` — the apply baseline;
//! 2. `cli::apply_variants::refreshed_live_hash` — `apply --refresh` and
//! `--refresh-only`, which RE-BASELINE the same digest;
//! 3. `tripwire::drift::check_nonfile_drift` — the comparison.
//!
//! Masking (1) and (3) alone is not a fix: one `--refresh` then writes an
//! unmasked digest and the very next `drift` reports FALSE drift on precisely
//! the field the operator asked forjar to ignore. Pinned by
//! `tests/falsification_ignore_drift_names_one_field.rs::
//! refresh_does_not_rebaseline_an_unmasked_observation`.
//!
//! # Why the vocabulary starts narrow, and why `content` is not in it
//!
//! `mask_observation` drops `key=value` TOKENS. A file's content hash is a bare
//! line with no `=`, and so is the `MISSING` existence sentinel — a "drop the
//! line without an `=`" rule cannot tell them apart, and would quietly erode
//! existence detection while claiming to ignore content. Content suppression
//! needs an explicit existence marker in the generator, which is a separate
//! increment. Until then `ignore_drift: ["content"]` stays a hard error, which
//! is also what keeps
//! `falsification_ignore_drift_is_not_an_off_switch::
//! a_narrowed_ignore_drift_never_reaches_the_tripwire` honest.
use crate;
/// `details` key recording the mask an observation was taken under.
///
/// Provenance, not decoration: adding `ignore_drift: ["mode"]` to an
/// already-converged resource leaves a baseline that was hashed WITHOUT the
/// mask. Comparing a masked live reading against it manufactures drift on the
/// very field being ignored — and since forjar#307 drift feeds the apply gate.
/// Drift skips the resource until the next apply re-baselines it.
pub const MASK_KEY: &str = "observed_mask";
/// A file's observation: `owner=U group=G mode=M size=S` plus a content hash.
const FILE_FIELDS: & = &;
/// A service's observation: `active=X` and `enabled=Y`, one per line.
const SERVICE_FIELDS: & = &;
/// The `ignore_drift` entries forjar can honour for this resource type.
///
/// `None` means the type has no per-field observation at all, so every narrowed
/// entry stays a validation error for it — the forjar#335 refusal, unchanged.
/// Entries of this resource's `ignore_drift` that name a maskable field.
///
/// Sorted and deduped so the recorded provenance is stable across a reordered
/// config. The wildcard is excluded: `["*"]` suppresses the whole resource
/// through `should_ignore_drift`, which never reaches the observation.
/// The provenance string for this resource's mask; empty when nothing is masked.
/// True when `token` is a `key=value` whose key is masked.
/// Drop the masked `key=value` tokens from a state query's stdout.
///
/// TOKEN-ANCHORED, never a substring replace: `mode=644` must not be found
/// inside a path or a hash. A line that consists only of masked tokens is
/// dropped; a line with no `=` at all (a content hash, the `MISSING`
/// sentinel) is never touched.
///
/// An empty mask returns the input verbatim, so every resource that does not
/// declare `ignore_drift` hashes exactly the bytes it always has — this change
/// moves no digest on the fleet.
/// Mask a state query's stdout for one resource.
/// Record the mask a freshly written observation was taken under.
/// The mask the lock entry's observation was taken under, `""` when none.