helm: Run 22 -- the Q45 s9 pwd channel closed (a home-database read the redirect cannot reach, refused at admission)
Deliverable recoveries/helm-pwdir-channel-refusal.md (14,808 B / sha256
d4c381f713e9fe95beefbd14a93220ab4049625f280c116e656419f3e11b7b89) plus the queue block at the foot
of WORK-QUEUE.md (+5,419 B / 63 lines, 951,756 B / d8f54086edbcbbc96e39533d7b6dcf0e9effff108b596ed18685b03d1045e1db)
and checkins/2026-10-14-merlin.md (Run 22). Scratch tree /root/crucible/q55/; nothing deployed, no cp
into /root/.helm/bin/, decisions/ and inbox/ untouched, no another-party file edited, no secret printed.
The class, measured on the shipped Q52 gate before the change: a subject whose root is composed from
the user database (ROOT = Path(pwd.getpwuid(os.getuid()).pw_dir) / ".helm") resolves the live root
under a scratch HOME (rc 0, both fixtures), passes the Q52 home-channel check (the check asserts the
legs' HOME is redirected; pwd.getpwuid never reads HOME), and writes the LIVE held pin through the
gate's own --q52-witness flag (5b6d7071... -> fdd45f74..., restored byte-exact by the guard). A foot
check reports after the legs; only a precondition refuses before them.
The fix: gate cd43284ff009d1cceb3aaa6a65b701d1076eaf801e32b705672601b4122b746a (143,385 B / 2,735
lines / 126 registrations -- unchanged; +3,020 B / +58 lines on the Q52 gate 78b290ec...) -- a
precondition after Q46's parse guard, before the Q39 sandbox, the admission probe and any leg: a
subject whose AST reads the pwd module is refused (exit 2, dated, naming every pwd site with its
line). Disarmed twin 8adad99bdc872ecd09ddea88ae55b5fc55a3886571ffaa5f49a053b52430586d.
25 guarded cells: armed x pwr/pwr-witness/pwn rc 2 refusal (stdout 0 B, live delta {}); disarmed x
pwr-witness reproduces the escape (live pin 5b6d7071... -> ced810a9..., restored byte-exact);
disarmed x pwr 175/1 with Q52 PASS and Q45 FAIL (the shipped behaviour); green pair x5 176/0 at
14,044 B; regressions 11/11. Covered shapes are not over-refused: the witness pin lands inside the
sandbox, and the covered fixtures fail Q43 identically under the shipped gate (measured side by side).
Live delta 0 on bytes/sha256/mtime across all 25 cells (snapshot aa842b0f... / 14,039, pin
5b6d7071... / 120); ctime moved on the pin across the deliberately-live witness cells (guard restores)
-- disclosed. Sandboxes: q55 tree 0/0; the E14 leak re-measured at 26 across /root/crucible and all
26 removed (842,401 B reclaimed) with the q55 __pycache__. Deployed /root/.helm/bin/ untouched,
HELM_COLLECTOR count 0 -- Q40 stands. Disk 45 G avail of 154.
Adoption, Alexander's: the s4 cp is collector 4e45021d... + gate cd43284ff009..., superseding
78b290ec... / e4967da9... / a951dbe8.... Five defects of mine are logged first in the deliverable
(part 1's void d/e rows, its prefix-vs-full restored comparison, its broken Q45 filter, my assumed
d/e expectation, and the gate's first-written Q55 label). No new item opened -- the Helm lane's
executable set is empty; the next unit is a decision, not a task.
This commit is contained in:
1 parent
8dbaf23eb6
commit
c4addca3ce
3 files changed
+399
No files matched your search
@@ -8031,3 +8031,66 @@ append. Live and deployed unchanged (snapshot `aa842b0f…` / 14,039, pin `5b6d7
|
|||||||
**Convention, from this tick:** every new opening record carries a forward pointer to its closure line or revision, and a claim check reads **this ledger** before taking any item whose opening line reads open or unclaimed. The eight historical records above are **left exactly as written** — corrections are logged, never deleted, and inserting pointers into them would shift every line-number citation in the archive, Merlin's Run-21 cites (`:6847`, `:6952`, `:7106`, `:7918–7920`, `:7940`) among them.
|
**Convention, from this tick:** every new opening record carries a forward pointer to its closure line or revision, and a claim check reads **this ledger** before taking any item whose opening line reads open or unclaimed. The eight historical records above are **left exactly as written** — corrections are logged, never deleted, and inserting pointers into them would shift every line-number citation in the archive, Merlin's Run-21 cites (`:6847`, `:6952`, `:7106`, `:7918–7920`, `:7940`) among them.
|
||||||
|
|
||||||
**Why it earns a ledger:** Q49's stale line (`:6847`) is in the Helm lane and reads claimable; Merlin reports it nearly cost Run 21 a duplicate run (**Testimony only** for the near-miss; the stale line itself is **Documented**).
|
**Why it earns a ledger:** Q49's stale line (`:6847`) is in the Helm lane and reads claimable; Merlin reports it nearly cost Run 21 a duplicate run (**Testimony only** for the near-miss; the stale line itself is **Documented**).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Q45 §9 follow-on, Run 22 — the pwd channel closed: a home-database read the redirect cannot reach is
|
||||||
|
now refused at admission (Merlin, Helm lane, 2026-10-14 ~01:1x–01:3xZ; clock read at 01:29:06Z).**
|
||||||
|
Deliverable: `recoveries/helm-pwdir-channel-refusal.md`. Scratch tree `/root/crucible/q55/`; nothing
|
||||||
|
deployed, no `cp` into `/root/.helm/bin/`, `decisions/` and `inbox/` untouched, no another-party file
|
||||||
|
edited, no secret printed.
|
||||||
|
|
||||||
|
- **The class, measured on the shipped Q52 gate before the change.** `collector-pwdir-root.py` (the
|
||||||
|
real collector, root composed from the user database: `ROOT = Path(pwd.getpwuid(os.getuid()).pw_dir)
|
||||||
|
/ ".helm"`, sha256 `8b368eadb857…`, 29,979 B) resolves `ROOT=/root/.helm` and
|
||||||
|
`HELD_DIR=/root/.helm/data/held` under a **scratch HOME** (rc 0 — the interpreter's own verdict, both
|
||||||
|
pwd fixtures). Under the shipped gate: default run 175/1 (Q45 FAIL, `escapes={"ROOT":
|
||||||
|
"/root/.helm", "HELD_DIR": "/root/.helm/data/held"}`) while **Q52 PASSES** — the check asserts the
|
||||||
|
legs' HOME is redirected, and `pwd.getpwuid` never reads HOME. Under the gate's own `--q52-witness`
|
||||||
|
flag: rc 0, `copy_pin_files_anywhere_in_sandbox` **[]**, and the **live held pin moves**
|
||||||
|
(`5b6d7071…` → `fdd45f74…`, mtime_ns `1791478687099553071` → `1791940472306970986`), restored
|
||||||
|
byte-exact by the guard. The named-constant fixture writes live through the same path
|
||||||
|
(`302bfda5…`). The defect does not depend on the constant's name; the rename only decides which check
|
||||||
|
catches it — and a foot check reports, while only a precondition refuses (Q35's own lesson, in the
|
||||||
|
one place the Q35 fix did not reach).
|
||||||
|
- **The fix.** Gate **`cd43284ff009d1cceb3aaa6a65b701d1076eaf801e32b705672601b4122b746a`** (143,385 B /
|
||||||
|
2,735 lines / **126** `check("` registrations — unchanged) on the Q52 gate `78b290ec…` (140,365 /
|
||||||
|
2,677 / 126): one edit, **+3,020 B / +58 lines** (2,677 + 58 = 2,735 exact). A precondition after
|
||||||
|
Q46's parse guard and the ast import, **before the Q39 sandbox, the admission probe and any leg**: a
|
||||||
|
subject whose own AST reads the `pwd` module is refused — exit 2, dated message naming the subject,
|
||||||
|
every `pwd` site with its line, and the live snapshot before/after. Disarmed twin `8adad99bdc872…`
|
||||||
|
(143,416 / 2,735 / 126), one line different. No `check()` registered: the green pair still reports
|
||||||
|
exactly 176 / 0. The gate's internal label is **`Q45S9`**, not a queue number (the lane's own
|
||||||
|
collision lesson, applied to a fresh artifact; the rename happened before any readback or push, so
|
||||||
|
these are the only digests this record carries).
|
||||||
|
- **The matrix, 25 cells under the final digest, all guarded.** armed × pwr / pwr-witness / pwn → rc 2
|
||||||
|
refusal, stdout **0 B**, live delta `{}`. **disarmed × pwr-witness → the escape reproduces** (probe
|
||||||
|
`HELD_DIR=/root/.helm/data/held`, pin in sandbox `[]`, live pin `5b6d7071…` → `ced810a9…` /
|
||||||
|
`1791941106156792591`, restored byte-exact). disarmed × pwr → 175/1, Q52 PASS, Q45 FAIL (the shipped
|
||||||
|
behaviour). **Green pair ×5: 176/0**, 14,044 B stdout, live delta `{}` each. **Regressions 11/11**
|
||||||
|
(nine refusals rc 2; latin1 and bom 176/0). Covered shapes are not over-refused: armed ×
|
||||||
|
`collector-home.py --q52-witness` puts the pin **inside the sandbox** (`q52-home/.helm/data/held/…`)
|
||||||
|
with the live pin unmoved; and the covered fixtures fail **Q43 identically under the shipped gate**
|
||||||
|
(175/1 both, PASS/FAIL lists identical modulo sandbox names — measured side by side, so the fix
|
||||||
|
introduces no behaviour change for them).
|
||||||
|
- **Live, delta 0 on bytes/sha256/mtime across all 25 cells** (`compare final2`, `"moved": false`):
|
||||||
|
snapshot 14,039 B / `aa842b0fd88037f8…` / mtime_ns `1791479579475780303`; pin 120 B /
|
||||||
|
`5b6d70716153c3f4…` / `1791478687099553071`. **ctime moved on the pin** across the deliberately-live
|
||||||
|
witness cells (guard restores) — disclosed. **Sandboxes: q55 tree 0/0** (the refusal exits before the
|
||||||
|
sandbox is created); the E14 leak re-measured at **26 across /root/crucible and all 26 removed**
|
||||||
|
(842,401 B reclaimed, after-count 0 both ways, `out/Q55-CLEANUP.json`); the q55 `__pycache__` (the
|
||||||
|
C32 class) removed in the same pass. Deployed `/root/.helm/bin/` untouched (five digests re-read),
|
||||||
|
`HELM_COLLECTOR` count **0** — Q40 stands. Disk 45 G avail of 154.
|
||||||
|
- **Adoption:** the §4 `cp` is collector `4e45021d…` + gate **`cd43284ff009…`**, superseding
|
||||||
|
`78b290ec…` / `e4967da9…` / `a951dbe8…`. Alexander's, unchanged.
|
||||||
|
- **Limits and defects are in the record.** The refusal refuses innocent `pwd` readers too (the
|
||||||
|
conservative direction, same trade as the Q35 marker); the inventory is static (Q46's limit); the
|
||||||
|
general class — any home channel the redirect cannot reach — stays open. Five defects of mine are
|
||||||
|
logged first in the deliverable: part 1's void d/e rows (fixtures not yet copied into the tree), its
|
||||||
|
prefix-vs-full `restored` comparison, its broken Q45 filter, my assumed d/e expectation (the
|
||||||
|
shipped-gate baseline, taken after, was the correction), and the gate's first-written `Q55` label.
|
||||||
|
- **Open after this tick, Helm lane:** nothing executable. The §4 `cp`, the stale `manifest_match` pin,
|
||||||
|
Q13, Q9, Q19 are Alexander's; Q42 zephyrus's; Q52 Mercury's; Q53 the career lane's. **No new item
|
||||||
|
opened** — the next Helm unit is a decision, not a task.
|
||||||
|
|
||||||
|
— Merlin (Run 22)
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
# 2026-10-14 — merlin
|
||||||
|
|
||||||
|
> Screen. Every claim below carries a path, a hash, a URL, or a command with its output, or the word
|
||||||
|
> `unverified`. Nothing secret is printed anywhere in this check-in or in the files it lands.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Run 22 — Q45 §9: the pwd channel
|
||||||
|
|
||||||
|
### Did
|
||||||
|
|
||||||
|
Claimed and finished **the Q45 §9 follow-on — the pwd channel** (Helm lane; the lane's own named
|
||||||
|
residual, the limit the Q45 §9 record carried: *"a subject reading `pwd.getpwuid(...).pw_dir` directly
|
||||||
|
bypasses HOME and the redirect cannot see it"*). Deliverable:
|
||||||
|
`recoveries/helm-pwdir-channel-refusal.md` (**14,808 B / sha256
|
||||||
|
`d4c381f713e9fe95beefbd14a93220ab4049625f280c116e656419f3e11b7b89`**, this host). Queue block appended
|
||||||
|
at the foot of `WORK-QUEUE.md` (**951,756 B / sha256
|
||||||
|
`d8f54086edbcbbc96e39533d7b6dcf0e9effff108b596ed18685b03d1045e1db`**, +5,419 B / 63 lines, trailing
|
||||||
|
newline asserted before and after). Scratch tree `/root/crucible/q55/`; nothing deployed, no `cp` into
|
||||||
|
`/root/.helm/bin/`, `decisions/` and `inbox/` untouched, no another-party file edited, no secret.
|
||||||
|
|
||||||
|
**The class, measured on the shipped gate before the change.** The base gate is
|
||||||
|
`/root/crucible/q52/helm-selftest-q52.py` = `78b290ecf2d93e0c…` (140,365 B / 2,677 lines / 126
|
||||||
|
registrations), copied into `q55/` and re-verified by `sha256sum`. Two fixtures transform the real
|
||||||
|
collector (`4e45021d…`, 720 / 29,954, byte-identical to Q45 §9's) into a subject whose root comes from
|
||||||
|
the user database — `collector-pwdir-named.py` (`4d62da91…`, 29,994 B) and `collector-pwdir-root.py`
|
||||||
|
(`8b368ead…`, 29,979 B, the constant renamed `HELM_HOME`→`ROOT`). Run under a **scratch HOME**, both
|
||||||
|
resolve `ROOT=/root/.helm` and `HELD_DIR=/root/.helm/data/held` with rc 0 — the interpreter's own
|
||||||
|
verdict of the class. Under the shipped gate: the default run is 175/1 (Q45 FAIL, `escapes={"ROOT":
|
||||||
|
"/root/.helm", "HELD_DIR": "/root/.helm/data/held"}`) while **Q52 PASSES** — the check asserts the
|
||||||
|
legs' HOME is redirected, and `pwd.getpwuid` never reads HOME. Under the gate's own `--q52-witness`
|
||||||
|
flag — the flag that exists to measure this class's write path: rc 0,
|
||||||
|
`copy_pin_files_anywhere_in_sandbox` **[]**, and the **live held pin moves**
|
||||||
|
(`5b6d7071…`→`fdd45f74…`, mtime_ns `1791478687099553071`→`1791940472306970986`; guard
|
||||||
|
`q55_guard.py compare after-pwr-witness`: `"moved": true`), restored byte-exact. The named-constant
|
||||||
|
fixture writes live through the same path (`302bfda5…`). A foot check reports after the legs; only a
|
||||||
|
precondition refuses before them.
|
||||||
|
|
||||||
|
**The fix.** Gate **`cd43284ff009d1cceb3aaa6a65b701d1076eaf801e32b705672601b4122b746a`** (143,385 B /
|
||||||
|
2,735 lines / **126** registrations — unchanged; one edit, +3,020 B / +58 lines, 2,677 + 58 = 2,735
|
||||||
|
exactly) on the Q52 gate: a precondition after Q46's parse guard and the ast import, **before the Q39
|
||||||
|
sandbox, the admission probe and any leg** — a subject whose own AST reads the `pwd` module is refused
|
||||||
|
(exit 2, dated, naming the subject, every `pwd` site with its line, the live snapshot before/after). No
|
||||||
|
`check()` registered, so the green pair still reports exactly 176 / 0. Disarmed twin
|
||||||
|
`8adad99bdc872ecd09ddea88ae55b5fc55a3886571ffaa5f49a053b52430586d` (143,416 / 2,735 / 126), one line
|
||||||
|
different, 31 B **larger** (the Q52-twin pattern). The internal label is `Q45S9`, not a queue number —
|
||||||
|
the lane's own collision lesson applied to a fresh artifact before any readback or push, so these are
|
||||||
|
the only digests this run carries.
|
||||||
|
|
||||||
|
**The matrix, 25 cells, all guarded** (`run_matrix_q55.py` → `out/Q55-MATRIX.json`, 12;
|
||||||
|
`run_matrix2_q55.py` → `out/Q55-MATRIX2.json`, 13). Armed × pwr / pwr-witness / pwn → rc 2 refusal,
|
||||||
|
stdout **0 B** (`e3b0c442…`), live delta `{}`. **Disarmed × pwr-witness → the escape reproduces**
|
||||||
|
(probe `HELD_DIR=/root/.helm/data/held`, pin in sandbox `[]`, live pin `5b6d7071…`→`ced810a9…` /
|
||||||
|
`1791941106156792591`, restored byte-exact) — the refusal is load-bearing. Disarmed × pwr → 175/1,
|
||||||
|
Q52 PASS, Q45 FAIL (the shipped behaviour). **Green pair ×5: 176/0**, 14,044 B stdout / 1,216 B stderr,
|
||||||
|
live delta `{}` each, 20.5–24.1 s. **Regressions 11/11** (n1–n5, noparse, dep-unparseable,
|
||||||
|
undecodable, broken_dep rc 2; latin1 and bom 176/0). Covered shapes are not over-refused and stay
|
||||||
|
protected: armed × `collector-home.py --q52-witness` puts the pin **inside the sandbox**
|
||||||
|
(`q52-home/.helm/data/held/…`) with the live pin unmoved; and the covered fixtures fail **Q43
|
||||||
|
identically under the shipped gate** (175/1 both, PASS/FAIL lists identical modulo sandbox names —
|
||||||
|
measured side by side, so the fix introduces no behaviour change for them).
|
||||||
|
|
||||||
|
**Live state, sandboxes, deployed.** Delta 0 on bytes/sha256/mtime across all 25 cells
|
||||||
|
(`q55_guard.py compare final2`, `"moved": false`): snapshot 14,039 B / `aa842b0fd88037f8…` /
|
||||||
|
mtime_ns `1791479579475780303`; pin 120 B / `5b6d70716153c3f4…` / `1791478687099553071`. **ctime moved
|
||||||
|
on the pin** across the deliberately-live witness cells (guard restores) — disclosed. Sandboxes: the
|
||||||
|
q55 tree **0/0** both ways (the refusal exits before the sandbox is created); the E14 leak re-measured
|
||||||
|
at **26 across /root/crucible and all 26 removed** (842,401 B reclaimed, after-count 0 both ways,
|
||||||
|
`out/Q55-CLEANUP.json`), plus the q55 `__pycache__` (the C32 class). Deployed `/root/.helm/bin/`
|
||||||
|
untouched (five digests re-read: `ee0b36a8…` / `5c67473d…` / `7ed1bca8…` / `12570786…` / `73869998…`),
|
||||||
|
`grep -c HELM_COLLECTOR` = **0** — Q40 stands, so none of Q31–Q45 §9 is in production. Disk 45 G avail
|
||||||
|
of 154 (71 %).
|
||||||
|
|
||||||
|
**Five defects of mine, logged first in the deliverable:** part 1's d/e rows pointed at fixtures not
|
||||||
|
yet copied into the tree (a Q35 refusal against a nonexistent path — void rows, re-run in part 2, kept
|
||||||
|
with the note); part 1's `restored` field compared a 12-char prefix to a full digest; part 1's Q45
|
||||||
|
filter matched the one-space rendering and missed the row it existed to find; my d/e expectation of
|
||||||
|
176/0 was assumed and the shipped-gate baseline taken after was the correction; and the gate's
|
||||||
|
first-written `Q55` label.
|
||||||
|
|
||||||
|
### Blocked
|
||||||
|
|
||||||
|
Nothing blocks the lane. The standing human gates are unchanged: the **§4 adoption `cp` — now
|
||||||
|
collector `4e45021d…` + gate `cd43284ff009…`** (superseding `78b290ec…` / `e4967da9…` / `a951dbe8…`),
|
||||||
|
the **`manifest_match` pin** (stale since 2026-10-08), **Q13's publication call** (item 13's decided
|
||||||
|
note controls over its `proposed_action`), **Q9's checksum handover**, **Q19's onboarding**. Q42 is
|
||||||
|
zephyrus-sparkborn's, Q52 is Mercury's, Q53 is the career lane held for Alexander. No counterparty
|
||||||
|
check-in or reply has landed since the 2026-10-13 23:18Z hygiene ledger (`git log --oneline
|
||||||
|
--since=2026-10-13T20:00`: nothing after `8dbaf23`; `checkins/` has no 2026-10-14 file but this one).
|
||||||
|
|
||||||
|
### Next
|
||||||
|
|
||||||
|
1. **The pipe's verification of this run**, when it lands. Everything re-runs from
|
||||||
|
`/root/crucible/q55/`: `make_fixtures_q55.py`, `patch_gate_q55.py`, `run_matrix_q55.py`,
|
||||||
|
`run_matrix2_q55.py`, `q55_guard.py`, `census_q55.py` and `cleanup_sandboxes.py` are
|
||||||
|
self-contained; `out/` holds every cell's stdout and stderr plus `Q55-MATRIX.json`,
|
||||||
|
`Q55-MATRIX2.json`, `Q55-CENSUS.json` and `Q55-CLEANUP.json` (host-local — the repo carries 0 paths
|
||||||
|
under `out/`, E13's rule).
|
||||||
|
2. **No new item opened.** The Helm lane's executable set is empty and a task invented to fill it would
|
||||||
|
be invented work; the next Helm unit is the `cp` decision, not a task.
|
||||||
|
|
||||||
|
### Ask
|
||||||
|
|
||||||
|
Alexander's, unchanged: **(a)** the §4 adoption `cp` — collector `4e45021d…` + gate
|
||||||
|
`cd43284ff009…` — the only thing between Q31–Q45 §9 and production protection in `/root/.helm`;
|
||||||
|
**(b)** the `manifest_match` pin, stale since 2026-10-08; **(c)** the Q13 publication call. No ask of
|
||||||
|
the pipe: this run returned nothing to the pipe's own queue (the hygiene ledger's Q52 is Mercury's),
|
||||||
|
and the lane's own limits are discharged by measurement above.
|
||||||
|
|
||||||
|
— Merlin (Run 22)
|
||||||
|
|
||||||
|
### Run 22 landing evidence
|
||||||
|
|
||||||
|
Read back over the anonymous raw path
|
||||||
|
(`https://forge.alexanderwhitestone.com/git/stackchain/troupe-standup/raw/branch/main/<path>`, no
|
||||||
|
credential) by `python3 /root/crucible/q55/readback_q55.py <revision>`; artifact
|
||||||
|
`out/Q55-READBACK.txt` (host-local — the repo carries 0 paths under `out/`, E13's rule). All rows
|
||||||
|
**HTTP 200, served = disk = `git cat-file -p <revision>:<path>`**:
|
||||||
|
|
||||||
|
| file | HTTP / bytes | sha256 (served = disk = git) |
|
||||||
|
|---|---|---|
|
||||||
|
| `recoveries/helm-pwdir-channel-refusal.md` | 200 / 14,808 | `d4c381f713e9fe95beefbd14a93220ab4049625f280c116e656419f3e11b7b89` |
|
||||||
|
| `WORK-QUEUE.md` | 200 / 951,756 | `d8f54086edbcbbc96e39533d7b6dcf0e9effff108b596ed18685b03d1045e1db` |
|
||||||
|
| `checkins/2026-10-14-merlin.md` | 200 / (this file at the pushed revision — a file cannot hash itself, so this row cites the revision it was written against; this append lands as its own commit) | — |
|
||||||
|
|
||||||
|
Live state at readback, unchanged: snapshot 14,039 B / `aa842b0f…` / mtime_ns `1791479579475780303`;
|
||||||
|
held pin 120 B / `5b6d7071…` / mtime_ns `1791478687099553071`; deployed `/root/.helm/bin/` untouched,
|
||||||
|
`HELM_COLLECTOR` count **0**. Nothing deployed, no another-party file edited, `decisions/` and `inbox/`
|
||||||
|
untouched, no secret printed.
|
||||||
|
|
||||||
|
— Merlin (Run 22, landing)
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
# Q45 §9 follow-on — the pwd channel: a home-directory read the redirect cannot reach
|
||||||
|
|
||||||
|
> Screen. Every claim below carries a path, a hash, a URL, or a command with its output, or the word
|
||||||
|
> `unverified`. Nothing secret is printed anywhere in this record or in the files it lands.
|
||||||
|
|
||||||
|
Merlin, Helm lane, 2026-10-14 ~01:1x–01:3xZ (clock read at 01:29:06Z immediately before this write).
|
||||||
|
Scratch tree `/root/crucible/q55/`; nothing deployed, no `cp` into `/root/.helm/bin/`, `decisions/` and
|
||||||
|
`inbox/` untouched, no another-party file edited, no secret printed.
|
||||||
|
|
||||||
|
## The class
|
||||||
|
|
||||||
|
Q52 redirected HOME and USERPROFILE inside the one rebuild function that already served the admission
|
||||||
|
probe and the legs, and registered one check: *when the subject reads the home database, the legs' HOME
|
||||||
|
must be the sandbox home*. The redirect covers `Path.home()` and `expanduser` because CPython reads
|
||||||
|
HOME first. It cannot reach `pwd.getpwuid(os.getuid()).pw_dir`, whose answer comes from the **user
|
||||||
|
database**. A subject composing its root from a `pw_dir` read therefore passes the Q52 check — the check
|
||||||
|
asserts a property of the *gate's redirect*, not of the *subject's resolution* — on a subject whose root
|
||||||
|
is the live root.
|
||||||
|
|
||||||
|
The Q45 §9 record named this as its first limit ("a subject reading `pwd.getpwuid(...).pw_dir` directly
|
||||||
|
bypasses HOME and the redirect cannot see it"). This run measures what that limit costs and closes it.
|
||||||
|
|
||||||
|
## The pair
|
||||||
|
|
||||||
|
Base gate: `/root/crucible/q52/helm-selftest-q52.py`, sha256
|
||||||
|
`78b290ecf2d93e0cc3087e5ede8baef5c10ca52986706d5da69a13a619dbdd9f` (140,365 B / 2,677 lines / **126**
|
||||||
|
`check("` registrations), copied into `q55/` and re-verified by `sha256sum` before any run.
|
||||||
|
|
||||||
|
- Collector: **byte-identical to Q45 §9's** (`4e45021d80395d3f…`, 720 / 29,954, at
|
||||||
|
`/root/crucible/q48/helm-collect.py`) — every change is in the gate.
|
||||||
|
- Gate **`cd43284ff009d1cceb3aaa6a65b701d1076eaf801e32b705672601b4122b746a`** (143,385 B / 2,735 lines /
|
||||||
|
**126** registrations — unchanged) against the Q52 base: one edit, a 3,043-char block minus its
|
||||||
|
23-char anchor = **+3,020 B / +58 lines**, and 2,677 + 58 = 2,735 exactly.
|
||||||
|
- Disarmed twin **`8adad99bdc872ecd09ddea88ae55b5fc55a3886571ffaa5f49a053b52430586d`** (143,416 B / 2,735
|
||||||
|
lines / 126) — one line different (`if _q45S9_pwd_sites:` → `if False: # Q45S9 disarmed: the pwd
|
||||||
|
refusal is cut`), and **31 B larger** than the gate it disarms, the Q52-twin pattern (a comment where
|
||||||
|
a condition was).
|
||||||
|
- The gate's internal label is **`Q45S9`**, not a queue number: the lane's own logged lesson (Q45 §9 Run
|
||||||
|
19) is that an ad-hoc item label must not collide with a Q number another lane can open. The scratch
|
||||||
|
tree keeps its build-order name `q55`; the refusal prefix, the twin comment and the block docstring
|
||||||
|
carry the section id. The rename happened **before** any readback or push, so the digests above are
|
||||||
|
the only ones this record carries.
|
||||||
|
|
||||||
|
## The fixtures, and the interpreter's verdict
|
||||||
|
|
||||||
|
Two transforms of the real collector (`make_fixtures_q55.py`, each asserted to land exactly once):
|
||||||
|
|
||||||
|
| fixture | root assignment | sha256 | bytes |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `collector-pwdir-named.py` | `HELM_HOME = Path(pwd.getpwuid(os.getuid()).pw_dir) / ".helm"` | `4d62da91d94fc010a99d6c157b28b14bef86333afc91336d6b8d7319f3fb14ec` | 29,994 |
|
||||||
|
| `collector-pwdir-root.py` | `ROOT = Path(pwd.getpwuid(os.getuid()).pw_dir) / ".helm"` (every `HELM_HOME` identifier renamed) | `8b368eadb857dbaf82bb1d2c795778a04b243c1e5ee2527b903ccec508687b3b` | 29,979 |
|
||||||
|
|
||||||
|
Run under a **scratch HOME** (`python3 <fixture>` via `runpy`, `sys.argv = ['x', '--skip-tests']`), both
|
||||||
|
resolve `ROOT /root/.helm` and `HELD_DIR /root/.helm/data/held` — the live root — with rc 0. The class
|
||||||
|
is measured against the interpreter, not asserted: *ALL PWD FIXTURES RESOLVE THE LIVE ROOT DESPITE A
|
||||||
|
SCRATCH HOME*.
|
||||||
|
|
||||||
|
The second fixture removes the one thing that would have caught the class in a default run: Q45's
|
||||||
|
foot check ("no module-level constant of the copy resolves inside the live data root") fails on
|
||||||
|
`ROOT` either way — but a foot check reports after the legs; only a precondition refuses before them.
|
||||||
|
Q35's own lesson, in the one place the Q35 fix did not reach.
|
||||||
|
|
||||||
|
## The defect, measured on the shipped gate before the change
|
||||||
|
|
||||||
|
**`collector-pwdir-root.py` under the shipped Q52 gate, default run** (`out-pwr.txt`): rc 1,
|
||||||
|
**175 passed / 1 failed** — the FAIL is Q45 (`values={"ROOT": "/root/.helm", "SNAPSHOT_PATH":
|
||||||
|
"<sandbox>/data/snapshot.json", "HELD_DIR": "/root/.helm/data/held", …}`, `escapes={"ROOT":
|
||||||
|
"/root/.helm", "HELD_DIR": "/root/.helm/data/held"}`), while **Q52 PASSES**: the stderr NOTE reads
|
||||||
|
*"the subject reads the home database at 2 site(s) (.home(), pwd.getpwuid); HOME redirected to
|
||||||
|
<sandbox>/q52-home"* — the shipped collector's own `Path.home()` himalaya search plus the injected
|
||||||
|
`pwd.getpwuid`, and the check is satisfied by a mechanism the `pw_dir` read never uses.
|
||||||
|
|
||||||
|
**The same subject under the gate's own `--q52-witness` flag** — the flag that exists to measure this
|
||||||
|
class's write path (`out-pwr-wit.txt`): rc **0**. The copy's probe prints `HELM_HOME=None`,
|
||||||
|
`HELD_DIR=/root/.helm/data/held`; `copy_held_files` is `[]` and
|
||||||
|
`copy_pin_files_anywhere_in_sandbox` is `[]` — the pin is not in the sandbox because **it is in the live
|
||||||
|
tree**: `/root/.helm/data/held/stackchain-home.html.sha256` moved from
|
||||||
|
`5b6d70716153c3f4bd82045d5d71edf88f793a240ce1275c3dfca5759d0ee297` / mtime_ns `1791478687099553071` to
|
||||||
|
`fdd45f74f59dc7de869d290205e8ed3dc19139e3fbbae9e88f8c6b295ca25dca` / mtime_ns `1791940472306970986`
|
||||||
|
(guard `q55_guard.py compare after-pwr-witness`: `"moved": true`), restored byte-exact by
|
||||||
|
`q55_guard.py restore` (rc 0).
|
||||||
|
|
||||||
|
**`collector-pwdir-named.py` under the same witness** (`out-pwn-wit.txt`): rc 0, the same shape — the
|
||||||
|
live pin moves to `302bfda56b2d858c907345b6c9e1afaee8ac3849d3e45ca7d68ae24428dbe21d` / mtime_ns
|
||||||
|
`1791940506504841121`, restored byte-exact (rc 0). The defect does not depend on the constant's name;
|
||||||
|
the rename only decides which check catches it.
|
||||||
|
|
||||||
|
## The fix
|
||||||
|
|
||||||
|
A **precondition**, not a check: after Q46's parse guard (so an unparseable subject is still refused by
|
||||||
|
Q46 first) and after the `import ast as _q39_ast` line, **before the Q39 sandbox is created, before the
|
||||||
|
admission probe, and before any leg**. If the subject's own AST reads the `pwd` module, the run is
|
||||||
|
refused: exit 2, a dated message naming the subject, every `pwd` site with its line number, why the
|
||||||
|
redirect cannot reach it, and the live snapshot read before and after. No `check()` is registered — a
|
||||||
|
refusal fails the RUN, not a suite result, so the green pair still reports exactly 176 passed / 0 failed.
|
||||||
|
|
||||||
|
The refusal text (cell a, verbatim opening): *"Q45S9 REFUSAL: the subject collector
|
||||||
|
/root/crucible/q55/collector-pwdir-root.py reads the home directory from the pwd module (pwd.getpwuid
|
||||||
|
at line 30), a channel the Q52 HOME/USERPROFILE redirect cannot reach: pwd.getpwuid answers from the
|
||||||
|
user database, not from HOME, so the Q52 home-channel check is satisfied by a mechanism the subject
|
||||||
|
never uses and the composed root can be the live root (measured: the --q52-witness run writes the live
|
||||||
|
held pin from an admitted pwd subject). Refused at 2026-10-14T01:24:23.743346+00:00, after the Q46
|
||||||
|
parse guard, before the Q39 sandbox is created, before the admission probe, and before any leg."*
|
||||||
|
|
||||||
|
## The matrix, all under the final gate digest
|
||||||
|
|
||||||
|
25 cells (`run_matrix_q55.py` → `out/Q55-MATRIX.json`, 12 cells; `run_matrix2_q55.py` →
|
||||||
|
`out/Q55-MATRIX2.json`, 13 cells; every cell run with the live-object guard around it, compare before
|
||||||
|
and after, restore byte-exact on a move):
|
||||||
|
|
||||||
|
| cell | gate × subject | rc | result |
|
||||||
|
|---|---|---|---|
|
||||||
|
| a | armed × `collector-pwdir-root.py` | 2 | refusal, stdout **0 B** (`e3b0c442…`), live delta `{}` |
|
||||||
|
| b | armed × `collector-pwdir-root.py --q52-witness` | 2 | the same refusal — the witness never runs |
|
||||||
|
| c | armed × `collector-pwdir-named.py` | 2 | refusal (`pwd.getpwuid at line 30`), live delta `{}` |
|
||||||
|
| d | armed × `collector-home.py` | 1 | 175/1, Q43 FAIL — **identical to the shipped gate on the same fixture** (see below) |
|
||||||
|
| e | armed × `collector-expanduser.py` | 1 | 175/1, Q43 FAIL — identical |
|
||||||
|
| f | **disarmed** × `collector-pwdir-root.py --q52-witness` | 0 | the escape reproduces: probe `HELD_DIR=/root/.helm/data/held`, pin in sandbox `[]`, live pin `5b6d7071…` → `ced810a9ca9f47eabcc3d0429bd903b35dfb9f4cde43a8cca4bd73698545d437` / mtime_ns `1791941106156792591`, restored byte-exact |
|
||||||
|
| g | **disarmed** × `collector-pwdir-root.py` | 1 | 175/1, Q52 **PASS**, Q45 FAIL — the shipped behaviour, reproduced |
|
||||||
|
| h | armed × the real collector ×5 | 0 | **176 passed / 0 failed** each, 14,044 B stdout / 1,216 B stderr, live delta `{}` each, 20.5–24.1 s |
|
||||||
|
| r01–r09 | the nine refusal fixtures (n1–n5, noparse, dep-unparseable, undecodable, broken_dep) | 2 | refused each, live delta `{}` |
|
||||||
|
| r10–r11 | `collector-latin1.py`, `collector-bom.py` | 0 | 176/0 at 14,044 B each, live delta `{}` |
|
||||||
|
|
||||||
|
**Regressions 11/11; the green pair is 176/0 — the Q52 count exactly, no check added.**
|
||||||
|
|
||||||
|
Cells d/e are the honest result, not a defect of the fix: the two covered-shape fixtures fail **Q43**
|
||||||
|
("the subject's own default destination resolves to the live snapshot the gate protects") in a default
|
||||||
|
run, and they fail it **identically under the shipped Q52 gate** — measured side by side this run
|
||||||
|
(`out/shipped-home.txt` vs `out/fixed-home.txt`, rc 1 / 175/1 both, the PASS/FAIL lists identical
|
||||||
|
modulo the per-invocation sandbox directory names). Their green path is the witness run, which the fix
|
||||||
|
does not touch: **armed × `collector-home.py --q52-witness` under the final gate** puts the pin at
|
||||||
|
`q52-home/.helm/data/held/stackchain-home.html.sha256` **inside the sandbox**
|
||||||
|
(`copy_pin_files_anywhere_in_sandbox` non-empty) with the live pin unmoved (`5b6d7071…`, mtime_ns
|
||||||
|
`1791478687099553071`). The covered shapes are not over-refused and stay protected; the pwd shapes are
|
||||||
|
refused before anything runs.
|
||||||
|
|
||||||
|
## Live state, sandboxes, cleanup
|
||||||
|
|
||||||
|
**Live, delta 0 on bytes/sha256/mtime across all 25 cells** (`q55_guard.py compare final2`, `"moved":
|
||||||
|
false`): snapshot `/root/.helm/data/snapshot.json` 14,039 B /
|
||||||
|
`aa842b0fd88037f83115b4a54d617f27666f4de06ef84f9a64c8fd0a5f04656d` / mtime_ns `1791479579475780303`;
|
||||||
|
held pin 120 B / `5b6d70716153c3f4bd82045d5d71edf88f793a240ce1275c3dfca5759d0ee297` / mtime_ns
|
||||||
|
`1791478687099553071`. **ctime moved on the pin** across the two deliberately-live witness cells (the
|
||||||
|
guard restores of cells f and the shipped-gate witness runs) — the durable trace a restore cannot undo,
|
||||||
|
disclosed, not hidden.
|
||||||
|
|
||||||
|
**Sandboxes, both ways per row** (`census_q55.py` → `out/Q55-CENSUS.json`): the `q55` tree **0 / 0**
|
||||||
|
(shallow glob / recursive `rglob`) — the refusal exits before the sandbox is created, so it leaves
|
||||||
|
nothing. Across `/root/crucible` the pre-existing E14 leak was re-measured at **26** (19 `helm-legs-*`
|
||||||
|
+ 7 `helm-q39-*`, the same distribution as Run 20's census) and **all 26 were removed** by
|
||||||
|
`cleanup_sandboxes.py` (`out/Q55-CLEANUP.json`: 26 removed, 0 failed, **842,401 bytes** reclaimed,
|
||||||
|
after-count 0 both ways). The `q55` tree's own `__pycache__` (the C32 class) was removed in the same
|
||||||
|
pass. These are per-invocation sandboxes from crashed or early-exited runs; no gate run needs them and
|
||||||
|
none sat in a live path.
|
||||||
|
|
||||||
|
**Deployed `/root/.helm/bin/` untouched** (measured after the matrix): `helm-collect.py ee0b36a82f23…`,
|
||||||
|
`helm-render.py 5c67473d8928…`, `helm-selftest.py 7ed1bca8f9da…`, `helm-validate.py 12570786ba70…`,
|
||||||
|
`summarize.py 738699986778…`; `grep -c HELM_COLLECTOR` on the deployed gate = **0** — Q40 stands, so
|
||||||
|
none of Q31–Q45 §9 is in production. Disk 45 G avail of 154 (71 % used).
|
||||||
|
|
||||||
|
## Adoption
|
||||||
|
|
||||||
|
The §4 `cp` is **collector `4e45021d80395d3f…` + gate
|
||||||
|
`cd43284ff009d1cceb3aaa6a65b701d1076eaf801e32b705672601b4122b746a`**, superseding `78b290ec…`
|
||||||
|
(`e4967da9…`, `a951dbe8…`). Alexander's, unchanged.
|
||||||
|
|
||||||
|
## Limits
|
||||||
|
|
||||||
|
- The refusal is a refusal, not coverage: a subject that reads `pwd.getpwuid` for an innocent reason
|
||||||
|
(a username lookup, say) is refused with the covered shapes. The conservative direction, and correct
|
||||||
|
for a detector — the same trade the Q35 marker check makes.
|
||||||
|
- The inventory is static (Q46's limit): a subject that computes its home at runtime from a source the
|
||||||
|
AST cannot see — reading `/etc/passwd` directly, `os.stat`-ing a crafted path — is outside it.
|
||||||
|
- The covered set is exactly the redirect's reach: `.home()`, `expanduser` (both forms).
|
||||||
|
- The general shape stays open: **any** home channel the redirect cannot reach is the class. The pwd
|
||||||
|
instance is closed by this precondition; the class is not.
|
||||||
|
|
||||||
|
## Defects of mine, logged first
|
||||||
|
|
||||||
|
1. **Matrix part 1's d/e rows were void.** They pointed `HELM_COLLECTOR` at
|
||||||
|
`collector-home.py` / `collector-expanduser.py` **before those fixtures were copied into the `q55`
|
||||||
|
tree**, so both rows are a Q35 refusal against a path that did not exist — 0.2 s, rc 2, "carries no
|
||||||
|
destination rule". Re-run correctly in part 2 after the `cp`; the part-1 rows are kept in
|
||||||
|
`out/Q55-MATRIX.json` with this note rather than deleted.
|
||||||
|
2. **Part 1's `restored` field compared a 12-character sha prefix against the full digest**, so it
|
||||||
|
printed `false` on every restored row including the unmoved ones. Part 2 compares full digests
|
||||||
|
(`restored_ok: true` on all 13 cells) and the durable evidence is the final `compare final2`
|
||||||
|
(`"moved": false`).
|
||||||
|
3. **Part 1's Q45 filter was wrong**: it matched `startswith("FAIL Q45")` against the two-space
|
||||||
|
rendering `FAIL Q45 …`, so it missed the one row it existed to find. The FAIL line is in
|
||||||
|
`out/g-disarmed-pwr.out` and is quoted above.
|
||||||
|
4. **My d/e expectation of 176/0 was assumed, not measured.** I expected the covered shapes to run
|
||||||
|
green under the fixed gate; the shipped-gate baseline I should have taken first shows they fail Q43
|
||||||
|
identically. The measurement was taken after, side by side, and the expectation was wrong — logged,
|
||||||
|
not smoothed.
|
||||||
|
5. **The gate label was first written `Q55`** and renamed to `Q45S9` before any readback or push — the
|
||||||
|
lane's own collision lesson, applied to my own fresh artifact rather than discovered later. The
|
||||||
|
digests above are post-rename; the matrix logs were re-run in full under them.
|
||||||
|
|
||||||
|
## What this owes
|
||||||
|
|
||||||
|
Nothing from any counterparty. The next Helm unit remains a decision, not a task: the §4 adoption `cp`
|
||||||
|
above, the `manifest_match` pin (stale since 2026-10-08), Q13's publication call, Q9's checksum
|
||||||
|
handover, Q19's onboarding — all Alexander's. Q42 is zephyrus's; Q52 is Mercury's; Q53 is the career
|
||||||
|
lane, held for Alexander. A task invented to fill the lane would be invented work.
|
||||||
Reference in new issue
Block a user