Advance blocked assurance and operator callback work
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a06ecb-456a-71c2-b41e-0755d336e883
This commit is contained in:
parent
a3ca4b708f
commit
445f1361dc
16 changed files with 505 additions and 144 deletions
44
scripts/recovery_evidence.py
Normal file
44
scripts/recovery_evidence.py
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
"""Hash-pinned native recovery receipts, with original completion timestamps."""
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
from service_assurance import timestamp
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def recovery_signals(now, root=ROOT):
|
||||
index = json.loads((root / 'assurance/recovery-evidence.json').read_text())
|
||||
if index['schema'] != 'railiance-platform.recovery-evidence.v1':
|
||||
raise ValueError('unknown recovery index')
|
||||
signals = {}
|
||||
for entry in index['receipts']:
|
||||
signal = entry['signal']
|
||||
if signal in signals or signal not in ('apps-pg.restore', 'forgejo-db.restore'):
|
||||
raise ValueError('unexpected recovery signal')
|
||||
sample = {'result': 'unavailable', 'observed_at': now.isoformat()}
|
||||
try:
|
||||
path = (root / entry['path']).resolve()
|
||||
if not path.is_relative_to((root / 'docs/evidence').resolve()):
|
||||
raise ValueError('receipt outside evidence directory')
|
||||
raw = path.read_bytes()
|
||||
if hashlib.sha256(raw).hexdigest() != entry['sha256']:
|
||||
raise ValueError('receipt drift')
|
||||
receipt = json.loads(raw)
|
||||
cell = signal.removesuffix('.restore')
|
||||
if (receipt['schema'] != 'platform.scaleway-primary-restore.v1'
|
||||
or receipt['primary_destination'] != f's3://railiance-platform-pg-backup/platform-pg/{cell}/'
|
||||
or receipt['source'] != 'Scaleway Barman base backup and WAL'
|
||||
or receipt['stage'] != 'database_acceptance'
|
||||
or receipt['status'] != 'verified'
|
||||
or receipt['cleanup'] is not True
|
||||
or receipt['production_ready'] is not True):
|
||||
raise ValueError('receipt not accepted')
|
||||
completed = timestamp(receipt['finished_at'])
|
||||
if not timestamp(receipt['started_at']) <= completed <= now:
|
||||
raise ValueError('invalid receipt chronology')
|
||||
sample = {'result': 'pass', 'observed_at': receipt['finished_at']}
|
||||
except (OSError, ValueError, KeyError, TypeError):
|
||||
pass
|
||||
signals[signal] = sample
|
||||
return signals
|
||||
Loading…
Add table
Add a link
Reference in a new issue