helm: the Q45 s9 follow-on closed -- the home-directory channel (collector 4e45021d unchanged; gate 78b290ecf2d93e0c3087e5ede8baef5c10ca52986706d5da69a13a619dbdd9f, 140,365 B / 2,677 lines / 126 registrations, on the Q51 gate e4967da9, diff 6 hunks + 122 - 3 = 2,677 lines; disarmed twin be7b306a one line different)
The last runtime-resolution route in Q45 s7-limit-1's family with neither a literal nor an ast-visible env read: HELM_HOME = Path.home() / ".helm" composes the live root because CPython's posixpath.expanduser reads HOME itself. The channel is live in the shipped subject -- the real collector calls Path.home() twice (its himalaya search). Mechanism: an ast inventory of home-database reads (_q52_home_sites, :786) and, inside Q50's one rebuild function (two callers, probe :977 / legs :1162), a redirect of HOME + USERPROFILE to <target>/q52-home -- a redirect, not a refusal -- with a conditional Q52 NOTE and one new check("Q52", :1204). A --q52-witness extra flag (:1261) runs the copy with --deploy --pin --skip-tests --out <sandbox dest>, the pin shape the suite itself refuses (Q44's rule), because the class's write path is unreachable from the green run.
Measured: green 5/5 at 176 passed / 0 failed (= 175 + 1), 14,044 B stdout, live delta {}; the real collector's own .home() read makes the check non-vacuous. Discriminator, five cells under the digest-verified guard: armed x collector-home.py (d62888c4) -- the copy resolves inside the sandbox and the pin lands at q52-home/.helm/data/held/stackchain-home.html.sha256 in the sandbox, live delta 0; armed x collector-expanduser.py (99c49d39) -- same; disarmed x home -- HOME stays /root, the copy resolves HELM_HOME=/root/.helm and the live pin moves to sha256 caff1f504855264a / mtime_ns 1791907100434371499, restored byte-exact; disarmed without the witness flag -- the foot's own check fails (FAIL Q52, home_reads=['.home()'] HOME='/root', 174 passed / 2 failed); direct invocation, ambient env -- the live pin moves (the class's floor). Regressions 11/11 hold; live delta 0 on bytes/sha256/mtime across all 21 invocations (snapshot 14,039 B / aa842b0f, held pin 120 B / 5b6d7071); ctime moved on both objects (1791907371554631194), the guard restores of the two deliberately-live rows -- disclosed. Final tree 0 sandboxes; deployed /root/.helm/bin untouched (7ed1bca8, grep -c HELM_COLLECTOR = 0 -- Q40 stands, so none of Q31-Q52 is in production); disk 46 GiB free of 154.
Five defects of mine logged first in the deliverable: the witness's missing local subprocess import; the witness anchored before the leg copy was written; the witness's live census reading the held directory (IsADirectoryError); the NOTE claiming a redirect that had not fired (the disarmed twin's own FAIL contradicted it -- the NOTE is now derived from the actual env); and the first matrix measuring nothing because the real collector pins only under --pin, which the suite refuses. Limits stated as the item's own: the original-subject legs inherit the ambient environment (Q48's residual); pwd.getpwuid().pw_dir bypasses HOME (the inventory names it, the redirect cannot relocate it); the inventory is static (Q46's limit).
Files: recoveries/helm-home-channel-guard.md (13,173 B / sha256 3f813ca23202590c675f7ac7587f0d5e7d8b0ff9ee9f4f0711996530a2d6d7db), WORK-QUEUE.md (897,771 B), checkins/2026-10-13-merlin.md (Run 18). Scratch tree /root/crucible/q52/ not committed. No deployment, decisions/ and inbox/ untouched, no another-party file edited, no secret printed. The Helm lane's executable set is empty again; the adoption cp (collector 4e45021d + gate 78b290ec, superseding e4967da9) remains Alexander's.
This commit is contained in:
1 parent
0c190a4cec
commit
c559d33131
3 files changed
+396
No files matched your search
@@ -7586,3 +7586,68 @@ claim of mine is corrected. 2026-10-13 15:37Z (control plane; `date -u` read at
|
||||
**nothing** (no commit exists between `b7601a0` 14:15:22Z and `7d76bf2` 15:14:24Z), which is the v1.3 cost behaving as
|
||||
its note describes. If Alexander would rather the pipe's own queue-only blocks did not re-arm the monitor at all, that is
|
||||
a change to `troupe_pipe_monitor.py` and his call — not one this tick takes on its own.
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## Q45 §9 follow-on — the home-directory channel *(Helm lane; Merlin)*
|
||||
|
||||
**The named follow-on, closed.** The item Merlin's Q45 §7/Q45 §9 follow-on line left open and every check-in
|
||||
since has carried: *"resolve every path the subject declares **at runtime**, rather than rewriting the literals it
|
||||
declares statically."* Q48 discharged the environment-value and argv halves; **the home-directory channel was the
|
||||
remainder and is now closed.** Deliverable: `recoveries/helm-home-channel-guard.md` (**13,173 B / sha256
|
||||
`3f813ca23202590c675f7ac7587f0d5e7d8b0ff9ee9f4f0711996530a2d6d7db`**, this host, measured after the write below**). Scratch tree `/root/crucible/q52/`; nothing deployed,
|
||||
no `cp` into `/root/.helm/bin/`, `decisions/` and `inbox/` untouched, no another-party file edited, no secret printed.
|
||||
|
||||
**The class.** `HELM_HOME = Path.home() / ".helm"` — a root composed from the **user database**. CPython's
|
||||
`posixpath.expanduser` reads `HOME` itself and `Path.home()` calls it, so the composed path is the live root
|
||||
(`/root/.helm`) while Q45's literal scan sees only `".helm"` (not absolute), Q48's env inventory finds no
|
||||
`os.environ` access in the subject's own AST, Q49's default redirect keys on that same inventory, and Q48's argv
|
||||
rule sees no root argument. The channel is live in the shipped subject: the real collector calls `Path.home()`
|
||||
**twice** (`/root/crucible/q48/helm-collect.py` lines 297–298, its himalaya config search), on the collector
|
||||
byte-identical since Q33 (`4e45021d80395d3f…`, 720 / 29,954).
|
||||
|
||||
**The pair.** Collector untouched (measured by sha256). Gate **`78b290ecf2d93e0c…`** (**140,365 B / 2,677 lines /
|
||||
126 registrations**) on the Q51 gate `e4967da9…` (134,427 / 2,558 / 125);
|
||||
`out/DIFF-vs-q51.txt` = **169 lines = 6 hunks + 122 added − 3 removed**, and 2,558 + 122 − 3 = 2,677 exactly.
|
||||
Disarmed twin `be7b306a…` (140,390 / 2,677 / 126) — one line different. **Mechanism:** an ast inventory of
|
||||
home-database reads (`_q52_home_sites`, gate :786) and, inside Q50's one rebuild function (two callers, :977 probe /
|
||||
:1162 legs), a redirect of `HOME` + `USERPROFILE` to `<target>/q52-home` — a redirect, not a refusal — with a
|
||||
conditional `Q52 NOTE` on stderr and one new `check("Q52")` (:1204): when the subject reads the home database, the
|
||||
legs' `HOME` must be the sandbox home; vacuous otherwise. A `--q52-witness` extra flag (:1261) runs the copy with
|
||||
`--deploy --pin --skip-tests --out <sandbox dest>` — the pin shape the suite itself refuses (Q44's rule) — because
|
||||
the class's write path is structurally unreachable from the green run.
|
||||
|
||||
**Measured, not asserted.** Green pair **5/5**: rc 0, **176 passed / 0 failed** (= 175 + 1), 14,044 B stdout /
|
||||
1,216 B stderr, live delta `{}`, the real collector's own `.home()` read making the check non-vacuous
|
||||
(`PASS Q52 …`; `Q52 NOTE: … HOME redirected to <sandbox>/q52-home (/root -> <sandbox>/q52-home)`).
|
||||
**The discriminator, five cells under the digest-verified guard** (`out/Q52-MATRIX.json`): armed ×
|
||||
`collector-home.py` (`HELM_HOME = Path.home()/".helm"`, `d62888c4…`) — the copy resolves inside the sandbox, the
|
||||
pin lands at `q52-home/.helm/data/held/stackchain-home.html.sha256` **inside the sandbox**, live delta 0; armed ×
|
||||
`collector-expanduser.py` (`99c49d39…`) — same; **disarmed × home** — `HOME` stays `/root`, the copy resolves
|
||||
`HELM_HOME=/root/.helm`, the live pin moves to sha256 `caff1f504855264a…` / mtime_ns `1791907100434371499` and is
|
||||
restored byte-exact by the guard; disarmed without the witness flag — the foot's own check fails
|
||||
(`FAIL Q52 … home_reads=['.home()'] HOME='/root' expected='<sandbox>/q52-home'`, 174/2); direct invocation with
|
||||
the ambient environment — the live pin moves (the class's floor). Regressions **11/11**: nine refusals rc 2,
|
||||
`collector-latin1.py` / `collector-bom.py` green at 14,044 B, live delta `{}` on all eleven.
|
||||
|
||||
**Live state, delta 0 on bytes/sha256/mtime across all 21 invocations** (5 green + 5 matrix + 11 regressions):
|
||||
snapshot **14,039 B / `aa842b0fd88037f8…` / mtime_ns `1791479579475780303`**; held pin **120 B /
|
||||
`5b6d70716153c3f4…` / mtime_ns `1791478687099553071`**. **ctime moved on both objects**
|
||||
(`1791907371554631194`) — the guard restores of the two deliberately-live rows; disclosed, not hidden. Final tree
|
||||
0 sandboxes; deployed `/root/.helm/bin/` untouched (`7ed1bca8…`, `grep -c HELM_COLLECTOR` = **0** — Q40 stands, so
|
||||
none of Q31–Q52 is in production); disk 46 GiB free of 154.
|
||||
|
||||
**Limits, stated in the record as the item's first limits:** the original-subject legs still inherit the ambient
|
||||
environment (Q48's residual); a subject reading `pwd.getpwuid(...).pw_dir` directly bypasses `HOME` and the
|
||||
redirect cannot see it (the inventory names it); the inventory is static (Q46's limit); `USERPROFILE` is
|
||||
redirected alongside `HOME` as a no-op on this host, for portability.
|
||||
|
||||
**Consequence beside it (reply 36 §3, restated):** the run-wide Q44 instrument now fails on a *restore trace*
|
||||
alone — ctime is the only stamp separating `delta {}` from "moved" in the run-wide record. That is the
|
||||
conservative direction for a detector and is not reopened here.
|
||||
|
||||
**Open after this item, Helm lane:** the §4 adoption `cp` — now collector `4e45021d…` + gate **`78b290ec…`**
|
||||
(superseding `e4967da9…`) — Q9 (checksum handover), Q13 (wording fixes), Q19 (wizard onboarding), Q42
|
||||
(zephyrus's). **The Helm lane's executable set is empty again; the next Helm unit is a decision, not a task.**
|
||||
@@ -796,3 +796,101 @@ paths and sha256 digests. This run's commit is authored `Merlin <merlin@agents.i
|
||||
so the monitor sees it as counterparty content per Q36's v1.3 rule.
|
||||
|
||||
— Merlin
|
||||
|
||||
---
|
||||
|
||||
## Run 18 — the Q45 §9 follow-on: the home-directory channel
|
||||
|
||||
### Screen
|
||||
|
||||
Merlin, Helm lane, 2026-10-13, this host. Item: **the named Q45 §9 follow-on** — *resolve every path the subject
|
||||
declares at runtime, rather than rewriting the literals it declares statically* — the one Helm unit the queue's own
|
||||
open set carried as mine and unclaimed (the pipe's 15:37Z block lists it by name; Q52 is Mercury's and is not
|
||||
touched here). Deliverable: `recoveries/helm-home-channel-guard.md` (**13,173 B / sha256
|
||||
`3f813ca23202590c675f7ac7587f0d5e7d8b0ff9ee9f4f0711996530a2d6d7db`**, this host). Queue block appended to
|
||||
`WORK-QUEUE.md` (897,771 B after the append). Scratch tree `/root/crucible/q52/`; nothing deployed, no `cp` into
|
||||
`/root/.helm/bin/`, `decisions/` and `inbox/` untouched, no another-party file edited, no secret.
|
||||
|
||||
### Did
|
||||
|
||||
**The class, and why every existing mechanism missed it.** `HELM_HOME = Path.home() / ".helm"` composes the live
|
||||
root at runtime from the user database: CPython's `posixpath.expanduser` reads `HOME` itself, so Q45's literal scan
|
||||
sees only `".helm"` (not absolute), Q48's env inventory finds no `os.environ` access in the subject's own AST,
|
||||
Q49's default redirect keys on that same inventory, and Q48's argv rule sees no root argument. The channel is live
|
||||
in the shipped subject: the real collector calls `Path.home()` **twice** (lines 297–298 of
|
||||
`/root/crucible/q48/helm-collect.py`, `4e45021d…`, 720 / 29,954) for its himalaya config search.
|
||||
|
||||
**The pair.** Collector byte-identical (measured by sha256) — every change is in the gate. Gate **`78b290ecf2d93e0c…`**
|
||||
(**140,365 B / 2,677 lines / 126 registrations**) on the Q51 gate `e4967da9…` (134,427 / 2,558 / 125);
|
||||
`out/DIFF-vs-q51.txt` = **169 lines = 6 hunks + 122 added − 3 removed**, 2,558 + 122 − 3 = 2,677 exactly.
|
||||
Disarmed twin `be7b306a…` (140,390 / 2,677 / 126), one line different. **Mechanism:** an ast inventory of
|
||||
home-database reads (`_q52_home_sites`, :786) and, inside Q50's one rebuild function (two callers — probe :977,
|
||||
legs :1162), a redirect of `HOME` + `USERPROFILE` to `<target>/q52-home`; a conditional `Q52 NOTE` on stderr and
|
||||
one new `check("Q52")` (:1204). A `--q52-witness` extra flag (:1261) runs the copy with `--deploy --pin
|
||||
--skip-tests --out <sandbox dest>` — the pin shape the suite refuses (Q44's rule) — because the class's write path
|
||||
is structurally unreachable from the green run.
|
||||
|
||||
**Fixtures, verified against the interpreter first** (`make_fixtures_q52.py`): `collector-home.py`
|
||||
(`d62888c4c9470d30…`, 29,956 B) and `collector-expanduser.py` (`99c49d395467709d…`, 29,974 B), each
|
||||
`py_compile`-clean and each resolving `HELM_HOME /tmp/<scratch>/.helm` under a scratch HOME — the class measured,
|
||||
not asserted.
|
||||
|
||||
**Green pair, five consecutive invocations** (`run_green_q52.py`): rc 0 each, **176 passed / 0 failed** (= 175 + 1),
|
||||
ALL GATES PASS, 14,044 B stdout / 1,216 B stderr, 12.34–12.73 s, live delta `{}`. The real collector's own
|
||||
`.home()` read makes the check non-vacuous: `PASS Q52 the subject's home-directory reads resolve inside the sandbox
|
||||
when the subject reads the home database`, `Q52 NOTE: … HOME redirected to <sandbox>/q52-home (/root -> <sandbox>/q52-home)`
|
||||
(`out/q52-green-1.{out,err}`). Q44 and Q50 stay green on the same runs.
|
||||
|
||||
**The discriminator, five cells under the digest-verified guard** (`run_matrix_q52.py` → `out/Q52-MATRIX.json`):
|
||||
armed × `collector-home.py` — the copy resolves `HELM_HOME=<sandbox>/q52-home/.helm`, the pin lands at
|
||||
`q52-home/.helm/data/held/stackchain-home.html.sha256` **inside the sandbox**, live delta 0; armed ×
|
||||
`collector-expanduser.py` — same, on the second channel; **disarmed × home** — `HOME` stays `/root`, the copy
|
||||
resolves `HELM_HOME=/root/.helm` and the live pin moves to sha256 `caff1f504855264a…` / mtime_ns
|
||||
`1791907100434371499`, restored byte-exact by the guard; **disarmed without the witness flag** — the foot's own
|
||||
check fails (`FAIL Q52 … home_reads=['.home()'] HOME='/root' expected='<sandbox>/q52-home'`, 174 passed / 2
|
||||
failed); **direct invocation, ambient env** — the live pin moves, guard restores (the class's floor). Prevention
|
||||
and detection measured separately.
|
||||
|
||||
**Regressions 11/11** (`run_regressions_q52.py` → `out/Q52-REGRESSIONS.json`): nine refusals rc 2 (n1–n5, noparse,
|
||||
dep-unparseable, undecodable, broken_dep); `collector-latin1.py` and `collector-bom.py` green at 14,044 B; live
|
||||
delta `{}` on all eleven. `VERDICT: ALL REGRESSIONS HOLD`.
|
||||
|
||||
**Live state, delta 0 on bytes/sha256/mtime across all 21 invocations** (5 green + 5 matrix + 11 regressions):
|
||||
snapshot **14,039 B / `aa842b0fd88037f83115b4a54d617f27666f4de06ef84f9a64c8fd0a5f04656d` / mtime_ns
|
||||
`1791479579475780303`**; held pin **120 B /
|
||||
`5b6d70716153c3f4bd82045d5d71edf88f793a240ce1275c3dfca5759d0ee297` / mtime_ns `1791478687099553071`**.
|
||||
**ctime moved on both objects** (`1791907371554631194`) — the guard restores of the two deliberately-live rows;
|
||||
disclosed, not hidden. Final tree 0 sandboxes; deployed `/root/.helm/bin/` untouched (`7ed1bca8…`, `grep -c
|
||||
HELM_COLLECTOR` = **0** — Q40 stands); disk 46 GiB free of 154.
|
||||
|
||||
**Five defects of mine, logged first in the deliverable:** the witness's missing local `subprocess` import; the
|
||||
witness anchored before the leg copy was written; the witness's live census reading the held *directory*
|
||||
(`IsADirectoryError`); the NOTE claiming a redirect that had not fired (the disarmed twin's own FAIL contradicted
|
||||
it — the NOTE is now derived from the actual environment); and the first matrix measuring nothing because the real
|
||||
collector pins only under `--pin`, which the suite refuses — the witness flag exists because of that.
|
||||
|
||||
### Blocked
|
||||
|
||||
Nothing in my lane. Same standing gates, all Alexander's: the **§4 adoption `cp`** — now collector `4e45021d…` +
|
||||
gate **`78b290ec…`** (superseding `e4967da9…`); Q40's finding stands (the deployed gate `7ed1bca8…` reads
|
||||
`HELM_COLLECTOR` zero times), so none of Q31–Q52's protection is in production until it lands — Q9 (checksum
|
||||
handover), Q13 (wording fixes), Q19 (wizard onboarding), Q42 (zephyrus's).
|
||||
|
||||
### Next
|
||||
|
||||
1. **The pipe's verification of this run**, when it lands. Everything re-runs from `/root/crucible/q52/`:
|
||||
`make_fixtures_q52.py`, `patch_gate_q52.py`, `run_green_q52.py`, `run_matrix_q52.py`,
|
||||
`run_regressions_q52.py`, `final_state.py`.
|
||||
2. **The Helm lane's executable set is now empty.** The Q45 §9 follow-on is closed; the adoption, Q9, Q13/Q19 and
|
||||
Q42 are human- or counterparty-gated. **No new item opened** — the next Helm unit is a decision, not a task.
|
||||
The two limits the record states as the item's own — the original-subject legs' ambient environment (Q48's
|
||||
residual) and `pwd.getpwuid()` bypassing `HOME` — are the only named candidates, and neither is a task until
|
||||
someone says the class matters.
|
||||
|
||||
### Ask
|
||||
|
||||
Alexander: nothing blocking. The standing items, consolidated: (a) rule on the **§4 adoption `cp`** — collector
|
||||
`4e45021d…` + gate **`78b290ec…`**, the pair that makes `python3 helm-selftest.py` green with no environment and
|
||||
puts Q31–Q52's protection into production (the deployed gate today has no subject-selection path at all); (b) the
|
||||
**`manifest_match` pin** decision (stale since 2026-10-08); (c) the **Q13 publication call** (item 13's decided
|
||||
note — escalate, do not edit the live page — still controls over its `proposed_action`).
|
||||
@@ -0,0 +1,233 @@
|
||||
# Q52 — the home-directory channel
|
||||
|
||||
**Merlin, Helm lane, 2026-10-13.** The last route in Q45's §7-limit-1 family
|
||||
that carries neither a literal nor an ast-visible environment read. Scratch
|
||||
tree `/root/crucible/q52/`; nothing deployed, no `cp` into `/root/.helm/bin/`,
|
||||
`decisions/` and `inbox/` untouched, no another-party file edited, no secret.
|
||||
|
||||
> Screen. Every claim below carries a path, a hash, a URL, or a command with
|
||||
> its output, or the word `unverified`.
|
||||
|
||||
---
|
||||
|
||||
## 1. The class
|
||||
|
||||
The subject resolves its root from the **user database**:
|
||||
|
||||
```python
|
||||
HELM_HOME = Path.home() / ".helm"
|
||||
```
|
||||
|
||||
CPython's `posixpath.expanduser` reads `HOME` from `os.environ` **itself**,
|
||||
and `Path.home()` calls it, so:
|
||||
|
||||
- **Q45's literal relocation** sees no absolute live-root literal — the only
|
||||
string is `".helm"`, which does not resolve inside the live root on its
|
||||
own;
|
||||
- **Q48's environment inventory** walks the subject's own AST for
|
||||
`os.environ` accesses and finds none — the read happens inside the
|
||||
interpreter;
|
||||
- **Q49's default redirect** keys on the same inventory;
|
||||
- **Q48's argv rule** sees no root argument;
|
||||
- **`__file__`** needs no mechanism (Q48's note) but is not this class;
|
||||
|
||||
yet the composed path is the live root `/root/.helm`, and the subject pins
|
||||
the live held directory. The channel is live in the shipped subject, not
|
||||
hypothetical: the real collector calls `Path.home()` **twice** — its himalaya
|
||||
config search, `/root/crucible/q48/helm-collect.py` lines 297–298
|
||||
(`grep -n 'Path.home()'` → 2 hits), on the collector byte-identical since Q33
|
||||
(`4e45021d80395d3f…`, 720 lines / 29,954 B, sha256 measured this run).
|
||||
|
||||
## 2. The pair
|
||||
|
||||
**Collector untouched** — every change is in the gate, as the item requires
|
||||
(`4e45021d…` re-measured by sha256).
|
||||
|
||||
| | bytes / lines | registrations | sha256 |
|
||||
|---|---|---|---|
|
||||
| Q51 gate (base) | 134,427 / 2,558 | 125 | `e4967da9bdc45378…` |
|
||||
| **Q52 gate (shipped)** | **140,365 / 2,677** | **126** | **`78b290ecf2d93e0c…`** |
|
||||
| Q52 disarmed twin | 140,390 / 2,677 | 126 | `be7b306afa7c5b3a3…` |
|
||||
|
||||
`out/DIFF-vs-q51.txt` = **169 lines = 6 hunks + 122 added − 3 removed**, and
|
||||
2,558 + 122 − 3 = 2,677 exactly (counted by `grep -c`, not asserted).
|
||||
Registration count `grep -cE '^[[:space:]]*check\('` = 126; the disarmed twin
|
||||
differs from the shipped gate in exactly one line (the redirect cut), as in
|
||||
Q50's and Q51's pairs.
|
||||
|
||||
**The mechanism, one function, two call sites, one check.**
|
||||
|
||||
1. **The inventory** — `_q52_home_sites()` (gate line 786): every
|
||||
home-database read in the subject's own AST — `Path.home()`, a bare
|
||||
`.home()`, `expanduser` (attribute or bare), `pwd.*`. Module-level
|
||||
`_q52_home_reads` holds the result for both rebuild callers.
|
||||
2. **The redirect** — inside `_q50_rebuild_env` (line 815), the same function
|
||||
that already serves the admission probe (Q50) and the legs: when the
|
||||
subject reads the home database, `HOME` **and** `USERPROFILE` are
|
||||
redirected to `<target>/q52-home`. A redirect, not a refusal; the
|
||||
collector's own `Path.home()` reads keep working, against the sandbox's
|
||||
home. Probe caller at :977, legs caller at :1162 — both take the new
|
||||
four-tuple, so the two paths cannot drift.
|
||||
3. **The NOTE** — one `Q52 NOTE` on stderr, derived from the **actual**
|
||||
environment, so it cannot claim a redirect that did not fire (see defect 4).
|
||||
4. **The check** — `check("Q52", …)` at :1204, registered at the legs' site:
|
||||
when the subject reads the home database, the legs' `HOME` must be the
|
||||
sandbox home; with no home-database read it passes vacuously and the NOTE
|
||||
says so.
|
||||
5. **The witness** — `--q52-witness` (line 1261, after the leg-copy write):
|
||||
runs the copy the way the suite never does — `--deploy --pin --skip-tests
|
||||
--out <sandbox dest>` — under the rebuilt environment, prints one JSON
|
||||
object and exits before the foot. The green run never passes the flag, so
|
||||
the block cannot affect it. It exists because the suite itself refuses a
|
||||
`--pin` leg (Q44's rule), so the pin shape is unreachable from the suite
|
||||
(defect 5).
|
||||
|
||||
## 3. The fixtures, verified against the interpreter first
|
||||
|
||||
Two transforms of the real collector (`make_fixtures_q52.py`, asserted count
|
||||
1 each):
|
||||
|
||||
| fixture | transform | bytes | sha256 |
|
||||
|---|---|---|---|
|
||||
| `collector-home.py` | `HELM_HOME = Path.home() / ".helm"` | 29,956 | `d62888c4c9470d30…` |
|
||||
| `collector-expanduser.py` | `HELM_HOME = Path(os.path.expanduser("~")) / ".helm"` | 29,974 | `99c49d395467709d…` |
|
||||
|
||||
The interpreter's own verdict, measured by `make_fixtures_q52.py`'s probe
|
||||
(`runpy` + a scratch `HOME`): both fixtures `rc 0` with
|
||||
`HELM_HOME /tmp/<scratch>/.helm` and `HELD_DIR /tmp/<scratch>/.helm/data/held`
|
||||
— **each resolves inside a scratch HOME**, and with the ambient HOME (`/root`)
|
||||
the same composition is the live root. That is the class, measured rather
|
||||
than asserted.
|
||||
|
||||
## 4. The green pair, five consecutive invocations
|
||||
|
||||
`run_green_q52.py` (subject `/root/crucible/q48/helm-collect.py`,
|
||||
`Q48_ROOT` unset; logs `out/q52-green-{1..5}.{out,err}`, artifact
|
||||
`out/Q52-GREEN.json`): **rc 0 each, 176 passed / 0 failed**, `VERDICT: ALL
|
||||
GATES PASS`, **14,044 B stdout / 1,216 B stderr**, 12.34–12.73 s, live delta
|
||||
`{}` on all five. 176 = 175 + 1; no pre-existing check moved.
|
||||
|
||||
The control that matters: the real collector **reads the home database**, so
|
||||
the redirect fires on every green run and the check is non-vacuous —
|
||||
|
||||
```
|
||||
PASS Q52 the subject's home-directory reads resolve inside the sandbox when the subject reads the home database
|
||||
Q52 NOTE: the subject reads the home database at 1 site(s) (.home()); HOME redirected to
|
||||
/root/crucible/q52/helm-legs-abis_h7c/q52-home (/root -> /root/crucible/q52/helm-legs-abis_h7c/q52-home)
|
||||
```
|
||||
|
||||
(`out/q52-green-1.out`, `out/q52-green-1.err`). The himalaya source is
|
||||
`UNREACHABLE` either way — the binary is absent from this host — so the
|
||||
redirect moves an already-empty search, and `Q44`/`Q50` stay green:
|
||||
`PASS Q50 the admission probe's spawn moved neither live object`, `PASS Q44
|
||||
the live held directory is unchanged by every leg in the run`.
|
||||
|
||||
## 5. The discriminator, five cells, every one under the guard
|
||||
|
||||
`run_matrix_q52.py` → `out/Q52-MATRIX.json` (guard: `q52_guard.py`, refuses
|
||||
to restore unless the backup digest matches the recorded `5b6d7071…`).
|
||||
|
||||
| row | gate | subject | rc | live moved | what the witness shows |
|
||||
|---|---|---|---|---|---|
|
||||
| `q52-armed-home` | Q52 `78b290ec…` | `collector-home.py` | 0 | **no** | `HOME_witness_env` = `<sandbox>/q52-home`; the copy's `HELM_HOME=<sandbox>/q52-home/.helm`, `HELD_DIR=<sandbox>/q52-home/.helm/data/held`; the pin landed at `q52-home/.helm/data/held/stackchain-home.html.sha256` **inside the sandbox**; live snapshot `14,039 B / aa842b0f…`, live pin `120 B / 5b6d7071…` — unchanged |
|
||||
| `q52-armed-expand` | Q52 `78b290ec…` | `collector-expanduser.py` | 0 | **no** | same, on the `expanduser` channel (2 inventory sites: `.home()`, `expanduser` — the collector's own himalaya search plus the transform) |
|
||||
| `q52-dis-home` | Q52 disarmed `be7b306a…` | `collector-home.py` | 0 | **YES** | `HOME_witness_env` = `/root`; the copy resolves `HELM_HOME=/root/.helm` — the LIVE root; the live pin moved to sha256 `caff1f504855264a…` / mtime_ns `1791907100434371499`, restored byte-exact by the guard |
|
||||
| `q52-dis-check` | Q52 disarmed `be7b306a…` | `collector-home.py` | 1 | no | the foot's own check fails: `FAIL Q52 … home_reads=['.home()'] HOME='/root' expected='<sandbox>/q52-home'`, `174 passed, 2 failed` |
|
||||
| `q52-raw-home` | none — direct invocation | `collector-home.py` | 0 | **YES** | the class with nothing in the path: `--deploy --pin --skip-tests --out <scratch>`, ambient env — the live pin moves, guard restores |
|
||||
|
||||
Prevention and detection are measured separately: the armed rows redirect
|
||||
and the pin lands in the sandbox; the disarmed row, same subject, writes
|
||||
live; the check fails when the redirect is cut. The class is real, one
|
||||
mechanism away, and the mechanism closes it.
|
||||
|
||||
## 6. Regressions, 11/11
|
||||
|
||||
`run_regressions_q52.py` → `out/Q52-REGRESSIONS.json`: `n1–n5`, `noparse-with-markers.py`,
|
||||
`dep-unparseable.py`, `collector-undecodable.py`, `broken_dep.py` all **rc 2**
|
||||
(Q39 refusals / Q46 parse guard / repair 3 / Q47 read path / Q35 source
|
||||
precondition); `collector-latin1.py` and `collector-bom.py` **green at 14,044
|
||||
B** — the read-path fix's subjects unaffected. Live delta `{}` on all eleven.
|
||||
`VERDICT: ALL REGRESSIONS HOLD`.
|
||||
|
||||
## 7. Live state
|
||||
|
||||
**Delta 0 on bytes / sha256 / mtime_ns across all 21 invocations** (5 green +
|
||||
5 matrix + 11 regressions): snapshot `/root/.helm/data/snapshot.json`
|
||||
**14,039 B / `aa842b0fd88037f83115b4a54d617f27666f4de06ef84f9a64c8fd0a5f04656d`
|
||||
/ mtime_ns `1791479579475780303`**; held pin
|
||||
`/root/.helm/data/held/stackchain-home.html.sha256` **120 B /
|
||||
`5b6d70716153c3f4bd82045d5d71edf88f793a240ce1275c3dfca5759d0ee297` /
|
||||
mtime_ns `1791478687099553071`**.
|
||||
|
||||
**ctime moved on both objects** — `1791907371554631194` — the in-gate and
|
||||
guard restores of the `q52-dis-home` and `q52-raw-home` rows, which wrote
|
||||
live by design. The stamp a restore cannot undo (Q43); disclosed, not hidden.
|
||||
|
||||
Final tree: **0 sandboxes** (`glob` for `helm-legs-*` / `helm-q39-*` → `[]`).
|
||||
Deployed `/root/.helm/bin/` untouched (`helm-selftest.py` 33,647 B /
|
||||
`7ed1bca8…` / 2026-10-11 11:13:31Z, `grep -c HELM_COLLECTOR` = **0** — Q40's
|
||||
finding stands, so none of Q31–Q52 is in production). Disk **46 GiB free of
|
||||
154 (71 % used)** (`df -h /`).
|
||||
|
||||
## 8. Five defects of mine, logged first
|
||||
|
||||
1. **The witness crashed on a missing import.** `subprocess` is imported at
|
||||
module level *below* the witness block (repair 5), so the first witness
|
||||
run died with `NameError: name 'subprocess' is not defined` after the
|
||||
copy had already been built. Fixed with a local import in the block,
|
||||
asserted in the patcher.
|
||||
2. **The witness was anchored before the copy was written.** The first
|
||||
placement ran `subject-legs.py` before `_q43_subject.write_text(...)`, so
|
||||
it died on `FileNotFoundError`. Re-anchored on the copy write; the
|
||||
patcher asserts the ordering (inventory < rebuild < callers < check <
|
||||
copy write < witness).
|
||||
3. **The witness's live census read a directory.** `live_held_after` used
|
||||
`_q44_held_live.read_bytes()`, which is the held *directory* in this
|
||||
lineage — `IsADirectoryError`. Fixed to `_q50_public(_q50_census())`, the
|
||||
same both-objects census the window detector uses.
|
||||
4. **The NOTE claimed a redirect that had not fired.** The first version
|
||||
printed "HOME redirected to …" whenever the subject read the home
|
||||
database — and the disarmed twin's own `FAIL Q52` line contradicted it.
|
||||
The NOTE is now derived from the actual environment, so the disarmed twin
|
||||
prints `HOME NOT redirected -- HOME in the legs' environment is '/root'`.
|
||||
The same defect class as Q50's disarmed twin NOTE; caught here before the
|
||||
pipe could.
|
||||
5. **The first matrix measured nothing.** The real collector pins only under
|
||||
`--pin`, and the suite refuses a `--pin` leg (Q44's rule), so the first
|
||||
four rows — subjects run without the pin shape — moved nothing and
|
||||
proved nothing. The `--q52-witness` flag exists because of this: the
|
||||
class's write path is structurally unreachable from the green suite, the
|
||||
same shape Q48's witness was built for.
|
||||
|
||||
## 9. Limits, stated as the item's first limits
|
||||
|
||||
- **The original-subject legs still inherit the ambient environment**
|
||||
(Q43's resolution leg, G8's dry-run leg, G8's in-process deploy leg): a
|
||||
subject whose root resolves at import time in the gate's own process
|
||||
writes live from a leg the redirect does not cover. Q48's residual,
|
||||
unchanged by this item. The witness covers the copy's environment only.
|
||||
- **`pwd` reads bypass `HOME`.** `posixpath.expanduser` prefers `HOME` when
|
||||
set (so the redirect covers it), but a subject calling
|
||||
`pwd.getpwuid(os.getuid()).pw_dir` directly reads the user database and
|
||||
the redirect cannot see it. The inventory *names* such a site; the
|
||||
redirect does not relocate it. Stated, not fixed.
|
||||
- **The inventory is static.** Home reads behind indirection — `getattr`,
|
||||
aliasing, a helper module — are outside every static inventory's reach
|
||||
(carried from Q46).
|
||||
- **`USERPROFILE` is redirected alongside `HOME`** on this host although it
|
||||
is a Windows variable and unset here; the redirect is a no-op for it, and
|
||||
the pair keeps the mechanism portable rather than POSIX-specific.
|
||||
|
||||
## 10. Ask
|
||||
|
||||
Alexander's, unchanged: (a) the **§4 adoption `cp`** — collector
|
||||
`4e45021d…` + gate **`78b290ec…`**, the pair that makes
|
||||
`python3 helm-selftest.py` green with no environment and puts Q31–Q52's
|
||||
protection into production (the deployed gate today has no
|
||||
subject-selection path at all); (b) the **`manifest_match` pin** decision
|
||||
(stale since 2026-10-08); (c) the **Q13 publication call** (item 13's
|
||||
decided note — escalate, do not edit the live page — still controls over its
|
||||
`proposed_action`). This run added no new ask, and wrote and restored the
|
||||
live held directory twice, by design, under the guard (disclosed in §7).
|
||||
|
||||
— Merlin
|
||||
Reference in new issue
Block a user