sable-sphynx 0.7.0.dev0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. sable_sphynx-0.7.0.dev0.dist-info/METADATA +152 -0
  2. sable_sphynx-0.7.0.dev0.dist-info/RECORD +174 -0
  3. sable_sphynx-0.7.0.dev0.dist-info/WHEEL +5 -0
  4. sable_sphynx-0.7.0.dev0.dist-info/top_level.txt +1 -0
  5. sphynx/__init__.py +469 -0
  6. sphynx/__init__.pyi +376 -0
  7. sphynx/_analytical_client.py +77 -0
  8. sphynx/_analytical_values.py +252 -0
  9. sphynx/_audit_client.py +173 -0
  10. sphynx/_comparison_adapters.py +113 -0
  11. sphynx/_componentized_full_rag.py +34 -0
  12. sphynx/_composed_pipeline.py +109 -0
  13. sphynx/_full_rag_grading.py +218 -0
  14. sphynx/_full_rag_trace.py +221 -0
  15. sphynx/_local_eval.py +268 -0
  16. sphynx/_pipeline_core.py +243 -0
  17. sphynx/_pr_client.py +237 -0
  18. sphynx/_rag_diagnostics_calibration.py +285 -0
  19. sphynx/_rag_diagnostics_catalog.py +39 -0
  20. sphynx/_rag_diagnostics_common.py +26 -0
  21. sphynx/_rag_diagnostics_contracts.py +186 -0
  22. sphynx/_rag_diagnostics_engine.py +243 -0
  23. sphynx/_rag_diagnostics_importers.py +242 -0
  24. sphynx/_rag_diagnostics_ranking.py +81 -0
  25. sphynx/_rag_diagnostics_semantic.py +325 -0
  26. sphynx/_rag_ranking.py +149 -0
  27. sphynx/_speech_benchmark_scoring.py +140 -0
  28. sphynx/_speech_benchmark_sources.py +275 -0
  29. sphynx/_speech_complete_scoring.py +218 -0
  30. sphynx/_speech_complete_source.py +118 -0
  31. sphynx/_speech_kido.py +294 -0
  32. sphynx/_speech_scoring.py +77 -0
  33. sphynx/_track_capture.py +213 -0
  34. sphynx/analytical_views.py +515 -0
  35. sphynx/assessment_contracts.py +378 -0
  36. sphynx/assessment_selection.py +57 -0
  37. sphynx/audit_access.py +245 -0
  38. sphynx/audit_contracts.py +204 -0
  39. sphynx/audit_requests.py +180 -0
  40. sphynx/authoring.py +188 -0
  41. sphynx/benchmarks.py +568 -0
  42. sphynx/candidates.py +62 -0
  43. sphynx/catalog.py +272 -0
  44. sphynx/chunking.py +372 -0
  45. sphynx/clickhouse_reader.py +169 -0
  46. sphynx/client.py +486 -0
  47. sphynx/comparison_core.py +149 -0
  48. sphynx/comparison_views.py +223 -0
  49. sphynx/components.py +1661 -0
  50. sphynx/contracts.py +514 -0
  51. sphynx/customer_rerank.py +299 -0
  52. sphynx/customer_retrieval.py +146 -0
  53. sphynx/data/benchmark-releases.v1.json +164 -0
  54. sphynx/data/public-benchmark-release.v1.json +354 -0
  55. sphynx/data/rag-diagnostics-synthetic.v1.json +6982 -0
  56. sphynx/data/rag-paper-references.v1.json +176 -0
  57. sphynx/data/rag-paper-schemas.v1.json +1069 -0
  58. sphynx/data/rag-rerank-v1.json +507 -0
  59. sphynx/dataset_catalog_data.py +1214 -0
  60. sphynx/dataset_readiness.py +138 -0
  61. sphynx/dataset_v3.py +999 -0
  62. sphynx/demo.py +130 -0
  63. sphynx/demo_candidates/__init__.py +1 -0
  64. sphynx/demo_candidates/dense.py +11 -0
  65. sphynx/demo_candidates/lexical.py +21 -0
  66. sphynx/diagnostics.py +207 -0
  67. sphynx/documentation.py +737 -0
  68. sphynx/documentation_candidate.py +939 -0
  69. sphynx/evals.py +538 -0
  70. sphynx/evaluation_protocols.py +124 -0
  71. sphynx/evaluator_bindings.py +370 -0
  72. sphynx/evidence.py +352 -0
  73. sphynx/execution_capabilities.py +109 -0
  74. sphynx/fast_search.py +285 -0
  75. sphynx/full_rag.py +341 -0
  76. sphynx/hosted/__init__.py +18 -0
  77. sphynx/hosted/benchmark_intake.py +218 -0
  78. sphynx/hosted/contracts.py +361 -0
  79. sphynx/hosted/model_admission.py +130 -0
  80. sphynx/hosted/model_artifacts.py +298 -0
  81. sphynx/hosted/model_contracts.py +63 -0
  82. sphynx/hosted/model_gcs.py +181 -0
  83. sphynx/hosted/model_history.py +84 -0
  84. sphynx/hosted/model_session.py +233 -0
  85. sphynx/hosted/public_suite.py +74 -0
  86. sphynx/hosted/registry.py +97 -0
  87. sphynx/hosted/revisions.py +126 -0
  88. sphynx/hosted/sandbox.py +105 -0
  89. sphynx/hosted/submission.py +287 -0
  90. sphynx/hosted_workflow.py +451 -0
  91. sphynx/http_transport.py +86 -0
  92. sphynx/integrated_rag.py +352 -0
  93. sphynx/integrated_workflow.py +552 -0
  94. sphynx/integration_contracts.py +474 -0
  95. sphynx/invocation.py +791 -0
  96. sphynx/invocation_candidates.py +392 -0
  97. sphynx/invocation_catalog_data.py +40 -0
  98. sphynx/learned_catalog_data.py +30 -0
  99. sphynx/learned_rerank.py +267 -0
  100. sphynx/legacy_full_rag.py +67 -0
  101. sphynx/lifecycle.py +885 -0
  102. sphynx/lifecycle_workflow.py +490 -0
  103. sphynx/metric_views.py +474 -0
  104. sphynx/metrics.py +282 -0
  105. sphynx/model_adapters.py +438 -0
  106. sphynx/model_catalog_data.py +30 -0
  107. sphynx/official_benchmarks.py +385 -0
  108. sphynx/packs.py +258 -0
  109. sphynx/pipeline.py +313 -0
  110. sphynx/pipeline_workflow.py +551 -0
  111. sphynx/pr_evaluations.py +560 -0
  112. sphynx/progress.py +89 -0
  113. sphynx/public_bundle.py +173 -0
  114. sphynx/public_eval.py +62 -0
  115. sphynx/public_rerank.py +355 -0
  116. sphynx/py.typed +0 -0
  117. sphynx/pytest_plugin.py +62 -0
  118. sphynx/question_robustness.py +437 -0
  119. sphynx/rag.py +329 -0
  120. sphynx/rag_diagnostics.py +309 -0
  121. sphynx/rag_papers/__init__.py +29 -0
  122. sphynx/rag_papers/__main__.py +45 -0
  123. sphynx/rag_papers/candidates.py +19 -0
  124. sphynx/rag_papers/contracts.py +282 -0
  125. sphynx/rag_papers/datasets.py +291 -0
  126. sphynx/rag_papers/experiments.py +116 -0
  127. sphynx/rag_papers/judges.py +308 -0
  128. sphynx/rag_papers/registry.py +33 -0
  129. sphynx/rag_papers/runner.py +176 -0
  130. sphynx/rag_papers/sable.py +135 -0
  131. sphynx/rag_papers/schemas.py +12 -0
  132. sphynx/rag_papers/scoring.py +249 -0
  133. sphynx/rag_papers/semantic.py +288 -0
  134. sphynx/rag_papers/trec.py +80 -0
  135. sphynx/recorded_scores.py +352 -0
  136. sphynx/records.py +124 -0
  137. sphynx/reflex_traces.py +193 -0
  138. sphynx/report_rendering.py +71 -0
  139. sphynx/rerank_family_contracts.py +150 -0
  140. sphynx/rerank_workflow.py +424 -0
  141. sphynx/research_candidate.py +190 -0
  142. sphynx/results.py +852 -0
  143. sphynx/retrieval.py +750 -0
  144. sphynx/retrieval_candidates.py +295 -0
  145. sphynx/retrieval_catalog_data.py +19 -0
  146. sphynx/retrieval_recordings.py +125 -0
  147. sphynx/reviewer_audit.py +266 -0
  148. sphynx/routing_policies.py +375 -0
  149. sphynx/runner.py +544 -0
  150. sphynx/sable_rerank.py +489 -0
  151. sphynx/score_reliability.py +393 -0
  152. sphynx/speech_benchmarks.py +204 -0
  153. sphynx/speech_campaigns.py +378 -0
  154. sphynx/speech_complete.py +236 -0
  155. sphynx/speech_contracts.py +225 -0
  156. sphynx/speech_evidence.py +429 -0
  157. sphynx/speech_listening.py +115 -0
  158. sphynx/speech_multilingual.py +71 -0
  159. sphynx/speech_tts.py +479 -0
  160. sphynx/sql/001_sync_v1.sql +30 -0
  161. sphynx/stage_contracts.py +199 -0
  162. sphynx/statistical_catalog_data.py +53 -0
  163. sphynx/statistical_rerank.py +232 -0
  164. sphynx/sync.py +797 -0
  165. sphynx/workbench.py +378 -0
  166. sphynx/workbench_catalog_data.py +13 -0
  167. sphynx/workbench_tracks.py +78 -0
  168. sphynx/workflow_async.py +197 -0
  169. sphynx/workflow_contracts.py +594 -0
  170. sphynx/workflow_decisions.py +211 -0
  171. sphynx/workflow_defaults.py +16 -0
  172. sphynx/workflow_extensions.py +212 -0
  173. sphynx/workflow_registry.py +513 -0
  174. sphynx/workflows.py +792 -0
@@ -0,0 +1,218 @@
1
+ """Remaining native Kido analyses; descriptive cohorts never imply quality."""
2
+ from collections import Counter, defaultdict
3
+ import math
4
+ import re
5
+ import statistics
6
+
7
+ from ._speech_kido import csv_rows, json_value, number, require
8
+ from ._speech_scoring import timing_summary, normalize, score_transcript
9
+ from ._speech_benchmark_sources import DIMENSIONS, synthesis_record
10
+ from ._speech_benchmark_scoring import evaluate_pt_br_listening
11
+ from ._speech_complete_source import GOOGLE
12
+
13
+
14
+ def summary(values, convention='kido.google-linear.v1'):
15
+ require(all(type(x) in (int,float) and math.isfinite(x) and x>=0 for x in values),'Invalid descriptive measurement')
16
+ values=sorted(values);n=len(values)
17
+ if convention=='kido.google-linear.v1':
18
+ def q(p):
19
+ k=(n-1)*p;i=int(k)
20
+ return values[i]+(values[min(i+1,n-1)]-values[i])*(k-i)
21
+ result=dict(n=n,mean=statistics.mean(values) if n else None,p50=q(.5) if n else None,p95=q(.95) if n else None,convention=convention)
22
+ else:result=timing_summary(values,convention=convention)
23
+ mean=result['mean'];sd=statistics.stdev(values) if n>1 else None
24
+ return dict(result,min=min(values) if n else None,max=max(values) if n else None,
25
+ sample_stdev=sd,coefficient_of_variation=sd/mean if sd is not None and mean else None,
26
+ p95_p50_ratio=result['p95']/result['p50'] if result['p50'] else None)
27
+
28
+
29
+ def _groups(rows,key):
30
+ result=defaultdict(list)
31
+ for row in rows:result[key(row)].append(row)
32
+ return sorted(result.items(),key=lambda x:str(x[0]))
33
+
34
+
35
+ def _seconds(row,key):
36
+ return number(row.get(key),key,nullable=True)
37
+
38
+
39
+ def evaluate_google_direct_api(rows):
40
+ """Preserve runs, smoke tests, configs, formats, cancellation and concurrency."""
41
+ results=[]
42
+ for (scope,profile),group in _groups(rows,lambda x:(x['scope'],x['record'].get('model',x['record'].get('cfg')))):
43
+ require(isinstance(profile,str),'Google request lacks profile')
44
+ records=[r['record'] for r in group]
45
+ def measurements(items):
46
+ valid=[r for r in items if r['ok']]
47
+ timings=[_seconds(r,'ttfa')*1000 for r in valid if r.get('ttfa') is not None]
48
+ onset=[(_seconds(r,'ttfa')+_seconds(r,'lead_silence_s'))*1000 for r in valid
49
+ if r.get('ttfa') is not None and r.get('lead_silence_s') is not None]
50
+ return dict(attempts=len(items),ok=sum(r['ok'] for r in items),not_ok=sum(not r['ok'] for r in items),
51
+ ttfa_ms=summary(timings),ttfa_missing=len(valid)-len(timings),
52
+ audible_onset_proxy_ms=summary(onset),audible_onset_missing=len(valid)-len(onset),
53
+ proxy_scope='TTFA plus reported RMS-threshold leading silence; no playback buffer or listener judgment',
54
+ leading_silence_ms=summary([_seconds(r,'lead_silence_s')*1000 for r in valid if r.get('lead_silence_s') is not None]),
55
+ reported_audio_duration_s=summary([_seconds(r,'audio_s') for r in valid if r.get('audio_s') is not None]),
56
+ reported_rtf=summary([_seconds(r,'rtf') for r in valid if r.get('rtf') is not None]),
57
+ stall_seconds=summary([_seconds(r,'stall_s') for r in valid if r.get('stall_s') is not None]),
58
+ gap_max_ms=summary([_seconds(r,'gap_max')*1000 for r in valid if r.get('gap_max') is not None]),
59
+ stall_over_10ms=sum(_seconds(r,'stall_s')>.01 for r in valid if r.get('stall_s') is not None),
60
+ http_status_counts=dict(Counter(str(r.get('status')) for r in items)))
61
+ seq=[r for r in records if r['test']=='seq' or r['test'].startswith('seq:')]
62
+ by_test={test:measurements(g) for test,g in _groups(records,lambda r:r['test'].split(':')[0])}
63
+ by_text={text:measurements(g) for text,g in _groups(seq,lambda r:r.get('text') or r['test'].split(':',1)[1])}
64
+ lang=lambda t:'pt-BR' if t in ('short_pt','long_pt','numbers_pt','mixed_pt_en') else 'en'
65
+ by_language={language:measurements([r for text,items in _groups(seq,lambda r:r.get('text') or r['test'].split(':',1)[1]) if lang(text)==language for r in items]) for language in ('en','pt-BR')}
66
+ cancellation=[]
67
+ for r in records:
68
+ if r['test']!='cancel':continue
69
+ start,end=_seconds(r,'cancelled_at'),_seconds(r,'t_closed')
70
+ cancellation.append(dict(cancel_recorded=start is not None,
71
+ close_after_cancel_ms=(end-start)*1000 if start is not None and end is not None and end>=start else None,
72
+ clock_valid=start is not None and end is not None and end>=start,
73
+ observed_ok=r['ok'],server_stopped_work=None,controlled_microphone_barge_in=None))
74
+ results.append(dict(scope=scope,profile=profile,requests=len(records),sequential=measurements(seq),
75
+ by_test=by_test,by_text=by_text,by_language=by_language,
76
+ by_format={str(fmt):measurements(g) for fmt,g in _groups(records,lambda r:r.get('fmt'))},
77
+ cancellation=cancellation,reported_wav_validation=[{k:r.get(k) for k in
78
+ ('test','fmt','wav_valid','riff_size_ok','data_size_ok','align_ok','byterate_ok','odd_bytes')} for r in records if 'wav_valid' in r or r['test'].startswith('fmt')],
79
+ usage_records=[r['usage'] for r in records if 'usage' in r],cost_usd=None,
80
+ quality_score=None,perceived_latency=None,confidence_interval=None,
81
+ execution_boundary='historical direct HTTP; never pooled with official/live results'))
82
+ return results
83
+
84
+
85
+ def evaluate_pronunciation_proxy(rows):
86
+ require(len({r['id'] for r in rows})==len(rows),'Duplicate pronunciation proxy ID')
87
+ result=[]
88
+ for row in rows:
89
+ require(all(isinstance(row.get(k),str) for k in ('id','category','reference','stt_proxy_transcript')),'Invalid pronunciation proxy')
90
+ result.append(dict(id=row['id'],category=row['category'],reference=row['reference'],
91
+ stt_proxy_transcript=row['stt_proxy_transcript'],
92
+ normalized_text_differs=normalize(row['reference'])!=normalize(row['stt_proxy_transcript']),
93
+ human_review_required=True,human_review_available=False,pronunciation_correct=None,wer_as_pronunciation_quality=None,
94
+ note='TTS and ASR effects are confounded; written numbers differ from spoken forms; no inferred pronunciation score.'))
95
+ return result
96
+
97
+
98
+ def evaluate_isolated_diagnostics(trials):
99
+ """All-calls, first measured call per process/wave, slices and repetition."""
100
+ results=[];convention='kido.isolated-rounded-index.v1'
101
+ for profile,group in _groups(trials,lambda r:r['candidate']):
102
+ require(len({(r['line_id'],r['repetition']) for r in group})==len(group),'Duplicate isolated trial')
103
+ good=[r for r in group if r['success']=='true']
104
+ def timing(rows):return summary([float(r['ttfa_ms']) for r in rows if r['success']=='true' and r['ttfa_ms']!=''],convention)
105
+ first=[g[0] for _,g in _groups(group,lambda r:r['repetition'])]
106
+ first_ids={id(r) for r in first};steady=[r for r in group if id(r) not in first_ids]
107
+ all_calls=timing(group);first_stat=timing(first);steady_stat=timing(steady)
108
+ threshold=all_calls['mean']+3*all_calls['sample_stdev'] if all_calls['sample_stdev'] is not None else None
109
+ tags=sorted({t for r in group for t in re.split('[|,]',r['tags']) if t})
110
+ results.append(dict(profile=profile,provider=group[0]['provider'],model=group[0]['model'],
111
+ attempts=len(group),successful=len(good),failed=len(group)-len(good),all_calls_ms=all_calls,
112
+ request_completion_ms=summary([float(r['duration_ms']) for r in good if r['duration_ms']!=''],convention),
113
+ request_completion_is_playback_duration=False,
114
+ first_measured_call_ms=first_stat,steady_state_ms=steady_stat,
115
+ first_call_delta_mean_ms=first_stat['mean']-steady_stat['mean'] if first_stat['mean'] is not None and steady_stat['mean'] is not None else None,
116
+ first_call_lines=[r['line_id'] for r in first],schedule_confound='First call is confounded with its scheduled corpus text; not an isolated causal cold-start effect.',
117
+ by_repetition={str(rep):timing(g) for rep,g in _groups(group,lambda r:r['repetition'])},
118
+ by_content_slice={tag:timing([r for r in group if tag in re.split('[|,]',r['tags'])]) for tag in tags},
119
+ outlier_threshold_ms=threshold,outlier_rule='strictly above sample mean + 3 sample standard deviations',
120
+ outliers=[dict(line_id=r['line_id'],repetition=r['repetition'],ttfa_ms=float(r['ttfa_ms'])) for r in good if threshold is not None and float(r['ttfa_ms'])>threshold],
121
+ confidence_interval=None,perceived_latency=None))
122
+ return results
123
+
124
+
125
+ def evaluate_listening_diagnostics(observations,references,trials):
126
+ refs={r['id']:r for r in references};tag_map={r['line_id']:re.split('[|,]',r['tags']) for r in trials}
127
+ result=[]
128
+ for (cohort,model),group in _groups([r for r in observations if r['track']=='tts_listening'],lambda r:(r['cohort'],r['model'])):
129
+ def ratings(rows):
130
+ values=[refs[r['id']]['overall_quality'] for r in rows];present=[v for v in values if v is not None]
131
+ return dict(expected=len(values),rated=len(present),pending=len(values)-len(present),
132
+ mean=statistics.mean(present) if present and len(present)==len(values) else None,
133
+ observed_subset_mean=statistics.mean(present) if present else None,
134
+ sample_stdev=statistics.stdev(present) if len(present)>1 else None,
135
+ distribution={str(v):present.count(v) for v in range(1,6)})
136
+ tags=sorted({t for r in group for t in tag_map.get(r['case_id'],[])})
137
+ result.append(dict(cohort=cohort,model=model,overall=ratings(group),
138
+ by_content_slice={tag:ratings([r for r in group if tag in tag_map.get(r['case_id'],[])]) for tag in tags},
139
+ historical_raters=1,confidence_interval=None,subrating_dimensions_available=False))
140
+ return result
141
+
142
+
143
+ def evaluate_pt_br_categories(rows):
144
+ rated=[r for r in rows if all(r['ratings'][d] is not None for d in DIMENSIONS)]
145
+ return dict(by_category={category:evaluate_pt_br_listening(g) for category,g in _groups(rows,lambda r:r['category'])},
146
+ complete_rating_vectors=len(rated),identical_four_dimension_vectors=sum(len(set(r['ratings'].values()))==1 for r in rated),
147
+ independent_dimension_judgments_established=False,historical_raters=1)
148
+
149
+
150
+ def evaluate_official_turns(sessions):
151
+ """Native delivery order and final interruption/recovery joins, without guessed moments."""
152
+ results=[]
153
+ for session in sessions:
154
+ entry=session['identity'];events=session['events'];attempts=[];responses=[];playout={};delivery_response={}
155
+ for index,event in enumerate(events):
156
+ d=event.get('data',{})
157
+ if event.get('type')=='synthesis_complete':
158
+ r=synthesis_record(event,entry['configured_provider']);r['event_index']=index
159
+ if r['response_id'] not in responses:responses.append(r['response_id'])
160
+ r['delivery_ordinal']=responses.index(r['response_id'])+1;attempts.append(r)
161
+ if event.get('type')=='turn_trace' and isinstance(d.get('delivery_id'),str):
162
+ if d.get('response_id'):delivery_response[d['delivery_id']]=d['response_id']
163
+ if d.get('name')=='playout' and d.get('measurement')=='transport_buffer_estimate':
164
+ require(type(d.get('interrupted')) is bool,'Invalid official playout flag')
165
+ playout[d['delivery_id']]=dict(interrupted=d['interrupted'],complete=d.get('complete'),event_index=index)
166
+ interruptions=[]
167
+ for delivery,record in playout.items():
168
+ if not record['interrupted']:continue
169
+ response=delivery_response.get(delivery)
170
+ next_attempt=next((r for r in attempts if r['event_index']>record['event_index'] and r['response_id']!=response),None)
171
+ interruptions.append(dict(delivery_id=delivery,response_id=response,
172
+ next_attempt_after_terminal_event=next_attempt,
173
+ recovery_observed=next_attempt['success'] and next_attempt['provider_matches'] if next_attempt else None,
174
+ controlled_barge_in=False,cause_known=False))
175
+ moments=entry.get('moment_map',{})
176
+ rapid=[]
177
+ for ordinal in range(7,11):
178
+ step=[a for a in attempts if a['delivery_ordinal']==ordinal]
179
+ rapid.append(dict(delivery_ordinal=ordinal,verified_moment_id=moments.get(str(ordinal)),attempts=step,
180
+ fresh_valid_ttfa_ms=[a['fresh_ttfa_ms'] for a in step if a['fresh_ttfa_ms'] is not None]))
181
+ results.append(dict(session_id=entry['room_sid'],experiment_id=entry['experiment_id'],provider=entry['configured_provider'],
182
+ model=entry['configured_model'],runtime_sha=entry['runtime_sha'],attempts=attempts,
183
+ terminal_playout_deliveries=len(playout),interrupted_deliveries=len(interruptions),interruptions=interruptions,
184
+ ordinal_window_7_to_10=rapid,scenario_alignment='explicit-native-moment-map' if moments else 'unavailable-do-not-equate-delivery-index-with-scenario',
185
+ controlled_barge_in_success=None,timing_controlled_stress_result=None,perceived_latency=None))
186
+ return results
187
+
188
+
189
+ def evaluate_stt_diagnostics(analyses):
190
+ """Recompute strict scores; retain separately supplied sensitivity counts."""
191
+ results=[]
192
+ for profile,data in sorted(analyses.items()):
193
+ rows=data['per_clip'];require(len({r['id'] for r in rows})==len(rows),'Duplicate STT analysis clip')
194
+ groups={}
195
+ for tag in sorted({t for r in rows for t in r['tags']}):
196
+ group=[r for r in rows if tag in r['tags']];scores=[score_transcript(r['reference'],r['hypothesis']) for r in group if isinstance(r.get('hypothesis'),str)]
197
+ words=sum(s['reference_words'] for s in scores);chars=sum(s['reference_characters'] for s in scores)
198
+ groups[tag]=dict(clips=len(group),scored=len(scores),strict_wer=sum(s['word_errors'] for s in scores)/words if words and len(scores)==len(group) else None,
199
+ strict_cer=sum(s['character_errors'] for s in scores)/chars if chars and len(scores)==len(group) else None)
200
+ parity=[]
201
+ for row in rows:
202
+ if not isinstance(row.get('hypothesis'),str):continue
203
+ score=score_transcript(row['reference'],row['hypothesis'])
204
+ require(score['word_errors']==row['strict_edit_distance'] and score['reference_words']==row['strict_ref_words'] and
205
+ score['character_errors']==row['cer_edit_distance'] and score['reference_characters']==row['cer_ref_chars'],'Independent STT strict counts differ')
206
+ parity.append(row['id'])
207
+ sensitivity={}
208
+ for key in ('core','sensitivity'):
209
+ eligible=[r for r in rows if r.get(key+'_ref_words') is not None and r.get(key+'_edit_distance') is not None]
210
+ require(all(type(r[key+'_ref_words']) is int and r[key+'_ref_words']>=0 and type(r[key+'_edit_distance']) is int and r[key+'_edit_distance']>=0 for r in eligible),'Invalid supplied sensitivity counts')
211
+ words=sum(r[key+'_ref_words'] for r in eligible)
212
+ sensitivity[key]=dict(scored=len(eligible),expected=len(rows),reference_words=words,
213
+ pooled_wer=sum(r[key+'_edit_distance'] for r in eligible)/words if words and len(eligible)==len(rows) else None,
214
+ provenance='arithmetic over supplied transformed-text counts; generic transformation code not retained upstream')
215
+ results.append(dict(profile=profile,strict_parity_clips=len(parity),by_content_slice=groups,supplied_sensitivity=sensitivity,
216
+ supplied_entity_exactness=data['entity_exactness'],supplied_latency=data['latency'],
217
+ latency_scope='source transcript delay; no audio-based endpoint accuracy',confidence_interval=None))
218
+ return results
@@ -0,0 +1,118 @@
1
+ """Complete, inert Kido source census. Private evidence is never packaged."""
2
+ from pathlib import Path
3
+ import ast
4
+ import hashlib
5
+ import subprocess
6
+
7
+ from ._speech_kido import digest, encoded, json_value, normalize_kido, require, SpeechEvidenceError
8
+ from ._speech_benchmark_sources import SOURCE_COMMIT, PT, PRON, SWITCH, normalize_sources
9
+ from .speech_evidence import _plain, _read
10
+
11
+ SOURCE_PIN = '95f2a5872f3256d407479dc62b8daf2cf63d585accc72be81b3b3b61cf89a26e'
12
+ SCHEMA = 'sphynx.speech.kido-complete.source.v1'
13
+ GOOGLE = 'results/exploratory/google-gemini-tts/'
14
+ PROXY = 'results/exploratory/pronunciation-v1-cartesia-only/'
15
+
16
+
17
+ def selected(path):
18
+ p = Path(path)
19
+ if any(x.startswith('.') for x in p.parts):
20
+ return False
21
+ if p.suffix in {'.md', '.py', '.go'}:
22
+ return True
23
+ if path.startswith((PRON+'sessions/', SWITCH+'sessions/')):
24
+ return path.endswith(('/session.json', '/metadata.json', '/agent_config.json')) or (
25
+ '/events/chunk_' in path and '/record/' not in path and path.endswith('.jsonl')) or (
26
+ path.startswith(SWITCH) and '/record/transcript/chunk_' in path and path.endswith('.jsonl'))
27
+ return (path.startswith('harness/corpus/') and p.suffix == '.json' or
28
+ path.startswith('configs/') and path.count('/') == 1 and p.suffix == '.json' or
29
+ path.startswith(('provenance/run-jsons/', 'provenance/manifests/', 'scripts/manifests/')) and p.suffix == '.json' or
30
+ path == 'provenance/audio-evidence-manifest.csv' or
31
+ path.startswith('results/human-eval/') and p.suffix in {'.json', '.csv'} or
32
+ path.startswith('results/isolated/official-v1/') and p.suffix == '.csv' or
33
+ path.startswith('results/live-agent/official-v1/raw-events/') and p.suffix in {'.json', '.jsonl'} or
34
+ path.startswith('results/live-agent/official-v1/analysis/') and p.suffix == '.csv' or
35
+ path.startswith(PT) and p.suffix in {'.json', '.csv'} or
36
+ path.startswith(PRON+'listening-review/') and p.suffix == '.csv' or
37
+ path.startswith(SWITCH+'track-c-code-switching/') and p.suffix == '.json' or
38
+ path.startswith((GOOGLE, PROXY)) and p.suffix == '.json' or
39
+ path.startswith('results/exploratory/cartesia-ink-2-stt/hands-on') and path.endswith(('corpus.json', 'raw_results.json', 'final_analysis.json')) or
40
+ path.startswith('results/exploratory/cartesia-ink-2-stt/turn-detection') and path.endswith(('turn_events_raw.json', 'pause_clips_manifest.json')))
41
+
42
+
43
+ def inventory(data):
44
+ require(0 < len(data) <= 2000 and sum(map(len, data.values())) <= 64*1024*1024, 'Invalid complete source census')
45
+ require(all(selected(p) and len(b) <= 16*1024*1024 for p,b in data.items()), 'Invalid complete source file')
46
+ return dict(schema_version=SCHEMA, source_commit=SOURCE_COMMIT,
47
+ files={p:dict(sha256=digest(b), size_bytes=len(b)) for p,b in sorted(data.items())})
48
+
49
+
50
+ def source_bytes(root):
51
+ root = Path(root).absolute(); _plain(root)
52
+ data = {p.relative_to(root).as_posix():_read(root,p.relative_to(root).as_posix())
53
+ for p in sorted(root.rglob('*')) if p.is_file() and selected(p.relative_to(root).as_posix())}
54
+ lock = inventory(data)
55
+ require(digest(encoded(lock)) == SOURCE_PIN, 'Complete source differs from reviewed committed bytes')
56
+ return lock, data
57
+
58
+
59
+ def git_bytes(repository):
60
+ root = Path(repository).absolute(); _plain(root)
61
+ try:
62
+ names = subprocess.check_output(['git','ls-tree','-r','--name-only',SOURCE_COMMIT], cwd=root, timeout=30).decode().splitlines()
63
+ paths = sorted(p for p in names if selected(p))
64
+ raw = subprocess.check_output(['git','cat-file','--batch'], cwd=root,
65
+ input=''.join(SOURCE_COMMIT+':'+p+'\n' for p in paths).encode(), timeout=60)
66
+ except (OSError, subprocess.SubprocessError, UnicodeError):
67
+ raise SpeechEvidenceError('Unable to read committed complete source') from None
68
+ cursor=0; data={}
69
+ for p in paths:
70
+ end=raw.find(b'\n',cursor); require(end>=cursor,'Missing Git blob header')
71
+ header=raw[cursor:end].split(); require(len(header)==3 and header[1]==b'blob','Missing committed source blob')
72
+ size=int(header[2]); require(0<=size<=16*1024*1024,'Oversized source blob')
73
+ start=end+1; b=raw[start:start+size]; cursor=start+size+1
74
+ require(len(b)==size and raw[cursor-1:cursor]==b'\n' and
75
+ hashlib.sha1(b'blob '+str(size).encode()+b'\0'+b).hexdigest().encode()==header[0], 'Git blob identity mismatch')
76
+ data[p]=b
77
+ lock=inventory(data)
78
+ require(cursor==len(raw) and digest(encoded(lock))==SOURCE_PIN,'Complete Git source differs from reviewed source')
79
+ return lock,data
80
+
81
+
82
+ def literal_assignment(raw, name):
83
+ tree=ast.parse(raw.decode('utf-8'))
84
+ values=[ast.literal_eval(n.value) for n in tree.body if isinstance(n,ast.Assign)
85
+ and any(isinstance(t,ast.Name) and t.id==name for t in n.targets)]
86
+ require(len(values)==1,'Missing or duplicated inert source constant')
87
+ return values[0]
88
+
89
+
90
+ def normalize(data):
91
+ observations,references,audit=normalize_kido(data)
92
+ google=[]
93
+ for filename,scope in [('full.json','A'),('compare_full.json','B'),('smoke.json','smoke-A'),('compare_smoke.json','smoke-B')]:
94
+ rows=json_value(data[GOOGLE+'raw-results/'+filename]); require(type(rows) is list,'Invalid Google trial census')
95
+ for i,row in enumerate(rows):
96
+ require(type(row) is dict and type(row.get('ok')) is bool and isinstance(row.get('test'),str),'Invalid Google request record')
97
+ google.append(dict(id='google:'+scope+':'+str(i+1),scope=scope,source_index=i+1,record=row))
98
+ proxy=json_value(data[PROXY+'pronunciation_results.json'])
99
+ require(type(proxy) is list and len({r['id'] for r in proxy})==len(proxy),'Invalid proxy census')
100
+ texts=literal_assignment(data[GOOGLE+'code/bench.py'],'TEXTS')
101
+ require(type(texts) is dict and all(isinstance(k,str) and isinstance(v,str) for k,v in texts.items()),'Invalid Google text corpus')
102
+ multilingual=json_value(data['harness/corpus/multilingual-extension-v1.json'])
103
+ require(len(multilingual['lines'])==len(multilingual['languages'])*multilingual['lines_per_language']
104
+ and len({r['id'] for r in multilingual['lines']})==len(multilingual['lines']),'Invalid multilingual corpus')
105
+ sessions=[]
106
+ for entry in json_value(data['scripts/manifests/official-v1-live-sessions.json']):
107
+ prefix='results/live-agent/official-v1/raw-events/'+entry['room_sid']+'-events-chunk_'
108
+ events=[json_value(line) for p in sorted(data) if p.startswith(prefix) for line in data[p].splitlines() if line.strip()]
109
+ sessions.append(dict(identity=entry,events=events))
110
+ snapshot=data['results/live-agent/official-v1/live-eval-11-moment-config-snapshot-2026-09-22.md'].decode('utf-8')
111
+ import re
112
+ script=[dict(id='moment:'+m[0],text=m[2].strip('"'),language='pt-BR' if m[0]=='10' else 'en',title=m[1])
113
+ for m in re.findall(r'^\| ([0-9]+) \| ([^|]+) \| (.+) \|$',snapshot,re.M)]
114
+ require(len(script)==11,'Missing frozen eleven-moment script')
115
+ return dict(saved=dict(observations=observations,references=references,audit=audit),benchmarks=normalize_sources(data),official_script=script,
116
+ google=google,google_texts=texts,proxy=proxy,multilingual=multilingual,official_sessions=sessions,
117
+ stt_analyses={folder:json_value(data['results/exploratory/cartesia-ink-2-stt/'+folder+'/final_analysis.json']) for folder in ('hands-on-v1','hands-on-ink2-v1')},
118
+ source_documents={p:digest(b) for p,b in data.items() if p.endswith('.md')})
sphynx/_speech_kido.py ADDED
@@ -0,0 +1,294 @@
1
+ """Parse a pinned Kido source export as inert data. Never import source code."""
2
+ from __future__ import annotations
3
+
4
+ import csv
5
+ import hashlib
6
+ import io
7
+ import json
8
+ import math
9
+ import re
10
+
11
+
12
+ class SpeechEvidenceError(ValueError):
13
+ """Speech input bytes, identities, or saved evidence are invalid."""
14
+
15
+
16
+ def require(condition, message):
17
+ if not condition:
18
+ raise SpeechEvidenceError(message)
19
+
20
+
21
+ def digest(raw):
22
+ return hashlib.sha256(raw).hexdigest()
23
+
24
+
25
+ def encoded(value):
26
+ return (json.dumps(value, ensure_ascii=False, sort_keys=True,
27
+ separators=(",", ":"), allow_nan=False) + "\n").encode("utf-8")
28
+
29
+
30
+ def json_value(raw):
31
+ def pairs(items):
32
+ result = {}
33
+ for key, value in items:
34
+ require(key not in result, "duplicate speech JSON field")
35
+ result[key] = value
36
+ return result
37
+ def finite(value):
38
+ result = float(value)
39
+ require(math.isfinite(result), "nonfinite speech JSON number")
40
+ return result
41
+ try:
42
+ return json.loads(raw.decode("utf-8"), object_pairs_hook=pairs, parse_float=finite,
43
+ parse_constant=lambda _: require(False, "nonfinite speech JSON"))
44
+ except (ValueError, UnicodeError, RecursionError) as exc:
45
+ if isinstance(exc, SpeechEvidenceError):
46
+ raise
47
+ raise SpeechEvidenceError("invalid speech JSON") from None
48
+
49
+
50
+ def csv_rows(raw):
51
+ reader = csv.DictReader(io.StringIO(raw.decode("utf-8-sig"), newline=""))
52
+ names = reader.fieldnames
53
+ require(names and len(set(names)) == len(names), "missing or duplicate speech CSV headers")
54
+ rows = list(reader)
55
+ require(all(None not in row and None not in row.values() for row in rows), "malformed speech CSV row")
56
+ return rows
57
+
58
+
59
+ def number(value, label, *, nullable=False):
60
+ if value is None and nullable:
61
+ return None
62
+ require(type(value) in (int, float) and math.isfinite(value) and value >= 0,
63
+ label + " must be finite and nonnegative")
64
+ return value
65
+
66
+
67
+ def _float(value, label):
68
+ try:
69
+ return number(float(value), label)
70
+ except (ValueError, OverflowError):
71
+ raise SpeechEvidenceError(label + " must be numeric") from None
72
+
73
+
74
+ def _integer(value, label):
75
+ require(isinstance(value, str) and re.fullmatch(r"0|[1-9][0-9]*", value), label + " must be an integer")
76
+ return int(value)
77
+
78
+
79
+ def _bool(value):
80
+ require(value in ("true", "false"), "speech success must be true or false")
81
+ return value == "true"
82
+
83
+
84
+ def reconstruct_transcript(events):
85
+ """Final segments carry their own spacing. Never join them with spaces."""
86
+ require(isinstance(events, list), "STT events must be an ordered list")
87
+ segments = []
88
+ last = -1
89
+ for event in events:
90
+ require(isinstance(event, dict) and isinstance(event.get("msg"), dict), "invalid STT event")
91
+ when = number(event.get("t"), "STT event timestamp")
92
+ require(when >= last, "STT events are out of order")
93
+ last = when
94
+ msg = event["msg"]
95
+ if event.get("type") == "transcript":
96
+ require(type(msg.get("is_final")) is bool and isinstance(msg.get("text"), str),
97
+ "invalid STT transcript event")
98
+ if msg["is_final"]:
99
+ segments.append(msg["text"])
100
+ return "".join(segments).strip() if segments else None
101
+
102
+
103
+ def provider_gate(provider, records):
104
+ """Compatibility gate from the frozen parser; not full model verification."""
105
+ successes = [r for r in records if r["success"]]
106
+ observed = {r["observed_provider"] for r in successes if r["observed_provider"]}
107
+ fallback = any(r["observed_provider"] and r["observed_provider"] != provider for r in records)
108
+ confirmed = bool(records) and observed == {provider}
109
+ any_error = any(r["error_present"] for r in records)
110
+ return {"provider_confirmed": confirmed, "fallback_observed": fallback,
111
+ "any_synthesis_error": any_error,
112
+ "legacy_provider_gate": confirmed and not fallback and not any_error and bool(successes)}
113
+
114
+
115
+ def normalize_kido(files):
116
+ """Return normalized observations, protected references, and intake audit."""
117
+ observations, references = [], []
118
+ trials_path = "results/isolated/official-v1/combined/trials.csv"
119
+ raw_trials = files[trials_path]
120
+ trials = csv_rows(raw_trials)
121
+ legacy = json_value(files["provenance/manifests/combined-manifest.json"])
122
+ n = legacy["observed_total_trials"]
123
+ require(type(n) is int and 0 < n <= len(trials), "invalid historical trial census")
124
+ # The original dataset is an exact byte prefix. Prove it; never waive a hash.
125
+ prefix = b"".join(raw_trials.splitlines(keepends=True)[:n + 1])
126
+ require(digest(prefix) == legacy["combined_trials_csv_sha256"],
127
+ "historical TTS prefix no longer matches its manifest")
128
+ configs = {}
129
+ for path, raw in files.items():
130
+ if path.startswith("configs/") and path.count("/") == 1 and path.endswith(".json"):
131
+ config = json_value(raw)
132
+ for candidate in config["candidates"]:
133
+ key = candidate["name"]
134
+ require(key not in configs, "duplicate configured speech candidate")
135
+ configs[key] = (candidate, {line["id"]: line for line in config["lines"]})
136
+ seen = set()
137
+ config_identity_verified = 0
138
+ for index, row in enumerate(trials):
139
+ name, rep = row["candidate"], _integer(row["repetition"], "repetition")
140
+ require(name in configs, "TTS trial has no pinned configuration")
141
+ candidate, lines = configs[name]
142
+ require(row["line_id"] in lines and rep > 0, "TTS trial has no eligible corpus line")
143
+ require(all(row[k] == candidate[k] for k in ("provider", "model", "voice_id")),
144
+ "TTS trial differs from configured identity")
145
+ key = (name, row["line_id"], rep)
146
+ require(key not in seen, "duplicate isolated TTS trial")
147
+ seen.add(key)
148
+ success = _bool(row["success"])
149
+ count = _integer(row["bytes"], "audio bytes")
150
+ require(not success or (count > 0 and not row["failure_reason"]), "successful TTS trial lacks audio or has a failure")
151
+ line = lines[row["line_id"]]
152
+ if index < n:
153
+ cohort = "core-five-20260921"
154
+ receipt_path = f"provenance/run-jsons/wave-{rep}-{row['provider']}.run.json"
155
+ require(receipt_path in files, "missing original TTS run receipt")
156
+ receipt = json_value(files[receipt_path])
157
+ require(receipt["rep_start"] == rep and receipt["scheduled_trials"] == len(lines)
158
+ and receipt["candidates"] == [candidate], "TTS run receipt differs from trials")
159
+ config_path = next(p for p in files if p.startswith("configs/") and p.endswith(".json")
160
+ and json_value(files[p]).get("candidates") == [candidate])
161
+ config_matches = digest(files[config_path]) == receipt["corpus_sha256"]
162
+ config_identity_verified += config_matches
163
+ source_status = ("original_run_receipt_and_config_verified" if config_matches else
164
+ "original_run_receipt_present_config_bytes_unverified")
165
+ else:
166
+ cohort = "google-extension" if row["provider"] == "google" else "eleven-v4-extension"
167
+ source_status = "repository_bytes_pinned_original_run_receipt_unavailable"
168
+ observations.append({"id": f"isolated/{name}/{row['line_id']}/{rep}", "track": "tts_isolated",
169
+ "cohort": cohort, "model": row["model"], "provider": row["provider"],
170
+ "voice_id": row["voice_id"], "case_id": row["line_id"], "repetition": rep,
171
+ "cluster_id": row["line_id"], "input_text": line["text"], "language": line.get("language", "en"),
172
+ "success": success, "failure_reason": row["failure_reason"], "audio_bytes": count,
173
+ "ttfa_ms": _float(row["ttfa_ms"], "isolated TTFA") if success else None,
174
+ "request_completion_ms": _float(row["duration_ms"], "request completion") if success else None,
175
+ "audio_duration_ms": None, "audio_available": False,
176
+ "execution_source_status": source_status, "source_row": index + 1})
177
+ # Check every eligible case/repetition exists. No success-only or provider-only census.
178
+ for name, (_, lines) in configs.items():
179
+ require({key for key in seen if key[0] == name} ==
180
+ {(name, line, rep) for line in lines for rep in range(1, 6)}, "isolated TTS census is incomplete")
181
+
182
+ for folder, model in (("hands-on-v1", "ink-preview"), ("hands-on-ink2-v1", "ink-2")):
183
+ base = "results/exploratory/cartesia-ink-2-stt/" + folder + "/"
184
+ corpus = json_value(files[base + "corpus.json"])
185
+ lines = {line["id"]: line for line in corpus["lines"]}
186
+ rows = json_value(files[base + "raw_results.json"])
187
+ require(len(rows) == len(lines) and {r["clip_id"] for r in rows} == set(lines), "STT census is incomplete or duplicated")
188
+ for row in rows:
189
+ hypothesis = reconstruct_transcript(row["events"])
190
+ success = row["status"] == "success"
191
+ require(not success or hypothesis is not None, "successful STT trace has no final transcript")
192
+ start = number(row["stream_start_epoch"], "STT stream start")
193
+ sent = number(row["last_audio_sent_epoch"], "STT last chunk")
194
+ require(sent >= start, "STT send clock is reversed")
195
+ first = number(row["first_transcript_epoch"], "STT first transcript", nullable=True)
196
+ final = number(row["final_transcript_epoch"], "STT final transcript", nullable=True)
197
+ require(first is None or first >= start, "STT first-text clock is reversed")
198
+ require(final is None or final >= sent, "STT final-text clock is reversed")
199
+ case = row["clip_id"]
200
+ identity = f"stt/{model}/{case}"
201
+ observations.append({"id": identity, "track": "stt_recognition", "cohort": "synthetic-english-20",
202
+ "model": model, "provider": "cartesia", "case_id": case, "cluster_id": case,
203
+ "success": success, "hypothesis": hypothesis if success else None,
204
+ "events": row["events"], "stored_final_text": row.get("final_text"),
205
+ "reconstruction": "exact-final-segment-concatenation.v1",
206
+ "first_text_ms": (first - start) * 1000 if success and first is not None else None,
207
+ "final_text_ms": (final - sent) * 1000 if success and final is not None else None,
208
+ "clock_domain": "source-client-monotonic-relative", "audio_available": False})
209
+ references.append({"id": identity, "reference": lines[case]["text"],
210
+ "provenance": "synthetic-script-not-human-audio-review", "language": "en"})
211
+
212
+ mapping_rows = json_value(files["results/human-eval/mapping.json"])["mapping"]
213
+ mapping = {r["blind_sample_id"]: r for r in mapping_rows}
214
+ require(len(mapping) == len(mapping_rows), "duplicate blind sample mapping")
215
+ scores = csv_rows(files["results/human-eval/scores.csv"])
216
+ require(len(scores) == len(mapping) and {r["blind_sample_id"] for r in scores} == set(mapping),
217
+ "blind rating census is incomplete or duplicated")
218
+ for row in scores:
219
+ sample = mapping[row["blind_sample_id"]]
220
+ require(row["corpus_line_id"] == sample["corpus_line_id"], "rating and mapping case differ")
221
+ score = _float(row["overall_quality_1_5"], "overall rating") if row["overall_quality_1_5"] else None
222
+ require(score is None or (score.is_integer() and 1 <= score <= 5), "rating outside integer 1-5 scale")
223
+ identity = "listening/" + row["blind_sample_id"]
224
+ observations.append({"id": identity, "track": "tts_listening", "cohort": "single-rater-repetition-3",
225
+ "model": sample["model"], "provider": sample["provider"], "case_id": sample["corpus_line_id"],
226
+ "cluster_id": sample["corpus_line_id"], "sample_id": row["blind_sample_id"],
227
+ "audio_available": False})
228
+ references.append({"id": identity, "overall_quality": score, "review_status": "rated" if score is not None else "pending",
229
+ "provenance": "historical-single-rater-overall-only", "naturalness": None, "perceived_waiting": None})
230
+
231
+ sessions = json_value(files["scripts/manifests/official-v1-live-sessions.json"])
232
+ require(len({s["room_sid"] for s in sessions}) == len(sessions), "duplicate speech session")
233
+ for session in sessions:
234
+ prefix = "results/live-agent/official-v1/raw-events/" + session["room_sid"]
235
+ metadata = json_value(files[prefix + "-metadata.json"]) if prefix + "-metadata.json" in files else {}
236
+ chunk_paths = sorted(p for p in files if p.startswith(prefix + "-events-chunk_") and p.endswith(".jsonl"))
237
+ require(chunk_paths, "missing live event chunks")
238
+ chunk_numbers = [int(re.search(r"chunk_(\d+)\.jsonl$", p).group(1)) for p in chunk_paths]
239
+ require(chunk_numbers == list(range(chunk_numbers[0], chunk_numbers[0] + len(chunk_numbers))), "gap in live event chunks")
240
+ events = [json_value(line) for path in chunk_paths for line in files[path].splitlines() if line.strip()]
241
+ event_ids = [e["id"] for e in events if e.get("id")]
242
+ require(len(event_ids) == len(set(event_ids)), "duplicate live event ID")
243
+ records, attempts = [], {}
244
+ for event in events:
245
+ if event.get("type") != "synthesis_complete":
246
+ continue
247
+ data = event["data"]
248
+ require(all(type(data.get(k)) is bool for k in ("complete", "cancelled", "cache_hit")), "missing live synthesis flags")
249
+ response = data.get("response_id") or ""
250
+ require(response, "live synthesis has no response ID")
251
+ attempts[response] = attempts.get(response, 0) + 1
252
+ audio_bytes = data.get("produced_audio_bytes")
253
+ require(type(audio_bytes) is int and audio_bytes >= 0, "invalid live audio bytes")
254
+ error = data.get("error") or ""
255
+ require(isinstance(error, str), "invalid live error")
256
+ success = data["complete"] and not data["cancelled"] and not error and audio_bytes > 0
257
+ start = number(data.get("started_at"), "live synthesis start", nullable=True)
258
+ first = number(data.get("first_audio_at"), "live first audio", nullable=True)
259
+ end = number(data.get("ended_at"), "live synthesis end", nullable=True)
260
+ clock_ok = start is not None and first is not None and end is not None and start <= first <= end
261
+ row = {"id": f"live/{session['room_sid']}/{response}/{attempts[response]}", "track": "tts_live",
262
+ "cohort": session["experiment_id"] + ("/substitute-agent-v4" if session["configured_model"] in ("eleven_v4", "eleven_v4_turbo") else "/original-agent"),
263
+ "session_id": session["room_sid"], "cluster_id": session["room_sid"],
264
+ "case_id": event.get("turn_id") or response, "attempt": attempts[response],
265
+ "model": session["configured_model"], "provider": session["configured_provider"],
266
+ "declared_voice_id": session["configured_voice_id"],
267
+ "observed_provider": data.get("provider") or None, "observed_model": data.get("model") or None,
268
+ "observed_voice_id": data.get("voice_id") or None,
269
+ "runtime_sha_declared": session["runtime_sha"], "runtime_sha_observed": metadata.get("git_sha"),
270
+ "session_metadata_present": bool(metadata),
271
+ "success": success, "cancelled": data["cancelled"], "cache_hit": data["cache_hit"],
272
+ "error_present": bool(error), "audio_bytes": audio_bytes,
273
+ "ttfa_ms": (first - start) * 1000 if success and clock_ok else None,
274
+ "clock_status": "ordered" if clock_ok else "unavailable_or_reversed",
275
+ "audio_available": False}
276
+ records.append(row)
277
+ observations.extend(records)
278
+ gate = provider_gate(session["configured_provider"], records)
279
+ # One census record even when a session never produced synthesis evidence.
280
+ observations.append({"id": "session/" + session["room_sid"], "track": "tts_live_session",
281
+ "cohort": session["experiment_id"] + ("/substitute-agent-v4" if session["configured_model"] in ("eleven_v4", "eleven_v4_turbo") else "/original-agent"),
282
+ "model": session["configured_model"], "provider": session["configured_provider"],
283
+ "case_id": session["room_sid"], "cluster_id": session["room_sid"],
284
+ "session_metadata_present": bool(metadata), "attempts": len(records), **gate})
285
+ require(len({r["id"] for r in observations}) == len(observations), "duplicate normalized speech observation")
286
+ audit = {"historical_manifest_matches_current_csv": digest(raw_trials) == legacy["combined_trials_csv_sha256"],
287
+ "historical_prefix_verified": True, "historical_prefix_trials": n, "current_trials": len(trials),
288
+ "extension_trials": len(trials) - n,
289
+ "extension_original_run_receipts": "unavailable",
290
+ "original_configuration_identity_verified_trials": config_identity_verified,
291
+ "original_configuration_identity_unverified_trials": n - config_identity_verified,
292
+ "audio_assets_copied": False, "capture_completeness": "historical-export-no-global-loss-counter",
293
+ "perceived_latency_available": False, "human_review": "existing-single-rater-only"}
294
+ return observations, references, audit