swayfx-mosaic/tooling/probes/step20c-overnight/release_shape/gate.py
fy59 30dc3182ab docs(mosaic): record partial STEP20C overnight evidence
Record the annotated rebuild, bounded graphics matrices, private release-shape smoke, and remaining integration gates. Preserve STEP20C NEXT and production multi-output NOT YET SUPPORTED.
2026-10-14 01:47:19 +02:00

333 lines
20 KiB
Python

"""Fail-closed, one-shot gate for the private G release-shape smoke."""
import fcntl
from collections import Counter
import hashlib
import json
import os
from pathlib import Path
import stat
import subprocess
import sys
import time
HERE = Path(__file__).resolve().parent
REPO = HERE.parents[3]
OVERNIGHT = REPO / 'tooling/probes/step20c-overnight'
BASE = REPO / 'tooling/probes/step20c-static-e2-completion'
LAB = Path('/home/fy59/swayfx-mosaic-overnight-mcolx0gq')
GROOT = LAB / 'release-shape'
WORK = GROOT / 'smoke'
CASE = 'G-smoke'
BINARY = GROOT / 'stage-static/usr/bin/swayfx-mosaic'
BINARY_SHA = '8063478b2d5948da3eb8b1a329978882dc1d045cf36932cc2145a78af20ba76a'
BUILD_ID = '83896fdb36f3fd21522becec0e8c5219e15f4b8e'
WLROOTS = Path('/usr/lib/libwlroots-0.20.so').resolve()
WLROOTS_SHA = '7edca9a7d65855addda68e29ba5db94d57c803eb5fa7d0f86db3136b90ca9837'
PATCH_SHA = '6e8a5b50149732ac70ecd79ab8db9258956f59036aba1bf17a477866bc44a5c1'
REVIEW = LAB / 'reviews/release-patch-review.json'
REVIEW_SHA = '29e7ea8b9124fae59a231ff6abcab3981e1ac9c697da51c05c334e888970897a'
BUILD_JSON = GROOT / 'results/phase-g-build.json'
PLANS = dict(chain=2, grab=4, click=8, capture=17, camera=2, transition=1, move=10)
# Contract: the move budget counts pointer relocations, including the eight
# click injections; the event trace therefore has only two standalone `move`
# actions plus eight `left`/`right` click actions.
# The three Mosaic source files whose G runtime traces bind the smoke.
TRACE_SOURCES = {
str(GROOT / 'mosaic-source/sway/desktop/output.c'): 'f703fab0828ee886ce690acfa727a1f70d0dad5f9ec1f5c153db646c73d10485',
str(GROOT / 'mosaic-source/sway/desktop/transaction.c'): '1bb30b3f171f680f8b2e6ed28aa7aad0a6854d8d77360ac8b8e314918c0982f2',
str(GROOT / 'mosaic-source/sway/desktop/xdg_shell.c'): '123313e80fbf5d55a8e9bf2edca5a0f15a7601643a5af2904da6ba9b3914d29f',
}
def sha(path):
return hashlib.sha256(Path(path).read_bytes()).hexdigest()
def read(path):
return json.loads(Path(path).read_text())
def require(ok, message):
if not ok:
raise ValueError(message)
def validate_binary_identity(path, digest):
require(Path(path).resolve() == BINARY.resolve() and digest == BINARY_SHA and sha(path) == BINARY_SHA, 'only exact G ELF accepted')
def validate_budget_room(state):
sys.path.insert(0, str(OVERNIGHT))
import ledger
ledger._validate(state)
require(CASE not in state['runs'] and state['used']['run'] < ledger.LIMITS['run'], 'run quota/unique id')
require(state['campaign_used']['G'] < ledger.CAMPAIGNS['G'], 'G campaign quota')
require(state['used']['capture'] + PLANS['capture'] <= ledger.LIMITS['capture'], 'global PNG quota')
require(all(state['used'][k] == ledger.LIMITS[k] for k in ('scene_build','mosaic_build','stage')), 'G smoke must not extend exhausted build/stage budget')
def validate_wlroots(path, digest):
require(Path(path).resolve()==WLROOTS and digest==WLROOTS_SHA and sha(path)==WLROOTS_SHA, 'wlroots exact bytes')
def validate_file_map(files):
require(isinstance(files,dict) and bool(files), 'frozen file map missing')
for path,digest in files.items():
require(Path(path).is_file() and sha(path)==digest, 'frozen input changed: '+path)
def validate_one_shot(work):
work=Path(work)
require(not (work/'run').exists() and not (work/'consumed.json').exists(), 'G smoke is one-shot; no retry')
def validate_capture_batch(work, captures):
from PIL import Image
require(len(captures)==17 and len({row.get('file') for row in captures})==17, 'incomplete/duplicate G capture batch')
expected={'H2-reference-1.png','H2-reference-2.png','E1-top.png','E1-A.png','E1-mosaic-H1.png','E1-mosaic-H2.png','E1-dismissed-H2.png','E2-top.png','E2-A.png','E2-mosaic-H1.png','E2-mosaic-H2.png','standard-H1.png','standard-H2.png','standard-A-H1.png','standard-A-H2.png','final-H2.png','final-H1.png'}
require({row.get('file') for row in captures}==expected, 'G capture frame set incomplete')
for row in captures:
path=Path(work)/'run'/row['file']
require(path.is_file() and sha(path)==row['sha256'] and row.get('exit')==0, 'G capture bytes changed')
with Image.open(path) as image: require(image.size==(800,600), 'G capture frame dimensions')
def validate_window_results(work):
names=('grab-serials.json','xdg-protocol-order.json','popup-root-geometry.json','popup-nested-geometry.json','applied-clips.json','mosaic-pixels.json','standard-return.json','standard-popup-input.json','final-restoration.json','cleanup.json','terminal-exit.json')
missing=[name for name in names if not (Path(work)/'results'/name).is_file()]
require(not missing, 'G window/lifecycle evidence incomplete: '+','.join(missing))
def durable(path, data):
path = Path(path)
tmp = path.with_name('.' + path.name + '.tmp-' + str(os.getpid()))
fd = os.open(tmp, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600)
try:
with os.fdopen(fd, 'w') as stream:
json.dump(data, stream, indent=2); stream.write('\n'); stream.flush(); os.fsync(stream.fileno())
os.replace(tmp, path)
dfd = os.open(path.parent, os.O_RDONLY | os.O_DIRECTORY)
try: os.fsync(dfd)
finally: os.close(dfd)
finally:
if tmp.exists(): tmp.unlink()
def exclusive(path, data):
fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600)
with os.fdopen(fd, 'w') as stream:
json.dump(data, stream, indent=2); stream.write('\n'); stream.flush(); os.fsync(stream.fileno())
dfd = os.open(path.parent, os.O_RDONLY | os.O_DIRECTORY)
try: os.fsync(dfd)
finally: os.close(dfd)
def source_bindings(auth):
rows = auth['trace_sources']
require(rows == TRACE_SOURCES, 'G source binding set changed')
require(all(sha(path) == digest for path, digest in rows.items()), 'G source hash changed')
return {Path(path).name: digest for path, digest in rows.items()}
def _check_build_identity():
build = read(BUILD_JSON)
require(build['status'] == 'BUILDABLE', 'G build receipt status')
require(build['source']['accepted_review'] == 'ACCEPTED', 'G accepted patch review')
require(build['source']['patch_sha256'] == PATCH_SHA, 'G patch identity')
require(build['source']['review_json_sha256'] == REVIEW_SHA, 'G review receipt binding')
require(build['source']['release_manifest_sha256'] == sha(GROOT/'release-manifest.json'), 'G release manifest receipt binding')
manifest=read(GROOT/'release-manifest.json')
require(manifest['patch_sha256']==PATCH_SHA and manifest['result_source_hashes'], 'G release manifest identity')
for rel,digest in manifest['result_source_hashes'].items(): require(sha(GROOT/rel)==digest, 'G release source changed: '+rel)
require(build['elf']['sha256'] == BINARY_SHA and build['elf']['build_id'] == BUILD_ID, 'G ELF receipt identity')
require(Path(build['elf']['path']).resolve() == BINARY.resolve(), 'G stage path identity')
require(build['headless_runtime'] == 'NOT RUN' and build['graphics'] == 'NOT TESTED', 'no inherited G runtime result')
validate_binary_identity(BINARY, BINARY_SHA)
readelf = subprocess.run(['readelf', '-n', str(BINARY)], capture_output=True, text=True, timeout=5)
require(readelf.returncode == 0 and BUILD_ID in readelf.stdout, 'G ELF build ID bytes')
validate_wlroots(WLROOTS, WLROOTS_SHA)
review = read(REVIEW)
require(sha(REVIEW) == REVIEW_SHA and review['status'] == 'ACCEPTED' and review['independent'] is True, 'accepted exact G patch review')
require(review['patch_sha256'] == PATCH_SHA, 'reviewed G patch digest')
require(sha(review['report_file']) == review['report_sha256'] == '52e0db6963c44d6e17eab92404c9a882ca03a0bbae9d59ff15c43bdfb0aab0b8', 'accepted patch review report bytes')
plan=read(LAB/'reviews/release-plan-review.json')
require(plan.get('status')=='ACCEPTED_PLAN_ONLY' and plan.get('plan_sha256')=='b64201aa3f6a51c385ca8f6934ad20c07ea3853b5a08766e2ae174f5da229547', 'accepted plan binding')
return build
def _frozen_files():
freeze = read(WORK / 'freeze.json')
require(freeze.get('status') == 'READY_FOR_REVIEW' and isinstance(freeze.get('files'), dict), 'G smoke freeze missing')
files = freeze['files']
require(files == expected_files(), 'G freeze file set changed')
validate_file_map(files)
return freeze
def expected_files():
"""Complete inputs: C2 closure plus G sources/build/patch and fixture code."""
c2 = read(LAB / 'horizontal/freeze.json')
rows = dict(c2['files'])
# Reused runtime modules and their sealed generator/input closure.
for path in [HERE/'adapter.py', HERE/'gate.py', HERE/'dispatch.py', HERE/'tests.py',
HERE/'README.md', HERE/'__init__.py',
OVERNIGHT/'horizontal_adapter.py', OVERNIGHT/'horizontal_gate.py',
OVERNIGHT/'ledger.py', BASE/'run.py', BASE/'client-contract.json',
*(BASE/n for n in ('model.py','observations.py','ipc_predicates.py','pixels.py','predicates.py','lifecycle.py','transition.py','support.py')),
WORK/'runner.py', WORK/'results/prepare-tests.json']:
rows[str(path)] = sha(path)
# Bind every source input of the G SceneFX and Mosaic trees.
for root in (GROOT/'scene-source', GROOT/'mosaic-source'):
for path in root.rglob('*'):
if path.is_file() and '.git' not in path.parts:
rows[str(path)] = sha(path)
fixed = [
REVIEW, LAB/'reviews/release-patch-review.md',
GROOT/'release-removal.patch', GROOT/'release-manifest.json',
LAB/'parent/release-plan.json', LAB/'reviews/release-plan-review.json',
LAB/'reviews/release-plan-review.md', BUILD_JSON, GROOT/'REPORT-BUILD.md',
GROOT/'results/static-stage-manifest.json', GROOT/'results/binary-elf.json',
GROOT/'raw/phase-g-commands.json',
GROOT/'scene-build/meson-info/intro-buildoptions.json',
GROOT/'mosaic-build/meson-info/intro-buildoptions.json',
GROOT/'mosaic-build/meson-info/intro-targets.json',
LAB/'horizontal/identity.json', LAB/'horizontal/compile.json',
LAB/'horizontal/protocol-scanner.json', LAB/'horizontal/offline-tests-repair-v1.json',
LAB/'horizontal/C2/runner.py',
Path('/usr/lib/libwlroots-0.20.so').resolve(), BINARY,
]
# The offline completion-predicate test consumes this immutable, completed
# C2 evidence. Freeze the actual events and the records that validate them.
c2work=LAB/'horizontal/C2'
c2_evidence=[c2work/'actions.json',c2work/'run/events.jsonl',
c2work/'results/captures.json',c2work/'results/clicks.json',c2work/'results/runtime.json']
c2_evidence.extend(c2work/'results'/name for name in (
'grab-serials.json','xdg-protocol-order.json','popup-root-geometry.json',
'popup-nested-geometry.json','applied-clips.json','mosaic-pixels.json',
'standard-return.json','standard-popup-input.json','final-restoration.json',
'cleanup.json','terminal-exit.json'))
c2_evidence.extend(c2work/'run'/name for name in (
'H2-reference-1.png','H2-reference-2.png','E1-top.png','E1-A.png',
'E1-mosaic-H1.png','E1-mosaic-H2.png','E1-dismissed-H2.png','E2-top.png',
'E2-A.png','E2-mosaic-H1.png','E2-mosaic-H2.png','standard-H1.png',
'standard-H2.png','standard-A-H1.png','standard-A-H2.png','final-H2.png','final-H1.png'))
fixed.extend(c2_evidence)
fixed.extend(path for info in (GROOT/'scene-build/meson-info',GROOT/'mosaic-build/meson-info') for path in info.iterdir() if path.is_file())
for path in fixed:
require(path.is_file(), 'required G freeze input missing: ' + str(path))
rows[str(path)] = sha(path)
# Complete DESTDIR identity, including the four separate future notices.
for path in (GROOT/'stage-static').rglob('*'):
if path.is_file(): rows[str(path)] = sha(path)
# Exact C17 fixture binaries and generated protocol sources are also present through C2 closure.
return dict(sorted(rows.items()))
def verify_review():
freeze = _frozen_files()
freeze_sha = sha(WORK/'freeze.json')
review_path = GROOT/'reviews/release-smoke-review.json'
require(review_path.is_file(), 'independent G smoke byte review missing')
review = read(review_path)
require(review.get('status') == 'ACCEPTED' and review.get('scope') == 'OVERNIGHT_G_SMOKE_FINAL_BYTES', 'G smoke review scope/status')
require(review.get('independent') is True and review.get('read_only') is True and review.get('blockers') == [], 'G smoke review independence/blockers')
require(review.get('freeze_sha256') == freeze_sha and review.get('files') == freeze['files'], 'G smoke review bindings')
require(Path(review['report_file']).is_file() and sha(review['report_file']) == review['report_sha256'], 'G smoke report bytes')
return review
def preflight(require_review=True):
require(sys.flags.optimize == 0, 'assertions disabled')
require(LAB.is_dir() and LAB.stat().st_uid == os.getuid() and stat.S_IMODE(LAB.stat().st_mode) == 0o700, 'registered owned LAB mode')
require(WORK.is_dir() and WORK.stat().st_uid == os.getuid(), 'G smoke workspace owner')
validate_one_shot(WORK)
require((WORK/'results').is_dir(), 'G results directory')
require((WORK/'results/clients').is_symlink() and (WORK/'results/clients').resolve()==(LAB/'horizontal/clients').resolve(), 'results client link changed')
for name in ('nested-client','pointer-daemon','static-reference'):
p=WORK/name
require(p.is_symlink() and p.resolve()==(LAB/'horizontal/clients'/name).resolve(), 'C2 client symlink changed: '+name)
for proc in Path('/proc').iterdir():
if proc.name.isdigit():
try: exe=(proc/'exe').resolve(strict=True)
except (FileNotFoundError, PermissionError, ProcessLookupError): continue
require(exe != BINARY.resolve(), 'G binary already running')
build = _check_build_identity()
sys.path.insert(0, str(OVERNIGHT)); import ledger
validate_budget_room(read(LAB/'budget.json'))
freeze = _frozen_files()
# Refuse inherited A bytes and any other ELF even if it is executable.
require(freeze['files'].get(str(BINARY)) == BINARY_SHA, 'G ELF absent from freeze')
require(all(sha(path)==digest for path,digest in TRACE_SOURCES.items()), 'G runtime sources changed')
from adapter import generate_g
require((WORK/'runner.py').read_text() == generate_g(), 'generated G C2 runner changed')
if require_review: verify_review()
return dict(binary=str(BINARY), binary_sha256=BINARY_SHA, build_id=BUILD_ID,
wlroots_sha256=WLROOTS_SHA, trace_sources=TRACE_SOURCES,
pristine=dict(path=str(LAB/'stage-static/usr/bin/swayfx-mosaic'), sha256='979dcd95ab43fa1e465d6e9d262485b73d186cedacd0f957059dd24f482ce6f4'))
def begin_run():
auth = preflight(require_review=True)
token = WORK/'consumed.json'
exclusive(token, dict(case=CASE, freeze_sha256=sha(WORK/'freeze.json'), consumed_at_ns=time.time_ns()))
# The durable unique token comes before the locked global ledger reservation.
sys.path.insert(0, str(OVERNIGHT))
import ledger
ledger.reserve(LAB/'budget.json', 'run', campaign='G', run_id=CASE, duration_s=150)
exclusive(WORK/'actions.json', dict(planned=PLANS, reserved=dict.fromkeys(PLANS,0), events=[]))
return LAB, CASE, WORK, auth
def debit(lab, case, work, *kinds):
require(Path(lab).resolve()==LAB and case==CASE and Path(work).resolve()==WORK, 'G action binding')
require(kinds and all(k in PLANS for k in kinds), 'unknown G action')
lockpath=WORK/'actions.lock'
with lockpath.open('a+') as lock:
fcntl.flock(lock, fcntl.LOCK_EX)
state=read(WORK/'actions.json')
require(state.get('planned')==PLANS and set(state.get('reserved',{}))==set(PLANS), 'G action ledger schema')
require(len(state.get('events',[]))==sum(state['reserved'].values()), 'G action ledger count mismatch')
require(dict(Counter(e.get('kind') for e in state['events']))=={k:v for k,v in state['reserved'].items() if v}, 'G action event/count mismatch')
for i,event in enumerate(state['events']):
require(event.get('sequence')==i and event.get('kind') in PLANS and type(event.get('ns')) is int, 'G action event schema')
require(all(state['reserved'][k]+kinds.count(k)<=PLANS[k] for k in kinds), 'G local action quota')
for kind in kinds:
if kind=='capture':
sys.path.insert(0,str(OVERNIGHT));import ledger
ledger.reserve(LAB/'budget.json','capture',run_id=CASE)
state['reserved'][kind]+=1
state['events'].append(dict(sequence=len(state['events']),kind=kind,ns=time.monotonic_ns()))
durable(WORK/'actions.json',state)
def binding_sources(auth):
require(auth['binary']==str(BINARY) and auth['binary_sha256']==BINARY_SHA, 'G binary source binding')
return source_bindings(auth)
def finish_predicate(state, events, captures, clicks, runtime, capture_ok, window_ok):
"""Evaluate the real completion gate over recorded action and runtime evidence."""
counts=dict(Counter(e.get('kind') for e in state.get('events',[])))
ledger_ok=(state.get('planned')==PLANS and set(state.get('reserved',{}))==set(PLANS)
and counts=={k:v for k,v in state.get('reserved',{}).items() if v}
and all(type(v) is int and 0<=v<=PLANS.get(k,-1) for k,v in state.get('reserved',{}).items()))
event_capture_ok=sum(e.get('kind')=='capture' for e in events)==PLANS['capture']
injections=[e for e in events if e.get('kind')=='injection']
click_actions=[e for e in injections if e.get('action') in ('left','right')]
standalone_moves=[e for e in injections if e.get('action')=='move']
injection_ok=(len(injections)==PLANS['click']+2
and len(click_actions)==PLANS['click']
and len(standalone_moves)==2
and len(click_actions)+len(standalone_moves)==len(injections))
grabs=sum(e.get('grab') is True for e in injections)
complete=(ledger_ok and state.get('reserved')==PLANS and capture_ok and window_ok
and event_capture_ok and injection_ok and grabs==PLANS['grab']
and len(clicks)==PLANS['click'] and runtime.get('status')=='PASS')
return dict(complete=complete, action_ledger_valid=ledger_ok,
event_capture_count_valid=event_capture_ok, injections=len(injections),
click_injections=len(click_actions), standalone_move_injections=len(standalone_moves),
pointer_counts_valid=injection_ok, grabs=grabs)
def finish_run(lab, case, work):
verify_review()
require(Path(lab).resolve()==LAB and case==CASE and Path(work).resolve()==WORK, 'G finish binding')
state=read(WORK/'actions.json')
events_path=WORK/'run/events.jsonl'
events=[json.loads(line) for line in events_path.read_text().splitlines()] if events_path.is_file() else []
captures=read(WORK/'results/captures.json') if (WORK/'results/captures.json').is_file() else []
clicks=read(WORK/'results/clicks.json') if (WORK/'results/clicks.json').is_file() else []
runtime=read(WORK/'results/runtime.json') if (WORK/'results/runtime.json').is_file() else {'status':'INCONCLUSIVE','failure':'runtime result missing'}
try: validate_capture_batch(WORK,captures);capture_ok=True
except Exception: capture_ok=False
try: validate_window_results(WORK);window_ok=True
except Exception: window_ok=False
predicate=finish_predicate(state,events,captures,clicks,runtime,capture_ok,window_ok)
observed=dict(status='PASS' if predicate['complete'] else 'INCONCLUSIVE', runtime=runtime,
planned=PLANS, reserved=state.get('reserved'), action_ledger_valid=predicate['action_ledger_valid'],
captures=len(captures), capture_bytes_valid=capture_ok, event_capture_count_valid=predicate['event_capture_count_valid'],
clicks=len(clicks), injections=predicate['injections'], click_injections=predicate['click_injections'],
standalone_move_injections=predicate['standalone_move_injections'], grabs=predicate['grabs'],
pointer_counts_valid=predicate['pointer_counts_valid'],
window_evidence_complete=window_ok,
measurement_event=any(e.get('kind')=='measurements-completed' and e.get('status')=='PASS' for e in events))
durable(WORK/'finish.json',observed)
return observed