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
sphynx/benchmarks.py ADDED
@@ -0,0 +1,568 @@
1
+ """Immutable benchmark/label metadata contracts; no execution or data downloads.
2
+
3
+ Eligibility validates declared metadata, not receipt bytes or human approval.
4
+ The only default release is the existing public illustrative synthetic fixture.
5
+ Legacy evaluation records and comparison digests are not changed by this layer.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import asdict, dataclass, fields
10
+ from datetime import datetime
11
+ import hashlib
12
+ import json
13
+ from pathlib import Path
14
+ import re
15
+ from typing import Any
16
+
17
+ from .evals import EvaluationContractError, canonical_contract_json
18
+
19
+ LABEL_SCHEMA = "sphynx.label-set-release.v1"
20
+ BENCHMARK_SCHEMA = "sphynx.benchmark-release.v1"
21
+ CATALOG_SCHEMA = "sphynx.benchmark-catalog.v1"
22
+ ELIGIBILITY_SCHEMA = "sphynx.benchmark-eligibility.v1"
23
+ PROJECTION_VERSION = "sphynx.synthetic-rerank.metadata-projections.v1"
24
+ DEFAULT_BENCHMARK = "synthetic.rerank.fixture@2.0.0"
25
+ DEFAULT_LABELS = "synthetic.rerank.labels@2.0.0"
26
+ LEGACY_BANK = "help-center-evidence-mini@2.0.0"
27
+ _ID = re.compile(r"[A-Za-z0-9][A-Za-z0-9._:@/-]{0,255}\Z")
28
+ _DIGEST = re.compile(r"sha256:[0-9a-f]{64}\Z")
29
+ _AUTHORITY = {"synthetic_fixture", "native_public_qrels", "owner_reviewed", "human_adjudicated", "machine_provisional"}
30
+ _PROFILES = {"routing_actions", "passage_qrels", "required_evidence", "passage_qrels_and_evidence_groups", "answer_references", "query_intent"}
31
+ _METRICS = {
32
+ "rag.invoke.metric.v1": ("routing_actions", "sphynx.rag.invoke.v1"),
33
+ "rag.rerank.metric.v1": ("passage_qrels_and_evidence_groups", "sphynx.rag.rerank.v1"),
34
+ "rag.rerank.beir.metric.v1": ("passage_qrels", "sphynx.rag.rerank.v1"),
35
+ "rag.retrieve.metric.v1": ("required_evidence", "sphynx.rag.retrieve.v1"),
36
+ }
37
+
38
+
39
+ class BenchmarkContractError(ValueError):
40
+ """Stable payload-free diagnostic; metadata errors never expose data bytes."""
41
+ def __init__(self, code):
42
+ self.code = code
43
+ super().__init__(code)
44
+
45
+
46
+ def _require(condition, code):
47
+ if not condition:
48
+ raise BenchmarkContractError(code)
49
+
50
+
51
+ def _id(value):
52
+ _require(type(value) is str and _ID.fullmatch(value), "invalid_identifier")
53
+
54
+
55
+ def _digest(value):
56
+ _require(type(value) is str and _DIGEST.fullmatch(value), "invalid_digest")
57
+
58
+
59
+ def _choice(value, options):
60
+ _require(type(value) is str and value in options, "unsupported_option")
61
+
62
+
63
+ def _count(value, maximum=2**53 - 1):
64
+ _require(type(value) is int and 0 <= value <= maximum, "invalid_count")
65
+
66
+
67
+ def _tuples(values, kind, *, maximum=512):
68
+ _require(type(values) is tuple and len(values) <= maximum and all(isinstance(v, kind) for v in values), "invalid_collection")
69
+
70
+
71
+ def _ids(values):
72
+ _tuples(values, str)
73
+ for value in values:
74
+ _id(value)
75
+ _require(len(set(values)) == len(values), "duplicate_identifier")
76
+
77
+
78
+ def record_digest(value):
79
+ try:
80
+ return "sha256:" + hashlib.sha256(canonical_contract_json(value).encode()).hexdigest()
81
+ except EvaluationContractError:
82
+ raise BenchmarkContractError("invalid_canonical_metadata") from None
83
+
84
+
85
+ class _Record:
86
+ def to_dict(self):
87
+ return json.loads(canonical_contract_json(asdict(self)))
88
+
89
+ @classmethod
90
+ def _values(cls, value):
91
+ _require(type(value) is dict and set(value) == {field.name for field in fields(cls)}, "invalid_record_shape")
92
+ return dict(value)
93
+
94
+
95
+ @dataclass(frozen=True)
96
+ class AssetRef(_Record):
97
+ ref: str
98
+ version: str
99
+ digest: str
100
+
101
+ def __post_init__(self):
102
+ _id(self.ref); _id(self.version); _digest(self.digest)
103
+
104
+ @classmethod
105
+ def from_dict(cls, value):
106
+ return cls(**cls._values(value))
107
+
108
+
109
+ def _asset(value, *, optional=False):
110
+ _require(isinstance(value, AssetRef) or optional and value is None, "invalid_asset_reference")
111
+
112
+
113
+ @dataclass(frozen=True)
114
+ class ReviewReceipt(_Record):
115
+ receipt: AssetRef
116
+ reviewer_id: str
117
+ reviewed_at_utc: str
118
+ case_ids: tuple[str, ...]
119
+ kind: str
120
+ policy_revision: str
121
+ rubric_revision: str
122
+
123
+ def __post_init__(self):
124
+ _asset(self.receipt); _id(self.reviewer_id); _ids(self.case_ids)
125
+ _require(bool(self.case_ids), "empty_review_scope")
126
+ _id(self.policy_revision); _id(self.rubric_revision)
127
+ _choice(self.kind, {"review", "adjudication"})
128
+ try:
129
+ _require(type(self.reviewed_at_utc) is str and re.fullmatch(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,6})?Z", self.reviewed_at_utc), "invalid_review_timestamp")
130
+ datetime.fromisoformat(self.reviewed_at_utc.replace("Z", "+00:00"))
131
+ except ValueError:
132
+ raise BenchmarkContractError("invalid_review_timestamp") from None
133
+
134
+ @classmethod
135
+ def from_dict(cls, value):
136
+ value = cls._values(value)
137
+ _require(type(value["case_ids"]) is list, "invalid_collection")
138
+ value.update(receipt=AssetRef.from_dict(value["receipt"]), case_ids=tuple(value["case_ids"]))
139
+ return cls(**value)
140
+
141
+
142
+ @dataclass(frozen=True)
143
+ class LabelCase(_Record):
144
+ case_id: str
145
+ input_digest: str
146
+ status: str
147
+
148
+ def __post_init__(self):
149
+ _id(self.case_id); _digest(self.input_digest)
150
+ _choice(self.status, {"judged", "unjudged", "blocked"})
151
+
152
+ @classmethod
153
+ def from_dict(cls, value):
154
+ return cls(**cls._values(value))
155
+
156
+
157
+ @dataclass(frozen=True)
158
+ class LabelLineage(_Record):
159
+ parent_ref: str
160
+ parent_digest: str
161
+ relationship: str
162
+ reason: str
163
+ changed_case_count: int
164
+ evidence: AssetRef | None
165
+
166
+ def __post_init__(self):
167
+ _id(self.parent_ref); _digest(self.parent_digest)
168
+ _choice(self.relationship, {"revision", "subset"})
169
+ _choice(self.reason, {"labels_corrected", "policy_changed", "rubric_changed", "subset_selected"})
170
+ _count(self.changed_case_count); _asset(self.evidence, optional=True)
171
+ _require(self.changed_case_count == 0 or self.evidence is not None, "lineage_evidence_required")
172
+ _require(self.reason != "subset_selected" or self.changed_case_count == 0, "changed_labels_need_revision_reason")
173
+ _require(self.reason != "subset_selected" or self.relationship == "subset", "lineage_subset_reason_mismatch")
174
+
175
+ @classmethod
176
+ def from_dict(cls, value):
177
+ value = cls._values(value)
178
+ value["evidence"] = None if value["evidence"] is None else AssetRef.from_dict(value["evidence"])
179
+ return cls(**value)
180
+
181
+
182
+ @dataclass(frozen=True)
183
+ class LabelSetRelease(_Record):
184
+ schema_version: str
185
+ ref: str
186
+ version: str
187
+ input: AssetRef
188
+ corpus: AssetRef | None
189
+ reference: AssetRef
190
+ label_profile: str
191
+ policy_revision: str
192
+ rubric_revision: str
193
+ authority: str
194
+ missingness: str
195
+ cases: tuple[LabelCase, ...]
196
+ review_receipts: tuple[ReviewReceipt, ...]
197
+ lineage: tuple[LabelLineage, ...]
198
+
199
+ def __post_init__(self):
200
+ _require(self.schema_version == LABEL_SCHEMA, "unsupported_label_schema")
201
+ _id(self.ref); _id(self.version); _asset(self.input); _asset(self.corpus, optional=True); _asset(self.reference)
202
+ _choice(self.label_profile, _PROFILES); _id(self.policy_revision); _id(self.rubric_revision)
203
+ _choice(self.authority, _AUTHORITY)
204
+ _choice(self.missingness, {"complete_required", "native_qrels_unjudged", "development_assume_zero"})
205
+ _tuples(self.cases, LabelCase); _tuples(self.review_receipts, ReviewReceipt); _tuples(self.lineage, LabelLineage, maximum=16)
206
+ ids = tuple(case.case_id for case in self.cases)
207
+ _ids(ids); _require(bool(ids), "empty_label_release")
208
+ _require(len({r.receipt.ref for r in self.review_receipts}) == len(self.review_receipts), "duplicate_review_receipt")
209
+ _require(len({parent.parent_ref for parent in self.lineage}) == len(self.lineage), "duplicate_lineage_parent")
210
+ _require(self.authority != "native_public_qrels" or self.label_profile == "passage_qrels", "authority_profile_mismatch")
211
+ _require(self.missingness != "native_qrels_unjudged" or self.authority == "native_public_qrels", "missingness_authority_mismatch")
212
+ _require(self.missingness != "development_assume_zero" or self.authority == "machine_provisional", "missingness_authority_mismatch")
213
+ _require(self.authority in {"owner_reviewed", "human_adjudicated"} or not self.review_receipts, "unexpected_human_receipts")
214
+ for receipt in self.review_receipts:
215
+ _require(set(receipt.case_ids) <= set(ids), "review_scope_mismatch")
216
+ _require(receipt.policy_revision == self.policy_revision and receipt.rubric_revision == self.rubric_revision, "review_policy_mismatch")
217
+ self.digest
218
+
219
+ @property
220
+ def digest(self):
221
+ return record_digest(self.to_dict())
222
+
223
+ @property
224
+ def asset_ref(self):
225
+ return AssetRef(self.ref, self.version, self.digest)
226
+
227
+ @classmethod
228
+ def from_dict(cls, value):
229
+ value = cls._values(value)
230
+ for key in ("input", "reference"):
231
+ value[key] = AssetRef.from_dict(value[key])
232
+ value["corpus"] = None if value["corpus"] is None else AssetRef.from_dict(value["corpus"])
233
+ for key, kind in (("cases", LabelCase), ("review_receipts", ReviewReceipt), ("lineage", LabelLineage)):
234
+ _require(type(value[key]) is list, "invalid_collection")
235
+ value[key] = tuple(kind.from_dict(item) for item in value[key])
236
+ return cls(**value)
237
+
238
+
239
+ @dataclass(frozen=True)
240
+ class BenchmarkCase(_Record):
241
+ case_id: str
242
+ input_digest: str
243
+ company: str
244
+ split: str
245
+ input_form: str
246
+ form_status: str
247
+ form_receipt: AssetRef | None
248
+ length_words: int
249
+
250
+ def __post_init__(self):
251
+ _id(self.case_id); _digest(self.input_digest); _id(self.company); _id(self.split)
252
+ _require(self.split != "all", "reserved_split_name")
253
+ _choice(self.input_form, {"single_sentence", "paragraph", "not_applicable", "unclassified"})
254
+ _choice(self.form_status, {"reviewed", "authored", "unclassified"})
255
+ _asset(self.form_receipt, optional=True); _count(self.length_words, 1_000_000)
256
+ _require((self.form_status == "reviewed") == (self.form_receipt is not None), "form_receipt_mismatch")
257
+ _require((self.input_form == "unclassified") == (self.form_status == "unclassified"), "form_classification_mismatch")
258
+
259
+ @classmethod
260
+ def from_dict(cls, value):
261
+ value = cls._values(value)
262
+ value["form_receipt"] = None if value["form_receipt"] is None else AssetRef.from_dict(value["form_receipt"])
263
+ return cls(**value)
264
+
265
+
266
+ @dataclass(frozen=True)
267
+ class BenchmarkRelease(_Record):
268
+ schema_version: str
269
+ ref: str
270
+ version: str
271
+ evaluation_id: str
272
+ protocol_revision: str
273
+ interface_version: str
274
+ scope: str
275
+ input: AssetRef
276
+ corpus: AssetRef | None
277
+ label_set: AssetRef
278
+ required_label_profile: str
279
+ metric_profile: str
280
+ required_input_form: str
281
+ intended_use: str
282
+ cases: tuple[BenchmarkCase, ...]
283
+
284
+ def __post_init__(self):
285
+ _require(self.schema_version == BENCHMARK_SCHEMA, "unsupported_benchmark_schema")
286
+ for value in (self.ref, self.version, self.evaluation_id, self.protocol_revision, self.interface_version):
287
+ _id(value)
288
+ _choice(self.scope, {"component", "pipeline", "complete_system"})
289
+ _asset(self.input); _asset(self.corpus, optional=True); _asset(self.label_set)
290
+ _choice(self.required_input_form, {"any", "single_sentence", "paragraph"})
291
+ _choice(self.intended_use, {"illustrative", "development", "benchmark", "protected"})
292
+ _require(type(self.metric_profile) is str and self.metric_profile in _METRICS and _METRICS[self.metric_profile] == (self.required_label_profile, self.interface_version), "metric_label_interface_mismatch")
293
+ _tuples(self.cases, BenchmarkCase); _ids(tuple(case.case_id for case in self.cases))
294
+ _require(bool(self.cases), "empty_benchmark_release")
295
+ self.digest
296
+
297
+ @property
298
+ def digest(self):
299
+ return record_digest(self.to_dict())
300
+
301
+ @property
302
+ def asset_ref(self):
303
+ return AssetRef(self.ref, self.version, self.digest)
304
+
305
+ @classmethod
306
+ def from_dict(cls, value):
307
+ value = cls._values(value)
308
+ for key in ("input", "label_set"):
309
+ value[key] = AssetRef.from_dict(value[key])
310
+ value["corpus"] = None if value["corpus"] is None else AssetRef.from_dict(value["corpus"])
311
+ _require(type(value["cases"]) is list, "invalid_collection")
312
+ value["cases"] = tuple(BenchmarkCase.from_dict(item) for item in value["cases"])
313
+ return cls(**value)
314
+
315
+
316
+ @dataclass(frozen=True)
317
+ class SliceParameters(_Record):
318
+ companies: tuple[str, ...] = ()
319
+ split: str = "all"
320
+ length_words: tuple[int, int] | None = None
321
+
322
+ def __post_init__(self):
323
+ _ids(self.companies); _id(self.split)
324
+ if self.length_words is not None:
325
+ _require(type(self.length_words) is tuple and len(self.length_words) == 2, "invalid_length_range")
326
+ for value in self.length_words:
327
+ _count(value, 1_000_000)
328
+ _require(self.length_words[0] <= self.length_words[1], "invalid_length_range")
329
+
330
+ def to_dict(self):
331
+ return {"companies": list(self.companies), "split": self.split,
332
+ "length_words": None if self.length_words is None else dict(zip(("minimum", "maximum"), self.length_words))}
333
+
334
+ @classmethod
335
+ def from_dict(cls, value):
336
+ _require(type(value) is dict and set(value) <= {"companies", "split", "length_words"}, "invalid_slice_parameters")
337
+ companies, length = value.get("companies", []), value.get("length_words")
338
+ _require(type(companies) is list, "invalid_collection")
339
+ if length is not None:
340
+ _require(type(length) is dict and set(length) == {"minimum", "maximum"}, "invalid_length_range")
341
+ length = (length["minimum"], length["maximum"])
342
+ return cls(tuple(companies), value.get("split", "all"), length)
343
+
344
+
345
+ @dataclass(frozen=True)
346
+ class BenchmarkEligibility:
347
+ """A detached compatibility report, not approval, scoring or execution."""
348
+ _json: str
349
+
350
+ def __post_init__(self):
351
+ try:
352
+ value = json.loads(self._json)
353
+ except (ValueError, TypeError):
354
+ raise BenchmarkContractError("invalid_eligibility_record") from None
355
+ required = {"schema_version", "benchmark", "label_set", "evaluation_id", "protocol_revision", "interface_version", "scope", "metric_profile", "authority", "missingness", "intended_use", "purpose", "metadata_only", "review_receipts_verified", "semantic_authority_validated", "selected_case_ids", "selected_case_count", "judged_case_count", "unjudged_case_count", "blocked_case_count", "form_blocked_case_count", "status", "blockers", "slice"}
356
+ _require(type(value) is dict and set(value) == required and value["schema_version"] == ELIGIBILITY_SCHEMA, "invalid_eligibility_record")
357
+ _require(value["metadata_only"] is True and value["review_receipts_verified"] is False and value["semantic_authority_validated"] is False, "invalid_eligibility_trust_claim")
358
+ AssetRef.from_dict(value["benchmark"]); AssetRef.from_dict(value["label_set"])
359
+ for key in ("evaluation_id", "protocol_revision", "interface_version", "metric_profile"):
360
+ _id(value[key])
361
+ _choice(value["scope"], {"component", "pipeline", "complete_system"})
362
+ _choice(value["authority"], _AUTHORITY)
363
+ _choice(value["missingness"], {"complete_required", "native_qrels_unjudged", "development_assume_zero"})
364
+ _choice(value["intended_use"], {"illustrative", "development", "benchmark", "protected"})
365
+ _choice(value["purpose"], {"development_instrumentation", "evaluation"})
366
+ _choice(value["status"], {"eligible", "blocked"})
367
+ _require(type(value["selected_case_ids"]) is list and type(value["blockers"]) is list, "invalid_eligibility_record")
368
+ _ids(tuple(value["selected_case_ids"])); _ids(tuple(value["blockers"]))
369
+ for key in ("selected_case_count", "judged_case_count", "unjudged_case_count", "blocked_case_count", "form_blocked_case_count"):
370
+ _count(value[key], 512)
371
+ _require(0 < value["selected_case_count"] == len(value["selected_case_ids"]) and all(value[key] <= value["selected_case_count"] for key in ("judged_case_count", "unjudged_case_count", "blocked_case_count", "form_blocked_case_count")) and value["judged_case_count"] + value["unjudged_case_count"] <= value["selected_case_count"], "invalid_eligibility_counts")
372
+ _require((value["status"] == "eligible") == (not value["blockers"]) and value["blockers"] == sorted(value["blockers"]), "invalid_eligibility_status")
373
+ SliceParameters.from_dict(value["slice"])
374
+ _require(canonical_contract_json(value) == self._json, "invalid_canonical_metadata")
375
+
376
+ def to_dict(self):
377
+ return json.loads(self._json)
378
+
379
+ @property
380
+ def eligible(self):
381
+ return self.to_dict()["status"] == "eligible"
382
+
383
+
384
+ @dataclass(frozen=True)
385
+ class BenchmarkRegistry:
386
+ label_sets: tuple[LabelSetRelease, ...]
387
+ benchmarks: tuple[BenchmarkRelease, ...]
388
+
389
+ def __post_init__(self):
390
+ self.validate()
391
+
392
+ def validate(self):
393
+ _tuples(self.label_sets, LabelSetRelease); _tuples(self.benchmarks, BenchmarkRelease)
394
+ labels = {record.ref: record for record in self.label_sets}
395
+ _require(len(labels) == len(self.label_sets), "duplicate_label_release")
396
+ _require(len({record.ref for record in self.benchmarks}) == len(self.benchmarks), "duplicate_benchmark_release")
397
+ visiting, visited = set(), set()
398
+ def visit(ref):
399
+ _require(ref not in visiting, "lineage_cycle")
400
+ if ref in visited:
401
+ return
402
+ visiting.add(ref)
403
+ current = labels[ref]
404
+ current_cases = {case.case_id: case.input_digest for case in current.cases}
405
+ for link in current.lineage:
406
+ _require(link.parent_ref in labels, "lineage_parent_missing")
407
+ parent = labels[link.parent_ref]
408
+ visit(parent.ref)
409
+ _require(link.parent_digest == parent.digest, "lineage_parent_digest_mismatch")
410
+ _require(current.label_profile == parent.label_profile, "lineage_label_profile_mismatch")
411
+ _require(current.corpus == parent.corpus, "lineage_corpus_mismatch")
412
+ parent_cases = {case.case_id: case.input_digest for case in parent.cases}
413
+ if link.relationship == "revision":
414
+ _require(current.input == parent.input and current_cases == parent_cases, "lineage_input_mismatch")
415
+ else:
416
+ _require(all(parent_cases.get(key) == value for key, value in current_cases.items()), "lineage_subset_mismatch")
417
+ _require(link.changed_case_count <= len(current_cases), "lineage_changed_count_invalid")
418
+ _require(current.policy_revision == parent.policy_revision or link.reason == "policy_changed", "lineage_policy_reason_required")
419
+ _require(current.rubric_revision == parent.rubric_revision or link.reason in {"rubric_changed", "policy_changed"}, "lineage_rubric_reason_required")
420
+ visiting.remove(ref); visited.add(ref)
421
+ for ref in labels:
422
+ visit(ref)
423
+ protocols = {}
424
+ for benchmark in self.benchmarks:
425
+ _require(benchmark.label_set.ref in labels, "label_release_missing")
426
+ label = labels[benchmark.label_set.ref]
427
+ _require(benchmark.label_set == label.asset_ref, "label_release_digest_mismatch")
428
+ _require((benchmark.input, benchmark.corpus) == (label.input, label.corpus), "benchmark_input_binding_mismatch")
429
+ _require({c.case_id: c.input_digest for c in benchmark.cases} == {c.case_id: c.input_digest for c in label.cases}, "benchmark_case_binding_mismatch")
430
+ _require(benchmark.required_label_profile == label.label_profile, "benchmark_label_profile_mismatch")
431
+ _require(label.authority != "synthetic_fixture" or benchmark.intended_use == "illustrative", "synthetic_authority_not_benchmark")
432
+ _require(label.authority != "machine_provisional" or benchmark.intended_use == "development", "provisional_authority_not_benchmark")
433
+ _require(benchmark.intended_use != "protected" or label.authority in {"owner_reviewed", "human_adjudicated"}, "protected_authority_required")
434
+ key = (benchmark.evaluation_id, benchmark.protocol_revision)
435
+ boundary = (benchmark.interface_version, benchmark.scope, benchmark.required_label_profile, benchmark.metric_profile, benchmark.required_input_form)
436
+ _require(key not in protocols or protocols[key] == boundary, "protocol_boundary_mismatch")
437
+ protocols[key] = boundary
438
+ return self
439
+
440
+ def to_dict(self):
441
+ return {"schema_version": CATALOG_SCHEMA, "label_sets": [record.to_dict() for record in self.label_sets],
442
+ "benchmarks": [record.to_dict() for record in self.benchmarks]}
443
+
444
+ def resolve(self, benchmark_ref, parameters=None):
445
+ benchmark = next((b for b in self.benchmarks if b.ref == benchmark_ref), None)
446
+ _require(benchmark is not None, "benchmark_not_registered")
447
+ parameters = SliceParameters() if parameters is None else parameters
448
+ if type(parameters) is dict:
449
+ parameters = SliceParameters.from_dict(parameters)
450
+ _require(isinstance(parameters, SliceParameters), "invalid_slice_parameters")
451
+ _require(set(parameters.companies) <= {case.company for case in benchmark.cases}, "company_not_supported")
452
+ _require(parameters.split == "all" or parameters.split in {case.split for case in benchmark.cases}, "split_not_supported")
453
+ selected = [case for case in benchmark.cases if
454
+ (not parameters.companies or case.company in parameters.companies)
455
+ and (parameters.split == "all" or case.split == parameters.split)
456
+ and (parameters.length_words is None or parameters.length_words[0] <= case.length_words <= parameters.length_words[1])]
457
+ _require(bool(selected), "empty_benchmark_slice")
458
+ _require(not parameters.companies or {case.company for case in selected} == set(parameters.companies), "unsupported_joint_slice")
459
+ label = next(record for record in self.label_sets if record.ref == benchmark.label_set.ref)
460
+ labels = {case.case_id: case for case in label.cases}
461
+ reviewed = {key for receipt in label.review_receipts for key in receipt.case_ids
462
+ if label.authority != "human_adjudicated" or receipt.kind == "adjudication"}
463
+ blocked, forms, blockers = set(), set(), set()
464
+ judged = unjudged = 0
465
+ for case in selected:
466
+ status = labels[case.case_id].status
467
+ judged += status == "judged"; unjudged += status == "unjudged"
468
+ if status == "blocked":
469
+ blocked.add(case.case_id); blockers.add("blocked_reference_cases")
470
+ if status == "unjudged" and label.missingness == "complete_required":
471
+ blockers.add("required_labels_missing")
472
+ if status == "judged" and label.authority in {"owner_reviewed", "human_adjudicated"} and case.case_id not in reviewed:
473
+ blocked.add(case.case_id); blockers.add("review_coverage_incomplete")
474
+ if benchmark.required_input_form != "any" and (case.input_form != benchmark.required_input_form
475
+ or case.form_status != "reviewed" and benchmark.intended_use != "illustrative"):
476
+ forms.add(case.case_id); blockers.add("reviewed_input_form_required")
477
+ value = {"schema_version": ELIGIBILITY_SCHEMA, "benchmark": benchmark.asset_ref.to_dict(),
478
+ "label_set": label.asset_ref.to_dict(), "evaluation_id": benchmark.evaluation_id,
479
+ "protocol_revision": benchmark.protocol_revision, "interface_version": benchmark.interface_version,
480
+ "scope": benchmark.scope, "metric_profile": benchmark.metric_profile, "authority": label.authority,
481
+ "missingness": label.missingness,
482
+ "intended_use": benchmark.intended_use,
483
+ "purpose": "development_instrumentation" if label.authority == "synthetic_fixture" else "evaluation",
484
+ "metadata_only": True, "review_receipts_verified": False, "semantic_authority_validated": False,
485
+ "selected_case_ids": [case.case_id for case in selected], "selected_case_count": len(selected),
486
+ "judged_case_count": judged, "unjudged_case_count": unjudged, "blocked_case_count": len(blocked),
487
+ "form_blocked_case_count": len(forms), "status": "blocked" if blockers else "eligible",
488
+ "blockers": sorted(blockers), "slice": parameters.to_dict()}
489
+ return BenchmarkEligibility(canonical_contract_json(value))
490
+
491
+ def validate_legacy(self, resolved_record):
492
+ """Gate a trusted resolved legacy invocation without rewriting its bytes."""
493
+ try:
494
+ from .evals import resolve_eval
495
+ definition, parameters = resolved_record["definition"], resolved_record["parameters"]
496
+ expected = resolve_eval("rag.rerank.v1", parameters).to_dict()
497
+ _require(definition == expected["definition"] and parameters == expected["parameters"]
498
+ and resolved_record["artifacts"] == expected["artifacts"],
499
+ "legacy_benchmark_binding_mismatch")
500
+ except (KeyError, TypeError, EvaluationContractError):
501
+ raise BenchmarkContractError("legacy_benchmark_binding_mismatch") from None
502
+ eligibility = self.resolve(DEFAULT_BENCHMARK)
503
+ _require(eligibility.eligible, "legacy_benchmark_not_eligible")
504
+ return eligibility
505
+
506
+
507
+ def synthetic_projection_metadata():
508
+ """Derived digests only; do not claim independently archived input/label assets."""
509
+ from .contracts import build_candidate_request, canonical_json
510
+ from .rag import FIXTURE_SHA256, load_rag_rerank_pack
511
+ pack = load_rag_rerank_pack()
512
+ requests = [build_candidate_request(case).to_dict() for case in pack.cases]
513
+ def projection_digest(value):
514
+ return "sha256:" + hashlib.sha256(canonical_json(value).encode()).hexdigest()
515
+ inputs = {"schema_version": "sphynx.synthetic-rerank.input-projection.v1", "cases": requests}
516
+ corpus = {"schema_version": "sphynx.synthetic-rerank.corpus-projection.v1", "cases": [
517
+ {"case_id": request["case_id"], "versions": request["versions"], "candidates": [
518
+ {"candidate_id": candidate["candidate_id"], "input": candidate["input"]} for candidate in request["candidates"]]}
519
+ for request in requests]}
520
+ references = {"schema_version": "sphynx.synthetic-rerank.reference-projection.v1", "cases": [
521
+ asdict(pack.reference_for(case.case_id)) for case in pack.cases]}
522
+ source = AssetRef(LEGACY_BANK, "2.0.0", "sha256:" + FIXTURE_SHA256)
523
+ input_ref = AssetRef("synthetic.rerank.input-projection@2.0.0", "2.0.0", projection_digest(inputs))
524
+ corpus_ref = AssetRef("synthetic.rerank.corpus-projection@2.0.0", "2.0.0", projection_digest(corpus))
525
+ reference_ref = AssetRef("synthetic.rerank.reference-projection@2.0.0", "2.0.0", projection_digest(references))
526
+ cases = tuple(BenchmarkCase(case.case_id, projection_digest(request), "synthetic", case.split,
527
+ "unclassified", "unclassified", None, len(case.query.split())) for case, request in zip(pack.cases, requests))
528
+ return source, input_ref, corpus_ref, reference_ref, cases
529
+
530
+
531
+ def default_registry():
532
+ """Validate bundled metadata against the actual existing fixture before use."""
533
+ path = Path(__file__).resolve().parent / "data" / "benchmark-releases.v1.json"
534
+ try:
535
+ def unique(pairs):
536
+ value = {}
537
+ for key, item in pairs:
538
+ _require(key not in value, "duplicate_metadata_field")
539
+ value[key] = item
540
+ return value
541
+ raw = json.loads(path.read_bytes(), object_pairs_hook=unique,
542
+ parse_constant=lambda _: (_ for _ in ()).throw(BenchmarkContractError("invalid_canonical_metadata")))
543
+ _require(type(raw) is dict and set(raw) == {"schema_version", "source", "projection_version", "metadata_only", "independent_assets_archived", "label_sets", "benchmarks"}, "invalid_catalog_shape")
544
+ _require(raw["schema_version"] == CATALOG_SCHEMA and raw["projection_version"] == PROJECTION_VERSION
545
+ and raw["metadata_only"] is True and raw["independent_assets_archived"] is False,
546
+ "invalid_catalog_provenance")
547
+ source, inputs, corpus, references, cases = synthetic_projection_metadata()
548
+ _require(raw["source"] == source.to_dict(), "fixture_source_mismatch")
549
+ _require(type(raw["label_sets"]) is list and type(raw["benchmarks"]) is list
550
+ and len(raw["label_sets"]) == len(raw["benchmarks"]) == 1, "unexpected_default_catalog")
551
+ registry = BenchmarkRegistry(tuple(LabelSetRelease.from_dict(item) for item in raw["label_sets"]),
552
+ tuple(BenchmarkRelease.from_dict(item) for item in raw["benchmarks"]))
553
+ label, benchmark = registry.label_sets[0], registry.benchmarks[0]
554
+ _require((label.ref, label.input, label.corpus, label.reference) == (DEFAULT_LABELS, inputs, corpus, references), "fixture_projection_mismatch")
555
+ _require(benchmark.ref == DEFAULT_BENCHMARK and benchmark.cases == cases and benchmark.evaluation_id == "rag.rerank.v1"
556
+ and benchmark.protocol_revision == "rag.rerank.v1" and benchmark.scope == "component"
557
+ and benchmark.version == label.version == "2.0.0"
558
+ and benchmark.interface_version == "sphynx.rag.rerank.v1" and benchmark.metric_profile == "rag.rerank.metric.v1"
559
+ and benchmark.required_label_profile == label.label_profile == "passage_qrels_and_evidence_groups"
560
+ and label.policy_revision == "rag.rerank.synthetic-label-policy.v1" and label.rubric_revision == "rag.rerank.metric.v1"
561
+ and benchmark.required_input_form == "any" and benchmark.intended_use == "illustrative"
562
+ and label.authority == "synthetic_fixture" and label.missingness == "complete_required"
563
+ and all(case.status == "judged" for case in label.cases), "fixture_membership_mismatch")
564
+ return registry
565
+ except (OSError, ValueError, TypeError, RecursionError) as exc:
566
+ if isinstance(exc, BenchmarkContractError):
567
+ raise
568
+ raise BenchmarkContractError("invalid_benchmark_catalog") from None
sphynx/candidates.py ADDED
@@ -0,0 +1,62 @@
1
+ """Small adapters for candidate scripts using the existing subprocess protocol."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from contextlib import redirect_stdout
7
+ from typing import Callable
8
+
9
+ from .contracts import (
10
+ RESPONSE_SCHEMA_VERSION,
11
+ ContractError,
12
+ canonical_json,
13
+ parse_candidate_request,
14
+ validate_candidate_response,
15
+ )
16
+
17
+
18
+ def serve_reranker(score: Callable[[str, str, float], float]) -> None:
19
+ """Consume one v1 request and write one fully validated v1 response.
20
+
21
+ Call this from a candidate script's ``if __name__ == "__main__"`` block.
22
+ ``score(query, passage, supplied_score)`` runs once per frozen passage and
23
+ must return a finite Python int or float (booleans are rejected). Callback
24
+ prints go to stderr. On invalid input/output or callback failure this raises
25
+ ``SystemExit(1)`` after a payload-free diagnostic on stderr and writes no
26
+ response. Timeout, environment, stream limits, and execution remain owned
27
+ by the subprocess runner. This adapter does not provide OS isolation.
28
+ """
29
+
30
+ try:
31
+ if not callable(score):
32
+ raise ContractError("score must be callable")
33
+ request = parse_candidate_request(getattr(sys.stdin, "buffer", sys.stdin).read())
34
+ except Exception as exc:
35
+ message = str(exc) if isinstance(exc, ContractError) else f"could not read request ({type(exc).__name__})"
36
+ print(f"serve_reranker: {message}", file=sys.stderr)
37
+ raise SystemExit(1) from None
38
+
39
+ scores = []
40
+ for index, item in enumerate(request.candidates):
41
+ try:
42
+ with redirect_stdout(sys.stderr):
43
+ number = score(request.query, item.input, item.dense_score)
44
+ except BaseException as exc:
45
+ # Exception text can contain the query/passage or caller secrets.
46
+ print(f"serve_reranker: scoring callback failed at passage {index + 1} ({type(exc).__name__})", file=sys.stderr)
47
+ raise SystemExit(1) from None
48
+ scores.append({"candidate_id": item.candidate_id, "ranking_score": number})
49
+ try:
50
+ response = validate_candidate_response({
51
+ "schema_version": RESPONSE_SCHEMA_VERSION,
52
+ "query_id": request.query_id,
53
+ "pool_digest": request.pool_digest,
54
+ "scores": scores,
55
+ }, request)
56
+ payload = canonical_json(response.to_dict()) + "\n"
57
+ except Exception as exc:
58
+ message = str(exc) if isinstance(exc, ContractError) else f"invalid response ({type(exc).__name__})"
59
+ print(f"serve_reranker: {message}", file=sys.stderr)
60
+ raise SystemExit(1) from None
61
+ sys.stdout.write(payload)
62
+ sys.stdout.flush()