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
+1674 -60

No files matched your search

+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)
@_tracked("server.operation.execute")
def execute_operation(
+63 -5
View File
@@ -1,9 +1,10 @@
"""Client for the private, typed deployment broker surface.
The client only sends a canonical DeploymentPlanV1 to a configured controller.
It neither exposes a command field nor calls a Docker socket/Portainer exec
endpoint. The controller independently validates repository, revision and
runtime topology before it can mutate production.
The client sends a canonical DeploymentPlanV1 or a closed fixed-target
operation to a configured controller. It neither exposes a command field nor
calls a Docker socket/Portainer exec endpoint. The controller independently
validates repository, revision and runtime topology before it can mutate
production.
"""
from __future__ import annotations
@@ -200,7 +201,15 @@ class DeploymentBrokerClient:
except httpx.RequestError as exc:
return {"status": "unreachable", "reason": redact_message(str(exc))}
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:
decoded = response.json()
except ValueError:
@@ -316,6 +325,55 @@ class DeploymentBrokerClient:
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]:
"""Signed fixed-stage metadata adoption/recovery for existing registered targets."""
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)
spec.loader.exec_module(module)
REVISION = "a" * 40
OLD_REVISION = "c" * 40
IMAGE_ID = "sha256:" + "b" * 64
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(),
"target": module.EXECUTABLE_TARGET,
"read_only": True,
"bind": {"propagation": "rprivate"},
"bind": {"propagation": "rslave"},
} in volumes
assert {
"type": "volume",
@@ -849,7 +850,7 @@ def test_compose_uses_protected_code_and_two_config_views(compose):
"source": module._expected_config_path().as_posix(),
"target": module.CONFIG_TARGET,
"read_only": True,
"bind": {"propagation": "rprivate"},
"bind": {"propagation": "rslave"},
} in volumes
assert len(volumes) == 5
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(),
"target": module.CONFIG_TARGET,
"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}
]
assert {item["target"]: item["bind"] for item in managed} == {
module.EXECUTABLE_TARGET: {"propagation": "rprivate"},
module.CONFIG_TARGET: {"propagation": "rprivate"},
module.EXECUTABLE_TARGET: {"propagation": "rslave"},
module.CONFIG_TARGET: {"propagation": "rslave"},
}
@@ -947,7 +948,7 @@ def test_compose_normalizes_all_observed_live_generated_bind_defaults(compose):
@pytest.mark.parametrize("bind", [
{"create_host_path": 1}, {"create_host_path": "true"},
{"create_host_path": False},
{"create_host_path": True, "propagation": "rslave"},
{"create_host_path": True, "propagation": "rshared"},
{"create_host_path": True, "unknown": True},
])
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("private", [False, True])
@pytest.mark.parametrize("private", [None, "rprivate", "rslave"])
def test_compose_normalizes_generated_create_host_path_for_fixed_readonly_mounts(
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"]
bind = {"create_host_path": True}
if private:
bind["propagation"] = "rprivate"
bind["propagation"] = private
if target == module.EXECUTABLE_TARGET:
mount = next(item for item in volumes if item["target"] == target)
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}]
assert len(managed) == 2
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"]
# Only captured source metadata is normalized. Activation stays canonical.
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", [
{"create_host_path": 1},
{"create_host_path": "true"},
{"create_host_path": True, "propagation": "rslave"},
{"create_host_path": True, "propagation": "rshared"},
{"create_host_path": True, "selinux": "z"},
])
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(
("target", "bind"),
[
(module.EXECUTABLE_TARGET, {"propagation": "rslave"}),
(module.EXECUTABLE_TARGET, {"propagation": "rshared"}),
(module.EXECUTABLE_TARGET, {"propagation": "shared"}),
(module.EXECUTABLE_TARGET, {"propagation": "rprivate", "create_host_path": False}),
(module.CONFIG_TARGET, {"propagation": "rslave"}),
(module.EXECUTABLE_TARGET, {"propagation": "rslave", "create_host_path": False}),
(module.CONFIG_TARGET, {"propagation": "rshared"}),
(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):
@@ -1049,7 +1050,7 @@ def test_compose_accepts_already_canonical_controller_mounts_without_duplicates(
baseline, _calls = compose
volumes = baseline["services"][module.SERVICE]["volumes"]
next(item for item in volumes if item["target"] == module.EXECUTABLE_TARGET)["bind"] = {
"propagation": "rprivate"
"propagation": "rslave"
}
volumes.extend(
[
@@ -1070,7 +1071,7 @@ def test_compose_accepts_already_canonical_controller_mounts_without_duplicates(
"source": module._expected_config_path().as_posix(),
"target": module.CONFIG_TARGET,
"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}
}
assert set(managed) == {module.EXECUTABLE_TARGET, module.CONFIG_TARGET}
assert managed[module.EXECUTABLE_TARGET]["bind"] == {"propagation": "rprivate"}
assert managed[module.CONFIG_TARGET]["bind"] == {"propagation": "rprivate"}
assert managed[module.EXECUTABLE_TARGET]["bind"] == {"propagation": "rslave"}
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):
@@ -2304,6 +2426,70 @@ def test_copy_diagnostic_classifies_missing_passwd_user_without_retaining_user_v
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):
baseline, calls = compose
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"
@pytest.mark.parametrize("phase", ["stopped", "activated"])
def test_rollback_uses_no_dependencies_when_recreating_prior_controller(
compose, tmp_path, monkeypatch, phase,
@pytest.mark.parametrize(
("operation", "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
previous = REVISION
target = "c" * 40
previous_compose = module.rendered_model(ORIGINAL_COMPOSE, previous, IMAGE_ID)
target_compose = module.rendered_model(ORIGINAL_COMPOSE, target, IMAGE_ID)
previous_compose = _legacy_private_compose(previous)
target_compose = _legacy_private_compose(target)
receipt = {
"phase": phase,
"imageId": IMAGE_ID,
"previousRevision": previous,
"sourceRevision": target,
"composeBeforeSha256": module._hash(b"captured-source-compose"),
"composeAfterSha256": module._hash(target_compose),
"composeRollbackSha256": module._hash(previous_compose),
}
values = {
"controller-before.tar": module._release_tar_bytes(
previous,
({"controller-release.json": (b"previous release", 0o644)}, {"": 0o755}),
),
"controller-after.tar": b"unused by isolated rollback test",
"mirror-before.tar": b"unused by isolated rollback test",
"controller-before.tar": b"previous tree",
"controller-after.tar": b"target tree",
"mirror-before.tar": b"mirror snapshot",
"compose-rollback.json": previous_compose,
"compose-after.json": target_compose,
}
current = {
"Mounts": [{
"Destination": module.EXECUTABLE_TARGET,
"Source": module._expected_revision_path(previous).as_posix(),
}]
}
current = {"Mounts": [{
"Destination": module.EXECUTABLE_TARGET,
"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.os, "close", lambda _descriptor: None)
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, "_stop_current_controller", 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_active_mount", 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}')
module.rollback_promotion(tmp_path / "backup")
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")
raw_target = previous_compose
projected_target = _projected_compose(raw_target)
assert live_file["value"] == projected_target
up_calls = [
arguments for arguments, _data in calls
if arguments[:2] == ["docker", "--config"] and arguments[3] == "compose" and "up" in arguments
(arguments, data) for arguments, data in calls
if arguments[:2] == ["docker", "--config"]
and arguments[3] == "compose" and "up" in arguments
]
assert len(up_calls) == 1
up_index = up_calls[0].index("up")
assert up_calls[0][up_index:] == [
up_index = up_calls[0][0].index("up")
assert up_calls[0][0][up_index:] == [
"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():
@@ -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)
with pytest.raises(module.VolumeTransitionError):
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 subprocess
import sys
import threading
import time
import uuid
from concurrent.futures import ThreadPoolExecutor
@@ -186,7 +187,11 @@ def test_revalidate_rejects_stale_fingerprint_without_queueing(controller, monke
)
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 == []
@@ -876,6 +881,29 @@ def test_concurrent_enqueue_creates_one_additive_attempt_and_preserves_original(
preflight, hashlib.sha256(b"pb-revalidate-concurrent-2026").hexdigest(),
)
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():
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:
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"]
attempt_files = list(fixture["inbox"].glob("*.revalidation.json"))
assert len(attempt_files) == 1
record = controller._read_inbox_record(attempt_files[0])
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(