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,552 @@
1
+ """Protected grading and stage access for actual integrated RAG execution.
2
+
3
+ The configured factory registers one explicit domain policy. Custom evaluators,
4
+ review protocols and report shapes can instead register IntegratedDriver with
5
+ LifecycleBackend. Neither path changes the neutral orchestration contracts.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from collections import Counter, defaultdict
10
+ import json
11
+ import math
12
+
13
+ from .assessment_contracts import Assessment, EvidenceView, _thaw
14
+ from .integrated_rag import (
15
+ INTEGRATED_SCHEMA, INTEGRATED_STAGE_SCHEMA, INTEGRATED_STAGES, IntegratedDriver,
16
+ build_context, validate_answer,
17
+ )
18
+ from .integration_contracts import require_digest, value_digest
19
+ from .lifecycle import LifecycleGradingRef, LifecycleJournal, RunPolicy, _selection
20
+ from .lifecycle_workflow import LifecycleBackend, _comparison_require
21
+ from .pipeline import _score
22
+ from .pipeline_workflow import _CASE_SCHEMA, _validate_case
23
+ from .stage_contracts import (
24
+ STAGE_EVIDENCE_CAPABILITY, StageContractError, StageEvidence, StageSelector,
25
+ )
26
+ from .workflow_contracts import (
27
+ Criterion, Dataset, DatasetDefinition, EvaluatorDefinition, MetricSpec,
28
+ PayloadSchema, SuiteDefinition, WorkflowContractError, contract_digest,
29
+ )
30
+ from .workflow_registry import WorkflowRegistry
31
+
32
+ _REFERENCE = "sphynx.rag.integrated.references.v1"
33
+ _REPORT = "sphynx.rag.integrated.report.v1"
34
+ _EVALUATOR = "sphynx.rag.integrated.evaluator.v1"
35
+ _REDUCER = "sphynx.rag.integrated.equal-case-trial-mean.v1"
36
+ _COMPARISON = "sphynx.rag.integrated.paired-delta.v1"
37
+ _PREFIX = "sphynx.rag.integrated."
38
+ _NUMERIC = ("execution_completed", "routing_correct", "retained_pool_ndcg_at_5",
39
+ "retained_pool_recall_at_5", "context_count")
40
+ _CRITERIA = tuple(_PREFIX + name for name in (*_NUMERIC, "answer_semantic_quality"))
41
+ _RANKING_POPULATION = {
42
+ "boundary": "N17_retained_stage_pool",
43
+ "denominator": "retained_inherited_chunk_pool",
44
+ "source_truth_rule": "native_complete_source_span_inheritance.v1",
45
+ "duplicate_credit": "overlapping_or_duplicate_inherited_chunks_receive_separate_native_credit",
46
+ "interpretation": "conditional_stage_pool_diagnostic_not_fixed_source_evidence_recall",
47
+ }
48
+ _RANKING_LIMITS = ["ranking_diagnostics_condition_on_retained_inherited_chunk_pool",
49
+ "overlapping_or_duplicate_chunks_receive_separate_native_credit",
50
+ "not_end_to_end_source_evidence_recall"]
51
+
52
+
53
+ def _metric_population(name):
54
+ return {"selection": "first_success_or_last_failed_per_case_trial", "missing": "excluded_with_coverage",
55
+ **(_RANKING_POPULATION if name.startswith("retained_pool_") else {})}
56
+
57
+
58
+ def _require(value, message):
59
+ if not value:
60
+ raise WorkflowContractError(message)
61
+
62
+
63
+ def _validate_reference(value):
64
+ _require(type(value) is dict and set(value) == {"source_sha256", "source_version", "task"},
65
+ "integrated reference fields differ")
66
+ _require(type(value["task"]) is dict and type(value["task"].get("should_call_rag")) is bool,
67
+ "integrated source reference is incomplete")
68
+ _require(all(type(value[key]) is str and value[key] for key in ("source_sha256", "source_version")),
69
+ "integrated reference revision is incomplete")
70
+ require_digest(value["source_sha256"], "reference source sha256")
71
+
72
+
73
+ def _validate_stage(value):
74
+ required = {"schema_version", "run_id", "case_id", "trial", "attempt", "stage_id", "occurrence",
75
+ "operation", "input_digest", "input", "output", "output_digest", "status", "implementation"}
76
+ _require(type(value) is dict and required <= set(value)
77
+ and set(value) <= required | {"parent_elapsed_seconds", "error_type"},
78
+ "integrated stage fields differ")
79
+ _require(value["schema_version"] == INTEGRATED_STAGE_SCHEMA and value["stage_id"] in INTEGRATED_STAGES
80
+ and type(value["occurrence"]) is int and value["occurrence"] == 1
81
+ and value["status"] in {"started", "completed", "failed"},
82
+ "integrated stage identity/status differs")
83
+ _require(all(type(value[key]) is str and value[key] for key in ("run_id", "case_id", "operation"))
84
+ and all(type(value[key]) is int and value[key] > 0 for key in ("trial", "attempt"))
85
+ and type(value["input"]) is dict and type(value["implementation"]) is dict,
86
+ "integrated stage identity/payload types differ")
87
+ _require(value["input_digest"] == value_digest(value["input"]), "integrated stage input digest differs")
88
+ if value["status"] == "completed":
89
+ _require(value["output_digest"] == value_digest(value["output"]), "integrated stage output digest differs")
90
+ else:
91
+ _require(value["output"] is None and value["output_digest"] is None, "uncompleted stage has an output")
92
+ if "parent_elapsed_seconds" in value:
93
+ n = value["parent_elapsed_seconds"]
94
+ _require(type(n) in (float, int) and math.isfinite(n) and n >= 0, "stage wall observation differs")
95
+
96
+
97
+ def _validate_report(value):
98
+ _require(type(value) is dict and value.get("schema_version") == _REPORT
99
+ and set(value) == {"schema_version", "run_id", "grading_id", "candidate", "scope", "metrics",
100
+ "counts", "selection", "assessment_rows", "trust_tier", "limitations"},
101
+ "integrated report fields differ")
102
+
103
+
104
+ def _evaluate(context):
105
+ """Deterministic source-bound diagnostics; no inferred answer-quality label."""
106
+ output = _thaw(context.outputs)
107
+ reference = _thaw(context.references)
108
+ _require(reference["task"]["task_id"] == context.case.case_id, "reference belongs to another case")
109
+ fields = dict(evaluator_id=_EVALUATOR, case_id=context.case.case_id,
110
+ run_id=context.run_id, grading_id=context.grading_id,
111
+ trial=context.metadata["trial"], attempt=context.metadata["attempt"],
112
+ evidence_ids=tuple(e.evidence_id for e in context.evidence))
113
+ def assessment(name, kind, value=None, availability="available", reason=None):
114
+ return Assessment(_PREFIX + name, kind=kind, value=value, availability=availability, reason=reason, **fields)
115
+ failure = context.metadata["candidate_failure"]
116
+ rows = [assessment("execution_completed", "assertion", failure is None)]
117
+ observed = output.get("routing_decision")
118
+ rows.append(assessment("routing_correct", "assertion", observed == reference["task"]["should_call_rag"])
119
+ if type(observed) is bool else assessment("routing_correct", "assertion", availability="missing",
120
+ reason="router_output_not_retained"))
121
+ retrieval = output.get("retrieval")
122
+ score = None
123
+ if retrieval is not None:
124
+ score, _ = _score(reference["task"], retrieval["response"], retrieval["request"])
125
+ for name in ("ndcg_at_5", "recall_at_5"):
126
+ value = None if score is None else score["stage_metrics"]["N17"][name]
127
+ rows.append(assessment("retained_pool_" + name, "score", value) if value is not None else
128
+ assessment("retained_pool_" + name, "score", availability="undefined" if score is not None else "missing",
129
+ reason="native_metric_population_undefined" if score is not None else
130
+ "source_labels_unavailable" if retrieval is not None else "retrieval_not_observed"))
131
+ context_output = output.get("context")
132
+ rows.append(assessment("context_count", "score", len(context_output["rows"])) if context_output is not None else
133
+ assessment("context_count", "score", availability="missing", reason="context_not_observed"))
134
+ rows.append(assessment("answer_semantic_quality", "label", availability="missing",
135
+ reason="evidence_bound_semantic_review_not_provided"))
136
+ return tuple(rows)
137
+
138
+
139
+ def _planned(record):
140
+ definition = record["run"]["definition"]
141
+ return tuple((case["case_id"], trial) for case in definition["cases"]
142
+ for trial in range(1, definition["policy"]["trials"] + 1))
143
+
144
+
145
+ def _assessment_index(record):
146
+ result = {}
147
+ for row in record["rows"]:
148
+ if row["failure"] is not None:
149
+ continue
150
+ for assessment in row["output"]["assessments"]:
151
+ key = (row["case_id"], row["trial"], assessment["criterion_id"])
152
+ _require(key not in result, "duplicate selected integrated assessment")
153
+ result[key] = assessment
154
+ return result
155
+
156
+
157
+ def _missing_reasons(record):
158
+ """Index each lane once; absent pairs must not rescan the whole cohort."""
159
+ selected, grading = _selection(record["run"]), {}
160
+ for row in record["rows"]:
161
+ key = (row["case_id"], row["trial"])
162
+ state = grading.setdefault(key, {"successful": False, "last_failure": None})
163
+ state["successful"] |= row["failure"] is None
164
+ state["last_failure"] = row["failure"]
165
+ result = {}
166
+ for key in _planned(record):
167
+ state = grading.get(key)
168
+ if key not in selected:
169
+ result[key] = "candidate_not_started"
170
+ elif state is None:
171
+ result[key] = "grading_not_started_or_unavailable"
172
+ elif not state["successful"]:
173
+ result[key] = "grading_failed:" + state["last_failure"]
174
+ else:
175
+ result[key] = "criterion_not_assessed"
176
+ return result
177
+
178
+
179
+ def _reduce(record):
180
+ """Mean trials within each case, then equal weight to eligible cases."""
181
+ values, coverage = defaultdict(lambda: defaultdict(list)), defaultdict(Counter)
182
+ planned, index = _planned(record), _assessment_index(record)
183
+ for case, trial in planned:
184
+ for name in _CRITERIA:
185
+ assessment = index.get((case, trial, name))
186
+ availability = "ungraded" if assessment is None else assessment["availability"]
187
+ coverage[name][availability] += 1
188
+ if assessment is not None and availability == "available" and assessment["kind"] in {"assertion", "score"}:
189
+ values[name][case].append(float(assessment["value"]))
190
+ metrics = {}
191
+ for name in _NUMERIC:
192
+ identifier = _PREFIX + name
193
+ case_values = [sum(v) / len(v) for v in values[identifier].values()]
194
+ metrics[identifier] = {"value": sum(case_values) / len(case_values) if case_values else None,
195
+ "eligible_cases": len(case_values), "planned_trials": len(planned),
196
+ "ungraded_trials": coverage[identifier]["ungraded"], "coverage": dict(coverage[identifier]),
197
+ "population": _metric_population(name)}
198
+ return {"policy_id": _REDUCER, "metrics": metrics,
199
+ "coverage": {key: dict(value) for key, value in coverage.items()}}
200
+
201
+
202
+ def _project(record):
203
+ run, grading = record["run"], record["definition"]
204
+ selected = _selection(run)
205
+ rows = [a for row in record["rows"] if row["failure"] is None for a in row["output"]["assessments"]]
206
+ planned = len(run["definition"]["cases"]) * run["definition"]["policy"]["trials"]
207
+ return {"schema_version": _REPORT, "run_id": grading["run_id"], "grading_id": grading["grading_id"],
208
+ "candidate": run["definition"]["candidate_identity"], "scope": run["definition"]["identity"]["scope"],
209
+ "metrics": record.get("analysis", {}).get("metrics", {}),
210
+ "counts": {"candidate_attempts": len(run["attempts"]),
211
+ "failed_candidate_attempts": sum(a["failure"] is not None for a in run["attempts"]),
212
+ "grading_attempts": len(record["rows"]),
213
+ "failed_grading_attempts": sum(a["failure"] is not None for a in record["rows"]),
214
+ "planned_trials": planned, "selected_trials": len(selected),
215
+ "graded_trials": len({(r["case_id"], r["trial"]) for r in record["rows"] if r["failure"] is None})},
216
+ "selection": [{"case_id": case, "trial": trial, "attempt": row["attempt"], "failure": row["failure"]}
217
+ for (case, trial), row in sorted(selected.items())],
218
+ "assessment_rows": rows, "trust_tier": "local_unverified",
219
+ "limitations": ["caller_declared_revision_not_code_attestation", "semantic_review_unavailable",
220
+ "source_span_represented_documents", "local_stage_wall_times_not_service_latency", *_RANKING_LIMITS]}
221
+
222
+
223
+ def _compare(baseline, candidate):
224
+ left, right = (record["run"]["definition"] for record in (baseline, candidate))
225
+ _require(left["candidate_identity"]["context_policy"] == right["candidate_identity"]["context_policy"],
226
+ "integrated context policies differ")
227
+ planned = _planned(baseline)
228
+ _require(planned == _planned(candidate), "integrated planned comparison cohort differs")
229
+ a, b, pairs, metrics = _assessment_index(baseline), _assessment_index(candidate), [], {}
230
+ baseline_absent, candidate_absent = _missing_reasons(baseline), _missing_reasons(candidate)
231
+ for name in _NUMERIC:
232
+ criterion = _PREFIX + name
233
+ case_deltas = defaultdict(list)
234
+ eligible = excluded = 0
235
+ baseline_ungraded = candidate_ungraded = 0
236
+ for case, trial in planned:
237
+ key = (case, trial, criterion)
238
+ x, y = a.get(key), b.get(key)
239
+ baseline_ungraded += x is None
240
+ candidate_ungraded += y is None
241
+ valid = x is not None and y is not None and x["availability"] == y["availability"] == "available"
242
+ delta = float(y["value"]) - float(x["value"]) if valid else None
243
+ pairs.append({"case_id": key[0], "trial": key[1], "criterion_id": criterion,
244
+ "baseline": None if x is None else x["value"], "candidate": None if y is None else y["value"],
245
+ "delta": delta, "availability": "available" if valid else "unavailable",
246
+ "baseline_availability": "ungraded" if x is None else x["availability"],
247
+ "candidate_availability": "ungraded" if y is None else y["availability"],
248
+ "baseline_reason": baseline_absent[(case, trial)] if x is None else x["reason"],
249
+ "candidate_reason": candidate_absent[(case, trial)] if y is None else y["reason"]})
250
+ if valid:
251
+ eligible += 1
252
+ case_deltas[key[0]].append(delta)
253
+ else:
254
+ excluded += 1
255
+ means = [sum(v) / len(v) for v in case_deltas.values()]
256
+ metrics[criterion] = {"candidate_minus_baseline": sum(means) / len(means) if means else None,
257
+ "planned_pairs": len(planned), "eligible_pairs": eligible, "excluded_pairs": excluded,
258
+ "baseline_ungraded_trials": baseline_ungraded, "candidate_ungraded_trials": candidate_ungraded,
259
+ "eligible_cases": len(means), "population": _metric_population(name)}
260
+ return {"policy_id": _COMPARISON, "baseline_run_id": left["run_id"], "candidate_run_id": right["run_id"],
261
+ "baseline_revision": left["candidate_identity"]["revision"],
262
+ "candidate_revision": right["candidate_identity"]["revision"], "metrics": metrics, "pairs": pairs,
263
+ "verdict": None, "reason": "no_quality_gate_or_statistical_significance_policy_selected",
264
+ "limitations": list(_RANKING_LIMITS)}
265
+
266
+
267
+ def _bound_context_evaluator(registry):
268
+ """Opt-in adapter: keep the native criterion while binding its stage input."""
269
+ from dataclasses import replace
270
+ from .evaluator_bindings import (
271
+ EvaluatorBinding, InputRequirement, _dispatch_binding, _schema_identity,
272
+ )
273
+ evaluator_id = "sphynx.rag.integrated.context-count-stage.v1"
274
+ criterion_id = _PREFIX + "context_count"
275
+ schema = registry.get_schema(INTEGRATED_STAGE_SCHEMA)
276
+
277
+ def count_stage(context):
278
+ stage = context.outputs
279
+ available = stage.get("status") == "completed"
280
+ return (Assessment(criterion_id, evaluator_id, context.case.case_id, "score",
281
+ "available" if available else "missing",
282
+ len(stage["output"]["rows"]) if available else None,
283
+ reason=None if available else "context_not_observed",
284
+ evidence_ids=tuple(item.evidence_id for item in context.evidence),
285
+ run_id=context.run_id, grading_id=context.grading_id,
286
+ trial=context.metadata["trial"], attempt=context.metadata["attempt"]),)
287
+
288
+ definition = EvaluatorDefinition(evaluator_id, "v1", evaluator_id, (criterion_id,),
289
+ metric_ids=(criterion_id,))
290
+ registry.register_evaluator(definition, count_stage)
291
+ binding = EvaluatorBinding.for_evaluator("sphynx.integrated.context-count-binding.v1", "v1",
292
+ definition, target="stage", stage_id="context", stage_schema=_schema_identity(schema),
293
+ requirements=(InputRequirement.for_schema("outputs", schema, fields=("status",), optional=True),))
294
+
295
+ def evaluate(context):
296
+ # IntegratedBackend._run verified these terminal snapshots against the
297
+ # retained artifact bytes before this grading context was constructed.
298
+ selector = binding.selector(context)
299
+ raw = context.outputs.get("stages", {}).get("context")
300
+ if raw is None:
301
+ evidence = EvidenceView(context.run_id + ":unobserved:" + contract_digest(selector.to_dict()),
302
+ None, "application/json", "stage.event", "verified_integrated_grading", "candidate_permitted",
303
+ INTEGRATED_STAGE_SCHEMA, selector.case_id, selector.stage_id, "unavailable",
304
+ {"run_id": context.run_id, "trial": selector.trial, "attempt": selector.attempt})
305
+ stage = StageEvidence.unavailable(selector, evidence, reason="context_not_observed")
306
+ else:
307
+ value = _thaw(raw); artifact = value.pop("artifact")
308
+ _require((value["run_id"], value["case_id"], value["trial"], value["attempt"],
309
+ value["stage_id"], value["occurrence"]) ==
310
+ (context.run_id, selector.case_id, selector.trial, selector.attempt,
311
+ selector.stage_id, selector.occurrence), "context stage grading selection differs")
312
+ matching = [item for item in context.evidence
313
+ if item.evidence_id == context.run_id + ":" + artifact["path"]
314
+ and item.sha256 == artifact["sha256"]]
315
+ _require(len(matching) == 1, "context stage lacks its verified retained artifact")
316
+ evidence = replace(matching[0], schema_version=INTEGRATED_STAGE_SCHEMA,
317
+ step_id=selector.stage_id, metadata={**_thaw(matching[0].metadata), "run_id": context.run_id})
318
+ stage = StageEvidence.projected(selector, evidence, value, schema=schema)
319
+ bound = _dispatch_binding(registry, binding, context, stage=stage)
320
+ allowed = {item.evidence_id for item in context.evidence}
321
+ # The primary native evaluator composes the child. Its new implementation
322
+ # identity and metadata bind that child; stable native criterion IDs remain.
323
+ mapped = tuple(replace(row, evaluator_id=_EVALUATOR,
324
+ evidence_ids=tuple(key for key in row.evidence_ids if key in allowed)) for row in bound)
325
+ return tuple(row for row in _evaluate(context) if row.criterion_id != criterion_id) + mapped
326
+
327
+ primary = EvaluatorDefinition(_EVALUATOR, "v1", _EVALUATOR + ".context-binding.v1", _CRITERIA,
328
+ metric_ids=tuple(_PREFIX + name for name in _NUMERIC),
329
+ configuration={"stage_binding": binding.to_dict(), "composition": "native-criterion-stage-adapter.v1"})
330
+ return primary, evaluate
331
+
332
+
333
+ class IntegratedBackend(LifecycleBackend):
334
+ """One configured local system boundary with protected native references.
335
+
336
+ ``authorize_references(case_id, access)`` must return True before source
337
+ labels can be loaded for grading. It grants no candidate access. The default
338
+ evaluator reports structural/routing/retrieval diagnostics; answer semantic
339
+ quality remains missing. For different criteria or bound reviews, register
340
+ the driver with the general LifecycleBackend and explicit domain callbacks.
341
+ """
342
+ def __init__(self, registry: WorkflowRegistry, driver: IntegratedDriver, *, authorize_references,
343
+ reference_access=None, run_policy=None, grading_policy=None, suite_id="rag.integrated.v1",
344
+ bind_context_evaluator=False):
345
+ _require(isinstance(driver, IntegratedDriver) and callable(authorize_references),
346
+ "integrated backend needs driver and explicit reference authorization")
347
+ _require(type(bind_context_evaluator) is bool, "bind_context_evaluator must be boolean")
348
+ source = driver.pipeline.source
349
+ schema = registry.register_schema(PayloadSchema(_CASE_SCHEMA, "v1", "sphynx.pipeline.case-validator.v1", _validate_case))
350
+ reference = registry.register_schema(PayloadSchema(_REFERENCE, "v1", "sphynx.integrated.reference-validator.v1", _validate_reference))
351
+ registry.register_schema(PayloadSchema(INTEGRATED_STAGE_SCHEMA, "v1", "sphynx.integrated.stage-record-validator.v1", _validate_stage))
352
+ registry.register_schema(PayloadSchema(_REPORT, "v1", "sphynx.integrated.report-validator.v1", _validate_report))
353
+ definition = DatasetDefinition(source.dataset_id, source.version, suite_id + ".provider", _CASE_SCHEMA, _REFERENCE,
354
+ selection={"case_ids": list(source.case_ids), "order": "native_dataset_order"},
355
+ identity={"source_sha256": source.sha256, "reference_revision": source.version,
356
+ "reference_rule": "native_complete_source_span_inheritance.v1"})
357
+ def load_reference(case_id):
358
+ _, tasks = driver.pipeline._loaded()
359
+ task = next((task for task in tasks if task["task_id"] == case_id), None)
360
+ _require(task is not None, "reference case is outside the selected cohort")
361
+ return {"source_sha256": source.sha256, "source_version": source.version, "task": task}
362
+ registry.register_provider(Dataset(definition, schema, lambda selection: driver.cases(),
363
+ reference_schema=reference, reference_loader=load_reference, authorize_references=authorize_references))
364
+ for identifier in _CRITERIA:
365
+ registry.register_criterion(Criterion(identifier, "v1", identifier.rsplit(".", 1)[-1]))
366
+ registry.register_policy(_REDUCER, version="v1", implementation_id=_REDUCER, kind="aggregation", callback=_reduce,
367
+ metadata={"population": "first_success_or_last_failed_per_case_trial; available_values_only",
368
+ "weights": "mean_trials_within_case_then_equal_case_mean"})
369
+ registry.register_policy(_COMPARISON, version="v1", implementation_id=_COMPARISON, kind="comparison", callback=_compare,
370
+ metadata={"population": "same_case_trial_available_pairs", "quality_gate": None})
371
+ for name in _NUMERIC:
372
+ registry.register_metric(MetricSpec(_PREFIX + name, "v1", "count" if name == "context_count" else "ratio",
373
+ "descriptive" if name == "context_count" else "higher", _REDUCER, _COMPARISON,
374
+ population=_metric_population(name)))
375
+ if bind_context_evaluator:
376
+ evaluator, callback = _bound_context_evaluator(registry)
377
+ registry.register_evaluator(evaluator, callback)
378
+ else:
379
+ registry.register_evaluator(EvaluatorDefinition(_EVALUATOR, "v1", _EVALUATOR, _CRITERIA,
380
+ metric_ids=tuple(_PREFIX + name for name in _NUMERIC)), _evaluate)
381
+ registry.register_projection(_REPORT, _project, version="v1", implementation_id="sphynx.integrated.projection.v1")
382
+ suite = SuiteDefinition(suite_id, "v1", "sphynx.integrated." + suite_id, "Integrated retrieval, context and answer",
383
+ configuration={"boundary": "sphynx.rag.integrated.v1", "pipeline": driver.pipeline.describe(driver.pipeline.suite_id).to_dict(),
384
+ "context_policy": {"max_bytes": driver.max_context_bytes,
385
+ "selection": "whole_chunks_in_final_rank_order_utf8_bytes.v1"}},
386
+ criterion_ids=_CRITERIA, evaluator_ids=(_EVALUATOR,), metric_ids=tuple(_PREFIX + name for name in _NUMERIC),
387
+ required_capabilities=(STAGE_EVIDENCE_CAPABILITY,), datasets=(definition,),
388
+ metadata={"candidate_source_bytes_verified": False, "semantic_review": "not_provided"})
389
+ def admit(plan, cases):
390
+ admitted = driver.admit(plan.candidate)
391
+ _require(tuple(c.case_id for c in cases) == source.case_ids, "integrated selected case order differs")
392
+ return {"artifact_bytes_verified": admitted.artifact_bytes_verified, "candidate_source_bytes_verified": False,
393
+ "fixed": {"boundary": "sphynx.rag.integrated.v1", "source": source.to_dict(),
394
+ "context_policy": _thaw(suite.configuration["context_policy"])},
395
+ "candidate": {"identity": driver.candidate_identity(plan.candidate),
396
+ "pipeline_admission": admitted.to_dict()}}
397
+ super().__init__(registry, suite, driver=driver, evaluator_id=_EVALUATOR, projection_schema_id=_REPORT,
398
+ comparison_policy_id=_COMPARISON, run_policy=run_policy or RunPolicy(reducer_id=_REDUCER),
399
+ grading_policy=grading_policy, reference_access=reference_access, admission_verifier=admit,
400
+ scopes=("system",), capabilities=(STAGE_EVIDENCE_CAPABILITY, "sphynx.rag.integrated.v1"),
401
+ include_emitted_evidence=bind_context_evaluator)
402
+ registry.register_backend(self)
403
+ registry.register_suite(suite)
404
+
405
+ @staticmethod
406
+ def _comparison_conditions(baseline, candidate):
407
+ LifecycleBackend._comparison_conditions(baseline, candidate)
408
+ left, right = (record["run"]["definition"] for record in (baseline, candidate))
409
+ _comparison_require(left["candidate_identity"]["context_policy"] == right["candidate_identity"]["context_policy"],
410
+ "integrated context policies differ")
411
+ _comparison_require(_planned(baseline) == _planned(candidate), "integrated planned comparison cohort differs")
412
+
413
+ def project_comparison(self, sources, native):
414
+ from ._comparison_adapters import _integrated_view
415
+ return _integrated_view(sources, native, {item.metric_id: item for item in self.registry.list_metrics()})
416
+
417
+ def _run(self, ref):
418
+ record = super()._run(ref)
419
+ _require(record["definition"]["identity"]["scope"] == "system"
420
+ and record["definition"]["identity"]["mode"] == "local", "integrated saved scope/mode differs")
421
+ cases = {case["case_id"]: case for case in record["definition"]["cases"]}
422
+ identity = record["definition"]["candidate_identity"]
423
+ journal = LifecycleJournal(ref.path, ref.definition_sha256)
424
+ emitted = {e["data"]["binding"]["path"]: e["data"] for e in record["events"] if e["event"] == "artifact"}
425
+ for attempt in record["attempts"]:
426
+ output = attempt["outputs"]
427
+ if not output:
428
+ _require(attempt["failure"] is not None, "successful integrated attempt has no output")
429
+ continue
430
+ _require(output.get("schema_version") == INTEGRATED_SCHEMA
431
+ and (output["case_id"], output["trial"], output["attempt"]) ==
432
+ (attempt["case_id"], attempt["trial"], attempt["attempt"])
433
+ and output["candidate_revision"] == identity["revision"]
434
+ and output["input_digest"] == contract_digest(cases[attempt["case_id"]])
435
+ and output["candidate_identity_digest"] == value_digest(identity), "integrated output binding differs")
436
+ terminals, order = {}, []
437
+ for index, event in enumerate(output["events"]):
438
+ _require(event["sequence"] == index and event["stage_id"] in INTEGRATED_STAGES
439
+ and event["occurrence"] == 1 and event["phase"] in {"started", "completed", "failed"},
440
+ "integrated event sequence differs")
441
+ if event["phase"] == "started":
442
+ order.append(event["stage_id"])
443
+ binding = event["artifact"]
444
+ native = emitted.get(binding["path"])
445
+ _require(native is not None and native["binding"] == binding and
446
+ binding["role"] == "integrated." + event["stage_id"] + "." + event["phase"] and
447
+ (native["case_id"], native["trial"], native["attempt"]) ==
448
+ (attempt["case_id"], attempt["trial"], attempt["attempt"]), "stage event is outside its attempt")
449
+ if native["truncated"]:
450
+ continue
451
+ value = json.loads(journal.read_artifact(binding)); _validate_stage(value)
452
+ _require((value["run_id"], value["case_id"], value["trial"], value["attempt"], value["stage_id"], value["occurrence"], value["status"]) ==
453
+ (ref.run_id, attempt["case_id"], attempt["trial"], attempt["attempt"], event["stage_id"], event["occurrence"], event["phase"]),
454
+ "stage event selector binding differs")
455
+ if event["phase"] != "started":
456
+ _require(event["stage_id"] not in terminals, "integrated stage has duplicate terminal events")
457
+ terminals[event["stage_id"]] = event["phase"]
458
+ _require(output["stages"].get(event["stage_id"]) == {**value, "artifact": binding}, "stage snapshot differs from retained bytes")
459
+ _require(order == list(INTEGRATED_STAGES[:len(order)]) and set(output["stages"]) == set(terminals),
460
+ "integrated stages are not the retained execution prefix")
461
+ if attempt["failure"] is None:
462
+ expected = ["router"] if output.get("routing_decision") is False else list(INTEGRATED_STAGES)
463
+ _require(list(terminals) == expected and set(terminals.values()) == {"completed"},
464
+ "successful integrated execution lacks required stages")
465
+ retrieval = output.get("retrieval")
466
+ if retrieval is not None:
467
+ request = retrieval["request"]
468
+ body = dict(request); digest = body.pop("input_digest")
469
+ _require(value_digest(body) == digest and request["case_id"] == attempt["case_id"]
470
+ and request["query"] == output["rewritten_query"]
471
+ and request["documents"] == cases[attempt["case_id"]]["inputs"]["documents"],
472
+ "retained retrieval request/source binding differs")
473
+ from .pipeline import validate_pipeline_response
474
+ validate_pipeline_response(retrieval["response"], request)
475
+ for kind, value in retrieval["response"]["stages"].items():
476
+ _require(output["stages"][kind]["output"] == value, "retrieval response differs from stage artifact")
477
+ if output.get("context") is not None:
478
+ expected = build_context(retrieval["response"], request["documents"], identity["context_policy"]["max_bytes"])
479
+ _require(output["context"] == expected and output["stages"]["context"]["output"] == expected,
480
+ "integrated context differs from retained retrieval")
481
+ if output.get("answer") is not None:
482
+ generated = output["stages"]["generator"]
483
+ _require(generated["input"]["contexts"] == output["context"]["rows"]
484
+ and generated["input"]["query"] == output["rewritten_query"], "generator handoff differs")
485
+ validate_answer(generated["output"], output["context"])
486
+ _require(all(generated["output"].get(key, [] if key == "citations" else None) == output[key]
487
+ for key in ("answer", "citations", "abstained", "token_counts")), "answer differs from generation artifact")
488
+ return record
489
+
490
+ def _references(self, run_record):
491
+ references = super()._references(run_record)
492
+ source = self.driver.pipeline.source
493
+ for case, reference in references.items():
494
+ _require(reference["source_sha256"] == source.sha256 and reference["source_version"] == source.version
495
+ and reference["task"]["task_id"] == case, "integrated reference revision/case differs")
496
+ return references
497
+
498
+ def stage_evidence(self, native_ref, selector):
499
+ if not isinstance(selector, StageSelector) or selector.stage_id not in INTEGRATED_STAGES or selector.occurrence != 1:
500
+ raise StageContractError("integrated stage selection requires a supported exact selector")
501
+ run_ref = native_ref.run if isinstance(native_ref, LifecycleGradingRef) else native_ref
502
+ record = self._run(run_ref)
503
+ if isinstance(native_ref, LifecycleGradingRef):
504
+ self._grading(native_ref)
505
+ starts = {(e["data"]["case_id"], e["data"]["trial"], e["data"]["attempt"])
506
+ for e in record["events"] if e["event"] == "candidate_started"}
507
+ if (selector.case_id, selector.trial, selector.attempt) not in starts:
508
+ raise StageContractError("stage selector is outside the run's attempted cohort")
509
+ journal, result = LifecycleJournal(run_ref.path, run_ref.definition_sha256), []
510
+ for event in record["events"]:
511
+ data = event["data"]
512
+ if event["event"] != "artifact" or (data["case_id"], data["trial"], data["attempt"]) != (selector.case_id, selector.trial, selector.attempt):
513
+ continue
514
+ binding = data["binding"]
515
+ if not binding["role"].startswith("integrated." + selector.stage_id + "."):
516
+ continue
517
+ evidence = EvidenceView(run_ref.run_id + ":" + binding["path"], binding["sha256"], "application/json",
518
+ "stage.event", "parent_retained_integrated_stage", "candidate_permitted", INTEGRATED_STAGE_SCHEMA,
519
+ selector.case_id, selector.stage_id, "unavailable" if data["truncated"] else "available",
520
+ {"artifact_path": binding["path"], "journal_sequence": event["sequence"], "role": binding["role"],
521
+ "timing_authority": "parent_observed_local_stage_wall", "trial": selector.trial, "attempt": selector.attempt})
522
+ if data["truncated"]:
523
+ result.append(StageEvidence.unavailable(selector, evidence, reason="lifecycle_output_limit_truncated_stage_record"))
524
+ else:
525
+ value = json.loads(journal.read_artifact(binding))
526
+ _require((value["run_id"], value["case_id"], value["trial"], value["attempt"], value["stage_id"], value["occurrence"]) ==
527
+ (run_ref.run_id, selector.case_id, selector.trial, selector.attempt, selector.stage_id, selector.occurrence)
528
+ and binding["role"] == "integrated." + selector.stage_id + "." + value["status"],
529
+ "stage selector binding differs")
530
+ result.append(StageEvidence.projected(selector, evidence, value, schema=self.registry.get_schema(INTEGRATED_STAGE_SCHEMA)))
531
+ if result:
532
+ return tuple(result)
533
+ evidence = EvidenceView(run_ref.run_id + ":unobserved:" + contract_digest(selector.to_dict()), None,
534
+ "application/json", "stage.event", "parent_retained_integrated_stage", "candidate_permitted",
535
+ INTEGRATED_STAGE_SCHEMA, selector.case_id, selector.stage_id, "unavailable")
536
+ return (StageEvidence.unavailable(selector, evidence, reason="stage_not_observed_or_skipped"),)
537
+
538
+
539
+ def build_integrated_registry(driver: IntegratedDriver, *, authorize_references, reference_access=None,
540
+ run_policy=None, grading_policy=None, suite_id="rag.integrated.v1",
541
+ bind_context_evaluator=False) -> WorkflowRegistry:
542
+ """Register configured local system execution; load no assets or references.
543
+
544
+ The default reducer is explicit equal-case mean of available trial means.
545
+ Plan policies can omit aggregation; per-case assessments remain available.
546
+ No quality gate, semantic review or authenticated revision is implied.
547
+ """
548
+ registry = WorkflowRegistry(legacy_ids=(driver.pipeline.source.dataset_id,))
549
+ IntegratedBackend(registry, driver, authorize_references=authorize_references, reference_access=reference_access,
550
+ run_policy=run_policy, grading_policy=grading_policy, suite_id=suite_id,
551
+ bind_context_evaluator=bind_context_evaluator)
552
+ return registry