ouroboros/tests/test_delegated_directory.py
Ouroboros e1727bc25e Close the review findings on the snapshot-lock change
Five reviews of 226285a1f (Fable triad, grok-4.7 triad and scope, gpt-6-astra
triad and scope; dispositions in the sprint ledger) converged on these fixes:

- untracked_binary_verdicts: a path the first diff omits is identical to the
  staged empty blob, i.e. an empty file, which git still classifies by
  attribute; those paths get a second batch staged as a one-byte blob, so an
  empty `-diff` / `binary` / driver-binary file is binary exactly as the
  per-file verdict said. Only the two staging blobs enter the target's object
  database. Scratch-index allocation is inside the guarded lifecycle, so an
  unavailable temp dir also falls back to the per-file verdict with a warning.
- binary_verdict_candidates: the dotenv policy, the name rules, the PEM head
  (restricted modes) and the size cap decide BEFORE the batch, so a vetoed or
  over-cap file is never handed to git and never reaches a clean filter or an
  encoding conversion; both callers batch only candidates.
- provision_execution_snapshot: the provisional row carries no per-file maps;
  the first lock section is inside the cleanup scope, so a failed update-ref or
  worktree add after the row discards row, pin and admin dir; a discard after a
  busy lock waits 5 s, not the full timeout.
- provision_worktree / remove_worktree follow the same split: admin dir and
  branch under the lock, the checkout populated (`reset --hard --quiet
  --no-recurse-submodules`) and deleted outside it.
- A refused snapshot provision keeps its facts on the configured-child path:
  cause, holder, waited seconds and the producer's detail ride
  `subagent_availability`, the $0 terminal text, the START_FAILED row and the
  acceptance evidence.
- Tests: platform guard on the newline-named fixture entry; non-UTF-8 name
  only where the filesystem accepts it; hook positive control; `_git_env`
  spy; acting-lane split; empty-file classes; filter-tee guard; refusal facts
  on the bootstrap path; the receipt's timing facts join the per-case identity
  set of the directory-geometry payload comparison.
- Docs: the delegated-lane sentences name the acting checkout/delete, the
  registry read-modify-write residual and the target object-database growth
  (owner decision: disclose only); data-layout inventory regenerated.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-24 01:38:17 +03:00

310 lines
16 KiB
Python

"""Ordinary-folder dispatch and complete engine result custody without host Git."""
from hashlib import sha256
import json
from pathlib import Path
import pytest
from ouroboros import delegate_custody as custody
from ouroboros.delegate_directory import capture_directory_result, integrate_directory_result
from ouroboros.tools import delegate
from ouroboros.tools.registry import ToolContext
from tests._delegated_transport_shared import _HealthStub, _owned_gateway_uses_each_test_transport # noqa: F401
class DirectoryEngine(_HealthStub):
def __init__(self, target, strategy="copy", *, lost_apply=False):
super().__init__(engine_version="99.0.0")
self.target, self.strategy, self.lost_apply = target, strategy, lost_apply
self.posts, self.applies, self.decisions = [], [], []
self.body = b"\x00complete binary result\xff" * 1000
state = {"kind": "file", "sha256": "sha256:" + sha256(self.body).hexdigest(),
"sizeBytes": len(self.body), "mode": 420, "artifactPath": "final/files/output.bin"}
self.manifest = {"version": 1, "sourceRoot": str(target),
"executionRoot": str(target if strategy == "direct" else target.parent / "engine-copy"),
"isolation": "live" if strategy == "direct" else "envelope",
"scopePaths": ["."], "complete": True,
"entries": [{"path": "out.bin", "before": None, "after": state}]}
self.raw = json.dumps(self.manifest).encode()
def agent_capabilities(self):
return {**super().agent_capabilities(), "mutability": {"workspaceKinds": ["git", "directory"]}}
def find_project_id(self, root):
assert root == str(self.target)
return "project-existing"
def start_run(self, request, *, idempotency_key):
self.posts.append((request, idempotency_key))
return {"runId": "directory-run", "runDir": "/engine/run"}
def get_run(self, rid):
return {"summary": {"result": {"applyState": "applied" if self.applies else "not_applied"}},
"workProduct": {"kind": "files", "files": {"manifest": "final/files/manifest.json"},
"meta": {"manifest_sha256": "sha256:" + sha256(self.raw).hexdigest(),
"apply_state": "applied" if self.strategy == "direct" else "not_applied"}}}
def stream_run_artifact(self, rid, path, sink, *, expected=None):
body = self.raw if path.endswith("manifest.json") else self.body
facts = {"sha256": sha256(body).hexdigest(), "size": len(body)}
if expected:
assert facts["sha256"] == expected["sha256"].removeprefix("sha256:")
assert expected.get("sizeBytes", len(body)) == len(body)
sink.write(body)
return facts
def apply_run(self, rid, request, *, idempotency_key):
self.applies.append(idempotency_key)
(self.target / "out.bin").write_bytes(self.body)
if self.lost_apply:
self.lost_apply = False
raise OSError("response lost after application")
return {"applied": True, "refused": False, "appliedPaths": ["out.bin"],
"treeMutated": True, "alreadyApplied": False}
def decide_run(self, rid, request, *, idempotency_key):
self.decisions.append((request, idempotency_key))
return {"accepted": True, "status": "discarded"}
def context(tmp_path, monkeypatch):
repo, data, target = [tmp_path / name for name in ("system", "data", "documents")]
for path in (repo, data, target):
path.mkdir()
monkeypatch.setenv("OUROBOROS_SUBAGENT_HARNESS", "some-route=weak-model:low")
return ToolContext(repo_dir=repo, drive_root=data, task_id="parent", workspace_root=target,
workspace_mode="external"), target
@pytest.mark.parametrize("strategy", ["direct", "copy"])
@pytest.mark.parametrize("access", ["workspace_write", "full"])
def test_start_uses_normal_writing_mode_without_git_or_fake_snapshot(tmp_path, monkeypatch, strategy, access):
from ouroboros.gateways import claudexor
import tests._delegated_transport_shared as shared
ctx, target = context(tmp_path, monkeypatch)
engine = DirectoryEngine(target, strategy)
engine.profiles = ("readonly", "workspace_write", "full")
snapshot = shared._transport_snapshot
monkeypatch.setattr(shared, "_transport_snapshot", lambda route: {**snapshot(route), "access": access})
grants = []
monkeypatch.setattr(engine, "ensure_full_access", lambda root: grants.append(root), raising=False)
monkeypatch.setattr(claudexor, "ClaudexorGateway", lambda *a, **k: engine)
result = json.loads(delegate._delegate_start(ctx, "edit documents", directory_strategy=strategy, scope_paths=["."]).text)
assert result["status"] == "started", result
request, key = engine.posts[0]
assert request["scope"]["root"] == str(target)
assert request["mode"] == "agent" and request["access"] == access
assert grants == ([str(target)] if access == "full" else [])
assert request["execution"]["workspaceKind"] == "directory"
assert request["execution"]["isolation"] == ("live" if strategy == "direct" else "envelope")
assert request["execution"]["scopePaths"] == ["."]
assert result["execution_root"] == (str(target) if strategy == "direct" else None)
recorded = custody.replay(ctx.drive_root)["directory-run"]
assert recorded.invocation_id == key and not recorded.snapshot_id and not recorded.baseline_sha
assert recorded.resource_ref["strategy"] == strategy
assert not (target / ".git").exists()
@pytest.mark.parametrize("options,expected_scope", [
({}, None),
({"directory_strategy": "direct"}, None),
({"scope_paths": []}, []),
({"directory_strategy": "direct", "scope_paths": []}, []),
])
def test_a_write_capable_child_keeps_its_attested_folder_shape(
tmp_path, monkeypatch, options, expected_scope,
):
"""#882 changed nothing for a child that can actually open the session.
The read-only repair must not quietly rewrite a write-capable request: an
explicit `direct` still starts the same live directory session as omitting
it, and an explicit empty footprint still rides the wire as `scopePaths: []`
— the parent said "capture nothing", which is a different attested choice
from saying nothing at all, and only the engine gets to interpret it.
"""
from ouroboros.gateways import claudexor
ctx, target = context(tmp_path, monkeypatch)
engine = DirectoryEngine(target, "direct")
monkeypatch.setattr(claudexor, "ClaudexorGateway", lambda *a, **k: engine)
result = json.loads(delegate._delegate_start(ctx, "edit documents", **options).text)
assert result["status"] == "started", result
execution = engine.posts[0][0]["execution"]
assert execution["workspaceKind"] == "directory" and execution["isolation"] == "live"
assert execution.get("scopePaths") == expected_scope
def _git_workspace_start(tmp_path, monkeypatch, case, **options):
"""Start one write-capable child against a fresh Git workspace."""
import subprocess
from ouroboros.gateways import claudexor
root = tmp_path / case
root.mkdir()
monkeypatch.setenv("OUROBOROS_SUBAGENT_WORKTREE_ROOT", str(root / "snaps"))
ctx, target = context(root, monkeypatch)
subprocess.run(["git", "init"], cwd=str(target), capture_output=True, check=True)
engine = DirectoryEngine(target, "direct")
monkeypatch.setattr(claudexor, "ClaudexorGateway", lambda *a, **k: engine)
delegate._CUSTODY.clear()
payload = json.loads(delegate._delegate_start(ctx, "edit documents", **options).text)
delegate._CUSTODY.clear()
return payload, engine
def test_a_git_workspace_treats_the_named_default_as_omission_and_still_refuses_real_geometry(
tmp_path, monkeypatch,
):
"""#882 reaches the sibling refusal site too: a named default is not a request.
A Git tree keeps its private-snapshot contract, so `copy` or a selected
footprint is a genuine contradiction for a write-capable child there and stays
a typed `definitely_unrun` refusal naming that contract. `direct` with nothing
selected asks for nothing at all — it is the documented spelling of omitting
both — so it takes the unchanged snapshot path omission takes instead of dying
at the host's pre-start over a word that changed no behaviour.
"""
# Each case gets its own folder and its own invocation, so identity fields
# differ by construction; every OTHER key and value must match, including the
# key set itself — that is what "took the omitted path" means here.
per_case = ("root", "execution_root", "snapshot_id", "baseline_sha", "baseline_id",
"baseline_manifest_read", "run_id", "invocation_id", "authority_target_root",
"snapshot") # the receipt's provisioning facts carry wall-clock seconds (#1241)
compared = lambda payload: {key: ("<per-case identity>" if key in per_case else value)
for key, value in payload.items()}
omitted, omitted_engine = _git_workspace_start(tmp_path, monkeypatch, "omit")
assert omitted["status"] == "started" and omitted["baseline_id"], omitted
for index, named_default in enumerate((
{"directory_strategy": "direct"}, {"scope_paths": []},
{"directory_strategy": "direct", "scope_paths": []},
)):
named, engine = _git_workspace_start(tmp_path, monkeypatch, f"named-{index}", **named_default)
assert compared(named) == compared(omitted), named_default
assert len(engine.posts) == len(omitted_engine.posts), named_default
for index, geometry in enumerate((
{"directory_strategy": "copy", "scope_paths": ["."]},
{"scope_paths": ["src"]},
)):
refused, engine = _git_workspace_start(tmp_path, monkeypatch, f"geometry-{index}", **geometry)
assert refused["status"] == "refused", geometry
assert refused["reason"] == "directory_execution_unavailable"
assert "Git workspaces keep their snapshot contract" in refused["detail"]
assert refused["definitely_unrun"] is True
assert engine.posts == []
def entry(ctx, target, strategy):
return custody.RunCustody(run_id="directory-run", task_id=ctx.task_id, route_id="some-route",
target_root=str(target), settled=True, access="workspace_write",
resource_ref={"workspace_kind": "directory", "strategy": strategy, "scopePaths": ["."]})
@pytest.mark.parametrize("strategy", ["direct", "copy"])
def test_capture_reopen_and_disposition_use_complete_engine_bytes(tmp_path, monkeypatch, strategy):
ctx, target = context(tmp_path, monkeypatch)
engine = DirectoryEngine(target, strategy)
held = entry(ctx, target, strategy)
if strategy == "direct":
(target / "out.bin").write_bytes(engine.body)
captured = capture_directory_result(ctx.drive_root, held, engine)
assert Path(captured["file_outputs"][1]["path"]).read_bytes() == engine.body
assert capture_directory_result(ctx.drive_root, held, engine) == captured
response = json.loads(integrate_directory_result(ctx, held, "apply", "accepted", engine))
assert response["status"] == "applied"
assert (target / "out.bin").read_bytes() == engine.body
assert len(engine.applies) == (1 if strategy == "copy" else 0)
assert not (target / ".git").exists()
def test_discard_is_engine_disposition_not_fake_apply(tmp_path, monkeypatch):
ctx, target = context(tmp_path, monkeypatch)
engine, held = DirectoryEngine(target), entry(ctx, target, "copy")
response = json.loads(integrate_directory_result(ctx, held, "reject", "not selected", engine))
assert response["status"] == "rejected" and held.patch_disposed == "rejected"
assert engine.decisions[0][0] == {"action": "discard"}
assert not engine.applies and not (target / "out.bin").exists()
def test_lost_apply_leaves_existing_intent_pending(tmp_path, monkeypatch):
ctx, target = context(tmp_path, monkeypatch)
engine, held = DirectoryEngine(target, lost_apply=True), entry(ctx, target, "copy")
with pytest.raises(OSError, match="response lost"):
integrate_directory_result(ctx, held, "apply", "accepted", engine)
assert held.patch_apply_pending and not held.patch_disposed
assert (target / "out.bin").read_bytes() == engine.body
assert "APPLY_AMBIGUOUS" in integrate_directory_result(ctx, held, "apply", "", engine)
assert len(engine.applies) == 1
response = json.loads(integrate_directory_result(ctx, held, "apply", "", engine, acknowledge_ambiguous=True))
assert response["status"] == "applied"
assert engine.applies[0] == engine.applies[1]
def test_selected_apply_keeps_remaining_results_undisposed(tmp_path, monkeypatch):
ctx, target = context(tmp_path, monkeypatch)
engine, held = DirectoryEngine(target), entry(ctx, target, "copy")
get_run = engine.get_run
def partially_delivered(run_id):
detail = get_run(run_id)
detail["summary"]["result"]["applyState"] = "not_applied"
return detail
monkeypatch.setattr(engine, "get_run", partially_delivered)
response = json.loads(integrate_directory_result(
ctx, held, "apply", "selected output", engine, paths=["out.bin"]))
assert response["status"] == "partially_applied"
assert response["engine_receipt"]["appliedPaths"] == ["out.bin"]
assert (target / "out.bin").read_bytes() == engine.body
assert not held.patch_disposed and not held.patch_apply_pending
def test_registered_integration_tool_accepts_selected_paths():
from ouroboros.tools.subagent_integration import get_tools
entry = next(item for item in get_tools() if item.name == "integrate_delegated_patch")
assert entry.handler.__defaults__ is not None
assert "paths" in __import__("inspect").signature(entry.handler).parameters
def test_lost_start_replays_original_processing_facts_after_setting_changes(tmp_path, monkeypatch):
from ouroboros.gateways import claudexor
ctx, target = context(tmp_path, monkeypatch)
engine = DirectoryEngine(target)
capabilities = engine.agent_capabilities
monkeypatch.setattr(engine, "agent_capabilities", lambda: {
**capabilities(), "harnesses": [{**row, "processingPreferences": ["fast", "economy"]}
for row in capabilities()["harnesses"]]})
prepare_actor = delegate.prepare_delegate_start_actor
preference = "economy"
def actor(*args, **kwargs):
captured, refusal = prepare_actor(*args, **kwargs)
return {**captured, "processing_preference": preference}, refusal
def start(request, *, idempotency_key):
engine.posts.append((request, idempotency_key))
if len(engine.posts) == 1:
raise claudexor.ClaudexorUnavailable("daemon_unreachable", "response lost")
return {"runId": "directory-run"}
monkeypatch.setattr(delegate, "prepare_delegate_start_actor", actor)
monkeypatch.setattr(engine, "start_run", start)
monkeypatch.setattr(claudexor, "ClaudexorGateway", lambda: engine)
lost = json.loads(delegate._delegate_start(ctx, "edit documents", directory_strategy="copy", scope_paths=["."]).text)
token = lost["pending_invocation_id"]
original = custody.invocation_record(ctx.drive_root, token)["processing"]
assert original["requested"] == original["submitted"] == "economy"
preference = "fast"
retried = json.loads(delegate._delegate_start(ctx, "edit documents", retry_of=token).text)
assert retried["status"] == "started" and retried["processing"] == original
assert engine.posts[0] == engine.posts[1]
assert engine.posts[1][0]["processingPreference"] == "economy"
held = custody.replay(ctx.drive_root)["directory-run"]
assert held.processing_preference == "economy"
assert held.effort == engine.posts[1][0].get("effort", "")
from ouroboros.subagent_history import record_session_execution, subagent_last_delegation
monkeypatch.setattr(custody, "invocation_record", lambda *_a, **_kw: pytest.fail("history scanned an invocation"))
record_session_execution(ctx.drive_root, held,
{"summary": {"state": "succeeded", "finishedAt": "2099-01-01T00:00:00Z"}}, {})
assert subagent_last_delegation(ctx.drive_root)["identity"]["processing_preference"] == "economy"