Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/scripts/disksage_copy_readiness_handoff.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ def _decode_protocol(result: VerifierResult) -> dict[str, object]:
and payload.get("ok") is True
and payload.get("schema_kind") == "disksage.naruon.cloud-copy-readiness"
and type(payload.get("schema_version")) is int
and payload.get("schema_version") == 3
and payload.get("schema_version") in (3, 4)
and payload.get("provider") in PROVIDERS
and payload.get("readiness_state") in READINESS_STATES
and type(payload.get("candidate_count")) is int
Expand Down
16 changes: 15 additions & 1 deletion backend/tests/test_disksage_copy_readiness_handoff.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def _success_payload() -> dict[str, object]:
return {
"ok": True,
"schema_kind": "disksage.naruon.cloud-copy-readiness",
"schema_version": 3,
"schema_version": 4,
"provider": "icloud",
"readiness_state": "blocked",
"candidate_count": 19,
Expand Down Expand Up @@ -111,6 +111,19 @@ def test_main_delegates_to_absolute_verifier_without_shell_env_or_input_read(
assert not (tmp_path / "must-not-exist").exists()


@pytest.mark.parametrize("schema_version", [3, 4])
def test_protocol_decoder_accepts_supported_schema_versions(schema_version):
"""Accept the deployed v3 contract and the WAL-consistent v4 contract."""
payload = {**_success_payload(), "schema_version": schema_version}
result = handoff.VerifierResult(
0,
json.dumps(payload).encode("utf-8"),
b"",
)

assert handoff._decode_protocol(result) == payload


@pytest.mark.parametrize("exit_code", [64, 65])
def test_main_preserves_valid_disksage_failure_protocol(tmp_path, capsys, exit_code):
payload = {
Expand Down Expand Up @@ -711,6 +724,7 @@ def wrapped_popen(*args, **kwargs):
("payload", "exit_code"),
[
({**_success_payload(), "private_path": "/private/source"}, 0),
({**_success_payload(), "schema_version": 5}, 0),
({"ok": False, "error_code": "invalid"}, 0),
(_success_payload(), 65),
({"ok": False, "error_code": "invalid/path"}, 65),
Expand Down
18 changes: 18 additions & 0 deletions docs/integrations/disksage-readiness-version-policy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# DiskSage readiness schema compatibility

Naruon delegates validation of DiskSage cloud-copy readiness artifacts to the digest-bound offline Rust verifier and then validates the verifier's bounded JSON response.

## Supported versions

Naruon accepts exactly these successful response versions:

- **Schema 3** — the deployed readiness contract used before WAL-consistency evidence was added.
- **Schema 4** — the WAL-consistent readiness contract.

Supporting version 4 is an additive compatibility change. Version 3 remains valid so an independently deployed DiskSage installation does not fail merely because Naruon upgrades first. Versions outside this explicit set are rejected.

## Invariants shared by both versions

A successful response must retain the exact allowlisted fields, a recognized provider and readiness state, non-negative candidate counts and byte totals, a lowercase SHA-256 fingerprint, and false values for every local-path, raw-metadata, cloud-write, and source-eviction claim. Duplicate JSON member names, unexpected stderr, oversized output, invalid encodings, unknown fields, or unsupported exit codes fail closed.

The compatibility policy changes only the accepted schema-version set. It does not authorize a cloud write, source eviction, network request, or access to local file paths.
Loading