Publish curated Unraid-AppOps-Gateway source from 6d175c3333218e2b580a310496fed6c407f28114

This commit is contained in:
Public Source Publisher committed 2026-10-09 12:15:31 +00:00
1 parent 58f5e3c8c2
commit 41fd48f0bc
7 files changed
+339 -16

No files matched your search

+1 -1
View File
@@ -1,3 +1,3 @@
# Public source export
This source snapshot was generated from the private canonical repository at revision `402a0ef2e7d14e98325504f79ab7d7651ffc88b1`. 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 `6d175c3333218e2b580a310496fed6c407f28114`. It contains no private Git history or operational evidence. Changes are published from the canonical repository.
+11 -11
View File
@@ -1,6 +1,6 @@
{
"schemaVersion": 1,
"sourceRevision": "402a0ef2e7d14e98325504f79ab7d7651ffc88b1",
"sourceRevision": "6d175c3333218e2b580a310496fed6c407f28114",
"files": [
{
"path": ".dockerignore",
@@ -404,8 +404,8 @@
},
{
"path": "backend/app/mcp_server.py",
"sha256": "feac22ee7a1c755f0fafd6890500b83442b5f13b51e6a6cde3b0227c5b5ab25b",
"bytes": 49536
"sha256": "598e7f7a5020d92becfb143d31dbd096f16bf79ae73b26e6ecae710661f73ca1",
"bytes": 50519
},
{
"path": "backend/app/runner_statistics_mcp.py",
@@ -899,8 +899,8 @@
},
{
"path": "backend/tests/test_appops_recovery_deploy_binding.py",
"sha256": "4902f2e16ed35b8bd52d8610ab61b9752aa10a4147e5aafc27404bbbfef61e0a",
"bytes": 50460
"sha256": "274e401e81eda762ff4cae0b64ecdefd322972c72a004fdd84f02b93a2e8b44d",
"bytes": 52001
},
{
"path": "backend/tests/test_appops_self_upgrade_controller.py",
@@ -909,8 +909,8 @@
},
{
"path": "backend/tests/test_appops_self_upgrade_recovery_boundaries.py",
"sha256": "07c3a38c360f5b70b6f3efbc8ba62356d4937170667d07d31d82a6810ed58a0e",
"bytes": 21578
"sha256": "676a465700296b081cc07eaeab3d2669f3047001b28b614e8a8a82088010f3f0",
"bytes": 29130
},
{
"path": "backend/tests/test_appops_self_upgrade_runtime_model.py",
@@ -1004,8 +1004,8 @@
},
{
"path": "backend/tests/test_deploy_broker.py",
"sha256": "b0e96b1cd2954dc2a6452f5f7c50c5fbae541dc0b4de1e60af11349654594902",
"bytes": 438851
"sha256": "40c290925c0569192973d858e5df36b91e3c5dd38ce638b9f19b0ca60e61903a",
"bytes": 442136
},
{
"path": "backend/tests/test_deployment_auto_rollback.py",
@@ -1284,8 +1284,8 @@
},
{
"path": "backend/tests/test_mcp_rag_n8n_integrations.py",
"sha256": "0c90431bb77eab27f0da21afd02dd5fb232141483062f7a19c3340d1d91f6c9d",
"bytes": 52814
"sha256": "1d72e95529863d3263a178d71d4c7729c8b624b13e734460fb9303a3f6f7416d",
"bytes": 54507
},
{
"path": "backend/tests/test_media_codec_dashboard_deployment_reconciliation.py",
+16 -1
View File
@@ -15,7 +15,7 @@ from typing import Any
from mcp.server import MCPServer
from mcp.server.transport_security import TransportSecuritySettings
from mcp.types import ToolAnnotations
from pydantic import StrictBool
from pydantic import StrictBool, ValidationError
from sqlmodel import Session, select
from .api.routes_actions import action_registry as build_action_registry
@@ -894,6 +894,9 @@ def deployment_preflight(plan: dict[str, Any], idempotencyKey: str) -> dict[str,
operation="preflight",
)
)
except ValidationError:
# Pydantic errors may contain arbitrary keys, input values and validator context.
return {"status": "invalid", "executed": False, "reason": "invalid_typed_plan"}
except (ValueError, DeploymentControlError) as exc:
return {"status": "invalid", "executed": False, "reason": redact_message(str(exc))}
@@ -918,6 +921,8 @@ def forge_bootstrap_apply(profile: dict[str, Any], idempotencyKey: str) -> dict[
idempotency_key=idempotencyKey,
)
)
except ValidationError:
return {"status": "invalid", "executed": False, "reason": "invalid_typed_plan"}
except ValueError as exc:
return {"status": "invalid", "executed": False, "reason": redact_message(str(exc))}
finally:
@@ -967,6 +972,8 @@ def enrollment_preflight(plan: dict[str, Any]) -> dict[str, Any]:
typed_plan = EnrollmentPlanV1.model_validate(plan)
with Session(engine) as session:
return _json(EnrollmentPlane().preflight(session, typed_plan))
except ValidationError:
return {"status": "invalid", "executed": False, "reason": "invalid_typed_plan"}
except (ValueError, EnrollmentError) as exc:
return {"status": "invalid", "executed": False, "reason": redact_message(str(exc))}
@@ -981,6 +988,8 @@ def target_enroll(plan: dict[str, Any], idempotencyKey: str) -> dict[str, Any]:
typed_plan = EnrollmentPlanV1.model_validate(plan)
with Session(engine) as session:
return _json(EnrollmentPlane().enroll(session, typed_plan, idempotency_key=idempotencyKey))
except ValidationError:
return {"status": "invalid", "executed": False, "reason": "invalid_typed_plan"}
except (ValueError, EnrollmentError) as exc:
return {"status": "invalid", "executed": False, "reason": redact_message(str(exc))}
finally:
@@ -1015,6 +1024,9 @@ def deployment_apply(plan: dict[str, Any], idempotencyKey: str) -> dict[str, Any
operation="apply",
)
)
except ValidationError:
# Pydantic errors may contain arbitrary keys, input values and validator context.
return {"status": "invalid", "executed": False, "reason": "invalid_typed_plan"}
except (ValueError, DeploymentControlError) as exc:
return {"status": "invalid", "executed": False, "reason": redact_message(str(exc))}
finally:
@@ -1063,6 +1075,9 @@ def deployment_rollback(plan: dict[str, Any], idempotencyKey: str) -> dict[str,
operation="rollback",
)
)
except ValidationError:
# Pydantic errors may contain arbitrary keys, input values and validator context.
return {"status": "invalid", "executed": False, "reason": "invalid_typed_plan"}
except (ValueError, DeploymentControlError) as exc:
return {"status": "invalid", "executed": False, "reason": redact_message(str(exc))}
finally:
@@ -1135,3 +1135,36 @@ def test_followup_transaction_journal_failure_precedes_all_source_runtime_and_st
assert [event[0] for event in events] == ["green", "snapshot", "backup", "journal"]
assert marker.read_text() == "prior source remains\n"
assert state["live_revision"] == prior_revision
def test_prepare_restored_recovery_rechecks_signed_live_state_and_recaptures_under_new_id(
deploy, monkeypatch, tmp_path,
):
case = _appops_prepare_recovery_case(deploy, monkeypatch, tmp_path)
calls = []
def verify(_project, container, image, **kwargs):
calls.append(kwargs)
assert container["Id"] != deploy.APPOPS_LEGACY_OLD_CONTAINER_ID
monkeypatch.setattr(deploy, "verify_appops_legacy_runtime", verify)
deploy._appops_prepare_recovery(
case.project, APP_OPS_REPOSITORY, case.repo, case.revision, case.metadata, case.state,
)
assert calls == [{"require_original_container": False, "recovery_state": case.state}]
assert case.calls["models"][0][0] == RECOVERY_ID
assert case.calls["models"][0][2]["services"]["appops-gateway"]["environment"] == case.values
assert case.project["_compose_rollback"]["containers"][0]["container_id"] == case.container["Id"]
def test_prepare_recovery_rejects_environment_drift_before_model_backup_or_transaction(
deploy, monkeypatch, tmp_path,
):
case = _appops_prepare_recovery_case(deploy, monkeypatch, tmp_path)
case.container["Config"]["Env"].append("UNEXPECTED_SETTING=private-sentinel")
with pytest.raises(deploy.DeployError, match="complete runtime environment changed"):
deploy._appops_prepare_recovery(
case.project, APP_OPS_REPOSITORY, case.repo, case.revision, case.metadata, case.state,
)
assert not any(case.calls[name] for name in ("models", "capture", "backup", "begin"))
@@ -556,3 +556,166 @@ def test_original_failed_delivery_rejects_modified_legacy_record(controller, mon
assert controller._self_upgrade_original_failed_delivery(
semantic, revision, policy,
) is None
@pytest.mark.parametrize(("status", "extra", "valid"), [
("deploying", {}, True),
("failed", {"reason": "deployment_failed"}, True),
("deployed", {"receipt_sha256": "f" * 64}, True),
("deployed", {"receipt_sha256": "invalid"}, False),
("deploying", {"reason": "deployment_failed"}, False),
("failed", {"reason": "private-secret-value"}, False),
("failed", {"receipt_sha256": "f" * 64}, False),
("failed", {"unknown": "value"}, False),
("failed", {"reason": ["deployment_failed"]}, False),
("awaiting_validation", {"reason": "gitea_validation_unavailable"}, True),
("failed", {"reason": "recovery_preimage_changed"}, True),
])
def test_signed_recovery_reader_accepts_only_typed_worker_outcome_fields(
controller, monkeypatch, tmp_path, status, extra, valid,
):
record = _recovery_delivery(controller, secret="sentinel")
record.pop("runtimeEnvironment")
record.pop("privateCredential")
record.update(status=status, **extra)
secret = b"fixture-recovery-signing-key"
journal = {
"recoveryId": record["recovery_id"], "revision": record["revision"],
"idempotencyKeySha256": record["idempotency_key_sha256"],
"preflightSha256": record["preflight_sha256"],
"semanticSha256": record["semantic_sha256"],
"originalRawSha256": record["recovery_of_sha256"],
}
payload = {
"operationId": record["semantic_sha256"], "revision": record["revision"],
"preflightSha256": record["preflight_sha256"],
"idempotencyKeySha256": record["idempotency_key_sha256"],
"recoveryId": record["recovery_id"],
"recoveryOfSha256": record["recovery_of_sha256"],
}
record["body_sha256"] = controller.hashlib.sha256(controller._self_upgrade_canonical_json({
"semanticSha256": record["semantic_sha256"], "recoveryId": record["recovery_id"],
"idempotencyKeySha256": record["idempotency_key_sha256"],
})).hexdigest()
record["recovery_hmac"] = controller._self_upgrade_public_recovery_signature(record, secret)
monkeypatch.setattr(controller, "_self_upgrade_secret", lambda: secret)
monkeypatch.setattr(controller, "_self_upgrade_recovery_journal", lambda _id: journal)
monkeypatch.setattr(controller, "_load_projects", lambda: {controller.SELF_UPGRADE_REPOSITORY: {}})
monkeypatch.setattr(controller, "_validation_policy", lambda *_a: {})
monkeypatch.setattr(controller, "_validation_snapshot_matches", lambda *_a: True)
monkeypatch.setattr(controller, "_self_upgrade_request_root", lambda **_k: tmp_path)
monkeypatch.setattr(controller, "_self_upgrade_read_private_json", lambda _path: (b"", {}))
monkeypatch.setattr(controller, "_self_upgrade_recovery_request_payload", lambda _record: payload)
def verify():
return controller._self_upgrade_recovery_record_valid(
record, semantic_digest=record["semantic_sha256"],
original_raw_sha256=record["recovery_of_sha256"],
)
assert verify() is valid
record["updated_at"] += 1
assert verify() is False # Even permitted outcome fields require the original complete HMAC.
@pytest.mark.parametrize(("uid", "gid", "mode"), [(99, 100, 0o777), (0, 0, 0o755)])
@pytest.mark.parametrize("drift", ["none", "owner", "mode", "symlink", "foreign_cache", "inode"])
def test_legacy_cache_reader_and_state_cas_bind_both_exact_appdata_profiles(
controller, monkeypatch, tmp_path, uid, gid, mode, drift,
):
if os.name == "nt" or os.geteuid() != 0:
pytest.skip("Pinned dirfd ownership contract requires root POSIX")
root = tmp_path / "virtual-host"
root.mkdir(mode=0o755)
dirs = [
("mnt", 0, 0, 0o755), ("mnt/cache", 99, 100, 0o777),
("mnt/cache/appdata", uid, gid, mode),
("mnt/cache/appdata/gitea-deploy-runners", 0, 0, 0o755),
("mnt/cache/appdata/gitea-deploy-runners/state", 0, 0, 0o755),
]
for name, owner, group, permissions in dirs:
path = root / name
path.mkdir()
os.chown(path, owner, group)
path.chmod(permissions)
appdata = root / "mnt/cache/appdata"
state_root = appdata / "gitea-deploy-runners/state"
state_path = state_root / "unraid-appops-gateway.json"
before = controller._self_upgrade_canonical_json({
"schema_version": 3, "repository": controller.SELF_UPGRADE_REPOSITORY, "marker": "old",
}) + b"\n"
state_path.write_bytes(before)
state_path.chmod(0o600)
after = before.replace(b'"old"', b'"new"')
if drift == "owner":
os.chown(appdata, 1000, 1000)
elif drift == "mode":
appdata.chmod(0o775)
elif drift == "foreign_cache":
os.chown(root / "mnt/cache", 0, 0)
(root / "mnt/cache").chmod(0o755)
elif drift == "inode":
replacement = appdata.with_name("different-inode")
replacement.mkdir()
os.chown(replacement, uid, gid)
replacement.chmod(mode)
elif drift == "symlink":
renamed = appdata.with_name("appdata-elsewhere")
appdata.rename(renamed)
appdata.symlink_to(renamed, target_is_directory=True)
monkeypatch.setattr(controller, "STATE_ROOT", Path("/mnt/cache/appdata/gitea-deploy-runners/state"))
real_open = os.open
def virtual_root_open(path, flags, *args, **kwargs):
if drift == "inode" and str(path) == "appdata":
return real_open(replacement, flags)
return real_open(root if str(path) == "/" else path, flags, *args, **kwargs)
monkeypatch.setattr(controller.os, "open", virtual_root_open)
requested = controller.STATE_ROOT / state_path.name
if drift == "none":
assert controller._self_upgrade_read_legacy_cache_file(
requested, maximum_bytes=65536, allowed_modes=frozenset({0o600}),
) == before
assert not controller._self_upgrade_write_legacy_state_cas("0" * 64, after)
assert state_path.read_bytes() == before
assert controller._self_upgrade_write_legacy_state_cas(
controller.hashlib.sha256(before).hexdigest(), after,
)
assert state_path.read_bytes() == after
else:
with pytest.raises(OSError):
controller._self_upgrade_read_legacy_cache_file(
requested, maximum_bytes=65536, allowed_modes=frozenset({0o600}),
)
assert not controller._self_upgrade_write_legacy_state_cas(
controller.hashlib.sha256(before).hexdigest(), after,
)
@pytest.mark.parametrize(("profile", "valid"), [
((99, 100, 0o777), True), ((0, 0, 0o755), True),
((0, 0, 0o775), False), ((99, 100, 0o755), False),
])
def test_source_root_allows_only_legacy_or_root_owned_appdata_anchor(
controller, monkeypatch, profile, valid,
):
from types import SimpleNamespace
root = Path("/srv/example/appdata/unraid-appops-gateway/source")
profiles = {
Path("/mnt"): (0, 0, 0o755), Path("/mnt/user"): (99, 100, 0o777),
Path("/srv/example/appdata"): profile,
root.parent: (0, 0, 0o755), root: (100, 101, 0o755),
}
def lstat(path):
uid, gid, mode = profiles[path]
return SimpleNamespace(st_uid=uid, st_gid=gid, st_mode=stat.S_IFDIR | mode)
monkeypatch.setattr(Path, "lstat", lstat)
monkeypatch.setattr(Path, "is_symlink", lambda _path: False)
assert controller._self_upgrade_source_root_safe(root) is valid
monkeypatch.setattr(Path, "is_symlink", lambda path: path == root.parent)
assert controller._self_upgrade_source_root_safe(root) is False
+74 -3
View File
@@ -379,9 +379,7 @@ def test_appops_legacy_state_anchor_accepts_only_known_preimage_or_current_green
)
def test_appops_legacy_runtime_requires_pinned_source_image_and_resolved_model(
deploy_module, tmp_path
):
def _fixed_appops_legacy_runtime_case(deploy_module, tmp_path):
project = _project(tmp_path)
project.update({
"branch": "main", "gitea_repository_id": 9,
@@ -435,6 +433,15 @@ def test_appops_legacy_runtime_requires_pinned_source_image_and_resolved_model(
"Config": {"Labels": container["Config"]["Labels"]},
}
return project, container, image
def test_appops_legacy_runtime_requires_pinned_source_image_and_resolved_model(
deploy_module, tmp_path
):
project, container, image = _fixed_appops_legacy_runtime_case(deploy_module, tmp_path)
image_id = deploy_module.APPOPS_LEGACY_OLD_IMAGE_ID
source = "https://gitea.example.com/Jens/Unraid-AppOps-Gateway"
observed = deploy_module.verify_appops_legacy_runtime(project, container, image)
assert observed["imageId"] == image_id
assert observed["imageRef"] == "unraid-appops-gateway:platform-5553c04ce497"
@@ -10604,3 +10611,67 @@ def test_managed_appdata_initializer_http_prerequisite_and_refusal_are_sanitized
server.shutdown()
server.server_close()
serving.join(timeout=2)
@pytest.mark.parametrize("proof", ["signed", "missing", "foreign", "candidate"])
def test_recovery_verifies_restored_container_only_with_signed_live_555_model(
deploy_module, monkeypatch, tmp_path, proof,
):
project, container, image = _fixed_appops_legacy_runtime_case(deploy_module, tmp_path)
container["Id"] = "a" * 64
model_path = "/private/models/signed-555.compose.json"
container["Config"]["Labels"]["com.docker.compose.project.config_files"] = model_path
state = {"live_revision": deploy_module.APPOPS_LEGACY_OLD_REVISION}
active = {"legacy": True, "path": model_path}
if proof == "missing":
active = None
elif proof == "foreign":
active["path"] = "/private/models/foreign.compose.json"
elif proof == "candidate":
active["legacy"] = False
calls = []
def verify_active(actual_state, actual_container, *, project):
calls.append((actual_state, actual_container, project))
return active
reader = types.SimpleNamespace(_self_upgrade_active_compose_model=verify_active)
monkeypatch.setattr(deploy_module, "_appops_status_reader", lambda: reader)
with pytest.raises(deploy_module.DeployError, match="fixed AppOps legacy runtime"):
deploy_module.verify_appops_legacy_runtime(project, container, image)
if proof == "signed":
observed = deploy_module.verify_appops_legacy_runtime(
project, container, image, require_original_container=False, recovery_state=state,
)
assert observed["containerId"] == container["Id"]
else:
with pytest.raises(deploy_module.DeployError):
deploy_module.verify_appops_legacy_runtime(
project, container, image, require_original_container=False, recovery_state=state,
)
assert calls == [(state, container, project)]
@pytest.mark.parametrize("drift", ["image", "ports", "mounts", "health"])
def test_signed_restored_555_proof_preserves_fixed_runtime_checks(
deploy_module, monkeypatch, tmp_path, drift,
):
project, container, image = _fixed_appops_legacy_runtime_case(deploy_module, tmp_path)
container["Id"] = "a" * 64
active = {"legacy": True, "path": "/private/models/signed-555.compose.json"}
monkeypatch.setattr(deploy_module, "_appops_status_reader", lambda: types.SimpleNamespace(
_self_upgrade_active_compose_model=lambda *_a, **_k: active,
))
if drift == "image":
image["Id"] = "sha256:" + "0" * 64
elif drift == "ports":
container["NetworkSettings"]["Ports"]["1216/tcp"][0]["HostIp"] = "127.0.0.1"
elif drift == "mounts":
container["Mounts"][0]["RW"] = False
elif drift == "health":
container["State"]["Health"]["Status"] = "unhealthy"
with pytest.raises(deploy_module.DeployError):
deploy_module.verify_appops_legacy_runtime(
project, container, image, require_original_container=False, recovery_state={},
)
@@ -1369,3 +1369,44 @@ def test_controller_preflight_changed_identity_expires_approval_without_unknown_
promotion = session.get(WorkMeshPromotion, promotion_id)
assert approval is not None and approval.status == "expired"
assert promotion is not None and promotion.status == "preflight_changed"
@pytest.mark.parametrize(("name", "argument", "discriminator"), [
("enrollment.preflight", "plan", None),
("target.enroll", "plan", None),
("deployment.preflight", "plan", None),
("deployment.apply", "plan", None),
("deployment.rollback", "plan", None),
("deployment.preflight", "plan", "itworx.forge-bootstrap-preflight/v1"),
("forge.bootstrap.apply", "profile", None),
])
def test_mcp_typed_schema_rejection_never_echoes_opaque_input(
integration_client, monkeypatch, name, argument, discriminator,
):
from backend.app import mcp_server
canary = "syntheticOpaqueInput_9Z7XQ"
payload = {"api_key": canary, canary: {"nested": canary}, "application_id": canary}
if discriminator:
payload["api_version"] = discriminator
arguments = {argument: payload}
if name != "enrollment.preflight":
arguments["idempotencyKey"] = "schema-redaction-fixture"
def forbidden_service(*_args, **_kwargs):
pytest.fail("invalid schema must not reach deployment/enrollment services")
monkeypatch.setattr(mcp_server, "EnrollmentPlane", forbidden_service)
monkeypatch.setattr(mcp_server, "DeploymentControlPlane", forbidden_service)
async def exercise():
async with Client(mcp_server.appops_mcp) as client:
result = await client.call_tool(name, arguments)
assert result.is_error is False
assert result.structured_content == {
"status": "invalid", "executed": False, "reason": "invalid_typed_plan",
}
assert canary not in json.dumps(result.model_dump(mode="json"))
asyncio.run(exercise())
assert mcp_server._MCP_EXECUTION_LOCK.locked() is False