318 lines
20 KiB
Python
318 lines
20 KiB
Python
#!/usr/bin/env python3
|
|
"""Offline transfer and strict preflight around the unchanged historical gate.
|
|
|
|
WHY: offline acceptance does not authenticate missing runtime installation data.
|
|
CONTRACT: historical bytes remain read-only; only new transfer records are made.
|
|
INVARIANT: checks create no socket, process, runtime directory or budget debit.
|
|
"""
|
|
import ast
|
|
import hashlib
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import shutil
|
|
import stat
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
|
|
HERE = Path(__file__).resolve().parent
|
|
REPO = HERE.parents[2]
|
|
OLD_FIXTURE = REPO / 'tooling/probes/step20c-static-nested-geometry-replay'
|
|
OLD_LAB = Path('/home/fy59/swayfx-mosaic-receipt-integrity-cof0umuk')
|
|
TICKET = 'SWAYFX_MOSAIC_STEP20C_RECEIPT_FREEZE_AND_STATIC_NESTED_RUNTIME_V1'
|
|
REGISTRY = Path('/home/fy59/swayfx-mosaic-static-nested-runtime-v1.owner')
|
|
REGISTRATION = REGISTRY.with_suffix('.registration.json')
|
|
AMENDMENT = REGISTRY.with_suffix('.registration-amendment.json')
|
|
SPENT = REGISTRY.with_suffix('.launch-consumed.json')
|
|
BINARY = Path('/home/fy59/swayfx-mosaic-static-runtime-hm5kbdkt/stage-static/usr/bin/swayfx-mosaic')
|
|
BINARY_SHA = 'f82557c1a9978a5e9665b9b4291b74880253725359e3df1c5b8d554921f7603b'
|
|
BUILD_ID = '942db64269640778ee433e5970ba1100bc60f94c'
|
|
WLROOTS = Path('/usr/lib/libwlroots-0.20.so')
|
|
WLROOTS_SHA = '7edca9a7d65855addda68e29ba5db94d57c803eb5fa7d0f86db3136b90ca9837'
|
|
SEALS = {'source-sha256.txt': (76, 'dbd96db3f19f6eb60fc8be13c62b1e71944a759b481d008a7825b3720cf525ce'),
|
|
'evidence-sha256.txt': (199, 'f00ca54e39bff031a5bda585ea52cfebe9239e71b4c7ee7dc5c41a930e1598b3')}
|
|
REPORT_SHA = '311af1fe694d1bde88884afa21ef16a4fe908d6cfa47b6cbed515d1a21fedc9e'
|
|
REVIEW_SHA = '4d8401978e69a6c54f2f6144ba06deff4952671bc8e1ae3008376ab6ffe01e09'
|
|
sys.path.insert(0, str(OLD_FIXTURE))
|
|
from authorization import authorize, MARKER
|
|
from ledger import LIMITS, valid
|
|
from support import environment, sha, verify
|
|
from write_results import REQUIRED, aggregate
|
|
from receipt_integrity import make_bindings
|
|
|
|
|
|
def need(ok, message):
|
|
if not ok:
|
|
raise ValueError(message)
|
|
|
|
|
|
def read(path):
|
|
return json.loads(Path(path).read_text())
|
|
|
|
|
|
def exclusive(path, data, mode=0o600):
|
|
"""File and parent fsync precede use; existing ownership is never overwritten."""
|
|
path = Path(path)
|
|
fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, mode)
|
|
with os.fdopen(fd, 'w') as out:
|
|
out.write(data if isinstance(data, str) else json.dumps(data, indent=2) + '\n')
|
|
out.flush()
|
|
os.fsync(out.fileno())
|
|
fd = os.open(path.parent, os.O_RDONLY | os.O_DIRECTORY)
|
|
try:
|
|
os.fsync(fd)
|
|
finally:
|
|
os.close(fd)
|
|
|
|
|
|
def regular(path, mode=None):
|
|
path = Path(path)
|
|
info = path.lstat()
|
|
need(stat.S_ISREG(info.st_mode) and info.st_uid == os.getuid(), 'unsafe-owned-file: ' + str(path))
|
|
if mode is not None:
|
|
need(stat.S_IMODE(info.st_mode) == mode, 'file-mode: ' + str(path))
|
|
|
|
|
|
def accepted():
|
|
"""Revalidate real file-backed bindings with the actual accepted aggregate."""
|
|
manifests = {}
|
|
for name, (count, digest) in SEALS.items():
|
|
p = OLD_LAB / 'results' / name
|
|
need(sha(p) == digest, 'historical-manifest-identity')
|
|
observed = verify(p)
|
|
need(observed['entries'] == count, 'historical-manifest-count')
|
|
manifests[name] = observed
|
|
need(sha(OLD_LAB / 'independent-review.md') == REPORT_SHA, 'historical-report-identity')
|
|
need(sha(OLD_LAB / 'results/independent-review.json') == REVIEW_SHA, 'historical-review-identity')
|
|
evidence = {n: read(OLD_LAB / 'results' / (n + '.json')) for n in REQUIRED}
|
|
evidence['independent-review'] = read(OLD_LAB / 'results/independent-review.json')
|
|
bindings = make_bindings(evidence, {n: OLD_LAB / 'results' / (n + '.json') for n in REQUIRED})
|
|
need(bindings == read(OLD_LAB / 'results/review-bindings.json'), 'accepted-bindings')
|
|
for name, digest in bindings['source_delta'].items():
|
|
need(sha(OLD_FIXTURE / name) == sha(OLD_LAB / 'proposed-fixture' / name) == digest, 'accepted-source: ' + name)
|
|
gates = aggregate(evidence, sha, bindings)
|
|
need(gates['status'] == 'READY_FOR_NEW_RUNTIME' and not gates['blockers'] and
|
|
len(gates['markers']) == 5 and set(gates['markers'].values()) == {'PASS'}, 'accepted-aggregate')
|
|
ready = read(OLD_LAB / 'results/offline-gate.json')
|
|
need(ready['status'] == 'READY_FOR_NEW_RUNTIME' and ready['marker'] == MARKER and
|
|
ready['review_bindings'] == bindings and not ready['blockers'] and ready['markers'] == gates['markers'], 'historical-READY')
|
|
return dict(manifests=manifests, bindings=bindings, aggregate=gates,
|
|
report_sha256=REPORT_SHA, review_sha256=REVIEW_SHA, historical_lab=str(OLD_LAB))
|
|
|
|
|
|
def copy_exact(source, destination):
|
|
source, destination = Path(source), Path(destination)
|
|
need(source.is_file() and not source.is_symlink(), 'copy-source-regular')
|
|
with source.open('rb') as src, destination.open('xb') as dst:
|
|
shutil.copyfileobj(src, dst)
|
|
dst.flush()
|
|
os.fsync(dst.fileno())
|
|
destination.chmod(stat.S_IMODE(source.stat().st_mode))
|
|
need(sha(source) == sha(destination), 'copy-identity')
|
|
return dict(source=str(source), destination=str(destination), sha256=sha(source))
|
|
|
|
|
|
def run_command(lab, label, argv):
|
|
result = subprocess.run(list(map(str, argv)), env=environment(), capture_output=True, text=True, timeout=30)
|
|
exclusive(lab / 'raw' / (label + '.json'), dict(argv=list(map(str, argv)), exit_code=result.returncode,
|
|
stdout=result.stdout, stderr=result.stderr))
|
|
need(result.returncode == 0, 'preparation-command: ' + label)
|
|
return result.stdout
|
|
|
|
|
|
def prepare():
|
|
proof = accepted()
|
|
need(not REGISTRY.exists() and not REGISTRATION.exists() and not AMENDMENT.exists() and not SPENT.exists(), 'ticket-already-registered')
|
|
need(not BINARY.is_symlink() and sha(BINARY) == BINARY_SHA and
|
|
stat.S_IMODE(BINARY.stat().st_mode) == 0o755 and sha(WLROOTS) == WLROOTS_SHA, 'binary-and-wlroots-before-copy')
|
|
lab = Path(tempfile.mkdtemp(prefix='swayfx-mosaic-static-nested-runtime-', dir='/home/fy59'))
|
|
lab.chmod(0o700)
|
|
exclusive(REGISTRY, str(lab) + '\n')
|
|
print('LAB=' + str(lab), flush=True)
|
|
for name in ('results', 'raw', 'fixture', 'accepted-offline', 'handoff-source'):
|
|
(lab / name).mkdir()
|
|
exclusive(lab / 'campaign-owner', str(REGISTRY) + '\n')
|
|
transfers = []
|
|
# CONTRACT: copied receipts keep historical paths and scope byte-for-byte.
|
|
for n in REQUIRED:
|
|
transfers.append(copy_exact(OLD_LAB / 'results' / (n + '.json'), lab / 'accepted-offline' / (n + '.json')))
|
|
for n in ('offline-gate.json', 'runner-readiness.json', 'review-bindings.json', *SEALS):
|
|
transfers.append(copy_exact(OLD_LAB / 'results' / n, lab / 'accepted-offline' / n))
|
|
transfers.append(copy_exact(OLD_LAB / 'independent-review.md', lab / 'accepted-offline/independent-review.md'))
|
|
transfers.append(copy_exact(OLD_LAB / 'results/independent-review.json', lab / 'accepted-offline/independent-review.json'))
|
|
for name in proof['bindings']['source_delta']:
|
|
transfers.append(copy_exact(OLD_FIXTURE / name, lab / 'fixture' / name))
|
|
for p in sorted(HERE.iterdir()):
|
|
if p.is_file():
|
|
transfers.append(copy_exact(p, lab / 'handoff-source' / p.name))
|
|
copy_exact(REPO / 'docs/design/STEP20C_STATIC_NESTED_RUNTIME_HANDOFF_AND_VALID_GRAB.md', lab / 'protocol-before-run.md')
|
|
binary_copy = copy_exact(BINARY, lab / 'swayfx-mosaic')
|
|
elf = run_command(lab, 'binary-ELF', ['readelf', '-hW', '-nW', '-dW', lab / 'swayfx-mosaic'])
|
|
need('ELF64' in elf and 'Advanced Micro Devices X86-64' in elf and BUILD_ID in elf and
|
|
all(s not in elf for s in ('libscenefx', '(RPATH)', '(RUNPATH)')), 'ELF-contract')
|
|
symbols = run_command(lab, 'binary-symbols', ['nm', lab / 'swayfx-mosaic'])
|
|
need(all(' T ' + n in symbols for n in ('wlr_scene_tree_set_clip', 'wlr_scene_tree_get_clip')), 'clip-symbols')
|
|
loader = run_command(lab, 'binary-relocations', ['ldd', '-r', lab / 'swayfx-mosaic'])
|
|
need(all(s not in loader for s in ('undefined symbol', 'not found', 'libscenefx')) and
|
|
str(WLROOTS.resolve()) in loader, 'resolved-relocations')
|
|
exclusive(lab / 'results/binary-identity.json', dict(status='PASS', sha256=BINARY_SHA, build_id=BUILD_ID,
|
|
wlroots_sha256=WLROOTS_SHA, copy=binary_copy, mode='0755', ELF='ELF64 x86-64', SceneFX_dynamic=False))
|
|
from prepare_clients import prepare as prepare_clients
|
|
prepare_clients(lab)
|
|
clients = read(lab / 'results/client-provenance.json')
|
|
for name in ('nested-client', 'pointer-daemon', 'static-reference'):
|
|
installation = copy_exact(lab / 'client-build' / name, lab / name)
|
|
regular(lab / name, 0o755)
|
|
clients.setdefault('runtime_installation', []).append(installation)
|
|
# This is a new runtime build receipt, not one of the historical REQUIREDs.
|
|
(lab / 'results/client-provenance.json').write_text(json.dumps(clients, indent=2) + '\n')
|
|
tree = ast.parse((lab / 'fixture/run.py').read_text())
|
|
configs = [ast.literal_eval(n.value) for n in tree.body if isinstance(n, ast.Assign)
|
|
and any(isinstance(t, ast.Name) and t.id == 'config' for t in n.targets)]
|
|
need(len(configs) == 1, 'runner-config-constant')
|
|
exclusive(lab / 'headless-config-template', configs[0])
|
|
exclusive(lab / 'results/accepted-offline-provenance.json', proof)
|
|
# Freeze exact runtime inputs, including installed clients and binary, but
|
|
# never budgets/logs/future captures or circular manifest-bound records.
|
|
immutable = [p for root in ('fixture', 'handoff-source', 'accepted-offline', 'client-build')
|
|
for p in (lab / root).iterdir() if p.is_file()]
|
|
immutable += [lab / n for n in ('swayfx-mosaic', 'nested-client', 'pointer-daemon', 'static-reference',
|
|
'campaign-owner', 'headless-config-template', 'protocol-before-run.md')]
|
|
immutable += [lab / 'results' / n for n in ('accepted-offline-provenance.json', 'binary-identity.json', 'client-provenance.json')]
|
|
immutable += [OLD_FIXTURE / n for n in proof['bindings']['source_delta']]
|
|
immutable += [p for p in HERE.iterdir() if p.is_file()]
|
|
immutable += [REGISTRY, BINARY, WLROOTS.resolve()]
|
|
need(len(immutable) == len(set(immutable)), 'unique-freeze-paths')
|
|
for p in immutable:
|
|
need(not p.is_symlink(), 'freeze-no-symlink')
|
|
manifest = lab / 'results/fixture-freeze-sha256.txt'
|
|
exclusive(manifest, ''.join(sha(p) + ' ' + str(p) + '\n' for p in sorted(immutable)))
|
|
fixture = verify(manifest)
|
|
registration = dict(ticket=TICKET, lab=str(lab), uid=os.getuid(), owner_registry=str(REGISTRY),
|
|
binary=str(BINARY), binary_sha256=BINARY_SHA, wlroots_sha256=WLROOTS_SHA,
|
|
fixture_sha256=fixture['sha256'], non_reusable=True)
|
|
exclusive(REGISTRATION, registration)
|
|
copy_exact(REGISTRATION, lab / 'results/registration.json')
|
|
auth = dict(scope='one-new-static-nested-headless-run', explicit_user_authorization=True,
|
|
ticket=TICKET, registered_lab=str(lab), owner_registry=str(REGISTRY), binary=str(BINARY),
|
|
binary_sha256=BINARY_SHA, wlroots_sha256=WLROOTS_SHA, fixture_sha256=fixture['sha256'], uid=os.getuid(),
|
|
authorization_basis='User transmitted the exact current ticket; one private launch only.')
|
|
exclusive(lab / 'results/authorization.json', auth)
|
|
exclusive(lab / 'results/budget.json', dict(limits=LIMITS, used=dict.fromkeys(LIMITS, 0), events=[]))
|
|
transfer = dict(status='PREPARED_FOR_INDEPENDENT_REVIEW', ticket=TICKET, destination_lab=str(lab),
|
|
fixture=fixture, transfers=transfers, provenance_sha256=sha(lab / 'results/accepted-offline-provenance.json'),
|
|
registration_sha256=sha(REGISTRATION), authorization_sha256=sha(lab / 'results/authorization.json'),
|
|
historical_review_scope='FINAL_OFFLINE_RECEIPT_INTEGRITY; does not sign this transfer or dispatch')
|
|
exclusive(lab / 'results/runtime-handoff.json', transfer)
|
|
# WHY: this derived gate satisfies the runner with verified transfer data,
|
|
# while the original historical gate and ACCEPTED review are left intact.
|
|
exclusive(lab / 'results/offline-gate.json', dict(status='READY_FOR_NEW_RUNTIME', marker=MARKER,
|
|
fixture_sha256=fixture['sha256'], markers=proof['aggregate']['markers'], blockers=[],
|
|
scope='DERIVED_TRANSFER_OF_ACCEPTED_OFFLINE_EVIDENCE', historical_gate_sha256=sha(OLD_LAB / 'results/offline-gate.json'),
|
|
runtime_handoff_sha256=sha(lab / 'results/runtime-handoff.json')))
|
|
copy_exact(OLD_LAB / 'results/independent-review.json', lab / 'results/independent-review.json')
|
|
exclusive(lab / 'results/preparation.json', dict(status='PASS', lab=str(lab), fixture=fixture, run_reserved=False))
|
|
return lab
|
|
|
|
|
|
def preflight(lab, authorization, review=True):
|
|
"""Pure checks; review=False is only for offline tests and review preparation.
|
|
|
|
CONTRACT: dispatch always uses review=True. The original authorize() is
|
|
mandatory and runs before any ticket-owned action can be consumed.
|
|
"""
|
|
lab = Path(lab).resolve()
|
|
need(not sys.flags.optimize, 'assertions-required')
|
|
auth = authorize(lab, authorization)
|
|
need(auth['ticket'] == TICKET and auth['registered_lab'] == str(lab), 'current-ticket-and-lab')
|
|
need(lab.stat().st_uid == os.getuid() and stat.S_IMODE(lab.stat().st_mode) == 0o700, 'lab-local-owner-mode')
|
|
regular(REGISTRY, 0o600)
|
|
regular(REGISTRATION, 0o600)
|
|
regular(lab / 'campaign-owner', 0o600)
|
|
need(Path(auth['owner_registry']) == REGISTRY and REGISTRY.read_text().strip() == str(lab) and
|
|
(lab / 'campaign-owner').read_text().strip() == str(REGISTRY), 'exclusive-owner-identity')
|
|
need(not SPENT.exists() and not (lab / 'run').exists() and not (lab / 'run.lock').exists(), 'fresh-unconsumed-run')
|
|
need(not any(stat.S_ISSOCK(p.lstat().st_mode) for p in lab.rglob('*')), 'no-preexisting-sockets')
|
|
need(auth['binary'] == str(BINARY) and auth['binary_sha256'] == BINARY_SHA and
|
|
auth['wlroots_sha256'] == WLROOTS_SHA and auth['uid'] == os.getuid(), 'authorized-identities')
|
|
need(sha(BINARY) == sha(lab / 'swayfx-mosaic') == BINARY_SHA and sha(WLROOTS) == WLROOTS_SHA, 'actual-binary-wlroots')
|
|
regular(lab / 'swayfx-mosaic', 0o755)
|
|
fixture = verify(lab / 'results/fixture-freeze-sha256.txt')
|
|
registration = read(REGISTRATION)
|
|
expected = dict(ticket=TICKET, lab=str(lab), uid=os.getuid(), owner_registry=str(REGISTRY), binary=str(BINARY),
|
|
binary_sha256=BINARY_SHA, wlroots_sha256=WLROOTS_SHA, fixture_sha256=fixture['sha256'], non_reusable=True)
|
|
need(registration == read(lab / 'results/registration.json'), 'original-registration-copy')
|
|
if AMENDMENT.exists():
|
|
# WHY: a rejected preparation candidate is never silently accepted as
|
|
# final. CONTRACT: preserve original registration and append exactly one
|
|
# pre-runtime amendment; ownership and all budget counters are retained.
|
|
regular(AMENDMENT, 0o600)
|
|
amendment = read(AMENDMENT)
|
|
original_identity = {k: v for k, v in registration.items() if k != 'fixture_sha256'}
|
|
need(original_identity == {k: v for k, v in expected.items() if k != 'fixture_sha256'}, 'amendment-identity-unchanged')
|
|
need(amendment == dict(registration=expected, previous_registration_sha256=sha(REGISTRATION),
|
|
superseded_fixture_sha256=registration['fixture_sha256'], reason='pre-runtime test exception-class correction',
|
|
runtime_reserved=False, budget_reset=False), 'append-only-registration-amendment')
|
|
need(read(lab / 'results/registration-amendment.json') == amendment, 'amendment-copy')
|
|
else:
|
|
need(registration == expected, 'durable-registration-bindings')
|
|
need(auth['fixture_sha256'] == fixture['sha256'], 'authorization-fixture-binding')
|
|
proof = accepted()
|
|
need(read(lab / 'results/accepted-offline-provenance.json') == proof, 'accepted-offline-provenance')
|
|
handoff = read(lab / 'results/runtime-handoff.json')
|
|
need(handoff['ticket'] == TICKET and handoff['destination_lab'] == str(lab) and handoff['fixture'] == fixture and
|
|
handoff['registration_sha256'] == sha(REGISTRATION) and handoff['authorization_sha256'] == sha(authorization) and
|
|
handoff['provenance_sha256'] == sha(lab / 'results/accepted-offline-provenance.json'), 'handoff-bindings')
|
|
if AMENDMENT.exists():
|
|
need(handoff['registration_amendment_sha256'] == sha(AMENDMENT) and
|
|
auth['registration_amendment_sha256'] == sha(AMENDMENT), 'explicit-amendment-binding')
|
|
for item in handoff['transfers']:
|
|
need(sha(item['source']) == sha(item['destination']) == item['sha256'], 'exact-transfer: ' + item['source'])
|
|
gate = read(lab / 'results/offline-gate.json')
|
|
need(gate == dict(status='READY_FOR_NEW_RUNTIME', marker=MARKER, fixture_sha256=fixture['sha256'],
|
|
markers=proof['aggregate']['markers'], blockers=[], scope='DERIVED_TRANSFER_OF_ACCEPTED_OFFLINE_EVIDENCE',
|
|
historical_gate_sha256=sha(OLD_LAB / 'results/offline-gate.json'),
|
|
runtime_handoff_sha256=sha(lab / 'results/runtime-handoff.json')), 'derived-gate-bindings')
|
|
need(sha(lab / 'results/independent-review.json') == REVIEW_SHA, 'historical-review-copy')
|
|
need(read(lab / 'results/binary-identity.json')['status'] == 'PASS', 'binary-receipt')
|
|
clients = read(lab / 'results/client-provenance.json')
|
|
for item in clients['runtime_installation']:
|
|
name = Path(item['destination']).name
|
|
need(name in ('nested-client', 'pointer-daemon', 'static-reference') and
|
|
item['destination'] == str(lab / name) and item['source'] == str(lab / 'client-build' / name), 'client-installation-path')
|
|
regular(lab / name, 0o755)
|
|
need(sha(item['source']) == sha(item['destination']) == item['sha256'], 'client-installation-bytes')
|
|
need({Path(i['destination']).name for i in clients['runtime_installation']} ==
|
|
{'nested-client', 'pointer-daemon', 'static-reference'}, 'all-runtime-clients')
|
|
budget = valid(read(lab / 'results/budget.json'))
|
|
need(not any(budget['used'].values()) and not budget['events'], 'unused-ledger-required')
|
|
if review:
|
|
checks = read(lab / 'results/preflight-tests.json')
|
|
need(checks['status'] == 'PASS' and checks['tests'] >= 13 and checks['source_sha256'] == sha(HERE / 'tests_offline.py'), 'negative-preflight-tests')
|
|
reviewed = read(lab / 'results/handoff-review.json')
|
|
need(reviewed['status'] == 'ACCEPTED' and reviewed['independent'] is True and reviewed['read_only'] is True and
|
|
reviewed['scope'] == 'STATIC_NESTED_RUNTIME_HANDOFF' and reviewed['fixture_sha256'] == fixture['sha256'] and
|
|
reviewed['runtime_handoff_sha256'] == sha(lab / 'results/runtime-handoff.json') and
|
|
reviewed['authorization_sha256'] == sha(authorization) and reviewed['registration_sha256'] == sha(REGISTRATION), 'independent-handoff-acceptance')
|
|
need(reviewed['source_sha256'] == {p.name: sha(p) for p in HERE.iterdir() if p.is_file()}, 'reviewed-handoff-sources')
|
|
if AMENDMENT.exists():
|
|
need(reviewed['registration_amendment_sha256'] == sha(AMENDMENT), 'reviewed-registration-amendment')
|
|
need(sha(reviewed['report_file']) == reviewed['report_sha256'] and not reviewed['blockers'], 'handoff-review-report')
|
|
return dict(status='PASS', marker='STATIC_NESTED_RUNTIME_PREFLIGHT=PASS', fixture=fixture,
|
|
authorization='PASS', handoff='PASS' if review else 'PENDING_INDEPENDENT_REVIEW', run_reserved=False,
|
|
budget=budget, ticket=TICKET, lab=str(lab))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
import argparse
|
|
p = argparse.ArgumentParser()
|
|
p.add_argument('action', choices=('prepare', 'preflight'))
|
|
p.add_argument('lab', nargs='?', type=Path)
|
|
a = p.parse_args()
|
|
if a.action == 'prepare':
|
|
prepare()
|
|
else:
|
|
result = preflight(a.lab, a.lab / 'results/authorization.json')
|
|
exclusive(a.lab / 'results/preflight.json', result)
|
|
print(result['marker'])
|