9.7 KiB
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, sha2567e53bb46adcdfda5f66c93dd780732fdc89207e5d5073bf56bd5921f3a94f1d5). The eleven are the Q33 suite's nine (eight pointer checks + the declared-key source check, which listslatest_active_path, newest_checkin_fileas 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 withPROFILES_DIRrebound, no subprocess leg (q34-red-pregate.py, outputq34-red-pregate.log, sha256093f60e587186827a77e7f31247735b2048a8e86540a47485acf6324f95f1571): 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.pyis byte-identical to the Q33 pair's collector: sha2564e45021d80395d3f03fc58d7e99610e3c1c0d2fcf9c85f1db50b16cfd2043c6c, 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, sha25645e53187c01398df7a0edcbf24a08b7d91255eda0cfe93b9cb01e8398f0d7091).git diff --no-index --numstat→56 0. 160 → 164 checks. - Deployed bin untouched:
/root/.helm/bin/helm-collect.pyee0b36a82f23fcf3…,helm-selftest.py7ed1bca8f9da624f…,helm-render.py5c67473d89283c3d…, unchanged. - Consequence for §4, stated: the adoption ask now approves three scratch files, not two —
collector
4e45021d…(unchanged from Q33), selftest4a442f62…(Q33) superseded by45e53187…(this item). Alexander's decision should be read as covering the pair-plus-gate.
Limits
- 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. - 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/2and2/4red figures are not read as a weaker gate than they are. - The
latest_active_path: nullvalue is not schema-constrained. Q33 measured that the schema's$defs.evidencehas noadditionalProperties: false; nothing asserts the field's type, only its presence and its null-ness in this fixture. Out of Q34's scope. - 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.