Publish curated Unraid-AppOps-Gateway source from aaf7d11821a86be65c91fead97a6a4758c4fbacd

This commit is contained in:
Public Source Publisher committed 2026-10-08 13:51:07 +00:00
1 parent 76d7249b5e
commit 837c263cd8
7 files changed
+1672 -58

No files matched your search

+1 -1
View File
@@ -1,3 +1,3 @@
# Public source export # Public source export
This source snapshot was generated from the private canonical repository at revision `21729207a161d87393cfb73e79582bc373aa19cf`. It contains no private Git history or operational evidence. Changes are published from the canonical repository. This source snapshot was generated from the private canonical repository at revision `aaf7d11821a86be65c91fead97a6a4758c4fbacd`. It contains no private Git history or operational evidence. Changes are published from the canonical repository.
+14 -9
View File
@@ -1,6 +1,6 @@
{ {
"schemaVersion": 1, "schemaVersion": 1,
"sourceRevision": "21729207a161d87393cfb73e79582bc373aa19cf", "sourceRevision": "aaf7d11821a86be65c91fead97a6a4758c4fbacd",
"files": [ "files": [
{ {
"path": ".dockerignore", "path": ".dockerignore",
@@ -404,8 +404,8 @@
}, },
{ {
"path": "backend/app/mcp_server.py", "path": "backend/app/mcp_server.py",
"sha256": "24817f3e7566b441876b3e12c0bffe52cf596cf27d098e1f8579bce8a28c65ec", "sha256": "feac22ee7a1c755f0fafd6890500b83442b5f13b51e6a6cde3b0227c5b5ab25b",
"bytes": 47931 "bytes": 49536
}, },
{ {
"path": "backend/app/runner_statistics_mcp.py", "path": "backend/app/runner_statistics_mcp.py",
@@ -539,8 +539,8 @@
}, },
{ {
"path": "backend/app/services/deployment_broker.py", "path": "backend/app/services/deployment_broker.py",
"sha256": "452e6a2c3316501a7828990cb9fe7f689edfd1fa07e68e20e612126cd387bc5f", "sha256": "4370e327b0b608b46be446159f59b9975fb99381967282c9661dc1c7837f16bd",
"bytes": 42391 "bytes": 45377
}, },
{ {
"path": "backend/app/services/deployment_control_plane.py", "path": "backend/app/services/deployment_control_plane.py",
@@ -969,8 +969,8 @@
}, },
{ {
"path": "backend/tests/test_controller_hub_release_volume.py", "path": "backend/tests/test_controller_hub_release_volume.py",
"sha256": "b629dadd5616d5afd5ff8ad33662d93da829424226f0bb36bda71d9c621049cf", "sha256": "630ba534344dd11a499f32ed6aa53816f1cefd0537a4d69f278ed2a11fa68f6a",
"bytes": 144585 "bytes": 160633
}, },
{ {
"path": "backend/tests/test_controller_root_procedure_contract.py", "path": "backend/tests/test_controller_root_procedure_contract.py",
@@ -1182,6 +1182,11 @@
"sha256": "7fbce0be7f6cc12b68f5b05eb3f7c9eb00478207f1c49ab36ef88915ee53eb82", "sha256": "7fbce0be7f6cc12b68f5b05eb3f7c9eb00478207f1c49ab36ef88915ee53eb82",
"bytes": 9756 "bytes": 9756
}, },
{
"path": "backend/tests/test_hub_delivery_revalidation.py",
"sha256": "4913be4a0b04549d709c56af2fc88fa6743fca54581c69d3cc853277ab44f7a0",
"bytes": 45474
},
{ {
"path": "backend/tests/test_hub_permission_preparation.py", "path": "backend/tests/test_hub_permission_preparation.py",
"sha256": "3ba8efe338a21b100e4bc20e4fbffe043eb57c411a4143c00332fdb368db5011", "sha256": "3ba8efe338a21b100e4bc20e4fbffe043eb57c411a4143c00332fdb368db5011",
@@ -1389,8 +1394,8 @@
}, },
{ {
"path": "backend/tests/test_projectbrain_delivery_revalidation.py", "path": "backend/tests/test_projectbrain_delivery_revalidation.py",
"sha256": "579550ac68438da05d9472196961062f33364fe734fc8c4dfc8f6f912ab286c3", "sha256": "d46946e8be179372c84adab6f56f57de27a4492db6d0edb73d75035a433c5b40",
"bytes": 50954 "bytes": 57909
}, },
{ {
"path": "backend/tests/test_prowlarr_connector.py", "path": "backend/tests/test_prowlarr_connector.py",
+32
View File
@@ -792,6 +792,38 @@ def appops_projectbrain_delivery_revalidate(
)) ))
@appops_mcp.tool(name="appops.hub_delivery.preflight", annotations=_READ_ONLY_TOOL)
@_tracked("appops.hub_delivery.preflight")
def appops_hub_delivery_preflight(revision: str) -> dict[str, Any]:
"""Read fixed MCP Hub delivery evidence without importing or deploying a candidate."""
if not isinstance(revision, str) or not re.fullmatch(r"[0-9a-f]{40}", revision):
raise ValueError("Hub delivery revision is invalid")
return _json(DeploymentBrokerClient().hub_delivery_operation(
"preflight", {"revision": revision},
))
@appops_mcp.tool(name="appops.hub_delivery.revalidate", annotations=_OPERATION_RUN_TOOL)
@_tracked("appops.hub_delivery.revalidate")
def appops_hub_delivery_revalidate(
revision: str, expected_preflight_sha256: str, idempotency_key: str,
) -> dict[str, Any]:
"""Queue one fixed MCP Hub delivery only after its exact native evidence fingerprint is rechecked."""
if (not isinstance(revision, str) or not re.fullmatch(r"[0-9a-f]{40}", revision)
or not isinstance(expected_preflight_sha256, str)
or not re.fullmatch(r"[0-9a-f]{64}", expected_preflight_sha256)
or not isinstance(idempotency_key, str)
or not re.fullmatch(r"[A-Za-z0-9._:-]{8,128}", idempotency_key)):
raise ValueError("Hub delivery revalidation contract is invalid")
return _json(DeploymentBrokerClient().hub_delivery_operation(
"revalidate", {
"revision": revision,
"expectedPreflightSha256": expected_preflight_sha256,
"idempotencyKey": idempotency_key,
},
))
@appops_mcp.tool(name="server.operation.execute", annotations=_LEGACY_APPROVAL_EXECUTION_TOOL) @appops_mcp.tool(name="server.operation.execute", annotations=_LEGACY_APPROVAL_EXECUTION_TOOL)
@_tracked("server.operation.execute") @_tracked("server.operation.execute")
def execute_operation( def execute_operation(
+63 -5
View File
@@ -1,9 +1,10 @@
"""Client for the private, typed deployment broker surface. """Client for the private, typed deployment broker surface.
The client only sends a canonical DeploymentPlanV1 to a configured controller. The client sends a canonical DeploymentPlanV1 or a closed fixed-target
It neither exposes a command field nor calls a Docker socket/Portainer exec operation to a configured controller. It neither exposes a command field nor
endpoint. The controller independently validates repository, revision and calls a Docker socket/Portainer exec endpoint. The controller independently
runtime topology before it can mutate production. validates repository, revision and runtime topology before it can mutate
production.
""" """
from __future__ import annotations from __future__ import annotations
@@ -200,7 +201,15 @@ class DeploymentBrokerClient:
except httpx.RequestError as exc: except httpx.RequestError as exc:
return {"status": "unreachable", "reason": redact_message(str(exc))} return {"status": "unreachable", "reason": redact_message(str(exc))}
if response.status_code in {401, 403}: if response.status_code in {401, 403}:
return {"status": "unauthorized", "reason": "deployment_broker_authentication_failed"} rejected = {"status": "unauthorized", "reason": "deployment_broker_authentication_failed"}
try:
auth_payload = response.json()
except ValueError:
auth_payload = None
if ("noMutation" in error_response_fields and isinstance(auth_payload, dict)
and isinstance(auth_payload.get("noMutation"), bool)):
rejected["noMutation"] = auth_payload["noMutation"]
return rejected
try: try:
decoded = response.json() decoded = response.json()
except ValueError: except ValueError:
@@ -316,6 +325,55 @@ class DeploymentBrokerClient:
error_response_fields=frozenset({"noMutation"}), error_response_fields=frozenset({"noMutation"}),
) )
def hub_delivery_operation(self, stage: str, payload: dict[str, Any]) -> dict[str, Any]:
"""Use the fixed Hub signed-delivery gate without retrying uncertain requests."""
if stage not in {"preflight", "revalidate"} or not isinstance(payload, dict):
raise ValueError("Hub delivery stage is invalid")
if stage == "preflight":
if (set(payload) != {"revision"} or not isinstance(payload.get("revision"), str)
or not re.fullmatch(r"[0-9a-f]{40}", payload["revision"])):
raise ValueError("Hub delivery preflight contract is invalid")
elif (set(payload) != {"revision", "expectedPreflightSha256", "idempotencyKey"}
or not isinstance(payload.get("revision"), str)
or not re.fullmatch(r"[0-9a-f]{40}", payload["revision"])
or not isinstance(payload.get("expectedPreflightSha256"), str)
or not re.fullmatch(r"[0-9a-f]{64}", payload["expectedPreflightSha256"])
or not isinstance(payload.get("idempotencyKey"), str)
or not re.fullmatch(r"[A-Za-z0-9._:-]{8,128}", payload["idempotencyKey"])):
raise ValueError("Hub delivery revalidation contract is invalid")
result = self._request(
"POST", f"/v1/appops/hub-delivery/{stage}",
secret=settings.mcp_token or "", payload=payload,
error_response_fields=frozenset({"noMutation"}),
)
if stage == "revalidate" and (
result.get("status") == "unreachable" or type(result.get("noMutation")) is not bool
):
return {
"status": "unknown",
"reason": "hub_delivery_outcome_unknown",
"noMutation": False,
}
if stage == "preflight" and result.get("status") == "unreachable":
return {
"status": "unavailable",
"reason": "hub_delivery_preflight_unavailable",
"noMutation": True,
}
if stage == "preflight" and type(result.get("noMutation")) is not bool:
return {
"status": "unavailable",
"reason": "hub_delivery_preflight_response_invalid",
"noMutation": True,
}
if stage == "preflight" and result.get("noMutation") is not True:
return {
"status": "unknown",
"reason": "hub_delivery_preflight_mutation_unexpected",
"noMutation": False,
}
return result
def registered_legacy_operation(self, stage: str, payload: dict[str, Any]) -> dict[str, Any]: def registered_legacy_operation(self, stage: str, payload: dict[str, Any]) -> dict[str, Any]:
"""Signed fixed-stage metadata adoption/recovery for existing registered targets.""" """Signed fixed-stage metadata adoption/recovery for existing registered targets."""
if stage not in {"observe", "baseline", "preflight", "adopt", "status", "rollback", "delivery"}: if stage not in {"observe", "baseline", "preflight", "adopt", "status", "rollback", "delivery"}:
@@ -22,6 +22,7 @@ spec = importlib.util.spec_from_file_location(
module = importlib.util.module_from_spec(spec) module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module) spec.loader.exec_module(module)
REVISION = "a" * 40 REVISION = "a" * 40
OLD_REVISION = "c" * 40
IMAGE_ID = "sha256:" + "b" * 64 IMAGE_ID = "sha256:" + "b" * 64
ORIGINAL_COMPOSE = b"name: gitea-deploy-runners\nservices:\n deploy-control:\n image: immutable-controller\n" ORIGINAL_COMPOSE = b"name: gitea-deploy-runners\nservices:\n deploy-control:\n image: immutable-controller\n"
@@ -830,7 +831,7 @@ def test_compose_uses_protected_code_and_two_config_views(compose):
"source": module._expected_revision_path(REVISION).as_posix(), "source": module._expected_revision_path(REVISION).as_posix(),
"target": module.EXECUTABLE_TARGET, "target": module.EXECUTABLE_TARGET,
"read_only": True, "read_only": True,
"bind": {"propagation": "rprivate"}, "bind": {"propagation": "rslave"},
} in volumes } in volumes
assert { assert {
"type": "volume", "type": "volume",
@@ -849,7 +850,7 @@ def test_compose_uses_protected_code_and_two_config_views(compose):
"source": module._expected_config_path().as_posix(), "source": module._expected_config_path().as_posix(),
"target": module.CONFIG_TARGET, "target": module.CONFIG_TARGET,
"read_only": True, "read_only": True,
"bind": {"propagation": "rprivate"}, "bind": {"propagation": "rslave"},
} in volumes } in volumes
assert len(volumes) == 5 assert len(volumes) == 5
volumes.remove(next(item for item in volumes if item["target"] == module.EXECUTABLE_TARGET)) volumes.remove(next(item for item in volumes if item["target"] == module.EXECUTABLE_TARGET))
@@ -899,7 +900,7 @@ def test_compose_migrates_legacy_config_alias_to_explicit_private_bind(compose):
"source": module._expected_config_path().as_posix(), "source": module._expected_config_path().as_posix(),
"target": module.CONFIG_TARGET, "target": module.CONFIG_TARGET,
"read_only": True, "read_only": True,
"bind": {"propagation": "rprivate"}, "bind": {"propagation": "rslave"},
} }
] ]
@@ -924,8 +925,8 @@ def test_compose_accepts_empty_bind_normalization_and_canonicalizes_both_managed
if mount.get("target") in {module.EXECUTABLE_TARGET, module.CONFIG_TARGET} if mount.get("target") in {module.EXECUTABLE_TARGET, module.CONFIG_TARGET}
] ]
assert {item["target"]: item["bind"] for item in managed} == { assert {item["target"]: item["bind"] for item in managed} == {
module.EXECUTABLE_TARGET: {"propagation": "rprivate"}, module.EXECUTABLE_TARGET: {"propagation": "rslave"},
module.CONFIG_TARGET: {"propagation": "rprivate"}, module.CONFIG_TARGET: {"propagation": "rslave"},
} }
@@ -947,7 +948,7 @@ def test_compose_normalizes_all_observed_live_generated_bind_defaults(compose):
@pytest.mark.parametrize("bind", [ @pytest.mark.parametrize("bind", [
{"create_host_path": 1}, {"create_host_path": "true"}, {"create_host_path": 1}, {"create_host_path": "true"},
{"create_host_path": False}, {"create_host_path": False},
{"create_host_path": True, "propagation": "rslave"}, {"create_host_path": True, "propagation": "rshared"},
{"create_host_path": True, "unknown": True}, {"create_host_path": True, "unknown": True},
]) ])
def test_compose_rejects_unsafe_generated_docker_root_bind(compose, bind): def test_compose_rejects_unsafe_generated_docker_root_bind(compose, bind):
@@ -960,7 +961,7 @@ def test_compose_rejects_unsafe_generated_docker_root_bind(compose, bind):
@pytest.mark.parametrize("target", [module.EXECUTABLE_TARGET, module.CONFIG_TARGET]) @pytest.mark.parametrize("target", [module.EXECUTABLE_TARGET, module.CONFIG_TARGET])
@pytest.mark.parametrize("private", [False, True]) @pytest.mark.parametrize("private", [None, "rprivate", "rslave"])
def test_compose_normalizes_generated_create_host_path_for_fixed_readonly_mounts( def test_compose_normalizes_generated_create_host_path_for_fixed_readonly_mounts(
compose, target, private compose, target, private
): ):
@@ -968,7 +969,7 @@ def test_compose_normalizes_generated_create_host_path_for_fixed_readonly_mounts
volumes = baseline["services"][module.SERVICE]["volumes"] volumes = baseline["services"][module.SERVICE]["volumes"]
bind = {"create_host_path": True} bind = {"create_host_path": True}
if private: if private:
bind["propagation"] = "rprivate" bind["propagation"] = private
if target == module.EXECUTABLE_TARGET: if target == module.EXECUTABLE_TARGET:
mount = next(item for item in volumes if item["target"] == target) mount = next(item for item in volumes if item["target"] == target)
mount["bind"] = bind mount["bind"] = bind
@@ -983,7 +984,7 @@ def test_compose_normalizes_generated_create_host_path_for_fixed_readonly_mounts
if mount["target"] in {module.EXECUTABLE_TARGET, module.CONFIG_TARGET}] if mount["target"] in {module.EXECUTABLE_TARGET, module.CONFIG_TARGET}]
assert len(managed) == 2 assert len(managed) == 2
assert all(mount["read_only"] is True for mount in managed) assert all(mount["read_only"] is True for mount in managed)
assert all(mount["bind"] == {"propagation": "rprivate"} for mount in managed) assert all(mount["bind"] == {"propagation": "rslave"} for mount in managed)
assert rendered["services"]["runner"] == baseline["services"]["runner"] assert rendered["services"]["runner"] == baseline["services"]["runner"]
# Only captured source metadata is normalized. Activation stays canonical. # Only captured source metadata is normalized. Activation stays canonical.
module._bound_model(json.dumps(rendered).encode()) module._bound_model(json.dumps(rendered).encode())
@@ -996,7 +997,7 @@ def test_compose_normalizes_generated_create_host_path_for_fixed_readonly_mounts
@pytest.mark.parametrize("bind", [ @pytest.mark.parametrize("bind", [
{"create_host_path": 1}, {"create_host_path": 1},
{"create_host_path": "true"}, {"create_host_path": "true"},
{"create_host_path": True, "propagation": "rslave"}, {"create_host_path": True, "propagation": "rshared"},
{"create_host_path": True, "selinux": "z"}, {"create_host_path": True, "selinux": "z"},
]) ])
def test_compose_rejects_unsafe_or_untyped_generated_bind_metadata(compose, target, bind): def test_compose_rejects_unsafe_or_untyped_generated_bind_metadata(compose, target, bind):
@@ -1016,12 +1017,12 @@ def test_compose_rejects_unsafe_or_untyped_generated_bind_metadata(compose, targ
@pytest.mark.parametrize( @pytest.mark.parametrize(
("target", "bind"), ("target", "bind"),
[ [
(module.EXECUTABLE_TARGET, {"propagation": "rslave"}), (module.EXECUTABLE_TARGET, {"propagation": "rshared"}),
(module.EXECUTABLE_TARGET, {"propagation": "shared"}), (module.EXECUTABLE_TARGET, {"propagation": "shared"}),
(module.EXECUTABLE_TARGET, {"propagation": "rprivate", "create_host_path": False}), (module.EXECUTABLE_TARGET, {"propagation": "rslave", "create_host_path": False}),
(module.CONFIG_TARGET, {"propagation": "rslave"}), (module.CONFIG_TARGET, {"propagation": "rshared"}),
(module.CONFIG_TARGET, {"propagation": "shared"}), (module.CONFIG_TARGET, {"propagation": "shared"}),
(module.CONFIG_TARGET, {"propagation": "rprivate", "create_host_path": False}), (module.CONFIG_TARGET, {"propagation": "rslave", "create_host_path": False}),
], ],
) )
def test_compose_rejects_noncanonical_controller_bind_propagation(compose, target, bind): def test_compose_rejects_noncanonical_controller_bind_propagation(compose, target, bind):
@@ -1049,7 +1050,7 @@ def test_compose_accepts_already_canonical_controller_mounts_without_duplicates(
baseline, _calls = compose baseline, _calls = compose
volumes = baseline["services"][module.SERVICE]["volumes"] volumes = baseline["services"][module.SERVICE]["volumes"]
next(item for item in volumes if item["target"] == module.EXECUTABLE_TARGET)["bind"] = { next(item for item in volumes if item["target"] == module.EXECUTABLE_TARGET)["bind"] = {
"propagation": "rprivate" "propagation": "rslave"
} }
volumes.extend( volumes.extend(
[ [
@@ -1070,7 +1071,7 @@ def test_compose_accepts_already_canonical_controller_mounts_without_duplicates(
"source": module._expected_config_path().as_posix(), "source": module._expected_config_path().as_posix(),
"target": module.CONFIG_TARGET, "target": module.CONFIG_TARGET,
"read_only": True, "read_only": True,
"bind": {"propagation": "rprivate"}, "bind": {"propagation": "rslave"},
}, },
] ]
) )
@@ -1101,8 +1102,129 @@ def test_activation_and_rollback_models_both_pin_managed_bind_propagation(compos
if mount.get("target") in {module.EXECUTABLE_TARGET, module.CONFIG_TARGET} if mount.get("target") in {module.EXECUTABLE_TARGET, module.CONFIG_TARGET}
} }
assert set(managed) == {module.EXECUTABLE_TARGET, module.CONFIG_TARGET} assert set(managed) == {module.EXECUTABLE_TARGET, module.CONFIG_TARGET}
assert managed[module.EXECUTABLE_TARGET]["bind"] == {"propagation": "rprivate"} assert managed[module.EXECUTABLE_TARGET]["bind"] == {"propagation": "rslave"}
assert managed[module.CONFIG_TARGET]["bind"] == {"propagation": "rprivate"} assert managed[module.CONFIG_TARGET]["bind"] == {"propagation": "rslave"}
def _legacy_private_compose(revision):
value = json.loads(module.rendered_model(ORIGINAL_COMPOSE, revision, IMAGE_ID))
for mount in value["services"][module.SERVICE]["volumes"]:
if mount.get("target") in {module.EXECUTABLE_TARGET, module.CONFIG_TARGET}:
mount["bind"] = {"propagation": "rprivate"}
return (json.dumps(value, sort_keys=True, indent=2) + "\n").encode()
def _projected_compose(captured):
value = json.loads(captured)
for mount in value["services"][module.SERVICE]["volumes"]:
if mount.get("target") in {module.EXECUTABLE_TARGET, module.CONFIG_TARGET}:
mount["bind"] = {"propagation": "rslave"}
assert mount["read_only"] is True
return (json.dumps(value, sort_keys=True, indent=2) + "\n").encode()
@pytest.mark.parametrize(
("rollback", "current_state", "write_expected", "error"),
[
(False, "raw_target", True, None),
(False, "projected_target", False, None),
(False, "before", True, None),
(True, "raw_target", True, None),
(True, "raw_after", True, None),
(True, "projected_after", True, None),
(True, "projected_target", False, None),
(True, "before", True, None),
(False, "unrecognized", False, "controller_volume_compose_transition_conflict"),
(True, "unrecognized", False, "controller_volume_compose_rollback_conflict"),
],
)
def test_root_compose_projects_legacy_receipts_and_bounds_retries(
compose, monkeypatch, tmp_path, rollback, current_state, write_expected, error,
):
_baseline, _calls = compose
before = b"captured-source-compose"
after = _legacy_private_compose("c" * 40)
rollback_value = _legacy_private_compose("b" * 40)
values = {"compose-after.json": after, "compose-rollback.json": rollback_value}
raw_target = rollback_value if rollback else after
projected_target = _projected_compose(raw_target)
current_by_state = {
"raw_target": raw_target,
"projected_target": projected_target,
"raw_after": after,
"projected_after": _projected_compose(after),
"before": before,
"unrecognized": b"unrecognized-compose",
}
live_file = {"value": current_by_state[current_state], "mode": 0o640}
receipt = {
"composeBeforeSha256": module._hash(before),
"composeAfterSha256": module._hash(after),
"composeRollbackSha256": module._hash(rollback_value),
}
writes = []
monkeypatch.setattr(module, "_directory", lambda *_args: 23)
monkeypatch.setattr(
module, "_file_info_at", lambda *_args: (live_file["value"], live_file["mode"])
)
monkeypatch.setattr(module, "_read_backup", lambda *_args: b'{"composeMode":416}')
monkeypatch.setattr(module.os, "close", lambda _descriptor: None)
def atomic_at(_parent, name, value, expected, *, private, mode):
assert name == "docker-compose.yml"
assert expected == live_file["value"]
assert private is False
assert mode == 0o640
writes.append(value)
live_file["value"] = value
monkeypatch.setattr(module, "_atomic_at", atomic_at)
backup = tmp_path / "backup"
if error:
with pytest.raises(module.VolumeTransitionError, match=error):
module._ensure_root_compose(backup, receipt, values, rollback=rollback)
assert writes == []
assert live_file["value"] == current_by_state[current_state]
return
module._ensure_root_compose(backup, receipt, values, rollback=rollback)
module._ensure_root_compose(backup, receipt, values, rollback=rollback)
assert len(writes) == int(write_expected)
assert live_file["value"] == projected_target
assert module._hash(after) == receipt["composeAfterSha256"]
assert module._hash(rollback_value) == receipt["composeRollbackSha256"]
assert values["compose-after.json"] == after
assert values["compose-rollback.json"] == rollback_value
@pytest.mark.parametrize("rollback", [False, True])
def test_root_compose_projected_retry_still_checks_file_mode(
compose, monkeypatch, tmp_path, rollback,
):
_baseline, _calls = compose
after = module.rendered_model(ORIGINAL_COMPOSE, "c" * 40, IMAGE_ID)
rollback_value = module.rendered_model(ORIGINAL_COMPOSE, "b" * 40, IMAGE_ID)
projected = rollback_value if rollback else after
before = b"captured-source-compose"
receipt = {
"composeBeforeSha256": module._hash(before),
"composeAfterSha256": module._hash(after),
"composeRollbackSha256": module._hash(rollback_value),
}
values = {"compose-after.json": after, "compose-rollback.json": rollback_value}
monkeypatch.setattr(module, "_directory", lambda *_args: 23)
monkeypatch.setattr(module, "_file_info_at", lambda *_args: (projected, 0o600))
monkeypatch.setattr(module, "_read_backup", lambda *_args: b'{"composeMode":416}')
monkeypatch.setattr(module.os, "close", lambda _descriptor: None)
monkeypatch.setattr(
module, "_atomic_at",
lambda *_args, **_kwargs: pytest.fail("A projected retry must not rewrite the file"),
)
with pytest.raises(module.VolumeTransitionError, match="compose_mode_invalid"):
module._ensure_root_compose(tmp_path / "backup", receipt, values, rollback=rollback)
def test_compose_allows_only_the_fixed_managed_appdata_volume_mount(compose): def test_compose_allows_only_the_fixed_managed_appdata_volume_mount(compose):
@@ -2304,6 +2426,70 @@ def test_copy_diagnostic_classifies_missing_passwd_user_without_retaining_user_v
assert "fixture-user-name" not in diagnostic assert "fixture-user-name" not in diagnostic
@pytest.mark.parametrize('stderr,condition', [
(b'cannot create /private/fixture-secret: No space left on device', 'storage_full'),
(b'Cannot connect to the Docker daemon at unix:///private/fixture-secret', 'daemon_unavailable'),
])
def test_storage_command_diagnostics_never_retain_private_paths(stderr, condition):
assert module._command_stderr_condition(stderr, 'compose_up', max_bytes=1024) == condition
diagnostic = module._safe_command_diagnostic(('compose_up', 'exit_1', condition))
assert diagnostic == f'compose_up_exit_1_{condition}'
assert 'fixture-secret' not in diagnostic
@pytest.mark.parametrize('read_only,free_bytes', [(True, 1024**3), (False, 0)])
def test_stop_rejects_failed_storage_before_recording_intent_or_stopping(monkeypatch, read_only, free_bytes):
receipt = {'phase': 'prepared'}
monkeypatch.setattr(module, '_load_transaction', lambda _: (42, receipt, {}))
monkeypatch.setattr(module.os, 'close', lambda _: None)
monkeypatch.setattr(module.os, 'statvfs', lambda _: types.SimpleNamespace(
f_flag=1 if read_only else 0, f_bavail=free_bytes, f_frsize=1,
), raising=False)
def forbidden(*args, **kwargs):
pytest.fail('Storage rejection must happen before any persisted stop intent or container stop')
monkeypatch.setattr(module, '_set_promotion_phase', forbidden)
monkeypatch.setattr(module, '_stop_current_controller', forbidden)
code = 'controller_volume_storage_read_only' if read_only else 'controller_volume_storage_insufficient'
with pytest.raises(module.VolumeTransitionError, match=code):
module.stop_promotion(Path('/unused-backup'))
assert receipt['phase'] == 'prepared'
@pytest.mark.parametrize('native_read_only_graph', [False, True])
def test_stop_allows_healthy_storage_and_preserves_intent_before_stop(monkeypatch, native_read_only_graph):
receipt = {'phase': 'prepared'}
events = []
monkeypatch.setattr(module, '_load_transaction', lambda _: (42, receipt, {}))
monkeypatch.setattr(module.os, 'close', lambda _: None)
def filesystem(path):
events.append(('storage', path))
return types.SimpleNamespace(f_flag=1 if native_read_only_graph and path == module.DOCKER_ROOT else 0, f_bavail=1024**3, f_frsize=1)
monkeypatch.setattr(module.os, 'statvfs', filesystem, raising=False)
def persist(descriptor, value, phase):
events.append(('intent', phase))
return {**value, 'phase': phase}
monkeypatch.setattr(module, '_set_promotion_phase', persist)
monkeypatch.setattr(module, '_stop_current_controller', lambda *a, **kw: events.append(('stop', None)))
module.stop_promotion(Path('/unused-backup'))
assert events == [('storage', module.ROOT), ('storage', module.DOCKER_ROOT), ('intent', 'stopped'), ('stop', None)]
def test_stop_rejects_full_graph_even_with_native_read_only_bind(monkeypatch):
receipt = {'phase': 'prepared'}
monkeypatch.setattr(module, '_load_transaction', lambda _: (42, receipt, {}))
monkeypatch.setattr(module.os, 'close', lambda _: None)
monkeypatch.setattr(module.os, 'statvfs', lambda p: types.SimpleNamespace(
f_flag=1 if p == module.DOCKER_ROOT else 0,
f_bavail=0 if p == module.DOCKER_ROOT else 1024**3, f_frsize=1,
), raising=False)
def forbidden(*args, **kwargs):
pytest.fail('Full graph must not record a stop or interrupt the controller')
monkeypatch.setattr(module, '_set_promotion_phase', forbidden)
monkeypatch.setattr(module, '_stop_current_controller', forbidden)
with pytest.raises(module.VolumeTransitionError, match='controller_volume_storage_insufficient'):
module.stop_promotion(Path('/unused-backup'))
def test_promoted_compose_actions_use_only_stdin_and_fixed_service(compose): def test_promoted_compose_actions_use_only_stdin_and_fixed_service(compose):
baseline, calls = compose baseline, calls = compose
rendered = module.rendered_model(ORIGINAL_COMPOSE, REVISION, IMAGE_ID) rendered = module.rendered_model(ORIGINAL_COMPOSE, REVISION, IMAGE_ID)
@@ -2319,59 +2505,86 @@ def test_promoted_compose_actions_use_only_stdin_and_fixed_service(compose):
assert baseline["name"] == "gitea-deploy-runners" assert baseline["name"] == "gitea-deploy-runners"
@pytest.mark.parametrize("phase", ["stopped", "activated"]) @pytest.mark.parametrize(
def test_rollback_uses_no_dependencies_when_recreating_prior_controller( ("operation", "phase"),
compose, tmp_path, monkeypatch, phase, [("activate", "stopped"), ("rollback", "stopped"), ("rollback", "activated")],
)
def test_promotion_uses_projected_persistent_compose_without_dependencies(
compose, tmp_path, monkeypatch, operation, phase,
): ):
_baseline, calls = compose _baseline, calls = compose
previous = REVISION previous = REVISION
target = "c" * 40 target = "c" * 40
previous_compose = module.rendered_model(ORIGINAL_COMPOSE, previous, IMAGE_ID) previous_compose = _legacy_private_compose(previous)
target_compose = module.rendered_model(ORIGINAL_COMPOSE, target, IMAGE_ID) target_compose = _legacy_private_compose(target)
receipt = { receipt = {
"phase": phase, "phase": phase,
"imageId": IMAGE_ID, "imageId": IMAGE_ID,
"previousRevision": previous, "previousRevision": previous,
"sourceRevision": target, "sourceRevision": target,
"composeBeforeSha256": module._hash(b"captured-source-compose"),
"composeAfterSha256": module._hash(target_compose),
"composeRollbackSha256": module._hash(previous_compose),
} }
values = { values = {
"controller-before.tar": module._release_tar_bytes( "controller-before.tar": b"previous tree",
previous, "controller-after.tar": b"target tree",
({"controller-release.json": (b"previous release", 0o644)}, {"": 0o755}), "mirror-before.tar": b"mirror snapshot",
),
"controller-after.tar": b"unused by isolated rollback test",
"mirror-before.tar": b"unused by isolated rollback test",
"compose-rollback.json": previous_compose, "compose-rollback.json": previous_compose,
"compose-after.json": target_compose, "compose-after.json": target_compose,
} }
current = { current = {"Mounts": [{
"Mounts": [{
"Destination": module.EXECUTABLE_TARGET, "Destination": module.EXECUTABLE_TARGET,
"Source": module._expected_revision_path(previous).as_posix(), "Source": module._expected_revision_path(target).as_posix(),
}] }]}
} live_file = {"value": target_compose, "mode": 0o640}
monkeypatch.setattr(module, "_load_transaction", lambda _backup: (1, receipt, values)) monkeypatch.setattr(module, "_load_transaction", lambda _backup: (1, receipt, values))
monkeypatch.setattr(module.os, "close", lambda _descriptor: None) monkeypatch.setattr(module.os, "close", lambda _descriptor: None)
monkeypatch.setattr(module, "_controller_image_inspect", lambda *args, **kwargs: current) monkeypatch.setattr(module, "_controller_image_inspect", lambda *args, **kwargs: current)
monkeypatch.setattr(module, "_read_tar_tree", lambda _value: ({}, {"": 0o755}))
monkeypatch.setattr(module, "_verify_release_leaf", lambda *args, **kwargs: True) monkeypatch.setattr(module, "_verify_release_leaf", lambda *args, **kwargs: True)
monkeypatch.setattr(module, "_stop_current_controller", lambda *args, **kwargs: None)
monkeypatch.setattr(module, "_sync_controller_mirror", lambda *args, **kwargs: None) monkeypatch.setattr(module, "_sync_controller_mirror", lambda *args, **kwargs: None)
monkeypatch.setattr(module, "_ensure_root_compose", lambda *args, **kwargs: None) monkeypatch.setattr(module, "_stop_current_controller", lambda *args, **kwargs: None)
monkeypatch.setattr(module, "_verify_compose_image", lambda *args, **kwargs: None) monkeypatch.setattr(module, "_verify_compose_image", lambda *args, **kwargs: None)
monkeypatch.setattr(module, "_verify_active_mount", lambda *args, **kwargs: None) monkeypatch.setattr(module, "_verify_active_mount", lambda *args, **kwargs: None)
monkeypatch.setattr(module, "_set_promotion_phase", lambda *args, **kwargs: None) monkeypatch.setattr(module, "_set_promotion_phase", lambda *args, **kwargs: None)
monkeypatch.setattr(module, "_directory", lambda *_args: 23)
monkeypatch.setattr(
module, "_file_info_at", lambda *_args: (live_file["value"], live_file["mode"])
)
monkeypatch.setattr(module, "_read_backup", lambda *_args: b'{"composeMode":416}')
def atomic_at(_parent, _name, value, expected, **_kwargs):
assert expected == live_file["value"]
live_file["value"] = value
monkeypatch.setattr(module, "_atomic_at", atomic_at)
calls.clear()
if operation == "activate":
module.activate_promotion(tmp_path / "backup")
raw_target = target_compose
else:
module.rollback_promotion(tmp_path / "backup") module.rollback_promotion(tmp_path / "backup")
raw_target = previous_compose
projected_target = _projected_compose(raw_target)
assert live_file["value"] == projected_target
up_calls = [ up_calls = [
arguments for arguments, _data in calls (arguments, data) for arguments, data in calls
if arguments[:2] == ["docker", "--config"] and arguments[3] == "compose" and "up" in arguments if arguments[:2] == ["docker", "--config"]
and arguments[3] == "compose" and "up" in arguments
] ]
assert len(up_calls) == 1 assert len(up_calls) == 1
up_index = up_calls[0].index("up") up_index = up_calls[0][0].index("up")
assert up_calls[0][up_index:] == [ assert up_calls[0][0][up_index:] == [
"up", "-d", "--force-recreate", "--pull", "never", "--no-deps", module.SERVICE "up", "-d", "--force-recreate", "--pull", "never", "--no-deps", module.SERVICE
] ]
assert up_calls[0][1] == projected_target
assert module._hash(target_compose) == receipt["composeAfterSha256"]
assert module._hash(previous_compose) == receipt["composeRollbackSha256"]
assert values["compose-after.json"] == target_compose
assert values["compose-rollback.json"] == previous_compose
def test_installer_has_no_shared_executable_stage_or_compose_path(): def test_installer_has_no_shared_executable_stage_or_compose_path():
@@ -3261,3 +3474,108 @@ def test_config_snapshot_keeps_appops_legacy_state_contract_closed(damage):
entries["appops-legacy-upgrade/unraid-appops-gateway.json"] = (b"{}", 0o644) entries["appops-legacy-upgrade/unraid-appops-gateway.json"] = (b"{}", 0o644)
with pytest.raises(module.VolumeTransitionError): with pytest.raises(module.VolumeTransitionError):
module._config_tree(entries, directories) module._config_tree(entries, directories)
@pytest.mark.parametrize("action", ["up", "stop"])
def test_legacy_receipt_projection_preserves_evidence_and_readonly_mounts(compose, action):
_baseline, calls = compose
value = json.loads(module.rendered_model(ORIGINAL_COMPOSE, REVISION, IMAGE_ID))
for mount in value["services"][module.SERVICE]["volumes"]:
if mount.get("target") in {module.EXECUTABLE_TARGET, module.CONFIG_TARGET}:
mount["bind"] = {"propagation": "rprivate"}
captured = json.dumps(value).encode()
original_hash = module._hash(captured)
with pytest.raises(module.VolumeTransitionError):
module._bound_model(captured)
module.compose_action(captured, action)
projected = json.loads(calls[-1][1])
for mount in projected["services"][module.SERVICE]["volumes"]:
if mount.get("target") in {module.EXECUTABLE_TARGET, module.CONFIG_TARGET}:
assert mount["read_only"] is True
assert mount["bind"] == {"propagation": "rslave"}
mount["bind"] = {"propagation": "rprivate"}
assert projected == value
assert module._hash(captured) == original_hash
@pytest.mark.parametrize("damage", ["writable", "shared", "extra_option", "foreign_source"])
def test_legacy_receipt_projection_rejects_unsafe_mount_before_docker(compose, damage):
_baseline, calls = compose
value = json.loads(module.rendered_model(ORIGINAL_COMPOSE, REVISION, IMAGE_ID))
mount = next(x for x in value["services"][module.SERVICE]["volumes"]
if x.get("target") == module.EXECUTABLE_TARGET)
mount["bind"] = {"propagation": "rprivate"}
if damage == "writable":
mount["read_only"] = False
elif damage == "shared":
mount["bind"]["propagation"] = "rshared"
elif damage == "extra_option":
mount["bind"]["create_host_path"] = True
else:
mount["source"] = "/tmp/untrusted-controller"
calls.clear()
with pytest.raises(module.VolumeTransitionError):
module.compose_action(json.dumps(value).encode(), "up")
assert calls == []
@pytest.mark.parametrize("phase", ["prepared", "stopped", "activated", "verified"])
@pytest.mark.parametrize("failure", ["read_only", "full"])
def test_forward_stop_rechecks_storage_after_intent_or_on_resume(monkeypatch, phase, failure):
receipt = {"phase": phase, "imageId": IMAGE_ID,
"previousRevision": OLD_REVISION, "sourceRevision": REVISION}
persisted = phase != "prepared"
events = []
monkeypatch.setattr(module, "_load_transaction", lambda _: (42, receipt, {
"compose-rollback.json": b"old", "compose-after.json": b"new"}))
monkeypatch.setattr(module.os, "close", lambda _: None)
def persist(_fd, value, new_phase):
nonlocal persisted
persisted = True
return {**value, "phase": new_phase}
monkeypatch.setattr(module, "_set_promotion_phase", persist)
def filesystem(_path):
return types.SimpleNamespace(
f_flag=1 if persisted and failure == "read_only" else 0,
f_bavail=0 if persisted and failure == "full" else 1024**3,
f_frsize=1)
monkeypatch.setattr(module.os, "statvfs", filesystem, raising=False)
monkeypatch.setattr(module, "_controller_image_inspect", lambda *_a, **_kw: {
"State": {"Running": True}, "Mounts": [{"Destination": module.EXECUTABLE_TARGET,
"Source": module._expected_revision_path(OLD_REVISION).as_posix()}]})
monkeypatch.setattr(module, "compose_action", lambda *args: events.append(args))
code = "controller_volume_storage_read_only" if failure == "read_only" else "controller_volume_storage_insufficient"
with pytest.raises(module.VolumeTransitionError, match=code):
module.stop_promotion(Path("/unused-backup"))
assert events == []
def test_resumed_forward_stop_of_stopped_controller_needs_no_storage(monkeypatch):
receipt = {"phase": "stopped", "imageId": IMAGE_ID,
"previousRevision": OLD_REVISION, "sourceRevision": REVISION}
monkeypatch.setattr(module, "_load_transaction", lambda _: (42, receipt, {}))
monkeypatch.setattr(module.os, "close", lambda _: None)
monkeypatch.setattr(module, "_controller_image_inspect", lambda *_a, **_kw: {
"State": {"Running": False}, "Mounts": [{"Destination": module.EXECUTABLE_TARGET,
"Source": module._expected_revision_path(OLD_REVISION).as_posix()}]})
monkeypatch.setattr(module, "_require_writable_promotion_storage",
lambda: pytest.fail("Already stopped recovery must remain possible"))
monkeypatch.setattr(module, "compose_action", lambda *_: pytest.fail("No stop needed"))
module.stop_promotion(Path("/unused-backup"))
def test_rollback_stop_remains_available_without_forward_storage_gate(monkeypatch):
receipt = {"phase": "stopped", "imageId": IMAGE_ID,
"previousRevision": OLD_REVISION, "sourceRevision": REVISION}
events = []
monkeypatch.setattr(module, "_load_transaction", lambda _: (42, receipt, {
"compose-rollback.json": b"old", "compose-after.json": b"new"}))
monkeypatch.setattr(module.os, "close", lambda _: None)
monkeypatch.setattr(module, "_controller_image_inspect", lambda *_a, **_kw: {
"State": {"Running": True}, "Mounts": [{"Destination": module.EXECUTABLE_TARGET,
"Source": module._expected_revision_path(REVISION).as_posix()}]})
monkeypatch.setattr(module, "_require_writable_promotion_storage",
lambda: pytest.fail("Rollback must not inherit forward capacity gate"))
monkeypatch.setattr(module, "compose_action", lambda *args: events.append(args))
module.rollback_promotion(Path("/unused-backup"), stop_only=True)
assert events == [(b"new", "stop")]
File diff suppressed because it is too large. Load diff
@@ -5,6 +5,7 @@ import io
import json import json
import subprocess import subprocess
import sys import sys
import threading
import time import time
import uuid import uuid
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
@@ -186,7 +187,11 @@ def test_revalidate_rejects_stale_fingerprint_without_queueing(controller, monke
) )
assert code == 409 assert code == 409
assert result == {"status": "blocked", "reason": "projectbrain_delivery_preflight_changed"} assert result == {
"status": "blocked",
"reason": "projectbrain_delivery_preflight_changed",
"noMutation": True,
}
assert queued == [] assert queued == []
@@ -876,6 +881,29 @@ def test_concurrent_enqueue_creates_one_additive_attempt_and_preserves_original(
preflight, hashlib.sha256(b"pb-revalidate-concurrent-2026").hexdigest(), preflight, hashlib.sha256(b"pb-revalidate-concurrent-2026").hexdigest(),
) )
project = fixture["project"] project = fixture["project"]
marker = fixture["inbox"] / f"{fixture['semantic']}.revalidation.json"
writer_opened = threading.Event()
allow_writer_to_continue = threading.Event()
gate_lock = threading.Lock()
gate_used = False
real_open = controller.os.open
def gated_open(path, flags, mode=0o777):
nonlocal gate_used
descriptor = real_open(path, flags, mode)
name = Path(path).name
if name == marker.name or name.startswith(f".{marker.name}."):
with gate_lock:
pause = not gate_used
gate_used = True
if pause:
writer_opened.set()
if not allow_writer_to_continue.wait(timeout=5):
controller.os.close(descriptor)
raise TimeoutError("test writer gate timed out")
return descriptor
monkeypatch.setattr(controller.os, "open", gated_open)
def enqueue(): def enqueue():
return controller._enqueue_terminal_revalidation( return controller._enqueue_terminal_revalidation(
@@ -887,14 +915,173 @@ def test_concurrent_enqueue_creates_one_additive_attempt_and_preserves_original(
) )
with ThreadPoolExecutor(max_workers=2) as workers: with ThreadPoolExecutor(max_workers=2) as workers:
results = list(workers.map(lambda _index: enqueue(), range(2))) first = workers.submit(enqueue)
assert writer_opened.wait(timeout=5)
try:
assert not marker.exists(), "an incomplete revalidation marker became visible"
second = workers.submit(enqueue)
assert second.result(timeout=5) == ("awaiting_validation", fixture["semantic"])
finally:
allow_writer_to_continue.set()
assert first.result(timeout=5) == ("awaiting_validation", fixture["semantic"])
assert results == [("awaiting_validation", fixture["semantic"])] * 2
assert fixture["original_path"].read_bytes() == fixture["original_bytes"] assert fixture["original_path"].read_bytes() == fixture["original_bytes"]
attempt_files = list(fixture["inbox"].glob("*.revalidation.json")) attempt_files = list(fixture["inbox"].glob("*.revalidation.json"))
assert len(attempt_files) == 1 assert len(attempt_files) == 1
record = controller._read_inbox_record(attempt_files[0]) record = controller._read_inbox_record(attempt_files[0])
assert record["projectbrain_delivery_revalidation"] == binding assert record["projectbrain_delivery_revalidation"] == binding
assert not list(fixture["inbox"].glob(".*.tmp"))
def test_new_inbox_record_ignores_stale_temporary_file(controller, tmp_path):
marker = tmp_path / "stale.json"
record = {"record": "complete"}
stale = marker.with_name(
f".{marker.name}.{controller.os.getpid()}.{controller.threading.get_ident()}.tmp"
)
stale_bytes = b"stale temporary file from an earlier process\n"
stale.write_bytes(stale_bytes)
controller._write_new_inbox_record(marker, record)
assert json.loads(marker.read_text(encoding="utf-8")) == record
if controller.os.name != "nt":
assert marker.stat().st_mode & 0o777 == 0o600
assert stale.read_bytes() == stale_bytes
assert set(tmp_path.iterdir()) == {marker, stale}
def test_new_inbox_record_collision_preserves_existing_bytes_and_cleans_temp(
controller, tmp_path,
):
marker = tmp_path / "existing.json"
original = b'{"existing":true}\n'
marker.write_bytes(original)
with pytest.raises(FileExistsError):
controller._write_new_inbox_record(marker, {"replacement": True})
assert marker.read_bytes() == original
assert not list(tmp_path.glob(".*.tmp"))
def test_new_inbox_record_write_failure_cleans_temporary_file(
controller, monkeypatch, tmp_path,
):
marker = tmp_path / "failed.json"
real_fdopen = controller.os.fdopen
class FailingWriter:
def __init__(self, handle):
self.handle = handle
def __enter__(self):
self.handle.__enter__()
return self
def __exit__(self, exc_type, exc, traceback):
return self.handle.__exit__(exc_type, exc, traceback)
def write(self, _payload):
raise OSError("simulated inbox write failure")
monkeypatch.setattr(
controller.os,
"fdopen",
lambda descriptor, mode: FailingWriter(real_fdopen(descriptor, mode)),
)
with pytest.raises(OSError, match="simulated inbox write failure"):
controller._write_new_inbox_record(marker, {"record": True})
assert not marker.exists()
assert not list(tmp_path.glob(".*.tmp"))
@pytest.mark.parametrize("failure_point", ["directory_fsync", "wakeup"])
def test_publish_followup_failure_reports_unknown_and_same_key_replay_resolves(
controller, monkeypatch, tmp_path, failure_point,
):
fixture = _snapshot_fixture(controller, monkeypatch, tmp_path)
monkeypatch.setattr(controller, "_projectbrain_delivery_mutation_lock", _null_context)
preflight = controller._projectbrain_delivery_snapshot(fixture["revision"])
idempotency_key = "pb-revalidate-publish-failure-2026"
payload = {
"revision": fixture["revision"],
"expectedPreflightSha256": preflight["preflightSha256"],
"idempotencyKey": idempotency_key,
}
real_fsync = controller._fsync_inbox_root
real_wakeup = controller._webhook_wakeup
if failure_point == "directory_fsync":
def fail_fsync():
raise OSError("simulated directory fsync failure")
monkeypatch.setattr(controller, "_fsync_inbox_root", fail_fsync)
else:
def fail_wakeup():
raise RuntimeError("simulated worker wakeup failure")
monkeypatch.setattr(controller, "_webhook_wakeup", SimpleNamespace(set=fail_wakeup))
code, result = invoke(controller, "revalidate", payload)
assert code == 503
assert result == {
"status": "unknown",
"reason": "projectbrain_delivery_enqueue_outcome_unknown",
"noMutation": False,
}
assert fixture["original_path"].read_bytes() == fixture["original_bytes"]
marker = fixture["inbox"] / f"{fixture['semantic']}.revalidation.json"
record = controller._read_inbox_record(marker)
assert record is not None
assert record["schema"] == controller._WEBHOOK_REVALIDATION_SCHEMA
assert record["status"] == "awaiting_validation"
assert record["projectbrain_delivery_revalidation"]["idempotencyKeySha256"] == hashlib.sha256(
idempotency_key.encode("ascii"),
).hexdigest()
assert not list(fixture["inbox"].glob(".*.tmp"))
if failure_point == "directory_fsync":
monkeypatch.setattr(controller, "_fsync_inbox_root", real_fsync)
else:
monkeypatch.setattr(controller, "_webhook_wakeup", real_wakeup)
replay_code, replay = invoke(controller, "revalidate", payload)
assert replay_code == 202
assert replay["status"] == "awaiting_validation"
assert replay["delivery"] == fixture["semantic"]
assert fixture["original_path"].read_bytes() == fixture["original_bytes"]
def test_revalidation_preflight_refusal_reports_no_mutation(
controller, monkeypatch, tmp_path,
):
fixture = _snapshot_fixture(controller, monkeypatch, tmp_path)
monkeypatch.setattr(controller, "_projectbrain_delivery_mutation_lock", _null_context)
def refuse_snapshot(_revision):
raise controller.ProjectBrainDeliveryRefused("projectbrain_delivery_preflight_changed")
monkeypatch.setattr(controller, "_projectbrain_delivery_snapshot", refuse_snapshot)
payload = {
"revision": fixture["revision"],
"expectedPreflightSha256": "a" * 64,
"idempotencyKey": "pb-revalidate-preflight-refusal-2026",
}
code, result = invoke(controller, "revalidate", payload)
assert code == 409
assert result == {
"status": "blocked",
"reason": "projectbrain_delivery_preflight_changed",
"noMutation": True,
}
assert fixture["original_path"].read_bytes() == fixture["original_bytes"]
assert not (fixture["inbox"] / f"{fixture['semantic']}.revalidation.json").exists()
def test_generic_terminal_revalidation_cannot_occupy_projectbrain_sibling( def test_generic_terminal_revalidation_cannot_occupy_projectbrain_sibling(