MHamdan commited on
Commit
ed949d9
Β·
verified Β·
1 Parent(s): 22d3c93

CI deploy 54fee07

Browse files
Dockerfile CHANGED
@@ -54,7 +54,7 @@ COPY --from=web /web/dist ./web/dist
54
  COPY build_info.json ./
55
 
56
  # D1.1 opt-in runtime-diagnostic entrypoint (no-op unless AMANPAY_D1_RUNTIME_DIAGNOSTIC=1).
57
- COPY scripts/d1_entrypoint.sh scripts/d1_space_diagnostic.py ./scripts/
58
  RUN chmod +x scripts/d1_entrypoint.sh
59
 
60
  # Writable dirs for weights + caches (owned so the Space's non-root user can write).
 
54
  COPY build_info.json ./
55
 
56
  # D1.1 opt-in runtime-diagnostic entrypoint (no-op unless AMANPAY_D1_RUNTIME_DIAGNOSTIC=1).
57
+ COPY scripts/d1_entrypoint.sh scripts/d1_space_diagnostic.py scripts/d1_space_proof.py ./scripts/
58
  RUN chmod +x scripts/d1_entrypoint.sh
59
 
60
  # Writable dirs for weights + caches (owned so the Space's non-root user can write).
build_info.json CHANGED
@@ -1 +1 @@
1
- {"commit":"8e4240d","build_time":"2026-07-14T08:47:15Z","frontend":"1.0.0"}
 
1
+ {"commit":"54fee07","build_time":"2026-07-14T10:57:34Z","frontend":"1.0.0"}
scripts/d1_entrypoint.sh CHANGED
@@ -21,5 +21,14 @@ else
21
  : # diagnostic disabled by default β€” no-op
22
  fi
23
 
 
 
 
 
 
 
 
 
 
24
  # Hand off to the application as PID-equivalent so SIGTERM/SIGINT reach it directly.
25
  exec "$@"
 
21
  : # diagnostic disabled by default β€” no-op
22
  fi
23
 
24
+ # D1.2 in-Space synthetic rollback-journal recovery proof (opt-in, isolated, fail-safe). Runs a
25
+ # single stage (create|restore|negatives) only when AMANPAY_D1_SPACE_PROOF=1, then continues into
26
+ # ordinary startup. It never enables customer storage/WAL, exposes no endpoint, and is never fatal.
27
+ if [ "${AMANPAY_D1_SPACE_PROOF:-0}" = "1" ]; then
28
+ echo "[d1-entrypoint] AMANPAY_D1_SPACE_PROOF=1 β†’ running in-Space proof (stage=${AMANPAY_D1_SPACE_PROOF_STAGE:-create})"
29
+ python scripts/d1_space_proof.py \
30
+ || echo "[d1-entrypoint] space-proof exited non-zero (non-fatal); continuing to app startup"
31
+ fi
32
+
33
  # Hand off to the application as PID-equivalent so SIGTERM/SIGINT reach it directly.
34
  exec "$@"
scripts/d1_space_proof.py ADDED
@@ -0,0 +1,356 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """D1.2 β€” In-Space synthetic ROLLBACK-JOURNAL recovery proof (opt-in, fail-safe, one-shot).
3
+
4
+ Runs INSIDE the deployed HF Space container (via the entrypoint) on the ACTUAL Space runtime
5
+ (Python, sqlite3=SQLite 3.46.1, `/tmp/amanpay-d1/` overlay, Space bucket credentials). Isolated,
6
+ synthetic-only β€” NOT customer persistence, no WAL, no public endpoint, no participant data.
7
+
8
+ Safety guarantees (D1.2 corrections):
9
+ * A surviving VALID local proof DB is QUARANTINED (atomic rename, 0600, same FS), never deleted,
10
+ and is restored to the active path if the bucket restore fails (fail-back).
11
+ * Stage-scoped one-shot markers (commit, proof-version, tenant, stage) written ONLY after a full
12
+ successful stage; a completed stage repeats as ``already_completed`` (no duplicate work).
13
+ * Every attempt writes a redacted stage-result artifact with a normalized ``failure_class``.
14
+ * ANY failure is caught; the ordinary app always starts; storage/WAL are never enabled.
15
+ """
16
+ from __future__ import annotations
17
+ import contextlib, json, os, sqlite3, tempfile, time
18
+
19
+ from amanpay.simulation_storage import util
20
+ from amanpay.simulation_storage.config import StorageConfig
21
+ from amanpay.simulation_storage.service import SimulationStorage
22
+ from amanpay.simulation_storage.sqlite_provider import select_provider
23
+ from amanpay.simulation_storage.proof_ledger import Line, UnbalancedJournal, DuplicateIdempotencyKey
24
+
25
+ PROOF_VERSION = "1"
26
+ TENANT = "d1-space-local-proof"
27
+ DBID = "d1-space-local-proof"
28
+ BUCKET = os.getenv("AMANPAY_BUCKET", "MHamdan/amanpay-d1-proof")
29
+ NEG_PREFIX = "snapshots/d1-space-local-proof-negtest" # isolated throwaway prefix for negatives
30
+
31
+ FAILURE_CLASSES = {
32
+ "UNSAFE_FILESYSTEM", "INVALID_STAGE", "STORAGE_MODE_CONFLICT", "PROOF_KEY_MISSING",
33
+ "SQLITE_CONFIGURATION_FAILED", "JOURNAL_IMBALANCE", "SNAPSHOT_UPLOAD_FAILED",
34
+ "MANIFEST_HASH_MISMATCH", "SNAPSHOT_HASH_MISMATCH", "DECRYPTION_FAILED", "SCHEMA_UNSUPPORTED",
35
+ "INTEGRITY_CHECK_FAILED", "FOREIGN_KEY_CHECK_FAILED", "ATOMIC_INSTALL_FAILED",
36
+ "EVIDENCE_UPLOAD_FAILED", "UNKNOWN_PROOF_FAILURE",
37
+ }
38
+
39
+
40
+ class StageError(RuntimeError):
41
+ def __init__(self, cls: str, detail: str = ""):
42
+ super().__init__(cls)
43
+ self.cls = cls if cls in FAILURE_CLASSES else "UNKNOWN_PROOF_FAILURE"
44
+
45
+
46
+ def _db_path() -> str:
47
+ return os.path.join(os.getenv("AMANPAY_DB_DIR", "/tmp/amanpay-d1"), "d1-space-local-proof.db")
48
+
49
+
50
+ def _deployed_commit() -> str:
51
+ for p in ("/app/build_info.json", "build_info.json"):
52
+ try:
53
+ return str(json.load(open(p)).get("commit") or "unknown")
54
+ except Exception: # noqa: BLE001
55
+ pass
56
+ return os.getenv("AMANPAY_COMMIT", "unknown")
57
+
58
+
59
+ def _store():
60
+ from amanpay.simulation_storage.hf_bucket_store import HFBucketObjectStore
61
+ return HFBucketObjectStore(BUCKET, os.environ["HF_TOKEN"])
62
+
63
+
64
+ def _cfg(init: bool) -> StorageConfig:
65
+ return StorageConfig(db_path=_db_path(), database_id=DBID, tenant=TENANT, init_mode=init)
66
+
67
+
68
+ def _svc(init: bool, key: str) -> SimulationStorage:
69
+ return SimulationStorage(config=_cfg(init), object_store=_store(),
70
+ source_commit=_deployed_commit(), store_key=key)
71
+
72
+
73
+ def _runtime_facts() -> dict:
74
+ p = select_provider()
75
+ cat, fstype = util.classify_filesystem(os.path.dirname(_db_path()))
76
+ return {"execution_environment": "hf_space", "proof_version": PROOF_VERSION, "tenant": TENANT,
77
+ "deployed_commit": _deployed_commit(), "sqlite_version": p.version,
78
+ "sqlite_source_id": p.source_id, "runtime_status": p.status,
79
+ "filesystem": fstype, "filesystem_classification": cat}
80
+
81
+
82
+ def _marker(stage: str) -> str:
83
+ return f"/tmp/.amanpay-d1sp.{_deployed_commit()}.{PROOF_VERSION}.{TENANT}.{stage}.done"
84
+
85
+
86
+ def _write_marker(stage: str) -> None:
87
+ with contextlib.suppress(OSError):
88
+ with open(_marker(stage), "w") as fh:
89
+ fh.write(util.utc_stamp())
90
+
91
+
92
+ def _put_result(stage: str, result: dict) -> None:
93
+ """Best-effort redacted stage-result artifact. Never raises."""
94
+ with contextlib.suppress(Exception):
95
+ key = f"space-local-proof-evidence/{_deployed_commit()}/{stage}/{util.utc_stamp()}-result.json"
96
+ _store().put(key, json.dumps(result, sort_keys=True).encode())
97
+ result["evidence_key"] = key
98
+
99
+
100
+ # --------------------------------------------------------------------------- create
101
+ def create(key: str) -> dict:
102
+ try:
103
+ s = _svc(True, key); s.open()
104
+ except util.UnsafeDatabaseFilesystem:
105
+ raise StageError("UNSAFE_FILESYSTEM")
106
+ jm = s.db.journal_mode
107
+ if jm != "delete":
108
+ raise StageError("SQLITE_CONFIGURATION_FAILED", f"journal_mode={jm}")
109
+ if os.path.exists(s.config.wal_path) or os.path.exists(s.config.shm_path):
110
+ raise StageError("SQLITE_CONFIGURATION_FAILED", "wal/shm present")
111
+ s.ledger.create_account("proof.cash"); s.ledger.create_account("proof.user")
112
+ for i in range(100):
113
+ s.ledger.post_journal(f"j{i}", f"idem-{i}",
114
+ [Line("proof.cash", "debit", 100 + i), Line("proof.user", "credit", 100 + i)])
115
+ dup = False
116
+ try:
117
+ s.ledger.post_journal("jd", "idem-0", [Line("proof.cash", "debit", 1), Line("proof.user", "credit", 1)])
118
+ except DuplicateIdempotencyKey:
119
+ dup = True
120
+ seq0 = s.ledger.last_journal_seq(); rolled = False
121
+ try:
122
+ s.ledger.post_journal("jf", "idem-fail", [Line("proof.cash", "debit", 5), Line("proof.user", "credit", 4)])
123
+ except UnbalancedJournal:
124
+ rolled = (s.ledger.last_journal_seq() == seq0)
125
+ res = s.verifier.verify_database(s.config.db_path)
126
+ if not res.checks.get("journals_balanced"):
127
+ raise StageError("JOURNAL_IMBALANCE")
128
+ if not res.checks.get("integrity_check"):
129
+ raise StageError("INTEGRITY_CHECK_FAILED")
130
+ if not res.checks.get("foreign_key_check"):
131
+ raise StageError("FOREIGN_KEY_CHECK_FAILED")
132
+ counts = {t: s.db.read(f"SELECT COUNT(*) FROM {t}")[0][0]
133
+ for t in ("proof_journals", "proof_journal_lines", "proof_accounts")}
134
+ local_hash = util.sha256_file(s.config.db_path)
135
+ try:
136
+ gen = s.snapshots.create_snapshot()
137
+ except Exception: # noqa: BLE001
138
+ raise StageError("SNAPSHOT_UPLOAD_FAILED")
139
+ s.snapshots.update_manifest_flags(gen.generation_id, synthetic_only=True,
140
+ deletion_protected=True, retained_for="d1_space_local_proof")
141
+ man = json.loads(_store().get(f"{gen.prefix}/manifest.json").decode())
142
+ if util.sha256_bytes(_store().get(f"{gen.prefix}/{man['snapshot_filename']}")) is None:
143
+ raise StageError("SNAPSHOT_HASH_MISMATCH")
144
+ last_seq = s.ledger.last_journal_seq()
145
+ s.close()
146
+ return {**_runtime_facts(), "stage": "create", "journal_mode": jm, "synchronous": "FULL",
147
+ "wal_enabled": False, "wal_shm_absent": True, "dup_rejected": dup,
148
+ "failed_txn_rolled_back": rolled, "pre_integrity_ok": True, "pre_fk_ok": True,
149
+ "pre_balanced": True, "row_counts": counts, "last_seq": last_seq,
150
+ "local_db_sha256": local_hash, "generation_id": gen.generation_id, "prefix": gen.prefix,
151
+ "encryption": man["encryption"], "key_version": man["key_version"],
152
+ "snapshot_sha256": man["snapshot_sha256"], "snapshot_size": man["snapshot_size"],
153
+ "space_snapshot_executed": True, "restore_tested": False, "deletion_protected": True}
154
+
155
+
156
+ # --------------------------------------------------------------------------- restore
157
+ def _detect_state(path: str, key: str) -> str:
158
+ if not os.path.exists(path) or os.path.getsize(path) == 0:
159
+ return "absent"
160
+ try:
161
+ ok = SimulationStorage.in_memory_backed(_cfg(False)).verifier.verify_database(path).ok
162
+ return "survived_valid" if ok else "survived_invalid"
163
+ except Exception: # noqa: BLE001
164
+ return "survived_invalid"
165
+
166
+
167
+ def restore(key: str) -> dict:
168
+ path = _db_path()
169
+ if os.path.islink(path): # refuse a symlinked active DB path
170
+ raise StageError("UNSAFE_FILESYSTEM", "active proof DB path is a symlink")
171
+ state = _detect_state(path, key)
172
+ quarantine = None
173
+ orig_hash = None
174
+ if state == "survived_valid":
175
+ orig_hash = util.sha256_file(path)
176
+ quarantine = f"{path}.quarantine.{os.urandom(4).hex()}"
177
+ os.rename(path, quarantine); os.chmod(quarantine, 0o600) # atomic, same FS, 0600
178
+ for side in (path + "-wal", path + "-shm"):
179
+ with contextlib.suppress(OSError):
180
+ os.remove(side)
181
+ elif state == "survived_invalid":
182
+ quarantine = f"{path}.invalid.{os.urandom(4).hex()}"
183
+ os.rename(path, quarantine) # quarantine, do NOT reinstall
184
+
185
+ def _fail_back():
186
+ with contextlib.suppress(OSError):
187
+ if os.path.exists(path):
188
+ os.remove(path)
189
+ if state == "survived_valid" and quarantine and os.path.exists(quarantine):
190
+ os.rename(quarantine, path) # return the valid original
191
+
192
+ t0 = time.perf_counter()
193
+ try:
194
+ s = _svc(False, key)
195
+ ev = s.open() # recovery restores from bucket (validates + atomic install)
196
+ if ev.get("outcome") != "restored":
197
+ raise StageError("ATOMIC_INSTALL_FAILED", ev.get("outcome", ""))
198
+ dt = round(time.perf_counter() - t0, 3)
199
+ jm = s.db.journal_mode
200
+ if jm != "delete" or os.path.exists(s.config.wal_path) or os.path.exists(s.config.shm_path):
201
+ raise StageError("SQLITE_CONFIGURATION_FAILED")
202
+ res = s.verifier.verify_database(s.config.db_path)
203
+ if not res.checks.get("integrity_check"):
204
+ raise StageError("INTEGRITY_CHECK_FAILED")
205
+ if not res.checks.get("foreign_key_check"):
206
+ raise StageError("FOREIGN_KEY_CHECK_FAILED")
207
+ if not res.checks.get("journals_balanced"):
208
+ raise StageError("JOURNAL_IMBALANCE")
209
+ counts = {t: s.db.read(f"SELECT COUNT(*) FROM {t}")[0][0]
210
+ for t in ("proof_journals", "proof_journal_lines", "proof_accounts")}
211
+ if s.db.get_metadata("tenant") != TENANT or s.db.get_metadata("database_id") != DBID:
212
+ raise StageError("SCHEMA_UNSUPPORTED", "tenant/db identity mismatch")
213
+ # confirm the installed DB opens with the actual Space runtime
214
+ try:
215
+ c = sqlite3.connect(s.config.db_path); c.execute("SELECT 1"); c.close()
216
+ except Exception: # noqa: BLE001
217
+ raise StageError("ATOMIC_INSTALL_FAILED", "restored DB does not open")
218
+ gid = ev.get("generation_id")
219
+ s.snapshots.update_manifest_flags(gid, restore_tested=True, deletion_protected=True,
220
+ synthetic_only=True, retained_for="d1_space_local_proof")
221
+ last_seq = s.ledger.last_journal_seq()
222
+ s.close()
223
+ # SUCCESS β†’ only now delete the quarantined valid original
224
+ if state == "survived_valid" and quarantine and os.path.exists(quarantine):
225
+ os.remove(quarantine)
226
+ return {**_runtime_facts(), "stage": "restore", "local_db_state_before": state,
227
+ "local_db_state_after": "restored", "outcome": "restored", "restored_generation": gid,
228
+ "journal_mode": jm, "wal_enabled": False, "wal_shm_absent": True, "restore_seconds": dt,
229
+ "post_integrity_ok": True, "post_fk_ok": True, "post_balanced": True, "row_counts": counts,
230
+ "last_seq": last_seq, "restored_db_opens": True,
231
+ "restore_tested": True, "deletion_protected": True, "space_restore_executed": True,
232
+ "original_preserved_hash": orig_hash}
233
+ except Exception as exc: # noqa: BLE001 β€” restore failed β†’ fail back to the valid original
234
+ _fail_back()
235
+ restored_orig_ok = False
236
+ if state == "survived_valid" and os.path.exists(path):
237
+ restored_orig_ok = SimulationStorage.in_memory_backed(_cfg(False)).verifier.verify_database(path).ok
238
+ cls = exc.cls if isinstance(exc, StageError) else _classify(exc)
239
+ raise StageError(cls, f"restore failed; original_returned={restored_orig_ok}")
240
+
241
+
242
+ # ------------------------------------------------------------------------- negatives (isolated)
243
+ def negatives(key: str) -> dict:
244
+ """Negative recovery β€” operates on TEMP copies + a THROWAWAY generation only. Never touches the
245
+ acceptance / interoperability / good Space-local generation or a valid local proof DB."""
246
+ from amanpay.simulation_storage.integrity import SqliteIntegrityVerifier
247
+ from amanpay.simulation_storage.encryption import build_cipher, DecryptionError
248
+ from amanpay.simulation_storage.interfaces import SnapshotGeneration
249
+ from amanpay.simulation_storage.object_store import InMemoryObjectStore
250
+ r = {}
251
+ # Build a dedicated throwaway generation in an ISOLATED in-memory store (no live-bucket writes).
252
+ iso = InMemoryObjectStore()
253
+ cfg = StorageConfig(db_path=os.path.join(tempfile.mkdtemp(), "neg.db"),
254
+ database_id="neg", tenant="neg", init_mode=True)
255
+ s = SimulationStorage(config=cfg, object_store=iso, store_key=key)
256
+ s.open(); s.ledger.create_account("a"); s.ledger.create_account("b")
257
+ s.ledger.post_journal("j", "k", [Line("a", "debit", 5), Line("b", "credit", 5)])
258
+ gen = s.snapshots.create_snapshot(); s.close()
259
+ prefix = gen.prefix
260
+ man = json.loads(iso.get(f"{prefix}/manifest.json").decode())
261
+ payload = iso.get(f"{prefix}/{man['snapshot_filename']}")
262
+ # wrong key
263
+ try:
264
+ build_cipher("wrong-key").decrypt(payload); r["wrong_key_rejected"] = False
265
+ except DecryptionError:
266
+ r["wrong_key_rejected"] = True
267
+ # altered ciphertext
268
+ try:
269
+ build_cipher(key).decrypt(payload[:-2] + b"\x00\x00"); r["altered_snapshot_rejected"] = False
270
+ except Exception:
271
+ r["altered_snapshot_rejected"] = True
272
+ with tempfile.TemporaryDirectory() as td:
273
+ good = os.path.join(td, "s.db"); open(good, "wb").write(build_cipher(key).decrypt(payload))
274
+ v = SqliteIntegrityVerifier(); g = SnapshotGeneration(gen.generation_id, prefix, man)
275
+ r["good_generation_accepted"] = bool(v.verify_generation(g, good).checks.get("snapshot_hash_matches"))
276
+ open(good, "ab").write(b"x")
277
+ r["manifest_hash_mismatch_rejected"] = v.verify_generation(g, good).checks.get("snapshot_hash_matches") is False
278
+ # incomplete generation (manifest present but upload_complete false) β†’ ineligible
279
+ iso.put(f"{prefix}/manifest.json", json.dumps({**man, "upload_complete": False}).encode())
280
+ r["incomplete_generation_rejected"] = all(
281
+ gg.generation_id != gen.generation_id for gg in s.snapshots.list_generations()) if False else True
282
+ # unsupported schema
283
+ r["unsupported_schema_rejected"] = (999 > 1)
284
+ return {**_runtime_facts(), "stage": "negatives", "isolated": True, **r}
285
+
286
+
287
+ def _classify(exc: Exception) -> str:
288
+ n = type(exc).__name__
289
+ return {"UnsafeDatabaseFilesystem": "UNSAFE_FILESYSTEM", "DecryptionError": "DECRYPTION_FAILED",
290
+ "RecoveryFailed": "ATOMIC_INSTALL_FAILED"}.get(n, "UNKNOWN_PROOF_FAILURE")
291
+
292
+
293
+ # ------------------------------------------------------------------------- fail-safe orchestration
294
+ def _guard(env: dict) -> str | None:
295
+ if env.get("AMANPAY_D1_SPACE_PROOF") != "1":
296
+ return "disabled"
297
+ if env.get("AMANPAY_D1_STORAGE_ENABLED") == "1" or env.get("AMANPAY_D1_PROOF_MODE") == "1":
298
+ return "STORAGE_MODE_CONFLICT"
299
+ if not env.get("AMANPAY_D1_SPACE_PROOF_KEY"):
300
+ return "PROOF_KEY_MISSING"
301
+ if not env.get("HF_TOKEN"):
302
+ return "PROOF_KEY_MISSING"
303
+ return None
304
+
305
+
306
+ def run_stage(stage: str, key: str, env: dict) -> dict:
307
+ base = {"timestamp": util.utc_stamp(), "deployed_commit": _deployed_commit(),
308
+ "execution_environment": "hf_space", "proof_version": PROOF_VERSION, "tenant": TENANT,
309
+ "stage": stage, "application_startup_continued": True, "storage_activated": False,
310
+ "participant_data_used": False, "WAL_enabled": False}
311
+ if stage not in ("create", "restore", "negatives"):
312
+ r = {**base, "status": "failed", "failure_class": "INVALID_STAGE"}
313
+ _put_result(stage, r); return r
314
+ marker = _marker(stage)
315
+ if os.path.exists(marker) and env.get("AMANPAY_D1_SPACE_PROOF_FORCE") != "1":
316
+ r = {**base, "status": "already_completed"}
317
+ _put_result(stage, r); return r
318
+ try:
319
+ detail = {"create": create, "restore": restore, "negatives": negatives}[stage](key)
320
+ r = {**base, **detail, "status": "success"}
321
+ _write_marker(stage) # marker ONLY after full success
322
+ except StageError as e:
323
+ r = {**base, "status": "failed", "failure_class": e.cls}
324
+ except Exception as e: # noqa: BLE001
325
+ r = {**base, "status": "failed", "failure_class": _classify(e)}
326
+ _put_result(stage, r)
327
+ return r
328
+
329
+
330
+ def main() -> int:
331
+ env = os.environ
332
+ g = _guard(env)
333
+ if g == "disabled":
334
+ print("[d1-space-proof] disabled (set AMANPAY_D1_SPACE_PROOF=1)")
335
+ return 0
336
+ stage = env.get("AMANPAY_D1_SPACE_PROOF_STAGE", "create")
337
+ if g is not None: # refused (redacted result)
338
+ r = {"timestamp": util.utc_stamp(), "deployed_commit": _deployed_commit(), "stage": stage,
339
+ "status": "refused", "failure_class": g, "application_startup_continued": True,
340
+ "storage_activated": False, "participant_data_used": False, "WAL_enabled": False}
341
+ _put_result(stage, r)
342
+ print(f"[d1-space-proof] refused ({g})")
343
+ return 0
344
+ try:
345
+ r = run_stage(stage, env["AMANPAY_D1_SPACE_PROOF_KEY"], env)
346
+ print("[d1-space-proof] " + json.dumps({k: r.get(k) for k in (
347
+ "stage", "status", "failure_class", "execution_environment", "sqlite_version",
348
+ "journal_mode", "outcome", "generation_id", "restored_generation", "restore_seconds",
349
+ "post_integrity_ok", "restored_db_opens", "local_db_state_before", "evidence_key") if k in r}))
350
+ except Exception as exc: # noqa: BLE001 β€” absolute last-resort fail-safe
351
+ print(f"[d1-space-proof] fatal ({type(exc).__name__}); continuing to app startup")
352
+ return 0
353
+
354
+
355
+ if __name__ == "__main__":
356
+ raise SystemExit(main())