agentloop-profiler 0.1.0__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.
- agentloop/__init__.py +136 -0
- agentloop/ablation_protocol.py +229 -0
- agentloop/ablations.py +564 -0
- agentloop/aggregate_cli.py +88 -0
- agentloop/aggregate_stats.py +175 -0
- agentloop/aggregates.py +532 -0
- agentloop/audit.py +39 -0
- agentloop/autoinstrument.py +145 -0
- agentloop/batch_experiments.py +295 -0
- agentloop/batching.py +95 -0
- agentloop/budget_types.py +335 -0
- agentloop/budgets.py +539 -0
- agentloop/calibration.py +872 -0
- agentloop/checkpoint_state.py +306 -0
- agentloop/checkpoint_store.py +86 -0
- agentloop/ci.py +202 -0
- agentloop/cli.py +1314 -0
- agentloop/client.py +228 -0
- agentloop/completion.py +345 -0
- agentloop/completion_types.py +199 -0
- agentloop/config.py +50 -0
- agentloop/context_controls.py +443 -0
- agentloop/context_experiments.py +337 -0
- agentloop/context_types.py +216 -0
- agentloop/costs.py +667 -0
- agentloop/cursor_validation.py +48 -0
- agentloop/decorators.py +445 -0
- agentloop/demo.py +300 -0
- agentloop/doctor.py +341 -0
- agentloop/drift.py +361 -0
- agentloop/drift_cli.py +147 -0
- agentloop/drift_types.py +110 -0
- agentloop/drift_windows.py +340 -0
- agentloop/entrypoint.py +201 -0
- agentloop/estimates.py +133 -0
- agentloop/events.py +75 -0
- agentloop/experiment_artifacts.py +80 -0
- agentloop/experiment_observations.py +169 -0
- agentloop/experiment_reports.py +545 -0
- agentloop/experiment_types.py +355 -0
- agentloop/experiments.py +392 -0
- agentloop/exporters.py +117 -0
- agentloop/finding_benchmark_types.py +131 -0
- agentloop/finding_benchmarks.py +424 -0
- agentloop/findings.py +419 -0
- agentloop/graph.py +464 -0
- agentloop/harness.py +825 -0
- agentloop/harness_bench/__init__.py +1 -0
- agentloop/harness_bench/cli.py +77 -0
- agentloop/harness_bench/drivers.py +96 -0
- agentloop/harness_bench/offline.py +507 -0
- agentloop/harness_evidence.py +585 -0
- agentloop/harness_usage.py +91 -0
- agentloop/html_report.py +860 -0
- agentloop/integrations/__init__.py +1 -0
- agentloop/integrations/crewai.py +144 -0
- agentloop/integrations/harbor/__init__.py +1 -0
- agentloop/integrations/harbor/atif.py +982 -0
- agentloop/integrations/harbor/atif_validation.py +329 -0
- agentloop/integrations/harbor/cli.py +207 -0
- agentloop/integrations/harbor/manifest.py +86 -0
- agentloop/integrations/harbor/trial_evidence.py +196 -0
- agentloop/integrations/harbor/trials.py +1109 -0
- agentloop/integrations/harbor/verifier.py +118 -0
- agentloop/integrations/langgraph.py +150 -0
- agentloop/integrations/langgraph_harness.py +466 -0
- agentloop/integrations/langgraph_recovery.py +366 -0
- agentloop/integrations/omnigent/__init__.py +1 -0
- agentloop/integrations/omnigent/cli.py +78 -0
- agentloop/integrations/omnigent/evidence.py +93 -0
- agentloop/integrations/omnigent/telemetry.py +422 -0
- agentloop/integrations/openai.py +415 -0
- agentloop/integrations/openai_agents.py +291 -0
- agentloop/integrations/sdk.py +82 -0
- agentloop/integrations/vercel_ai.py +83 -0
- agentloop/interoperability/__init__.py +5 -0
- agentloop/interoperability/artifacts.py +112 -0
- agentloop/interoperability/cli.py +59 -0
- agentloop/interoperability/cohort_evidence.py +175 -0
- agentloop/interoperability/cohorts.py +681 -0
- agentloop/interoperability/contracts.py +427 -0
- agentloop/interoperability/coordination.py +554 -0
- agentloop/interoperability/evidence.py +215 -0
- agentloop/interoperability/otlp_jsonl.py +775 -0
- agentloop/interoperability/otlp_validation.py +496 -0
- agentloop/interoperability/registry.py +561 -0
- agentloop/interoperability/validation.py +333 -0
- agentloop/intervention_service.py +110 -0
- agentloop/intervention_store.py +118 -0
- agentloop/interventions.py +190 -0
- agentloop/issues.py +90 -0
- agentloop/judgment_adapters.py +125 -0
- agentloop/judgment_benchmark_types.py +98 -0
- agentloop/judgment_benchmarks.py +606 -0
- agentloop/judgment_types.py +241 -0
- agentloop/judgment_views.py +38 -0
- agentloop/judgments.py +342 -0
- agentloop/loop_guards.py +274 -0
- agentloop/loop_types.py +66 -0
- agentloop/markdown.py +102 -0
- agentloop/metrics.py +342 -0
- agentloop/migrations.py +303 -0
- agentloop/model_routing.py +470 -0
- agentloop/onboarding.py +315 -0
- agentloop/operations.py +44 -0
- agentloop/optimizer.py +199 -0
- agentloop/otel.py +715 -0
- agentloop/otel_ids.py +81 -0
- agentloop/otel_semantics.py +134 -0
- agentloop/parallelism.py +141 -0
- agentloop/patches.py +603 -0
- agentloop/plan_export.py +126 -0
- agentloop/policy_promotion.py +325 -0
- agentloop/promotion_evidence.py +304 -0
- agentloop/promotion_types.py +209 -0
- agentloop/quality.py +446 -0
- agentloop/ranking.py +529 -0
- agentloop/recovery_evidence.py +96 -0
- agentloop/replay.py +753 -0
- agentloop/retention.py +375 -0
- agentloop/retention_types.py +113 -0
- agentloop/reuse_experiments.py +247 -0
- agentloop/routing_checks.py +116 -0
- agentloop/routing_types.py +241 -0
- agentloop/rules.py +396 -0
- agentloop/runtime.py +286 -0
- agentloop/savings.py +158 -0
- agentloop/scheduling_plan.py +99 -0
- agentloop/scheduling_types.py +169 -0
- agentloop/schema.py +257 -0
- agentloop/semantic_rules.py +200 -0
- agentloop/semantic_waste.py +345 -0
- agentloop/semantic_waste_types.py +88 -0
- agentloop/server.py +494 -0
- agentloop/store.py +1429 -0
- agentloop/structural_experiments.py +431 -0
- agentloop/structural_types.py +113 -0
- agentloop/structured_quality.py +1020 -0
- agentloop/studies.py +629 -0
- agentloop/study_statistics.py +58 -0
- agentloop/substitution_evidence.py +199 -0
- agentloop/substitution_export.py +190 -0
- agentloop/substitution_reports.py +407 -0
- agentloop/substitution_types.py +284 -0
- agentloop/substitutions.py +267 -0
- agentloop/timing.py +142 -0
- agentloop/tokens.py +188 -0
- agentloop/tool_scheduling.py +410 -0
- agentloop/trace_sources.py +31 -0
- agentloop/tracer.py +494 -0
- agentloop/value.py +306 -0
- agentloop/version.py +3 -0
- agentloop/workflow_types.py +264 -0
- agentloop/workflows.py +284 -0
- agentloop_profiler-0.1.0.dist-info/METADATA +513 -0
- agentloop_profiler-0.1.0.dist-info/RECORD +166 -0
- agentloop_profiler-0.1.0.dist-info/WHEEL +5 -0
- agentloop_profiler-0.1.0.dist-info/entry_points.txt +2 -0
- agentloop_profiler-0.1.0.dist-info/licenses/LICENSE +201 -0
- agentloop_profiler-0.1.0.dist-info/licenses/THIRD_PARTY_LICENSES.md +101 -0
- agentloop_profiler-0.1.0.dist-info/top_level.txt +2 -0
- dashboard/__init__.py +5 -0
- dashboard/app.py +922 -0
- dashboard/streamlit_compat.py +63 -0
- dashboard/trace_upload.py +57 -0
- dashboard/value_view.py +105 -0
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
"""Internal source receipt 1.0; execution measurements remain native traces."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import re
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from hashlib import sha256
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from agentloop.interoperability.validation import (
|
|
12
|
+
ImportLimits,
|
|
13
|
+
ImportValidationError,
|
|
14
|
+
relative_reference,
|
|
15
|
+
validate_json_tree,
|
|
16
|
+
)
|
|
17
|
+
from agentloop.interventions import canonical_json
|
|
18
|
+
|
|
19
|
+
RECEIPT_SCHEMA_VERSION = "1.0"
|
|
20
|
+
SOURCE_SYSTEMS = frozenset({"harbor", "omnigent", "otel"})
|
|
21
|
+
PROVENANCE = frozenset(
|
|
22
|
+
{
|
|
23
|
+
"native_observed",
|
|
24
|
+
"provider_reported",
|
|
25
|
+
"external_reported",
|
|
26
|
+
"calculated",
|
|
27
|
+
"inferred",
|
|
28
|
+
"synthetic",
|
|
29
|
+
"unknown",
|
|
30
|
+
}
|
|
31
|
+
)
|
|
32
|
+
IDENTITY_FIELDS = frozenset(
|
|
33
|
+
{
|
|
34
|
+
"job_id",
|
|
35
|
+
"trial_id",
|
|
36
|
+
"task_id",
|
|
37
|
+
"task_digest",
|
|
38
|
+
"step_id",
|
|
39
|
+
"session_id",
|
|
40
|
+
"trajectory_id",
|
|
41
|
+
"trace_id",
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
_COMPLETENESS_FIELDS = frozenset({"trajectory", "usage", "quality", "parentage", "timing", "cost"})
|
|
45
|
+
_COVERAGE = frozenset({"complete", "partial", "missing", "unknown", "not_applicable"})
|
|
46
|
+
_HASH = re.compile(r"^[0-9a-f]{64}$")
|
|
47
|
+
_CODE = re.compile(r"^[a-z][a-z0-9_]{0,63}$")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _error(field: str, reason: str) -> None:
|
|
51
|
+
raise ImportValidationError("invalid_receipt", field, reason)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _object(value: Any, field: str, expected: set[str] | frozenset[str]) -> dict[str, Any]:
|
|
55
|
+
if not isinstance(value, dict) or set(value) != expected:
|
|
56
|
+
_error(field, "must contain exactly the documented fields")
|
|
57
|
+
return value
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _string(value: Any, field: str, *, nullable: bool = False, limit: int = 512) -> None:
|
|
61
|
+
if value is None and nullable:
|
|
62
|
+
return
|
|
63
|
+
if not isinstance(value, str) or not value.strip() or len(value.encode("utf-8")) > limit:
|
|
64
|
+
_error(field, "must be a nonempty bounded string")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _enum(value: Any, field: str, choices: set[str] | frozenset[str]) -> None:
|
|
68
|
+
if not isinstance(value, str) or value not in choices:
|
|
69
|
+
_error(field, "unsupported value")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _hash(value: Any, field: str, *, nullable: bool = False) -> None:
|
|
73
|
+
if value is None and nullable:
|
|
74
|
+
return
|
|
75
|
+
if not isinstance(value, str) or not _HASH.fullmatch(value):
|
|
76
|
+
_error(field, "must be a lowercase SHA-256 digest")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def external_id(kind: str, system: str, identity: dict[str, Any]) -> str:
|
|
80
|
+
"""Hash a typed identity tuple; callers explicitly supply fallback hashes.
|
|
81
|
+
|
|
82
|
+
Do not use session IDs alone for document identity. Missing identity inputs
|
|
83
|
+
remain missing; deriving a document key from artifact bytes is a caller
|
|
84
|
+
decision whose provenance must be recorded in the receipt.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
if kind not in {"run", "event", "receipt", "group"} or system not in SOURCE_SYSTEMS:
|
|
88
|
+
raise ImportValidationError("invalid_identity", "identity", "unsupported namespace")
|
|
89
|
+
validate_json_tree(identity)
|
|
90
|
+
if not isinstance(identity, dict) or not identity:
|
|
91
|
+
raise ImportValidationError("invalid_identity", "identity", "identity inputs are required")
|
|
92
|
+
parts = []
|
|
93
|
+
for key, value in sorted(identity.items()):
|
|
94
|
+
_string(key, "identity.key")
|
|
95
|
+
if value is None:
|
|
96
|
+
continue
|
|
97
|
+
if type(value) not in {str, int}:
|
|
98
|
+
raise ImportValidationError(
|
|
99
|
+
"invalid_identity", "identity", "identity values must be strings or integers"
|
|
100
|
+
)
|
|
101
|
+
if isinstance(value, str):
|
|
102
|
+
_string(value, "identity.value", limit=4096)
|
|
103
|
+
parts.append([key, type(value).__name__, value])
|
|
104
|
+
if not parts:
|
|
105
|
+
raise ImportValidationError(
|
|
106
|
+
"invalid_identity", "identity", "non-null identity inputs are required"
|
|
107
|
+
)
|
|
108
|
+
digest = sha256(
|
|
109
|
+
canonical_json({"system": system, "identity": parts}).encode("utf-8")
|
|
110
|
+
).hexdigest()
|
|
111
|
+
return f"{kind}_{digest}"
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def receipt_id(payload: dict[str, Any]) -> str:
|
|
115
|
+
source = payload["source"]
|
|
116
|
+
return external_id(
|
|
117
|
+
"receipt",
|
|
118
|
+
source["system"],
|
|
119
|
+
{
|
|
120
|
+
**payload["external_identity"],
|
|
121
|
+
"artifact_reference": source["artifact_reference"],
|
|
122
|
+
"artifact_sha256": source["artifact_sha256"],
|
|
123
|
+
"source_format": source["format"],
|
|
124
|
+
"format_version": source["format_version"],
|
|
125
|
+
},
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _validate_quality(outcome: dict[str, Any]) -> None:
|
|
130
|
+
_object(
|
|
131
|
+
outcome,
|
|
132
|
+
"outcome",
|
|
133
|
+
{
|
|
134
|
+
"execution_status",
|
|
135
|
+
"verifier_status",
|
|
136
|
+
"verifier_dimensions",
|
|
137
|
+
"quality_pass",
|
|
138
|
+
"quality_basis",
|
|
139
|
+
"scoring_contract",
|
|
140
|
+
"verifier_isolation",
|
|
141
|
+
},
|
|
142
|
+
)
|
|
143
|
+
_enum(
|
|
144
|
+
outcome["execution_status"],
|
|
145
|
+
"outcome.execution_status",
|
|
146
|
+
{"completed", "failed", "timed_out", "cancelled", "partial", "missing", "unknown"},
|
|
147
|
+
)
|
|
148
|
+
_enum(
|
|
149
|
+
outcome["verifier_status"],
|
|
150
|
+
"outcome.verifier_status",
|
|
151
|
+
{"available", "failed", "missing", "invalid", "unknown"},
|
|
152
|
+
)
|
|
153
|
+
_enum(
|
|
154
|
+
outcome["verifier_isolation"],
|
|
155
|
+
"outcome.verifier_isolation",
|
|
156
|
+
{"shared", "separate", "mixed", "unknown"},
|
|
157
|
+
)
|
|
158
|
+
_enum(
|
|
159
|
+
outcome["quality_basis"],
|
|
160
|
+
"outcome.quality_basis",
|
|
161
|
+
{"external_uninterpreted_reward", "configured_thresholds", "unavailable", "unknown"},
|
|
162
|
+
)
|
|
163
|
+
dimensions = outcome["verifier_dimensions"]
|
|
164
|
+
if not isinstance(dimensions, dict):
|
|
165
|
+
_error("outcome.verifier_dimensions", "must be an object")
|
|
166
|
+
for name, value in dimensions.items():
|
|
167
|
+
_string(name, "outcome.verifier_dimensions.key")
|
|
168
|
+
if value is not None and type(value) not in {int, float}:
|
|
169
|
+
_error("outcome.verifier_dimensions", "dimensions must be finite numbers or null")
|
|
170
|
+
passed = outcome["quality_pass"]
|
|
171
|
+
if passed is not None and type(passed) is not bool:
|
|
172
|
+
_error("outcome.quality_pass", "must be a boolean or null")
|
|
173
|
+
scoring = outcome["scoring_contract"]
|
|
174
|
+
if scoring is None:
|
|
175
|
+
if passed is not None or outcome["quality_basis"] == "configured_thresholds":
|
|
176
|
+
_error("outcome.quality_pass", "a pass decision requires an explicit scoring contract")
|
|
177
|
+
if outcome["quality_basis"] == "external_uninterpreted_reward" and (
|
|
178
|
+
outcome["verifier_status"] != "available" or not dimensions
|
|
179
|
+
):
|
|
180
|
+
_error("outcome.quality_basis", "uninterpreted rewards require available dimensions")
|
|
181
|
+
return
|
|
182
|
+
_object(
|
|
183
|
+
scoring,
|
|
184
|
+
"outcome.scoring_contract",
|
|
185
|
+
{"schema_version", "scorer_id", "rule", "thresholds", "config_sha256"},
|
|
186
|
+
)
|
|
187
|
+
if scoring["schema_version"] != "1.0" or scoring["rule"] != "all_gte":
|
|
188
|
+
_error("outcome.scoring_contract", "unsupported scoring contract")
|
|
189
|
+
_string(scoring["scorer_id"], "outcome.scoring_contract.scorer_id")
|
|
190
|
+
thresholds = scoring["thresholds"]
|
|
191
|
+
if not isinstance(thresholds, dict) or not thresholds:
|
|
192
|
+
_error("outcome.scoring_contract.thresholds", "explicit required dimensions are necessary")
|
|
193
|
+
for name, value in thresholds.items():
|
|
194
|
+
_string(name, "outcome.scoring_contract.thresholds.key")
|
|
195
|
+
if type(value) not in {int, float}:
|
|
196
|
+
_error("outcome.scoring_contract.thresholds", "thresholds must be finite numbers")
|
|
197
|
+
config = {key: value for key, value in scoring.items() if key != "config_sha256"}
|
|
198
|
+
expected_hash = sha256(canonical_json(config).encode("utf-8")).hexdigest()
|
|
199
|
+
if scoring["config_sha256"] != expected_hash:
|
|
200
|
+
_error("outcome.scoring_contract.config_sha256", "does not match the scoring definition")
|
|
201
|
+
expected = None
|
|
202
|
+
if outcome["verifier_status"] == "available" and all(
|
|
203
|
+
dimensions.get(key) is not None for key in thresholds
|
|
204
|
+
):
|
|
205
|
+
expected = all(dimensions[key] >= threshold for key, threshold in thresholds.items())
|
|
206
|
+
if passed is not expected or outcome["quality_basis"] != "configured_thresholds":
|
|
207
|
+
_error("outcome.quality_pass", "must match available rewards and configured thresholds")
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _validate_notice(notice: Any) -> None:
|
|
211
|
+
_object(notice, "notices[]", {"code", "severity", "artifact", "field", "record"})
|
|
212
|
+
if not isinstance(notice["code"], str) or not _CODE.fullmatch(notice["code"]):
|
|
213
|
+
_error("notices[].code", "must be a bounded diagnostic code")
|
|
214
|
+
_enum(notice["severity"], "notices[].severity", {"info", "warning", "error"})
|
|
215
|
+
if notice["artifact"] is not None:
|
|
216
|
+
relative_reference(notice["artifact"])
|
|
217
|
+
_string(notice["field"], "notices[].field", nullable=True)
|
|
218
|
+
if notice["record"] is not None and (type(notice["record"]) is not int or notice["record"] < 1):
|
|
219
|
+
_error("notices[].record", "must be a positive source record number or null")
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
@dataclass(frozen=True)
|
|
223
|
+
class ImportReceipt:
|
|
224
|
+
"""Canonical JSON owns the snapshot; readers receive independent copies."""
|
|
225
|
+
|
|
226
|
+
_json: str
|
|
227
|
+
|
|
228
|
+
@property
|
|
229
|
+
def receipt_id(self) -> str:
|
|
230
|
+
return self.to_dict()["receipt_id"]
|
|
231
|
+
|
|
232
|
+
def to_dict(self) -> dict[str, Any]:
|
|
233
|
+
return json.loads(self._json)
|
|
234
|
+
|
|
235
|
+
@classmethod
|
|
236
|
+
def from_dict(
|
|
237
|
+
cls, payload: dict[str, Any], *, limits: ImportLimits = ImportLimits()
|
|
238
|
+
) -> ImportReceipt:
|
|
239
|
+
validate_json_tree(payload, limits)
|
|
240
|
+
_object(
|
|
241
|
+
payload,
|
|
242
|
+
"receipt",
|
|
243
|
+
{
|
|
244
|
+
"schema_version",
|
|
245
|
+
"receipt_id",
|
|
246
|
+
"source",
|
|
247
|
+
"external_identity",
|
|
248
|
+
"identity_provenance",
|
|
249
|
+
"traces",
|
|
250
|
+
"missing_trace_reason",
|
|
251
|
+
"outcome",
|
|
252
|
+
"completeness",
|
|
253
|
+
"relationships",
|
|
254
|
+
"notices",
|
|
255
|
+
"source_metadata",
|
|
256
|
+
},
|
|
257
|
+
)
|
|
258
|
+
if payload["schema_version"] != RECEIPT_SCHEMA_VERSION:
|
|
259
|
+
_error("schema_version", "unsupported receipt version")
|
|
260
|
+
source = _object(
|
|
261
|
+
payload["source"],
|
|
262
|
+
"source",
|
|
263
|
+
{
|
|
264
|
+
"system",
|
|
265
|
+
"producer_version",
|
|
266
|
+
"producer_revision",
|
|
267
|
+
"format",
|
|
268
|
+
"format_version",
|
|
269
|
+
"artifact_reference",
|
|
270
|
+
"artifact_sha256",
|
|
271
|
+
"trust",
|
|
272
|
+
},
|
|
273
|
+
)
|
|
274
|
+
_enum(source["system"], "source.system", SOURCE_SYSTEMS)
|
|
275
|
+
formats = {
|
|
276
|
+
"harbor": {"atif", "harbor_trial", "otlp_json", "otlp_jsonl"},
|
|
277
|
+
"omnigent": {"otlp_json", "otlp_jsonl"},
|
|
278
|
+
"otel": {"otlp_json", "otlp_jsonl"},
|
|
279
|
+
}
|
|
280
|
+
_enum(source["format"], "source.format", formats[source["system"]])
|
|
281
|
+
for key in ("producer_version", "format_version"):
|
|
282
|
+
_string(source[key], "source." + key, nullable=True)
|
|
283
|
+
revision = source["producer_revision"]
|
|
284
|
+
if revision is not None and (
|
|
285
|
+
not isinstance(revision, str) or not re.fullmatch(r"[0-9a-f]{40}", revision)
|
|
286
|
+
):
|
|
287
|
+
_error("source.producer_revision", "must be a pinned full Git SHA or null")
|
|
288
|
+
relative_reference(source["artifact_reference"], limits)
|
|
289
|
+
_hash(source["artifact_sha256"], "source.artifact_sha256")
|
|
290
|
+
_enum(source["trust"], "source.trust", {"agent_writable", "external_reported", "unknown"})
|
|
291
|
+
identity = _object(payload["external_identity"], "external_identity", IDENTITY_FIELDS)
|
|
292
|
+
for key, value in identity.items():
|
|
293
|
+
_string(value, "external_identity." + key, nullable=True)
|
|
294
|
+
if identity["task_digest"] is not None:
|
|
295
|
+
digest = identity["task_digest"]
|
|
296
|
+
if not digest.startswith("sha256:"):
|
|
297
|
+
_error("external_identity.task_digest", "must be a prefixed SHA-256 digest")
|
|
298
|
+
_hash(digest[7:], "external_identity.task_digest")
|
|
299
|
+
provenance = _object(payload["identity_provenance"], "identity_provenance", IDENTITY_FIELDS)
|
|
300
|
+
for key, value in provenance.items():
|
|
301
|
+
_enum(value, "identity_provenance." + key, PROVENANCE)
|
|
302
|
+
if identity[key] is None and value != "unknown":
|
|
303
|
+
_error("identity_provenance." + key, "absent identities have unknown provenance")
|
|
304
|
+
if payload["receipt_id"] != receipt_id(payload):
|
|
305
|
+
_error("receipt_id", "does not match source identity and supplied artifact bytes")
|
|
306
|
+
traces = payload["traces"]
|
|
307
|
+
if not isinstance(traces, list) or len(traces) > limits.max_trajectories:
|
|
308
|
+
_error("traces", "must be a bounded list")
|
|
309
|
+
seen_runs, seen_paths = set(), set()
|
|
310
|
+
for item in traces:
|
|
311
|
+
_object(item, "traces[]", {"run_id", "trace_file", "trace_sha256"})
|
|
312
|
+
_string(item["run_id"], "traces[].run_id")
|
|
313
|
+
relative_reference(item["trace_file"], limits)
|
|
314
|
+
_hash(item["trace_sha256"], "traces[].trace_sha256")
|
|
315
|
+
if item["run_id"] in seen_runs or item["trace_file"] in seen_paths:
|
|
316
|
+
_error("traces", "duplicate native trace identity or reference")
|
|
317
|
+
seen_runs.add(item["run_id"])
|
|
318
|
+
seen_paths.add(item["trace_file"])
|
|
319
|
+
reason = payload["missing_trace_reason"]
|
|
320
|
+
if traces and reason is not None:
|
|
321
|
+
_error("missing_trace_reason", "must be null when native traces are present")
|
|
322
|
+
if not traces:
|
|
323
|
+
_enum(
|
|
324
|
+
reason,
|
|
325
|
+
"missing_trace_reason",
|
|
326
|
+
{
|
|
327
|
+
"missing_artifact",
|
|
328
|
+
"invalid_artifact",
|
|
329
|
+
"untimed_operations",
|
|
330
|
+
"no_execution_spans",
|
|
331
|
+
"unsupported_format",
|
|
332
|
+
},
|
|
333
|
+
)
|
|
334
|
+
_validate_quality(payload["outcome"])
|
|
335
|
+
completeness = _object(payload["completeness"], "completeness", _COMPLETENESS_FIELDS)
|
|
336
|
+
for key, value in completeness.items():
|
|
337
|
+
_enum(value, "completeness." + key, _COVERAGE)
|
|
338
|
+
relationships = payload["relationships"]
|
|
339
|
+
if not isinstance(relationships, list) or len(relationships) > limits.max_references:
|
|
340
|
+
_error("relationships", "must be a bounded list")
|
|
341
|
+
for item in relationships:
|
|
342
|
+
_object(item, "relationships[]", {"kind", "target_id", "basis", "resolved"})
|
|
343
|
+
_enum(
|
|
344
|
+
item["kind"],
|
|
345
|
+
"relationships[].kind",
|
|
346
|
+
{"delegation", "continuation", "session", "span_link"},
|
|
347
|
+
)
|
|
348
|
+
_string(item["target_id"], "relationships[].target_id")
|
|
349
|
+
_enum(item["basis"], "relationships[].basis", PROVENANCE)
|
|
350
|
+
if type(item["resolved"]) is not bool:
|
|
351
|
+
_error("relationships[].resolved", "must be a boolean")
|
|
352
|
+
if not isinstance(payload["notices"], list) or len(payload["notices"]) > limits.max_nodes:
|
|
353
|
+
_error("notices", "must be a bounded list")
|
|
354
|
+
for notice in payload["notices"]:
|
|
355
|
+
_validate_notice(notice)
|
|
356
|
+
metadata = payload["source_metadata"]
|
|
357
|
+
if (
|
|
358
|
+
not isinstance(metadata, dict)
|
|
359
|
+
or len(canonical_json(metadata).encode("utf-8")) > limits.max_metadata_bytes
|
|
360
|
+
):
|
|
361
|
+
_error("source_metadata", "must be a bounded sanitized object")
|
|
362
|
+
serialized = canonical_json(payload)
|
|
363
|
+
if len(serialized.encode("utf-8")) > limits.max_json_bytes:
|
|
364
|
+
_error("receipt", "receipt byte limit exceeded")
|
|
365
|
+
return cls(serialized)
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def summarize_receipts(receipts: list[ImportReceipt]) -> dict[str, Any]:
|
|
369
|
+
"""Receipt-level inventory, never extrapolated into trial or success counts."""
|
|
370
|
+
|
|
371
|
+
seen = set()
|
|
372
|
+
report: dict[str, Any] = {
|
|
373
|
+
"receipts": 0,
|
|
374
|
+
"native_traces": 0,
|
|
375
|
+
"without_native_traces": 0,
|
|
376
|
+
"verifier_available": 0,
|
|
377
|
+
"external_quality_uninterpreted": 0,
|
|
378
|
+
"notices_by_code": {},
|
|
379
|
+
"source_versions": [],
|
|
380
|
+
}
|
|
381
|
+
report.update(dict.fromkeys(("quality_pass", "quality_fail", "quality_indeterminate"), 0))
|
|
382
|
+
versions = set()
|
|
383
|
+
for receipt in receipts:
|
|
384
|
+
data = receipt.to_dict()
|
|
385
|
+
if receipt.receipt_id in seen:
|
|
386
|
+
_error("receipts", "duplicate receipt identity")
|
|
387
|
+
seen.add(receipt.receipt_id)
|
|
388
|
+
report["receipts"] += 1
|
|
389
|
+
report["native_traces"] += len(data["traces"])
|
|
390
|
+
report["without_native_traces"] += int(not data["traces"])
|
|
391
|
+
outcome = data["outcome"]
|
|
392
|
+
report["verifier_available"] += int(outcome["verifier_status"] == "available")
|
|
393
|
+
report["external_quality_uninterpreted"] += int(
|
|
394
|
+
outcome["quality_basis"] == "external_uninterpreted_reward"
|
|
395
|
+
)
|
|
396
|
+
key = (
|
|
397
|
+
"quality_indeterminate"
|
|
398
|
+
if outcome["quality_pass"] is None
|
|
399
|
+
else "quality_pass"
|
|
400
|
+
if outcome["quality_pass"]
|
|
401
|
+
else "quality_fail"
|
|
402
|
+
)
|
|
403
|
+
report[key] += 1
|
|
404
|
+
for notice in data["notices"]:
|
|
405
|
+
codes = report["notices_by_code"]
|
|
406
|
+
codes[notice["code"]] = codes.get(notice["code"], 0) + 1
|
|
407
|
+
source = data["source"]
|
|
408
|
+
versions.add(
|
|
409
|
+
(
|
|
410
|
+
source["system"],
|
|
411
|
+
source["producer_version"],
|
|
412
|
+
source["producer_revision"],
|
|
413
|
+
source["format"],
|
|
414
|
+
source["format_version"],
|
|
415
|
+
)
|
|
416
|
+
)
|
|
417
|
+
report["notices_by_code"] = dict(sorted(report["notices_by_code"].items()))
|
|
418
|
+
report["source_versions"] = [
|
|
419
|
+
dict(
|
|
420
|
+
zip(
|
|
421
|
+
("system", "producer_version", "producer_revision", "format", "format_version"),
|
|
422
|
+
values,
|
|
423
|
+
)
|
|
424
|
+
)
|
|
425
|
+
for values in sorted(versions, key=lambda values: tuple(value or "" for value in values))
|
|
426
|
+
]
|
|
427
|
+
return report
|