Retain verified recovery proof independently and bind the protected point
All checks were successful
CCR intake gate / gate (push) Successful in 12s
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

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:49:33 +01:00
parent e1918b3f88
commit 09f4fd33cb
7 changed files with 257 additions and 11 deletions

View file

@ -41,5 +41,33 @@
"automatic_expiry_enabled": false
}
},
"native_application_recovery_verified": false
"native_application_recovery_verified": true,
"protected_recovered_point": {
"captured_at": "2026-10-29T21:22:46Z",
"primary_object_key": "platform-pg/application-archives/coulomb-social/2026/10/29/20261029T212246Z-fd128164c7bb22a3a6c42acf.tar.age",
"primary_version_id": "1793309020705518",
"ciphertext_sha256": "acbf92eda5074761bb6d00170e6e459e787359afcd377f2f18324ba17496510a",
"protected_kit": {
"automatic_expiry": false,
"bucket": "railiance-coulomb-social-recovery",
"bytes": 277585920,
"object_key": "kits/db05c8f3b5587dc1fc09a5352c317dd3357527dbe72366ab7ca1041db578c834/recovery-kit.tar",
"project_id": "06d107bc-9d84-4672-b2a0-95701cc0c07f",
"schema": "platform.coulomb-protected-recovery-kit.v1",
"sha256": "db05c8f3b5587dc1fc09a5352c317dd3357527dbe72366ab7ca1041db578c834",
"version_id": "1793309971213507"
},
"recovery_seconds": 30.232,
"proof": "docs/evidence/2026-10-29-coulomb-provider-kit-recovery.json",
"protected_recovery_proof": {
"project_id": "06d107bc-9d84-4672-b2a0-95701cc0c07f",
"bucket": "railiance-coulomb-social-recovery",
"object_key": "kits/receipts/a77477cac53086b83ad2cb512973ae0d1dfecba028a29a92e06a1bcfe5287f29/recovery-proof.json",
"version_id": "1793310476676486",
"sha256": "a77477cac53086b83ad2cb512973ae0d1dfecba028a29a92e06a1bcfe5287f29",
"bytes": 4237,
"independent_readback_verified": true,
"automatic_expiry": false
}
}
}

View file

@ -33,10 +33,11 @@ Negative PUT probes use separate transports because a denied upload can leave
the provider connection unusable for its next request.
Both buckets are private and versioned; no automatic expiry has been enabled.
The protected bucket never participates in rolling expiry. Rolling retention,
capacity, first coherent app capture, independent exact-version decryption and
isolated application recovery, monitoring and timers remain WP-0053/CSOC-WP-0012
acceptance work. Synthetic fixtures are boundary evidence, not app backups.
The protected bucket never participates in rolling expiry. First coherent app
capture, independent exact-version decryption and isolated application recovery
are now verified below. Rolling retention, capacity, copy disposition, monitoring
and timers remain WP-0053/CSOC-WP-0012 acceptance work. The earlier synthetic
fixtures remain boundary evidence, distinct from this real application backup.
The routine pairs and age identity each have a separate protected, enabled,
nonexpiring provider secret version. They survive node/workstation loss. If all
@ -51,3 +52,18 @@ deadlines, using a bounded attended construction window. No standing organizatio
administration key or publisher IAM administration is admitted. The construction
key was revoked, its temporary pair removed and the personal browser context
closed without saving state. Routine drills do not rerun identity bootstrap.
The first provider-only kit recovery is now verified in30.232s monotonic. The
public JSON contract records the complete protected kit, exact primary recovery
point and separately protected recovery-proof coordinates. Native receipt:
../evidence/2026-10-29-coulomb-provider-kit-recovery.json. All2098kit members,
13tables/112rows/sequences,82files and39rendered pages/access denials pass.
Images/tools/data came from the kit; the age identity came from separate escrow.
Held API callers and the existing Docker daemon were used. Account-recovery
latency, a fresh daemon and real-browser identity journeys are excluded.
application_recovery_drill.py previews without effects and executes only with
--execute, --kit-reference and a fresh --destination under an owner-private
directory outside Git. It never restores production. Retention/copy disposition,
monitoring and scheduled activation remain open; this receipt closes only the
consumer's first-paired-recovery task.

View file

@ -0,0 +1,117 @@
{
"age_identity_from_separate_provider_escrow": true,
"application": "coulomb-social",
"application_checks": {
"actual_pages_rendered_through_view": 39,
"content_files_unchanged": 82,
"database_restore": {
"content_files": 82,
"content_hashes_match": true,
"content_was_empty": false,
"database_container": "csoc-recovery-20261029214329",
"database_rows": 112,
"database_rows_and_sequences_match": true,
"database_tables": 13,
"elapsed_seconds": 4.43,
"network": "csoc-recovery-20261029214329-net",
"network_internal": true,
"postgres_image_id": "sha256:ff23cdce56cac62ada6f66013e1a50864c0abbe79d132d40b6e05bd80f378a70",
"published_ports": 0,
"restored_directory": "/tmp/csoc-recovery-20261029214329-0rvudi86",
"status": "restored_verified"
},
"external_identity_provider_used": false,
"fixture_transaction_rolled_back": true,
"packaged_application_image": "sha256:9a646d8e01a61ba5316d3c2eb0273064e17f2d0300ad07af766ccedda65ecb18",
"production_access": false,
"real_browser_journey_verified": false,
"recovered_spaces_with_content": 1,
"restored_owner_access_verified": true,
"same_tenant_ungranted_page_denials": 39,
"status": "isolated_application_recovery_verified",
"synthetic_viewer_mutation_denials": 1,
"synthetic_viewer_reads": 1
},
"application_restore_verified": true,
"captured_at": "2026-10-29T21:22:46Z",
"completed_at": "2026-10-29T21:43:40.590019+00:00",
"fresh_docker_daemon": false,
"kit_members_verified": 2098,
"operator_account_recovery_time_included": false,
"original_node_or_plaintext_used": false,
"personal_session_used": false,
"protected_kit": {
"automatic_expiry": false,
"bucket": "railiance-coulomb-social-recovery",
"bytes": 277585920,
"object_key": "kits/db05c8f3b5587dc1fc09a5352c317dd3357527dbe72366ab7ca1041db578c834/recovery-kit.tar",
"project_id": "06d107bc-9d84-4672-b2a0-95701cc0c07f",
"schema": "platform.coulomb-protected-recovery-kit.v1",
"sha256": "db05c8f3b5587dc1fc09a5352c317dd3357527dbe72366ab7ca1041db578c834",
"version_id": "1793309971213507"
},
"real_browser_journey_verified": false,
"recovery_seconds": 30.232,
"registry_pulls_performed": false,
"restore": {
"content_files": 82,
"content_hashes_match": true,
"content_was_empty": false,
"database_container": "csoc-recovery-20261029214329",
"database_rows": 112,
"database_rows_and_sequences_match": true,
"database_tables": 13,
"elapsed_seconds": 4.43,
"network": "csoc-recovery-20261029214329-net",
"network_internal": true,
"postgres_image_id": "sha256:ff23cdce56cac62ada6f66013e1a50864c0abbe79d132d40b6e05bd80f378a70",
"published_ports": 0,
"restored_directory": "/tmp/csoc-recovery-20261029214329-0rvudi86",
"status": "restored_verified"
},
"schema": "platform.coulomb-provider-kit-recovery.v1",
"stages_seconds": {
"all_kit_member_hashes_and_extraction": 2.501,
"application_image_load": 1.229,
"decrypt_and_validate_pair": 0.121,
"isolated_database_content_restore": 4.552,
"packaged_app_views_and_permissions": 6.555,
"postgres_image_load": 1.754,
"provider_age_escrow_retrieval": 0.503,
"provider_kit_retrieval": 12.373
},
"started_at": "2026-10-29T21:43:07.435367+00:00",
"status": "application_restored_verified",
"timers_activated": false,
"unpack": {
"application_restore_verified": false,
"captured_at": "2026-10-29T21:22:46Z",
"content_sha256": "2e81fa5fe2e1a82dfdec4df855a8c5643f59573cc0418a4a26f908d87727b125",
"database_sha256": "93f8f01edc9b9a2cd9580d0bab2048d1f5a8e2394e7fa266d419636f502e15f1",
"encrypted_sha256": "acbf92eda5074761bb6d00170e6e459e787359afcd377f2f18324ba17496510a",
"status": "decrypted_pair_verified",
"version_id": "1793309020705518"
},
"object_key": "platform-pg/application-archives/coulomb-social/2026/10/29/20261029T212246Z-fd128164c7bb22a3a6c42acf.tar.age",
"version_id": "1793309020705518",
"ciphertext_sha256": "acbf92eda5074761bb6d00170e6e459e787359afcd377f2f18324ba17496510a",
"ciphertext_bytes": 2120392,
"image": "forgejo.coulomb.social/coulomb/coulomb-social@sha256:9a646d8e01a61ba5316d3c2eb0273064e17f2d0300ad07af766ccedda65ecb18",
"age_recipient": "age1qm30jk8q0cezey652dk2dhcr3d9vs528v6lg8mavl6d4q3e3vgnqmrmyy3",
"database_sha256": "93f8f01edc9b9a2cd9580d0bab2048d1f5a8e2394e7fa266d419636f502e15f1",
"content_sha256": "2e81fa5fe2e1a82dfdec4df855a8c5643f59573cc0418a4a26f908d87727b125",
"application_data_images_and_recovery_tools_from_provider_kit": true,
"bootstrap_api_client_preinstalled": true,
"executed_drill_source": "e1918b3",
"metadata_enriched_from_hash_verified_kit": true,
"protected_recovery_proof": {
"project_id": "06d107bc-9d84-4672-b2a0-95701cc0c07f",
"bucket": "railiance-coulomb-social-recovery",
"object_key": "kits/receipts/a77477cac53086b83ad2cb512973ae0d1dfecba028a29a92e06a1bcfe5287f29/recovery-proof.json",
"version_id": "1793310476676486",
"sha256": "a77477cac53086b83ad2cb512973ae0d1dfecba028a29a92e06a1bcfe5287f29",
"bytes": 4237,
"independent_readback_verified": true,
"automatic_expiry": false
}
}

View file

@ -20,7 +20,7 @@ from application_recovery_identities import (
private_directory,
private_json,
)
from application_recovery_kit import PROJECT, download
from application_recovery_kit import PROJECT, download, protect_receipt
from application_recovery_resources import API, s3, status
@ -133,6 +133,7 @@ class Drill:
"--output", str(self.root / "application-receipt.json")]))
app = json.loads(app)
baseline = json.loads((contents / "application-recovery.json").read_text())
point = json.loads((contents / "primary-receipt.json").read_text())
for key in ("actual_pages_rendered_through_view", "same_tenant_ungranted_page_denials", "content_files_unchanged"):
if app.get(key) != baseline.get(key):
raise Refused("kit_application_baseline_differs")
@ -143,11 +144,16 @@ class Drill:
"recovery_seconds": round(time.monotonic() - began, 3), "protected_kit": self.reference,
"kit_members_verified": len(manifest["files"]) + 1, "application": "coulomb-social",
"captured_at": manifest["captured_at"], "application_restore_verified": True,
"all_data_images_and_tools_from_independent_provider_kit": True,
"application_data_images_and_recovery_tools_from_provider_kit": True,
"bootstrap_api_client_preinstalled": True,
"age_identity_from_separate_provider_escrow": True, "original_node_or_plaintext_used": False,
"personal_session_used": False, "registry_pulls_performed": False, "fresh_docker_daemon": False,
"operator_account_recovery_time_included": False, "real_browser_journey_verified": False,
"unpack": unpacked, "restore": restored, "application_checks": app, "timers_activated": False}
result.update({k: point[k] for k in ("object_key", "version_id", "ciphertext_sha256", "ciphertext_bytes",
"image", "age_recipient", "database_sha256", "content_sha256")})
result["protected_recovery_proof"] = protect_receipt(
s3(private_json(self.directory / "custodian-pair.json"), PROJECT), reader, result)
checkpoint(self.root / "recovery-receipt.json", result)
return result

View file

@ -49,8 +49,7 @@ def download(client, ref, output):
return dict(ref, independently_retrieved_hash_verified=True, automatic_expiry=False)
def publish(client, source):
archive.regular_file(source)
def require_protected_bucket(client):
if client.get_bucket_versioning(Bucket=BUCKET).get("Status") != "Enabled":
raise Refused("protected_bucket_versioning_required")
try:
@ -61,6 +60,40 @@ def publish(client, source):
lifecycle = []
if lifecycle:
raise Refused("protected_bucket_must_have_no_lifecycle_expiry")
def protect_receipt(writer, reader, receipt):
"""Retain the recovery proof independently, without a self-hash cycle."""
require_protected_bucket(writer)
payload = json.dumps(receipt, sort_keys=True, separators=(",", ":")).encode()
if len(payload) > 1024**2 or receipt.get("status") != "application_restored_verified":
raise Refused("bounded_successful_recovery_proof_required")
expected = hashlib.sha256(payload).hexdigest()
key = "kits/receipts/" + expected + "/recovery-proof.json"
try:
head = writer.head_object(Bucket=BUCKET, Key=key)
except Exception as error:
if status(error) != 404:
raise
response = writer.put_object(Bucket=BUCKET, Key=key, Body=payload,
IfNoneMatch="*", Metadata={"sha256": expected}, ContentType="application/json")
version = archive.require_version(response.get("VersionId"))
else:
if head["ContentLength"] != len(payload) or head.get("Metadata", {}).get("sha256") != expected:
raise Refused("existing_proof_binding_differs")
version = archive.require_version(head.get("VersionId"))
response = reader.get_object(Bucket=BUCKET, Key=key, VersionId=version)
with response["Body"] as stream:
if response.get("VersionId") != version or stream.read(len(payload) + 1) != payload:
raise Refused("independent_proof_readback_differs")
return {"project_id": PROJECT, "bucket": BUCKET, "object_key": key, "version_id": version,
"sha256": expected, "bytes": len(payload), "independent_readback_verified": True,
"automatic_expiry": False}
def publish(client, source):
archive.regular_file(source)
require_protected_bucket(client)
size = source.stat().st_size
if not 0 < size <= MAX_BYTES:
raise Refused("bounded_kit_required")

View file

@ -37,7 +37,8 @@ class Provider:
def put_object(self, **args):
self.calls.append("put")
assert args["Bucket"] == kit.BUCKET and args["IfNoneMatch"] == "*"
self.data = args["Body"].read()
body = args["Body"]
self.data = body.read() if hasattr(body, "read") else body
return {"VersionId": self.version}
def get_object(self, **args):
@ -104,3 +105,16 @@ def test_protected_bucket_expiry_refuses_before_upload(fixture):
with pytest.raises(kit.Refused, match="no_lifecycle_expiry"):
kit.publish(client, source)
assert client.calls == []
def test_recovery_proof_is_conditionally_retained_and_independently_verified():
client = Provider()
receipt = {"status": "application_restored_verified", "application": "coulomb-social"}
ref = kit.protect_receipt(client, client, receipt)
assert ref["independent_readback_verified"] is True
assert ref["object_key"] == "kits/receipts/" + ref["sha256"] + "/recovery-proof.json"
assert kit.protect_receipt(client, client, receipt) == ref
assert client.calls.count("put") == 1
client.corrupt = True
with pytest.raises(kit.Refused, match="proof_readback_differs"):
kit.protect_receipt(client, client, receipt)

View file

@ -47,7 +47,7 @@ Do not mark the warden offsite workload identity resolved without a declaration.
id: RPF-WP-0053-T02
status: progress
priority: high
intervention_note: "Publisher delivery and independent scoped API custody are approved and native-verified. Retention/capacity, protected app kit/last recovered point and historical-copy disposition remain open; no repeat credential or schedule approval is needed."
intervention_note: "Publisher delivery, independent scoped API custody and protected app kit/last recovered point are native-verified. Retention/capacity and historical-copy disposition remain open; no repeat credential or schedule approval is needed."
state_hub_task_id: "b06f1a44-1a55-588c-9c6c-3c1e290544df"
```
@ -401,3 +401,35 @@ as generic-api-key because of its metadata field name. The exact finding was
line2, containing only the already public approval reference. Renamed that field
to programmatic_direction_reference; no scanner rule, ignore or bypass changed.
Only unpublished commits are amended; published history is preserved.
## First paired and protected provider-only recovery — 2026-10-29
Admitted worker capture resumed the app before encryption/upload;54.3s pause/capture,
2,120,392byte ciphertext, immutable primary exact-version/hash readback. The new
application_recovery_mirror.py reuses the existing multipart/conditional-completion
engine through an exact Coulomb-only bucket/key adapter. Its independent reader
fetched the mirror; custodian escrow supplied only the dedicated age identity.
SQL restore matches13tables/112rows/sequences and82files. Actual packaged app views
render39pages; restored owner, viewer read/edit403 and ungranted404 checks pass.
application_recovery_kit.py publishes only new hash-addressed kits and checks live
versioning/no lifecycle before publication. The full277,585,920byte kit includes
encrypted data, exact images/tools/SDK/receipts and no private identity/API pair.
application_recovery_drill.py independently downloads/verifies all2098members,
retrieves the separate key, loads the retained images without registry pulls,
decrypts/restores and repeats all actual app checks in30.232s monotonic. Original
node/plaintext not used. Existing Docker daemon and held API callers were used;
operator account recovery time/fresh-daemon proof and real-browser identity
journeys are excluded. No production restore/expiry/timer activation.
The successful proof is itself hash-addressed/conditionally created under the
protected kits/receipts/ prefix and independently version/hash read back. The
API contract records that proof coordinate as well as the kit and recovered
primary point. Later automated drills retain the same independent proof.
Exact protected point and kit coordinates are in the public API contract; receipt:
docs/evidence/2026-10-29-coulomb-provider-kit-recovery.json. Consumer CSOC-WP-0012-T02
is done. Owner T02 retention/capacity/copy disposition and T03/T04 scheduled and
monitored operation remain open. Preserve isolated recovery fixtures for the
pending actual-data storage-migration rehearsal; do not treat their copies as
another independent offsite location or delete historical plaintext pairs.