"""Diagnostic-only #302 native phases; receipts retain exact producer exits."""
import importlib.util
import json
from pathlib import Path
import sys
OUT = Path(__file__).resolve().parent
ROOT = OUT.parents[3]
SOURCE = ROOT / '.spt/preserved/310-local-update-docs/windows-validation/run.py'
spec = importlib.util.spec_from_file_location('guard310', SOURCE)
p = importlib.util.module_from_spec(spec)
spec.loader.exec_module(p)
g = p.g
g.OUT = OUT
g.TREE = ROOT / '.worktrees/302-net-worker-stamps'
g.TARGET = g.TREE / 'target'
p.AUTHORIZED += [ROOT / '.worktrees/hertz-309-er-briefing']
SCOPE = ['-p','spt-net','-p','spt-daemon','--lib','--test','netbroker','--test','netstream','--test','pairjoin','--test','net_worker_starve']
EXPR = 'package(spt-net) | (package(spt-daemon) & (test(/^nethost::tests::/) | test(/^pairhost::tests::/) | binary(/^(netbroker|netstream|pairjoin|net_worker_starve)$/)))'
class Phase(g.Phase):
    def prepare(self):
        if g.os.name!='nt':raise RuntimeError('Windows only')
        self.receipt['source_before']=self.source('before')
        self.receipt['free_bytes_start']=g.shutil.disk_usage(g.TREE).free
        self.receipt['processes_before']=g.census()
        g.reparse_guard(g.TARGET)
        foreign=self.env.get('CARGO_TARGET_DIR')
        if foreign and (g.TREE/foreign).resolve()!=g.TARGET.resolve():raise RuntimeError('foreign target override')
        if not self.name.startswith('pool-claim-warm'):
            claim=json.loads((OUT/'pool-claim-warm-3/receipt.json').read_text())
            if claim['status']!='passed' or claim['source_before']['head']!=self.receipt['source_before']['head']:
                raise RuntimeError('successful same-HEAD warm pool claim required')
        base=Path(g.os.environ['LOCALAPPDATA'])/'Temp'
        g.reparse_guard(base)
        if g.inside(base.resolve(),ROOT):raise RuntimeError('private temporary storage must be outside repository')
        private=Path(g.tempfile.mkdtemp(prefix='spt-302-'+self.name+'-',dir=base))
        home=private/'home';tmp=private/'tmp';home.mkdir();tmp.mkdir()
        overlay=dict(CARGO_TARGET_DIR=str(g.TARGET),CARGO_BUILD_JOBS='2',NEXTEST_PROFILE='ci-windows',SPT_HOME=str(home),TEMP=str(tmp),TMP=str(tmp),SPT_INSTALL_NO_FIREWALL='1',SPT_TEST_EPHEMERAL_ADVISORY_PORTS='1')
        self.env.update(overlay);self.receipt['environment_allowlist']=overlay
        self.receipt['warm_pool_authorization']='FVREIGYT: released gate-229 pool moved intact into 302'
        self.receipt['driver_sha256']=g.digest(Path(__file__))
        self.flush()
    def execute(self):
        if self.name == 'pool-claim':
            self.run('claim',['cargo','run','-p','xtask','--','pool-claim','--pool',str(g.TARGET),'--label','todlando-302-step1'])
        elif self.name.startswith('pool-claim-warm'):
            helper=g.TARGET/'debug/xtask.exe'
            self.receipt['prebuilt_xtask_sha256']=g.digest(helper)
            self.run('claim',[str(helper),'pool-claim','--pool','target','--label','todlando-302-step1'],timeout=120)
        elif self.name.startswith('build'):
            self.run('build',['cargo','build','--locked','-p','spt','--bin','spt','-p','spt-daemon','--example','net_diag_smoke','-p','xtask','--bin','xtask'])
        elif self.name.startswith('seams'):
            self.inventory('inventory',SCOPE,EXPR)
            self.run('tests',['cargo','nextest','run','--locked',*SCOPE,'--profile','ci-windows','--test-threads','2','--retries','0','--no-fail-fast','-E',EXPR])
        elif self.name.startswith('stack-unit'):
            scope=['-p','spt-net-diag','--lib']
            expression='test(stacks::windows::tests::)'
            self.inventory('inventory',scope,expression)
            self.run('tests',['cargo','nextest','run','--locked',*scope,'--profile','ci-windows','--test-threads','2','--retries','0','--no-fail-fast','-E',expression])
        elif self.name.startswith(('smoke','forced')):
            self.env['SPT_RC_HITCH_DIAG']='1'
            self.receipt['environment_allowlist']['SPT_RC_HITCH_DIAG']='1'
            symbols=ROOT/'.spt/preserved/302/symbols/flat'
            g.reparse_guard(symbols)
            if not symbols.is_dir():raise RuntimeError('prepared local symbol directory missing')
            self.env['SPT_NET_DIAG_SYMBOL_DIR']=str(symbols)
            self.receipt['environment_allowlist']['SPT_NET_DIAG_SYMBOL_DIR']=str(symbols)
            symbol_receipt=ROOT/'.spt/preserved/302/symbols/preparation-receipt.json'
            prepared=json.loads(symbol_receipt.read_text())
            for module in prepared['modules']:
                if not module['identity_match'] or g.digest(Path(module['flat_path']))!=module['pdb_sha256']:
                    raise RuntimeError('prepared symbol identity/hash mismatch')
            self.receipt['symbol_receipt_sha256']=g.digest(symbol_receipt)
            amendment=ROOT/'.spt/preserved/302/raw-copy-resume-v1'
            analyzer=amendment/'step1-analyze.py'
            delivery=amendment/'DELIVERY.json'
            if g.digest(delivery)!='bd7f8f3e16e63b6f8c5a1bd7fa12daa89a29544d2b5b58c6b6bbcfe2aa2c0538' or g.digest(analyzer)!='6f36563c6882aed3d4d14f5b276531911d844f6c629b41963378f883a4e9ab24':
                raise RuntimeError('amended analyzer delivery pin mismatch')
            analyzer_inputs=[analyzer,amendment/'step1_rounds.py',delivery,ROOT/'.spt/preserved/302/STEP1-DRIVER.md',OUT/'verify.py']
            self.receipt['analyzer_inputs']={str(path):g.digest(path) for path in analyzer_inputs}
            binary=g.TARGET/'debug/examples/net_diag_smoke.exe'
            self.receipt['binary_sha256']=g.digest(binary)
            args=['freeze'] if self.name.startswith('forced') else []
            self.run('native',[str(binary),*args],timeout=120)
            self.run('analyze',[sys.executable,str(analyzer),'--samples',str(self.dir/'native.stdout'),'--log',str(self.dir/'native.stderr'),'--out',str(self.dir/'analysis')],expected=2,timeout=30)
            self.run('verify',[sys.executable,str(OUT/'verify.py'),str(self.dir)],timeout=30)
            if any(g.digest(Path(path))!=digest for path,digest in self.receipt['analyzer_inputs'].items()):
                raise RuntimeError('analyzer inputs changed during native proof')
        elif self.name.startswith('checks'):
            self.run('clippy',['cargo','clippy','--workspace','--locked'])
            self.run('trace',['traceable-reqs','check','--json'],timeout=600)
        elif self.name == 'pool-release':
            self.run('release',[str(g.TARGET/'debug/xtask.exe'),'pool-release','--pool',str(g.TARGET)],timeout=60)
        else:
            raise RuntimeError('unknown phase')
def main():
    phase=Phase(sys.argv[1]); code=1
    try:
        phase.prepare(); phase.execute(); phase.receipt['status']='passed'; code=0
    except BaseException as error:
        phase.receipt.update(status='refused-or-failed',error=repr(error))
    finally:
        try:
            phase.receipt['source_after']=phase.source('after')
            before=json.loads(Path(phase.receipt['source_before']['source_map']).read_text())
            after=json.loads(Path(phase.receipt['source_after']['source_map']).read_text())
            changed=[key for key in before.keys() | after.keys() if before.get(key)!=after.get(key)]
            phase.receipt['source_changes']=changed
            permitted=['Cargo.lock'] if phase.name=='pool-claim' else []
            if any(key not in permitted for key in changed): raise RuntimeError('unexpected source changes during phase: '+repr(changed))
            phase.receipt['processes_after']=g.census()
            phase.receipt['free_bytes_end']=g.shutil.disk_usage(g.TREE).free
            phase.receipt['target_bytes_end']=g.target_bytes()
        except BaseException as error:
            phase.receipt.update(status='refused-or-failed',finalization_error=repr(error)); code=1
        phase.receipt.update(end=g.now(),driver_exit=code); phase.flush()
        print(phase.dir/'receipt.json')
    return code
if __name__=='__main__': sys.exit(main())
