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,561 @@
|
|
|
1
|
+
"""Versioned capability declarations and source/version-scoped probe evidence.
|
|
2
|
+
|
|
3
|
+
Catalog JSON is data. Enumeration does not load entry points, optional SDKs or
|
|
4
|
+
vendor runtimes, and registry state never changes an existing harness policy.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import re
|
|
11
|
+
from dataclasses import asdict, dataclass, field
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
from hashlib import sha256
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any, Iterable
|
|
16
|
+
|
|
17
|
+
from agentloop.interoperability.validation import (
|
|
18
|
+
ImportValidationError,
|
|
19
|
+
load_json_artifact,
|
|
20
|
+
relative_reference,
|
|
21
|
+
validate_json_tree,
|
|
22
|
+
)
|
|
23
|
+
from agentloop.interventions import canonical_json
|
|
24
|
+
|
|
25
|
+
CAPABILITY_NAMES = frozenset(
|
|
26
|
+
{
|
|
27
|
+
"observation.turns",
|
|
28
|
+
"observation.model_calls",
|
|
29
|
+
"observation.tool_calls",
|
|
30
|
+
"observation.subagents",
|
|
31
|
+
"observation.policy_decisions",
|
|
32
|
+
"observation.usage",
|
|
33
|
+
"observation.outcomes",
|
|
34
|
+
"enforcement.pre_request",
|
|
35
|
+
"enforcement.pre_model",
|
|
36
|
+
"enforcement.pre_tool",
|
|
37
|
+
"enforcement.before_finish",
|
|
38
|
+
"enforcement.interrupt",
|
|
39
|
+
"enforcement.policy_failure",
|
|
40
|
+
"lifecycle.sync",
|
|
41
|
+
"lifecycle.async",
|
|
42
|
+
"lifecycle.stream",
|
|
43
|
+
"lifecycle.async_stream",
|
|
44
|
+
"lifecycle.partial_close",
|
|
45
|
+
"lifecycle.cancel",
|
|
46
|
+
"lifecycle.resume",
|
|
47
|
+
"lifecycle.fork",
|
|
48
|
+
"lifecycle.subagent_handoff",
|
|
49
|
+
"compatibility.model_override",
|
|
50
|
+
"compatibility.tool_registry",
|
|
51
|
+
"compatibility.mcp",
|
|
52
|
+
"compatibility.acp",
|
|
53
|
+
"compatibility.telemetry_export",
|
|
54
|
+
"compatibility.context_handling",
|
|
55
|
+
"compatibility.approval",
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
_LIVE_ONLY = frozenset(
|
|
59
|
+
{
|
|
60
|
+
"compatibility.model_override",
|
|
61
|
+
"compatibility.approval",
|
|
62
|
+
"compatibility.context_handling",
|
|
63
|
+
"lifecycle.resume",
|
|
64
|
+
"lifecycle.fork",
|
|
65
|
+
}
|
|
66
|
+
)
|
|
67
|
+
_ID = re.compile(r"[A-Za-z0-9_.:-]{1,128}\Z")
|
|
68
|
+
_HASH = re.compile(r"[0-9a-f]{64}\Z")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _fail(field: str, reason: str) -> None:
|
|
72
|
+
raise ImportValidationError("invalid_capability", field, reason)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _label(value: Any, field: str, *, nullable: bool = False) -> None:
|
|
76
|
+
if value is None and nullable:
|
|
77
|
+
return
|
|
78
|
+
if not isinstance(value, str) or not value.strip() or len(value.encode()) > 512:
|
|
79
|
+
_fail(field, "must be a bounded nonempty string")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dataclass(frozen=True)
|
|
83
|
+
class CapabilityDeclaration:
|
|
84
|
+
name: str
|
|
85
|
+
declared: bool | None
|
|
86
|
+
boundaries: tuple[str, ...] = ()
|
|
87
|
+
reason: str = "not declared"
|
|
88
|
+
requires_live: bool = False
|
|
89
|
+
|
|
90
|
+
def __post_init__(self) -> None:
|
|
91
|
+
if not isinstance(self.name, str) or self.name not in CAPABILITY_NAMES:
|
|
92
|
+
_fail("name", "unsupported capability dimension")
|
|
93
|
+
if self.declared is not None and type(self.declared) is not bool:
|
|
94
|
+
_fail("declared", "must be boolean or null")
|
|
95
|
+
if type(self.requires_live) is not bool:
|
|
96
|
+
_fail("requires_live", "must be boolean")
|
|
97
|
+
if not isinstance(self.boundaries, (tuple, list)) or any(
|
|
98
|
+
not isinstance(value, str) or not _ID.fullmatch(value) for value in self.boundaries
|
|
99
|
+
):
|
|
100
|
+
_fail("boundaries", "must be bounded boundary identifiers")
|
|
101
|
+
object.__setattr__(self, "boundaries", tuple(sorted(set(self.boundaries))))
|
|
102
|
+
_label(self.reason, "reason")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@dataclass(frozen=True)
|
|
106
|
+
class AdapterManifest:
|
|
107
|
+
adapter_id: str
|
|
108
|
+
adapter_version: str
|
|
109
|
+
upstream_harness: str
|
|
110
|
+
upstream_version: str | None
|
|
111
|
+
transport: str
|
|
112
|
+
kind: str
|
|
113
|
+
observation_source: str
|
|
114
|
+
declaration_source: str
|
|
115
|
+
capabilities: tuple[CapabilityDeclaration, ...]
|
|
116
|
+
code_fingerprint: str | None = None
|
|
117
|
+
schema_version: str = "1.0"
|
|
118
|
+
|
|
119
|
+
def __post_init__(self) -> None:
|
|
120
|
+
for name in (
|
|
121
|
+
"adapter_id",
|
|
122
|
+
"adapter_version",
|
|
123
|
+
"upstream_harness",
|
|
124
|
+
"transport",
|
|
125
|
+
"observation_source",
|
|
126
|
+
"declaration_source",
|
|
127
|
+
):
|
|
128
|
+
_label(getattr(self, name), name)
|
|
129
|
+
_label(self.upstream_version, "upstream_version", nullable=True)
|
|
130
|
+
if (
|
|
131
|
+
not isinstance(self.kind, str)
|
|
132
|
+
or self.kind not in {"control", "observation"}
|
|
133
|
+
or self.schema_version != "1.0"
|
|
134
|
+
):
|
|
135
|
+
_fail("manifest", "unsupported adapter kind or version")
|
|
136
|
+
if self.code_fingerprint is not None and (
|
|
137
|
+
not isinstance(self.code_fingerprint, str) or not _HASH.fullmatch(self.code_fingerprint)
|
|
138
|
+
):
|
|
139
|
+
_fail("code_fingerprint", "must be a SHA-256 digest or null")
|
|
140
|
+
if not isinstance(self.capabilities, (tuple, list)):
|
|
141
|
+
_fail("capabilities", "must be a declaration collection")
|
|
142
|
+
values = tuple(self.capabilities)
|
|
143
|
+
if any(not isinstance(item, CapabilityDeclaration) for item in values) or len(
|
|
144
|
+
{item.name for item in values}
|
|
145
|
+
) != len(values):
|
|
146
|
+
_fail("capabilities", "declarations must be typed and unique")
|
|
147
|
+
if self.kind == "observation" and any(
|
|
148
|
+
item.declared is True and item.name.startswith("enforcement.") for item in values
|
|
149
|
+
):
|
|
150
|
+
_fail("capabilities", "observation adapters cannot declare runtime enforcement")
|
|
151
|
+
object.__setattr__(self, "capabilities", tuple(sorted(values, key=lambda item: item.name)))
|
|
152
|
+
|
|
153
|
+
def to_dict(self) -> dict:
|
|
154
|
+
return json.loads(canonical_json(asdict(self)))
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def fingerprint(self) -> str:
|
|
158
|
+
return sha256(canonical_json(self.to_dict()).encode()).hexdigest()
|
|
159
|
+
|
|
160
|
+
@classmethod
|
|
161
|
+
def from_dict(cls, value: dict) -> AdapterManifest:
|
|
162
|
+
validate_json_tree(value)
|
|
163
|
+
expected = {
|
|
164
|
+
"schema_version",
|
|
165
|
+
"adapter_id",
|
|
166
|
+
"adapter_version",
|
|
167
|
+
"upstream_harness",
|
|
168
|
+
"upstream_version",
|
|
169
|
+
"transport",
|
|
170
|
+
"kind",
|
|
171
|
+
"observation_source",
|
|
172
|
+
"declaration_source",
|
|
173
|
+
"capabilities",
|
|
174
|
+
"code_fingerprint",
|
|
175
|
+
}
|
|
176
|
+
if (
|
|
177
|
+
not isinstance(value, dict)
|
|
178
|
+
or set(value) != expected
|
|
179
|
+
or not isinstance(value["capabilities"], list)
|
|
180
|
+
):
|
|
181
|
+
_fail("manifest", "must contain exactly the documented fields")
|
|
182
|
+
declarations = []
|
|
183
|
+
for item in value["capabilities"]:
|
|
184
|
+
if not isinstance(item, dict) or set(item) != {
|
|
185
|
+
"name",
|
|
186
|
+
"declared",
|
|
187
|
+
"boundaries",
|
|
188
|
+
"reason",
|
|
189
|
+
"requires_live",
|
|
190
|
+
}:
|
|
191
|
+
_fail("capabilities", "unsupported declaration fields")
|
|
192
|
+
declarations.append(CapabilityDeclaration(**item))
|
|
193
|
+
return cls(**{**value, "capabilities": tuple(declarations)})
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@dataclass(frozen=True)
|
|
197
|
+
class CapabilityObservation:
|
|
198
|
+
capability: str
|
|
199
|
+
verdict: str
|
|
200
|
+
probe_id: str | None
|
|
201
|
+
probe_version: str | None
|
|
202
|
+
tested_adapter_version: str | None
|
|
203
|
+
tested_transport: str | None
|
|
204
|
+
manifest_sha256: str | None
|
|
205
|
+
evidence_ref: str | None
|
|
206
|
+
evidence_sha256: str | None
|
|
207
|
+
environment_fingerprint: str | None
|
|
208
|
+
test_timestamp: str | None
|
|
209
|
+
boundaries: tuple[str, ...] = ()
|
|
210
|
+
source: str = "external_reported"
|
|
211
|
+
reason: str | None = None
|
|
212
|
+
_trusted: bool = field(default=False, init=False, repr=False, compare=False)
|
|
213
|
+
|
|
214
|
+
def __post_init__(self) -> None:
|
|
215
|
+
if (
|
|
216
|
+
not isinstance(self.capability, str)
|
|
217
|
+
or self.capability not in CAPABILITY_NAMES
|
|
218
|
+
or not isinstance(self.verdict, str)
|
|
219
|
+
or self.verdict
|
|
220
|
+
not in {
|
|
221
|
+
"verified_supported",
|
|
222
|
+
"verified_failed",
|
|
223
|
+
"skipped",
|
|
224
|
+
"unknown",
|
|
225
|
+
}
|
|
226
|
+
):
|
|
227
|
+
_fail("observation", "unsupported capability or verdict")
|
|
228
|
+
if not isinstance(self.source, str) or self.source not in {
|
|
229
|
+
"offline_behavioral",
|
|
230
|
+
"live_behavioral",
|
|
231
|
+
"external_reported",
|
|
232
|
+
}:
|
|
233
|
+
_fail("source", "unsupported evidence source")
|
|
234
|
+
for name in (
|
|
235
|
+
"probe_id",
|
|
236
|
+
"probe_version",
|
|
237
|
+
"tested_adapter_version",
|
|
238
|
+
"tested_transport",
|
|
239
|
+
"test_timestamp",
|
|
240
|
+
"reason",
|
|
241
|
+
):
|
|
242
|
+
_label(getattr(self, name), name, nullable=True)
|
|
243
|
+
if self.test_timestamp is not None:
|
|
244
|
+
try:
|
|
245
|
+
parsed = datetime.fromisoformat(self.test_timestamp.replace("Z", "+00:00"))
|
|
246
|
+
if parsed.tzinfo is None:
|
|
247
|
+
_fail("test_timestamp", "must include a timezone")
|
|
248
|
+
except ValueError as exc:
|
|
249
|
+
if isinstance(exc, ImportValidationError):
|
|
250
|
+
raise
|
|
251
|
+
_fail("test_timestamp", "must be an ISO timestamp")
|
|
252
|
+
for name in ("manifest_sha256", "evidence_sha256", "environment_fingerprint"):
|
|
253
|
+
value = getattr(self, name)
|
|
254
|
+
if value is not None and (not isinstance(value, str) or not _HASH.fullmatch(value)):
|
|
255
|
+
_fail(name, "must be a SHA-256 digest or null")
|
|
256
|
+
if self.evidence_ref is not None:
|
|
257
|
+
relative_reference(self.evidence_ref)
|
|
258
|
+
if self.verdict == "skipped" and self.reason is None:
|
|
259
|
+
_fail("reason", "skipped probes require a reason")
|
|
260
|
+
if not isinstance(self.boundaries, (tuple, list)) or any(
|
|
261
|
+
not isinstance(value, str) or not _ID.fullmatch(value) for value in self.boundaries
|
|
262
|
+
):
|
|
263
|
+
_fail("boundaries", "invalid observation boundaries")
|
|
264
|
+
object.__setattr__(self, "boundaries", tuple(sorted(set(self.boundaries))))
|
|
265
|
+
|
|
266
|
+
def to_dict(self) -> dict:
|
|
267
|
+
value = asdict(self)
|
|
268
|
+
value.pop("_trusted")
|
|
269
|
+
return json.loads(canonical_json(value))
|
|
270
|
+
|
|
271
|
+
@classmethod
|
|
272
|
+
def from_dict(cls, value: dict) -> CapabilityObservation:
|
|
273
|
+
validate_json_tree(value)
|
|
274
|
+
expected = set(cls.__dataclass_fields__) - {"_trusted"}
|
|
275
|
+
if not isinstance(value, dict) or set(value) != expected:
|
|
276
|
+
_fail("observation", "must contain exactly the documented fields")
|
|
277
|
+
# Deserialization preserves recorded claims, but does not turn a catalog
|
|
278
|
+
# or historical report into a fresh trusted behavioral probe.
|
|
279
|
+
return cls(**value)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _probe_observation(**kwargs: Any) -> CapabilityObservation:
|
|
283
|
+
"""Internal bridge used only after an executable driver records its outcome."""
|
|
284
|
+
result = CapabilityObservation(**kwargs)
|
|
285
|
+
object.__setattr__(result, "_trusted", True)
|
|
286
|
+
return result
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def _source_hash(paths: Iterable[Path]) -> str | None:
|
|
290
|
+
try:
|
|
291
|
+
return sha256(
|
|
292
|
+
canonical_json([sha256(path.read_bytes()).hexdigest() for path in paths]).encode()
|
|
293
|
+
).hexdigest()
|
|
294
|
+
except OSError:
|
|
295
|
+
return None
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def builtin_adapters() -> tuple[AdapterManifest, ...]:
|
|
299
|
+
"""Enumerate core descriptors without SDK imports or plugin discovery."""
|
|
300
|
+
from agentloop import harness
|
|
301
|
+
from agentloop.integrations import langgraph_harness
|
|
302
|
+
|
|
303
|
+
def control(adapter_id, capabilities, upstream, upstream_version, transport, paths):
|
|
304
|
+
declarations = {
|
|
305
|
+
name: CapabilityDeclaration(
|
|
306
|
+
name,
|
|
307
|
+
None,
|
|
308
|
+
reason="not declared by the current wrapped-call contract",
|
|
309
|
+
requires_live=name in _LIVE_ONLY,
|
|
310
|
+
)
|
|
311
|
+
for name in CAPABILITY_NAMES
|
|
312
|
+
}
|
|
313
|
+
for name, boundary in (
|
|
314
|
+
("enforcement.pre_model", "model"),
|
|
315
|
+
("enforcement.pre_tool", "tool"),
|
|
316
|
+
("enforcement.before_finish", "completion"),
|
|
317
|
+
):
|
|
318
|
+
declarations[name] = CapabilityDeclaration(
|
|
319
|
+
name,
|
|
320
|
+
harness.Hook(boundary) in capabilities.hooks and "deny" in capabilities.actions,
|
|
321
|
+
(boundary,),
|
|
322
|
+
"explicitly wrapped boundary only",
|
|
323
|
+
)
|
|
324
|
+
for name, kind in (
|
|
325
|
+
("lifecycle.sync", "sync"),
|
|
326
|
+
("lifecycle.async", "async"),
|
|
327
|
+
("lifecycle.stream", "generator"),
|
|
328
|
+
("lifecycle.async_stream", "async_generator"),
|
|
329
|
+
):
|
|
330
|
+
declarations[name] = CapabilityDeclaration(
|
|
331
|
+
name,
|
|
332
|
+
kind in capabilities.execution_kinds,
|
|
333
|
+
("wrapped_callable",),
|
|
334
|
+
"explicit callable lifecycle; hidden SDK work is outside coverage",
|
|
335
|
+
)
|
|
336
|
+
for name in ("lifecycle.partial_close", "lifecycle.cancel", "enforcement.policy_failure"):
|
|
337
|
+
declarations[name] = CapabilityDeclaration(
|
|
338
|
+
name,
|
|
339
|
+
True,
|
|
340
|
+
("wrapped_callable",),
|
|
341
|
+
"declared wrapper lifecycle, awaiting behavioral probe",
|
|
342
|
+
)
|
|
343
|
+
for name in ("observation.policy_decisions", "observation.usage", "observation.outcomes"):
|
|
344
|
+
declarations[name] = CapabilityDeclaration(
|
|
345
|
+
name,
|
|
346
|
+
True,
|
|
347
|
+
("wrapped_callable",),
|
|
348
|
+
"hook evidence/explicit usage reader; not complete application capture",
|
|
349
|
+
)
|
|
350
|
+
return AdapterManifest(
|
|
351
|
+
adapter_id,
|
|
352
|
+
harness.CONTRACT_VERSION,
|
|
353
|
+
upstream,
|
|
354
|
+
upstream_version,
|
|
355
|
+
transport,
|
|
356
|
+
"control",
|
|
357
|
+
"explicit_wrapped_calls",
|
|
358
|
+
"agentloop.harness.AdapterCapabilities",
|
|
359
|
+
tuple(declarations.values()),
|
|
360
|
+
_source_hash(paths),
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
python = control(
|
|
364
|
+
"python-wrapped",
|
|
365
|
+
harness.PYTHON_CAPABILITIES,
|
|
366
|
+
"python",
|
|
367
|
+
None,
|
|
368
|
+
"python-in-process",
|
|
369
|
+
(Path(harness.__file__),),
|
|
370
|
+
)
|
|
371
|
+
langgraph = control(
|
|
372
|
+
"langgraph-1.2.11",
|
|
373
|
+
langgraph_harness.LangGraphHarness(boundaries=harness.BOUNDARIES).capabilities,
|
|
374
|
+
"langgraph",
|
|
375
|
+
langgraph_harness.SUPPORTED_VERSION,
|
|
376
|
+
"langgraph-runnable",
|
|
377
|
+
(Path(harness.__file__), Path(langgraph_harness.__file__)),
|
|
378
|
+
)
|
|
379
|
+
sources = []
|
|
380
|
+
for adapter_id, system, module_path in (
|
|
381
|
+
("harbor-atif", "harbor", "integrations/harbor/atif.py"),
|
|
382
|
+
("omnigent-otel", "omnigent", "integrations/omnigent/telemetry.py"),
|
|
383
|
+
):
|
|
384
|
+
declarations = []
|
|
385
|
+
for name in sorted(CAPABILITY_NAMES):
|
|
386
|
+
value = (
|
|
387
|
+
False
|
|
388
|
+
if name.startswith("enforcement.")
|
|
389
|
+
else True
|
|
390
|
+
if name
|
|
391
|
+
in {
|
|
392
|
+
"observation.turns",
|
|
393
|
+
"observation.model_calls",
|
|
394
|
+
"observation.tool_calls",
|
|
395
|
+
"observation.usage",
|
|
396
|
+
"compatibility.telemetry_export",
|
|
397
|
+
}
|
|
398
|
+
or system == "omnigent"
|
|
399
|
+
and name == "observation.policy_decisions"
|
|
400
|
+
else None
|
|
401
|
+
)
|
|
402
|
+
declarations.append(
|
|
403
|
+
CapabilityDeclaration(
|
|
404
|
+
name,
|
|
405
|
+
value,
|
|
406
|
+
("file_import",),
|
|
407
|
+
"read-only source projection; no runtime interception or complete capture",
|
|
408
|
+
name in _LIVE_ONLY,
|
|
409
|
+
)
|
|
410
|
+
)
|
|
411
|
+
sources.append(
|
|
412
|
+
AdapterManifest(
|
|
413
|
+
adapter_id,
|
|
414
|
+
"1.0",
|
|
415
|
+
system,
|
|
416
|
+
None,
|
|
417
|
+
"atif-file" if system == "harbor" else "otlp-file",
|
|
418
|
+
"observation",
|
|
419
|
+
"external_reported",
|
|
420
|
+
module_path,
|
|
421
|
+
tuple(declarations),
|
|
422
|
+
_source_hash((Path(harness.__file__).parent / module_path,)),
|
|
423
|
+
)
|
|
424
|
+
)
|
|
425
|
+
return (python, langgraph, *sources)
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def get_adapter(adapter_id: str) -> AdapterManifest:
|
|
429
|
+
for descriptor in builtin_adapters():
|
|
430
|
+
if descriptor.adapter_id == adapter_id:
|
|
431
|
+
return descriptor
|
|
432
|
+
_fail("adapter_id", "adapter is not registered")
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def load_manifest(path: str | Path) -> AdapterManifest:
|
|
436
|
+
"""Read a strict data-only description; never import its named adapter."""
|
|
437
|
+
path = Path(path)
|
|
438
|
+
return AdapterManifest.from_dict(load_json_artifact(path.parent, path.name).payload)
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def capability_report(
|
|
442
|
+
manifest: AdapterManifest, observations: Iterable[CapabilityObservation] = ()
|
|
443
|
+
) -> dict:
|
|
444
|
+
by_name = {}
|
|
445
|
+
for item in observations:
|
|
446
|
+
if not isinstance(item, CapabilityObservation) or item.capability in by_name:
|
|
447
|
+
_fail("observations", "observations must be typed and unique per capability")
|
|
448
|
+
by_name[item.capability] = item
|
|
449
|
+
if set(by_name) - {item.name for item in manifest.capabilities}:
|
|
450
|
+
_fail("observations", "probe refers to an undeclared capability dimension")
|
|
451
|
+
rows, drift = [], []
|
|
452
|
+
for declaration in manifest.capabilities:
|
|
453
|
+
observed = by_name.get(declaration.name)
|
|
454
|
+
state = (
|
|
455
|
+
"declared_supported"
|
|
456
|
+
if declaration.declared is True
|
|
457
|
+
else "declared_unsupported"
|
|
458
|
+
if declaration.declared is False
|
|
459
|
+
else "unknown"
|
|
460
|
+
)
|
|
461
|
+
reason = declaration.reason
|
|
462
|
+
if observed is not None:
|
|
463
|
+
missing = any(
|
|
464
|
+
getattr(observed, key) is None
|
|
465
|
+
for key in (
|
|
466
|
+
"probe_id",
|
|
467
|
+
"probe_version",
|
|
468
|
+
"tested_adapter_version",
|
|
469
|
+
"tested_transport",
|
|
470
|
+
"manifest_sha256",
|
|
471
|
+
"evidence_ref",
|
|
472
|
+
"evidence_sha256",
|
|
473
|
+
"environment_fingerprint",
|
|
474
|
+
"test_timestamp",
|
|
475
|
+
)
|
|
476
|
+
)
|
|
477
|
+
current = (
|
|
478
|
+
observed.tested_adapter_version == manifest.adapter_version
|
|
479
|
+
and observed.tested_transport == manifest.transport
|
|
480
|
+
and observed.manifest_sha256 == manifest.fingerprint
|
|
481
|
+
)
|
|
482
|
+
scope = set(declaration.boundaries) <= set(observed.boundaries)
|
|
483
|
+
if observed.verdict == "skipped":
|
|
484
|
+
state, reason = "skipped", observed.reason
|
|
485
|
+
elif (
|
|
486
|
+
missing
|
|
487
|
+
or not current
|
|
488
|
+
or not scope
|
|
489
|
+
or not observed._trusted
|
|
490
|
+
or observed.source == "external_reported"
|
|
491
|
+
):
|
|
492
|
+
state, reason = (
|
|
493
|
+
"unknown",
|
|
494
|
+
"probe identity/evidence is incomplete, stale, out of scope or externally supplied",
|
|
495
|
+
)
|
|
496
|
+
elif (
|
|
497
|
+
declaration.requires_live or declaration.name in _LIVE_ONLY
|
|
498
|
+
) and observed.source != "live_behavioral":
|
|
499
|
+
state, reason = "unknown", "this capability requires a relevant live probe"
|
|
500
|
+
else:
|
|
501
|
+
state, reason = (
|
|
502
|
+
observed.verdict,
|
|
503
|
+
observed.reason or "current scoped behavioral probe",
|
|
504
|
+
)
|
|
505
|
+
if (
|
|
506
|
+
declaration.declared is True
|
|
507
|
+
and state == "verified_failed"
|
|
508
|
+
or declaration.declared is False
|
|
509
|
+
and state == "verified_supported"
|
|
510
|
+
):
|
|
511
|
+
drift.append(
|
|
512
|
+
{
|
|
513
|
+
"capability": declaration.name,
|
|
514
|
+
"declared": declaration.declared,
|
|
515
|
+
"observed": state,
|
|
516
|
+
}
|
|
517
|
+
)
|
|
518
|
+
rows.append(
|
|
519
|
+
{
|
|
520
|
+
**asdict(declaration),
|
|
521
|
+
"state": state,
|
|
522
|
+
"reason": reason,
|
|
523
|
+
"observation": observed.to_dict() if observed else None,
|
|
524
|
+
}
|
|
525
|
+
)
|
|
526
|
+
return {
|
|
527
|
+
"schema_version": "1.0",
|
|
528
|
+
"adapter": manifest.to_dict(),
|
|
529
|
+
"manifest_sha256": manifest.fingerprint,
|
|
530
|
+
"capabilities": rows,
|
|
531
|
+
"drift": drift,
|
|
532
|
+
"drift_status": "failed" if drift else "no_observed_drift",
|
|
533
|
+
"coverage": "explicit boundaries only; no claim of all application work",
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
def require_verified_boundary(
|
|
538
|
+
adapter_id: str, capability: str, observations: Iterable[CapabilityObservation]
|
|
539
|
+
) -> None:
|
|
540
|
+
"""Gate new registry-dependent controls on current built-in probe evidence."""
|
|
541
|
+
manifest = get_adapter(adapter_id)
|
|
542
|
+
if manifest.upstream_harness == "langgraph":
|
|
543
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
544
|
+
|
|
545
|
+
try:
|
|
546
|
+
installed = version("langgraph")
|
|
547
|
+
except PackageNotFoundError:
|
|
548
|
+
installed = None
|
|
549
|
+
if installed != manifest.upstream_version:
|
|
550
|
+
_fail("enforcement", "current upstream runtime version is unavailable or incompatible")
|
|
551
|
+
report = capability_report(manifest, observations)
|
|
552
|
+
row = next((row for row in report["capabilities"] if row["name"] == capability), None)
|
|
553
|
+
if (
|
|
554
|
+
manifest.kind != "control"
|
|
555
|
+
or manifest.code_fingerprint is None
|
|
556
|
+
or row is None
|
|
557
|
+
or not capability.startswith("enforcement.")
|
|
558
|
+
or row["declared"] is not True
|
|
559
|
+
or row["state"] != "verified_supported"
|
|
560
|
+
):
|
|
561
|
+
_fail("enforcement", "current adapter has no verified supported dispatch boundary")
|