techtree 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.
- techtree/__init__.py +35 -0
- techtree/__main__.py +14 -0
- techtree/canonical.py +239 -0
- techtree/catalog/__init__.py +25 -0
- techtree/catalog/repository.py +400 -0
- techtree/catalog/service.py +419 -0
- techtree/cli/__init__.py +1 -0
- techtree/cli/app.py +416 -0
- techtree/cli/commands/__init__.py +1 -0
- techtree/cli/commands/climb.py +1223 -0
- techtree/cli/commands/doctor.py +147 -0
- techtree/cli/commands/engine.py +207 -0
- techtree/cli/commands/proof.py +556 -0
- techtree/cli/commands/publish.py +447 -0
- techtree/cli/commands/release.py +303 -0
- techtree/cli/commands/run.py +1067 -0
- techtree/cli/commands/setup.py +181 -0
- techtree/cli/commands/skill.py +221 -0
- techtree/cli/commands/uplift.py +698 -0
- techtree/cli/commands/withdraw.py +212 -0
- techtree/cli/confirm.py +47 -0
- techtree/cli/context.py +96 -0
- techtree/cli/invoke.py +220 -0
- techtree/cli/output.py +280 -0
- techtree/constants.py +138 -0
- techtree/crypto.py +128 -0
- techtree/doctor/__init__.py +1 -0
- techtree/doctor/checks.py +675 -0
- techtree/doctor/execution_checks.py +435 -0
- techtree/doctor/service.py +326 -0
- techtree/drafts/__init__.py +32 -0
- techtree/drafts/source.py +146 -0
- techtree/drafts/store.py +992 -0
- techtree/engines/__init__.py +1 -0
- techtree/engines/bundle.py +251 -0
- techtree/engines/installer.py +679 -0
- techtree/engines/registry.py +235 -0
- techtree/engines/runner.py +170 -0
- techtree/errors.py +262 -0
- techtree/fs.py +234 -0
- techtree/harness.py +108 -0
- techtree/identity/__init__.py +41 -0
- techtree/identity/models.py +113 -0
- techtree/identity/service.py +199 -0
- techtree/identity/store.py +263 -0
- techtree/ids.py +85 -0
- techtree/manifests/__init__.py +39 -0
- techtree/manifests/builder.py +433 -0
- techtree/manifests/compare.py +376 -0
- techtree/models/__init__.py +282 -0
- techtree/models/base.py +201 -0
- techtree/models/campaign.py +484 -0
- techtree/models/catalog.py +227 -0
- techtree/models/cli.py +151 -0
- techtree/models/climb.py +254 -0
- techtree/models/data_policy.py +130 -0
- techtree/models/engine.py +156 -0
- techtree/models/episode_receipt.py +130 -0
- techtree/models/evaluation_backend.py +113 -0
- techtree/models/experiment.py +154 -0
- techtree/models/run.py +214 -0
- techtree/models/skill.py +156 -0
- techtree/models/uplift_report.py +158 -0
- techtree/models/validation.py +299 -0
- techtree/paths.py +116 -0
- techtree/presentation/__init__.py +31 -0
- techtree/presentation/build.py +1242 -0
- techtree/presentation/compact.py +246 -0
- techtree/presentation/evidence.py +169 -0
- techtree/presentation/models.py +358 -0
- techtree/presentation/rich.py +312 -0
- techtree/presentation/sanitize.py +156 -0
- techtree/publication/__init__.py +44 -0
- techtree/publication/address.py +180 -0
- techtree/publication/coordinates.py +26 -0
- techtree/publication/journal.py +212 -0
- techtree/publication/keccak.py +183 -0
- techtree/publication/models.py +209 -0
- techtree/publication/offer.py +35 -0
- techtree/publication/service.py +618 -0
- techtree/publication/transport.py +296 -0
- techtree/publication/verify.py +242 -0
- techtree/publication/withdraw.py +156 -0
- techtree/py.typed +0 -0
- techtree/receipts/__init__.py +52 -0
- techtree/receipts/bundle.py +578 -0
- techtree/receipts/compare.py +1065 -0
- techtree/receipts/episode.py +672 -0
- techtree/receipts/execution.py +630 -0
- techtree/receipts/observed.py +474 -0
- techtree/receipts/set.py +336 -0
- techtree/receipts/uplift.py +655 -0
- techtree/receipts/verify.py +1055 -0
- techtree/release/__init__.py +9 -0
- techtree/release/bootstrap.py +509 -0
- techtree/release/checks.py +376 -0
- techtree/release/document.py +125 -0
- techtree/release/generate.py +221 -0
- techtree/release/models.py +293 -0
- techtree/release/provenance.py +109 -0
- techtree/resources/catalog/campaigns/hello-world-climb.json +1 -0
- techtree/resources/catalog/catalog.json +32 -0
- techtree/resources/catalog/climbs/hello-world-climb.json +1 -0
- techtree/resources/catalog/data-policies/hello-world-climb.json +1 -0
- techtree/resources/catalog/taskset-validations/hello-world-climb.json +1 -0
- techtree/resources/catalog/validation-evidence/hello-world-climb.json +1 -0
- techtree/resources/engines/default/engine.json +20 -0
- techtree/resources/engines/default/packages/procedure-transfer-v1/procedure_transfer_v1/__init__.py +7 -0
- techtree/resources/engines/default/packages/procedure-transfer-v1/procedure_transfer_v1/algorithm.py +136 -0
- techtree/resources/engines/default/packages/procedure-transfer-v1/procedure_transfer_v1/dataset.py +156 -0
- techtree/resources/engines/default/packages/procedure-transfer-v1/procedure_transfer_v1/env.py +48 -0
- techtree/resources/engines/default/packages/procedure-transfer-v1/procedure_transfer_v1/taskset.py +163 -0
- techtree/resources/engines/default/packages/procedure-transfer-v1/pyproject.toml +13 -0
- techtree/resources/engines/default/pyproject.toml +23 -0
- techtree/resources/engines/default/tools/inspect_taskset.py +124 -0
- techtree/resources/engines/default/tools/normalize_eval_output.py +470 -0
- techtree/resources/engines/default/tools/normalize_validation.py +222 -0
- techtree/resources/engines/default/uv.lock +1758 -0
- techtree/resources/harness/hermes-agent-0.19.0.json +69 -0
- techtree/resources/release/build-provenance.json +4 -0
- techtree/resources/release/release-core.json +24 -0
- techtree/runs/__init__.py +31 -0
- techtree/runs/artifacts.py +750 -0
- techtree/runs/child_registry.py +228 -0
- techtree/runs/events.py +478 -0
- techtree/runs/executor.py +140 -0
- techtree/runs/fake.py +741 -0
- techtree/runs/launcher.py +253 -0
- techtree/runs/machine.py +489 -0
- techtree/runs/real.py +789 -0
- techtree/runs/service.py +616 -0
- techtree/runs/store.py +555 -0
- techtree/runs/validation.py +259 -0
- techtree/runs/variants.py +684 -0
- techtree/settings.py +143 -0
- techtree/skills/__init__.py +14 -0
- techtree/skills/archive.py +282 -0
- techtree/skills/policy.py +62 -0
- techtree/skills/scanner.py +394 -0
- techtree/skills/service.py +752 -0
- techtree/skills/starter.py +434 -0
- techtree/tasksets/__init__.py +1 -0
- techtree/tasksets/membership.py +269 -0
- techtree/tasksets/provider.py +207 -0
- techtree/tasksets/resolver.py +311 -0
- techtree/tasksets/service.py +484 -0
- techtree/tasksets/verifiers_cli.py +538 -0
- techtree/uplift/__init__.py +20 -0
- techtree/uplift/context.py +544 -0
- techtree/uplift/derive.py +203 -0
- techtree/uplift/public_tasks.py +151 -0
- techtree/uplift/service.py +719 -0
- techtree/uplift/source.py +160 -0
- techtree/verifiers/__init__.py +31 -0
- techtree/verifiers/budget.py +219 -0
- techtree/verifiers/child.py +633 -0
- techtree/verifiers/compiler.py +432 -0
- techtree/verifiers/config.py +365 -0
- techtree/verifiers/credentials.py +321 -0
- techtree/verifiers/image.py +126 -0
- techtree/verifiers/models.py +527 -0
- techtree/verifiers/outputs.py +368 -0
- techtree/verifiers/progress.py +192 -0
- techtree/verifiers/supervisor.py +341 -0
- techtree/verifiers/verify.py +782 -0
- techtree/version.py +39 -0
- techtree/worker/__init__.py +18 -0
- techtree/worker/execute.py +487 -0
- techtree/worker/main.py +57 -0
- techtree-0.1.0.dist-info/METADATA +344 -0
- techtree-0.1.0.dist-info/RECORD +174 -0
- techtree-0.1.0.dist-info/WHEEL +4 -0
- techtree-0.1.0.dist-info/entry_points.txt +3 -0
- techtree-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
"""Where a run's taskset validation comes from. Spec PR8 §8.4.
|
|
2
|
+
|
|
3
|
+
An executor has to know that the taskset it is about to score is the taskset
|
|
4
|
+
the Campaign committed to, and that somebody mechanically validated it. Who
|
|
5
|
+
answers that question changes over the life of the product: today it is the
|
|
6
|
+
publisher's own receipt, snapshotted into the draft and re-checked offline;
|
|
7
|
+
later it is a local Verifiers run that recomputes the same receipt on the
|
|
8
|
+
participant's machine.
|
|
9
|
+
|
|
10
|
+
This module is the seam between the two. The executor depends on
|
|
11
|
+
:class:`TasksetValidationProvider` and nothing else, so the later change
|
|
12
|
+
replaces one object at wiring time and touches no run lifecycle code.
|
|
13
|
+
|
|
14
|
+
:class:`PublisherFixtureValidationProvider` is the development answer. It is
|
|
15
|
+
careful about what it claims: it verifies the snapshotted publisher commitment
|
|
16
|
+
and says so — ``source = publisher_fixture``, ``execution_record = None`` —
|
|
17
|
+
rather than presenting a re-read commitment as a validation this machine
|
|
18
|
+
performed. Nothing downstream is entitled to treat it as one, which is
|
|
19
|
+
consistent with the fake executor's reports being development-only throughout.
|
|
20
|
+
|
|
21
|
+
The lock the receipt was issued under is not shipped in the public catalog;
|
|
22
|
+
only its digest is. The provider therefore *derives* the lock from what the
|
|
23
|
+
draft does carry — the Campaign's taskset reference and committed membership,
|
|
24
|
+
and the receipt's engine digest — and requires the derived lock to digest to
|
|
25
|
+
exactly the value the receipt names. A derivation that does not reproduce the
|
|
26
|
+
committed digest is a failure, never a warning.
|
|
27
|
+
|
|
28
|
+
:class:`UnconfiguredTasksetValidationProvider` is the fail-closed default for
|
|
29
|
+
any wiring that has no provider yet. It answers every request with
|
|
30
|
+
``taskset_validation_provider_unavailable``.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
from dataclasses import dataclass
|
|
36
|
+
from enum import StrEnum
|
|
37
|
+
from typing import Final, NoReturn, Protocol
|
|
38
|
+
|
|
39
|
+
from pydantic import ValidationError as PydanticValidationError
|
|
40
|
+
|
|
41
|
+
from techtree.canonical import digest_object, to_json_value
|
|
42
|
+
from techtree.constants import TASKSET_LOCK_SCHEMA_VERSION
|
|
43
|
+
from techtree.errors import PrerequisiteError, VerificationError
|
|
44
|
+
from techtree.models.base import JsonValue
|
|
45
|
+
from techtree.models.validation import (
|
|
46
|
+
TasksetLock,
|
|
47
|
+
TasksetValidationReceipt,
|
|
48
|
+
ValidationExecutionRecord,
|
|
49
|
+
)
|
|
50
|
+
from techtree.runs.artifacts import (
|
|
51
|
+
VALIDATION_MARKER_SCHEMA_VERSION,
|
|
52
|
+
RunInputBundle,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
__all__ = [
|
|
56
|
+
"TASKSET_VALIDATION_INVALID",
|
|
57
|
+
"TASKSET_VALIDATION_PROVIDER_UNAVAILABLE",
|
|
58
|
+
"PublisherFixtureValidationProvider",
|
|
59
|
+
"TasksetValidationOutcome",
|
|
60
|
+
"TasksetValidationProvider",
|
|
61
|
+
"TasksetValidationSource",
|
|
62
|
+
"UnconfiguredTasksetValidationProvider",
|
|
63
|
+
"derive_taskset_lock",
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
#: Stable error codes. Spec PR8 §8.16.
|
|
67
|
+
TASKSET_VALIDATION_INVALID: Final = "taskset_validation_invalid"
|
|
68
|
+
TASKSET_VALIDATION_PROVIDER_UNAVAILABLE: Final = (
|
|
69
|
+
"taskset_validation_provider_unavailable"
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class TasksetValidationSource(StrEnum):
|
|
74
|
+
"""Where a validation outcome came from."""
|
|
75
|
+
|
|
76
|
+
PUBLISHER_FIXTURE = "publisher_fixture"
|
|
77
|
+
LOCAL_VERIFIERS = "local_verifiers"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@dataclass(frozen=True)
|
|
81
|
+
class TasksetValidationOutcome:
|
|
82
|
+
"""The validation artifacts an executor needs, and their provenance."""
|
|
83
|
+
|
|
84
|
+
lock: TasksetLock
|
|
85
|
+
receipt: TasksetValidationReceipt
|
|
86
|
+
execution_record: ValidationExecutionRecord | None
|
|
87
|
+
source: TasksetValidationSource
|
|
88
|
+
|
|
89
|
+
def marker_document(self) -> dict[str, JsonValue]:
|
|
90
|
+
"""Return the JSON projection persisted beside the run.
|
|
91
|
+
|
|
92
|
+
The source is carried explicitly so that a reader of a run directory
|
|
93
|
+
can tell a re-checked publisher commitment from a locally executed
|
|
94
|
+
validation without inferring it from which fields are absent.
|
|
95
|
+
"""
|
|
96
|
+
return {
|
|
97
|
+
"schema_version": VALIDATION_MARKER_SCHEMA_VERSION,
|
|
98
|
+
"source": self.source.value,
|
|
99
|
+
"lock": to_json_value(self.lock),
|
|
100
|
+
"lock_digest": digest_object(self.lock),
|
|
101
|
+
"receipt": to_json_value(self.receipt),
|
|
102
|
+
"receipt_digest": digest_object(self.receipt),
|
|
103
|
+
"execution_record": (
|
|
104
|
+
None
|
|
105
|
+
if self.execution_record is None
|
|
106
|
+
else to_json_value(self.execution_record)
|
|
107
|
+
),
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class TasksetValidationProvider(Protocol):
|
|
112
|
+
"""Answers what a run may assume about its taskset's validation."""
|
|
113
|
+
|
|
114
|
+
def validate(
|
|
115
|
+
self,
|
|
116
|
+
*,
|
|
117
|
+
run_id: str,
|
|
118
|
+
inputs: RunInputBundle,
|
|
119
|
+
) -> TasksetValidationOutcome:
|
|
120
|
+
"""Return the validation artifacts required by the executor."""
|
|
121
|
+
...
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class PublisherFixtureValidationProvider:
|
|
125
|
+
"""Re-checks the snapshotted publisher commitment. Development only.
|
|
126
|
+
|
|
127
|
+
It does not claim to have rerun validation locally, and it never
|
|
128
|
+
fabricates a :class:`~techtree.models.validation.ValidationExecutionRecord`
|
|
129
|
+
for work that did not happen.
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
def validate(
|
|
133
|
+
self,
|
|
134
|
+
*,
|
|
135
|
+
run_id: str,
|
|
136
|
+
inputs: RunInputBundle,
|
|
137
|
+
) -> TasksetValidationOutcome:
|
|
138
|
+
"""Verify the publisher's receipt and return it as this run's outcome."""
|
|
139
|
+
campaign = inputs.campaign
|
|
140
|
+
source = inputs.source
|
|
141
|
+
receipt = source.publisher_validation
|
|
142
|
+
|
|
143
|
+
_require(
|
|
144
|
+
source.publisher_validation_digest
|
|
145
|
+
== campaign.taskset.validation_receipt_digest,
|
|
146
|
+
"the publisher validation receipt this run owns is not the one the "
|
|
147
|
+
"Campaign commits to",
|
|
148
|
+
run_id,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
reference = receipt.normalized_evidence
|
|
152
|
+
_require(
|
|
153
|
+
reference is not None
|
|
154
|
+
and reference.digest == digest_object(inputs.validation_evidence),
|
|
155
|
+
"the normalized evidence this run owns is not what the publisher's "
|
|
156
|
+
"receipt was issued from",
|
|
157
|
+
run_id,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
_require(
|
|
161
|
+
receipt.status == "valid",
|
|
162
|
+
f"the publisher's validation receipt reports {receipt.status}, so "
|
|
163
|
+
"this taskset is not fit to be scored",
|
|
164
|
+
run_id,
|
|
165
|
+
status=receipt.status,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
lock = derive_taskset_lock(run_id, inputs)
|
|
169
|
+
_require(
|
|
170
|
+
digest_object(lock) == receipt.taskset_lock_digest,
|
|
171
|
+
"the taskset lock derived from this Campaign is not the lock the "
|
|
172
|
+
"publisher's receipt was issued under",
|
|
173
|
+
run_id,
|
|
174
|
+
)
|
|
175
|
+
_require(
|
|
176
|
+
lock.ordered_task_hashes
|
|
177
|
+
== list(campaign.taskset.membership.ordered_task_hashes)
|
|
178
|
+
and lock.membership_digest == campaign.taskset.membership.membership_digest
|
|
179
|
+
and lock.task_count == campaign.taskset.selection.num_tasks,
|
|
180
|
+
"the locked task membership is not the membership the Campaign commits to",
|
|
181
|
+
run_id,
|
|
182
|
+
)
|
|
183
|
+
_require(
|
|
184
|
+
inputs.validation_evidence.taskset_lock_digest
|
|
185
|
+
== receipt.taskset_lock_digest,
|
|
186
|
+
"the normalized evidence was produced under a different taskset "
|
|
187
|
+
"lock than the receipt",
|
|
188
|
+
run_id,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
return TasksetValidationOutcome(
|
|
192
|
+
lock=lock,
|
|
193
|
+
receipt=receipt,
|
|
194
|
+
execution_record=None,
|
|
195
|
+
source=TasksetValidationSource.PUBLISHER_FIXTURE,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class UnconfiguredTasksetValidationProvider:
|
|
200
|
+
"""The fail-closed provider for a build with no validation source wired."""
|
|
201
|
+
|
|
202
|
+
def validate(
|
|
203
|
+
self,
|
|
204
|
+
*,
|
|
205
|
+
run_id: str,
|
|
206
|
+
inputs: RunInputBundle,
|
|
207
|
+
) -> NoReturn:
|
|
208
|
+
"""Refuse to let a run proceed without a validation source."""
|
|
209
|
+
raise PrerequisiteError(
|
|
210
|
+
"this build has no taskset validation source configured, so no run "
|
|
211
|
+
"may claim its taskset was validated",
|
|
212
|
+
code=TASKSET_VALIDATION_PROVIDER_UNAVAILABLE,
|
|
213
|
+
details={"run_id": run_id},
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def derive_taskset_lock(run_id: str, inputs: RunInputBundle) -> TasksetLock:
|
|
218
|
+
"""Rebuild the lock the publisher's receipt was issued under.
|
|
219
|
+
|
|
220
|
+
Every field comes from something the run already owns and has verified:
|
|
221
|
+
the taskset reference and committed membership from the Campaign, and the
|
|
222
|
+
engine digest from the receipt. The caller checks the result against the
|
|
223
|
+
digest the receipt names, which is what makes the derivation a proof
|
|
224
|
+
rather than a guess.
|
|
225
|
+
"""
|
|
226
|
+
campaign = inputs.campaign
|
|
227
|
+
receipt = inputs.source.publisher_validation
|
|
228
|
+
membership = campaign.taskset.membership
|
|
229
|
+
|
|
230
|
+
try:
|
|
231
|
+
return TasksetLock(
|
|
232
|
+
schema_version=TASKSET_LOCK_SCHEMA_VERSION,
|
|
233
|
+
taskset_ref=campaign.taskset.ref,
|
|
234
|
+
engine_digest=receipt.engine_digest,
|
|
235
|
+
resolved_package_digest=campaign.taskset.ref.package.digest,
|
|
236
|
+
ordered_task_hashes=list(membership.ordered_task_hashes),
|
|
237
|
+
membership_digest=membership.membership_digest,
|
|
238
|
+
task_count=len(membership.ordered_task_hashes),
|
|
239
|
+
)
|
|
240
|
+
except PydanticValidationError as error:
|
|
241
|
+
raise VerificationError(
|
|
242
|
+
"no taskset lock can be derived from this Campaign: "
|
|
243
|
+
f"{error.errors()[0]['msg']}",
|
|
244
|
+
code=TASKSET_VALIDATION_INVALID,
|
|
245
|
+
details={"run_id": run_id},
|
|
246
|
+
) from error
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _require(condition: bool, message: str, run_id: str, **details: str) -> None:
|
|
250
|
+
"""Raise a typed validation failure unless the condition holds."""
|
|
251
|
+
if condition:
|
|
252
|
+
return
|
|
253
|
+
reported: dict[str, JsonValue] = {"run_id": run_id}
|
|
254
|
+
reported.update(details)
|
|
255
|
+
raise VerificationError(
|
|
256
|
+
message,
|
|
257
|
+
code=TASKSET_VALIDATION_INVALID,
|
|
258
|
+
details=reported,
|
|
259
|
+
)
|