101 lines
5.2 KiB
Python
101 lines
5.2 KiB
Python
"""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))
|