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:
1 parent
b61af7a526
commit
ff2abcd502
14 files changed
+100
-27
No files matched your search
+1
-1
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"BLE001": 346,
|
||||
"BLE001": 345,
|
||||
"S110": 75
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
+1
-1
@@ -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) |
|
||||
@@ -16,6 +16,7 @@ dependencies = [
|
||||
"aiohttp>=3.9.0",
|
||||
"websockets>=12.0",
|
||||
"psutil>=5.9.0",
|
||||
"pillow>=10.0",
|
||||
"pyyaml>=6.0",
|
||||
]
|
||||
|
||||
|
||||
+5
-1
@@ -29,6 +29,10 @@
|
||||
"**/__pycache__"
|
||||
],
|
||||
"extraPaths": [
|
||||
"scripts"
|
||||
"scripts",
|
||||
"src/rune",
|
||||
"shared",
|
||||
"src/rust/evidence_provenance",
|
||||
"src/rust/query_memory"
|
||||
]
|
||||
}
|
||||
+3
-1
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)",
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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
|
||||
Reference in new issue
Block a user