feat(v4): consolidate audited data recovery economic replay and optional archival
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
"""Audited numeric-representation compatibility, not relaxed device identity checks.
|
||||
|
||||
Only the internal operator API can register equivalence. Raw journals are never
|
||||
rewritten, and the original received fingerprint is retained with each receipt.
|
||||
"""
|
||||
from hashlib import sha256
|
||||
import json
|
||||
import math
|
||||
import re
|
||||
|
||||
|
||||
def schema(con):
|
||||
con.executescript('''
|
||||
CREATE TABLE IF NOT EXISTS planner_mapping_compatibility(
|
||||
plant TEXT NOT NULL, dataset TEXT NOT NULL, alias TEXT NOT NULL,
|
||||
canonical TEXT NOT NULL, inventory TEXT NOT NULL, evidence TEXT NOT NULL,
|
||||
created_at INTEGER NOT NULL, PRIMARY KEY(plant,dataset,alias));
|
||||
CREATE TABLE IF NOT EXISTS planner_observation_origins(
|
||||
plant TEXT NOT NULL, dataset TEXT NOT NULL, captured_at INTEGER NOT NULL,
|
||||
received_mapping TEXT NOT NULL, inventory TEXT NOT NULL,
|
||||
evidence_id TEXT, received_at INTEGER NOT NULL,
|
||||
PRIMARY KEY(plant,dataset,captured_at,received_mapping));
|
||||
''')
|
||||
|
||||
|
||||
def _decode(text):
|
||||
if not isinstance(text, str) or len(text.encode()) > 131072:
|
||||
raise ValueError('Bounded configuration evidence required')
|
||||
def pairs(items):
|
||||
d = {}
|
||||
for k, v in items:
|
||||
if k in d: raise ValueError('Duplicate configuration key')
|
||||
d[k] = v
|
||||
return d
|
||||
def constant(_): raise ValueError('Nonfinite configuration')
|
||||
return json.loads(text, object_pairs_hook=pairs, parse_constant=constant)
|
||||
|
||||
|
||||
def _same(a, b):
|
||||
if type(a) is not type(b): return False
|
||||
if isinstance(a, dict):
|
||||
return list(a) == list(b) and all(_same(a[k], b[k]) for k in a)
|
||||
if isinstance(a, list): return len(a) == len(b) and all(_same(x, y) for x, y in zip(a, b))
|
||||
return a == b
|
||||
|
||||
|
||||
def validate_evidence(plant, config, payload):
|
||||
if not isinstance(payload, dict) or set(payload) != {'version', 'canonicalJson', 'legacyJson'} or type(payload['version']) is not int or payload['version'] != 1:
|
||||
raise ValueError('Explicit versioned representation evidence required')
|
||||
ca, le = payload['canonicalJson'], payload['legacyJson']
|
||||
a, b = _decode(ca), _decode(le)
|
||||
canonical_hash, alias = sha256(ca.encode()).hexdigest(), sha256(le.encode()).hexdigest()
|
||||
if canonical_hash != config['mappingSha256'] or alias == canonical_hash:
|
||||
raise ValueError('Evidence does not match configured mapping')
|
||||
for c in (a, b):
|
||||
if not isinstance(c, dict) or c.get('installationId') != plant or c.get('reportedInventorySha256') != config['inventorySha256']:
|
||||
raise ValueError('Evidence belongs to another installation or inventory')
|
||||
x = a.get('accounting', {}).get('splitToleranceW')
|
||||
y = b.get('accounting', {}).get('splitToleranceW')
|
||||
if type(x) is not float or type(y) is not int or not math.isfinite(x) or x != y or not 0 <= x <= 500:
|
||||
raise ValueError('Only demonstrated float/integer tolerance representation is compatible')
|
||||
b['accounting']['splitToleranceW'] = float(y)
|
||||
if not _same(a, b):
|
||||
raise ValueError('Other configuration differences are not representation compatibility')
|
||||
# Proof includes the exact hashed JSON strings; no arbitrary labels as evidence.
|
||||
evidence = json.dumps(payload, sort_keys=True, separators=(',', ':'), allow_nan=False)
|
||||
return {'alias': alias, 'canonical': canonical_hash, 'inventory': config['inventorySha256'],
|
||||
'evidenceId': sha256(evidence.encode()).hexdigest(), 'evidence': evidence}
|
||||
|
||||
|
||||
def register(con, plant, config, payload, now):
|
||||
if config.get('sourceDatasetId'): raise ValueError('Register compatibility on original dataset only')
|
||||
v = validate_evidence(plant, config, payload)
|
||||
con.execute('BEGIN IMMEDIATE')
|
||||
try:
|
||||
old = con.execute('SELECT canonical,inventory,evidence FROM planner_mapping_compatibility WHERE plant=? AND dataset=? AND alias=?',
|
||||
(plant, config['datasetId'], v['alias'])).fetchone()
|
||||
expected = (v['canonical'], v['inventory'], v['evidence'])
|
||||
if old and tuple(old) != expected: raise ValueError('Immutable mapping compatibility conflict')
|
||||
con.execute('INSERT OR IGNORE INTO planner_mapping_compatibility VALUES(?,?,?,?,?,?,?)',
|
||||
(plant, config['datasetId'], v['alias'], *expected, now))
|
||||
con.commit()
|
||||
except Exception:
|
||||
con.rollback(); raise
|
||||
return {'status': 'registered', 'datasetId': config['datasetId'], 'evidenceId': v['evidenceId'],
|
||||
'compatibleMapping': v['alias'], 'canonicalMapping': v['canonical'], 'controlEnabled': False}
|
||||
|
||||
|
||||
def approved(con, plant, config):
|
||||
rows = con.execute('SELECT alias,evidence FROM planner_mapping_compatibility WHERE plant=? AND dataset=? AND canonical=? AND inventory=?',
|
||||
(plant, config['datasetId'], config['mappingSha256'], config['inventorySha256']))
|
||||
return {r['alias']: sha256(r['evidence'].encode()).hexdigest() for r in rows}
|
||||
|
||||
|
||||
def save_origin(con, plant, config, record, captured_at, received_at, aliases):
|
||||
source = record['mappingSha256']
|
||||
if source != config['mappingSha256'] and source not in aliases:
|
||||
raise ValueError('Unknown mapping; no receipt written')
|
||||
con.execute('INSERT OR IGNORE INTO planner_observation_origins VALUES(?,?,?,?,?,?,?)',
|
||||
(plant, config['datasetId'], captured_at, source, config['inventorySha256'], aliases.get(source), received_at))
|
||||
Reference in New Issue
Block a user