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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
//! Which files `alef verify`'s ownership walk actually opens, and how much of the tree that
//! leaves unexamined.
//!
//! Split out of `helpers.rs` rather than added to it: that file sits at this repository's
//! 1,000-line cap, and "the scan set of the ownership walk" is a self-contained concern -- the
//! two allowlists, the directory prune, the walk itself, and the coverage tally the walk
//! produces all change together and for the same reason. ~keep
/// Build/cache directories the verify walk never descends into.
const VERIFY_SKIP_DIRS: & = &;
/// Dot-directories [`collect_alef_hashes`] descends into despite its blanket dot-directory prune.
///
/// The prune exists to keep the walk out of tool caches, but it is a proxy — "starts with a dot"
/// is not "is a cache" — and alef writes stamped, alef-owned output into several dot-directories:
/// `.cargo/config.toml` from [`crate::scaffold::scaffold`], and every `SKILL.md` under an agent
/// skills root. Those files were stamped and then never read back: the walk pruned their parent
/// before opening them, so `alef verify` could not report them stale no matter how far they
/// drifted. Refusing to *stamp* them instead would be worse — the stamp is also what makes poly's
/// built-in generated-file skip leave them alone, so unstamping them hands their formatting to
/// poly and their staleness to nobody.
///
/// Incomplete by construction, and knowingly so: skills roots are pure configuration
/// (`DocsSkillsConfig::outputs`), so a consumer that writes skills into a dot-directory not named
/// here is still invisible to the walk. Closing that fully requires the walk to consult the
/// resolved config, which it currently has no access to. ~keep
const VERIFY_SCAN_DOT_DIRS: & = &;
/// Extensions the ownership walk will open. A generated file whose extension is absent here is
/// invisible to `alef verify` entirely — not reported stale, not reported missing, and not
/// visible to [`super::helpers::find_stamp_disagreement`] either.
///
/// This list is only ONE of two filters. [`collect_alef_hashes`] needs a scanned extension AND
/// an `alef:hash:` line, so adding an extension does nothing for a language whose emitted files
/// carry no stamp at all — measured in a consumer repo, `packages/java` and `packages/go` had
/// ZERO stamped files while `java`/`go` were already listed here. Those are unreachable by any
/// extension change; see the task tracking per-file stamping.
///
/// Scope of what a passing verify proves, because "verify passed" reads as the stronger claim
/// downstream: the hash covers generation INPUTS, not output bytes. One stamped manifest per
/// crate therefore detects input drift for that crate's outputs even when the outputs are
/// unstamped — but a hand-edit to an emitted file leaves inputs untouched and still verifies
/// fresh. Demonstrated in tslp: a dependency bumped inside a stamped, alef-generated
/// `Cargo.toml` reports fresh while the committed bytes differ from what alef would emit.
/// Freshness means the inputs have not moved, not that the file is what the generator writes.
///
/// `zig`/`dart`/`kt`/`kts`/`swift`/`gleam` were missing, which meant the cross-artifact straddle
/// gate could not see the zig side of a zig-vs-FFI-header straddle — the exact artifact pair it
/// exists to protect. `properties`/`pro`/`sh`/`props` were also stamped-but-unscanned, and
/// `packages/csharp/Directory.Build.props` is the ONLY stamped file in that whole package, so
/// csharp's freshness claim rested entirely on a file this walk never opened. Any new emitting
/// backend must add its extension here or its output silently leaves the
/// freshness claim.
///
/// This list must stay a **superset** of everything
/// [`crate::cli::pipeline::generate::write::marker_header_syntax`] can stamp. The walk filters on
/// extension *before* it reads any content, so an unlisted extension is invisible no matter what
/// marker the file carries. `xml`/`csproj`/`zon`/`cmake`/`gemspec` were added to that emit table
/// while missing here, which made their freshness claim unverifiable rather than merely
/// unverified — a stamped file nothing ever checks. ~keep
const VERIFY_SCAN_EXTENSIONS: & = &;
/// Dotfiles alef stamps that [`VERIFY_SCAN_EXTENSIONS`] structurally cannot reach: `Path::extension`
/// returns `None` for a name that is entirely a leading-dot stem, so `.gitignore` has no extension
/// to match and would stay invisible no matter what is added to that list. Matched on the whole
/// file name instead.
///
/// Extensionless *stamped* files belong here for the same structural reason, not just dotfiles:
/// `Makefile`, `Rakefile` and `Makevars*` carry a `#` marker but have no extension to match, and
/// `go.mod` is matched by name rather than by its `mod` extension deliberately — `.mod` is shared
/// with unrelated binary formats (Fortran module files, tracker music), so listing the extension
/// would pull those into the walk. ~keep
const VERIFY_SCAN_FILENAMES: & = &;
/// Whether the ownership walk will open a file at `path`.
///
/// Three clauses, and the third is what keeps this honest. The two allowlists above are
/// hand-maintained and had already drifted from the emit table they are documented to be a
/// superset of, so this also asks
/// [`crate::cli::pipeline::is_markable_path`] -- the emit side's own predicate -- directly:
/// anything alef would stamp on write is opened on read, whatever the allowlists happen to
/// say today. Widening the scan set can only ever cause more files to be *read*; a file is
/// still reported only when it carries a marker, so this cannot produce a false finding.
///
/// The allowlists stay, and are not redundant: `.json` and `.md` are deliberately absent from
/// the emit table (no comment syntax, and marked upstream by `docs::render` respectively) yet
/// must still be read back, which the emit predicate alone would never do. ~keep
pub
/// How much of the tree the ownership walk actually looked at.
///
/// `alef verify`'s headline result is a claim about `marked` files and nothing else, while its
/// name and its use as a CI gate read as a claim about the whole tree. Reporting these three
/// numbers alongside the verdict is what keeps the two from being confused -- the same fix
/// `alef snippets audit` needed when a snippets-only invocation printed a bare "Audit clean"
/// for a run in which the documentation-page checks never executed. ~keep
pub
/// Walk `base_dir` and return every alef-owned file paired with its optional
/// `alef:hash:<hex>` stamp. Skips build/cache directories, every directory git considers
/// ignored (see [`super::verify_gitignore::gitignored_dirs`]), and files without the Alef
/// ownership marker. Shared by [`super::helpers::verify_walk`] and
/// [`super::helpers::verify_walk_multi`] so both see the same file set, and by
/// [`super::verify_orphans::find_orphaned_generated_files`] so the orphan check sees the
/// identical disk-side file set too. ~keep
pub
/// [`collect_alef_hashes`], plus the tally of what the same walk did *not* examine.
///
/// One walk answers both questions on purpose: a separate pass to count coverage would be a
/// second implementation of the prune and filter rules, free to disagree with the pass whose
/// coverage it claims to describe. ~keep
pub