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/audit_access.py ADDED
@@ -0,0 +1,245 @@
1
+ """Typed selected-bank views. The authenticated service remains authoritative."""
2
+ from __future__ import annotations
3
+ from collections.abc import Mapping
4
+ from dataclasses import dataclass, field
5
+ import hashlib
6
+ import json
7
+ from typing import Any
8
+
9
+ from .audit_contracts import BankRelease, ReviewEvent, ReviewTarget
10
+ from .audit_requests import _DIGEST, _exact_identity, _identifier, _key, _positive_version
11
+ from .evaluation_protocols import VersionedIdentity
12
+ from .workflow_contracts import WorkflowContractError, _object, _thaw
13
+
14
+
15
+ def _fields(value, schema, required):
16
+ if (not isinstance(value, Mapping) or value.get('schema_version') != schema
17
+ or set(value) != {'schema_version', *required}):
18
+ raise WorkflowContractError('invalid or unsupported audit response fields')
19
+ return {key: item for key, item in value.items() if key != 'schema_version'}
20
+
21
+
22
+ def _common(bank, release, through):
23
+ _identifier(bank); _identifier(release); _positive_version(through)
24
+
25
+
26
+ def _items(values, cls, maximum):
27
+ if not isinstance(values, (tuple, list)) or len(values) > maximum or any(not isinstance(x, cls) for x in values):
28
+ raise WorkflowContractError('invalid or oversized audit page items')
29
+ return tuple(values)
30
+
31
+
32
+ def _protocol(value, identity):
33
+ value = _object(value, 'protected protocol')
34
+ if (set(value) != {'schema_version', 'protocol_id', 'version', 'implementation_id',
35
+ 'instructions', 'required_fields', 'allow_extra_fields'}
36
+ or value.get('schema_version') != 'sphynx.bank-review-protocol.v1'
37
+ or value.get('protocol_id') != identity.identity_id or value.get('version') != identity.version):
38
+ raise WorkflowContractError('review protocol identity differs')
39
+ instructions, required = value['instructions'], value['required_fields']
40
+ if (type(value['implementation_id']) is not str or not value['implementation_id'].strip()
41
+ or type(instructions) is not str or not instructions.strip() or len(instructions.encode('utf-8')) > 8192
42
+ or type(value['allow_extra_fields']) is not bool or not isinstance(required, Mapping)
43
+ or not 1 <= len(required) <= 64):
44
+ raise WorkflowContractError('review protocol manifest shape differs')
45
+ for name, kind in required.items():
46
+ _identifier(name)
47
+ if kind not in ('string', 'boolean', 'number', 'integer', 'object', 'array'):
48
+ raise WorkflowContractError('unsupported review protocol field kind')
49
+ # This exact manifest schema has only strings, booleans and objects. Match
50
+ # the service's sorted UTF-8 digest profile; do not generalize to float JSON
51
+ # or replace the separate legacy/public canonical digest contracts.
52
+ encoded = json.dumps({'profile': 'sphynx.private-audit-json.v1', 'value': _thaw(value)},
53
+ ensure_ascii=False, sort_keys=True, separators=(',', ':'), allow_nan=False).encode('utf-8')
54
+ if 'sha256:' + hashlib.sha256(encoded).hexdigest() != identity.digest:
55
+ raise WorkflowContractError('review protocol manifest digest differs')
56
+ return value
57
+
58
+
59
+ def _events(items, bank, release, through, next_after):
60
+ versions = [event.version for event in items]
61
+ if versions != sorted(set(versions)) or any(event.version > through or event.bank_id != bank
62
+ or event.release_id != release for event in items) or len({event.team_id for event in items}) > 1:
63
+ raise WorkflowContractError('audit history identity or ordering differs')
64
+ if next_after is not None and (type(next_after) is not int or not items or next_after != versions[-1]):
65
+ raise WorkflowContractError('audit history cursor does not bind the last event')
66
+
67
+
68
+ def _page_dict(schema, page):
69
+ return {'schema_version': schema, 'bank_id': page.bank_id, 'release_id': page.release_id,
70
+ 'through_version': page.through_version, 'items': [item.to_dict() for item in page.items],
71
+ 'next_after': page.next_after}
72
+
73
+
74
+ def _metadata(event):
75
+ allowed = {'release_digest', 'contribution_id', 'target_id', 'decision',
76
+ 'supersedes_contribution_id', 'adjudication_id', 'contribution_set_digest'}
77
+ if set(event.details) - allowed or any(item is not None and type(item) is not str for item in event.details.values()):
78
+ raise WorkflowContractError('ordinary audit metadata contains unsupported detail')
79
+
80
+
81
+ class _Readable:
82
+ def to_text(self) -> str:
83
+ raise NotImplementedError
84
+
85
+ def __str__(self) -> str:
86
+ return self.to_text()
87
+
88
+
89
+ @dataclass(frozen=True)
90
+ class ReviewTargetPage(_Readable):
91
+ bank_id: str
92
+ release_id: str
93
+ through_version: int
94
+ items: tuple[ReviewTarget, ...] = field(repr=False)
95
+ protocols_by_digest: Mapping[str, Mapping[str, Any]] = field(repr=False)
96
+ next_after: str | None = None
97
+
98
+ def __post_init__(self):
99
+ _common(self.bank_id, self.release_id, self.through_version)
100
+ items = _items(self.items, ReviewTarget, 20)
101
+ keys = [_identifier(item.target_id) for item in items]
102
+ if keys != sorted(set(keys)) or (self.next_after is not None and (not keys or self.next_after != keys[-1])):
103
+ raise WorkflowContractError('target page order or cursor differs')
104
+ if not isinstance(self.protocols_by_digest, Mapping) or set(self.protocols_by_digest) != {item.protocol.digest for item in items}:
105
+ raise WorkflowContractError('target page protocol set differs')
106
+ protocols = {}
107
+ for item in items:
108
+ _exact_identity(item.subject); _exact_identity(item.protocol)
109
+ protocols[item.protocol.digest] = _protocol(self.protocols_by_digest[item.protocol.digest], item.protocol)
110
+ object.__setattr__(self, 'items', items)
111
+ object.__setattr__(self, 'protocols_by_digest', _object(protocols, 'protected protocol manifests'))
112
+
113
+ def to_dict(self) -> dict[str, Any]:
114
+ return {**_page_dict('sphynx.bank-review-targets.v1', self),
115
+ 'protocols_by_digest': _thaw(self.protocols_by_digest)}
116
+
117
+ @classmethod
118
+ def from_dict(cls, value: Mapping[str, Any]) -> ReviewTargetPage:
119
+ data = _fields(value, 'sphynx.bank-review-targets.v1',
120
+ {'bank_id', 'release_id', 'through_version', 'items', 'protocols_by_digest', 'next_after'})
121
+ data['items'] = _decode_items(data['items'], ReviewTarget, 20)
122
+ return cls(**data)
123
+
124
+ def to_text(self) -> str:
125
+ return f'Review targets: {self.bank_id}/{self.release_id} at version {self.through_version}; {len(self.items)} protected items. Inspect authorized items explicitly.'
126
+
127
+
128
+ def _decode_items(value, cls, maximum):
129
+ if not isinstance(value, (list, tuple)) or len(value) > maximum:
130
+ raise WorkflowContractError('invalid or oversized audit page items')
131
+ return tuple(cls.from_dict(item) for item in value)
132
+
133
+
134
+ @dataclass(frozen=True)
135
+ class ReviewHistoryPage(_Readable):
136
+ bank_id: str
137
+ release_id: str
138
+ through_version: int
139
+ items: tuple[ReviewEvent, ...] = field(repr=False)
140
+ next_after: int | None = None
141
+ protected: bool = False
142
+
143
+ def __post_init__(self):
144
+ _common(self.bank_id, self.release_id, self.through_version)
145
+ if type(self.protected) is not bool:
146
+ raise WorkflowContractError('protected history selection must be boolean')
147
+ items = _items(self.items, ReviewEvent, 10 if self.protected else 100)
148
+ _events(items, self.bank_id, self.release_id, self.through_version, self.next_after)
149
+ if not self.protected:
150
+ for item in items:
151
+ _metadata(item)
152
+ object.__setattr__(self, 'items', items)
153
+
154
+ def to_dict(self) -> dict[str, Any]:
155
+ schema = 'sphynx.bank-review-protected-history.v1' if self.protected else 'sphynx.bank-review-history.v1'
156
+ return _page_dict(schema, self)
157
+
158
+ @classmethod
159
+ def from_dict(cls, value: Mapping[str, Any]) -> ReviewHistoryPage:
160
+ protected = isinstance(value, Mapping) and value.get('schema_version') == 'sphynx.bank-review-protected-history.v1'
161
+ schema = 'sphynx.bank-review-protected-history.v1' if protected else 'sphynx.bank-review-history.v1'
162
+ data = _fields(value, schema, {'bank_id', 'release_id', 'through_version', 'items', 'next_after'})
163
+ data['items'] = _decode_items(data['items'], ReviewEvent, 10 if protected else 100)
164
+ return cls(**data, protected=protected)
165
+
166
+ def to_text(self) -> str:
167
+ return f'Review history: {self.bank_id}/{self.release_id} through version {self.through_version}; {len(self.items)} events; protected={self.protected}.'
168
+
169
+
170
+ @dataclass(frozen=True)
171
+ class TargetReviewPage(_Readable):
172
+ bank_id: str
173
+ release_id: str
174
+ target_id: str
175
+ through_version: int
176
+ subject: VersionedIdentity
177
+ protocol_identity: VersionedIdentity
178
+ protocol: Mapping[str, Any] = field(repr=False)
179
+ current_contribution_set_digest: str
180
+ items: tuple[ReviewEvent, ...] = field(repr=False)
181
+ next_after: int | None = None
182
+
183
+ def __post_init__(self):
184
+ _common(self.bank_id, self.release_id, self.through_version); _identifier(self.target_id)
185
+ _exact_identity(self.subject); _exact_identity(self.protocol_identity)
186
+ if type(self.current_contribution_set_digest) is not str or not _DIGEST.fullmatch(self.current_contribution_set_digest):
187
+ raise WorkflowContractError('review page requires an exact current-set digest')
188
+ items = _items(self.items, ReviewEvent, 10)
189
+ _events(items, self.bank_id, self.release_id, self.through_version, self.next_after)
190
+ if any(item.kind not in ('contribution', 'adjudication') or item.details.get('target_id') != self.target_id for item in items):
191
+ raise WorkflowContractError('review event belongs to another target or kind')
192
+ object.__setattr__(self, 'items', items)
193
+ object.__setattr__(self, 'protocol', _protocol(self.protocol, self.protocol_identity))
194
+
195
+ def to_dict(self) -> dict[str, Any]:
196
+ return {**_page_dict('sphynx.bank-target-review-details.v1', self), 'target_id': self.target_id,
197
+ 'subject': self.subject.to_dict(), 'protocol_identity': self.protocol_identity.to_dict(),
198
+ 'protocol': _thaw(self.protocol), 'current_contribution_set_digest': self.current_contribution_set_digest}
199
+
200
+ @classmethod
201
+ def from_dict(cls, value: Mapping[str, Any]) -> TargetReviewPage:
202
+ data = _fields(value, 'sphynx.bank-target-review-details.v1', {'bank_id', 'release_id', 'target_id',
203
+ 'through_version', 'subject', 'protocol_identity', 'protocol', 'current_contribution_set_digest', 'items', 'next_after'})
204
+ data['items'] = _decode_items(data['items'], ReviewEvent, 10)
205
+ for name in ('subject', 'protocol_identity'):
206
+ data[name] = VersionedIdentity.from_dict(data[name])
207
+ return cls(**data)
208
+
209
+ def to_text(self) -> str:
210
+ return f'Target reviews: {self.bank_id}/{self.release_id}/{self.target_id} through version {self.through_version}; {len(self.items)} protected events. Current authority is rechecked on each read.'
211
+
212
+
213
+ @dataclass(frozen=True)
214
+ class ReviewOperationReceipt(_Readable):
215
+ operation_key: str
216
+ event: ReviewEvent
217
+ release_snapshot: BankRelease = field(repr=False)
218
+ eligibility_authority: str = 'receipt_snapshot_requires_current_read'
219
+
220
+ def __post_init__(self):
221
+ _key(self.operation_key)
222
+ if not isinstance(self.event, ReviewEvent) or not isinstance(self.release_snapshot, BankRelease):
223
+ raise WorkflowContractError('invalid review receipt records')
224
+ event, release = self.event, self.release_snapshot
225
+ _metadata(event)
226
+ if (self.eligibility_authority != 'receipt_snapshot_requires_current_read'
227
+ or (event.bank_id, event.release_id, event.team_id, event.version) !=
228
+ (release.bank_id, release.release_id, release.team_id, release.version)):
229
+ raise WorkflowContractError('review receipt identity or historical authority differs')
230
+
231
+ def to_dict(self) -> dict[str, Any]:
232
+ return {'schema_version': 'sphynx.bank-review-operation.v1', 'operation_key': self.operation_key,
233
+ 'event': self.event.to_dict(), 'release_snapshot': self.release_snapshot.to_dict(),
234
+ 'eligibility_authority': self.eligibility_authority}
235
+
236
+ @classmethod
237
+ def from_dict(cls, value: Mapping[str, Any]) -> ReviewOperationReceipt:
238
+ data = _fields(value, 'sphynx.bank-review-operation.v1',
239
+ {'operation_key', 'event', 'release_snapshot', 'eligibility_authority'})
240
+ data['event'] = ReviewEvent.from_dict(data['event'])
241
+ data['release_snapshot'] = BankRelease.from_dict(data['release_snapshot'])
242
+ return cls(**data)
243
+
244
+ def to_text(self) -> str:
245
+ return f'Historical review receipt: {self.event.bank_id}/{self.event.release_id} at version {self.event.version}. Read the current release before using eligibility.'
@@ -0,0 +1,204 @@
1
+ """Neutral private audit records; construction never grants review authority.
2
+
3
+ Only an authenticated service can stamp an actor or decide eligibility. These
4
+ classes validate permitted wire records. Protected target/judgment payloads are
5
+ hidden from repr, and become explicit only through authorized reads/to_dict().
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from collections.abc import Mapping
10
+ from dataclasses import dataclass, field, fields
11
+ from datetime import datetime, timedelta
12
+ import re
13
+ from types import MappingProxyType
14
+ from typing import Any, ClassVar
15
+
16
+ from .evaluation_protocols import VersionedIdentity, _envelope
17
+ from .workflow_contracts import WorkflowContractError, _object, _optional_text, _text, _texts, _thaw
18
+
19
+ _DIGEST = re.compile(r"sha256:[0-9a-f]{64}\Z")
20
+ _OPERATION = re.compile(r"[A-Za-z0-9._:-]{16,100}\Z")
21
+ _TARGETS = ("bank_curation", "output_judgment", "evaluator_audit")
22
+
23
+
24
+ def _version(value):
25
+ if type(value) is not int or value < 1:
26
+ raise WorkflowContractError("audit version must be a positive integer")
27
+
28
+
29
+ def _identity(value):
30
+ if not isinstance(value, VersionedIdentity) or value.digest is None:
31
+ raise WorkflowContractError("audit identity requires an exact version and digest")
32
+ return value
33
+
34
+
35
+ def _wire(value):
36
+ if isinstance(value, VersionedIdentity):
37
+ return value.to_dict()
38
+ if isinstance(value, Mapping):
39
+ return {key: _wire(item) for key, item in value.items()}
40
+ return _thaw(value)
41
+
42
+
43
+ def _construct(cls, values):
44
+ try:
45
+ return cls(**values)
46
+ except TypeError as exc:
47
+ raise WorkflowContractError("invalid " + cls.__name__ + " fields") from exc
48
+
49
+
50
+ class _AuditWire:
51
+ _SCHEMA: ClassVar[str]
52
+
53
+ def to_dict(self) -> dict[str, Any]:
54
+ return {"schema_version": self._SCHEMA,
55
+ **{item.name: _wire(getattr(self, item.name)) for item in fields(self)}}
56
+
57
+
58
+ @dataclass(frozen=True)
59
+ class BankRelease(_AuditWire):
60
+ """A selected-bank summary, with separate use/ranking/acceptance eligibility."""
61
+
62
+ _SCHEMA: ClassVar[str] = "sphynx.bank-release.v1"
63
+ bank_id: str
64
+ release_id: str
65
+ team_id: str
66
+ release_digest: str
67
+ version: int
68
+ review_status: str
69
+ state: str
70
+ bindings: Mapping[str, VersionedIdentity]
71
+ eligibility: Mapping[str, Any]
72
+ parent_release_id: str | None = None
73
+ counts: Mapping[str, int] = field(default_factory=dict)
74
+
75
+ def __post_init__(self):
76
+ for name in ("bank_id", "release_id", "team_id"):
77
+ _text(getattr(self, name), name)
78
+ if type(self.release_digest) is not str or not _DIGEST.fullmatch(self.release_digest):
79
+ raise WorkflowContractError("release_digest must be canonical SHA-256")
80
+ _version(self.version)
81
+ if self.review_status not in ("pending", "provisional", "reviewed", "disputed", "needs_changes"):
82
+ raise WorkflowContractError("invalid bank review status")
83
+ if self.state not in ("active", "held", "withdrawn"):
84
+ raise WorkflowContractError("invalid bank release state")
85
+ _optional_text(self.parent_release_id, "parent_release_id")
86
+ if self.parent_release_id == self.release_id:
87
+ raise WorkflowContractError("release cannot be its own parent")
88
+ if not isinstance(self.bindings, Mapping) or not {"bank", "review_protocol", "review_policy"} <= set(self.bindings):
89
+ raise WorkflowContractError("release requires bank, review_protocol and review_policy identities")
90
+ bindings = {_text(key, "binding role"): _identity(value) for key, value in self.bindings.items()}
91
+ object.__setattr__(self, "bindings", MappingProxyType(bindings))
92
+ eligibility = _object(self.eligibility, "eligibility")
93
+ if set(eligibility) != {"development", "ranking", "acceptance"}:
94
+ raise WorkflowContractError("eligibility requires separate development, ranking and acceptance")
95
+ for value in eligibility.values():
96
+ if not isinstance(value, Mapping) or set(value) != {"eligible", "reasons"} or type(value["eligible"]) is not bool:
97
+ raise WorkflowContractError("eligibility requires an explicit boolean and reason list")
98
+ _texts(value["reasons"], "eligibility reasons")
99
+ if (not value["eligible"] and not value["reasons"]) or (self.state != "active" and value["eligible"]):
100
+ raise WorkflowContractError("ineligible/held release needs explicit truthful eligibility")
101
+ object.__setattr__(self, "eligibility", eligibility)
102
+ counts = _object(self.counts, "review counts")
103
+ if any(type(value) is not int or value < 0 for value in counts.values()):
104
+ raise WorkflowContractError("review counts must be nonnegative integers")
105
+ object.__setattr__(self, "counts", counts)
106
+
107
+ @classmethod
108
+ def from_dict(cls, value: Mapping[str, Any]) -> BankRelease:
109
+ values = _envelope(value, cls._SCHEMA, cls)
110
+ bindings = values.get("bindings")
111
+ if not isinstance(bindings, Mapping):
112
+ raise WorkflowContractError("release bindings must be an object")
113
+ values["bindings"] = {key: VersionedIdentity.from_dict(item) for key, item in bindings.items()}
114
+ return _construct(cls, values)
115
+
116
+
117
+ @dataclass(frozen=True)
118
+ class ReviewTarget(_AuditWire):
119
+ """An authorized private review target, without domain-specific fields."""
120
+
121
+ _SCHEMA: ClassVar[str] = "sphynx.review-target.v1"
122
+ target_id: str
123
+ kind: str
124
+ subject: VersionedIdentity
125
+ protocol: VersionedIdentity
126
+ display: Mapping[str, Any] = field(repr=False)
127
+ required_for_curation: bool = False
128
+
129
+ def __post_init__(self):
130
+ _text(self.target_id, "target_id")
131
+ if self.kind not in _TARGETS:
132
+ raise WorkflowContractError("invalid review target kind")
133
+ _identity(self.subject); _identity(self.protocol)
134
+ if type(self.required_for_curation) is not bool or (self.required_for_curation and self.kind != "bank_curation"):
135
+ raise WorkflowContractError("only bank curation targets can be required for bank review")
136
+ object.__setattr__(self, "display", _object(self.display, "private review display"))
137
+
138
+ @classmethod
139
+ def from_dict(cls, value: Mapping[str, Any]) -> ReviewTarget:
140
+ values = _envelope(value, cls._SCHEMA, cls)
141
+ for key in ("subject", "protocol"):
142
+ values[key] = VersionedIdentity.from_dict(values.get(key))
143
+ return _construct(cls, values)
144
+
145
+
146
+ @dataclass(frozen=True)
147
+ class ReviewContribution(_AuditWire):
148
+ """A write request. Actor, team, trust and timestamp are server-owned."""
149
+
150
+ _SCHEMA: ClassVar[str] = "sphynx.review-contribution-request.v1"
151
+ target_id: str
152
+ decision: str
153
+ expected_version: int
154
+ idempotency_key: str
155
+ judgment: Mapping[str, Any] = field(repr=False)
156
+ supersedes_contribution_id: str | None = None
157
+
158
+ def __post_init__(self):
159
+ _text(self.target_id, "target_id")
160
+ if self.decision not in ("approve", "reject", "abstain"):
161
+ raise WorkflowContractError("review decision must be approve, reject or abstain")
162
+ _version(self.expected_version)
163
+ if type(self.idempotency_key) is not str or not _OPERATION.fullmatch(self.idempotency_key):
164
+ raise WorkflowContractError("idempotency_key requires 16-100 ASCII identifier characters")
165
+ _optional_text(self.supersedes_contribution_id, "supersedes_contribution_id")
166
+ object.__setattr__(self, "judgment", _object(self.judgment, "private review judgment"))
167
+
168
+ @classmethod
169
+ def from_dict(cls, value: Mapping[str, Any]) -> ReviewContribution:
170
+ return _construct(cls, _envelope(value, cls._SCHEMA, cls))
171
+
172
+
173
+ @dataclass(frozen=True)
174
+ class ReviewEvent(_AuditWire):
175
+ """An append-only service receipt. It cannot authenticate itself."""
176
+
177
+ _SCHEMA: ClassVar[str] = "sphynx.bank-review-event.v1"
178
+ event_id: str
179
+ bank_id: str
180
+ release_id: str
181
+ team_id: str
182
+ version: int
183
+ actor_id: str
184
+ created_at: str
185
+ kind: str
186
+ details: Mapping[str, Any] = field(default_factory=dict, repr=False)
187
+
188
+ def __post_init__(self):
189
+ for name in ("event_id", "bank_id", "release_id", "team_id", "actor_id", "created_at"):
190
+ _text(getattr(self, name), name)
191
+ _version(self.version)
192
+ if self.kind not in ("release_created", "contribution", "adjudication", "hold", "resume", "withdraw"):
193
+ raise WorkflowContractError("invalid bank review event kind")
194
+ try:
195
+ timestamp = datetime.fromisoformat(self.created_at.replace("Z", "+00:00"))
196
+ except ValueError as exc:
197
+ raise WorkflowContractError("created_at must be an ISO UTC timestamp") from exc
198
+ if timestamp.utcoffset() != timedelta(0):
199
+ raise WorkflowContractError("created_at must include UTC timezone")
200
+ object.__setattr__(self, "details", _object(self.details, "permitted event details"))
201
+
202
+ @classmethod
203
+ def from_dict(cls, value: Mapping[str, Any]) -> ReviewEvent:
204
+ return _construct(cls, _envelope(value, cls._SCHEMA, cls))
@@ -0,0 +1,180 @@
1
+ """Immutable neutral private-audit requests; the service owns authorization."""
2
+ from __future__ import annotations
3
+ from collections.abc import Mapping
4
+ from dataclasses import dataclass, field
5
+ import json
6
+ import math
7
+ import re
8
+ import unicodedata
9
+ from types import MappingProxyType
10
+ from typing import Any, ClassVar
11
+
12
+ from .audit_contracts import ReviewTarget, _AuditWire, _construct, _identity
13
+ from .evaluation_protocols import VersionedIdentity, _envelope
14
+ from .workflow_contracts import WorkflowContractError
15
+
16
+ _ID = re.compile(r'[A-Za-z0-9._:-]{1,128}\Z')
17
+ _KEY = re.compile(r'[A-Za-z0-9._:-]{16,100}\Z')
18
+ _DIGEST = re.compile(r'sha256:[0-9a-f]{64}\Z')
19
+
20
+
21
+ def _identifier(value: Any) -> str:
22
+ if type(value) is not str or not _ID.fullmatch(value) or value in ('.', '..'):
23
+ raise WorkflowContractError('audit path identity requires 1-128 ASCII identifier characters')
24
+ return value
25
+
26
+
27
+ def _positive_version(value: Any) -> int:
28
+ if type(value) is not int or not 1 <= value <= 2**63 - 1:
29
+ raise WorkflowContractError('audit version must be a positive signed 64-bit integer')
30
+ return value
31
+
32
+
33
+ def _key(value: Any) -> str:
34
+ if type(value) is not str or not _KEY.fullmatch(value):
35
+ raise WorkflowContractError('audit key requires 16-100 ASCII identifier characters')
36
+ return value
37
+
38
+
39
+ def _bounded(value: Any, maximum: int) -> None:
40
+ nodes = 0
41
+ def walk(item, depth):
42
+ nonlocal nodes
43
+ nodes += 1
44
+ if depth > 20 or nodes > 4096:
45
+ raise WorkflowContractError('audit JSON exceeds structure limits')
46
+ if isinstance(item, Mapping):
47
+ if len(item) > 128 or any(type(key) is not str for key in item):
48
+ raise WorkflowContractError('audit JSON has invalid object fields')
49
+ for child in item.values():
50
+ walk(child, depth + 1)
51
+ elif isinstance(item, (tuple, list)):
52
+ for child in item:
53
+ walk(child, depth + 1)
54
+ elif type(item) is int:
55
+ if not -(2**63) <= item <= 2**64 - 1:
56
+ raise WorkflowContractError('audit integer exceeds exact service JSON range')
57
+ elif type(item) is float:
58
+ if not math.isfinite(item):
59
+ raise WorkflowContractError('audit number must be finite')
60
+ elif item is not None and type(item) not in (str, bool):
61
+ raise WorkflowContractError('audit payload must contain JSON values')
62
+ walk(value, 0)
63
+ try:
64
+ size = len(json.dumps(value, ensure_ascii=False, allow_nan=False, separators=(',', ':')).encode('utf-8'))
65
+ except (ValueError, TypeError, UnicodeError, RecursionError) as exc:
66
+ raise WorkflowContractError('invalid audit JSON') from exc
67
+ if size > maximum:
68
+ raise WorkflowContractError('audit JSON exceeds byte limit')
69
+
70
+
71
+ def _exact_identity(value: VersionedIdentity) -> VersionedIdentity:
72
+ _identity(value)
73
+ for text, maximum in ((value.identity_id, 512), (value.version, 128)):
74
+ if len(text.encode('utf-8')) > maximum or any(unicodedata.category(char) == 'Cc' for char in text):
75
+ raise WorkflowContractError('audit identity exceeds service text limits')
76
+ return value
77
+
78
+
79
+ def _reason(value: Any) -> str:
80
+ if type(value) is not str or not value.strip():
81
+ raise WorkflowContractError('audit reason requires 1-8192 UTF-8 bytes')
82
+ try:
83
+ size = len(value.encode('utf-8'))
84
+ except UnicodeEncodeError as exc:
85
+ raise WorkflowContractError('audit reason requires valid UTF-8') from exc
86
+ if size > 8192:
87
+ raise WorkflowContractError('audit reason requires 1-8192 UTF-8 bytes')
88
+ return value
89
+
90
+
91
+ @dataclass(frozen=True)
92
+ class BankReleaseDeclaration:
93
+ """Operator request. Contains identities/targets, no executable policy."""
94
+ release_id: str
95
+ bindings: Mapping[str, VersionedIdentity]
96
+ targets: tuple[ReviewTarget, ...] = field(repr=False)
97
+ idempotency_key: str
98
+ parent_release_id: str | None = None
99
+ _SCHEMA: ClassVar[str] = 'sphynx.bank-release-declaration.v1'
100
+
101
+ def __post_init__(self):
102
+ _identifier(self.release_id); _key(self.idempotency_key)
103
+ if self.parent_release_id is not None:
104
+ _identifier(self.parent_release_id)
105
+ if self.parent_release_id == self.release_id:
106
+ raise WorkflowContractError('release cannot be its own parent')
107
+ if (not isinstance(self.bindings, Mapping) or len(self.bindings) > 32 or
108
+ not {'bank', 'review_protocol', 'review_policy'} <= self.bindings.keys()):
109
+ raise WorkflowContractError('release needs exact bank/protocol/policy bindings')
110
+ bindings = {_identifier(key): _exact_identity(item) for key, item in self.bindings.items()}
111
+ if not isinstance(self.targets, (tuple, list)) or not 1 <= len(self.targets) <= 256:
112
+ raise WorkflowContractError('release needs 1-256 explicit targets')
113
+ if any(not isinstance(item, ReviewTarget) for item in self.targets):
114
+ raise WorkflowContractError('release targets must be ReviewTarget values')
115
+ if len({item.target_id for item in self.targets}) != len(self.targets):
116
+ raise WorkflowContractError('release has duplicate target identities')
117
+ for item in self.targets:
118
+ _identifier(item.target_id); _exact_identity(item.subject); _exact_identity(item.protocol)
119
+ _bounded(item.to_dict()['display'], 65536)
120
+ object.__setattr__(self, 'bindings', MappingProxyType(bindings))
121
+ object.__setattr__(self, 'targets', tuple(self.targets))
122
+ _bounded(self.to_dict(), 262144)
123
+
124
+ def to_dict(self) -> dict[str, Any]:
125
+ return {'schema_version': self._SCHEMA, 'release_id': self.release_id,
126
+ 'parent_release_id': self.parent_release_id,
127
+ 'bindings': {key: item.to_dict() for key, item in self.bindings.items()},
128
+ 'targets': [item.to_dict() for item in self.targets], 'idempotency_key': self.idempotency_key}
129
+
130
+ @classmethod
131
+ def from_dict(cls, value: Mapping[str, Any]) -> BankReleaseDeclaration:
132
+ values = _envelope(value, cls._SCHEMA, cls)
133
+ bindings, targets = values.get('bindings'), values.get('targets')
134
+ if not isinstance(bindings, Mapping) or not isinstance(targets, (tuple, list)):
135
+ raise WorkflowContractError('invalid release bindings/targets')
136
+ values['bindings'] = {key: VersionedIdentity.from_dict(item) for key, item in bindings.items()}
137
+ values['targets'] = tuple(ReviewTarget.from_dict(item) for item in targets)
138
+ return _construct(cls, values)
139
+
140
+
141
+ @dataclass(frozen=True)
142
+ class ReviewAdjudication(_AuditWire):
143
+ target_id: str
144
+ decision: str
145
+ expected_version: int
146
+ idempotency_key: str
147
+ contribution_set_digest: str
148
+ rationale: str = field(repr=False)
149
+ _SCHEMA: ClassVar[str] = 'sphynx.review-adjudication-request.v1'
150
+
151
+ def __post_init__(self):
152
+ _identifier(self.target_id); _positive_version(self.expected_version); _key(self.idempotency_key)
153
+ if self.decision not in ('approve', 'reject'):
154
+ raise WorkflowContractError('adjudication requires approve or reject')
155
+ if type(self.contribution_set_digest) is not str or not _DIGEST.fullmatch(self.contribution_set_digest):
156
+ raise WorkflowContractError('adjudication needs the exact current contribution-set digest')
157
+ _reason(self.rationale); _bounded(self.to_dict(), 16384)
158
+
159
+ @classmethod
160
+ def from_dict(cls, value: Mapping[str, Any]) -> ReviewAdjudication:
161
+ return _construct(cls, _envelope(value, cls._SCHEMA, cls))
162
+
163
+
164
+ @dataclass(frozen=True)
165
+ class BankReleaseTransition(_AuditWire):
166
+ action: str
167
+ expected_version: int
168
+ idempotency_key: str
169
+ reason: str = field(repr=False)
170
+ _SCHEMA: ClassVar[str] = 'sphynx.bank-release-transition.v1'
171
+
172
+ def __post_init__(self):
173
+ if self.action not in ('hold', 'resume', 'withdraw'):
174
+ raise WorkflowContractError('unknown release transition')
175
+ _positive_version(self.expected_version); _key(self.idempotency_key); _reason(self.reason)
176
+ _bounded(self.to_dict(), 16384)
177
+
178
+ @classmethod
179
+ def from_dict(cls, value: Mapping[str, Any]) -> BankReleaseTransition:
180
+ return _construct(cls, _envelope(value, cls._SCHEMA, cls))