fix: redact private paths from identity receipts
This commit is contained in:
parent
a356b21403
commit
b238fd6fc2
3 changed files with 168 additions and 11 deletions
|
|
@ -170,6 +170,12 @@ python -m scripts.leo_identity_profile compile \
|
||||||
All commands require a clean Git worktree because a commit plus an unretained
|
All commands require a clean Git worktree because a commit plus an unretained
|
||||||
dirty source tree is not reproducible.
|
dirty source tree is not reproducible.
|
||||||
|
|
||||||
|
Child processes execute the real interpreter and temporary-profile paths only
|
||||||
|
in process-local memory. Retained receipts persist a repo-relative
|
||||||
|
`logical_command`, the stable `<python>` and `<temporary-profile>` placeholders,
|
||||||
|
and an explicit `profile_role`; they never serialize the checkout, interpreter,
|
||||||
|
or temporary directory path.
|
||||||
|
|
||||||
## State inventory and transitions
|
## State inventory and transitions
|
||||||
|
|
||||||
| State | Meaning | Next valid transition |
|
| State | Meaning | Next valid transition |
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
from datetime import UTC, datetime
|
from datetime import UTC, datetime
|
||||||
from pathlib import Path
|
from pathlib import Path, PurePosixPath, PureWindowsPath
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
if __package__ in {None, ""}:
|
if __package__ in {None, ""}:
|
||||||
|
|
@ -24,6 +24,7 @@ from scripts import leo_identity_manifest as identity
|
||||||
from scripts import leo_identity_profile as profile_runtime
|
from scripts import leo_identity_profile as profile_runtime
|
||||||
|
|
||||||
SCHEMA = "livingip.leoIdentityReconstructionReceipt.v1"
|
SCHEMA = "livingip.leoIdentityReconstructionReceipt.v1"
|
||||||
|
PRIVATE_PATH_PREFIXES = tuple("/" + value for value in ("Users/", "private/tmp/", "var/folders/"))
|
||||||
|
|
||||||
|
|
||||||
class CanaryError(RuntimeError):
|
class CanaryError(RuntimeError):
|
||||||
|
|
@ -35,6 +36,25 @@ def _require(condition: bool, message: str) -> None:
|
||||||
raise CanaryError(message)
|
raise CanaryError(message)
|
||||||
|
|
||||||
|
|
||||||
|
def _receipt_strings(value: object) -> list[str]:
|
||||||
|
if isinstance(value, dict):
|
||||||
|
keys = [key for key in value if isinstance(key, str)]
|
||||||
|
return keys + [text for item in value.values() for text in _receipt_strings(item)]
|
||||||
|
if isinstance(value, list):
|
||||||
|
return [text for item in value for text in _receipt_strings(item)]
|
||||||
|
return [value] if isinstance(value, str) else []
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_portable_receipt(value: object) -> None:
|
||||||
|
for text in _receipt_strings(value):
|
||||||
|
if (
|
||||||
|
any(prefix in text for prefix in PRIVATE_PATH_PREFIXES)
|
||||||
|
or PurePosixPath(text).is_absolute()
|
||||||
|
or PureWindowsPath(text).is_absolute()
|
||||||
|
):
|
||||||
|
raise CanaryError("receipt contains a private or absolute filesystem path")
|
||||||
|
|
||||||
|
|
||||||
def _process_group_absent(process_group: int) -> bool:
|
def _process_group_absent(process_group: int) -> bool:
|
||||||
try:
|
try:
|
||||||
os.killpg(process_group, 0)
|
os.killpg(process_group, 0)
|
||||||
|
|
@ -72,15 +92,55 @@ def _cleanup_remaining_process_group(process_group: int) -> str | None:
|
||||||
return "SIGKILL"
|
return "SIGKILL"
|
||||||
|
|
||||||
|
|
||||||
|
def _logical_repo_path(path: Path, *, deployment_root: Path, placeholder: str) -> str:
|
||||||
|
"""Return a stable repo-relative path or a non-identifying placeholder."""
|
||||||
|
|
||||||
|
try:
|
||||||
|
relative = path.resolve().relative_to(deployment_root.resolve())
|
||||||
|
except (OSError, ValueError):
|
||||||
|
return placeholder
|
||||||
|
value = relative.as_posix()
|
||||||
|
return value if value != "." else "."
|
||||||
|
|
||||||
|
|
||||||
|
def _logical_query_command(
|
||||||
|
*,
|
||||||
|
deployment_root: Path,
|
||||||
|
source_root: Path,
|
||||||
|
hermes_root: Path,
|
||||||
|
) -> list[str]:
|
||||||
|
return [
|
||||||
|
"<python>",
|
||||||
|
"-m",
|
||||||
|
"scripts.leo_identity_profile",
|
||||||
|
"query",
|
||||||
|
"--profile",
|
||||||
|
"<temporary-profile>",
|
||||||
|
"--source-root",
|
||||||
|
_logical_repo_path(source_root, deployment_root=deployment_root, placeholder="<source-root>"),
|
||||||
|
"--hermes-root",
|
||||||
|
_logical_repo_path(hermes_root, deployment_root=deployment_root, placeholder="<hermes-root>"),
|
||||||
|
"--deployment-root",
|
||||||
|
".",
|
||||||
|
"--query",
|
||||||
|
profile_runtime.FIXED_QUERY,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def _run_query_child(
|
def _run_query_child(
|
||||||
*,
|
*,
|
||||||
deployment_root: Path,
|
deployment_root: Path,
|
||||||
profile: Path,
|
profile: Path,
|
||||||
source_root: Path,
|
source_root: Path,
|
||||||
hermes_root: Path,
|
hermes_root: Path,
|
||||||
|
profile_role: str,
|
||||||
timeout_seconds: float = 60,
|
timeout_seconds: float = 60,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
command = [
|
deployment_root = deployment_root.resolve()
|
||||||
|
profile = profile.resolve()
|
||||||
|
source_root = source_root.resolve()
|
||||||
|
hermes_root = hermes_root.resolve()
|
||||||
|
actual_argv = [
|
||||||
sys.executable,
|
sys.executable,
|
||||||
"-m",
|
"-m",
|
||||||
"scripts.leo_identity_profile",
|
"scripts.leo_identity_profile",
|
||||||
|
|
@ -97,7 +157,7 @@ def _run_query_child(
|
||||||
profile_runtime.FIXED_QUERY,
|
profile_runtime.FIXED_QUERY,
|
||||||
]
|
]
|
||||||
process = subprocess.Popen(
|
process = subprocess.Popen(
|
||||||
command,
|
actual_argv,
|
||||||
cwd=deployment_root,
|
cwd=deployment_root,
|
||||||
env={**os.environ, "PYTHONDONTWRITEBYTECODE": "1"},
|
env={**os.environ, "PYTHONDONTWRITEBYTECODE": "1"},
|
||||||
text=True,
|
text=True,
|
||||||
|
|
@ -143,7 +203,14 @@ def _run_query_child(
|
||||||
except json.JSONDecodeError as exc:
|
except json.JSONDecodeError as exc:
|
||||||
raise CanaryError("identity child returned invalid JSON") from exc
|
raise CanaryError("identity child returned invalid JSON") from exc
|
||||||
return {
|
return {
|
||||||
"command": command,
|
"logical_command": _logical_query_command(
|
||||||
|
deployment_root=deployment_root,
|
||||||
|
source_root=source_root,
|
||||||
|
hermes_root=hermes_root,
|
||||||
|
),
|
||||||
|
"command_serialization": "logical_redacted_v1",
|
||||||
|
"profile_role": profile_role,
|
||||||
|
"actual_argv_persisted": False,
|
||||||
"launcher_pid": process.pid,
|
"launcher_pid": process.pid,
|
||||||
"returncode": process.returncode,
|
"returncode": process.returncode,
|
||||||
"timed_out": timed_out,
|
"timed_out": timed_out,
|
||||||
|
|
@ -181,6 +248,7 @@ def _drift_attempt(
|
||||||
profile=drift_profile,
|
profile=drift_profile,
|
||||||
source_root=source_root,
|
source_root=source_root,
|
||||||
hermes_root=hermes_root,
|
hermes_root=hermes_root,
|
||||||
|
profile_role="drift_negative",
|
||||||
)
|
)
|
||||||
child["fail_closed"] = bool(
|
child["fail_closed"] = bool(
|
||||||
child["returncode"] == 3
|
child["returncode"] == 3
|
||||||
|
|
@ -250,6 +318,7 @@ def run_canary(args: argparse.Namespace) -> tuple[dict[str, Any], int]:
|
||||||
profile=compiled_profile,
|
profile=compiled_profile,
|
||||||
source_root=args.source_root,
|
source_root=args.source_root,
|
||||||
hermes_root=args.hermes_root,
|
hermes_root=args.hermes_root,
|
||||||
|
profile_role="first_start",
|
||||||
)
|
)
|
||||||
report["first_start"] = first
|
report["first_start"] = first
|
||||||
_require(_query_passed(first), "first disposable identity process did not answer successfully")
|
_require(_query_passed(first), "first disposable identity process did not answer successfully")
|
||||||
|
|
@ -271,6 +340,7 @@ def run_canary(args: argparse.Namespace) -> tuple[dict[str, Any], int]:
|
||||||
profile=compiled_profile,
|
profile=compiled_profile,
|
||||||
source_root=args.source_root,
|
source_root=args.source_root,
|
||||||
hermes_root=args.hermes_root,
|
hermes_root=args.hermes_root,
|
||||||
|
profile_role="pre_restart_drift",
|
||||||
)
|
)
|
||||||
report["pre_restart_drift"] = rejected
|
report["pre_restart_drift"] = rejected
|
||||||
report["drift_target"] = "compiled SOUL.md generated view"
|
report["drift_target"] = "compiled SOUL.md generated view"
|
||||||
|
|
@ -315,6 +385,7 @@ def run_canary(args: argparse.Namespace) -> tuple[dict[str, Any], int]:
|
||||||
profile=restarted_profile,
|
profile=restarted_profile,
|
||||||
source_root=args.source_root,
|
source_root=args.source_root,
|
||||||
hermes_root=args.hermes_root,
|
hermes_root=args.hermes_root,
|
||||||
|
profile_role="restart",
|
||||||
)
|
)
|
||||||
report["restart"] = second
|
report["restart"] = second
|
||||||
_require(_query_passed(second), "restarted disposable identity process did not answer successfully")
|
_require(_query_passed(second), "restarted disposable identity process did not answer successfully")
|
||||||
|
|
@ -370,7 +441,13 @@ def run_canary(args: argparse.Namespace) -> tuple[dict[str, Any], int]:
|
||||||
exit_code = 0
|
exit_code = 0
|
||||||
except (CanaryError, identity.IdentityManifestError, OSError, subprocess.SubprocessError) as exc:
|
except (CanaryError, identity.IdentityManifestError, OSError, subprocess.SubprocessError) as exc:
|
||||||
report["status"] = "fail"
|
report["status"] = "fail"
|
||||||
report["errors"].append({"type": type(exc).__name__, "message": str(exc)})
|
report["errors"].append(
|
||||||
|
{
|
||||||
|
"type": type(exc).__name__,
|
||||||
|
"message": "details_redacted",
|
||||||
|
"detail_sha256": behavior.sha256_bytes(str(exc).encode("utf-8")),
|
||||||
|
}
|
||||||
|
)
|
||||||
finally:
|
finally:
|
||||||
shutil.rmtree(temporary_root, ignore_errors=True)
|
shutil.rmtree(temporary_root, ignore_errors=True)
|
||||||
report["cleanup"] = {
|
report["cleanup"] = {
|
||||||
|
|
@ -402,6 +479,7 @@ def run_canary(args: argparse.Namespace) -> tuple[dict[str, Any], int]:
|
||||||
else "Local first-process and pre-restart drift-refusal component only; no successful restart proof, "
|
else "Local first-process and pre-restart drift-refusal component only; no successful restart proof, "
|
||||||
"GatewayRunner, hosted model, production database, VPS, or T3 claim."
|
"GatewayRunner, hosted model, production database, VPS, or T3 claim."
|
||||||
)
|
)
|
||||||
|
_validate_portable_receipt(report)
|
||||||
stable = {key: value for key, value in report.items() if key != "receipt_sha256"}
|
stable = {key: value for key, value in report.items() if key != "receipt_sha256"}
|
||||||
report["receipt_sha256"] = behavior.canonical_sha256(stable)
|
report["receipt_sha256"] = behavior.canonical_sha256(stable)
|
||||||
identity.write_json(output, report)
|
identity.write_json(output, report)
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import json
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path, PureWindowsPath
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
@ -16,6 +16,10 @@ from scripts import leo_identity_profile as profile_runtime
|
||||||
from scripts import run_leo_identity_reconstruction_canary as canary
|
from scripts import run_leo_identity_reconstruction_canary as canary
|
||||||
|
|
||||||
ROOT = Path(__file__).resolve().parents[1]
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
COMMITTED_RECEIPTS = (
|
||||||
|
ROOT / "docs/reports/leo-working-state-20260709/leo-reproducible-identity-t2-current.json",
|
||||||
|
ROOT / "docs/reports/leo-working-state-20260709/leo-reproducible-identity-drift-negative-current.json",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _write(path: Path, value: str) -> None:
|
def _write(path: Path, value: str) -> None:
|
||||||
|
|
@ -27,6 +31,21 @@ def _write_json(path: Path, value: dict) -> None:
|
||||||
_write(path, json.dumps(value, indent=2, sort_keys=True) + "\n")
|
_write(path, json.dumps(value, indent=2, sort_keys=True) + "\n")
|
||||||
|
|
||||||
|
|
||||||
|
def _assert_path_portable_receipt(value: object) -> None:
|
||||||
|
canary._validate_portable_receipt(value)
|
||||||
|
|
||||||
|
|
||||||
|
def _assert_logical_child_execution(child: dict, *, profile_role: str) -> None:
|
||||||
|
logical_command = child["logical_command"]
|
||||||
|
assert "command" not in child
|
||||||
|
assert child["profile_role"] == profile_role
|
||||||
|
assert child["command_serialization"] == "logical_redacted_v1"
|
||||||
|
assert child["actual_argv_persisted"] is False
|
||||||
|
assert logical_command[0] == "<python>"
|
||||||
|
assert logical_command[logical_command.index("--profile") + 1] == "<temporary-profile>"
|
||||||
|
_assert_path_portable_receipt(child)
|
||||||
|
|
||||||
|
|
||||||
def _fixture(tmp_path: Path) -> dict[str, Path | dict]:
|
def _fixture(tmp_path: Path) -> dict[str, Path | dict]:
|
||||||
repo = tmp_path / "repo"
|
repo = tmp_path / "repo"
|
||||||
profile = repo / "profile-template"
|
profile = repo / "profile-template"
|
||||||
|
|
@ -224,6 +243,50 @@ def _canary_args(fixture: dict[str, Path | dict], output: Path, *, inject_drift:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"private_path",
|
||||||
|
[
|
||||||
|
"/" + "Users/operator/checkout/profile",
|
||||||
|
"/" + "private/tmp/lane/.venv/bin/python",
|
||||||
|
"/" + "var/folders/cache/temporary-profile",
|
||||||
|
"/" + "tmp/temporary-profile",
|
||||||
|
"/" + "opt/checkout/interpreter",
|
||||||
|
"/" + "workspace/checkout/temporary-profile",
|
||||||
|
str(PureWindowsPath("C:/operator/checkout/temporary-profile")),
|
||||||
|
"\\\\" + "server\\share\\checkout\\temporary-profile",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_receipt_path_guard_recursively_rejects_private_and_absolute_paths(private_path: str) -> None:
|
||||||
|
with pytest.raises(canary.CanaryError, match="private or absolute filesystem path"):
|
||||||
|
_assert_path_portable_receipt({"outer": [{"logical_command": ["<python>", private_path]}]})
|
||||||
|
|
||||||
|
|
||||||
|
def test_receipt_path_guard_rejects_an_absolute_path_in_a_nested_key() -> None:
|
||||||
|
private_key = "/" + "tmp/checkout/temporary-profile"
|
||||||
|
with pytest.raises(canary.CanaryError, match="private or absolute filesystem path"):
|
||||||
|
_assert_path_portable_receipt({"outer": [{private_key: "<temporary-profile>"}]})
|
||||||
|
|
||||||
|
|
||||||
|
def test_logical_command_uses_placeholders_for_paths_outside_the_deployment_root(tmp_path: Path) -> None:
|
||||||
|
logical_command = canary._logical_query_command(
|
||||||
|
deployment_root=tmp_path / "checkout",
|
||||||
|
source_root=tmp_path / "outside-source",
|
||||||
|
hermes_root=tmp_path / "outside-hermes",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert logical_command[logical_command.index("--source-root") + 1] == "<source-root>"
|
||||||
|
assert logical_command[logical_command.index("--hermes-root") + 1] == "<hermes-root>"
|
||||||
|
_assert_path_portable_receipt(logical_command)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("receipt_path", COMMITTED_RECEIPTS, ids=lambda path: path.name)
|
||||||
|
def test_committed_identity_receipts_are_path_portable_and_self_hashed(receipt_path: Path) -> None:
|
||||||
|
receipt = json.loads(receipt_path.read_text(encoding="utf-8"))
|
||||||
|
_assert_path_portable_receipt(receipt)
|
||||||
|
stable = {key: value for key, value in receipt.items() if key != "receipt_sha256"}
|
||||||
|
assert behavior.canonical_sha256(stable) == receipt["receipt_sha256"]
|
||||||
|
|
||||||
|
|
||||||
def test_manifest_compiles_generated_views_and_reproduces_identity_across_queries(tmp_path: Path) -> None:
|
def test_manifest_compiles_generated_views_and_reproduces_identity_across_queries(tmp_path: Path) -> None:
|
||||||
fixture = _fixture(tmp_path)
|
fixture = _fixture(tmp_path)
|
||||||
manifest = fixture["manifest"]
|
manifest = fixture["manifest"]
|
||||||
|
|
@ -493,7 +556,8 @@ def test_fully_rehashed_model_claim_must_match_observed_runtime(tmp_path: Path)
|
||||||
|
|
||||||
def test_restart_canary_uses_distinct_processes_and_cleans_up(tmp_path: Path) -> None:
|
def test_restart_canary_uses_distinct_processes_and_cleans_up(tmp_path: Path) -> None:
|
||||||
fixture = _fixture(tmp_path)
|
fixture = _fixture(tmp_path)
|
||||||
report, exit_code = canary.run_canary(_canary_args(fixture, tmp_path / "restart-receipt.json"))
|
output = tmp_path / "restart-receipt.json"
|
||||||
|
report, exit_code = canary.run_canary(_canary_args(fixture, output))
|
||||||
|
|
||||||
assert exit_code == 0
|
assert exit_code == 0
|
||||||
assert report["status"] == "pass"
|
assert report["status"] == "pass"
|
||||||
|
|
@ -501,9 +565,10 @@ def test_restart_canary_uses_distinct_processes_and_cleans_up(tmp_path: Path) ->
|
||||||
assert report["gates"]["restart_profile_recompiled_from_manifest"] is True
|
assert report["gates"]["restart_profile_recompiled_from_manifest"] is True
|
||||||
assert report["restart_compile"]["session_directories_started_empty"] is True
|
assert report["restart_compile"]["session_directories_started_empty"] is True
|
||||||
assert report["drift_compile"]["session_directories_started_empty"] is True
|
assert report["drift_compile"]["session_directories_started_empty"] is True
|
||||||
first_profile = report["first_start"]["command"][report["first_start"]["command"].index("--profile") + 1]
|
_assert_logical_child_execution(report["first_start"], profile_role="first_start")
|
||||||
restart_profile = report["restart"]["command"][report["restart"]["command"].index("--profile") + 1]
|
_assert_logical_child_execution(report["restart"], profile_role="restart")
|
||||||
assert first_profile != restart_profile
|
_assert_logical_child_execution(report["drift_negative"], profile_role="drift_negative")
|
||||||
|
_assert_path_portable_receipt(report)
|
||||||
assert report["first_start"]["launcher_pid"] != report["restart"]["launcher_pid"]
|
assert report["first_start"]["launcher_pid"] != report["restart"]["launcher_pid"]
|
||||||
assert report["first_start"]["process_group_absent_after_wait"] is True
|
assert report["first_start"]["process_group_absent_after_wait"] is True
|
||||||
assert report["restart"]["process_group_absent_after_wait"] is True
|
assert report["restart"]["process_group_absent_after_wait"] is True
|
||||||
|
|
@ -513,22 +578,28 @@ def test_restart_canary_uses_distinct_processes_and_cleans_up(tmp_path: Path) ->
|
||||||
assert report["current_tier"] == "T2_runtime"
|
assert report["current_tier"] == "T2_runtime"
|
||||||
assert report["cleanup"]["temporary_root_removed"] is True
|
assert report["cleanup"]["temporary_root_removed"] is True
|
||||||
assert behavior.git_state(fixture["repo"])["worktree_clean"] is True
|
assert behavior.git_state(fixture["repo"])["worktree_clean"] is True
|
||||||
|
assert json.loads(output.read_text(encoding="utf-8")) == report
|
||||||
|
|
||||||
|
|
||||||
def test_restart_canary_rejects_drift_before_second_start(tmp_path: Path) -> None:
|
def test_restart_canary_rejects_drift_before_second_start(tmp_path: Path) -> None:
|
||||||
fixture = _fixture(tmp_path)
|
fixture = _fixture(tmp_path)
|
||||||
report, exit_code = canary.run_canary(_canary_args(fixture, tmp_path / "drift-receipt.json", inject_drift=True))
|
output = tmp_path / "drift-receipt.json"
|
||||||
|
report, exit_code = canary.run_canary(_canary_args(fixture, output, inject_drift=True))
|
||||||
|
|
||||||
assert exit_code == 0
|
assert exit_code == 0
|
||||||
assert report["status"] == "pass"
|
assert report["status"] == "pass"
|
||||||
assert report["gates"]["drift_detected_before_restart"] is True
|
assert report["gates"]["drift_detected_before_restart"] is True
|
||||||
assert report["second_start_attempted"] is False
|
assert report["second_start_attempted"] is False
|
||||||
assert "restart" not in report
|
assert "restart" not in report
|
||||||
|
_assert_logical_child_execution(report["first_start"], profile_role="first_start")
|
||||||
|
_assert_logical_child_execution(report["pre_restart_drift"], profile_role="pre_restart_drift")
|
||||||
|
_assert_path_portable_receipt(report)
|
||||||
assert report["pre_restart_drift"]["process_group_absent_after_wait"] is True
|
assert report["pre_restart_drift"]["process_group_absent_after_wait"] is True
|
||||||
assert all(report["gates"].values())
|
assert all(report["gates"].values())
|
||||||
assert report["goal_tier_satisfied"] is False
|
assert report["goal_tier_satisfied"] is False
|
||||||
assert report["current_tier"] == "T1_model"
|
assert report["current_tier"] == "T1_model"
|
||||||
assert report["cleanup"]["temporary_root_removed"] is True
|
assert report["cleanup"]["temporary_root_removed"] is True
|
||||||
|
assert json.loads(output.read_text(encoding="utf-8")) == report
|
||||||
|
|
||||||
|
|
||||||
def test_query_child_timeout_terminates_process_group(tmp_path: Path) -> None:
|
def test_query_child_timeout_terminates_process_group(tmp_path: Path) -> None:
|
||||||
|
|
@ -550,6 +621,7 @@ time.sleep(60)
|
||||||
profile=fixture["compiled"],
|
profile=fixture["compiled"],
|
||||||
source_root=fixture["repo"],
|
source_root=fixture["repo"],
|
||||||
hermes_root=fixture["hermes"],
|
hermes_root=fixture["hermes"],
|
||||||
|
profile_role="timeout_test",
|
||||||
timeout_seconds=0.5,
|
timeout_seconds=0.5,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -594,6 +666,7 @@ print(json.dumps({query_payload!r}))
|
||||||
profile=fixture["compiled"],
|
profile=fixture["compiled"],
|
||||||
source_root=fixture["repo"],
|
source_root=fixture["repo"],
|
||||||
hermes_root=fixture["hermes"],
|
hermes_root=fixture["hermes"],
|
||||||
|
profile_role="orphan_cleanup_test",
|
||||||
timeout_seconds=5,
|
timeout_seconds=5,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue