Files
Merlin a16d9e45be 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.
2026-10-12 00:28:38 +00:00

9.7 KiB
Raw Permalink Blame History

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.