context-engineering-core 0.12.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.
- context_engine/__init__.py +15 -0
- context_engine/__main__.py +3 -0
- context_engine/_sqlite.py +19 -0
- context_engine/_windows_security.py +125 -0
- context_engine/_windows_storage.py +110 -0
- context_engine/adapters.py +148 -0
- context_engine/answers.py +90 -0
- context_engine/budget.py +93 -0
- context_engine/cli.py +222 -0
- context_engine/config.py +193 -0
- context_engine/context.py +116 -0
- context_engine/demo.py +98 -0
- context_engine/errors.py +91 -0
- context_engine/evaluation/__init__.py +1 -0
- context_engine/evaluation/commands.py +90 -0
- context_engine/evaluation/corpus.py +247 -0
- context_engine/evaluation/data/freeze.json +26 -0
- context_engine/evaluation/data/ground_truth.json +287 -0
- context_engine/evaluation/data/pin_retention.json +8 -0
- context_engine/evaluation/data/protocol.json +122 -0
- context_engine/evaluation/data/scenario.json +1226 -0
- context_engine/evaluation/data/summary_fixture.json +7 -0
- context_engine/evaluation/execution.py +538 -0
- context_engine/evaluation/fixtures.py +32 -0
- context_engine/evaluation/grading.py +62 -0
- context_engine/evaluation/harness.py +165 -0
- context_engine/evaluation/heldout/__init__.py +260 -0
- context_engine/evaluation/heldout/__main__.py +40 -0
- context_engine/evaluation/heldout/data/ground_truth.json +18 -0
- context_engine/evaluation/heldout/data/manifest.json +9 -0
- context_engine/evaluation/heldout/data/protocol.json +14 -0
- context_engine/evaluation/heldout/data/scenarios.json +18 -0
- context_engine/evaluation/heldout/data/scoring_manifest.json +4 -0
- context_engine/evaluation/heldout/data/scoring_protocol.json +12 -0
- context_engine/evaluation/heldout/scoring.py +176 -0
- context_engine/evaluation/protocol.py +264 -0
- context_engine/evaluation/reporting.py +294 -0
- context_engine/evaluation/results.py +331 -0
- context_engine/evaluation/run_commands.py +146 -0
- context_engine/evaluation/strategies.py +261 -0
- context_engine/inspection.py +266 -0
- context_engine/layers/__init__.py +18 -0
- context_engine/layers/cap.py +99 -0
- context_engine/layers/common.py +162 -0
- context_engine/layers/pin.py +68 -0
- context_engine/layers/retrieve.py +233 -0
- context_engine/layers/summarize.py +202 -0
- context_engine/layers/window.py +29 -0
- context_engine/mcp_server.py +267 -0
- context_engine/memory/__init__.py +5 -0
- context_engine/memory/codec.py +91 -0
- context_engine/memory/demo.py +90 -0
- context_engine/memory/store.py +770 -0
- context_engine/models.py +384 -0
- context_engine/pipeline.py +383 -0
- context_engine/providers/__init__.py +1 -0
- context_engine/providers/client.py +301 -0
- context_engine/providers/commands.py +138 -0
- context_engine/providers/contracts.py +324 -0
- context_engine/providers/credentials.py +66 -0
- context_engine/providers/memory.py +112 -0
- context_engine/providers/store.py +408 -0
- context_engine/providers/transport.py +195 -0
- context_engine/providers/windows_credentials.py +23 -0
- context_engine/py.typed +0 -0
- context_engine/service/__init__.py +1 -0
- context_engine/service/__main__.py +68 -0
- context_engine/service/app.py +404 -0
- context_engine/service/auth.py +132 -0
- context_engine/service/client.py +52 -0
- context_engine/service/control.py +134 -0
- context_engine/service/demo.py +151 -0
- context_engine/service/integrations.py +28 -0
- context_engine/service/schemas.py +59 -0
- context_engine/service/worker.py +59 -0
- context_engine/service/workers.py +156 -0
- context_engine/summaries.py +196 -0
- context_engine/tokens.py +171 -0
- context_engine/work.py +46 -0
- context_engineering_core-0.12.0.dist-info/METADATA +203 -0
- context_engineering_core-0.12.0.dist-info/RECORD +84 -0
- context_engineering_core-0.12.0.dist-info/WHEEL +4 -0
- context_engineering_core-0.12.0.dist-info/entry_points.txt +3 -0
- context_engineering_core-0.12.0.dist-info/licenses/LICENSE +202 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""Evaluator-only retention and strictly TEST_ONLY response validation/scoring."""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
import re
|
|
6
|
+
import unicodedata
|
|
7
|
+
from collections import defaultdict
|
|
8
|
+
|
|
9
|
+
from ...errors import BenchmarkError
|
|
10
|
+
from ...models import canonical_json
|
|
11
|
+
from ..corpus import strict_json
|
|
12
|
+
from . import _root, load, prepare
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def fingerprint(value):
|
|
16
|
+
return hashlib.sha256(canonical_json(value).encode()).hexdigest()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def scoring_freeze():
|
|
20
|
+
root = _root(None)
|
|
21
|
+
raw = root.joinpath("scoring_protocol.json").read_bytes()
|
|
22
|
+
frozen = strict_json(root.joinpath("scoring_manifest.json").read_text())
|
|
23
|
+
if frozen != {"id": "heldout-scoring-v1", "sha256": hashlib.sha256(raw).hexdigest()}:
|
|
24
|
+
raise BenchmarkError("Scoring protocol freeze mismatch")
|
|
25
|
+
return frozen
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def normalize(text):
|
|
29
|
+
return " ".join(unicodedata.normalize("NFKC", text).casefold().split())
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def contains(text, literal):
|
|
33
|
+
return (
|
|
34
|
+
re.search(r"(?<!\w)" + re.escape(normalize(literal)) + r"(?!\w)", normalize(text))
|
|
35
|
+
is not None
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def retained(case, truth, result):
|
|
40
|
+
"""Inspect actual rendered data, not mere presence of original source IDs."""
|
|
41
|
+
if case["category"] == "no_answer":
|
|
42
|
+
return None
|
|
43
|
+
fragments = defaultdict(list)
|
|
44
|
+
for block in result.blocks:
|
|
45
|
+
if not block.content:
|
|
46
|
+
continue
|
|
47
|
+
if block.kind.value == "window":
|
|
48
|
+
for turn in json.loads(block.content):
|
|
49
|
+
for index, message in enumerate(turn["messages"]):
|
|
50
|
+
# Fixture tN contains exactly one message mN; filler IDs never qualify.
|
|
51
|
+
if index == 0 and turn["turn"].startswith("t"):
|
|
52
|
+
fragments["m" + turn["turn"][1:]].append(message["content"])
|
|
53
|
+
elif block.kind.value == "retrieved":
|
|
54
|
+
for chunk in json.loads(block.content):
|
|
55
|
+
fragments[chunk["message"]].append(chunk["text"])
|
|
56
|
+
if case["category"] == "contradictory":
|
|
57
|
+
return all(
|
|
58
|
+
any(normalize(text) in normalize(fragment) for fragment in fragments[f"m{i}"])
|
|
59
|
+
for i, (_, text) in enumerate(case["messages"])
|
|
60
|
+
)
|
|
61
|
+
return all(
|
|
62
|
+
any(contains(fragment, truth["answer"]) for fragment in fragments[source])
|
|
63
|
+
for source in truth["sources"]
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def empty_responses(preparation):
|
|
68
|
+
return {
|
|
69
|
+
"schema_version": 1,
|
|
70
|
+
"provenance": "TEST_ONLY",
|
|
71
|
+
"preparation_hash": fingerprint(preparation),
|
|
72
|
+
"responses": [],
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def score_test(preparation, recorded):
|
|
77
|
+
"""Reject forged/stale plans, duplicate/mismatched answers and all live labels."""
|
|
78
|
+
freeze = scoring_freeze()
|
|
79
|
+
try:
|
|
80
|
+
if preparation != prepare(split=preparation["split"], retention=True):
|
|
81
|
+
raise ValueError("preparation does not reproduce")
|
|
82
|
+
if (
|
|
83
|
+
set(recorded) != {"schema_version", "provenance", "preparation_hash", "responses"}
|
|
84
|
+
or type(recorded["schema_version"]) is not int
|
|
85
|
+
or recorded["schema_version"] != 1
|
|
86
|
+
or recorded["provenance"] != "TEST_ONLY"
|
|
87
|
+
or recorded["preparation_hash"] != fingerprint(preparation)
|
|
88
|
+
or not isinstance(recorded["responses"], list)
|
|
89
|
+
or len(recorded["responses"]) > preparation["planned_slots"]
|
|
90
|
+
):
|
|
91
|
+
raise ValueError("recording envelope")
|
|
92
|
+
|
|
93
|
+
def key(row):
|
|
94
|
+
return row["case"], row["variant"], row["budget"]
|
|
95
|
+
|
|
96
|
+
planned = {key(row): row for row in preparation["rows"]}
|
|
97
|
+
answers = {}
|
|
98
|
+
for row in recorded["responses"]:
|
|
99
|
+
if (
|
|
100
|
+
set(row) != {"case", "variant", "budget", "request_hash", "status", "answer"}
|
|
101
|
+
or type(row["budget"]) is not int
|
|
102
|
+
or key(row) not in planned
|
|
103
|
+
or key(row) in answers
|
|
104
|
+
or planned[key(row)]["status"] != "PREPARED"
|
|
105
|
+
or row["request_hash"] != planned[key(row)]["request_hash"]
|
|
106
|
+
or row["status"] not in ("response", "error")
|
|
107
|
+
or (
|
|
108
|
+
row["status"] == "response"
|
|
109
|
+
and (not isinstance(row["answer"], str) or len(row["answer"]) > 16000)
|
|
110
|
+
)
|
|
111
|
+
or (row["status"] == "error" and row["answer"] is not None)
|
|
112
|
+
):
|
|
113
|
+
raise ValueError("response binding")
|
|
114
|
+
answers[key(row)] = row
|
|
115
|
+
except (KeyError, TypeError, ValueError) as exc:
|
|
116
|
+
raise BenchmarkError("Invalid TEST_ONLY response evidence") from exc
|
|
117
|
+
_, truth, _, _ = load()
|
|
118
|
+
rows = []
|
|
119
|
+
groups = defaultdict(
|
|
120
|
+
lambda: {
|
|
121
|
+
"planned": 0,
|
|
122
|
+
"responded": 0,
|
|
123
|
+
"correct": 0,
|
|
124
|
+
"forbidden": 0,
|
|
125
|
+
"retention_eligible": 0,
|
|
126
|
+
"retained": 0,
|
|
127
|
+
}
|
|
128
|
+
)
|
|
129
|
+
for plan in preparation["rows"]:
|
|
130
|
+
answer = answers.get(key(plan))
|
|
131
|
+
status = (
|
|
132
|
+
"required_non_fit"
|
|
133
|
+
if plan["status"] != "PREPARED"
|
|
134
|
+
else answer["status"]
|
|
135
|
+
if answer
|
|
136
|
+
else "missing"
|
|
137
|
+
)
|
|
138
|
+
expected = truth[plan["case"]]
|
|
139
|
+
responded = status == "response"
|
|
140
|
+
correct = responded and normalize(answer["answer"]) == normalize(expected["answer"])
|
|
141
|
+
forbidden = responded and any(
|
|
142
|
+
contains(answer["answer"], word) for word in expected["forbidden"]
|
|
143
|
+
)
|
|
144
|
+
row = {k: plan[k] for k in ("case", "split", "variant", "budget", "category", "retained")}
|
|
145
|
+
row.update(status=status, correct=correct, forbidden=forbidden)
|
|
146
|
+
rows.append(row)
|
|
147
|
+
grouping = tuple(plan[k] for k in ("split", "variant", "budget", "category"))
|
|
148
|
+
group = groups[grouping]
|
|
149
|
+
group["planned"] += 1
|
|
150
|
+
group["responded"] += responded
|
|
151
|
+
group["correct"] += correct
|
|
152
|
+
group["forbidden"] += forbidden
|
|
153
|
+
group["retention_eligible"] += plan["retained"] is not None
|
|
154
|
+
group["retained"] += plan["retained"] is True
|
|
155
|
+
return {
|
|
156
|
+
"schema_version": 1,
|
|
157
|
+
"status": "TEST_ONLY",
|
|
158
|
+
"kind": "scorer_verification",
|
|
159
|
+
"provider_calls": 0,
|
|
160
|
+
"answer_quality": "NOT_EVALUATED",
|
|
161
|
+
"provider_calibration": "NOT_EVALUATED",
|
|
162
|
+
"provider_cost": "NOT_EVALUATED",
|
|
163
|
+
"scoring_freeze": freeze,
|
|
164
|
+
"preparation_hash": fingerprint(preparation),
|
|
165
|
+
"recording_hash": fingerprint(recorded),
|
|
166
|
+
"planned_slots": len(rows),
|
|
167
|
+
"synthetic_correct": sum(r["correct"] for r in rows),
|
|
168
|
+
"synthetic_responded": sum(r["status"] == "response" for r in rows),
|
|
169
|
+
"missing": sum(r["status"] == "missing" for r in rows),
|
|
170
|
+
"errors": sum(r["status"] == "error" for r in rows),
|
|
171
|
+
"groups": [
|
|
172
|
+
dict(zip(("split", "variant", "budget", "category"), k, strict=True), **v)
|
|
173
|
+
for k, v in sorted(groups.items())
|
|
174
|
+
],
|
|
175
|
+
"rows": rows,
|
|
176
|
+
}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"""Versioned protocol, code/resource freeze and deterministic planned probe identities."""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import platform
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from importlib import metadata, resources
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from .. import __version__
|
|
10
|
+
from ..config import BudgetConfig, CapConfig, RetrievalConfig, TokenizerConfig
|
|
11
|
+
from ..errors import BenchmarkError, ContextEngineError
|
|
12
|
+
from ..models import canonical_json
|
|
13
|
+
from ..pipeline import AssemblyOptions
|
|
14
|
+
from .corpus import Bundle, check_fixture, check_leakage, digest, read_resource, strict_json
|
|
15
|
+
|
|
16
|
+
VARIANTS = (
|
|
17
|
+
("A0", False, False, False, False, False),
|
|
18
|
+
("A1", False, False, False, False, True),
|
|
19
|
+
("A2", True, False, False, False, True),
|
|
20
|
+
("A3", True, True, False, False, True),
|
|
21
|
+
("A4", True, True, True, False, True),
|
|
22
|
+
("A5", True, True, True, True, True),
|
|
23
|
+
)
|
|
24
|
+
DEPENDENCIES = (
|
|
25
|
+
"tiktoken",
|
|
26
|
+
"rank-bm25",
|
|
27
|
+
"numpy",
|
|
28
|
+
"regex",
|
|
29
|
+
"requests",
|
|
30
|
+
"certifi",
|
|
31
|
+
"charset-normalizer",
|
|
32
|
+
"idna",
|
|
33
|
+
"urllib3",
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def code_hash() -> str:
|
|
38
|
+
root = resources.files("context_engine")
|
|
39
|
+
entries = []
|
|
40
|
+
|
|
41
|
+
def visit(directory, prefix=""):
|
|
42
|
+
for item in sorted(directory.iterdir(), key=lambda p: p.name):
|
|
43
|
+
name = prefix + item.name
|
|
44
|
+
if item.is_dir() and item.name != "__pycache__":
|
|
45
|
+
visit(item, name + "/")
|
|
46
|
+
elif item.name.endswith(".py"):
|
|
47
|
+
entries.append((name, hashlib.sha256(item.read_bytes()).hexdigest()))
|
|
48
|
+
|
|
49
|
+
visit(root)
|
|
50
|
+
return digest(entries)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def runtime_identity() -> dict:
|
|
54
|
+
return {
|
|
55
|
+
"package_version": __version__,
|
|
56
|
+
"python": platform.python_version(),
|
|
57
|
+
"dependencies": {name: metadata.version(name) for name in DEPENDENCIES},
|
|
58
|
+
"code_hash": code_hash(),
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def freeze_record(bundle: Bundle) -> dict:
|
|
63
|
+
"""Authoring helper, never called implicitly to bless drift during validation."""
|
|
64
|
+
return {"schema_version": 1, "fixture_hashes": bundle.hashes, "runtime": runtime_identity()}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def load_freeze(directory: Path | None = None) -> dict:
|
|
68
|
+
return strict_json(read_resource("freeze.json", directory))
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def check_protocol(bundle: Bundle) -> None:
|
|
72
|
+
try:
|
|
73
|
+
p = bundle.get("protocol")
|
|
74
|
+
expected = [
|
|
75
|
+
dict(zip(("id", "cap", "pins", "retrieve", "summarize", "window"), row, strict=True))
|
|
76
|
+
for row in VARIANTS
|
|
77
|
+
]
|
|
78
|
+
if canonical_json(p["variants"]) != canonical_json(expected):
|
|
79
|
+
raise BenchmarkError("The six declared variants changed")
|
|
80
|
+
if p["approval"] not in ("owner_pending", "owner_approved"):
|
|
81
|
+
raise BenchmarkError("Unknown protocol approval state")
|
|
82
|
+
if (
|
|
83
|
+
p["grading"]
|
|
84
|
+
!= {
|
|
85
|
+
"normalization": "nfkc-casefold-whitespace-v1",
|
|
86
|
+
"answer_mode": "exact-alias-or-json-answer",
|
|
87
|
+
"presence_mode": "identifier-boundary-v1",
|
|
88
|
+
"abstentions": ["UNKNOWN"],
|
|
89
|
+
}
|
|
90
|
+
or p["bm25_variant"] != "BM25Plus-delta1"
|
|
91
|
+
or p["corpus_limits"] != {"turns": 100, "facts": 31}
|
|
92
|
+
):
|
|
93
|
+
raise BenchmarkError("Declared grading or corpus policy differs from implementation")
|
|
94
|
+
if p["summary_coverage"] != "frozen-content-bound-to-omitted-history":
|
|
95
|
+
raise BenchmarkError("Unsupported summary coverage policy")
|
|
96
|
+
if p["budgets"] != [900, 3000] or p["zone_boundaries"] != [33, 66, 100]:
|
|
97
|
+
raise BenchmarkError("Unsupported version-1 evaluation protocol")
|
|
98
|
+
if len(set(p["models"])) != len(p["models"]) or not p["models"]:
|
|
99
|
+
raise BenchmarkError("Invalid model plan")
|
|
100
|
+
if any(not isinstance(m, str) or not m.strip() for m in p["models"]):
|
|
101
|
+
raise BenchmarkError("Invalid model identity")
|
|
102
|
+
if p["generation"] != {
|
|
103
|
+
"temperature": 0,
|
|
104
|
+
"reasoning_effort": "low",
|
|
105
|
+
"max_completion_tokens": 256,
|
|
106
|
+
}:
|
|
107
|
+
raise BenchmarkError("Unsupported version-1 generation settings")
|
|
108
|
+
if p["targets"] != {
|
|
109
|
+
"input_budget": 900,
|
|
110
|
+
"variant": "A5",
|
|
111
|
+
"fit": 31,
|
|
112
|
+
"retained": 28,
|
|
113
|
+
"correct": 27,
|
|
114
|
+
"median_input_reduction": 0.9,
|
|
115
|
+
"max_truncated": 0,
|
|
116
|
+
}:
|
|
117
|
+
raise BenchmarkError("Version-1 pre-registered targets changed")
|
|
118
|
+
for cap in p["budgets"]:
|
|
119
|
+
BudgetConfig(
|
|
120
|
+
input_cap=cap,
|
|
121
|
+
completion_reservation=p["generation"]["max_completion_tokens"],
|
|
122
|
+
**p["budget"],
|
|
123
|
+
)
|
|
124
|
+
CapConfig(**p["cap"])
|
|
125
|
+
RetrievalConfig(**p["retrieval"])
|
|
126
|
+
TokenizerConfig(**p["tokenizer"])
|
|
127
|
+
except BenchmarkError:
|
|
128
|
+
raise
|
|
129
|
+
except (KeyError, TypeError, ValueError, ContextEngineError):
|
|
130
|
+
raise BenchmarkError("Malformed evaluation protocol") from None
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@dataclass(frozen=True)
|
|
134
|
+
class Gate:
|
|
135
|
+
gate: str
|
|
136
|
+
status: str
|
|
137
|
+
reason: str
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def preflight(bundle: Bundle, frozen: dict) -> tuple[Gate, ...]:
|
|
141
|
+
gates = []
|
|
142
|
+
for identifier, operation in (("V1", check_fixture), ("V2", check_leakage)):
|
|
143
|
+
try:
|
|
144
|
+
operation(bundle)
|
|
145
|
+
gates.append(Gate(identifier, "PASS", "checked"))
|
|
146
|
+
except (ContextEngineError, KeyError, TypeError, ValueError, StopIteration):
|
|
147
|
+
gates.append(
|
|
148
|
+
Gate(
|
|
149
|
+
identifier,
|
|
150
|
+
"FAIL",
|
|
151
|
+
"fixture_integrity" if identifier == "V1" else "answer_leakage",
|
|
152
|
+
)
|
|
153
|
+
)
|
|
154
|
+
try:
|
|
155
|
+
check_protocol(bundle)
|
|
156
|
+
if frozen != freeze_record(bundle):
|
|
157
|
+
raise BenchmarkError("Frozen resources or execution identity changed")
|
|
158
|
+
gates.append(Gate("V5", "PASS", "frozen_identity_matches"))
|
|
159
|
+
except (ContextEngineError, KeyError, TypeError, ValueError):
|
|
160
|
+
gates.append(Gate("V5", "FAIL", "configuration_or_code_drift"))
|
|
161
|
+
return tuple(gates)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def require_preflight(bundle: Bundle, frozen: dict) -> None:
|
|
165
|
+
failed = [g.gate for g in preflight(bundle, frozen) if g.status == "FAIL"]
|
|
166
|
+
if failed:
|
|
167
|
+
raise BenchmarkError("Benchmark preflight INVALID", gates=",".join(failed))
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@dataclass(frozen=True)
|
|
171
|
+
class Manifest:
|
|
172
|
+
payload_json: str
|
|
173
|
+
|
|
174
|
+
def __post_init__(self):
|
|
175
|
+
payload = strict_json(self.payload_json)
|
|
176
|
+
object.__setattr__(self, "payload_json", canonical_json(payload))
|
|
177
|
+
|
|
178
|
+
@property
|
|
179
|
+
def data(self):
|
|
180
|
+
return strict_json(self.payload_json)
|
|
181
|
+
|
|
182
|
+
@property
|
|
183
|
+
def run_id(self):
|
|
184
|
+
return digest(self.data)
|
|
185
|
+
|
|
186
|
+
@property
|
|
187
|
+
def slots(self) -> tuple[dict, ...]:
|
|
188
|
+
data = self.data
|
|
189
|
+
return tuple(
|
|
190
|
+
{
|
|
191
|
+
"probe_id": digest([self.run_id, model, budget, variant, fact]),
|
|
192
|
+
"model": model,
|
|
193
|
+
"budget": budget,
|
|
194
|
+
"variant": variant,
|
|
195
|
+
"fact_id": fact,
|
|
196
|
+
}
|
|
197
|
+
for model in data["models"]
|
|
198
|
+
for budget in data["budgets"]
|
|
199
|
+
for variant in data["variants"]
|
|
200
|
+
for fact in data["facts"]
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
def slot(self, identifier: str) -> dict:
|
|
204
|
+
for slot in self.slots:
|
|
205
|
+
if slot["probe_id"] == identifier:
|
|
206
|
+
return slot
|
|
207
|
+
raise BenchmarkError("Unknown planned probe ID")
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def make_manifest(
|
|
211
|
+
bundle: Bundle, frozen: dict, *, facts=None, models=None, budgets=None, variants=None
|
|
212
|
+
) -> Manifest:
|
|
213
|
+
require_preflight(bundle, frozen)
|
|
214
|
+
p = bundle.get("protocol")
|
|
215
|
+
available = {
|
|
216
|
+
"facts": [f["fact_id"] for f in bundle.facts],
|
|
217
|
+
"models": p["models"],
|
|
218
|
+
"budgets": p["budgets"],
|
|
219
|
+
"variants": [v["id"] for v in p["variants"]],
|
|
220
|
+
}
|
|
221
|
+
selected = {}
|
|
222
|
+
for name, value in (
|
|
223
|
+
("facts", facts),
|
|
224
|
+
("models", models),
|
|
225
|
+
("budgets", budgets),
|
|
226
|
+
("variants", variants),
|
|
227
|
+
):
|
|
228
|
+
value = available[name] if value is None else value
|
|
229
|
+
expected_type = int if name == "budgets" else str
|
|
230
|
+
if (
|
|
231
|
+
not isinstance(value, (list, tuple))
|
|
232
|
+
or not value
|
|
233
|
+
or any(type(item) is not expected_type for item in value)
|
|
234
|
+
):
|
|
235
|
+
raise BenchmarkError("Invalid probe plan selection")
|
|
236
|
+
if len(set(value)) != len(value):
|
|
237
|
+
raise BenchmarkError("Invalid probe plan selection")
|
|
238
|
+
if any(item not in available[name] for item in value):
|
|
239
|
+
raise BenchmarkError("Probe selection is outside the frozen protocol")
|
|
240
|
+
selected[name] = list(value)
|
|
241
|
+
return Manifest(
|
|
242
|
+
canonical_json({"schema_version": 1, "freeze": frozen, "protocol": p, **selected})
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def check_manifest(bundle: Bundle, frozen: dict, manifest: Manifest) -> None:
|
|
247
|
+
try:
|
|
248
|
+
data = manifest.data
|
|
249
|
+
rebuilt = make_manifest(
|
|
250
|
+
bundle, frozen, **{key: data[key] for key in ("facts", "models", "budgets", "variants")}
|
|
251
|
+
)
|
|
252
|
+
if rebuilt != manifest:
|
|
253
|
+
raise BenchmarkError("Run manifest drift")
|
|
254
|
+
except (KeyError, TypeError):
|
|
255
|
+
raise BenchmarkError("Malformed run manifest") from None
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def variant_options(bundle: Bundle, identifier: str) -> AssemblyOptions:
|
|
259
|
+
for row in bundle.get("protocol")["variants"]:
|
|
260
|
+
if row["id"] == identifier and row["window"]:
|
|
261
|
+
return AssemblyOptions(
|
|
262
|
+
**{key: row[key] for key in ("cap", "pins", "retrieve", "summarize")}
|
|
263
|
+
)
|
|
264
|
+
raise BenchmarkError("Unknown layered variant")
|