import json, sqlite3, statistics, hashlib
from pathlib import Path
from datetime import datetime, timezone
root=Path('/home/ubuntu/wt/issue-21578'); source=Path('/tmp/issue-21578-lead'); out=Path('/tmp/issue-21578-evidence')
raw=(root/'.t3/userdata/logs/server.trace.ndjson').read_bytes()
rows=[json.loads(l) for l in raw.splitlines() if l.strip()]
before_ids=json.loads((source/'before-span-ids.json').read_text()); after_input=json.loads((source/'after-spans.json').read_text())
allowed=('name','spanId','traceId','startTimeUnixNano','durationMs')
def select(ids):
 result=[]
 for sid in ids:
  matches=[r for r in rows if r.get('spanId')==sid]
  assert len(matches)==1, (sid,len(matches))
  r=matches[0]; assert r['name']=='ProjectionStore.getRuntimeRecoveryProjection'
  r={k:r[k] for k in allowed}
  ns=int(r['startTimeUnixNano']); sec,nano=divmod(ns,10**9)
  r['startTimeUtc']=datetime.fromtimestamp(sec,timezone.utc).strftime('%Y-%m-%dT%H:%M:%S')+f'.{nano:09d}Z'
  result.append(r)
 return result
all_before=select(before_ids); before=[r for r in all_before if r['traceId']==all_before[0]['traceId']]; excluded=[r for r in all_before if r not in before]
after=select([r['spanId'] for r in after_input]); assert len(before)==len(after)==8
assert [r['durationMs'] for r in before]==[r['durationMs'] for r in json.loads((source/'runtime-before.json').read_text())['spans'] if r['name']=='ProjectionStore.getRuntimeRecoveryProjection']
for a,b in zip(after,after_input):
 for k in allowed: assert a[k]==b[k]
def summary(rs):
 d=[r['durationMs'] for r in rs];return dict(count=len(d),minMs=min(d),maxMs=max(d),meanMs=statistics.mean(d),medianMs=statistics.median(d),sumMs=sum(d))
c=sqlite3.connect(f'file:{root}/.t3/userdata/statev2.sqlite?mode=ro',uri=True)
m=c.execute('select migration_id,name from effect_sql_migrations where migration_id=68').fetchall(); assert m==[(68,'UnfinishedTurnItemsIndex')]
counts=c.execute("select type,status,count(*),count(distinct thread_id) from orchestration_v2_projection_turn_items where turn_item_id glob 'fixture-21578-item-*' group by type,status").fetchall();assert counts==[('command_execution','completed',17517,1)]
index=c.execute("select sql from sqlite_master where name='orchestration_v2_projection_turn_items_unfinished_idx'").fetchone()[0]
evidence=dict(base='8d3b061121fe65669d5d43d011b48cd3027a4108',head='521eeb4349f8795bdbede9a5af73d1e79556f1fd',source=dict(path='.t3/userdata/logs/server.trace.ndjson',snapshotSha256=hashlib.sha256(raw).hexdigest(),snapshotBytes=len(raw),exportPolicy='Explicit span metadata allowlist only; no attributes, events, links, exit, payloads, conversation text, or credentials.'),matrix=dict(entries=[],reason='Server-only change; no render paths, confirmed by lead proof audit pass 1. No captures, recordings, or seeding commit required.'),before=dict(spans=before,summary=summary(before)),after=dict(spans=after,summary=summary(after)),excludedBeforeCandidates=dict(reason='Different trace from the eight baseline recovery spans; absent from baseline runtime summary.',spans=excluded),database=dict(access='SQLite mode=ro',migrationId=68,migrationName='UnfinishedTurnItemsIndex',indexSql=index,fixtureRowCount=17517,fixtureDistinctThreadCount=1,fixtureType='command_execution',fixtureStatus='completed'),provenance=dict(runtime='Existing logs independently reconciled against lead span lists and baseline summary.',interaction='Lead reports successful real-browser Codex GPT-6-Astra turns before and after; conversation content intentionally excluded.',tests='Reported by lead; test output not supplied or rerun in this evidence step.'),tests=dict(implementerGreen=dict(tests=64,files=19),independentReviewGreen=dict(tests=39,files=2),redAssertion='Pending exact implement report; not present in supplied artifacts.'),limitations=['Synthetic completed projection rows are performance fixtures, not event-replay business proof.','Same isolated thread reused; after includes the previous successful user/assistant turn.','Single before/after interaction with eight internal recovery spans each, not eight independent trials.','Timings are indicative and do not establish exact reporter latency or end-to-end speedup.','Commit attribution and interaction outcome are lead-supplied; trace metadata alone does not encode commit identity.'])
(out/'runtime-proof.json').write_text(json.dumps(evidence,indent=2)+'\n')
print(json.dumps({'before':summary(before),'after':summary(after),'excluded':len(excluded)},indent=2))
