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,252 @@
1
+ """Permitted analytical value schemas; no service, policy or credential code."""
2
+ from __future__ import annotations
3
+ from collections.abc import Mapping
4
+ from dataclasses import dataclass,field,fields
5
+ import hashlib
6
+ import json
7
+ import math
8
+ import re
9
+ import struct
10
+ from types import MappingProxyType
11
+ from typing import Any,Callable,cast
12
+ from .evaluation_protocols import VersionedIdentity
13
+ from .workflow_contracts import WorkflowContractError
14
+
15
+
16
+ def require(ok: object,message: str='invalid analytical response') -> None:
17
+ if not ok:raise WorkflowContractError(message)
18
+
19
+
20
+ def identifier(value: Any,*,empty: bool=False) -> str:
21
+ require(type(value) is str and ((empty and value=='') or
22
+ re.fullmatch(r'[A-Za-z0-9._:-]{1,128}',value) is not None and value not in ('.','..')),
23
+ 'invalid private analytical identifier')
24
+ return value
25
+
26
+
27
+ def text(value: Any) -> str:
28
+ require(type(value) is str and bool(value.strip()) and '\0' not in value)
29
+ try:require(len(value.encode('utf-8'))<=1024)
30
+ except UnicodeError:raise WorkflowContractError('invalid analytical UTF-8 text') from None
31
+ return value
32
+
33
+
34
+ def digest(value: Any) -> str:
35
+ require(type(value) is str and re.fullmatch(r'sha256:[0-9a-f]{64}',value) is not None)
36
+ return value
37
+
38
+
39
+ def count(value: Any,*,nullable: bool=True,maximum: int=2**63-1) -> int|None:
40
+ require(value is None and nullable or type(value) is int and 0<=value<=maximum)
41
+ return value
42
+
43
+
44
+ def keys(value: Any,names: set[str]) -> dict[str,Any]:
45
+ require(isinstance(value,Mapping) and set(value)==names,'invalid analytical response fields')
46
+ return dict(value)
47
+
48
+
49
+ def identity(value: Any) -> VersionedIdentity:
50
+ values=keys(value,{'schema_version','identity_id','version','digest'})
51
+ text(values['identity_id']);text(values['version'])
52
+ return VersionedIdentity.from_dict(values)
53
+
54
+
55
+ def score_bits(value: Any) -> float:
56
+ require(type(value) is str and re.fullmatch('[0-9a-f]{16}',value) is not None)
57
+ number=struct.unpack('>d',bytes.fromhex(value))[0]
58
+ require(math.isfinite(number),'analytical score must have exact finite bits')
59
+ return number
60
+
61
+
62
+ def sha(value: str) -> str:
63
+ return 'sha256:'+hashlib.sha256(value.encode('utf-8')).hexdigest()
64
+
65
+
66
+ def frame(profile: str,*values: object) -> str:
67
+ return profile+'\n'+'\n'.join(str(value) for value in values)
68
+
69
+
70
+ def parse_json(raw: Any,*,maximum: int=65536) -> Any:
71
+ require(type(raw) is str)
72
+ try:
73
+ require(len(raw.encode('utf-8'))<=maximum)
74
+ def unique(pairs):
75
+ result={}
76
+ for key,value in pairs:require(key not in result);result[key]=value
77
+ return result
78
+ return json.loads(raw,object_pairs_hook=unique,
79
+ parse_constant=lambda _:(_ for _ in ()).throw(ValueError()))
80
+ except (ValueError,TypeError,UnicodeError,RecursionError):
81
+ raise WorkflowContractError('invalid analytical JSON') from None
82
+
83
+
84
+ def wire(value: Any) -> Any:
85
+ if isinstance(value,(VersionedIdentity,_Values)):return value.to_dict()
86
+ if isinstance(value,Mapping):return {key:wire(item) for key,item in value.items()}
87
+ if isinstance(value,(tuple,list)):return [wire(item) for item in value]
88
+ return value
89
+
90
+
91
+ class _Values:
92
+ def to_dict(self) -> dict[str,Any]:
93
+ return {item.name:wire(getattr(self,item.name)) for item in fields(cast(Any,self))}
94
+
95
+
96
+ @dataclass(frozen=True)
97
+ class ProjectionCoverage(_Values):
98
+ """Native counts and unit, including explicit unknown values."""
99
+ unit: str
100
+ required: int|None
101
+ observed: int|None
102
+ missing: int|None
103
+ undefined: int|None
104
+ def __post_init__(self) -> None:
105
+ text(self.unit)
106
+ for name in ('required','observed','missing','undefined'):count(getattr(self,name))
107
+ @classmethod
108
+ def from_dict(cls,value: Mapping[str,Any]) -> ProjectionCoverage:
109
+ return cls(**keys(value,{'unit','required','observed','missing','undefined'}))
110
+
111
+
112
+ @dataclass(frozen=True)
113
+ class ReportProjectionSummary(_Values):
114
+ run_id: str
115
+ grading_id: str
116
+ scope: str
117
+ mode: str
118
+ status: str
119
+ trust_tier: str
120
+ bindings: Mapping[str,VersionedIdentity]=field(repr=False)
121
+ coverage: ProjectionCoverage
122
+ def __post_init__(self) -> None:
123
+ for value in (self.run_id,self.grading_id,self.status,self.trust_tier):text(value)
124
+ require(self.scope in ('component','subsystem','system') and self.mode in ('local','hosted'))
125
+ require(isinstance(self.coverage,ProjectionCoverage))
126
+ require(isinstance(self.bindings,Mapping) and {'candidate','protocol','bank','evaluator','runtime'}<=set(self.bindings)
127
+ and set(self.bindings)<={'candidate','protocol','bank','evaluator','runtime','labels','references','hardware'})
128
+ require(all(isinstance(value,VersionedIdentity) for value in self.bindings.values()))
129
+ object.__setattr__(self,'bindings',MappingProxyType(dict(self.bindings)))
130
+ @classmethod
131
+ def from_dict(cls,value: Mapping[str,Any]) -> ReportProjectionSummary:
132
+ values=keys(value,{'run_id','grading_id','scope','mode','status','trust_tier','bindings','coverage'})
133
+ require(isinstance(values['bindings'],Mapping))
134
+ values['bindings']={key:identity(item) for key,item in values['bindings'].items()}
135
+ values['coverage']=ProjectionCoverage.from_dict(values['coverage'])
136
+ return cls(**values)
137
+
138
+
139
+ @dataclass(frozen=True)
140
+ class MetricProjectionSummary(_Values):
141
+ run_id: str
142
+ grading_id: str
143
+ metric_id: str
144
+ unit: str
145
+ direction: str
146
+ availability: str
147
+ value_kind: str
148
+ value_bits: str|None=field(repr=False)
149
+ assertion: bool|None
150
+ label: str|None=field(repr=False)
151
+ coverage: ProjectionCoverage
152
+ population_id: str
153
+ method_id: str
154
+ uncertainty_id: str|None
155
+ def __post_init__(self) -> None:
156
+ for name in ('run_id','grading_id','metric_id','unit','direction','population_id','method_id'):text(getattr(self,name))
157
+ if self.uncertainty_id is not None:text(self.uncertainty_id)
158
+ require(isinstance(self.coverage,ProjectionCoverage))
159
+ require(self.availability in ('available','missing','undefined','unavailable')
160
+ and self.value_kind in ('score','assertion','label','custom'))
161
+ available=self.availability=='available'
162
+ if available and self.value_kind=='score':score_bits(self.value_bits)
163
+ else:require(self.value_bits is None)
164
+ require(type(self.assertion) is bool if available and self.value_kind=='assertion' else self.assertion is None)
165
+ if available and self.value_kind=='label':text(self.label)
166
+ else:require(self.label is None)
167
+ @property
168
+ def value(self) -> float|bool|str|None:
169
+ """A custom value stays in its native artifact; availability is explicit."""
170
+ if self.availability!='available':return None
171
+ if self.value_kind=='score':return score_bits(self.value_bits)
172
+ return self.assertion if self.value_kind=='assertion' else self.label
173
+ @classmethod
174
+ def from_dict(cls,value: Mapping[str,Any]) -> MetricProjectionSummary:
175
+ values=keys(value,{'run_id','grading_id','metric_id','unit','direction','availability','value_kind',
176
+ 'value_bits','assertion','label','coverage','population_id','method_id','uncertainty_id'})
177
+ values['coverage']=ProjectionCoverage.from_dict(values['coverage']);return cls(**values)
178
+
179
+
180
+ @dataclass(frozen=True)
181
+ class ComparisonProjectionSummary(_Values):
182
+ baseline_grading_id: str
183
+ candidate_grading_id: str
184
+ status: str
185
+ reason: str|None
186
+ policy_id: str
187
+ population_id: str
188
+ metric_ids: tuple[str,...]
189
+ artifact_digest: str
190
+ def __post_init__(self) -> None:
191
+ for name in ('baseline_grading_id','candidate_grading_id','policy_id','population_id'):text(getattr(self,name))
192
+ require(self.status in ('supported','incomplete','incompatible','unavailable'))
193
+ if self.reason is not None:text(self.reason)
194
+ require(self.status=='supported' or self.reason is not None)
195
+ require(isinstance(self.metric_ids,(tuple,list)) and len(self.metric_ids)<=128)
196
+ for name in self.metric_ids:text(name)
197
+ object.__setattr__(self,'metric_ids',tuple(self.metric_ids));digest(self.artifact_digest)
198
+ @classmethod
199
+ def from_dict(cls,value: Mapping[str,Any]) -> ComparisonProjectionSummary:
200
+ return cls(**keys(value,{'baseline_grading_id','candidate_grading_id','status','reason','policy_id',
201
+ 'population_id','metric_ids','artifact_digest'}))
202
+
203
+
204
+ @dataclass(frozen=True)
205
+ class EligibilityView(_Values):
206
+ """A recorded status. This object cannot grant current service permission."""
207
+ eligible: bool
208
+ reasons: tuple[str,...]
209
+ def __post_init__(self) -> None:
210
+ require(type(self.eligible) is bool and isinstance(self.reasons,(tuple,list)))
211
+ for reason in self.reasons:text(reason)
212
+ require(self.eligible or bool(self.reasons));object.__setattr__(self,'reasons',tuple(self.reasons))
213
+ @classmethod
214
+ def from_dict(cls,value: Mapping[str,Any]) -> EligibilityView:
215
+ return cls(**keys(value,{'eligible','reasons'}))
216
+
217
+
218
+ @dataclass(frozen=True)
219
+ class AuditProjectionSummary(_Values):
220
+ """Historical review status, with no protected target or judgment content."""
221
+ review_status: str
222
+ state: str
223
+ policy_id: VersionedIdentity
224
+ eligibility: Mapping[str,EligibilityView]=field(repr=False)
225
+ counts: Mapping[str,int]
226
+ def __post_init__(self) -> None:
227
+ require(self.review_status in ('pending','provisional','reviewed','disputed','needs_changes')
228
+ and self.state in ('active','held','withdrawn') and isinstance(self.policy_id,VersionedIdentity))
229
+ require(isinstance(self.eligibility,Mapping) and set(self.eligibility)=={'development','ranking','acceptance'})
230
+ require(all(isinstance(value,EligibilityView) and (self.state=='active' or not value.eligible)
231
+ for value in self.eligibility.values()))
232
+ require(isinstance(self.counts,Mapping) and len(self.counts)<=32)
233
+ for name,value in self.counts.items():identifier(name);count(value,nullable=False)
234
+ object.__setattr__(self,'counts',MappingProxyType(dict(self.counts)))
235
+ object.__setattr__(self,'eligibility',MappingProxyType(dict(self.eligibility)))
236
+ @classmethod
237
+ def from_dict(cls,value: Mapping[str,Any]) -> AuditProjectionSummary:
238
+ values=keys(value,{'review_status','state','policy_id','eligibility','counts'})
239
+ values['policy_id']=identity(values['policy_id']);require(isinstance(values['eligibility'],Mapping))
240
+ values['eligibility']={key:EligibilityView.from_dict(item) for key,item in values['eligibility'].items()}
241
+ return cls(**values)
242
+
243
+
244
+ ProjectionSummary=ReportProjectionSummary|MetricProjectionSummary|ComparisonProjectionSummary|AuditProjectionSummary
245
+
246
+
247
+ def summary(kind: str,value: Mapping[str,Any]) -> ProjectionSummary:
248
+ readers: dict[str,Callable[[Mapping[str,Any]],ProjectionSummary]]={
249
+ 'report':ReportProjectionSummary.from_dict,'metric':MetricProjectionSummary.from_dict,
250
+ 'comparison':ComparisonProjectionSummary.from_dict,'audit':AuditProjectionSummary.from_dict}
251
+ require(type(kind) is str and kind in readers)
252
+ return readers[kind](value)
@@ -0,0 +1,173 @@
1
+ """Lazy audit transport helpers over the existing authenticated Client."""
2
+ from __future__ import annotations
3
+ from collections.abc import Mapping
4
+ import json
5
+ from dataclasses import replace
6
+ from typing import Any, Literal, TYPE_CHECKING
7
+ from urllib.parse import quote, urlencode
8
+
9
+ from .audit_access import ReviewHistoryPage, ReviewOperationReceipt, ReviewTargetPage, TargetReviewPage
10
+ from .audit_contracts import BankRelease, ReviewContribution
11
+ from .audit_requests import (BankReleaseDeclaration, BankReleaseTransition, ReviewAdjudication,
12
+ _bounded, _identifier, _key, _positive_version)
13
+ from .client import HostedClientError
14
+ from .workflow_contracts import WorkflowContractError
15
+
16
+ if TYPE_CHECKING:
17
+ from .diagnostics import Diagnostic
18
+
19
+ AuditRequest = BankReleaseDeclaration | ReviewContribution | ReviewAdjudication | BankReleaseTransition
20
+
21
+
22
+ class AuditRequestError(HostedClientError):
23
+ """Preserve the exact original write for explicit uncertain-outcome recovery."""
24
+ request: AuditRequest
25
+
26
+ def __init__(self, cause: HostedClientError, *, bank_id: str, release_id: str,
27
+ operation: str, request: AuditRequest):
28
+ self.bank_id, self.release_id, self.operation = bank_id, release_id, operation
29
+ self.request, self.idempotency_key = request, request.idempotency_key
30
+ super().__init__(cause.code, 'audit request did not return a verified receipt; retain the original request and key')
31
+
32
+ @property
33
+ def diagnostic(self) -> Diagnostic:
34
+ view = super().diagnostic
35
+ if self.code in {'hosted_transport_failed', 'invalid_server_response', 'store_unavailable',
36
+ 'service_unavailable', 'hosted_request_failed'}:
37
+ return replace(view, summary='The audit write outcome needs to be reconciled.',
38
+ next_actions=('Read current status or explicitly replay the original request and key before a new judgment/version.',),
39
+ retryability='reconcile_before_retry')
40
+ return view
41
+
42
+
43
+ def _invalid():
44
+ return HostedClientError('invalid_server_response', 'the selected-bank audit response could not be verified')
45
+
46
+
47
+ def _path(bank, release=None):
48
+ path = '/v1/banks/' + quote(_identifier(bank), safe='') + '/releases'
49
+ if release is not None:
50
+ path += '/' + quote(_identifier(release), safe='')
51
+ return path
52
+
53
+
54
+ def _query(*, after, through_version, limit, maximum, target_cursor=False):
55
+ if type(limit) is not int or not 1 <= limit <= maximum:
56
+ raise WorkflowContractError('audit page limit is outside its supported range')
57
+ data: dict[str, Any] = {'limit': limit}
58
+ if after is not None:
59
+ if target_cursor:
60
+ data['after'] = _identifier(after)
61
+ elif type(after) is not int or not 0 <= after <= 2**63 - 1:
62
+ raise WorkflowContractError('audit history cursor must be a nonnegative signed 64-bit integer')
63
+ else:
64
+ data['after'] = after
65
+ if through_version is not None:
66
+ data['through_version'] = _positive_version(through_version)
67
+ if not target_cursor and after is not None and after > through_version:
68
+ raise WorkflowContractError('audit cursor exceeds its selected snapshot')
69
+ return data
70
+
71
+
72
+ def _decode(cls, value, bank, release):
73
+ try:
74
+ result = cls.from_dict(value)
75
+ if (result.bank_id, result.release_id) != (bank, release):
76
+ raise WorkflowContractError('selected audit response identity changed')
77
+ return result
78
+ except (WorkflowContractError, ValueError, TypeError, KeyError, AttributeError, RecursionError):
79
+ raise _invalid() from None
80
+
81
+
82
+ def _page(cls, client, path, query, bank, release, *, protected=None):
83
+ result = _decode(cls, client._request('GET', path + '?' + urlencode(query)), bank, release)
84
+ if (len(result.items) > query['limit'] or
85
+ (query.get('through_version') is not None and result.through_version != query['through_version'])):
86
+ raise _invalid()
87
+ after = query.get('after')
88
+ if after is not None and any((item.target_id if isinstance(result, ReviewTargetPage) else item.version) <= after
89
+ for item in result.items):
90
+ raise _invalid()
91
+ if protected is not None and result.protected is not protected:
92
+ raise _invalid()
93
+ return result
94
+
95
+
96
+ def release(client, bank, release_id) -> BankRelease:
97
+ return _decode(BankRelease, client._request('GET', _path(bank, release_id)), bank, release_id)
98
+
99
+
100
+ def targets(client, bank, release_id, *, after=None, through_version=None, limit=20) -> ReviewTargetPage:
101
+ path = _path(bank, release_id) + '/targets'
102
+ query = _query(after=after, through_version=through_version, limit=limit, maximum=20, target_cursor=True)
103
+ return _page(ReviewTargetPage, client, path, query, bank, release_id)
104
+
105
+
106
+ def history(client, bank, release_id, *, after=0, through_version=None, limit=None, protected=False) -> ReviewHistoryPage:
107
+ if type(protected) is not bool:
108
+ raise WorkflowContractError('protected history selection must be boolean')
109
+ maximum = 10 if protected else 100
110
+ path = _path(bank, release_id) + '/history'
111
+ query = _query(after=after, through_version=through_version, limit=maximum if limit is None else limit, maximum=maximum)
112
+ if protected:
113
+ query['protected'] = 'true'
114
+ return _page(ReviewHistoryPage, client, path, query, bank, release_id, protected=protected)
115
+
116
+
117
+ def details(client, bank, release_id, target_id, *, after=0, through_version=None, limit=10) -> TargetReviewPage:
118
+ path = _path(bank, release_id) + '/targets/' + quote(_identifier(target_id), safe='') + '/reviews'
119
+ query = _query(after=after, through_version=through_version, limit=limit, maximum=10)
120
+ result = _page(TargetReviewPage, client, path, query, bank, release_id)
121
+ if result.target_id != target_id:
122
+ raise _invalid()
123
+ return result
124
+
125
+
126
+ def write(client, bank: str, release_id: str | None, operation: Literal['create', 'contribute', 'adjudicate', 'transition'],
127
+ request: AuditRequest) -> ReviewOperationReceipt:
128
+ kind, suffix, event_kind, maximum = {
129
+ 'create': (BankReleaseDeclaration, '', 'release_created', 262144),
130
+ 'contribute': (ReviewContribution, '/contributions', 'contribution', 131072),
131
+ 'adjudicate': (ReviewAdjudication, '/adjudications', 'adjudication', 16384),
132
+ 'transition': (BankReleaseTransition, '/transitions', None, 16384),
133
+ }[operation]
134
+ if not isinstance(request, kind):
135
+ raise WorkflowContractError('audit operation needs its matching immutable request type')
136
+ path = _path(bank, release_id) + suffix
137
+ if operation == 'create':
138
+ assert isinstance(request, BankReleaseDeclaration)
139
+ release_id = request.release_id
140
+ assert release_id is not None
141
+ body = request.to_dict()
142
+ _key(request.idempotency_key)
143
+ if isinstance(request, ReviewContribution):
144
+ _identifier(request.target_id); _positive_version(request.expected_version)
145
+ if request.supersedes_contribution_id is not None:
146
+ _identifier(request.supersedes_contribution_id)
147
+ _bounded(body['judgment'], 65536)
148
+ _bounded(body, maximum)
149
+ # Existing Client serializes non-ASCII as escapes. Bound the actual request
150
+ # bytes as well as the decoded domain JSON, before any network work.
151
+ if len(json.dumps(body, separators=(',', ':'), allow_nan=False).encode()) > maximum:
152
+ raise WorkflowContractError('encoded audit request exceeds its route body limit')
153
+ try:
154
+ raw = client._request('POST', path, body, retry_key=request.idempotency_key)
155
+ try:
156
+ result = ReviewOperationReceipt.from_dict(raw)
157
+ expected_kind = request.action if isinstance(request, BankReleaseTransition) else event_kind
158
+ event = result.event
159
+ if ((event.bank_id, event.release_id, event.kind, result.operation_key) !=
160
+ (bank, release_id, expected_kind, request.idempotency_key)):
161
+ raise WorkflowContractError('audit receipt belongs to another operation')
162
+ if not isinstance(request, BankReleaseDeclaration) and event.version != request.expected_version + 1:
163
+ raise WorkflowContractError('audit receipt version differs from the original write')
164
+ if isinstance(request, BankReleaseDeclaration) and event.version != 1:
165
+ raise WorkflowContractError('new release receipt must have version one')
166
+ if isinstance(request, (ReviewContribution, ReviewAdjudication)) and event.details.get('target_id') != request.target_id:
167
+ raise WorkflowContractError('audit receipt belongs to another target')
168
+ return result
169
+ except (WorkflowContractError, ValueError, TypeError, KeyError, AttributeError, RecursionError):
170
+ raise _invalid() from None
171
+ except HostedClientError as error:
172
+ raise AuditRequestError(error, bank_id=bank, release_id=release_id,
173
+ operation=operation, request=request) from None
@@ -0,0 +1,113 @@
1
+ """Explicit projections of already verified permitted native comparisons.
2
+
3
+ These functions neither authenticate nor compare evidence. Their backend caller
4
+ must retain native verification and compatibility gates before projection.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ from .comparison_views import ComparisonMetric, ComparisonPair, ComparisonView
9
+ from .workflow_contracts import _thaw
10
+
11
+
12
+ def _sources(backend, baseline, candidate, *, conditions=None):
13
+ sources = []
14
+ for index, ref in enumerate((baseline, candidate)):
15
+ binding = backend.reference(ref, "grading")
16
+ source = {"grading_id": binding.record_id, "run_id": binding.run_id,
17
+ "status": binding.status, "trust_tier": binding.trust_tier,
18
+ "metadata": _thaw(binding.metadata)}
19
+ if conditions is not None:
20
+ source["conditions"] = conditions[index]
21
+ sources.append(source)
22
+ return {"baseline": sources[0], "candidate": sources[1], "backend": backend.descriptor.to_dict()}
23
+
24
+
25
+ def _view(sources, native, metrics=(), *, reason=None, status=None):
26
+ if status is None:
27
+ status = "incomplete" if any(item.status == "incomplete" for item in metrics) else "supported"
28
+ return ComparisonView(**sources, status=status, native=native, metrics=tuple(metrics), reason=reason)
29
+
30
+
31
+ def _rerank_view(sources, native, specs):
32
+ if not native["compatible"]:
33
+ return _view(sources, native, status="incompatible", reason=native["reason"])
34
+ metrics = []
35
+ for identifier, data in native["metrics"].items():
36
+ spec = specs[identifier]
37
+ pairs = []
38
+ for row in data["per_case"]:
39
+ available = row["delta_b_minus_a"] is not None
40
+ pairs.append(ComparisonPair({"case_id": row["case_id"]}, row["value_a"], row["value_b"],
41
+ row["delta_b_minus_a"], "available" if available else
42
+ "undefined" if row["reason"] == "unsupported_query" else "missing", row["reason"]))
43
+ status = "supported" if data["complete"] else "incomplete" if data["missing_pair_count"] else "unavailable"
44
+ metrics.append(ComparisonMetric(identifier, data["unit"], data["direction"], "candidate_minus_baseline",
45
+ status, _thaw(spec.population),
46
+ {"policy_id": spec.comparison_policy, "aggregation": "equal_case_mean_of_paired_differences",
47
+ "sampling_unit": "case_id", "uncertainty": "not_estimated"},
48
+ delta=data["mean_delta"], observed_delta=data["observed_mean_delta"],
49
+ counts={key: data[key] for key in ("eligible_count", "paired_count", "missing_pair_count", "undefined_count")},
50
+ pairs=tuple(pairs), reason="no_eligible_observed_pairs" if status == "unavailable" else None))
51
+ return _view(sources, native, metrics)
52
+
53
+
54
+ def _integrated_view(sources, native, specs):
55
+ metrics = []
56
+ for identifier, data in native["metrics"].items():
57
+ spec = specs[identifier]
58
+ pairs = tuple(ComparisonPair({"case_id": row["case_id"], "trial": row["trial"]},
59
+ row["baseline"], row["candidate"], row["delta"], row["availability"],
60
+ None if row["availability"] == "available" else
61
+ "baseline=" + str(row["baseline_reason"]) + "; candidate=" + str(row["candidate_reason"]),
62
+ {key: row[key] for key in ("baseline_availability", "candidate_availability", "baseline_reason", "candidate_reason")})
63
+ for row in native["pairs"] if row["criterion_id"] == identifier)
64
+ status = "incomplete" if data["excluded_pairs"] else "supported"
65
+ metrics.append(ComparisonMetric(identifier, spec.unit, spec.direction, "candidate_minus_baseline", status,
66
+ data["population"], {"policy_id": native["policy_id"],
67
+ "aggregation": "mean_available_trial_deltas_within_case_then_equal_case_mean",
68
+ "grouping_keys": ["case_id"], "paired_unit": ["case_id", "trial"],
69
+ "trials_independent": False, "uncertainty": "not_estimated"},
70
+ delta=data["candidate_minus_baseline"] if status == "supported" else None,
71
+ observed_delta=data["candidate_minus_baseline"],
72
+ counts={key: data[key] for key in ("planned_pairs", "eligible_pairs", "excluded_pairs", "eligible_cases",
73
+ "baseline_ungraded_trials", "candidate_ungraded_trials")}, pairs=pairs))
74
+ return _view(sources, native, metrics, reason=native["reason"])
75
+
76
+
77
+ def _pipeline_view(sources, native, specs, reports):
78
+ metrics = []
79
+ for stage, values in native["delta"].items():
80
+ for name, delta in values.items():
81
+ identifier = "sphynx.pipeline." + stage + "." + name
82
+ spec = specs[identifier]
83
+ accounting = [report["metrics"]["stage_metric_accounting"][stage][name] for report in reports]
84
+ missing = any(item["missing_count"] for item in accounting)
85
+ status = "incomplete" if missing else "unavailable" if delta is None else "supported"
86
+ metrics.append(ComparisonMetric(identifier, spec.unit, spec.direction,
87
+ "candidate_minus_baseline", status,
88
+ {**_thaw(spec.population), "boundary": "native_stage_retained_inherited_pool",
89
+ "eligibility": "native_side_specific_metric_eligibility"},
90
+ {"policy_id": spec.comparison_policy, "aggregation": "difference_of_native_stage_aggregates",
91
+ "pair_details": "not_exposed", "uncertainty": "not_estimated",
92
+ "timing_comparable": native["timing_comparable"]},
93
+ delta=delta if status == "supported" else None, observed_delta=delta,
94
+ counts={side + "_" + key: value for side, item in zip(("baseline", "candidate"), accounting)
95
+ for key, value in item.items()},
96
+ reason="native_aggregate_unavailable" if status == "unavailable" else None))
97
+ return _view(sources, native, metrics)
98
+
99
+
100
+ def _score_reliability_view(sources, native):
101
+ """Retain native cluster-bootstrap meaning; callers verify source bindings."""
102
+ partial = native["conditional_on_judged_subset"]
103
+ value = native["mean_improvement"]
104
+ status = "unavailable" if value is None else "incomplete" if partial else "supported"
105
+ metric = ComparisonMetric("brier_score", "squared_probability_error", "lower",
106
+ native["direction"], status,
107
+ {"reference_digest": native["reference_digest"], "label_policy": native["label_policy"],
108
+ "conditional_on_judged_subset": partial},
109
+ {"aggregation": native["aggregation"], "paired_unit": "query_id", "grouping_keys": ["cluster_id"],
110
+ "trials_independent": False}, delta=value if status == "supported" else None,
111
+ observed_delta=value, uncertainty=native["bootstrap"],
112
+ reason="no_observed_pairs" if status == "unavailable" else None)
113
+ return _view(sources, native, (metric,))
@@ -0,0 +1,34 @@
1
+ """Controlled router/rewriter/generator diagnostic over an explicit context."""
2
+ from copy import deepcopy
3
+ from .contracts import ContractError
4
+
5
+
6
+ def compose_full_rag(request,invoke):
7
+ """Call registered components; this boundary does not execute retrieval."""
8
+ stages={"router":None,"rewriter":None,"generator":None}
9
+ def call(kind,operation,payload):
10
+ result=invoke(kind,operation,deepcopy(payload))
11
+ if type(result) is not dict:raise ContractError("component output must be an object")
12
+ stages[kind]=deepcopy(result);return result
13
+ routing=call("router","route",{"task_id":request["case_id"],"query":request["query"],"conversation_context":request["conversation_context"]})
14
+ should_call=routing.get("should_call_rag")
15
+ if type(should_call) is not bool:raise ContractError("router must declare a boolean action")
16
+ rewritten=None;answer=None;citations=[];contexts=[];abstained=True
17
+ if should_call:
18
+ rewrite=call("rewriter","rewrite",{"task_id":request["case_id"],"query":request["query"]})
19
+ rewritten=rewrite.get("rewritten_query")
20
+ if type(rewritten) is not str or not rewritten.strip():raise ContractError("rewriter query is incomplete")
21
+ by_id={row["candidate_id"]:row for row in request["candidates"]}
22
+ contexts=list(request["context_candidate_ids"])
23
+ output=call("generator","generate",{"task_id":request["case_id"],"query":rewritten,
24
+ "contexts":[by_id[cid] for cid in contexts]})
25
+ answer=output.get("answer");abstained=output.get("abstained",False);citations=output.get("citations",[])
26
+ if type(answer) is not str or type(abstained) is not bool:raise ContractError("generator answer/action differs")
27
+ trace={"schema_version":"sable.rag.level3-trace.v1","task_id":request["case_id"],
28
+ "actual_rag_call_count":int(should_call),"call_outcome":"success" if should_call else "no_call",
29
+ "error":None,"as_uttered_query":request["query"],"rewritten_query":rewritten,
30
+ "retrieved_candidate_ids":contexts,"retrieved_context_ids":contexts,"handed_context_ids":contexts,
31
+ "answer":answer,"citations":citations,"abstained":abstained,"deferred":False,
32
+ "token_counts":{"prompt":0,"completion":0},"latency_ms":{}}
33
+ return {"schema_version":"sphynx.rag.full_rag.response.v1","case_id":request["case_id"],
34
+ "input_digest":request["input_digest"],"trace":trace,"stages":stages}