fix(ci): run-18161 follow-through — mode detection, plan kebab-case, smoke coverage, deterministic type instrument

Canary mode detection (the real 18161 defect):
- isfile() is not a valid LIVE/ISOLATED discriminator: run 18141's shim
  writes left a zombie 1-entry ledger in the runner's persistent HOME, which
  flipped the naive gate to LIVE with wrong data. The discriminator is now
  the repo's own CI-sandbox convention (RUNE_HOME=/tmp/rune-ci-<id> — the
  workflow's own cleanup guard matches exactly that).
- The isolated battery seeds a PER-RUN temp ledger (module-attr override on
  the shim's LEDGER_FILE) instead of writing the runner's persistent home;
  binary §5 gets HOME=<temp> so binary and shim resolve the same file.
  Both modes: 74/74 locally and under simulated runner topology (zombie
  ledger present, RUNE_HOME set). Rust canary §4 gets the same dual-mode
  treatment.

Henxels holds cleared:
- plans/: three phase-g files renamed to kebab-case; references updated in
  lib.rs, ep_loader.py, canary docstring, Cargo.toml, supersession ledger.
- ffi_query_memory_smoke coverage: smoke repaired (was stranded — imported
  the deleted src/rune vintage; now drives shared/ shim, 41/41 green) and
  tests/test_ffi_query_memory_smoke.py runs it, skipping honestly when the
  cdylib isn't built (CI has no cargo step).

Type ratchet — instrument repair (§16V), not reading-management:
- scripts/type_check_ratchet.py now pins pyright --pythonpath to the
  INVOKING interpreter. Ambient-interpreter dependency resolution was the
  residual non-determinism: runner (py311, no rich/Pillow) vs desktop (3.14
  system site) produced 460 vs 454 on the identical tree, and any import
  ignore flipped to unnecessary-ignore in the other environment.
- pyrightconfig extraPaths now covers src/rune + shared + both crate dirs
  (bare intra-repo imports resolve under the pinned environment).
- Pillow declared in pyproject (genuine lazy dep of the diamond-frames
  surface; was undeclared) — lock regenerated, dep-lock checker green.
- Measured under the pinned instrument: 115 errors / 439 warnings
  (CI-equivalent locked 3.11 venv) and 115/436 (local 3.14 venv) — both
  below the 117/454 baseline; baseline unchanged. My canary's 4
  reportOptionalSubscript errors fixed by real narrowing, not suppression.

Lint baseline: BLE001 346 -> 345 (bulk_issue_guard.py's retry catch gains a
reasoned noqa; the file stays untracked per checkpoint hygiene, but the
honest-ledger test counts disk state — the noqa keeps local == CI).

Local wall: full pytest 2251/0 (incl. the new smoke test); canaries 74+66
both modes; parities 33/24/13/16/17; henxels exit 0; umbrella gate exit 0;
dep-lock green.
This commit is contained in:
Nick committed 2026-10-04 01:08:20 +01:00
1 parent b61af7a526
commit ff2abcd502
14 files changed
+100 -27

No files matched your search

+1 -1
View File
@@ -1,4 +1,4 @@
{
"BLE001": 346,
"BLE001": 345,
"S110": 75
}
@@ -27,7 +27,7 @@ Errors: byte-equal (duplicate/unknown-behaviour/unknown-state; probe schema-gate
| Step | Requirement | Evidence |
|------|-------------|----------|
| 1–4 | candidate built, contract frozen, parity proven | commits `114c394` → `969e39f`; frozen contract `plans/2026-10-03-phaseG-evidence-provenance-contract.md` (commit `7c68e41`); per-port parity harnesses ALL GREEN |
| 1–4 | candidate built, contract frozen, parity proven | commits `114c394` → `969e39f`; frozen contract `plans/2026-10-03-phase-g-evidence-provenance-contract.md` (commit `7c68e41`); per-port parity harnesses ALL GREEN |
| 5 | qualification | per-port parity + decision-math batteries; canary pair per port (Python 74/74, Rust 66/66 — all six port sections accumulated); performance follows the slice-1 in-process shape (no interpreter startup; same cdylib) |
| 6 | canaries conformance | canary_audit_ledger.py 74/74; canary_evidence_provenance_rust.py 66/66 (per-port sections for ALL six ports; dual-mode LIVE/ISOLATED for live-ledger pins — run 18141 repair); embedded tests through shims (verification test, 23 consumer tests) |
| 7 | shadow comparison vs live-shaped data | port-1 shadow 18/18 vs `939883d:shared/audit_ledger.py` (banked at `114c394`); ports 2–6 use direct byte-parity batteries vs the live originals (same discipline, stronger: full-document byte-quality) |
+1
View File
@@ -16,6 +16,7 @@ dependencies = [
"aiohttp>=3.9.0",
"websockets>=12.0",
"psutil>=5.9.0",
"pillow>=10.0",
"pyyaml>=6.0",
]
+5 -1
View File
@@ -29,6 +29,10 @@
"**/__pycache__"
],
"extraPaths": [
"scripts"
"scripts",
"src/rune",
"shared",
"src/rust/evidence_provenance",
"src/rust/query_memory"
]
}
+3 -1
View File
@@ -64,7 +64,9 @@ nodeenv==1.11.0
# via pyright
packaging==26.3
# via pytest
platformdirs==4.12.2
pillow==12.3.0
# via rune (pyproject.toml)
platformdirs==4.12.3
# via textual
pluggy==1.6.0
# via
+13 -1
View File
@@ -47,7 +47,19 @@ def main() -> int:
p for p in env.get("PATH", "").split(os.pathsep)
if "ci-venv" not in p and ".type-venv" not in p and "venv/bin" not in p)
result = subprocess.run(
cmd + [str(ROOT / "src" / "rune"), "--outputjson"],
# §16V AMBIENT-INTERPRETER CLOSURE (T7.3 slice-2 CI repair, 2026-10-03):
# venv-signal stripping fixed ACTIVATION drift, but dependency
# resolution still followed the AMBIENT host interpreter — runner
# (py311, no rich/Pillow) vs desktop (3.14 system site with both)
# produced 460 vs 454 on the identical tree, and any import-level
# ignore flips to reportUnnecessaryTypeIgnoreComment in the other
# environment. Pin --pythonpath to the INVOKING interpreter (the
# venv running this script) so dependency resolution follows the
# installed lock, not the host: CI's .type-venv has the locked deps;
# local preflight must run the ratchet from a venv built from
# requirements-ci.txt (same lock) — that is now the contract.
cmd + [str(ROOT / "src" / "rune"), "--outputjson",
"--pythonpath", sys.executable],
capture_output=True,
text=True,
timeout=600,
+38 -19
View File
@@ -2,7 +2,7 @@
"""canary_audit_ledger.py — Python canary for slice-2 ports 1-6.
Slice-1 PASS/FAIL discipline (counter harness, exit 1 on any FAIL), pinned to
the FROZEN CONTRACT /mnt/workdrive/rune/plans/2026-10-03-phaseG-evidence-provenance-contract.md:
the FROZEN CONTRACT /mnt/workdrive/rune/plans/2026-10-03-phase-g-evidence-provenance-contract.md:
§5 requires the canary pair to carry per-port sections for ALL SIX ports.
DUAL-MODE LAW (added 2026-10-03 after run 18141 went red): sections 2/3/4/5
@@ -40,11 +40,16 @@ _CRATE_DIR = os.path.join(_REPO, "src", "rust", "evidence_provenance")
_BIN = os.path.join(_CRATE_DIR, "target", "release", "evidence_provenance")
# Dual-mode switch (DUAL-MODE LAW in the docstring): LIVE when this process's
# HOME actually carries the live promotion ledger (desktop), ISOLATED under
# CI's sandboxed HOME. The mode only changes count/data expectations, never
# the number or shape of the checks.
# HOME carries the real promotion ledger AND we are not inside the CI sandbox.
# The discriminator is the repo's own CI-sandbox convention — the workflow sets
# RUNE_HOME=/tmp/rune-ci-<run_id> and its cleanup guard matches /tmp/rune-ci-*
# exactly. File EXISTENCE is NOT a valid discriminator: run 18141's shim writes
# left a zombie 1-entry ledger in the runner's persistent HOME, which would
# flip a naive isfile() gate to LIVE with the wrong data (proven 2026-10-03
# on run 18161: "entries >= 23 — got 1").
_LIVE_LEDGER = os.path.expanduser("~/.config/opencode/memory/promotion_ledger.jsonl")
LIVE = os.path.isfile(_LIVE_LEDGER)
_IN_CI_SANDBOX = os.environ.get("RUNE_HOME", "").startswith("/tmp/rune-ci-")
LIVE = os.path.isfile(_LIVE_LEDGER) and not _IN_CI_SANDBOX
PASS = 0
FAIL = 0
@@ -66,15 +71,7 @@ def main():
print("=== AUDIT LEDGER CANARY (slice-2 ports 1-6) ===")
print(f"MODE: {'LIVE (deployed surface + live ledger)' if LIVE else 'ISOLATED (sandboxed ledger — CI mode)'}\n")
if not LIVE:
# Seed the sandboxed ledger so sections 2/3/5 exercise the same shapes
# the live battery exercises on the desktop. Two seeds here + the §4
# canary append = 3 entries, so `--limit 3` renders 3 in both modes.
import audit_ledger as _al_seed
for seed_id in (9001, 9002):
_al_seed.append_entry("promotion", seed_id,
{"reason": "canary-ci-seed", "old_status": "a",
"new_status": "b", "wash_count": 1})
bin_home = None # binary HOME override for §5 in ISOLATED mode
# --- 1. Deployed module surface ---
print("--- 1. Deployed module surface ---")
@@ -107,6 +104,24 @@ def main():
# --- 2. Ledger verification through the shim (live on desktop / isolated in CI) ---
print("\n--- 2. Ledger verification ---")
if not LIVE:
# Seed a PER-RUN isolated ledger (never the runner's persistent HOME —
# leftover writes there are what poisoned run 18161's mode detection):
# point the shim's module-level LEDGER_FILE at a temp path; all §2/§3/§4
# reads/writes then land in the temp file. Binary §5 gets HOME=<temp>.
# Placed AFTER §1 so the surface pins observe the shim's DEFAULT
# resolution before the override.
import tempfile as _tf
bin_home = _tf.mkdtemp(prefix="alcanary_home_")
iso_ledger = os.path.join(bin_home, ".config", "opencode", "memory",
"promotion_ledger.jsonl")
os.makedirs(os.path.dirname(iso_ledger), exist_ok=True)
audit_ledger.LEDGER_FILE = iso_ledger
for seed_id in (9001, 9002):
audit_ledger.append_entry("promotion", seed_id,
{"reason": "canary-ci-seed", "old_status": "a",
"new_status": "b", "wash_count": 1})
try:
result = audit_ledger.verify_chain()
test("verify_chain() returns dict", isinstance(result, dict), f"got {type(result)}") # type: ignore[reportUnnecessaryIsInstance]
@@ -197,7 +212,7 @@ def main():
test("binary invalid choice -> exit 2", r.returncode == 2
and "invalid choice" in r.stderr, f"rc={r.returncode} err={r.stderr!r}")
r = subprocess.run([BIN, "list", "--limit", "3"], capture_output=True, text=True,
env=clean_env)
env=dict(clean_env, HOME=bin_home) if bin_home else clean_env)
test("binary list --limit 3 on the shim's ledger renders 3 entries",
r.returncode == 0 and "Recent ledger entries (3):" in r.stdout,
f"rc={r.returncode} out={r.stdout[:120]!r}")
@@ -339,22 +354,25 @@ def main():
doc, _ = ep_loader.bl_record_auto(doc, "alpha", passed=True,
detail="tests green", generation="g1",
now="2026-10-03T23:01:00Z")
rec_a = ep_loader.bl_get(doc, "alpha")
test("record_auto pass -> AUTO_VERIFIED (a CONFIRMED state)",
ep_loader.bl_get(doc, "alpha")["state"] == "AUTO_VERIFIED")
rec_a is not None and rec_a["state"] == "AUTO_VERIFIED")
doc, _ = ep_loader.bl_define(doc, behaviour_id="beta", owning_project="proj",
intended_behaviour="ib2", now="2026-10-03T23:02:00Z")
doc, _ = ep_loader.bl_record_auto(doc, "beta", passed=False, detail="flaky",
now="2026-10-03T23:03:00Z")
rec_b = ep_loader.bl_get(doc, "beta")
test("record_auto fail -> TEST_FAILED",
ep_loader.bl_get(doc, "beta")["state"] == "TEST_FAILED")
rec_b is not None and rec_b["state"] == "TEST_FAILED")
# Selective invalidation (rule 19I): only CONFIRMED_STATES with
# dep/owner overlap reopen; unrelated change preserves.
doc, untouched = ep_loader.bl_invalidate(
doc, changed_entities=["dep:unrelated"], now="2026-10-03T23:04:00Z")
rec_a = ep_loader.bl_get(doc, "alpha")
test("unrelated change invalidates nothing; AUTO_VERIFIED preserved",
untouched == [] and ep_loader.bl_get(doc, "alpha")["state"] == "AUTO_VERIFIED")
untouched == [] and rec_a is not None and rec_a["state"] == "AUTO_VERIFIED")
doc, hit = ep_loader.bl_invalidate(
doc, changed_entities=["dep:one"], generation="g2",
reasons={"behaviour:alpha": "dep one changed"}, now="2026-10-03T23:05:00Z")
@@ -370,8 +388,9 @@ def main():
f"got {inv_entry}")
doc, rehit = ep_loader.bl_invalidate(
doc, changed_entities=["dep:one"], now="2026-10-03T23:06:00Z")
rec_a = ep_loader.bl_get(doc, "alpha")
test("re-invalidating a non-confirmed record is a no-op",
rehit == [] and ep_loader.bl_get(doc, "alpha")["state"] == "RETEST_REQUIRED")
rehit == [] and rec_a is not None and rec_a["state"] == "RETEST_REQUIRED")
out = ep_loader.bl_outstanding(doc)
test("outstanding() puts TEST_FAILED first (queue ordering)",
+4
View File
@@ -41,6 +41,10 @@ def mask_ts(text):
def main():
tmp = tempfile.mkdtemp(prefix="ffi_smoke_")
os.environ["HOME"] = tmp # BEFORE query_memory import (import-time constant)
# Post-supersession (2ac2d0a): the Python vintage is deleted; the
# "consumer side" of this smoke is now the shared/ ctypes shim — the same
# deployed-consumer surface the smoke was built to prove.
sys.path.insert(0, os.path.join(REPO, "shared"))
sys.path.insert(0, PY_DIR)
import query_memory as qm_py
+1 -1
View File
@@ -2,7 +2,7 @@
name = "evidence_provenance"
version = "0.1.0"
edition = "2021"
description = "Phase G slice 2 (T7.3) — evidence-provenance Rust candidate: audit_ledger port 1 per frozen contract plans/2026-10-03-phaseG-evidence-provenance-contract.md"
description = "Phase G slice 2 (T7.3) — evidence-provenance Rust candidate: audit_ledger port 1 per frozen contract plans/2026-10-03-phase-g-evidence-provenance-contract.md"
[lib]
name = "evidence_provenance"
+1 -1
View File
@@ -7,7 +7,7 @@ Serves the deployed audit_ledger consumer shape. The deployed consumers
the HMAC-chained promotion ledger IN-PROCESS — this loader gives them the Rust
core through the cdylib with no interpreter startup and no CLI subprocess.
Contract (frozen `plans/2026-10-03-phaseG-evidence-provenance-contract.md`):
Contract (frozen `plans/2026-10-03-phase-g-evidence-provenance-contract.md`):
* compute_hash(prev_hash, entry_json, key) -> str
* append_entry(ledger_path, key, entry_type, entry_id, details_json) -> dict
* verify_chain(ledger_path, key) -> dict {valid, entries, reason}
+1 -1
View File
@@ -5,7 +5,7 @@
//! hash = HMAC-SHA256(key, prev_hash + json.dumps(entry_minus_hash, sort_keys=True))
//! serialized with Python-default separators (", " / ": ") via pyjson.
//!
//! KEY PROVENANCE (frozen contract `plans/2026-10-03-phaseG-evidence-provenance-contract.md`):
//! KEY PROVENANCE (frozen contract `plans/2026-10-03-phase-g-evidence-provenance-contract.md`):
//! the LIVE ledger (~/.config/opencode/memory/promotion_ledger.jsonl) verifies valid
//! under SECRET_KEY default `rune-drift-integrity-2024` (the deployed vintage's
//! default) and FAILS under src/rune's newer `rune-governed-adaptation-2024`
+31
View File
@@ -0,0 +1,31 @@
"""Coverage for src/rune/ffi_query_memory_smoke.py (slice-1 FFI smoke).
The smoke drives the compiled query_memory cdylib through the deployed-consumer
shape. When the artifact is not built in this checkout (CI has no cargo step),
the test SKIPS rather than falsifying coverage — the henxel rule is satisfied
by the reference, and desktop runs exercise the full 41-check battery.
"""
import subprocess
import sys
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[1]
SMOKE = ROOT / "src" / "rune" / "ffi_query_memory_smoke.py"
CDYLIB = (
ROOT / "src" / "rust" / "query_memory" / "target" / "release" / "libquery_memory.so"
)
@pytest.mark.skipif(
not CDYLIB.exists(),
reason="query_memory cdylib not built in this environment (CI has no cargo step)",
)
def test_ffi_query_memory_smoke_green():
r = subprocess.run(
[sys.executable, str(SMOKE)], capture_output=True, text=True, timeout=120
)
assert r.returncode == 0, r.stdout[-500:] + r.stderr[-500:]
assert "41/41 PASS" in r.stdout