Add immutable Coulomb recovery mirroring through scoped APIs

Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a17f16-d99c-7ea3-95f0-8212897fba6b
This commit is contained in:
codex 2026-10-29 22:25:41 +01:00
parent 7e0b7d9062
commit b16f8bab8c
4 changed files with 225 additions and 2 deletions

View file

@ -152,7 +152,7 @@
| task | RPF-WP-0052-T03 | done | — | workplans/RPF-WP-0052-authelia-platform-smtp-admission.md |
| task | RPF-WP-0053-T01 | done | — | workplans/RPF-WP-0053-coulomb-paired-backup-admission.md |
| task | RPF-WP-0053-T02 | progress | — | workplans/RPF-WP-0053-coulomb-paired-backup-admission.md |
| task | RPF-WP-0053-T03 | todo | — | workplans/RPF-WP-0053-coulomb-paired-backup-admission.md |
| task | RPF-WP-0053-T03 | progress | — | workplans/RPF-WP-0053-coulomb-paired-backup-admission.md |
| task | RPF-WP-0053-T04 | todo | — | workplans/RPF-WP-0053-coulomb-paired-backup-admission.md |
| task | RPF-WP-0053-T05 | progress | — | workplans/RPF-WP-0053-coulomb-paired-backup-admission.md |
| task | RPF-WP-0053-T06 | done | — | workplans/RPF-WP-0053-coulomb-paired-backup-admission.md |

View file

@ -0,0 +1,141 @@
"""Coulomb immutable recovery mirror using the proven primary multipart engine.
The adapter pins every logical request to Coulomb and remaps only to its own
isolated recovery project/bucket/prefix. Independent fetch uses the reader.
"""
from __future__ import annotations
import argparse
import json
import re
import sys
from pathlib import Path
import primary_application_archive as primary
from application_recovery_identities import (
Refused,
checkpoint,
private_directory,
private_json,
)
from application_recovery_resources import s3, status
PROJECT = "06d107bc-9d84-4672-b2a0-95701cc0c07f"
BUCKET = "railiance-coulomb-social-archives"
OPERATIONS = {"head_object", "get_object", "create_multipart_upload", "upload_part",
"complete_multipart_upload", "abort_multipart_upload"}
class Mirror:
def __init__(self, client):
self.client = client
def __getattr__(self, operation):
if operation not in OPERATIONS:
raise Refused("mirror_operation_outside_contract")
def invoke(**arguments):
if arguments.get("Bucket") != primary.BUCKET:
raise Refused("mirror_logical_bucket_refused")
key = arguments.get("Key", "")
primary.validate_key("coulomb-social", key)
return getattr(self.client, operation)(**dict(arguments, Bucket=BUCKET, Key="archives/" + key))
return invoke
def validate_receipt(receipt):
primary.validate_key("coulomb-social", receipt.get("object_key", ""))
primary.require_version(receipt.get("version_id"))
if (receipt.get("application") != "coulomb-social"
or receipt.get("schema") != "platform.application-primary-archive.v1"
or receipt.get("status") != "offsite_fetched_verified"
or receipt.get("remote_retrieval_verified") is not True
or receipt.get("destination") != "s3://" + primary.BUCKET + "/" + receipt["object_key"]
or receipt.get("immutable_create") is not True
or not re.fullmatch(r"[a-f0-9]{64}", receipt.get("ciphertext_sha256", ""))
or type(receipt.get("ciphertext_bytes")) is not int
or not 0 < receipt["ciphertext_bytes"] <= primary.MAX_BYTES):
raise Refused("verified_coulomb_primary_receipt_required")
def mirror(client, source, readback, receipt, journal, save):
validate_receipt(receipt)
binding = {k: receipt[k] for k in ("object_key", "version_id", "ciphertext_sha256", "ciphertext_bytes",
"captured_at", "image", "age_recipient", "database_sha256", "content_sha256")}
if journal:
if journal.get("primary_binding") != binding or journal.get("recovery_project_id") != PROJECT:
raise Refused("mirror_resume_binding_differs")
else:
journal.update(schema="platform.coulomb-recovery-mirror.v1", primary_binding=binding,
recovery_project_id=PROJECT, object_key=receipt["object_key"],
ciphertext_sha256=receipt["ciphertext_sha256"])
primary.regular_file(source)
if primary.digest(source) != receipt["ciphertext_sha256"] or source.stat().st_size != receipt["ciphertext_bytes"]:
raise Refused("mirror_source_differs")
primary.transfer(Mirror(client), "coulomb-social", source, readback, journal, save)
reference = {"project_id": PROJECT, "bucket": BUCKET,
"object_key": "archives/" + receipt["object_key"],
"version_id": journal["version_id"], "ciphertext_sha256": receipt["ciphertext_sha256"],
"ciphertext_bytes": receipt["ciphertext_bytes"], "hash_readback_verified": True}
journal["recovery_mirror"] = reference
save()
return dict(receipt, recovery_mirror=reference)
def retrieve(client, output, receipt):
validate_receipt(receipt)
ref = receipt.get("recovery_mirror", {})
if (ref.get("project_id") != PROJECT or ref.get("bucket") != BUCKET
or ref.get("object_key") != "archives/" + receipt["object_key"]
or ref.get("ciphertext_sha256") != receipt["ciphertext_sha256"]
or ref.get("ciphertext_bytes") != receipt["ciphertext_bytes"]
or ref.get("hash_readback_verified") is not True):
raise Refused("exact_recovery_mirror_reference_required")
primary.require_version(ref.get("version_id"))
primary.download(Mirror(client), receipt["object_key"], ref["version_id"],
receipt["ciphertext_bytes"], receipt["ciphertext_sha256"], output)
return {"status": "independent_mirror_retrieved_verified", "provider_mutations": False,
"recovery_mirror": ref, "application_restore_verified": False}
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("operation", choices=("publish", "retrieve"))
parser.add_argument("--execute", action="store_true")
parser.add_argument("--directory", type=Path, default=Path.home() / ".config/railiance-platform/coulomb-social-recovery")
parser.add_argument("--receipt", type=Path, required=True)
parser.add_argument("--source", type=Path)
parser.add_argument("--output", type=Path, required=True)
parser.add_argument("--journal", type=Path)
parser.add_argument("--result", type=Path)
args = parser.parse_args()
if not args.execute:
print(json.dumps({"status": "preview", "provider_contacted": False, "operation": args.operation}))
return
private_directory(args.directory)
private_directory(args.output.parent)
receipt = private_json(args.receipt)
if args.result:
private_directory(args.result.parent)
if args.result.exists() or args.result.is_symlink():
raise Refused("fresh_result_required")
if args.operation == "retrieve":
result = retrieve(s3(private_json(args.directory / "reader-pair.json"), PROJECT), args.output, receipt)
else:
if not args.journal or not args.source or not args.result:
raise Refused("publish_source_journal_result_required")
private_directory(args.journal.parent)
journal = private_json(args.journal) if args.journal.exists() else {}
result = mirror(s3(private_json(args.directory / "publisher-pair.json"), PROJECT), args.source,
args.output, receipt, journal, lambda: checkpoint(args.journal, journal))
if args.result:
checkpoint(args.result, result)
print(json.dumps({"status": "mirror_publish_verified" if args.operation == "publish" else result["status"],
"application_restore_verified": False, "private_values_printed": False}))
if __name__ == "__main__":
try:
main()
except Exception as error: # noqa: BLE001 -- suppress private provider diagnostics
print(json.dumps({"status": "refused", "failure_type": type(error).__name__, "native_status": status(error)}))
sys.exit(1)

View file

@ -0,0 +1,82 @@
"""Exercise the real multipart engine through the exact recovery remapping."""
import copy
import hashlib
import importlib.util
import sys
from pathlib import Path
import pytest
SCRIPTS = Path(__file__).resolve().parents[1] / "scripts"
sys.path.insert(0, str(SCRIPTS))
import application_recovery_mirror as mirror
spec = importlib.util.spec_from_file_location("primary_tests", Path(__file__).with_name("test_primary_application_archive.py"))
provider = importlib.util.module_from_spec(spec)
spec.loader.exec_module(provider)
def fixture(tmp_path, failure=None):
tmp_path.chmod(0o700)
source = tmp_path / "ciphertext.age"
data = b"age-encryption.org/v1\nsynthetic-encrypted-pair"
source.write_bytes(data)
source.chmod(0o600)
key = mirror.primary.key_for("coulomb-social", "2026-10-29T00:00:00Z")
receipt = {"schema": "platform.application-primary-archive.v1", "application": "coulomb-social",
"status": "offsite_fetched_verified", "object_key": key, "version_id": "primary-version",
"destination": "s3://" + mirror.primary.BUCKET + "/" + key,
"ciphertext_sha256": hashlib.sha256(data).hexdigest(), "ciphertext_bytes": len(data),
"remote_retrieval_verified": True, "immutable_create": True,
"captured_at": "2026-10-29T00:00:00Z", "image": "synthetic-image",
"age_recipient": "synthetic-recipient", "database_sha256": "a" * 64, "content_sha256": "b" * 64}
return source, receipt, provider.Provider(data, failure)
def test_multipart_mirror_and_independent_version_retrieval(tmp_path):
source, original, client = fixture(tmp_path)
before = copy.deepcopy(original)
journal = {}
receipt = mirror.mirror(client, source, tmp_path / "readback.age", original, journal, lambda: None)
assert original == before
assert receipt["version_id"] == "primary-version"
assert receipt["recovery_mirror"]["version_id"] == client.version
for _, args in client.calls:
assert args["Bucket"] == mirror.BUCKET
assert args["Key"] == "archives/" + original["object_key"]
client.calls = []
result = mirror.retrieve(client, tmp_path / "independent.age", receipt)
assert result["provider_mutations"] is False
assert [name for name, _ in client.calls] == ["get"]
def test_uncertain_completion_reuses_same_mirror_without_reupload(tmp_path):
source, receipt, client = fixture(tmp_path, "lost-response")
journal = {}
with pytest.raises(TimeoutError):
mirror.mirror(client, source, tmp_path / "readback.age", receipt, journal, lambda: None)
client.failure = None
mirror.mirror(client, source, tmp_path / "retry.age", receipt, journal, lambda: None)
assert len([name for name, _ in client.calls if name == "start"]) == 1
@pytest.mark.parametrize("field,value", [("bucket", "foreign"), ("project_id", "foreign"),
("object_key", "archives/foreign"), ("ciphertext_sha256", "c" * 64)])
def test_foreign_mirror_reference_refuses_before_contact(tmp_path, field, value):
source, receipt, client = fixture(tmp_path)
result = mirror.mirror(client, source, tmp_path / "readback.age", receipt, {}, lambda: None)
client.calls = []
result["recovery_mirror"][field] = value
with pytest.raises(mirror.Refused):
mirror.retrieve(client, tmp_path / "independent.age", result)
assert client.calls == []
def test_adapter_cannot_address_siblings_or_delete(tmp_path):
_, receipt, client = fixture(tmp_path)
adapter = mirror.Mirror(client)
with pytest.raises(mirror.Refused):
adapter.delete_object(Bucket=mirror.primary.BUCKET, Key=receipt["object_key"])
with pytest.raises(ValueError):
adapter.get_object(Bucket=mirror.primary.BUCKET, Key="platform-pg/application-archives/email-connect/foreign")
assert client.calls == []

View file

@ -89,7 +89,7 @@ activation. No retained-object/version-delete rights belong in the publisher.
```task
id: RPF-WP-0053-T03
status: todo
status: progress
priority: high
intervention_note: "The first coherent capture/publication and daily schedule are already approved. Complete capacity/retention preflight, independently retained exact-version retrieval and isolated application recovery before timer activation."
state_hub_task_id: "871c14da-2940-5be6-8ce8-953973422064"