scout-essentials 1.9.1 → 1.9.3
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.
- checksums.yaml +4 -4
- data/.vimproject +18 -1
- data/Rakefile +9 -0
- data/VERSION +1 -1
- data/doc/Improvements.md +20 -0
- data/doc/StartHere.md +7 -4
- data/doc/developer/AnnotationSystem.md +10 -15
- data/doc/developer/Configuration.md +20 -30
- data/doc/developer/CoreUtilities.md +52 -22
- data/doc/developer/DesignPrinciples.md +7 -9
- data/doc/developer/DocumentationPractices.md +476 -0
- data/doc/developer/ErrorHandling.md +11 -20
- data/doc/developer/LockingAndConcurrency.md +20 -13
- data/doc/developer/StreamingModel.md +13 -14
- data/doc/developer/Testing.md +389 -0
- data/doc/user/AnnotatingData.md +9 -13
- data/doc/user/CommandLineOptions.md +9 -12
- data/doc/user/Cookbook.md +7 -10
- data/doc/user/HandlingStreams.md +22 -29
- data/doc/user/LoggingAndProgress.md +9 -11
- data/doc/user/ProducingResources.md +0 -1
- data/doc/user/RemoteData.md +10 -13
- data/doc/user/RunningCommands.md +12 -17
- data/doc/user/WorkingWithFiles.md +10 -2
- data/lib/scout/config.rb +1 -0
- data/lib/scout/indiferent_hash.rb +8 -2
- data/lib/scout/log/fingerprint.rb +1 -1
- data/lib/scout/named_array.rb +4 -0
- data/lib/scout/open/final.rb +1 -1
- data/lib/scout/open/stream.rb +14 -13
- data/lib/scout/path/util.rb +1 -1
- data/lib/scout/testing/availability.rb +264 -0
- data/lib/scout/testing/scout_state.rb +193 -0
- data/lib/scout/testing.rb +82 -0
- data/research/doc-audit-findings.md +7 -5
- data/research/doc-structure.md +89 -0
- data/research/lock-namespace-blindness.md +73 -0
- data/research/probing-guide.md +311 -0
- data/research/testing-availability-scout-ai-handoff.md +235 -0
- data/research/testing-availability-scout-rig-handoff.md +355 -0
- data/scout-essentials.gemspec +24 -3
- data/test/scout/annotation/test_dup_clone.rb +31 -0
- data/test/scout/indiferent_hash/test_case_insensitive_staleness.rb +27 -0
- data/test/scout/indiferent_hash/test_string2hash_false.rb +13 -0
- data/test/scout/misc/test_timespan.rb +30 -0
- data/test/scout/open/test_compression_detection.rb +32 -0
- data/test/scout/open/test_forced_gzip_broken.rb +25 -0
- data/test/scout/test_named_array_resolution.rb +49 -0
- data/test/scout/test_testing.rb +195 -0
- data/test/scout/test_tmpfile_cleanup.rb +23 -0
- data/test/scout/testing/test_availability.rb +222 -0
- data/test/scout/testing/test_state_bindings.rb +163 -0
- data/test/test_helper.rb +26 -12
- metadata +23 -2
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
# Documentation Practices
|
|
2
|
+
|
|
3
|
+
This page defines the normative practices for running documentation
|
|
4
|
+
campaigns in the Scout-AI ecosystem (`scout-essentials`, `scout-gear`,
|
|
5
|
+
`scout-ai`) with CortexCoder, Cortex, and ChatAnalyst. It is written for
|
|
6
|
+
campaign Managers planning a documentation review, CortexCoder agents owning
|
|
7
|
+
a subsystem study, Critic/verifier agents gating results, and any agent
|
|
8
|
+
resuming an interrupted campaign. Every practice below carries an evidence
|
|
9
|
+
label; thin or single-reviewer evidence is marked as such and never
|
|
10
|
+
presented as verified. The content is promoted from
|
|
11
|
+
`research/doc-practices-guide.md`, which was retired after Critic-verified
|
|
12
|
+
promotion; its unique associated-elements inventory is preserved in the
|
|
13
|
+
Cortex closure artifact `doc-guide/promotion-record.md`.
|
|
14
|
+
|
|
15
|
+
## Notation and evidence grades
|
|
16
|
+
|
|
17
|
+
Citations use the compact session keys below. Each advice item carries a
|
|
18
|
+
grade marker showing how strong its evidence is; **no item's grade may be
|
|
19
|
+
read as stronger than marked**.
|
|
20
|
+
|
|
21
|
+
- `[V]` — the cited message was re-read directly (via
|
|
22
|
+
`message_index`/`message_content` or the persisted chat) by the round-3
|
|
23
|
+
extraction that produced this page.
|
|
24
|
+
- `[R1]` — inherited from a round-1 review artifact; not re-verified.
|
|
25
|
+
- `[R2]` — inherited from the round-2 area map; not re-verified.
|
|
26
|
+
- `[REPT]` — ChatAnalyst report accounting only. Reported magnitudes,
|
|
27
|
+
never practice. In particular, token totals are accounting figures; do
|
|
28
|
+
not cite them as behavioral evidence.
|
|
29
|
+
|
|
30
|
+
Session keys (root chats): `ES-ROOT#N`, `GE-ROOT#N`, `SA-ROOT#N` are
|
|
31
|
+
message *N* of the scout-essentials, scout-gear, and scout-ai root
|
|
32
|
+
sessions. Society refs `<root>.files/agent.society/<Agent>/<conversation>/
|
|
33
|
+
agent.chat#N` are abbreviated: essentials `AH` (essentials-annotation-hash),
|
|
34
|
+
`OCS` (essentials-open-cmd-stream), `PRL`
|
|
35
|
+
(essentials-persist-resource-log), `COF` (cortexcoder_open_finish), `OCC`
|
|
36
|
+
(open_cmd_closeout); gear `SCC` (sgdoc-conc), `SKB` (sgdoc-kb), `DC-cli`
|
|
37
|
+
(docconsol-cli), `GE` (gear_entity), `STUB(<conv>)`; scout-ai `SAD`
|
|
38
|
+
(scoutai-subsys-agent-delegation), `STATE` (docrev-state), `B0B4`/`B5B7`
|
|
39
|
+
(docrev-exec-*), `CRITIC` (Critic/docrev-verify), `SUBSYS-*`, `PROMO-*`.
|
|
40
|
+
|
|
41
|
+
## Areas
|
|
42
|
+
|
|
43
|
+
### A1 — Campaign architecture: phases, root-vs-society, actor roles
|
|
44
|
+
|
|
45
|
+
Split the campaign into phases with distinct modes and name the boundary
|
|
46
|
+
explicitly. Study phases operate under an explicit "do not modify the
|
|
47
|
+
repository" scope and flip to writes only after a state probe confirms
|
|
48
|
+
authority (SA-ROOT#11 [V]); a phase-1 scope that leaves README untouched is
|
|
49
|
+
a *task scope*, not an omission, and is lifted only by user authorization
|
|
50
|
+
(GE-ROOT#146→#147 [V]).
|
|
51
|
+
|
|
52
|
+
- Do: make one delegation unit = one subsystem in one named conversation;
|
|
53
|
+
dispatch in waves and verify artifacts with `cortex_list` before
|
|
54
|
+
consolidating (SA-ROOT#11 [V]; R1-s D2 [R1]).
|
|
55
|
+
- Do: keep the roles distinct — Manager plans, CortexCoder owns subsystems,
|
|
56
|
+
Critic gates, Searcher/Worker support (SA-ROOT#68 [V]).
|
|
57
|
+
- Don't: keep the root hands-on once work parallelizes. A root doing the
|
|
58
|
+
first investigation itself is fine for a single artifact, but parallel
|
|
59
|
+
branches dispatched outside the parent transcript leave provenance gaps
|
|
60
|
+
(see A8) — this is what happened in the essentials session
|
|
61
|
+
(ES-ROOT#118/#148 [V]).
|
|
62
|
+
|
|
63
|
+
Context: the practice evolved across the three sessions — essentials
|
|
64
|
+
pioneered a root-hands-on root, gear systematized Manager-run batch
|
|
65
|
+
campaigns plus promotion, scout-ai governed phases behind independent
|
|
66
|
+
verification.
|
|
67
|
+
|
|
68
|
+
### A2 — Docs-first, gap-derived, labeled evidence
|
|
69
|
+
|
|
70
|
+
Review maintained documentation and prior Cortex artifacts *first*, derive
|
|
71
|
+
an explicit gap list, and probe only genuine gaps (STUB(gear_cli)#11 [V];
|
|
72
|
+
GE-ROOT#51 [R1]; SA-ROOT#11 [V]). Never probe what the documentation
|
|
73
|
+
already answers.
|
|
74
|
+
|
|
75
|
+
- Do: label every statement `[doc]`/`[code]`/`[verified]` (plus
|
|
76
|
+
`[artifact]` when prior Cortex work exists) and list doc–code mismatches
|
|
77
|
+
separately with `file:line`. The labels are what make later promotion
|
|
78
|
+
auditable (SA-ROOT#11 [V]).
|
|
79
|
+
- Do: make claims executable: each claim cites an executed probe and its
|
|
80
|
+
property-job receipt, never a transcribed number — "Claims must come from
|
|
81
|
+
executed probes, not transcription" was hard-coded in every essentials
|
|
82
|
+
packet (AH#11 [V]); the finished form is a bold claim plus an Evidence
|
|
83
|
+
paragraph naming result keys and the receipt JSON (ES-ROOT#118 [V]).
|
|
84
|
+
- Do: treat a probe failure as a bug in the probe — fix and re-run, never
|
|
85
|
+
transcribe. The annotation branch looped a failing probe through twelve
|
|
86
|
+
versions until it ran (AH#299/#305→#481 [V]); a delegation study drove
|
|
87
|
+
its probe past v40 the same way (SAD#768/#769 [V]).
|
|
88
|
+
- Don't: write unlabeled claims into durable artifacts (SA-ROOT#11 [V]).
|
|
89
|
+
|
|
90
|
+
### A3 — Delegation packets, parallel ownership, reconciliation
|
|
91
|
+
|
|
92
|
+
- Do: send five-section self-contained packets — Introduction / Current
|
|
93
|
+
state / Design / Task / Output required — carrying the checkout path,
|
|
94
|
+
HEAD, existing artifacts *with versions*, property digest, naming
|
|
95
|
+
prefixes, and a structured reply contract (AH#11 [V]; SA-ROOT#11 [V];
|
|
96
|
+
GE-ROOT#55 [V], #219 [V]). Self-contained packets are what make stub
|
|
97
|
+
re-dispatch cheap (A5).
|
|
98
|
+
- Do: name one canonical exemplar as the template and partition claim
|
|
99
|
+
territory. Every essentials packet named `path_map.md` the
|
|
100
|
+
"citation-style exemplar" and forbade duplicating it (AH#11 [V]);
|
|
101
|
+
scout-ai packets listed persisted siblings as "read if useful for
|
|
102
|
+
cross-refs, do not duplicate" plus explicit ownership splits
|
|
103
|
+
(R1-s D3 [SA-ROOT#18/#24/#27/#33/#35] [R1]).
|
|
104
|
+
- Do: run parallel owners in batches of three with a cross-edit
|
|
105
|
+
prohibition, and appoint a dedicated reconciler for shared files. The
|
|
106
|
+
reconciler reviews every modified path, fixes shared files, stages and
|
|
107
|
+
commits without pushing (GE-ROOT#51/53/55 [V]; GE-ROOT#219 [V]).
|
|
108
|
+
- Do: serialize long delegations that share an uncommitted tree (gear
|
|
109
|
+
phase-2 ran one at a time against survey-listed conflict risks — R1-g F7
|
|
110
|
+
[R1]); split big close-outs into ordered packets (scout-ai B0–B4 then
|
|
111
|
+
B5–B7, SA-ROOT#61 [V]).
|
|
112
|
+
- Do: for a dead branch, send a closeout-only packet that says "Do NOT redo
|
|
113
|
+
exploration", enumerates the existing probes *and their receipts*, and
|
|
114
|
+
includes a scratch-cleanup step (ES-ROOT#301 [V]).
|
|
115
|
+
- Don't: re-type an "established pattern (follow it exactly)" block into
|
|
116
|
+
every packet — that is a brief's job. The essentials campaign paid that
|
|
117
|
+
cost three times (AH#11, OCS#11, PRL#11 [V]; see A7).
|
|
118
|
+
|
|
119
|
+
### A4 — Persistence ladder & promotion governance
|
|
120
|
+
|
|
121
|
+
The persistence ladder as practiced: `tmp/` scratch → `probe/` Cortex
|
|
122
|
+
artifacts → claims artifacts → `research/` → `doc/`, with scratch deleted
|
|
123
|
+
after promotion (ES-ROOT#82→#88, #141–#143 [R1]; explicit
|
|
124
|
+
probe→`research/`→`doc/` mapping GE-ROOT#148 [R1]; scout-ai B-series
|
|
125
|
+
SA-ROOT#61 [V]).
|
|
126
|
+
|
|
127
|
+
- Do: promote into `doc/` only under explicit user authorization
|
|
128
|
+
(GE-ROOT#147 [V]).
|
|
129
|
+
- Do: survey before batching. One bounded read-only recon (git state,
|
|
130
|
+
per-subsystem status, conflict risks) can re-cut the whole plan — the
|
|
131
|
+
gear survey found all eight subsystems already had uncommitted doc edits
|
|
132
|
+
and shrank the batches accordingly (GE-ROOT#198/#199 [R1]; final report
|
|
133
|
+
GE-ROOT#225 [V]); in scout-ai the read-only state probe overturned the
|
|
134
|
+
inventory with 20/21 doc files already edited (SA-ROOT#61 [V]).
|
|
135
|
+
- Do: distill before delete. Retirement is a transfer: bounded diff of the
|
|
136
|
+
doomed file against `doc/` and the probe-verified counterpart, promote
|
|
137
|
+
the unique verified facts, drop the unverifiable, then delete
|
|
138
|
+
(SA-ROOT#61 B3/B4/B5 [V]; R1-s D7 [R1]).
|
|
139
|
+
- Do: use decision-bound retention instead of blanket deletion: when a
|
|
140
|
+
file's fate belongs to an open decision, keep it, add a dated retention
|
|
141
|
+
note, and flag it for the maintainer in the final report
|
|
142
|
+
(SA-ROOT#83 [V]).
|
|
143
|
+
- Do: pre-plan commit granularity and per-batch invariants; one retirement
|
|
144
|
+
per commit; deletions never mixed into the commit under test
|
|
145
|
+
(SA-ROOT#61 [V]). Name the record of truth per change class — the
|
|
146
|
+
Improvements ledger records promotions, commit messages record
|
|
147
|
+
retirements — and state the split explicitly (SA-ROOT#69 [V];
|
|
148
|
+
SA-ROOT#83 [V]).
|
|
149
|
+
- Do: route live code defects to the issue ledger with receipts, never
|
|
150
|
+
document broken behavior as normal (SCC#84 [V]); the gear campaign filed
|
|
151
|
+
48 receipt-cited items with 6 deliberate non-filings, each with a
|
|
152
|
+
recorded reason (GE-ROOT#225 [V]).
|
|
153
|
+
- Do: delete scratch after promotion, verifying each file is superseded
|
|
154
|
+
and leaving unrelated pre-existing `tmp/` material alone (81 files
|
|
155
|
+
deleted, older material kept; ES-ROOT#310 [V]).
|
|
156
|
+
|
|
157
|
+
### A5 — Interruption, hang, and recovery
|
|
158
|
+
|
|
159
|
+
Distinguish two failure families. A native-blocking hang (`sem_wait`,
|
|
160
|
+
fork+join, cross-pipe misuse) returns `exit_status:-1` with null stdout
|
|
161
|
+
only after the ~3600 s harness kill (SCC#83 [V]); a Cortex-side property
|
|
162
|
+
exception returns fast and is not a hang (OCS#507 [V]).
|
|
163
|
+
|
|
164
|
+
- Do: diagnose a suspect probe standalone under a short shell timeout
|
|
165
|
+
before re-executing it through the entity property (COF#11 step 2 [R1]).
|
|
166
|
+
- Do: salvage dead branches with a bounded closeout packet, never
|
|
167
|
+
re-exploration (ES-ROOT#301 [V]; outcome ES-ROOT#310 [V]).
|
|
168
|
+
- Do: on interruption, trust durable artifacts over memory — "a missing
|
|
169
|
+
transcript cannot establish execution; recover confidence from durable
|
|
170
|
+
outputs" (SA-ROOT#47 [V]) — and resume a mutation campaign only after a
|
|
171
|
+
fresh read-only state probe that closes every planning gap and persists
|
|
172
|
+
a state artifact (STATE#13 [V]; outcome SA-ROOT#83 [V]).
|
|
173
|
+
- Expect: zero-tool stub residue. A dispatch lands (full packet, zero
|
|
174
|
+
inferences, zero tool calls) and execution never starts; the gear
|
|
175
|
+
campaign accumulated five such conversations before the subsystem landed
|
|
176
|
+
elsewhere (STUB(gear_cli)#11 [V] + chat_overview [V]; R1-g F2 [R1]).
|
|
177
|
+
Recovery is re-dispatch — cheap precisely because packets are
|
|
178
|
+
self-contained.
|
|
179
|
+
|
|
180
|
+
### A6 — Artifact iteration & honest-limits hygiene
|
|
181
|
+
|
|
182
|
+
- Do: write once, refine with small targeted edits, and treat version
|
|
183
|
+
history as the audit trail (`path_map.md` v4 — ES-ROOT#118 [V] plus
|
|
184
|
+
three edits [R1]; annotation claims v6 with one probe at v12 — AH#481
|
|
185
|
+
[V]; scout-ai state artifact closed v3 [R1]).
|
|
186
|
+
- Do: re-read the current version before `cortex_edit` — editing from
|
|
187
|
+
memory fails with "text not found" (ES-ROOT#127/#128 [V]; recovered by
|
|
188
|
+
re-read then re-edit [R1]).
|
|
189
|
+
- Do: encode environment and divergences inside the deliverable: record
|
|
190
|
+
which version observations ran against (installed 1.9.0 gem vs 1.9.1
|
|
191
|
+
checkout in the essentials closeout) and keep both values as citation
|
|
192
|
+
when a sandbox diverges from the host (ES-ROOT#310 [V]).
|
|
193
|
+
- Do: ship a "not probed / why" section in every branch report (AH#481 §5
|
|
194
|
+
[V]; PRL#1661 §5 [V]) and declare missing result keys instead of
|
|
195
|
+
inventing values — demand this verbatim in closeout packets
|
|
196
|
+
(ES-ROOT#301 [V]).
|
|
197
|
+
- Do: strike through stale lines rather than deleting them
|
|
198
|
+
(SA-ROOT#81 [R1]); leave `# ScoutCoder:` gap notes in code where
|
|
199
|
+
undocumented behavior was discovered, as `Observation/probe` does for the
|
|
200
|
+
undocumented `.meta` sidecar layout (ES-ROOT#100 [V]).
|
|
201
|
+
|
|
202
|
+
### A7 — Cortex workspace usage patterns
|
|
203
|
+
|
|
204
|
+
- Do: recall first at every phase boundary — `cortex_list`/`cortex_search`
|
|
205
|
+
before any dispatch (GE-ROOT#3–#49 [R1]; R1-s [R1]; SA-ROOT#47 [V]).
|
|
206
|
+
- Do: recall before recompute: when a prior campaign already covered 7/8
|
|
207
|
+
subsystems, re-scope the "fresh" instruction to gap-filling and do not
|
|
208
|
+
attribute orphaned prior work to the current campaign (GE-ROOT#42 [V]).
|
|
209
|
+
- Do: use named conversations as durable delegation units and *reuse* an
|
|
210
|
+
executor conversation for follow-ups that depend on its accumulated
|
|
211
|
+
conventions — a one-line repair went back to `docrev-exec-b5b7` "because
|
|
212
|
+
it knows the pattern" (R1-s D9 [SA-ROOT#70/#71/#80] [R1]).
|
|
213
|
+
- Do: define one shared entity property as the probe interface and treat
|
|
214
|
+
the `properties/` registry as the execution ledger: define once with a
|
|
215
|
+
smoke test (ES-ROOT#100 [V]), share across branches, mine for diagnosis
|
|
216
|
+
("28/28 entries", COF#16/#17 [R1]).
|
|
217
|
+
- Don't: confuse the `ask` tool's `conversation` parameter with the Cortex
|
|
218
|
+
conversations namespace — verify deliverables via `cortex_list prefix=`
|
|
219
|
+
(R1-g F3 [R1]). Don't invoke `Agent/<brief>` before the brief exists
|
|
220
|
+
("No briefs exist yet"; fall back to the default agent)
|
|
221
|
+
(GE#217/#219 [V]).
|
|
222
|
+
- Gap (thin): the essentials campaign created no conversations, briefs, or
|
|
223
|
+
lists and re-typed its "established pattern" into every packet (AH#11
|
|
224
|
+
[V]) where a brief would have been cheaper. The brief counterfactual
|
|
225
|
+
lives in `guides/briefer-usage.md` and
|
|
226
|
+
`harness/briefed-agent-efficiency.md` — cross-linked artifacts, not chat
|
|
227
|
+
evidence of these campaigns.
|
|
228
|
+
|
|
229
|
+
### A8 — Provenance, receipts, attribution
|
|
230
|
+
|
|
231
|
+
- Do: route delegation through recorded `ask` calls so `agent_meta`
|
|
232
|
+
receipts survive. The gear root's 12 asks embed 3,111 receipt records
|
|
233
|
+
deduplicated against saved society logs with 0 conflicts (R1-g [R1]);
|
|
234
|
+
scout-ai's asks each returned receipts and the Critic job produced the
|
|
235
|
+
session's single `agent_job` edge (R1-s [R1]).
|
|
236
|
+
- Don't: dispatch work outside the persisted transcript. The essentials
|
|
237
|
+
root launched its three parallel branches and the first closeout with no
|
|
238
|
+
manager turn around them — only one `ask` is recorded (ES-ROOT#301 [V])
|
|
239
|
+
— leaving 54.97 M of the 59.09 M reported tokens unattributed to any
|
|
240
|
+
recorded delegation (chat_report [REPT V]; R1-e D12 [R1]).
|
|
241
|
+
- Do: cite `property_job` receipts (JSON name + definition version +
|
|
242
|
+
digest) per claim; this is the citation currency of every claims
|
|
243
|
+
artifact (ES-ROOT#118 [V]; "Never transcribe numbers without a probe
|
|
244
|
+
receipt", GE-ROOT#51 [R1]).
|
|
245
|
+
- Do: audit citation integrity against the registry before closing —
|
|
246
|
+
"verify every number you write against the actual artifacts/registry";
|
|
247
|
+
the gear synthesis chased a reported sixth mismatch into a transcription
|
|
248
|
+
error and closed at 238 claims / 101 unique receipts / 0 broken
|
|
249
|
+
(GE-ROOT#146 [V]; R1-g D2 [R1]).
|
|
250
|
+
- Expect: interrupted runs drop root segments while society children
|
|
251
|
+
persist — a 14-message zero-call stub holding the full packet
|
|
252
|
+
(PROMO-DOC#13 [V], chat_overview [V]) with no root ask for it
|
|
253
|
+
(R1-s F4 [R1]). Budget a state-reconstruction pass; durable artifacts
|
|
254
|
+
make it cheap (A5).
|
|
255
|
+
|
|
256
|
+
### A9 — Sandbox, path, and exec friction
|
|
257
|
+
|
|
258
|
+
- Do: state the concrete `/bulk/...` checkout path in packets. `~/git/<repo>`
|
|
259
|
+
does not exist inside bwrap while doc tooling advertises it; the
|
|
260
|
+
divergence is resolved with hypothesis-driven probes plus the designated
|
|
261
|
+
`sandbox_paths` diagnostic (STATE#19/#20/#30/#31 [V]); subsequent
|
|
262
|
+
packets carried the `/bulk` path explicitly (R1-s F1 [R1]).
|
|
263
|
+
- Do: reformulate bash commands against sandbox-visible paths when bwrap
|
|
264
|
+
rejects one (`exit_status:-1`, "Process failed - bwrap …" on an
|
|
265
|
+
unreadable target; DC-cli#77 [V], #1040 [V]). Redirect-heavy one-liners
|
|
266
|
+
fail more often under bwrap (R1-s F2 [R1]).
|
|
267
|
+
- Do: guard every risky subprocess with a shell `timeout`, and know the
|
|
268
|
+
limit: a SIGTERM-based timeout does not interrupt native-blocking
|
|
269
|
+
primitives (`sem_wait` on a missing name), so a "fixed" script can still
|
|
270
|
+
burn the full 3600 s (SCC#82/#83 [V]; use `timeout -s KILL` per R2 §2a
|
|
271
|
+
[R2]). The full friction table is under *Friction table* below.
|
|
272
|
+
|
|
273
|
+
### A10 — Cost, cache, and context economics
|
|
274
|
+
|
|
275
|
+
All totals in this section are `[REPT]` accounting — reported magnitudes,
|
|
276
|
+
never practice (see *Caveats and evidence limits*). Only the essentials
|
|
277
|
+
total was re-anchored: deduplicated 59,088,981 tt (pt 58,627,925; cct
|
|
278
|
+
56,841,472 ≈ 96.9 % of pt) over 1,346 calls / 990 events (chat_report +
|
|
279
|
+
chat_tool_calls [REPT V]). scout-ai ≈ 460.6 M tt over 6,989 calls
|
|
280
|
+
[REPT R1]; gear ≈ 775 M tt over 12,254 calls [REPT R1] — both inherited,
|
|
281
|
+
not re-anchored.
|
|
282
|
+
|
|
283
|
+
- Do: prefer cheap local probes; `bash` dominates every session's tool mix
|
|
284
|
+
(essentials 877/1,346 [REPT V]; gear 9,502/12,254 [R1]) and long
|
|
285
|
+
cache-friendly conversations made the economics work.
|
|
286
|
+
- Do: read artifacts page-wise even mid-task — every scout-ai consumption
|
|
287
|
+
used `cortex_read` with `start_line`/`lines` (SUBSYS-INDEX#16/#34 [V];
|
|
288
|
+
R1-s D12 [R1]).
|
|
289
|
+
- Don't: grow single-branch chats without bound — one branch reached 1,662
|
|
290
|
+
messages / 550 calls [R1], the shape that invites compaction (inferred
|
|
291
|
+
risk; no compaction events were observed — R1-e D11 [R1]).
|
|
292
|
+
|
|
293
|
+
### A11 — Campaign outputs & verification gates
|
|
294
|
+
|
|
295
|
+
- Do: independently re-verify load-bearing claims with fresh minimal code
|
|
296
|
+
in an isolated process before finalizing (ES-ROOT#136/#137 [V]; reported
|
|
297
|
+
at ES-ROOT#148 [V]).
|
|
298
|
+
- Do: gate the final report behind an independent verifier with concrete
|
|
299
|
+
numbered checks, its own methods, a PASS/NEEDS_WORK/BLOCKED verdict, and
|
|
300
|
+
a required "smallest next action". The scout-ai Critic found one real
|
|
301
|
+
defect (13 stale gemspec entries the promotion commits had missed); the
|
|
302
|
+
Manager chose the smallest repair, sent it to the same executor
|
|
303
|
+
conversation, and re-validated (SA-ROOT#68/#69/#72 [V]). Don't expand
|
|
304
|
+
repair scope beyond the verdict.
|
|
305
|
+
- Do: run the test suite and record counts per subsystem before commit —
|
|
306
|
+
including an explicit "no dedicated suite exists" — then review every
|
|
307
|
+
modified path, reconcile shared files, stage intentionally, commit
|
|
308
|
+
without pushing (GE-ROOT#219 [V]; GE-ROOT#225 [V]; R1-g D7 [R1]).
|
|
309
|
+
- Do: audit citation integrity as a closing gate (GE-ROOT#146 [V]) and let
|
|
310
|
+
the verifier expose implicit batch invariants — the gemspec defect
|
|
311
|
+
existed because "deletion must sweep file lists" had never been written
|
|
312
|
+
down (SA-ROOT#69 [V]); encode it as a standard retirement step
|
|
313
|
+
(R1-s F5 [R1]).
|
|
314
|
+
|
|
315
|
+
Context: the verification practice progressed from essentials' spot
|
|
316
|
+
re-verification, to gear's reconciler audit plus test gate, to scout-ai's
|
|
317
|
+
independent Critic with a smallest-repair loop.
|
|
318
|
+
|
|
319
|
+
## Failure-mode playbook
|
|
320
|
+
|
|
321
|
+
**Native-blocking hang: shell timeout is insufficient.** A planning
|
|
322
|
+
discipline that mandates `timeout 15` wrappers on forking probes
|
|
323
|
+
(SCC#60 [V]) is necessary but not sufficient. In the gear chain, a guard
|
|
324
|
+
with `timeout 120` correctly tripped (`exit=124`) and let the agent
|
|
325
|
+
diagnose its own cross-pipe bug (SCC#76/#77 [V], #78 [V]); after the fix,
|
|
326
|
+
`timeout 180` still hung for the hour — `exit_status:-1`, null stdout,
|
|
327
|
+
bwrap "exceeded 3600 seconds", timestamps 19:43:02.736→20:43:02.778
|
|
328
|
+
(SCC#82/#83 [V]) — because the blocker was
|
|
329
|
+
`ScoutSemaphore.wait_semaphore` (native `sem_wait`), which SIGTERM cannot
|
|
330
|
+
interrupt. Recipes: short `timeout` per invocation; `timeout -s KILL` for
|
|
331
|
+
native blockers [R2]; read `exit_status:-1` + null stdout + a ~3600 s
|
|
332
|
+
timestamp gap as a harness kill, not a quick failure.
|
|
333
|
+
|
|
334
|
+
**Hang recovery: staged, time-boxed, split probes.** After a kill returns,
|
|
335
|
+
wrap each individual script in its own timeout and split verification into
|
|
336
|
+
small scripts so one hang does not block everything: a deterministic
|
|
337
|
+
script with no waits plus a time-boxed script with waits — "even if B
|
|
338
|
+
hangs, A's evidence is in hand" (SCC#84 [V]). Then `pkill` cleanup and a
|
|
339
|
+
`/dev/shm` semaphore check — both themselves messy under bwrap (exit 143;
|
|
340
|
+
SCC#85/#86 [V]). Inbox advice posted during a blocked call is delivered
|
|
341
|
+
only when the call returns; it cannot interrupt it
|
|
342
|
+
(harness-findings/bash-tool-blocking-hangs.md [R1], cited for taxonomy
|
|
343
|
+
only — its message indices are unreliable; see caveats).
|
|
344
|
+
|
|
345
|
+
**Zero-tool stub residue.** A dispatch lands (full packet, zero
|
|
346
|
+
inferences, zero calls) and execution never starts; the gear campaign
|
|
347
|
+
accumulated five such conversations (STUB(gear_cli)#11 [V]; R1-g F2
|
|
348
|
+
[R1]). Recovery is re-dispatch. Whether stub prompts ever reached a model
|
|
349
|
+
anywhere is unproven (see caveats).
|
|
350
|
+
|
|
351
|
+
**Interruption resume: state artifacts plus read-only state probe.** On
|
|
352
|
+
"You were interrupted, please finish up" with no memory of prior work:
|
|
353
|
+
recall the workspace (SA-ROOT#47 [V]), run a dedicated read-only probe
|
|
354
|
+
closing every planning gap (git state, checkout identity, byte counts,
|
|
355
|
+
fix-sweep presence), persist a state artifact, and only then resume
|
|
356
|
+
mutation (STATE#13 [V]; outcome SA-ROOT#83 [V]). In scout-ai this probe
|
|
357
|
+
overturned the inventory — 20/21 doc files were already edited
|
|
358
|
+
(SA-ROOT#61 [V]).
|
|
359
|
+
|
|
360
|
+
**Dead-branch closeout.** Diagnose from the registry (executions with
|
|
361
|
+
forced updates, no claims artifact), then send a closeout-only packet: "Do
|
|
362
|
+
NOT redo exploration", enumerate probes and receipts, require scratch
|
|
363
|
+
cleanup with per-file supersession checks (ES-ROOT#301 [V];
|
|
364
|
+
ES-ROOT#310 [V]). Standalone-under-timeout diagnosis: COF#11 step 2 [R1].
|
|
365
|
+
|
|
366
|
+
**Cortex-side property exception is not a hang.** A fast-failing
|
|
367
|
+
`cortex_entity_property` ("Property execution raised: no implicit
|
|
368
|
+
conversion of Step into Integer", ~0.2 s, from the entity-task rescue)
|
|
369
|
+
blocked a branch at its tail (OCS#500→#507→#521 [V]). What happened after
|
|
370
|
+
the last fast edit (OCS#521, 18:14:05.202Z [V]) is unobservable in the
|
|
371
|
+
transcript (see caveats).
|
|
372
|
+
|
|
373
|
+
**Dispatch-provenance gap.** Work dispatched outside recorded calls leaves
|
|
374
|
+
society children hanging off log edges with no parent record and most cost
|
|
375
|
+
unattributable (essentials: 0 `agent_job` edges, 5 log edges, one recorded
|
|
376
|
+
`ask`; ES-ROOT#301 [V]; chat_report [REPT V]). Recovery is prevention:
|
|
377
|
+
route delegation through `ask`/`cortex_continue`.
|
|
378
|
+
|
|
379
|
+
## Friction table
|
|
380
|
+
|
|
381
|
+
First recovery move per observed harness/tool friction. Symptom → first
|
|
382
|
+
recovery move, with citation and grade:
|
|
383
|
+
|
|
384
|
+
| Friction | Symptom | First recovery move |
|
|
385
|
+
| --- | --- | --- |
|
|
386
|
+
| `search` path rejected | allowlist exception naming the path | `pwd`, then bash (ES-ROOT#16–#20 [R1]) |
|
|
387
|
+
| `cortex_edit` text-not-found | exception names the missing text | `cortex_read` current version, re-edit (ES-ROOT#127/#128 [V]) |
|
|
388
|
+
| `list_directory` recursive+stats | 142,439 chars truncated at the 100k cap | bash `ls`-based listing (COF#12/#13 [R1]) |
|
|
389
|
+
| `cortex_read type=properties` | ".json" suffix rejected | `cortex_list type=properties` prefix listing (OCC#68/#69, COF#16/#17 [R1]) |
|
|
390
|
+
| bwrap path divergence | `~/git/...` absent, `/home/.../git` ENOENT | hypothesis probes + `sandbox_paths`; carry the `/bulk` path in packets (STATE#19/#20/#30/#31 [V]) |
|
|
391
|
+
| bash under bwrap | `exit_status:-1`, "Process failed - bwrap…" | reformulate against sandbox-visible paths (DC-cli#77, #1040 [V]) |
|
|
392
|
+
| native-blocking hang | `-1` + null stdout after ~3600 s | `timeout -s KILL`; staged split probes (SCC#82/#83, #84 [V]) |
|
|
393
|
+
| `patch` auto-strip under bwrap | strips −p0…−p4 fail, hunk context lost | re-anchor the edit manually (DC-cli#1039/#1040 [R1]; tool identity not re-verified — see caveats) |
|
|
394
|
+
| test-unit multi-file | only the first file executes | one file per runner invocation (STATE#81–#85 [R1]) |
|
|
395
|
+
| `pkill` under bwrap | exit 143, argv elided | verify stray processes/semaphores another way (SCC#85/#86 [V]) |
|
|
396
|
+
| `Agent/<brief>` without a brief | "No briefs exist yet" | continue with the default agent (GE#217/#219 [V]) |
|
|
397
|
+
| ask-vs-Cortex namespace | "No conversation named …" | verify via `cortex_list prefix=` (R1-g F3 [R1]) |
|
|
398
|
+
|
|
399
|
+
## Caveats and evidence limits
|
|
400
|
+
|
|
401
|
+
These limits are Critic-mandated and binding on this page. They are limits
|
|
402
|
+
of the evidence, never practices to follow.
|
|
403
|
+
|
|
404
|
+
1. **Message-index mismatch in the bash-hang finding.**
|
|
405
|
+
`harness-findings/bash-tool-blocking-hangs.md` is cited on this page
|
|
406
|
+
**only for its hang taxonomy and its inbox-advice limit**; its message
|
|
407
|
+
indices are unreliable and must not be trusted: its `#52→#53` is in
|
|
408
|
+
fact a fast monitor.rb read pair and `#822→#823` a ConcurrencyModel
|
|
409
|
+
grep at ~22:47Z; the real hang chain is #76/#77 and #82/#83; the
|
|
410
|
+
claimed 22:54:57Z fifth hang was not located.
|
|
411
|
+
2. **Unresolvable artifact set.** The `scout-gear/*` campaign artifact set
|
|
412
|
+
(overview, 8 claims artifacts, consolidation series) is cited
|
|
413
|
+
throughout `GE-ROOT` and R1 but was **not resolvable via `cortex_list`
|
|
414
|
+
in this workspace's readable maps at extraction time** — verify with
|
|
415
|
+
`cortex_list prefix=` before reuse.
|
|
416
|
+
3. **The patch-tool friction row is R1-confirmed only.** The re-read of
|
|
417
|
+
DC-cli#1040 confirmed a bwrap process failure but not that the failing
|
|
418
|
+
tool was `patch`; the row stays `[R1]` and is not upgraded.
|
|
419
|
+
4. Token totals for scout-gear (~775 M tt) and scout-ai (~460.6 M tt) are
|
|
420
|
+
R1/REPT figures; only the essentials total was re-anchored (R2 §7.7).
|
|
421
|
+
Reported magnitudes only — never practice.
|
|
422
|
+
5. Open threads that stand from the round-2 map: the unobserved
|
|
423
|
+
post-#521 fate of the essentials Open/CMD/Stream branch; the sgdoc-kb
|
|
424
|
+
interior (structural only); whether stub prompts ever reached a model;
|
|
425
|
+
ES-ROOT#302 (552 KB) unread; PRL/AH/COF/OCC interiors R1-sampled only;
|
|
426
|
+
`Searcher/default` and `CortexCoder/default` structurally accounted
|
|
427
|
+
only.
|
|
428
|
+
6. The "no implicit conversion of Step into Integer" Cortex entity-task
|
|
429
|
+
error deserves its own harness finding (R2 §7.3) — it blocked a branch
|
|
430
|
+
and is distinct from the fork-hang family.
|
|
431
|
+
7. Essentials compaction risk is inferred, not observed (R1-e D11 [R1]).
|
|
432
|
+
|
|
433
|
+
## Provenance
|
|
434
|
+
|
|
435
|
+
This page is promoted from `research/doc-practices-guide.md` (491 lines),
|
|
436
|
+
which was retired in a follow-up commit after Critic-verified promotion
|
|
437
|
+
(931f728, PASS 97/100); retirement followed the distill-before-delete
|
|
438
|
+
rule in A4.
|
|
439
|
+
|
|
440
|
+
The guide was produced by a staged process: (1) a chat manifest fixed
|
|
441
|
+
session identity, path caveats, and the guide host repo; (2) three
|
|
442
|
+
independent ChatAnalyst reviewers — one per session, fresh conversations,
|
|
443
|
+
none seeing another's output — produced the round-1 reviews; (3) a distinct
|
|
444
|
+
round-2 mapper reconciled them into the eleven-area taxonomy (A1–A11) with
|
|
445
|
+
gap-closing direct reads; (4) a Critic gate passed that map (PASS, 88/100);
|
|
446
|
+
(5) a round-3 extraction (briefed CortexCoder, conversation `doc-guide`)
|
|
447
|
+
read the artifacts page-wise, spot-verified citations, and wrote the single
|
|
448
|
+
guide file. The guide itself was then Critic-reviewed (93/100) before this
|
|
449
|
+
promotion.
|
|
450
|
+
|
|
451
|
+
Working-review substrate (Cortex logical paths; working artifacts retained
|
|
452
|
+
for provenance, not re-inlined here): `doc-guide/chat-manifest.md`,
|
|
453
|
+
`doc-guide/R1-dimensions-essentials.md`,
|
|
454
|
+
`doc-guide/R1-dimensions-gear.md`,
|
|
455
|
+
`doc-guide/R1-dimensions-scout-ai.md`, `doc-guide/R2-area-map.md`,
|
|
456
|
+
`search/doc-guide-source-map.md`.
|
|
457
|
+
|
|
458
|
+
Underlying sessions (exact spellings; the `~/git/...` form does not
|
|
459
|
+
resolve):
|
|
460
|
+
|
|
461
|
+
- `~/chats/scout-essentials/cortex_coder` — 311 root msgs / 6 chats /
|
|
462
|
+
1,346 tool calls (09-07→09-08); pioneer root-hands-on campaign.
|
|
463
|
+
- `~/chats/scout-gear/cortex_coder` — 226 root msgs / 41 chats / 12,254
|
|
464
|
+
tool calls (09-08→09-10); Manager-orchestrated gap-fill then
|
|
465
|
+
user-authorized consolidation.
|
|
466
|
+
- `~/chats/scout-ai/cortex_coder` — 84 root msgs / 22 chats / 6,989 tool
|
|
467
|
+
calls (09-10→09-12); eleven labeled subsystem studies, interrupted
|
|
468
|
+
promotion, governed close-out (B0–B7, 22 commits) behind an independent
|
|
469
|
+
Critic gate.
|
|
470
|
+
|
|
471
|
+
Cross-linked campaign artifacts named by the guide (probe sets, claims
|
|
472
|
+
exemplars, harness findings, the scout-ai subsystem index and
|
|
473
|
+
promotion-state pair) remain in the Cortex workspace; the guide's
|
|
474
|
+
§17 associated-elements inventory is preserved verbatim in the Cortex
|
|
475
|
+
closure artifact `doc-guide/promotion-record.md` (campaign closure
|
|
476
|
+
record), which is the authoritative inventory.
|
|
@@ -9,8 +9,6 @@ file), `lib/scout/cmd.rb:22-31` (`CMD::Timeout`),
|
|
|
9
9
|
|
|
10
10
|
## The taxonomy
|
|
11
11
|
|
|
12
|
-
Verified by `tmp/rewrite_D/probe_14_exceptions_full.rb`:
|
|
13
|
-
|
|
14
12
|
```text
|
|
15
13
|
ScoutDeprecated parent=StandardError
|
|
16
14
|
ScoutException parent=StandardError
|
|
@@ -42,8 +40,7 @@ not in the raise/catch taxonomy at all.
|
|
|
42
40
|
|
|
43
41
|
Failures derive from `StandardError` and behave normally. Control-flow
|
|
44
42
|
signals derive straight from `Exception`, so a bare `rescue =>` (which
|
|
45
|
-
means `rescue StandardError`) will **not** see them
|
|
46
|
-
`probe_14`:
|
|
43
|
+
means `rescue StandardError`) will **not** see them:
|
|
47
44
|
|
|
48
45
|
```ruby
|
|
49
46
|
raise DontClose.new("payload")
|
|
@@ -51,7 +48,8 @@ raise DontClose.new("payload")
|
|
|
51
48
|
# rescue Exception -> catches
|
|
52
49
|
```
|
|
53
50
|
|
|
54
|
-
|
|
51
|
+
The rescue behaviour follows directly from the superclass — each of
|
|
52
|
+
them extends `Exception`, not `StandardError`:
|
|
55
53
|
|
|
56
54
|
```text
|
|
57
55
|
DontClose caught by 'rescue =>' => NOT caught; needs 'rescue Exception'
|
|
@@ -90,7 +88,7 @@ raised from `ConcurrentStream#join_threads` when a thread's value is a
|
|
|
90
88
|
`Process::Status` that did not succeed (unless `no_fail`).
|
|
91
89
|
|
|
92
90
|
`CMD::Timeout < ProcessFailed` (`lib/scout/cmd.rb:22-31`) adds `command`
|
|
93
|
-
and `timeout` readers and composes the message through `super
|
|
91
|
+
and `timeout` readers and composes the message through `super`:
|
|
94
92
|
|
|
95
93
|
```text
|
|
96
94
|
CMD::Timeout message => "Process 12 failed - command 'sleep 3 ' exceeded timeout of 0.1 seconds"
|
|
@@ -118,9 +116,8 @@ exception = (AbortedStream === content and content.exception) ? content.exceptio
|
|
|
118
116
|
```
|
|
119
117
|
|
|
120
118
|
So the caller of `sensible_write` sees the root cause that made the
|
|
121
|
-
producer abort, rather than a secondary wrapper
|
|
122
|
-
|
|
123
|
-
(`exists after Aborted => false`).
|
|
119
|
+
producer abort, rather than a secondary wrapper (`exists after Aborted
|
|
120
|
+
=> false`).
|
|
124
121
|
|
|
125
122
|
The `ensure` always removes the temp file and unlocks a held
|
|
126
123
|
`Lockfile` (`lib/scout/open/stream.rb:165-171`).
|
|
@@ -129,8 +126,6 @@ The `ensure` always removes the temp file and unlocks a held
|
|
|
129
126
|
|
|
130
127
|
`Misc.insist` (`lib/scout/misc/insist.rb`) retries the block while the
|
|
131
128
|
code inside raises `TryAgain`; `StopInsist` and `Aborted` break out.
|
|
132
|
-
Verified `tmp/rewrite_B/probe_01` / `probe_10_misc.rb` and
|
|
133
|
-
`tmp/rewrite_D/probe_04_exceptions.rb`:
|
|
134
129
|
|
|
135
130
|
```ruby
|
|
136
131
|
tries = 0
|
|
@@ -161,13 +156,11 @@ end
|
|
|
161
156
|
|
|
162
157
|
- `Persist.persist(..., :canfail => true)` — exceptions from the block are
|
|
163
158
|
caught; the persist file is removed and `nil` is returned
|
|
164
|
-
(`lib/scout/persist.rb:63-100`;
|
|
165
|
-
`persist canfail => nil`, `file removed: true`).
|
|
159
|
+
(`lib/scout/persist.rb:63-100`; `persist canfail => nil`, `file removed: true`).
|
|
166
160
|
- `ConcurrentStream` `no_fail: true` — a non-success
|
|
167
161
|
`Process::Status` from a joined thread is logged at low level instead of
|
|
168
162
|
raising `ConcurrentStreamProcessFailed`
|
|
169
|
-
(`lib/scout/concurrent_stream.rb:113
|
|
170
|
-
P26).
|
|
163
|
+
(`lib/scout/concurrent_stream.rb:113`).
|
|
171
164
|
- `CMD`'s `no_fail` similarly suppresses both `ProcessFailed` from a thread
|
|
172
165
|
join and exceptions during join.
|
|
173
166
|
|
|
@@ -181,11 +174,9 @@ happily write empty output unless the producer itself is aborted.
|
|
|
181
174
|
`ensure` that removes both. But cleanup is not uniform across the gem:
|
|
182
175
|
|
|
183
176
|
- **`TmpFile.with_file` leaks the temp file when the block raises** — there
|
|
184
|
-
is no `ensure` around the `yield`
|
|
185
|
-
|
|
186
|
-
`
|
|
187
|
-
present after the block raises `RuntimeError`). Wrap your own `begin/ensure`
|
|
188
|
-
around `with_file` if the block can fail.
|
|
177
|
+
is no `ensure` around the `yield` (`lib/scout/tmpfile.rb:34-48`); the
|
|
178
|
+
created tmp file is still present after the block raises `RuntimeError`.
|
|
179
|
+
Wrap your own `begin/ensure` around `with_file` if the block can fail.
|
|
189
180
|
- `Open.sensible_write` deletes the *target* on failure but only the
|
|
190
181
|
*temp* file, never other files written by a streaming producer.
|
|
191
182
|
- `Misc.insist` retries without undoing side effects already produced by
|
|
@@ -18,7 +18,7 @@ end
|
|
|
18
18
|
|
|
19
19
|
`Open.init_lock` (called when the file is loaded) overrides the library
|
|
20
20
|
defaults, so the effective settings here are **not** the upstream ones
|
|
21
|
-
|
|
21
|
+
:
|
|
22
22
|
|
|
23
23
|
| setting | library default | effective here |
|
|
24
24
|
|---|---|---|
|
|
@@ -34,7 +34,7 @@ attempts sleeps 4 s.
|
|
|
34
34
|
## What a lock looks like on disk
|
|
35
35
|
|
|
36
36
|
Acquiring `<file>` creates `<file>.lock` and a hidden sibling. The payload
|
|
37
|
-
written into the lock is four lines
|
|
37
|
+
written into the lock is four lines :
|
|
38
38
|
|
|
39
39
|
```text
|
|
40
40
|
/tmp/…/data.txt.lock contents:
|
|
@@ -53,6 +53,15 @@ temporary file in the same directory and then **hard-linked** to
|
|
|
53
53
|
recognise locks left behind by dead processes and remove them. Once the
|
|
54
54
|
original temp name is unlinked, `nlink` on the surviving `.lock` is 1.
|
|
55
55
|
|
|
56
|
+
The liveness test behind both `sweep` and the stale-age stealing is
|
|
57
|
+
`Process.kill(0, pid)` (`alive?`, `lib/scout/open/lock/lockfile.rb:379`),
|
|
58
|
+
which resolves pids in the **caller's PID namespace**. In a bubblewrap or
|
|
59
|
+
container namespace a host pid is simply absent ⇒ reported dead while its
|
|
60
|
+
process is alive ⇒ the lock can be swept or stolen from a live holder.
|
|
61
|
+
Never share a lock directory across PID namespaces: pin `HOME` (and the
|
|
62
|
+
`tmp` path map) per sandbox. See
|
|
63
|
+
[lock-namespace-blindness](../../research/lock-namespace-blindness.md).
|
|
64
|
+
|
|
56
65
|
## `Open.lock` and `LockInterrupted`
|
|
57
66
|
|
|
58
67
|
```ruby
|
|
@@ -75,7 +84,7 @@ raised over the block's own result.
|
|
|
75
84
|
## The three lock namespaces
|
|
76
85
|
|
|
77
86
|
Each subsystem locks in its own directory so the names never collide
|
|
78
|
-
|
|
87
|
+
:
|
|
79
88
|
|
|
80
89
|
| who | directory | name shape |
|
|
81
90
|
|---|---|---|
|
|
@@ -83,19 +92,19 @@ Each subsystem locks in its own directory so the names never collide
|
|
|
83
92
|
| `Resource#lock_dir` | `$HOME/.scout/tmp/produce_locks` | `TmpFile` digest of the resource path |
|
|
84
93
|
| `Open.sensible_write` | `$HOME/.scout/tmp/sensible_write_locks` | digest of the output path |
|
|
85
94
|
|
|
86
|
-
|
|
95
|
+
The three directories, with an example of each lock file name:
|
|
87
96
|
|
|
88
97
|
```text
|
|
89
98
|
Persist.lock_dir => $HOME/.scout/tmp/persist_locks
|
|
90
|
-
persist lock file => .../persist_locks
|
|
99
|
+
persist lock file => .../persist_locks/<key>.persist
|
|
91
100
|
Resource#lock_dir => $HOME/.scout/tmp/produce_locks
|
|
92
|
-
produce lock file => .../produce_locks
|
|
101
|
+
produce lock file => .../produce_locks/<flattened·produce·path>
|
|
93
102
|
sensible_write lock dir => $HOME/.scout/tmp/sensible_write_locks
|
|
94
103
|
```
|
|
95
104
|
|
|
96
|
-
(The `·
|
|
97
|
-
|
|
98
|
-
|
|
105
|
+
(The `·flattened·` component is `TmpFile.tmp_for_file` output: each `/`
|
|
106
|
+
in the produce path is flattened to `·` so the lock file name stays a
|
|
107
|
+
single path component.)
|
|
99
108
|
|
|
100
109
|
(See [PersistenceAndResources.md](PersistenceAndResources.md) for how
|
|
101
110
|
`persist` uses its lock, and [StreamingModel.md](StreamingModel.md) for
|
|
@@ -106,10 +115,10 @@ home-directory component is a machine-specific example value.)
|
|
|
106
115
|
`Persist.persist(..., :persist_type/:type)` can return a stream that must
|
|
107
116
|
stay locked while the consumer reads it. Raising `KeepLocked.new(res)` from
|
|
108
117
|
inside the persist block leaves the lock held; the caller gets the stream
|
|
109
|
-
back
|
|
118
|
+
back:
|
|
110
119
|
|
|
111
120
|
```ruby
|
|
112
|
-
res = Persist.persist("
|
|
121
|
+
res = Persist.persist("my-key", :text, :persist => false) do
|
|
113
122
|
raise KeepLocked.new("payload")
|
|
114
123
|
end
|
|
115
124
|
res # => "payload"
|
|
@@ -132,8 +141,6 @@ File.exist?(lock_path) # => true (lock still held)
|
|
|
132
141
|
|
|
133
142
|
## Thread-safety caveats
|
|
134
143
|
|
|
135
|
-
Verified by `tmp/rewrite_D/probe_09_threads_fork.rb`:
|
|
136
|
-
|
|
137
144
|
- `Log::LAST` is a shared top-level `String` — a write from one thread is
|
|
138
145
|
visible to the next with no synchronisation.
|
|
139
146
|
- `Scout::Config` holds **no mutex**; concurrent `set`/`get`/`with_config`
|