Files
troupe-standup/recoveries/helm-discovery-channel-seed.md
T
Merlin 2b834d4ce9 helm: Run 25 -- reply 47's corrections discharged by measurement (recoveries/helm-discovery-channel-seed.md 13218 B + record section 8, check-ins/2026-10-14-merlin.md + Run 25, WORK-QUEUE.md + Run 25 block). The pipe's Run-24 verification (a87745a) returned three corrections and one provenance defect; each re-measured from the git blobs and the scratch tree (/root/crucible/q57/), none accepted from the reply, all four confirmed. C1: the landing correction's character/byte pair is mispaired -- the queue at 8ba7c8e is 970138 B / 959834 chars (delta 10304) and at 32ad55b 978022 B / 967603 chars (delta 10419), so "978022 bytes = 959834 characters" pairs 32ad55b's bytes with 8ba7c8e's chars; the printed +7769 is the character delta, the byte delta is +7884; the named cause (len() of a decoded string) is right and the check-in's second pair (29217 B = 28840 chars) is correct as written. C2: stdout byte figures are a function of the gate's own directory length, coefficient measured not assumed -- both gates copied byte-identically into a tree whose full path is +16 per occurrence, the two green cells re-run rc 0 with 176/0 and 177/0 and live delta {}, each stdout grown exactly 96 B = 6 x 16, and len(replaced) - len(orig) = 0 after replacing every occurrence, so the tree path fully explains the growth; portable citations are the verdict, rc, suite counts, digest pair and the +210 B armed-vs-shipped delta (210 in both trees); tree-scoped are the raw 14044/14254; "byte-stable across all five" stands as written. C3: the 406212 B reclaimed has no derivation in its artifact -- out/Q56-CENSUS.json records one removed path with no byte count beside "reclaimed_bytes": 406212, so the sum of recorded per-path bytes is 0; restated, the removal is recorded and the figure is Testimony only. P: both Run-24 commits (32ad55b, 50bf350) are authored Timmy <timmy@stackchain.ai>, the clone's configured identity, because Run 24's append scripts carry no commit step; fixed prospectively -- this commit and every later one carries the Merlin identity, asserted by git log --author=merlin = 83 before the push; published history is not rewritten because both revisions are cited by the queue's Run-24 block and reply 47. Live delta 0 on bytes/sha256/mtime_ns/ctime_ns for both live objects (snapshot 14039 / aa842b0f / mtime_ns 1791479579475780303; held pin 120 / 5b6d7071 / mtime_ns 1791478687099553071); deployed /root/.helm/bin/ untouched with HELM_COLLECTOR count 0 so Q40 stands and none of Q31-Q45S10 is in production; sandboxes 0 in the q57 tree; disk 45 G of 154. No mechanism, digest, line count or verdict moves under any of the four corrections; the section 4 adoption pair (collector 4e45021d + gate c56230e5a075) is untouched. Two defects of mine logged first: the C2 coefficient (12 assumed, 16 measured -- the test tree was nested under q57/) and nothing else. No new item opened -- the Helm executable set is empty and the next Helm unit is the cp decision, not a task. Nothing deployed, no another-party file edited, decisions/ and inbox/ untouched, no secret printed.
2026-10-14 08:22:26 +00:00

18 KiB
Raw Blame History

The discovery channel: a subject that finds its held root by searching the filesystem

Helm lane — Q45S10 (Run 24), 2026-10-14. Scratch tree /root/crucible/q56/; nothing deployed, no cp into /root/.helm/bin/, decisions/ and inbox/ untouched, no another-party file edited, no secret printed. Internal label Q45S10 follows Run 22's Q45S9; the queue's Q52 is Mercury's and Q53 the career lane's, so neither number is reused.

The named residual this discharges is the one every record since Q45 has carried: resolve every path the subject declares at runtime. Q48 closed environment values and argv; Q52 (Run 18) closed Path.home()/expanduser; Q45S9 (Run 22) closed pwd.getpwuid. The filesystem search was the remainder, and it is the one channel no redirect can reach, because every redirect keys on how the destination is declared — a literal, a variable, an argument, a home read — and this class declares nothing.


1. The class

collector-discover.py is the real collector (4e45021d80395d3f…, 720 / 29,954, verified by sha256) with one change: the held directory is resolved at call time, by searching the filesystem — walk up from the module's own location, then scan the candidate roots, for the held pin:

def _discover_helm_home():
    here = Path(__file__).resolve().parent
    for parent in (here, *here.parents):
        cand = parent / ".helm"
        if (cand / _MARKER).is_file():
            return cand
    for scan in (Path("/"), Path("/root")):
        ...

No absolute literal (Q45 relocates those), no environment read (Q48 rebuilds those), no argv (Q48 rule 4 rewrites those), no home-database read (Q52 redirects HOME, Q45S9 refuses pwd). The snapshot destination keeps its literal, so the Q39 admission probe still has a module constant to rewrite and the subject is admitted on the same terms as every other collector — which is the point.

The interpreter's verdict, measured not asserted. Run bare from /root/crucible/q56/, both fixtures read the LIVE held pin (probe-collector-discover.py.json, probe-collector-discover-eager.py.json):

held_body_sha256: f27326e8b13d1dec9eb158fe40e5091f79dcaf73b47312c4e7d3958ba1d95d18
reads the LIVE held pin: True          (live pin digest, 120 B, same value)

And under a scratch HOME the resolution does not move (probe-scratch-home.json): the subject never reads HOME, so Q52's redirect is satisfied by a mechanism the subject does not use — the Q45S9 finding in a new place.

fixture bytes sha256 lines
collector-discover.py (lazy) 31,076 1f3dae9e7018a656722b8151226e4654931925f750f3154e49d2a103f987a0ba 753
collector-discover-eager.py (control) 31,094 8e58abeda872df2973ff1ce93b208f9c38f8efd4a9425c582f1f0b776efabdb3 753

2. The class on the shipped gate: silent

Shipped gate = helm-selftest-q55.py (cd43284ff009d1cceb3aaa6a65b701d1076eaf801e32b705672601b4122b746a, 143,389 B / 2,735 lines / 126 registrations, verified by sha256 from /root/crucible/q55/).

  • Cell A — shipped × discover, green shape: rc 0, helm-selftest: 176 passed, 0 failed, VERDICT: ALL GATES PASS, 13,918 B stdout, 11.9 s, live delta {}. The admission is normal (Q39 OK: the subject refused its sentinel destination (probe rc=2, sentinel written=False, other probe files=0, no traceback, live delta 0)), Q48/Q49 are vacuous on a subject that reads no variable, and the Q45 resolved-destination probe prints HELD_DIR=None — the module-level constant the copy declares — so check 2 passes vacuously. The suite is green on a subject whose held directory is the live one.
  • Cell A2 — shipped × discover --q52-witness: rc 0 and the live held pin moves (5b6d70716153c3f4… → 4e98bb00650e…), restored byte-exact by the guard; copy_pin_files_anywhere_in_sandbox = [] — the pin is not in the sandbox because it is in the live tree. The gate's own witness writes live from an admitted subject and nothing inside the gate sees it.
  • Cell D — shipped × discover-eager (the control): rc 1, 175/1. The import-time form is reported: FAIL Q45 no module-level constant of the copy resolves inside the live data root with values={"HELM_HOME": "/root/.helm", "SNAPSHOT_PATH": "<sandbox>/data/snapshot.json", "HELD_DIR": "/root/.helm/data/held", …}. So the probe catches the eager shape and is blind to the call-time one — which is exactly why the artifact check below is the load-bearing half.

3. The fix: the sandbox answers the search, and the artifact is the detector

Two inserts, one function's worth of code, into the shipped gate (patch_gate_q56.py, every anchor asserted count == 1 before the write, trailing newline asserted on the written file):

  1. The seed — prevention. At the sandbox-creation site, beside _q43_held.mkdir, the sandbox is given its own .helm/data/held/stackchain-home.html.sha256 carrying a decoy digest (sha256(b"Q45S10 sandbox decoy pin") = 1e0c8aeefeadd3a1af732b94b2b2b342dfc0a38446714f812305416323ee1ce3). The copy the legs run sits at <sandbox>/subject-legs.py, so its walk-up finds the sandbox's own tree first and every discovery resolves inside the sandbox. A decoy, never the live bytes: the live pin is not copied into a sandbox.
  2. The artifact check — detection. One more subject leg writes a snapshot into the sandbox (--out <sandbox>/q45s10-artifact.json --skip-tests, through the same wrapper as every other leg: argv rewrite, rebuilt environment, per-leg census, live watch) and the artifact must not carry the live held pin's digest. This is the only instrument that can see a call-time resolution, because the probe is vacuous on it by construction.

The pair. Gate c56230e5a075c5a12fd2f0d336dcac71c1078cb692a9e6c995780098a051f13e (147,151 B / 2,798 lines / 127 check(" registrations — 126 + 1, the new one). out/DIFF-vs-q55.txt: 2 hunks, +63 −0 lines, +3,762 −0 B, and 143,389 + 3,762 = 147,151 exactly (the byte identity is asserted in patch_gate_q56.py, not printed and hoped). Disarmed twin 820b7afdff6821a092a64d8a5c24d27f33baaa624534e9f1bfe351ef065a6e4a (147,074 / 2,796 / 127): the seed's write_text statement cut, 3 lines removed and 1 added, 77 B smaller — the Q50/Q51 twin pattern, one statement, and the directory is still created so the discriminator isolates the pin's presence and nothing else.

4. The matrix, 8 cells, every cell under the digest-verified guard

run_matrix_q56.py → out/Q56-MATRIX.json; run_matrix2_q56.py → out/Q56-MATRIX2.json. The guard (q56_guard.py, the q49/q55 discipline) holds a digest-verified backup of both live objects taken before the first cell and restores byte-exact on a move.

cell gate × subject rc suite live delta the measurement
A shipped × discover 0 176/0 {} admitted, ALL GATES PASS — the class is silent
A2 shipped × discover, witness 0 — MOVED 5b6d7071…→4e98bb00…, restored the live pin is written from an admitted subject
B armed × discover 0 177/0 {} artifact NOTE: the live held pin's digest is NOT in the sandbox artifact, and the sandbox decoy's digest IS
B2 armed × discover, witness 0 — {} copy_pin_files_anywhere_in_sandbox = ['.helm/data/held/stackchain-home.html.sha256'] — the pin lands inside the sandbox
C disarmed × discover 1 176/1 {} FAIL Q45S10 … live_pin_digest=f27326e8b13d1dec… live_digest_in_artifact=True — detection without prevention
C2 disarmed × discover, witness 0 — MOVED 5b6d7071…→0c4ec818…, restored the seed is load-bearing
D shipped × discover-eager 1 175/1 {} FAIL Q45 … HELM_HOME=/root/.helm, HELD_DIR=/root/.helm/data/held — the import-time form is reported
D2 armed × discover-eager 0 177/0 {} the seeded sandbox resolves even the eager form inside

The green pair and the control (out/Q56-MATRIX2.json):

  • G1 — shipped × real collector: rc 0, 176/0, 14,044 B stdout — the shipped gate's own historical baseline, reproduced to the byte.
  • G2–G6 — armed × real collector ×5: rc 0, 177/0, 14,254 B each (byte-stable across all five), live delta {} each, 14.1–14.2 s. The real collector's own held read is <sandbox>/data/held — the Q45-rewritten literal, which the seed does not touch — so its snapshot is unchanged in kind; the +210 B stdout delta is the seed's paths in the Q43 sandbox-census print plus the artifact leg and the new check's name. No pre-existing check moved: 176 → 177 is the one new check and nothing else.
  • Regressions 11/11: n1–n4 and dep-unparseable → Q39 rc 2; noparse → Q46 rc 2; undecodable → Q47 rc 2; broken_dep → Q35 rc 2; n5 → rc 2 (its own literal redirected into the sandbox, then its own destination rule refuses the sandbox dest without --deploy — the subject's rule, not the gate's); latin1 and bom → green 177/0 at 14,254 B, live delta {} on all eleven.

5. Live state, deployed, sandboxes, disk

out/Q56-CENSUS.json (re-measured, not carried):

  • Live, delta 0 on bytes/sha256/mtime across all 25 invocations: snapshot 14,039 B / aa842b0fd88037f83115b4a54d617f27666f4de06ef84f9a64c8fd0a5f04656d / mtime_ns 1791479579475780303; held pin 120 B / 5b6d70716153c3f4bd82045d5d71edf88f793a240ce1275c3dfca5759d0ee297 / mtime_ns 1791478687099553071. ctime moved on both (1791957230760646867 / 1791957230761646950) — the guard restores of the four deliberately-live cells; disclosed, not hidden.
  • Deployed /root/.helm/bin/ untouched, all five files re-read by digest (ee0b36a82f23… / 5c67473d8928… / 7ed1bca8f9da… / 12570786ba70… / 738699986778…), and grep -c HELM_COLLECTOR /root/.helm/bin/helm-selftest.py = 0 — Q40 stands, so none of Q31–Q45S9 or Q45S10 is in production.
  • Sandboxes: 0 in the q56 tree and 0 across /root/crucible (both censuses, E14's standing line); the q56 __pycache__ (the C32 class) removed in the same pass, 406,212 B reclaimed, after-count 0.
  • Disk: 45 G free of 154 (72 %).

6. Adoption

The §4 cp is collector 4e45021d80395d3f… + gate c56230e5a075c5a12fd2f0d336dcac71c1078cb692a9e6c995780098a051f13e (147,151 B / 2,798 lines / 127 registrations), superseding cd43284ff009… (Q45S9), 78b290ecf2d9… (Q52/home), e4967da9… (Q51) and a951dbe8… (Q50). Alexander's, unchanged. Beside it: the manifest_match pin stale since 2026-10-08, the Q13 publication call, Q9's checksum handover, Q19's onboarding.

7. Limits, and the defects of mine logged first

Limits. (a) The seed answers a search that walks up from the subject's own location or scans the candidate roots — a subject that globs the entire filesystem from / finds the live tree too; the artifact check catches that in the artifact, but the seed does not prevent it. (b) The check is artifact-shaped: a discovery subject that reads the live pin and emits nothing catches nothing. (c) The inventory of what counts as "the held pin" is the collector's own filename, carried by the gate — a subject that searches for a different marker is outside the class as measured. (d) The discovery is static in one respect: the fixture searches; a subject that receives its destination from a third party at runtime (a config file, a sibling process) is the next channel, and it is not measured here.

Defects of mine, in the order they happened. (1) The first lazy fixture used PEP 562 __getattr__ with no module-level assignment; it died NameError: name 'SNAPSHOT_PATH' is not defined, because a global name lookup inside a function never consults module __getattr__ — and the gate refuses a subject carrying no SNAPSHOT_PATH = assignment (Q39's failed-conditions list), so the design was wrong twice over. The fixture was rebuilt to keep the snapshot literal and move only the held channel to call time; the class statement moved with it, and §1 says so. (2) patch_gate_q56.py's first run read and wrote the same path, double-inserting (147,151 → 150,913 B on the second read); caught by the byte identity, fixed by re-copying the pristine gate and asserting count == 1. (3) The first twin cut one line of a three-line statement and left an IndentationError; the cut is now the whole statement. (4) The first diff-vs-shipped compared the file with itself (0 hunks) because base and target were one path; a pristine copy is now carried in the tree. (5) The first byte assertion added line counts where bytes were needed (143,389 + 63 ≠ 147,151); the identity closes on bytes, +3,762. (6) The green byte count moved 14,044 → 14,254 on the real collector — caused by the seed's paths in the Q43 census print and the extra artifact leg; recorded in §4, not smoothed over.

— Merlin (Run 24)


8. Run 25 — reply 47's three corrections and the provenance defect, discharged by measurement

2026-10-14, Merlin (Run 25, Helm lane). Scratch tree /root/crucible/q57/; nothing deployed, no cp into /root/.helm/bin/, decisions/ and inbox/ untouched, no another-party file edited, no secret printed. Append-only below the anchor; nothing above this line edited. Instruments: measure_r25.py → out/R25-MEASUREMENTS.json; measure_c2_r25.py → out/R25-C2.json; diff_c2_bytes_r25.py → out/R25-C2-BYTES.json (all host-local — the repo carries 0 paths under out/, E13's rule). Every correction below was verified from the git blobs and the scratch tree, not accepted from the reply.

C1 — the landing correction's character/byte pair is mispaired. Confirmed, and the correct pairs are measured. From the committed blobs (measure_r25.py):

revision file bytes characters byte − char
8ba7c8e WORK-QUEUE.md 970,138 959,834 10,304
32ad55b WORK-QUEUE.md 978,022 967,603 10,419
32ad55b checkins/2026-10-14-merlin.md 29,217 28,840 377
50bf350 checkins/2026-10-14-merlin.md 33,771 33,364 407
a87745a WORK-QUEUE.md 983,165 972,681 10,484

The correction's sentence "the queue is 978,022 bytes = 959,834 characters" pairs 32ad55b's bytes with 8ba7c8e's characters — both figures are real, the pairing is wrong. The 10,304 it prints is 970,138 − 959,834, the 8ba7c8e pair's own byte-minus-character difference; 32ad55b's is 10,419. The +7,769 the append scripts printed is the character delta (967,603 − 959,834); the byte delta is +7,884 (978,022 − 970,138). The named cause — len() of a decoded string — is correct. The second pair ("this file is 29,217 bytes = 28,840 characters at 32ad55b") is correct as written. No figure anywhere in this run depends on a character count; every assertion that mattered (sha256, trailing newline, anchor counts) was on bytes.

C2 — stdout byte figures are a function of the gate's own directory length. Confirmed, with the coefficient measured rather than assumed. The sandbox is created with dir = Path(__file__).resolve().parent, so every stdout line carrying a sandbox path grows with the gate's tree name. Measured (measure_c2_r25.py, diff_c2_bytes_r25.py): the shipped gate and the armed gate each print their own tree path exactly 6 times; both gates were copied byte-identically (cd43284ff009… / c56230e5a075…, sha256-verified) into /root/crucible/q57/q56-plus-twelve (tree name +12 chars, full path +16 per occurrence) and the two green cells re-run there: each stdout grew by exactly 96 B = 6 × 16, rc 0, suites 176/0 and 177/0, live delta {}; after replacing every occurrence of the long path with the short one, len(replaced) − len(orig) = 0 — the tree path fully explains the growth, the only residual differences being the per-run random names at equal length. Portable citations: the verdict, the rc, the suite counts, the digest pair, and the +210 B armed-vs-shipped delta (210 in both trees, because both gates print the path 6 times). Tree-scoped, not portable: the raw byte counts 14,044 / 14,254. §4's "byte-stable across all five" stands as written — it is a claim about five runs in one tree, and that is what was measured (5 × 14,254 B, 6 occurrences each).

C3 — the 406,212 B reclaimed figure has no derivation in its artifact. Confirmed. out/Q56-CENSUS.json records "cleanup": {"removed": [{"path": "/root/crucible/q56/__pycache__", "note": "the C32 class"}], "reclaimed_bytes": 406212, "after_count": 0} — one removed path, no byte count, so the sum of recorded per-path bytes is 0. The figure exists as a bare field; its evidence does not. Restated in §5: the removal (one path, after-count 0) is recorded; the 406,212 B is Testimony only.

P — the provenance defect, new this run: both Run-24 commits are authored by the clone's identity. git log --format='%h %an <%ae>' --date=iso: 32ad55b and 50bf350 read Timmy <timmy@stackchain.ai>; git log --author=merlin = 82, the lane's earlier commits; the clone's configured identity is Timmy (git config user.name). Cause: the Run-24 append scripts carry no commit step — the commit was run from the shell without the lane's per-commit -c user.name=Merlin -c user.email=merlin@agents.invalid overrides. Published history is not rewritten (32ad55b/50bf350 are cited by the queue's Run-24 block and by reply 47), so the fix is prospective and asserted: every commit from this run on carries the Merlin identity, git log --author=merlin re-counted before the push.

Defects of mine, logged first. (1) My first C2 prediction (12 × 6 = 72 B) was wrong because the test tree was nested under q57/, making the full path +16 per occurrence rather than +12 — measured, not assumed; the mechanism claim survives, the coefficient was mine to get right. (2) None of the three corrections changes any mechanism, digest, line count, registration count or verdict in §1–§7; they are corrections to the record's prose, and §6's adoption pair (4e45021d… + c56230e5a075…) is untouched.

— Merlin (Run 25)