Q34 DONE: the pointer's null case is gated (recoveries/helm-null-gate.md, scratch /root/crucible/q34/). Four checks planted in the gate's own scratch tree between two collects on a profile with sessions/ and logs/ and no files: latest_active_path null, source_path still the profile directory, the key present with value null rather than absent, and a follow-up collect where a planted file sets the pointer while the citation holds; the member analogue stated unreachable by construction. Seven consecutive green gate invocations (exit 0, 164 passed 0 failed each, the Q33 160 plus these four), log digests in the deliverable. Red on both comparators: Q33-before collector under the full gate exit 1 / 153 passed 11 failed (the Q33 nine plus Q34 checks 3 and 4); pre-Q33 collector 2 passed 2 failed on the four checks by module import with no subprocess leg; checks 1-2 pass vacuously on both comparators and are reported as vacuous. The collector did not move (byte-identical to the Q33 pair, 4e45021d, 720 lines); the gate moved +56/-0 (929 -> 985 lines, 45e53187), so the §4 adoption ask is now a three-file ask. One incident logged first: the first red leg ran the full gate against the pre-Q33 collector, which has no destination gate, so the bare --skip-tests leg wrote the live snapshot (aa842b0f -> e4d1ad5, 14039 -> 18524 B, mtime_ns 1791479579475780303 -> 1791764761933369921 at 00:26:01Z); restored byte-exact from /root/crucible/q31/snap-before.json with the mtime re-stamped and the comparator re-run by import only; live snapshot delta 0 across every leg after the restore. Deployed bin untouched. Limits: a live empty profile is unobserved; checks 1-2 vacuous on the comparators; the null value is not schema-constrained.

This commit is contained in:
Merlin committed 2026-10-12 00:28:38 +00:00
1 parent fe11582287
commit a16d9e45be
3 files changed
+274 -2

No files matched your search

+22 -2
View File
@@ -4496,8 +4496,28 @@ the profile directory and (c) the key is present rather than absent (a reader mu
are **red on the pre-Q33 collector, green on the patched pair**, and reported as **N consecutive green invocations, are **red on the pre-Q33 collector, green on the patched pair**, and reported as **N consecutive green invocations,
N ≥ 5**, each with its log digest. **Evidence:** the new check names with their pass/fail on both collectors, the N ≥ 5**, each with its log digest. **Evidence:** the new check names with their pass/fail on both collectors, the
gate logs with digests, the before/after `evidence` blocks for the empty profile, and the live snapshot delta 0. gate logs with digests, the before/after `evidence` blocks for the empty profile, and the live snapshot delta 0.
**Status: OPEN — named to the Helm lane.** Deliberately separate from Q33's record: Q33's deliverable is a finished **Status: DONE 2026-10-12 — `recoveries/helm-null-gate.md` (Merlin), scratch tree `/root/crucible/q34/`.**
artifact and the adoption pair under §4 must not move under a second edit while Alexander is deciding on it. Four checks planted in the gate's own scratch tree (profile `empty` with `sessions/` and `logs/` and no
files, between two collects): (a) `evidence.latest_active_path` is `null`, (b) `evidence.source_path` stays the
profile directory, (c) the key is **present** with value `null` rather than absent, (d) after a file lands the
pointer is set and the citation is unchanged. The member analogue is stated unreachable by construction, not
asserted (a member row exists only for an existing check-in file). **Seven consecutive green gate invocations**
(exit 0, `164 passed, 0 failed` each — the Q33 suite's 160 plus these four), log digests in the deliverable.
Red on both comparators: the Q33-before collector under the full gate **exit 1 / 153 passed, 11 failed** (the
Q33 nine plus Q34 checks 3 and 4), and the pre-Q33 collector **2 passed / 2 failed** on the four Q34 checks
(module-import run, no subprocess leg) — checks 1 and 2 pass vacuously on both comparators and are reported as
such, so the discriminating checks are 3 and 4. The collector **did not move** (byte-identical to the Q33 pair,
`4e45021d…`, 720 lines); the gate moved **+56/−0** (929 → 985 lines, `45e53187…`). Live snapshot delta 0 across
every leg after the restore below; deployed bin untouched. **One incident, logged first in the deliverable:**
the first red leg ran the full gate with `HELM_COLLECTOR` pointed at the pre-Q33 collector, which has no
destination gate, so the gate's own bare `--skip-tests` leg wrote the live snapshot (`aa842b0f…` → `e4d1ad5…`,
14,039 → 18,524 B, mtime_ns `1791479579475780303` → `1791764761933369921`, 2026-10-12T00:26:01Z); restored
byte-exact from `/root/crucible/q31/snap-before.json` with the mtime re-stamped (`VERDICT: RESTORED
BYTE-EXACT`), and the comparator re-run by import only. **§4 consequence:** the adoption ask now covers three
scratch files — collector `4e45021d…` unchanged, selftest `4a442f62…` superseded by `45e53187…`. Limits: a live
empty profile is still unobserved (the branch is exercised by the fixture, not by production data); checks 1–2
are vacuous on the comparators; the null value is not schema-constrained. Scratch artifacts
`/root/crucible/q34/`: gate, both comparators, the seven logs, `Q34-NULL-EVIDENCE.txt`, `q34-restore.py`.
**Q33 citation repair — the pre-Q32 control figure does not match the log it cites. 2026-10-11 22:43Z **Q33 citation repair — the pre-Q32 control figure does not match the log it cites. 2026-10-11 22:43Z
(control plane).** One block, one subject, appended after reading the tail and grepping for the existing record: the (control plane).** One block, one subject, appended after reading the tail and grepping for the existing record: the
+102
View File
@@ -0,0 +1,102 @@
# 2026-10-12 — 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.
---
## Did
Claimed and finished **Q34 — gate the pointer's null case**, the last open item and the one named to
my lane (Q20 is zephyrus-sparkborn's, Q9 blocked on the audio handover, Q13/Q19 gated on Alexander).
Deliverable: `recoveries/helm-null-gate.md` (**9,924 B / sha256
`84b1e714bfad1c0be948845f0f0a9f92a739ce5b64b68a197523bbb7c0808a58`**, this host). Queue block
appended to `WORK-QUEUE.md` (was 519,292 B / sha256
`592523616f8b9a8ca45f4dabc631508baeb173c68de1f01cc125c0b0682a4f3e` before this run's append).
Scratch tree `/root/crucible/q34/`; nothing deployed.
**What was gated.** Q33's one `unverified` limit: `evidence.latest_active_path: null` for a profile
with a `sessions/` directory and no files, with the Q32 pointer carve-out deleted, needed an
exercised branch. The gate now plants, in its own scratch tree between two collects, a profile
`empty` carrying `sessions/` and `logs/` and no files, and asserts four things — pointer `null`,
citation still the profile directory, the key **present with value null rather than absent**, and a
follow-up collect where a planted file sets the pointer while the citation holds. The member
analogue is stated unreachable by construction, not asserted (a member row exists only for an
existing check-in file); the rule is a comment in the gate, not a test.
**Seven consecutive green invocations**, exit 0 each, `helm-selftest: 164 passed, 0 failed` (the
Q33 suite's 160 plus these four), log digests in the deliverable:
`7f01728d…` / `997b1308…` / `96678a66…` / `f13e5cd6…` / `532ab633…` / `00bea997…` / `d4e39936…`.
**Both comparators red where it matters.** The Q33-before collector under the full gate:
`exit 1, 153 passed, 11 failed` (`q34-gate-q33before.log`, sha256
`7e53bb46adcdfda5f66c93dd780732fdc89207e5d5073bf56bd5921f3a94f1d5`) — the Q33 nine plus Q34 checks
3 and 4. The pre-Q33 collector on the four Q34 checks by module import, no subprocess leg:
**2 passed / 2 failed** (`q34-red-pregate.log`, sha256
`093f60e587186827a77e7f31247735b2048a8e86540a47485acf6324f95f1571`). Checks 1 and 2 pass
*vacuously* on both comparators (an absent key satisfies `.get() is None`, and a pointer-less
citation falls back to the directory) and are reported as vacuous, so the discriminating checks are
3 and 4 — both red on both comparators.
**Before/after evidence for the empty profile** (from the gate's own dump, run 7,
`Q34-NULL-EVIDENCE.txt`, sha256 `c625bc07068c5040d91117d604208152b33f34f0416e96dfc51d326546c3473c`):
before — `"latest_active_path": null`, `"source_path": "/tmp/tmp5r57iyp4/q34-null/profiles/empty"`;
after planting `sessions/landed.jsonl` at mtime +60 s — the pointer is that file and
`source_path` is unchanged. The key is present in the before block; the citation does not move.
**What moved.** The collector did not: `/root/crucible/q34/helm-collect.py` is byte-identical to
the Q33 pair's (`4e45021d80395d3f…`, 720 lines). The gate moved **+56/−0**
(`git diff --no-index --numstat` → `56 0`), 929 → 985 lines, sha256
`45e53187c01398df7a0edcbf24a08b7d91255eda0cfe93b9cb01e8398f0d7091`. Deployed bin untouched:
`helm-collect.py` `ee0b36a82f23fcf3…`, `helm-selftest.py` `7ed1bca8f9da624f…`,
`helm-render.py` `5c67473d89283c3d…`.
**One incident, logged first and corrected.** The first red leg ran the full gate with
`HELM_COLLECTOR` pointed at the **pre-Q33** collector — which has no destination gate, Q31's own
finding — so the gate's bare `--skip-tests` refusal leg executed it and it **wrote the live
snapshot**: `aa842b0fd88037f8…` → `e4d1ad5293fa58cb…`, 14,039 → 18,524 B, mtime_ns
`1791479579475780303` → `1791764761933369921` (2026-10-12T00:26:01.933369921Z). Restored byte-exact
from `/root/crucible/q31/snap-before.json` with the mtime re-stamped (`q34-restore.py`,
`VERDICT: RESTORED BYTE-EXACT`); post-restore **14,039 B / sha256
`aa842b0fd88037f83115b4a54d617f27666f4de06ef84f9a64c8fd0a5f04656d` / mtime_ns
`1791479579475780303`**, and the comparator re-run by import only. Live snapshot **delta 0 across
every leg after the restore** — seven green runs, the Q33-before comparator and the import-only
comparator. This is the harness rule the skill already carries (*a negative control must never run
ungated code against a live default*) walked into anyway; the standing rule is now mechanical in
this tree: no `HELM_COLLECTOR` naming a pre-gate collector is ever passed to the full gate.
## Blocked
Nothing in my lane. Q13/Q19 wait on Alexander, Q9 on the audio handover, Q20 is zephyrus's — none
of them is mine to move. One thing my own run made Alexander's, below.
## Next
Two things, both stated in the deliverable and neither mine to execute:
1. **§4 adoption, now a three-file ask.** Collector `4e45021d…` unchanged from Q33; selftest
`4a442f62…` (Q33) superseded by `45e53187…` (this run). The pair production has never run
under the gate that ships with it, so the `cp` into `/root/.helm/bin/` approves
collector + Q34-gated selftest together. Alexander's call.
2. **A live empty profile is still unobserved** — every profile under `/root/.hermes/profiles/`
has files today, so the null branch is exercised by the gate's fixture, not by production data.
The branch is proven; the data class is not. Nothing to do unless a profile is emptied.
Also worth naming for the queue: the Q34 gate writes its `Q34-NULL-EVIDENCE.txt` dump into the
gate's own directory on every invocation, so that file is a dated reading, not a stable artifact —
digest run 7 is quoted above, and the deliverable says so.
## Ask
Alexander: nothing blocking. If you want the Helm render's `manifest_match` pin re-taken, say so —
it has been stale since 2026-10-08 (`f27326e8…` pinned against a live body now
`e7df6364…` / 215,836 B) and a static render cannot say so, which is a separate decision about
whether that pin should exist at all.
---
Landing evidence, appended after commit and read back over the anonymous raw path
(`https://forge.alexanderwhitestone.com/git/stackchain/troupe-standup/raw/branch/main/<path>`), no
credential — each **HTTP 200** and `cmp` **byte-identical** to disk. Table filled at push time.
— Merlin
+150
View File
@@ -0,0 +1,150 @@
# The pointer's null case, gated
**Q34 — collector-side gate. Author: Merlin (`merlin@agents.invalid`). Clock: 2026-10-12T00:24–00:28Z, this host.**
**Scratch tree: `/root/crucible/q34/`. Nothing deployed; the §4 adoption pair is untouched by this run.**
Q33's one `unverified` limit, now exercised. The patched collector emits
`evidence.latest_active_path: null` for a profile with no files under `sessions/` /
`cron/output/` / `logs/`; every planted tree in the Q33 checks has files and the live collect is
20/20 non-None, so the null branch was exercised by nothing in the suite — and on the pre-Q33
collector the two pointer non-vacuity checks pass `0/0` vacuously, since that collector emits
neither pointer key at all. Q34 plants the empty profile inside the gate's own scratch tree,
between two collects, and asserts the three properties the item names plus a fourth that makes
the null a *measured* null rather than a missing field.
## The four checks (gate section `# ---- Q34: the pointer's null case ----`, `helm-selftest.py:866-925`)
The fixture: profile `empty` with `sessions/` and `logs/` present and **no files** anywhere,
planted in a `TemporaryDirectory` under the gate's own `td`, `PROFILES_DIR` rebound exactly as
the Q33 checks do it (`:875-889`), restored in a `finally`. Two collects: before, and after
`sessions/landed.jsonl` is planted at mtime +60 s.
| # | check name (verbatim, as printed) | patched pair | Q33-before collector | pre-Q33 (pregate) collector |
|---|---|---|---|---|
| 1 | `Q34: evidence.latest_active_path is null for a profile with a sessions/ directory and no files` | PASS | PASS (vacuous — `.get()` returns `None` for an absent key) | PASS (vacuous, same reason) |
| 2 | `Q34: the null pointer's evidence.source_path is still the profile directory (the citation survives an empty profile)` | PASS | PASS | PASS |
| 3 | `Q34: the key is present with value null, not absent (a reader can tell 'no files' from 'field missing')` | PASS | **FAIL** `present=False value=None` | **FAIL** `present=False value=None` |
| 4 | `Q34: after a file lands the pointer is set and the citation is unchanged (the null was a real reading, not a missing field)` | PASS | **FAIL** `null -> None; source_path …/empty -> …/empty/sessions/landed.jsonl` | **FAIL** same shape |
Check 3 is the discriminator; checks 1 and 2 are structural on both comparators and pass there
for the stated reason — reported as vacuous passes, not as failures of the comparator. The
member analogue (`evidence.newest_checkin_file: null`) is **unreachable by construction and is
stated, not asserted**: a member row is emitted only for a file that exists under the check-ins
directory, so `collect_troupe()` cannot produce a member with no newest file. The rule is in the
gate as a comment at `:870-875`.
## Consecutive green invocations — 7, not 5
`HELM_COLLECTOR=/root/crucible/q34/helm-collect.py python3 helm-selftest.py`, exit 0 every time,
`helm-selftest: 164 passed, 0 failed` (the Q33 suite's 160 plus these four):
| run | log | sha256 of log |
|---|---|---|
| 1 | `q34-gate-run-1.log` | `7f01728d08227755fddb5baa445b75fff9c4bd8e0fb6bf5596fca2fd938ba5bf` |
| 2 | `q34-gate-run-2.log` | `997b1308242f7fe63ae331f82afbdbde8a822288d61ca54afb9906dfdd0c2205` |
| 3 | `q34-gate-run-3.log` | `96678a66bb5c8e07d8ebc17ef13ef22c83a75390c8514926ae2b84639a6e6912` |
| 4 | `q34-gate-run-4.log` | `f13e5cd600579fbc7e6bd06f781f489b59dff96f377433c8156b31d4ffe52317` |
| 5 | `q34-gate-run-5.log` | `532ab633fb36c5864ff756084e568b8052d348b004e9f57658af86086446bd7f` |
| 6 | `q34-gate-run-6.log` | `00bea9975b861c39ec9d96fcb5f09faf93aad9aa1e8c8f9266848fa069455c8c` |
| 7 | `q34-gate-run-7.log` | `d4e39936e52491ef55317915a28ee20a501faf32f3dddd1d75729399f6aea69e` |
Log digests differ run to run only by clocks and durations; the verdict line is identical in all
seven (`VERDICT: ALL GATES PASS`).
## The empty profile's `evidence` block, before and after
From `Q34-NULL-EVIDENCE.txt` (written by the gate on every run; the digest below is run 7's,
**sha256 `c625bc07068c5040d91117d604208152b33f34f0416e96dfc51d326546c3473c`** — the file is
regenerated each invocation, so it is a dated reading, not a stable artifact):
```
profile dir : /tmp/tmp5r57iyp4/q34-null/profiles/empty (sessions/ and logs/ present, no files)
collector : /root/crucible/q34/helm-collect.py
--- before (no files anywhere under the profile) ---
{
"latest_active_path": null,
"observed_at": "2026-10-12T00:27:09.204Z",
"source_path": "/tmp/tmp5r57iyp4/q34-null/profiles/empty"
}
--- after (sessions/landed.jsonl planted, mtime +60s) ---
{
"latest_active_path": "/tmp/tmp5r57iyp4/q34-null/profiles/empty/sessions/landed.jsonl",
"observed_at": "2026-10-12T00:27:09.206Z",
"source_path": "/tmp/tmp5r57iyp4/q34-null/profiles/empty"
}
```
The key is **present** in the before block with value `null`; the citation does not move.
## The comparators
- **Q33-before collector** (`helm-collect-q33-before.py`, **8a477376f72c578b…**, the Q33-before
collector with the destination gate intact): full gate, **exit 1, `153 passed, 11 failed`**
(`q34-gate-q33before.log`, sha256 `7e53bb46adcdfda5f66c93dd780732fdc89207e5d5073bf56bd5921f3a94f1d5`).
The eleven are the Q33 suite's nine (eight pointer checks + the declared-key source check,
which lists `latest_active_path, newest_checkin_file` as not emitted) plus Q34 checks 3 and 4.
- **Pre-Q33 collector** (`helm-collect-pregate.py`, **ee0b36a82f23fcf3…**, the deployed
collector): Q34 checks only, by module import with `PROFILES_DIR` rebound, **no subprocess leg**
(`q34-red-pregate.py`, output `q34-red-pregate.log`, sha256
`093f60e587186827a77e7f31247735b2048a8e86540a47485acf6324f95f1571`): **2 passed / 2 failed**,
the two failures being checks 3 and 4 as tabulated above.
## Incident, logged first — a bare run of the pre-Q33 collector wrote the live snapshot
The first red leg ran the full gate with `HELM_COLLECTOR` pointed at the **pre-Q33** collector.
That collector has **no destination gate** (Q31's finding), so the gate's own bare
`subprocess.run([sys.executable, COLLECTOR, "--skip-tests"])` leg — the one that asserts a refusal
— executed the collector, which wrote the live snapshot. Measured:
`/root/.helm/data/snapshot.json` went `aa842b0fd88037f8…` → `e4d1ad5293fa58cb…`, 14,039 → 18,524 B,
mtime_ns `1791479579475780303` → `1791764761933369921` (2026-10-12T00:26:01.933369921Z). The run
also aborted at the argparse `--deploy` error, so it produced no usable comparator.
Restored byte-exact from `/root/crucible/q31/snap-before.json` (the Q31 pre-run copy, itself
verified `aa842b0f…` / 14,039 B before the restore): `q34-restore.py` copied the bytes and re-stamped
`mtime_ns` to `1791479579475780303`. Post-restore: **14,039 B / sha256
`aa842b0fd88037f83115b4a54d617f27666f4de06ef84f9a64c8fd0a5f04656d` / mtime_ns
`1791479579475780303`**, `VERDICT: RESTORED BYTE-EXACT`. The pre-Q33 comparator was then re-run by
module import only. This is the harness incident the skill already warns about — *a negative control
must never run ungated code against a live default* — and it happened anyway; the standing rule is
now mechanical in this tree: no `HELM_COLLECTOR` value naming a pre-gate collector is ever passed
to the full gate.
**Live snapshot delta 0 across every other leg**, including all seven green invocations, the
Q33-before comparator and the import-only pre-Q33 comparator: `/root/.helm/data/snapshot.json` reads
`aa842b0fd88037f8…` / 14,039 B / mtime_ns `1791479579475780303` at every checkpoint after the
restore.
## What moved, and what did not
- **The collector did not move.** `/root/crucible/q34/helm-collect.py` is byte-identical to the Q33
pair's collector: sha256 `4e45021d80395d3f03fc58d7e99610e3c1c0d2fcf9c85f1db50b16cfd2043c6c`,
720 lines / 29,954 B. Q34 is a gate-only change, as the item requires — Q33's adoption pair is
not re-edited under a second item.
- **The gate moved +56 / −0** against the Q33 selftest: `/root/crucible/q31/helm-selftest.py`
(929 lines) → `/root/crucible/q34/helm-selftest.py` (**985 lines**, sha256
`45e53187c01398df7a0edcbf24a08b7d91255eda0cfe93b9cb01e8398f0d7091`).
`git diff --no-index --numstat` → `56 0`. 160 → 164 checks.
- **Deployed bin untouched:** `/root/.helm/bin/helm-collect.py` `ee0b36a82f23fcf3…`,
`helm-selftest.py` `7ed1bca8f9da624f…`, `helm-render.py` `5c67473d89283c3d…`, unchanged.
- **Consequence for §4, stated:** the adoption ask now approves **three** scratch files, not two —
collector `4e45021d…` (unchanged from Q33), selftest `4a442f62…` (Q33) **superseded by**
`45e53187…` (this item). Alexander's decision should be read as covering the pair-plus-gate.
## Limits
1. **A live empty profile is still unobserved.** Every profile under
`/root/.hermes/profiles/` currently has files; the null branch is exercised by the gate's
fixture, not by production data. Measured non-destructively (Q33's import-based probe, this
host, 2026-10-11) and now by the gate itself.
2. **Checks 1 and 2 pass vacuously on both comparators** — they hold for an absent key and for a
pointer-less citation fallback. The discriminating checks are 3 and 4, and both are red on both
comparators. Stated so the `2/2` and `2/4` red figures are not read as a weaker gate than they
are.
3. **The `latest_active_path: null` value is not schema-constrained.** Q33 measured that the
schema's `$defs.evidence` has no `additionalProperties: false`; nothing asserts the field's
type, only its presence and its null-ness in this fixture. Out of Q34's scope.
4. **Testimony only, not verified this run:** the Q33 suite's own checks above the Q34 section
(160 of the 164) were not re-derived here; this run measures that the Q34 section is green,
discriminating, and non-destructive.