scout-essentials 1.8.7 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/.vimproject +26 -12
  3. data/README.md +83 -112
  4. data/VERSION +1 -1
  5. data/doc/Improvements.md +226 -0
  6. data/doc/StartHere.md +122 -0
  7. data/doc/developer/AnnotationSystem.md +184 -0
  8. data/doc/developer/Architecture.md +147 -0
  9. data/doc/developer/Configuration.md +238 -0
  10. data/doc/developer/CoreUtilities.md +265 -0
  11. data/doc/developer/DesignPrinciples.md +129 -0
  12. data/doc/developer/ErrorHandling.md +203 -0
  13. data/doc/developer/LockingAndConcurrency.md +157 -0
  14. data/doc/developer/PathResolution.md +200 -0
  15. data/doc/developer/PersistenceAndResources.md +119 -0
  16. data/doc/developer/StreamingModel.md +236 -0
  17. data/doc/user/AnnotatingData.md +202 -0
  18. data/doc/user/CachingResults.md +183 -0
  19. data/doc/user/CommandLineOptions.md +189 -0
  20. data/doc/user/Cookbook.md +211 -0
  21. data/doc/user/HandlingStreams.md +236 -0
  22. data/doc/user/LoggingAndProgress.md +158 -0
  23. data/doc/user/ProducingResources.md +177 -0
  24. data/doc/user/RemoteData.md +157 -0
  25. data/doc/user/RunningCommands.md +218 -0
  26. data/doc/user/WorkingWithFiles.md +217 -0
  27. data/lib/scout/cmd.rb +343 -40
  28. data/lib/scout/concurrent_stream.rb +14 -1
  29. data/lib/scout/indiferent_hash.rb +1 -1
  30. data/lib/scout/log/fingerprint.rb +13 -8
  31. data/lib/scout/log/progress/report.rb +1 -1
  32. data/lib/scout/log.rb +4 -1
  33. data/lib/scout/misc/digest.rb +6 -5
  34. data/lib/scout/misc/format.rb +24 -0
  35. data/lib/scout/named_array.rb +1 -1
  36. data/lib/scout/open/stream.rb +2 -2
  37. data/lib/scout/open/util.rb +8 -4
  38. data/lib/scout/open.rb +3 -3
  39. data/lib/scout/path/find.rb +3 -2
  40. data/lib/scout/persist.rb +14 -10
  41. data/lib/scout/resource/produce.rb +9 -1
  42. data/research/annotations-data-analysis.md +206 -0
  43. data/research/behavior-probes.md +1925 -0
  44. data/research/commands-streaming-analysis.md +272 -0
  45. data/research/design-philosophy-analysis.md +383 -0
  46. data/research/doc-audit-findings.md +294 -0
  47. data/research/ecosystem-attribution.md +118 -0
  48. data/research/implementation-inventory-core.md +1029 -0
  49. data/research/implementation-inventory-open.md +417 -0
  50. data/research/implementation-inventory-path-persist-resource.md +774 -0
  51. data/research/io-paths-analysis.md +228 -0
  52. data/research/persistence-resources-analysis.md +244 -0
  53. data/research/synthesis-report.md +80 -0
  54. data/scout-essentials.gemspec +37 -15
  55. data/test/scout/open/test_remote.rb +1 -2
  56. data/test/scout/test_cmd.rb +411 -0
  57. metadata +36 -14
  58. data/doc/Annotation.md +0 -352
  59. data/doc/CMD.md +0 -363
  60. data/doc/ConcurrentStream.md +0 -163
  61. data/doc/IndiferentHash.md +0 -240
  62. data/doc/Log.md +0 -235
  63. data/doc/NamedArray.md +0 -174
  64. data/doc/Open.md +0 -331
  65. data/doc/Path.md +0 -217
  66. data/doc/Persist.md +0 -214
  67. data/doc/Resource.md +0 -229
  68. data/doc/SimpleOPT.md +0 -236
  69. data/doc/TmpFile.md +0 -154
@@ -0,0 +1,294 @@
1
+ # Forensic doc audit of scout-essentials — final findings (durable report)
2
+
3
+ Audit target: `/bulk/mvazque2/git/scout-essentials` (gem v1.8.8, HEAD `a0324a4`).
4
+ Outcome: `doc/` fully rewritten (17 pages) + 5 new pages + `README.md` rewritten;
5
+ Critic final gate: **ACCEPT_WITH_NOTES** (notes applied).
6
+ This file is the retained summary. The full evidence trail (coverage matrix with
7
+ 261 rows, 22 per-claim ledgers, 3 implementation inventories, 66 behavior probes,
8
+ ecosystem attribution, phase reports and critic gates) lives in
9
+ `research/doc_audit/`, `research/` and `tmp/critic/` — see
10
+ `research/doc_audit/resumption-reference.md` §2 for the index.
11
+
12
+ Disposition counts (coverage-matrix sections A–F, 238 rows): OK 3 · FIX 117 ·
13
+ EXPAND 67 · NEW_PAGE 27 · INTERNAL 6 · ELSEWHERE 13 · OUT_OF_SCOPE 6.
14
+
15
+ ---
16
+
17
+ ## (a) Major hallucinations discovered and corrected
18
+
19
+ 59 confirmed false claims (coverage-matrix section E), grouped by theme:
20
+
21
+ **1. Invented APIs / nonexistent names (≈18).** Documented methods that do not
22
+ exist anywhere in `lib/` or the ecosystem: `Persist.lock(file){}` (API is
23
+ `Open.lock`), `Persist.save_job_run_info`, `CMD.add_tool` (real API
24
+ `CMD.tool(tool, claim, test, block, cmd)`), `Log.none` (only `Log::NONE`
25
+ exists), `TmpFile.tmp_dir` (real: `TmpFile.user_tmp`), `Path.map_order=`,
26
+ `Path#sync`, the `base[:results, :final]` `Path#[]` API, the `{PATH}` map
27
+ placeholder (exists only inside `Path.follow` rewriting), the `ANNOTATIONS`
28
+ constant (real: `@annotations` via `.annotations`), `MyMetadata.purge` (real:
29
+ `Annotation.purge`), `Log::Color`, `stream.join_callback <<` (real:
30
+ `add_callback`), `add_abort_callback`, `Open.pipe("a","b","c")` (takes no
31
+ args), `SOPT.get inputs: [...]`, `SOSOPT.input_descriptions` (typo),
32
+ `invert_grep` alone filtering at `Open.read` level, `deep_indifferent`.
33
+
34
+ **2. Inverted dependency graph (Architecture, 6+ rows).** Architecture.md had
35
+ ConcurrentStream above Open/Path/CMD (it is *required by* CMD and requires only
36
+ IndiferentHash), "Resource depends on Persist" / "produce acquires a Persist
37
+ lock" (no reference; the lock is `Open.lock` on `Resource#lock_dir`), "Open/CMD
38
+ depend on TmpFile" (TmpFile requires Open), and a phantom "extend `Log::Color`"
39
+ extension point. Rewritten from verified `require` edges.
40
+
41
+ **3. Wrong data types / return values (≈12).** `Persist.cache_dir`/`lock_dir`
42
+ "# => #<Path ...>" (both plain Strings), `find` "returns nil when nothing
43
+ resolves" (never nil — it falls through the map order), serialization types
44
+ `:array/:path/:file/:string_array` with invented semantics (rebuilt from
45
+ serialize.rb), `annotated_str.is_a?(MyAnnotation) # => false` (it is true),
46
+ `annotation_types.include?("MyAnnotation")` (entries are module objects),
47
+ `values.count` as a field accessor (plain `Array#count`), `zip` "propagates
48
+ annotations" (drops them), fabricated output "S003 (Human, Lua)" (prints
49
+ `(Human, Liver)`), "`Persist::Path` appends the serialization type to the
50
+ filename" (no suffix is ever appended).
51
+
52
+ **4. Fabricated semantics for real methods (≈15).** Abbreviated default
53
+ `map_order` (13 real entries), `persistence_path(name, :marshal)` positional
54
+ type (options-hash only), `save_drivers`/`load_drivers` as constants (accessor
55
+ hashes), `claim self.data.file` `self.`-prefixed paths and block-only
56
+ `claim do ... end` (type mandatory), "extend `Resource.produce` for new claim
57
+ types" (hard-coded `case`), `io.exit_status` reliable post-join (normally nil;
58
+ only `join_pids` sets it), CMD block "runs post-join" (accepted, never
59
+ invoked), progress bar "shows ETA" (elapsed + rate), `Log.debug "msg" do ... end`
60
+ "logs elapsed time" (lazy message evaluation), `with_obj_bar{|bar, item|}`
61
+ two-arg block (bar only), SOPT `=value` defaults and "`*` marks required"
62
+ (`*` = string-valued; `SOPT.require` enforces), `class MyPersist < Persist`
63
+ "wrong style" example (raises TypeError — Persist is a Module), "resources are
64
+ always released, even on error" (`TmpFile.with_file` leaks on raise), "always
65
+ pair method_missing with respond_to_missing?" (none in lib/), NamedArray "a
66
+ String with a name" / "NamedArray extends AnnotatedArray" (it does not),
67
+ **`dup` and `clone` both lose annotations — only `dup` loses; `clone` keeps
68
+ them** (clone copies the singleton class), "nested hashes are automatically
69
+ IndiferentHash" (plain Hash).
70
+
71
+ **5. Misdescribed streaming model (StreamingModel/HandlingStreams, 6+).** "CMD
72
+ creates paired stdout/stderr streams" (pairs are pipe ends; stderr is a thread),
73
+ "paired streams are joined on cleanup" (`join` never touches `@pair`), "Log
74
+ writes to the stream's paired stderr" (logfile/STDERR; per-stream capture is the
75
+ `std_err` attribute). Also the two synthesis-level artifacts: `Persist` is
76
+ *name*-based, not "content-addressed caching" (keys are names + option digests),
77
+ and `Open.read_stream`'s documented IO.select/Thread.pass loop was the **dead
78
+ first definition** (stream.rb:382-399); the effective method (stream.rb:401) is
79
+ a plain blocking read of exactly N bytes — same observable contract, corrected
80
+ in the final pass.
81
+
82
+ Top examples (doc:claim → actual): `find` nil-returns → never nil ·
83
+ `Persist.lock` → `Open.lock` · `save_drivers` constants → accessor hashes ·
84
+ paired stderr streams → pipe ends + stderr thread · `clone` loses annotations →
85
+ only `dup` does · invert_grep filters alone → no-op without `grep`.
86
+
87
+ ---
88
+
89
+ ## (b) Major omissions discovered and documented
90
+
91
+ From the 67 EXPAND rows and 27 NEW_PAGE rows.
92
+
93
+ **Five subsystems had no doc home at all** → new pages created:
94
+ 1. `doc/developer/Configuration.md` — `Scout::Config` token-priority resolution
95
+ (`workflow`4 > `task`3 > `file`2 > `line`1; `key:` = 20 = lowest; explicit
96
+ `::N` wins), `etc/config` line format, `:env`/`env:VAR` values,
97
+ `with_config` snapshot/restore, `process_config`, no-mutex caveat.
98
+ 2. `doc/developer/ErrorHandling.md` — 20-class exception taxonomy, control-flow
99
+ signals deriving from `Exception` not `StandardError` (`StopInsist`,
100
+ `DontClose`, `DontPersist`, `KeepLocked`, `KeepBar` — a bare `rescue =>` will
101
+ NOT catch them), `canfail`/`no_fail`, abort protocol.
102
+ 3. `doc/developer/LockingAndConcurrency.md` — vendored `Lockfile` (Ara T. Howard
103
+ v2.1.8, modified; guarded `unless defined?`), `Open.lock`/`LockInterrupted`,
104
+ the **three distinct lock namespaces** (`Persist.lock_dir` + `.persist`
105
+ suffix; `Resource#lock_dir` with TmpFile-digest names;
106
+ `sensible_write_lock_dir`), KeepLocked streaming persistence, fork model,
107
+ thread-safety caveats.
108
+ 4. `doc/developer/CoreUtilities.md` — TmpFile naming conventions (`·` U+00B7
109
+ per `/`, `PREFIX:`, `[key]`, `&F[m=<digest(value)>]` + `:md5` tail,
110
+ `MAX_FILE_LENGTH` 150), IndiferentHash instance protocol, Misc format/timespan/
111
+ digest/insist families, Hook (not auto-required), `NamedArray`/`Hook` explicit
112
+ require.
113
+ 5. `doc/user/RemoteData.md` — `Open.remote?`/`ssh?`, wget cache (`var/cache/open-remote`,
114
+ digest over url+post data; no TTL), `:force` behaviour (raises OpenURLError,
115
+ does not touch cache), ssh/scp/rsync, `Open.wait` `LAST_TIME` rate limiter.
116
+
117
+ **Biggest in-page gaps (EXPAND) now filled:**
118
+ - `update:`/`check:` cache staleness invalidation (persist.rb:41-47) — the single
119
+ most useful persistence feature, previously entirely absent.
120
+ - `save_stderr` three shapes (commit `ee24c68`): `true` → `std_err`; a file path
121
+ (CMD opens/truncates, creates parent dirs, line-buffered so `tail -f` works);
122
+ any IO-like (written+flushed, never closed). Old docs only showed
123
+ `save_stderr: true`.
124
+ - TmpFile naming + `$HOME/tmp/scout/tmpfiles` root with `tmpdir=` override; the
125
+ three lock namespaces, previously conflated into one.
126
+ - Annotation mechanics: `setup` extends in place (same object unless frozen),
127
+ `@produced` tri-state latching on `Path#produce`, `serialize` → plain Hash,
128
+ Marshal round-trip, `dup` vs `clone`.
129
+ - `:memory` + `MEMORY_CACHE` + `Persist.memory("key", key:)`, `TRUE_STRINGS`
130
+ (13 spellings), `:serializer == :json` default, `Open.json`/`Open.yaml`
131
+ `find_with_extension` fallback (`:yaml` load may return a Psych AST node).
132
+ - Log: severity constants, `Log::LAST` protocol, `with_severity`, `fingerprint`,
133
+ `Log.logfile` no-arg call *resets* the logfile, `SCOUT_NOCOLOR == 'true'`
134
+ exact string, ProgressBar YAML resume, `percent == 100` when `max == 0`.
135
+ - SOPT: `*` = string-valued, `SOPT.require` enforcement, destructive `consume!`,
136
+ `## SYNOPSYS` (sic) header, partial `reset`.
137
+ - CMD: `CMD::Timeout`, `:in`/`:in_pipe`/`:post`/`:wait`/`:canfail`/
138
+ `:empty_inputs`/`:separator`, pipe-mode timeout via stream abort, default
139
+ stderr severity `Log::DEBUG`.
140
+ - Path: `find_all`/`glob_all`, `@where`/`@original`, `identify`, `relocate`,
141
+ `#read`/`#open` produce first but `#write` does not, `method_missing` segment
142
+ building (`Scout.etc`), explicit `require 'scout/named_array'`.
143
+ - Resource: `rake_dirs` + `ScoutRake.run` fork model, `share/software` +
144
+ `install_helpers`, `Resource.install`.
145
+
146
+ ---
147
+
148
+ ## (c) Important concepts that remain undocumented, with reasons
149
+
150
+ **Dead / unreachable code — documented as known limitations, not as API:**
151
+ - First `Open.read_stream` definition (stream.rb:382-399, IO.select/Thread.pass
152
+ loop) — shadowed by the second def at :401; never reachable.
153
+ - Dead `Open.read_stream` duplicate + commented-out `sort_stream` (stream.rb:442-444).
154
+ - `Open.append` instance method (dead); docs teach `mode: 'a'` instead.
155
+ - `Open.notify_write` is a silent no-op in an essentials-only install (`Misc.notify`/
156
+ `send_email` are not defined here — see (d)); noted, not taught.
157
+ - `Open.with_fifo` cannot clean a pre-existing path (`File.rm` NoMethodError) —
158
+ listed as a known limitation.
159
+
160
+ **Kept as INTERNAL footnotes (not user-facing guarantees):**
161
+ - `Scout::Config` legacy rbbt caller-token regexes (`file:`/`line:` frames rarely
162
+ filtered by scout code) — implementation residue; explaining them in user docs
163
+ would document dead parsing branches.
164
+ - `Scout::Config` has no mutex around the shared CACHE — recorded as a known gap
165
+ in LockingAndConcurrency rather than as behaviour to rely on.
166
+ - `Misc` double definitions (`counts` defined twice), `intersect_sorted_arrays`
167
+ mutating its args, `file_md5` fallback hashing the *path string* rather than
168
+ content — all noted as code-health items, none is a documented guarantee.
169
+ - `Persist::CONNECTIONS` registry — internal plumbing.
170
+
171
+ **OUT_OF_SCOPE (by design):**
172
+ - The stale gemspec doc manifest (lists 12 nonexistent `doc/*.md` files). This is
173
+ a maintenance task (`rake gemspec` regeneration), not a documentation issue;
174
+ left open and flagged rather than claimed fixed.
175
+ - Cross-repo surfaces that this repo must not teach (see (d)).
176
+
177
+ ---
178
+
179
+ ## (d) Functionality previously attributed to this repo that is implemented elsewhere
180
+
181
+ From coverage-matrix section F + `research/ecosystem-attribution.md` (every row
182
+ verified by locating the defining line or the require/dependency edge, never by
183
+ name alone):
184
+
185
+ | concept | actually implemented in | key evidence |
186
+ |---|---|---|
187
+ | `TSV` class, `TSV::Dumper` | **scout-gear** | `GEAR/lib/scout/tsv.rb:17`, `tsv/dumper.rb:2` |
188
+ | `Annotation.tsv` / `Annotation.load_tsv` (hence the `:annotation` Persist serializer) | **scout-gear** (reopens essentials' Annotation) | `GEAR/lib/scout/tsv/annotation.rb:46,127`; essentials' serialize.rb:37-38,74-75 call it without requiring it → NoMethodError standalone |
189
+ | `Workflow` / `Task` / `Step` | **scout-gear** | `GEAR/lib/scout/workflow{,_task,_step}.rb` |
190
+ | `.info` Step blob format read by `Open.mtime` | **scout-gear** | essentials `open/final.rb:147-150` calls `Persist.load(info_file, Step::SERIALIZER)` — a gear constant, never required |
191
+ | Scheduler / HPC (SLURM/PBS/LFS/orchestrator) | **scout-gear**; **scout-camp** only adds cloud/terraform deployment (its bin loads gear's `bin/scout`) | `GEAR/.../deployment/scheduler/{slurm,pbs,lfs}.rb`; `CAMP/lib/scout-camp.rb` |
192
+ | `Misc.notify` / `Misc.send_email` | **legacy rbbt-util only** (no scout gem defines them) | `RU/lib/rbbt/util/misc/communication.rb:15,48` |
193
+ | `Bgzf` / bgzip path of `Open.bgunzip` | **legacy rbbt-util only** | `RU/lib/rbbt/util/misc/bgzf.rb:5` |
194
+ | `deep_indifferent` | **exists NOWHERE** (0 grep hits across essentials, gear, ai, rig, camp, rbbt-util 6.0.5) | ecosys #5 |
195
+ | CLI `bin/scout` / `scout_commands/` dispatch | **scout-gear** (+ per-gem command dirs) | `GEAR/bin/scout` |
196
+ | rbbt-util 6.0.5 `require_instead` shims (30 files redirecting rbbt requires onto scout files) | **rbbt-util**; direction UNVERIFIED at gemspec level | `RU/lib/rbbt/util/misc/indiferent_hash.rb` |
197
+
198
+ Genuinely local (for contrast): `ScoutRake`/`rake_dirs` (gear does not use it),
199
+ `Scout.etc` mechanism, `share/software` + `install_helpers`, Path/Resource/
200
+ Persist/Open/CMD/Log/SOPT/TmpFile/IndiferentHash/NamedArray/Misc/Annotation core,
201
+ vendored Lockfile, `Scout::Config`.
202
+
203
+ Dependency direction: scout-gear and scout-camp *declare* essentials;
204
+ scout-rig requires it in code but **omits the gemspec edge**; scout-ai reaches it
205
+ only transitively. Conversely essentials silently depends *upward* on gear types
206
+ (TSV, Step::SERIALIZER) and legacy rbbt-util methods for the items above — no
207
+ scout-family or rbbt-family runtime dependency is declared at all
208
+ (term-ansicolor, yaml, rake, listen only).
209
+
210
+ ---
211
+
212
+ ## (e) Significant inter-document inconsistencies corrected
213
+
214
+ From the critic gates (`tmp/critic/gate1|gate2/report.md`, final
215
+ acceptance notes) and the phase-6 consistency audit:
216
+
217
+ - **invert_grep no-op example** (WorkingWithFiles) — the rewrite claimed
218
+ `Open.read(f, invert_grep: 'an')` filters lines; live probe shows it returns
219
+ the whole file (`invert_grep` only takes effect together with `grep`; the
220
+ underlying `Open.grep(io, 'an', true)` works). New regression caught by gate 1.
221
+ - **dup-vs-clone** — AnnotationSystem said *both* lose annotations; its own cited
222
+ probe shows `clone` **keeps** them (clone copies the singleton class). Gate 2.
223
+ - **`&F[match={:m=>1}]` naming shape** (CoreUtilities) — real shape is
224
+ `&F[m=c4ca4238a0b923820dcc509a6f75849b]:<md5-of-options>`; the value is
225
+ *digested*, not inspected, and a `:md5` tail is appended. Gate 2.
226
+ - **`tmp_for_file` arity** (CoreUtilities) — `tmp_for_file('/a/b/c', :prefix =>
227
+ 'P')` raises NoMethodError (a Hash in the `tmp_options` slot); the runnable
228
+ form is `tmp_for_file('/a/b/c', {}, :prefix => 'P')`. Gate 2.
229
+ - **`save_job_run_info`** (Cookbook) — nonexistent API pointing at a page that
230
+ documents no such feature; sentence removed. Phase 6.
231
+ - **`TmpFile.tmp_dir`** (WorkingWithFiles) vs `TmpFile.user_tmp` (CoreUtilities)
232
+ — two pages disagreed, one referenced a nonexistent method; unified on
233
+ `user_tmp` (`$HOME/tmp/scout`). Phase 6.
234
+ - **`Log.none`** (LoggingAndProgress) — no such convenience method; ladder top
235
+ expressed via the `Log::NONE` constant. Phase 6.
236
+ - **Terminology anchors now enforced tree-wide** — "pipe ends" (never "paired
237
+ stdout/stderr streams"), `std_err` for the capture attribute, severity ladder
238
+ `DEBUG LOW MEDIUM HIGH INFO WARN ERROR NONE`, bare `claim` (never
239
+ `self.claim`), `find` never nil, drivers as accessor hashes.
240
+ - **Lock-dir examples** no longer hard-code a machine-specific home path
241
+ (`$HOME/.scout/tmp/{persist,produce,sensible_write}_locks`), while the literal
242
+ `·home·mvazque2·...probeD2` lock name is retained verbatim (it is actual
243
+ `TmpFile.tmp_for_file` output) and now annotated as a machine-specific example
244
+ value. Final pass.
245
+
246
+ ---
247
+
248
+ ## (f) Areas where the implementation is ambiguous and documentation remains uncertain
249
+
250
+ From `research/doc_audit/resumption-reference.md` §5 decisions and the
251
+ UNVERIFIED list:
252
+
253
+ - **AnnotatedArray Integer elements** — raise TypeError (`cm_aa2/3/4` probes);
254
+ annotated arrays require extendable elements. Documented as a requirement plus
255
+ an Improvements-style note; arguably a bug.
256
+ - **`AnnotatedArray#each_with_index` without a block** — NoMethodError. Treated
257
+ as a bug; explicitly not promised in the docs.
258
+ - **IndiferentHash `"false"` coercion asymmetry** — `string2hash`/`parse_options`
259
+ never coerce `"false"` to `false` (P20), while `Scout::Config.get` does.
260
+ Documented as-is and flagged as a bug candidate.
261
+ - **rbbt-util dependency direction** — `require_instead` shims clearly *consume*
262
+ scout files, but rbbt-util 6.0.5's installed tree has no readable gemspec, so
263
+ the declared edge is UNVERIFIED. Kept flagged in Architecture's table.
264
+ - **NamedArray `key:` kwarg** — claimed, but the signature is `*args` with no
265
+ kwarg handling; the claim was dropped rather than resolved.
266
+ - **Log severity env/file precedence** — both sources are real; resolved by probe
267
+ (environment variables override the config file) and documented with that order.
268
+ - **Persist driver registry naming** — resolved: the accessor-hash form
269
+ (`Persist.save_drivers` / `Persist.load_drivers`) is what the code uses;
270
+ documented accordingly.
271
+ - **`ConcurrentStreamProcessFailed` clobber** — the exception carries
272
+ `concurrent_stream => nil` in practice; documented as an unreliable attribute.
273
+ - **`Open.wgrep` / `Open.rgrep` claims** — nonexistent; removed from the docs.
274
+ - **`NamedArray` `key:` kwarg and Camp "job arrays"** — both remain UNVERIFIED in
275
+ the research record (see `research/ecosystem-attribution.md` §UNVERIFIED).
276
+
277
+ ---
278
+
279
+ ## Post-audit state
280
+
281
+ - 22 doc files delivered (17 rewritten + 5 NEW_PAGE); README.md rewritten and
282
+ gate-verified (all links resolve, getting-started snippet executable,
283
+ abstractions table traced live).
284
+ - `doc/Improvements.md` deliberately **untouched** — it is a bug/improvement log,
285
+ not documentation; its `deep_indifferent` guidance stays as a historical entry.
286
+ - Critic final verdict ACCEPT_WITH_NOTES; the three optional notes were applied:
287
+ HandlingStreams `read_stream` mechanism sentence (stream.rb:401), README
288
+ test-count parenthetical (49 files = 47 test files, one of which
289
+ `test/scout/log/test_color.rb` is empty, + 2 `test_helper.rb`, one defining
290
+ `TestMiscHelper`), and machine-specific-value annotations on the two remaining
291
+ `/home/mvazque2` strings.
292
+ - Open maintenance items (not docs): regenerate the gemspec doc manifest;
293
+ decide the fate of the untracked `test/scout/test_cmd_save_stderr.rb`.
294
+ - Nothing committed; `research/doc_audit/` retained pending user confirmation.
@@ -0,0 +1,118 @@
1
+ # Phase 3 — Cross-repo ecosystem attribution (scout-essentials)
2
+
3
+ Date: 2026-02 (audit working copy)
4
+ Audit target: `/bulk/mvazque2/git/scout-essentials` (defines the `Scout` namespace).
5
+
6
+ ## Evidence sources
7
+
8
+ | Repo | Where examined | Status |
9
+ |---|---|---|
10
+ | scout-essentials | `/bulk/mvazque2/git/scout-essentials` (worktree, VERSION 1.8.8) + installed gem `scout-essentials-1.8.8` | LOCAL, authoritative |
11
+ | scout-gear | installed gem `~/.rvm/gems/ruby-3.3.1/gems/scout-gear-10.12.2` (also 10.11.x, 10.12.1) | LOCAL (gem copy); the `/bulk/mvazque2/git/scout-gear` worktree is only bound for `tmp/` in this sandbox, so gem copies were used |
12
+ | scout-ai | installed gem `scout-ai-1.2.6` (gemspec stub says 1.2.5) | LOCAL (gem copy); worktree similarly `tmp/`-only |
13
+ | scout-rig | installed gem `scout-rig-0.2.6` | LOCAL (gem copy only) |
14
+ | scout-camp | installed gem `scout-camp-0.2.0` | LOCAL (gem copy only) |
15
+ | rbbt-util | installed gem `rbbt-util-6.0.5` | LOCAL (relevant for the refactor shim layer) |
16
+
17
+ Method: every attribution below was verified by locating the defining
18
+ `class`/`module` line, or the `require`/`add_runtime_dependency` edge — never by
19
+ name alone. File paths are gem paths; `SE = /bulk/mvazque2/git/scout-essentials`,
20
+ `GEAR = ~/.rvm/gems/ruby-3.3.1/gems/scout-gear-10.12.2`,
21
+ `AI = .../scout-ai-1.2.6`, `RIG = .../scout-rig-0.2.6`,
22
+ `CAMP = .../scout-camp-0.2.0`, `RU = .../rbbt-util-6.0.5`.
23
+
24
+ ## Attribution table
25
+
26
+ | # | Concept | Implementing repo | Evidence (file:line) | Classification | Notes |
27
+ |---|---|---|---|---|---|
28
+ | 1 | `TSV` class | **scout-gear** | `GEAR/lib/scout/tsv.rb:17 module TSV` (required by `GEAR/lib/scout-gear.rb:2 require_relative 'scout/tsv'`) | PROVIDED_BY_DEPENDENCY (w.r.t. essentials) | Not present anywhere in `SE/lib`. essentials' `lib/scout-essentials.rb` never requires `scout/tsv` — hence the standalone `NameError`s in the P-probes. |
29
+ | 1b | `TSV::Dumper` | **scout-gear** | `GEAR/lib/scout/tsv/dumper.rb:2 class Dumper` (inside `module TSV`) | PROVIDED_BY_DEPENDENCY | The `when TSV::Dumper` branch that `SE`'s produce code tests can only be reached with scout-gear loaded. |
30
+ | 2 | `Annotation.tsv` / `Annotation.load_tsv` | **scout-gear** | `GEAR/lib/scout/tsv/annotation.rb:46 def self.tsv(objs, *fields)`; `:127 def self.load_tsv_values(id, values, *fields)` (also referenced at `:158`) | PROVIDED_BY_DEPENDENCY (reverse dependency: gear reopens essentials' Annotation module) | `SE/lib/scout/persist/serialize.rb:37-38` (`Annotation.tsv(content, :all).to_s`) and `:74-75` (`Annotation.load_tsv(TSV.open(serialized))`) call methods that only exist once scout-gear's `scout/tsv/annotation.rb` is loaded. → **CONSUMED_OR_INTEGRATED_HERE** for the caller side. The `:annotation` Persist serializer in essentials is therefore only functional inside the full scout stack. |
31
+ | 3 | `Workflow` / `Task` / `Step` / Job machinery | **scout-gear** | `GEAR/lib/scout/workflow.rb:17 module Workflow`; `GEAR/lib/scout/workflow/task.rb:8 module Task`; `GEAR/lib/scout/workflow/step.rb:16 class Step`; entry chain `GEAR/lib/scout.rb → require 'workflow-scout' → require 'scout-gear' + require 'scout/workflow'` | NOT_IMPLEMENTED_IN_ESSENTIALS / PROVIDED_BY_DEPENDENCY | Confirms the P-inventory: `SE` defines none of these. |
32
+ | 3b | `.info` file format read by `Open.mtime` | **scout-gear** | `GEAR/lib/scout/workflow/step.rb:6 SERIALIZER = Scout::Config.get(:serializer, :step_info, ...)`; `GEAR/lib/scout/workflow/step/info.rb:7 def info_file`, `:16 def self.load_info` | CONSUMED_OR_INTEGRATED_HERE | `SE/lib/scout/open/final.rb:147-150` reads `<file>.info` and calls `Persist.load(info_file, Step::SERIALIZER)[:]` — it hard-depends on scout-gear's `Step::SERIALIZER` constant while never requiring scout-gear. Same silent-coupling pattern as #2. |
33
+ | 4 | Scheduler / HPC / orchestration (SLURM, PBS, LFS, orchestrator rules, job arrays, `scout camp`) | **scout-gear** (SLURM/PBS/LFS/orchestrator) + **scout-camp** (infra provisioning) | SLURM: `GEAR/lib/scout/workflow/deployment/scheduler/slurm.rb:4 module SLURM` (sibling files `job.rb`, `lfs.rb`, `pbs.rb`); orchestrator: `GEAR/lib/scout/workflow/deployment/orchestrator/rules.rb:220` reads `Scout.etc.batch[...]`; camp: `CAMP/lib/scout-camp.rb`, `CAMP/lib/scout/{offsite,terraform_dsl,aws,render}.rb`, `CAMP/bin/scout-camp` (ends `load Scout.bin.scout.find`), `CAMP/scout_commands/{find,glob,offsite,sync,terraform}` | NOT_IMPLEMENTED_IN_ESSENTIALS | No scheduler/HPC/SLURM/camp code or docs exist in scout-essentials (grep over `SE/lib` and `SE/doc` returns nothing). Note `CAMP`'s HPC/scheduler layer itself is **thin** — `scout-camp`'s gemspec runtime deps are scout-essentials + aws-sdk-s3 + sinatra + terraform + mimemagic + omniauth (no scout-gear edge), and its bin simply loads scout-gear's `bin/scout`; the SLURM/PBS/orchestrator logic lives in gear. |
34
+ | 5 | `deep_indifferent` | **NOWHERE (in this ecosystem snapshot)** | grep `def deep_indifferent` across all installed scout gems + rbbt-util-6.0.5/lib → 0 hits. Only textual mention: `SE/doc/Improvements.md:85-89` ("deep_indifferent must be called explicitly") and the AnnotatingData ledger citing `Misc.deep_indifferent` | NOT_FOUND | `SE/lib/scout/misc.rb` requires only format/insist/digest/filesystem/monitor/system/helper/matching/math — no `misc/indiferent_hash` and no such method. `SE/lib/scout/indiferent_hash.rb` has `deep_merge` (line 21) but no `deep_indifferent`. rbbt-util 6.0.5's `lib/rbbt/util/misc/indiferent_hash.rb` is a **refactor shim**: `Rbbt.require_instead 'scout/indiferent_hash'`, i.e. it points *back* at essentials, so it cannot be the implementation either. Doc claim that this is available behavior is **stale/hallucinated**; only nested *access* works, and only incidentally (ledger C17). |
35
+ | 6 | `Misc.notify` / `Misc.send_email` | **rbbt-util (legacy), not scout-gear/ai/rig/camp/essentials** | `RU/lib/rbbt/util/misc/communication.rb:15 def self.notify`, `:29 def self.send_email_old`, `:48 def self.send_email` | CONSUMED_OR_INTEGRATED_HERE (implicit, no require) | `SE/lib/scout/open/util.rb:89-99` (`Open.notify_write`) calls `Misc.notify` / `Misc.send_email`; `SE/lib/scout/misc.rb` does not require any communication module, and no scout gem defines these. In an essentials-only install these are `NoMethodError`s (rescued by `rescue` in `notify_write`). rbbt-util's communication.rb is **not** a shim (it contains the real implementation), unlike its other files. So the definitions survive only in the legacy rbbt-util gem, outside the Scout stack proper. |
36
+ | 7 | `Bgzf` (bgzip/bgzf decompression) | **rbbt-util (legacy)** | `RU/lib/rbbt/util/misc/bgzf.rb:5 module Bgzf` (used at `:24`) | CONSUMED_OR_INTEGRATED_HERE (implicit, no require) | `SE/lib/scout/open/util.rb:35 Bgzf.setup stream` (in `Open.bgunzip`) references a constant that no scout gem defines. rbbt-util's `lib/rbbt/tsv/util.rb:112` even has the Bgzf branch commented out. Same pattern as #6: works only when legacy rbbt-util happens to be loaded. |
37
+ | 8 | `ScoutRake` / `rake_dirs` | **scout-essentials** | `SE/lib/scout/resource/produce/rake.rb:5 class Rake::FileTask` patch, `:25 module ScoutRake`, `def self.run`; `rake_dirs` registry in `SE/lib/scout/resource/produce.rb` (claim → `@rake_dirs[path]`), consumed at `produce.rb:127 when :rake` | IMPLEMENTED_IN_ESSENTIALS | Genuinely local. `rake` is a declared runtime dep (`SE/scout-essentials.gemspec:174`). scout-gear does NOT use `rake_dirs`/`ScoutRake` (0 hits in `GEAR/lib`), so this is currently essentials-only plumbing consumed by end-user Resource claims. |
38
+ | 8b | `Scout.etc` / `etc` path convention | **scout-essentials** (mechanism) — consumed by all | mechanism: `SE/lib/scout/resource/scout.rb:9 Path.load_path_maps(Scout.etc["path_maps"])` + `SE/lib/scout/config.rb:29 Path.setup("etc").config.find_all...`; `etc` is not a hard-coded map — `Path.method_missing` (`SE/lib/scout/path.rb:44-53`) + `Resource#method_missing` (`SE/lib/scout/resource.rb:69`) turn `Scout.etc`/`Scout.share` into path-joins resolved through the map registry (`SE/lib/scout/path/find.rb:84-100` path_maps incl. `:user => "{HOME}/.{PKGDIR}/..."`) | IMPLEMENTED_IN_ESSENTIALS | Downstream consumers confirmed: `AI/lib/scout-ai.rb:1-9`, `RIG/lib/scout-rig.rb:1-4`, `CAMP/lib/scout-camp.rb:1-9` all do `require 'scout' + require 'scout/path' + require 'scout/resource'` then `Path.add_path :<self>_lib, ...`; gear reads `Scout.etc.batch[...]` (`GEAR/.../orchestrator/rules.rb:220`). |
39
+ | 8c | `Scout.share.software` / `share/software` + `install_helpers` | **scout-essentials** (convention + assets) | `SE/lib/scout/resource.rb:7,16 require_relative 'resource/software'`; `SE/lib/scout/resource/software.rb:4 File.expand_path(Scout.share.software.install_helpers.find(:lib))`, `:7 def self.install(...)`; assets shipped in `SE/share/software/install_helpers`; software claim branch `SE/lib/scout/resource/produce.rb:138-141` (`self.root.software`, `Resource.install`, `set_software_env`) | IMPLEMENTED_IN_ESSENTIALS | scout-rig contains **no** `software`/`share/software` references (0 hits in `RIG/lib` + Rakefile), so the "rig relationship" is only the generic `Path`/`Resource` integration above, not a software-install one. |
40
+ | 9 | `Scout` module default resource (`Resource.default_resource = Scout`, pkgdir `scout`) | **scout-essentials** | `SE/lib/scout/resource/scout.rb:1-10` (`module Scout; extend Resource; self.pkgdir='scout'`; `Resource.default_resource = Scout`); `Path.default_pkgdir` `SE/lib/scout/path.rb:10-15` | IMPLEMENTED_IN_ESSENTIALS | `Scout.version` is the only thing scout-gear adds (`GEAR/lib/scout.rb:4-7`). |
41
+ | 10 | CLI binary (`scout`) and `scout_commands/` dispatch | **scout-gear** (bin) — plus per-gem command dirs | `GEAR/bin/scout` (loads scout_commands: alias, batch, cat, doc, entity, find, glob, kb, log, purge, rbbt, resource, system, template, update, workflow); `CAMP/bin/scout-camp` ends with `load Scout.bin.scout.find`; `CAMP/scout_commands/{find,glob,offsite,sync,terraform}`; `AI/bin/scout-ai` + `AI/scout_commands/{agent,llm,workflow}` | NOT_IMPLEMENTED_IN_ESSENTIALS | Matches inventory: essentials ships no bin/ and no scout_commands/. |
42
+ | 11 | IndiferentHash, Path.find/path_maps, Log, SOPT/SimpleOpt, CMD, Persist, Resource claims, Open, TmpFile, Annotation (core), NamedArray, Config | **scout-essentials** | `SE/lib/scout-essentials.rb` require list; per-file definitions in `SE/lib/scout/{indiferent_hash,path,log,simple_opt,cmd,persist,resource,open,tmpfile,annotation,named_array,config}.rb` (verified in Phase 1/2 inventories and re-checked here) | IMPLEMENTED_IN_ESSENTIALS | — |
43
+
44
+ ## Dependency graph
45
+
46
+ ### What scout-essentials depends on (runtime)
47
+
48
+ From `SE/scout-essentials.gemspec:172-175`:
49
+ `term-ansicolor`, `yaml`, `rake`, `listen`. Dev deps: shoulda, rdoc, juwelier.
50
+
51
+ That is: **no scout-family or rbbt-family runtime dependency at all.**
52
+ Everything scout-flavoured it touches (TSV, Step::SERIALIZER, Annotation.tsv,
53
+ Misc.notify/send_email, Bgzf) is reached *implicitly* without a require or a
54
+ gemspec edge — the "phantom integrations" documented above.
55
+
56
+ ### Who depends on scout-essentials
57
+
58
+ Verified from installed gemspecs (grep `add_runtime_dependency`):
59
+
60
+ - **scout-gear** — yes. `GEAR/scout-gear.gemspec: add_runtime_dependency scout-essentials (>= 0)` (also in 10.11.6, 10.11.10, 10.12.1). Code edge too: `GEAR/lib/scout-gear.rb:1 require 'scout-essentials'`.
61
+ - **scout-camp** — yes. `CAMP/scout-camp.gemspec: add_runtime_dependency scout-essentials (>= 0)`; `CAMP/lib/scout-camp.rb:1-3 require 'scout'/'scout/path'/'scout/resource'` (which resolve to essentials' files, e.g. `SE/lib/scout/path.rb`, `SE/lib/scout/resource.rb`, `SE/lib/scout/resource/scout.rb`).
62
+ - **scout-rig** — **no scout-essentials edge in its gemspec** (`RIG/scout-rig.gemspec` runtime deps: `pycall` only), but **code-level dependency**: `RIG/lib/scout-rig.rb:1-4` requires `'scout'`, `'scout/path'`, `'scout/resource'` and calls `Path.caller_lib_dir`, `Path.add_path`; `RIG/lib/scout/python/paths.rb:26 add_paths(Scout.python.find_all)` uses essentials' Path machinery (and `RIG/lib/scout/workflow/python.rb:1 require 'scout/workflow'` needs scout-gear). So rig is a *declared* transitive consumer at best — the gemspec simply omits the constraint.
63
+ - **scout-ai** — **directly depends on scout-rig** (`AI/scout-ai.gemspec` runtime deps: scout-rig, ruby-openai, ollama-ai, ruby-mcp-client, hnswlib) and *transitively* on essentials via rig's runtime requires and via `AI/lib/scout-ai.rb:1-3 require 'scout'/'scout/path'/'scout/resource'` (0 direct mentions of scout-gear/scout-essentials in its gemspec). Its bin delegates to gear's CLI (`AI/bin/scout-ai`).
64
+ - **rbbt-util 6.0.5** — reverse-compat consumer: `RU/lib/rbbt/util/misc/indiferent_hash.rb` and 29 other files are two-line `Rbbt.require_instead 'scout/<path>'` shims redirecting rbbt requires onto essentials/gear files (`scout/persist/tsv`, `scout/resource`, `scout/workflow/step`, `scout/semaphore`, `scout/tmpfile`, `scout/tsv`, `scout/work_queue`, ...). Note this shim set targets the *whole* Scout stack, not only essentials, and rbbt-util's own gemspec is not readable at the audited path (gemspec file absent in the installed tree) → dependency *direction* for rbbt-util UNVERIFIED at gemspec level (see below).
65
+
66
+ ### Graph summary
67
+
68
+ ```
69
+ term-ansicolor/yaml/rake/listen
70
+
71
+ scout-essentials (Scout namespace core: Path, Open, Log, Persist, CMD,
72
+ ▲ ▲ ▲ Resource/claims, Annotation core, IndiferentHash, SOPT…)
73
+ │ │ │ (runtime requires; rig/camp also declare the gemspec edge,
74
+ │ │ │ rig's gemspec omits it)
75
+ │ │ └────────────── scout-camp ──(bin loads)──► bin/scout (gear)
76
+ │ └──── requires ──── scout-rig ──(gemspec dep)─► scout-ai
77
+ └─ gemspec+require ───── scout-gear (TSV, Workflow/Task/Step, HPC/
78
+ scheduler, CLI bin/scout, scout_commands)
79
+
80
+ └── loaded by CAMP/bin, AI/bin
81
+
82
+ legacy rbbt-util 6.0.5 ── require_instead shims ──► scout-essentials/gear
83
+ (and keeps the only surviving definitions of Misc.notify/send_email, Bgzf)
84
+ ```
85
+
86
+ ### "Foundation of Scout and Scout-AI" — verdict
87
+
88
+ `SE/doc/StartHere.md:3-5` says essentials "is the foundation upon which Scout
89
+ and Scout-AI ... are built". As a **dependency-graph statement**:
90
+
91
+ - **TRUE for scout-gear ("Scout")** — direct gemspec + `require 'scout-essentials'` edge; gear builds TSV/Workflow/Step/HPC on top of it.
92
+ - **TRUE for scout-camp** — direct gemspec edge.
93
+ - **ONLY INDIRECTLY TRUE for scout-rig** — required in code, missing from rig's gemspec (omitted dependency).
94
+ - **TRUE for scout-ai ("Scout-AI")** — as a *transitive* dependency via scout-rig + its own `require 'scout'`; scout-ai has **no direct** dependency declaration on essentials.
95
+
96
+ So the claim is substantively correct but imprecise: essentials is the
97
+ namespace/foundation layer of the *whole* family, yet scout-ai (and rig) reach it
98
+ without declaring it, and several essentials features are themselves only
99
+ completable by scout-gear (see misattributions).
100
+
101
+ ## Claims in scout-essentials docs that misattribute functionality
102
+
103
+ 1. **Persist `:annotation` serializer presented as working** (`SE/lib/scout/persist/serialize.rb:37-38, 74-75`; documented in the data/persistence doc pages): the `Annotation.tsv` / `Annotation.load_tsv` / `TSV.open` it invokes are implemented in **scout-gear** (`GEAR/lib/scout/tsv/annotation.rb:46,127`; `GEAR/lib/scout/tsv.rb:17`), not in essentials, and are never required. Correcting attribution: *scout-gear*; standalone essentials raises `NoMethodError`/`NameError`.
104
+ 2. **`Resource#produce` `:proc` / `:csv` claim types documented as usable** (`SE/lib/scout/resource/produce.rb`, branches testing `when TSV` / `when TSV::Dumper` before `when nil`, and `:csv` → "Not implemented yet"): the `TSV`/`TSV::Dumper` constants come from **scout-gear** (`GEAR/lib/scout/tsv.rb:17`, `GEAR/lib/scout/tsv/dumper.rb:2`). With gear absent, nil-returning procs crash with `NameError`. Correcting attribution: integration point with *scout-gear*, not an essentials feature.
105
+ 3. **`Open.mtime` documented as a plain file-metadata helper**: `SE/lib/scout/open/final.rb:147-150` parses `<file>.info` with `Step::SERIALIZER` — `Step` and that constant are defined in **scout-gear** (`GEAR/lib/scout/workflow/step.rb:6,16`). Correcting attribution: essentials *reads* a **scout-gear Step** file format; the format is owned by gear.
106
+ 4. **`Misc.notify` / `Misc.send_email` implied to exist** (`SE/lib/scout/open/util.rb:89-99` `Open.notify_write`): implemented only in **legacy rbbt-util** (`RU/lib/rbbt/util/misc/communication.rb:15,48`); no scout gem defines them. Correcting attribution: rbbt-util, and it is *not* a declared dependency of essentials.
107
+ 5. **`Bgzf` / `Open.bgunzip` implied to work** (`SE/lib/scout/open/util.rb:35`): `Bgzf` is defined only in **legacy rbbt-util** (`RU/lib/rbbt/util/misc/bgzf.rb:5`). Correcting attribution: rbbt-util, undeclared.
108
+ 6. **`deep_indifferent` (and the "call it explicitly" guidance)** — `SE/doc/Improvements.md:85-89`, plus the AnnotatingData ledger's `Misc.deep_indifferent` reference: **no implementation exists** in any audited repo (0 grep hits for `def deep_indifferent` across essentials, gear, ai, rig, camp, rbbt-util 6.0.5). Not merely misattributed — **NOT_FOUND**. The nested-IndiferentHash claim in the user docs (ledger C17) is correspondingly wrong.
109
+ 7. **"Foundation upon which Scout and Scout-AI are built"** (`SE/doc/StartHere.md:5`): correct in substance (see verdict) but should be qualified — scout-ai depends on it only *transitively* (via scout-rig, whose own gemspec omits the essentials edge), and essentials itself silently *depends upward* on scout-gear types (TSV, Step) and legacy rbbt-util methods for items 1-5.
110
+ 8. **Scheduler/HPC/"scout camp" style orchestration** — no essentials doc makes this claim (grep found none), but for completeness of the attribution ledger: all such functionality is in **scout-gear** (`workflow/deployment/scheduler/{slurm,pbs,lfs}.rb`, `orchestrator/`) with **scout-camp** adding cloud/offsite/terraform layers; nothing of it lives in essentials.
111
+
112
+ ## UNVERIFIED items
113
+
114
+ - **rbbt-util dependency direction (gemspec level)** — `rbbt-util-6.0.5`'s installed tree has no readable gemspec at the audited path, so I could not confirm whether rbbt-util *declares* scout-essentials/scout-gear as runtime deps (the `require_instead` shim mechanism suggests intent, but the edge is UNVERIFIED).
115
+ - **`rake_dirs` external consumers** — verified essentials implements it and gear does not use it; whether any *user* workflow repos use `:rake` claims could not be checked (outside audited set).
116
+ - **`NamedArray#setup` `key:` kwarg semantics** (ledger C13) — orthogonal to attribution, still unresolved in Phase 2; not re-attempted here.
117
+ - **Camp "job arrays"** — I located SLURM/PBS/LFS in gear and the camp command dirs (`offsite/sync/terraform`); I did not exhaustively enumerate every camp subcommand or array-job helper, so any *specific* "job array" implementation claim in camp remains UNVERIFIED.
118
+ - **GitHub/upstream repositories** — Scout-Camp and Scout-Rig were NOT present as local git worktrees in this sandbox (only their installed gem copies), so file:line citations for them are from gems, which may lag their git heads. Marked as gem-snapshot evidence above.