M4.1: toolchain preflight
A model works out and sets up what a project needs on its build VM; code makes sure that happens, bounds it and checks the result (D3). - Before each driver run on a project, one round trip checks the build VM: the harness's own baseline (missing: the item waits, a template problem), the workspace clone (missing: cloned again and said; the item resumes from what it pushed) and a per-project marker. - No marker (a new project, a new or wiped VM): the item's driver model runs a bounded preflight first, with its own prompt, the execution tools (screened as ever) and toolchain_ready. The harness runs each reported version command itself (version-shaped commands only, even from the marker, which the model's commands could edit) and writes the marker. Gaps and an unfinished preflight are facts and work goes on; the thread hears only of installs and gaps. A rebuilt VM starts from the last toolchain. - Later runs check the versions again; a change is a fact and a thread line. The PR body says what the work was tested with. - A changed host key is its own failure (HostKeyChanged): the item waits for the operator's `trust new host key`, which drops the pin; then the VM is set up again. - A prompt line: install what the work needs on the build VM, say why, and put lasting needs in the project's own pins. - tuning.preflight: max_turns, command_timeout_s. - The property walk gains a VM wipe and ready_before_work (checked against a mutant that skips preflight); parked_work_safe accepts that a wipe loses what wasn't pushed (D3). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
This commit is contained in:
1 parent
4564cd6f3f
commit
9968a0922a
19 files changed
+1103
-10
No files matched your search
@@ -75,3 +75,34 @@ def test_secrets_are_never_on_a_command_line(ssh_host: SSHHost, tmp_path: Path)
|
||||
def test_redact_masks_longest_values_first() -> None:
|
||||
assert redact("abc-abcdef", ["abcdef", "abc-abcdef"]) == REDACTED
|
||||
assert redact("short ab stays", ["ab"]) == "short ab stays" # too short to mask
|
||||
|
||||
|
||||
def test_a_changed_host_key_is_its_own_failure_and_only_forgetting_clears_it(
|
||||
ssh_host: SSHHost, tmp_path: Path
|
||||
) -> None:
|
||||
import subprocess
|
||||
|
||||
from nisse_dev.execution import HostKeyChanged
|
||||
|
||||
host = SSHHost(
|
||||
ssh_host.address,
|
||||
key=ssh_host._key,
|
||||
user=ssh_host.user,
|
||||
port=ssh_host.port,
|
||||
state_dir=tmp_path / "state",
|
||||
)
|
||||
assert host.execute("true").ok # first contact pins the key
|
||||
host.close()
|
||||
# Someone else's key where the pinned one was: a rebuilt VM, or not it.
|
||||
subprocess.run(
|
||||
["ssh-keygen", "-q", "-t", "ed25519", "-N", "", "-f", str(tmp_path / "other")],
|
||||
check=True,
|
||||
)
|
||||
other = (tmp_path / "other.pub").read_text().split()
|
||||
known = tmp_path / "state" / "known_hosts"
|
||||
name = host.address if host.port == 22 else f"[{host.address}]:{host.port}"
|
||||
known.write_text(f"{name} {other[0]} {other[1]}\n")
|
||||
with pytest.raises(HostKeyChanged):
|
||||
host.execute("true")
|
||||
host.forget_host_key()
|
||||
assert host.execute("true").ok # the real key pinned again
|
||||
Reference in new issue
Block a user