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,221 @@
|
|
|
1
|
+
"""Binding one release together. Spec sections 9.3.1, 9.3.3 and 9.4.
|
|
2
|
+
|
|
3
|
+
Generation is the step that turns "a source tree and a handful of founder
|
|
4
|
+
decisions" into one document every other repository copies. It runs *before*
|
|
5
|
+
the wheel is built and before the plugin is tagged, so nothing it reads may
|
|
6
|
+
depend on either — that ordering is the whole reason the cross-repository
|
|
7
|
+
binding has no cycle in it (spec section 9.3.3).
|
|
8
|
+
|
|
9
|
+
Two kinds of value meet here and they are kept strictly apart.
|
|
10
|
+
|
|
11
|
+
The founder's decisions arrive as :class:`~techtree.release.models.
|
|
12
|
+
ReleaseInputs`, read from a file a person edits. This module never invents one
|
|
13
|
+
and never guesses a default for one.
|
|
14
|
+
|
|
15
|
+
Everything else is *read out of the tree being released* — the protocol
|
|
16
|
+
version, the digest of the engine bundle, the digest of the catalog index, and
|
|
17
|
+
the subject harness version the introductory Climb's Campaign pins. Those are
|
|
18
|
+
facts about the source, so asking a person for them would only create a second
|
|
19
|
+
answer that could disagree with the first.
|
|
20
|
+
|
|
21
|
+
The introductory Climb reference sits on the seam: the founder chooses which
|
|
22
|
+
Climb the release leads with, and this module refuses the choice unless the
|
|
23
|
+
catalog actually ships it under exactly that reference. A release that
|
|
24
|
+
advertises a Climb nobody shipped is the failure this check exists for.
|
|
25
|
+
|
|
26
|
+
Generation is deterministic. The same source tree and the same inputs produce
|
|
27
|
+
the same bytes on any machine, at any time, which is what lets a drift check
|
|
28
|
+
regenerate the artifacts into a throwaway copy of the repository and compare.
|
|
29
|
+
Nothing here reads the clock, the environment, the network, or version control.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from __future__ import annotations
|
|
33
|
+
|
|
34
|
+
from collections.abc import Mapping
|
|
35
|
+
from dataclasses import dataclass
|
|
36
|
+
from importlib.resources.abc import Traversable
|
|
37
|
+
from typing import Final
|
|
38
|
+
|
|
39
|
+
from techtree.catalog.repository import (
|
|
40
|
+
CATALOG_INDEX_FILENAME,
|
|
41
|
+
EmbeddedCatalogRepository,
|
|
42
|
+
packaged_catalog_root,
|
|
43
|
+
)
|
|
44
|
+
from techtree.engines.bundle import embedded_engine_root, engine_bundle_digest
|
|
45
|
+
from techtree.errors import ValidationError
|
|
46
|
+
from techtree.models.base import Digest, JsonValue
|
|
47
|
+
from techtree.release.document import document_digest
|
|
48
|
+
from techtree.release.models import (
|
|
49
|
+
BUILD_INFO_SCHEMA_VERSION,
|
|
50
|
+
RELEASE_CORE_SCHEMA_VERSION,
|
|
51
|
+
ReleaseCore,
|
|
52
|
+
ReleaseInputs,
|
|
53
|
+
)
|
|
54
|
+
from techtree.version import PROTOCOL_VERSION
|
|
55
|
+
|
|
56
|
+
__all__ = [
|
|
57
|
+
"BUILD_PROVENANCE_MECHANISM",
|
|
58
|
+
"GENERATOR_VERSION",
|
|
59
|
+
"ReleaseSources",
|
|
60
|
+
"build_info_document",
|
|
61
|
+
"build_release_core",
|
|
62
|
+
"packaged_sources",
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
#: Bumped when the generated shape changes, so a stored artifact can say which
|
|
66
|
+
#: generator produced it.
|
|
67
|
+
GENERATOR_VERSION: Final = "0.1.0"
|
|
68
|
+
|
|
69
|
+
#: How a wheel comes to know which commit it was built from (decisions 0026).
|
|
70
|
+
#: One mechanism, described where the release process is recorded, so that a
|
|
71
|
+
#: reader of ``release/build-info.json`` never has to read the build hook to
|
|
72
|
+
#: learn what the stamp inside a wheel means or how much it is worth.
|
|
73
|
+
BUILD_PROVENANCE_MECHANISM: Final[Mapping[str, str]] = {
|
|
74
|
+
"mechanism": (
|
|
75
|
+
"a hatchling build hook, tools/stamp_provenance.py, writes the source "
|
|
76
|
+
"commit into the package while the wheel is being built"
|
|
77
|
+
),
|
|
78
|
+
"build_command": "uv build --wheel",
|
|
79
|
+
"stamped_file": "techtree/resources/release/build-provenance.json",
|
|
80
|
+
"stamp_schema_version": "techtree.build-provenance.v1",
|
|
81
|
+
"commit_source": "git rev-parse HEAD, asked of the tree being packaged",
|
|
82
|
+
"refuses": (
|
|
83
|
+
"a build with no git, no commit, or any packaged path (src/techtree, "
|
|
84
|
+
"pyproject.toml, README.md, LICENSE) differing from that commit; there "
|
|
85
|
+
"is no unknown value and no default"
|
|
86
|
+
),
|
|
87
|
+
"in_the_committed_tree": (
|
|
88
|
+
"never; the stamp belongs to one artifact and is written and removed "
|
|
89
|
+
"by the build"
|
|
90
|
+
),
|
|
91
|
+
"editable_installs": (
|
|
92
|
+
"not stamped: an editable install is the working tree rather than a "
|
|
93
|
+
"distributable artifact, and `techtree release info` reports that no "
|
|
94
|
+
"commit was stamped instead of naming one"
|
|
95
|
+
),
|
|
96
|
+
"reported_by": "techtree release info",
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@dataclass(frozen=True)
|
|
101
|
+
class ReleaseSources:
|
|
102
|
+
"""The part of a release that is read rather than decided."""
|
|
103
|
+
|
|
104
|
+
catalog_root: Traversable
|
|
105
|
+
engine_root: Traversable
|
|
106
|
+
protocol_version: str
|
|
107
|
+
|
|
108
|
+
def catalog_index_bytes(self) -> bytes:
|
|
109
|
+
"""Return the exact bytes of the catalog index."""
|
|
110
|
+
return (self.catalog_root / CATALOG_INDEX_FILENAME).read_bytes()
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def packaged_sources() -> ReleaseSources:
|
|
114
|
+
"""Return the sources of the build this process is running from."""
|
|
115
|
+
return ReleaseSources(
|
|
116
|
+
catalog_root=packaged_catalog_root(),
|
|
117
|
+
engine_root=embedded_engine_root(),
|
|
118
|
+
protocol_version=PROTOCOL_VERSION,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def build_release_core(inputs: ReleaseInputs, sources: ReleaseSources) -> ReleaseCore:
|
|
123
|
+
"""Bind founder decisions and source facts into one ReleaseCore."""
|
|
124
|
+
catalog = EmbeddedCatalogRepository(sources.catalog_root)
|
|
125
|
+
reference = _exact_climb_reference(catalog, inputs.intro_climb_reference)
|
|
126
|
+
climb = catalog.load_climb(reference)
|
|
127
|
+
campaign = catalog.load_campaign(climb.campaign_spec_digest)
|
|
128
|
+
|
|
129
|
+
values = {
|
|
130
|
+
"cli_version": inputs.cli_version,
|
|
131
|
+
"maximum_tested_host_hermes_version": (
|
|
132
|
+
inputs.maximum_tested_host_hermes_version
|
|
133
|
+
),
|
|
134
|
+
"minimum_host_hermes_version": inputs.minimum_host_hermes_version,
|
|
135
|
+
"release_id": inputs.release_id,
|
|
136
|
+
"skill_improver_digest": inputs.skill_improver_digest,
|
|
137
|
+
"starter_skill_digest": inputs.starter_skill_digest,
|
|
138
|
+
"starter_skill_object_url": inputs.starter_skill_object_url,
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return ReleaseCore(
|
|
142
|
+
schema_version=RELEASE_CORE_SCHEMA_VERSION,
|
|
143
|
+
protocol_version=sources.protocol_version,
|
|
144
|
+
engine_digest=engine_bundle_digest(sources.engine_root),
|
|
145
|
+
catalog_digest=document_digest(sources.catalog_index_bytes()),
|
|
146
|
+
intro_climb_reference=reference,
|
|
147
|
+
subject_hermes_version=campaign.subject.harness.version,
|
|
148
|
+
# Where a run is published, where it is then read, and the public half
|
|
149
|
+
# of the key that countersigns the answer. None of the three can be
|
|
150
|
+
# derived from this source tree — they are facts about a deployment and
|
|
151
|
+
# about a key the founder generates — so they arrive with the founder's
|
|
152
|
+
# other decisions and are copied through unchanged. Decisions 0038.
|
|
153
|
+
publication=inputs.publication,
|
|
154
|
+
**values,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def build_info_document(
|
|
159
|
+
*,
|
|
160
|
+
inputs_digest: Digest,
|
|
161
|
+
pyproject_version: str,
|
|
162
|
+
core: ReleaseCore,
|
|
163
|
+
artifacts: Mapping[str, Digest],
|
|
164
|
+
) -> dict[str, JsonValue]:
|
|
165
|
+
"""Return what was built, from what, without reading the clock.
|
|
166
|
+
|
|
167
|
+
A build record normally starts with a timestamp. This one cannot: the
|
|
168
|
+
artifacts it describes are regenerated in a throwaway copy of the
|
|
169
|
+
repository and compared byte for byte, so anything that changes between two
|
|
170
|
+
identical builds would turn every drift check into a false alarm. What
|
|
171
|
+
matters for support is which inputs produced which bytes, and that is what
|
|
172
|
+
is recorded.
|
|
173
|
+
|
|
174
|
+
``pyproject_version`` is recorded even though the ReleaseCore names the
|
|
175
|
+
published CLI version separately. They are two different statements — what
|
|
176
|
+
this source tree declares, and what the release publishes — and both a
|
|
177
|
+
support conversation and the version check in
|
|
178
|
+
:mod:`techtree.release.checks` need to see both.
|
|
179
|
+
|
|
180
|
+
``build_provenance`` describes how the *wheel* gets the one coordinate this
|
|
181
|
+
document deliberately does not carry: the commit it was built from
|
|
182
|
+
(decisions 0026). The description is here because this is the record of how
|
|
183
|
+
a release is produced; the value itself exists only inside a built wheel.
|
|
184
|
+
|
|
185
|
+
``artifacts`` does not include this document. A build record cannot state
|
|
186
|
+
its own digest, and pretending otherwise would only produce a value that
|
|
187
|
+
could never be recomputed.
|
|
188
|
+
"""
|
|
189
|
+
return {
|
|
190
|
+
"schema_version": BUILD_INFO_SCHEMA_VERSION,
|
|
191
|
+
"generator": "tools/build_release_core.py",
|
|
192
|
+
"generator_version": GENERATOR_VERSION,
|
|
193
|
+
"build_provenance": dict(BUILD_PROVENANCE_MECHANISM),
|
|
194
|
+
"inputs": {
|
|
195
|
+
"release_inputs_digest": inputs_digest,
|
|
196
|
+
"pyproject_version": pyproject_version,
|
|
197
|
+
"protocol_version": core.protocol_version,
|
|
198
|
+
"engine_digest": core.engine_digest,
|
|
199
|
+
"catalog_digest": core.catalog_digest,
|
|
200
|
+
"intro_climb_reference": core.intro_climb_reference,
|
|
201
|
+
},
|
|
202
|
+
"artifacts": dict(sorted(artifacts.items())),
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _exact_climb_reference(catalog: EmbeddedCatalogRepository, reference: str) -> str:
|
|
207
|
+
"""Return the reference, having proved the catalog ships exactly it.
|
|
208
|
+
|
|
209
|
+
The repository resolves a bare slug to the newest version, which is right
|
|
210
|
+
for a person browsing and wrong for a release document: a release names one
|
|
211
|
+
immutable Climb, and it has to name it the way the catalog files it.
|
|
212
|
+
"""
|
|
213
|
+
available = catalog.list_climb_references()
|
|
214
|
+
if reference not in available:
|
|
215
|
+
raise ValidationError(
|
|
216
|
+
f"the release names {reference!r} as its introductory Climb, but "
|
|
217
|
+
"this catalog does not ship that exact reference",
|
|
218
|
+
code="intro_climb_not_in_catalog",
|
|
219
|
+
details={"reference": reference, "available": list(available)},
|
|
220
|
+
)
|
|
221
|
+
return reference
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"""What a ReleaseCore is, and what makes one honest. Spec sections 6.6, 9.3.
|
|
2
|
+
|
|
3
|
+
A ReleaseCore is the single document that says which CLI, which engine, which
|
|
4
|
+
catalog, which Climb, and which Skills are one release. It is generated before
|
|
5
|
+
the CLI wheel and the plugin commit exist, which is what keeps the
|
|
6
|
+
cross-repository binding acyclic (spec section 9.3.3): the wheel and the plugin
|
|
7
|
+
embed these exact bytes, and the website's later ``BootstrapRelease`` wraps
|
|
8
|
+
them.
|
|
9
|
+
|
|
10
|
+
Decisions document 0026 settles what may be in it. A ReleaseCore is a
|
|
11
|
+
*contract*: it holds only values a person can know when they author it, so it
|
|
12
|
+
is honestly final the moment it is committed. It therefore says nothing about
|
|
13
|
+
the artifact built from it — no wheel hash, no plugin commit, and no source
|
|
14
|
+
commit, because an artifact never describes its own identity. Identity is
|
|
15
|
+
stamped onto the wheel at build time (:mod:`techtree.release.provenance`) and
|
|
16
|
+
witnessed externally by the website's bootstrap document.
|
|
17
|
+
|
|
18
|
+
Every coordinate here is concrete, and the schema is what makes that true
|
|
19
|
+
rather than a convention anyone has to remember. A version is three numbers, a
|
|
20
|
+
release identifier is a name, a digest is sixty-four hexadecimal characters
|
|
21
|
+
that are not all zero, and the starter Skill's address is a content address
|
|
22
|
+
ending in the digest of the bytes it returns. None of those admit a spelling
|
|
23
|
+
that means "nobody has chosen this yet", so a document that validates is a
|
|
24
|
+
document whose every coordinate was chosen.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import base64
|
|
30
|
+
import re
|
|
31
|
+
from typing import Annotated, Final, Literal, Self
|
|
32
|
+
|
|
33
|
+
from pydantic import AfterValidator, StringConstraints, model_validator
|
|
34
|
+
|
|
35
|
+
from techtree.canonical import sha256_digest_bytes
|
|
36
|
+
from techtree.constants import DIGEST_PREFIX
|
|
37
|
+
from techtree.crypto import ED25519_PUBLIC_KEY_BYTES
|
|
38
|
+
from techtree.models.base import Digest, NonEmptyString, ProtocolModel, PublicKeyRef
|
|
39
|
+
|
|
40
|
+
__all__ = [
|
|
41
|
+
"BUILD_INFO_SCHEMA_VERSION",
|
|
42
|
+
"CONCRETE_DIGEST_LENGTH",
|
|
43
|
+
"CONCRETE_DIGEST_PATTERN",
|
|
44
|
+
"OBJECT_URL_PATTERN",
|
|
45
|
+
"PLAIN_HTTPS_URL_PATTERN",
|
|
46
|
+
"RELEASE_CORE_SCHEMA_VERSION",
|
|
47
|
+
"RELEASE_ID_PATTERN",
|
|
48
|
+
"RELEASE_INPUTS_SCHEMA_VERSION",
|
|
49
|
+
"VERSION_PATTERN",
|
|
50
|
+
"ConcreteDigest",
|
|
51
|
+
"ObjectUrl",
|
|
52
|
+
"PinnedNetworkKey",
|
|
53
|
+
"PlainHttpsUrl",
|
|
54
|
+
"PublicationCoordinates",
|
|
55
|
+
"ReleaseCore",
|
|
56
|
+
"ReleaseId",
|
|
57
|
+
"ReleaseInputs",
|
|
58
|
+
"Version",
|
|
59
|
+
"object_url_digest",
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
RELEASE_CORE_SCHEMA_VERSION: Final = "techtree.release-core.v1"
|
|
63
|
+
RELEASE_INPUTS_SCHEMA_VERSION: Final = "techtree.release-inputs.v1"
|
|
64
|
+
BUILD_INFO_SCHEMA_VERSION: Final = "techtree.release-build-info.v1"
|
|
65
|
+
|
|
66
|
+
#: A published version is three numbers. Everything this release names — the
|
|
67
|
+
#: CLI, the host Hermes range, the subject harness — is versioned that way, and
|
|
68
|
+
#: nothing that is merely *proposed* has a version at all, so the pattern needs
|
|
69
|
+
#: no room for a suffix that would let one read as the other.
|
|
70
|
+
VERSION_PATTERN: Final = r"^[0-9]+(?:\.[0-9]+){2}$"
|
|
71
|
+
|
|
72
|
+
type Version = Annotated[str, StringConstraints(pattern=VERSION_PATTERN)]
|
|
73
|
+
|
|
74
|
+
#: A release identifier is a name a person gives one release — ``climb-v0.1.0``
|
|
75
|
+
#: — so it begins with a letter and joins lowercase words with ``-`` or ``.``.
|
|
76
|
+
#: A bare number is a version, and a version is not an identity.
|
|
77
|
+
RELEASE_ID_PATTERN: Final = r"^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$"
|
|
78
|
+
|
|
79
|
+
type ReleaseId = Annotated[str, StringConstraints(pattern=RELEASE_ID_PATTERN)]
|
|
80
|
+
|
|
81
|
+
#: Sixty-four hexadecimal characters with at least one of them non-zero. The
|
|
82
|
+
#: length is fixed by :data:`CONCRETE_DIGEST_LENGTH` beside it, so this pattern
|
|
83
|
+
#: only has to say "not all zeros": nothing hashes to zero, so a zeroed digest
|
|
84
|
+
#: is never a measurement.
|
|
85
|
+
CONCRETE_DIGEST_PATTERN: Final = rf"^{DIGEST_PREFIX}0*[1-9a-f][0-9a-f]*$"
|
|
86
|
+
CONCRETE_DIGEST_LENGTH: Final = len(DIGEST_PREFIX) + 64
|
|
87
|
+
|
|
88
|
+
type ConcreteDigest = Annotated[
|
|
89
|
+
str,
|
|
90
|
+
StringConstraints(
|
|
91
|
+
pattern=CONCRETE_DIGEST_PATTERN,
|
|
92
|
+
min_length=CONCRETE_DIGEST_LENGTH,
|
|
93
|
+
max_length=CONCRETE_DIGEST_LENGTH,
|
|
94
|
+
),
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
#: Where a published artifact is served from. Spec section 4.1 requires the
|
|
98
|
+
#: starter Skill to be an exact read-only object URL, and decision 0007 R10
|
|
99
|
+
#: requires every coordinate of a real release to be concrete and immutable, so
|
|
100
|
+
#: this is an absolute ``https`` content address: it ends in the digest of the
|
|
101
|
+
#: bytes it returns, which is how the website files public objects and how a
|
|
102
|
+
#: fetcher checks a response before it has scanned anything.
|
|
103
|
+
#:
|
|
104
|
+
#: The authority may not carry userinfo. A published coordinate is copied into
|
|
105
|
+
#: the plugin, into the website, into an approval packet and into support
|
|
106
|
+
#: transcripts, and ``https://user:token@host/…`` would carry a credential
|
|
107
|
+
#: through every one of them — while also making the address mean different
|
|
108
|
+
#: things to different readers. A public read-only object needs no credential,
|
|
109
|
+
#: so an address that offers one is not the coordinate this field is for.
|
|
110
|
+
OBJECT_URL_PATTERN: Final = rf"^https://[^\s/@]+/[^\s]*{DIGEST_PREFIX}[0-9a-f]{{64}}$"
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _require_resolvable_host(value: str) -> str:
|
|
114
|
+
"""Reject an address under a top-level domain that can never resolve.
|
|
115
|
+
|
|
116
|
+
RFC 2606 reserves ``.invalid`` so that names under it are guaranteed not to
|
|
117
|
+
resolve. An address nothing can ever fetch is not a place something is
|
|
118
|
+
published, whatever else it looks like.
|
|
119
|
+
"""
|
|
120
|
+
authority = value.removeprefix("https://").split("/", 1)[0]
|
|
121
|
+
host = authority.rsplit(":", 1)[0] if ":" in authority else authority
|
|
122
|
+
if host.lower().endswith(".invalid"):
|
|
123
|
+
raise ValueError("an address under .invalid can never resolve")
|
|
124
|
+
return value
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
type ObjectUrl = Annotated[
|
|
128
|
+
str,
|
|
129
|
+
StringConstraints(pattern=OBJECT_URL_PATTERN),
|
|
130
|
+
AfterValidator(_require_resolvable_host),
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
_OBJECT_URL_DIGEST_RE: Final = re.compile(rf"{DIGEST_PREFIX}[0-9a-f]{{64}}$")
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def object_url_digest(url: str) -> Digest:
|
|
138
|
+
"""Return the file digest a content address is keyed by.
|
|
139
|
+
|
|
140
|
+
The address ends in the digest of the bytes it serves, which is what makes
|
|
141
|
+
it safe to fetch from at all: whatever arrives is checked against the
|
|
142
|
+
address it came from before anything else looks at it.
|
|
143
|
+
|
|
144
|
+
Raises:
|
|
145
|
+
ValueError: when the address ends in no digest, and therefore promises
|
|
146
|
+
nothing about what it serves.
|
|
147
|
+
"""
|
|
148
|
+
found = _OBJECT_URL_DIGEST_RE.search(url)
|
|
149
|
+
if found is None:
|
|
150
|
+
raise ValueError(f"{url!r} is not keyed by the digest of what it serves")
|
|
151
|
+
return found.group()
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
#: A published address with nothing after its path. Every URL a release pins is
|
|
155
|
+
#: read by a machine and printed to a person, so it is ``https`` and it carries
|
|
156
|
+
#: no query string and no fragment: a submission travels in a request body and
|
|
157
|
+
#: never in a URL, and an address that could carry one would be a place a proof
|
|
158
|
+
#: or a volunteered address could end up in somebody's access log.
|
|
159
|
+
PLAIN_HTTPS_URL_PATTERN: Final = r"^https://[^\s/@?#]+(?:/[^\s?#]*)?$"
|
|
160
|
+
|
|
161
|
+
type PlainHttpsUrl = Annotated[
|
|
162
|
+
str,
|
|
163
|
+
StringConstraints(pattern=PLAIN_HTTPS_URL_PATTERN),
|
|
164
|
+
AfterValidator(_require_resolvable_host),
|
|
165
|
+
]
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class PinnedNetworkKey(PublicKeyRef):
|
|
169
|
+
"""The public half of the key the run log countersigns receipts with.
|
|
170
|
+
|
|
171
|
+
Requiring a receipt to carry a key and a signature proves nothing on its
|
|
172
|
+
own: a server that wanted to lie would invent a key, sign with it, and hand
|
|
173
|
+
both to the participant. What makes a countersignature worth having is that
|
|
174
|
+
the participant already knows which key to expect, so the key is pinned in
|
|
175
|
+
the release rather than learned from the answer.
|
|
176
|
+
|
|
177
|
+
The identifier is the digest of the public key and is checked to be, as
|
|
178
|
+
every other key identifier in this protocol is (decisions 0038, founder
|
|
179
|
+
ruling of 2026-08-27). A derived identifier cannot name one key and carry
|
|
180
|
+
another, so a receipt that names a key it does not carry is caught without
|
|
181
|
+
anybody writing a check for it. Rotation is a new key and therefore a new
|
|
182
|
+
identifier, and a release pins exactly the one it trusts.
|
|
183
|
+
|
|
184
|
+
Only the public half ever reaches this repository. Nothing here can sign,
|
|
185
|
+
and nothing here is a place a private key could be put.
|
|
186
|
+
"""
|
|
187
|
+
|
|
188
|
+
@model_validator(mode="after")
|
|
189
|
+
def _check_the_identifier_is_the_digest_of_the_key(self) -> Self:
|
|
190
|
+
"""Reject a key whose identifier was assigned rather than derived."""
|
|
191
|
+
raw = base64.b64decode(self.public_key, validate=True)
|
|
192
|
+
if len(raw) != ED25519_PUBLIC_KEY_BYTES:
|
|
193
|
+
raise ValueError(
|
|
194
|
+
f"an Ed25519 public key is {ED25519_PUBLIC_KEY_BYTES} raw bytes"
|
|
195
|
+
)
|
|
196
|
+
if not any(raw):
|
|
197
|
+
raise ValueError(
|
|
198
|
+
"an all-zero public key names no key; it is the spelling of a "
|
|
199
|
+
"coordinate nobody has chosen yet, and a release pins only "
|
|
200
|
+
"coordinates somebody chose"
|
|
201
|
+
)
|
|
202
|
+
derived = sha256_digest_bytes(raw)
|
|
203
|
+
if self.key_id != derived:
|
|
204
|
+
raise ValueError(
|
|
205
|
+
f"a network key identifier is the digest of the key it carries: "
|
|
206
|
+
f"this one says {self.key_id} and carries {derived}"
|
|
207
|
+
)
|
|
208
|
+
return self
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class PublicationCoordinates(ProtocolModel):
|
|
212
|
+
"""Where a run is published, where it is then read, and who countersigns.
|
|
213
|
+
|
|
214
|
+
All three are release coordinates rather than settings, for one reason each
|
|
215
|
+
(decisions 0038, founder ruling of 2026-08-27).
|
|
216
|
+
|
|
217
|
+
``submission_endpoint`` is pinned because a stable release has to be able to
|
|
218
|
+
publish with nothing configured. An address that only arrived from the
|
|
219
|
+
environment meant a published wheel could not publish out of the box, and a
|
|
220
|
+
person who had to set a variable before publishing would be a person who
|
|
221
|
+
could set it to the wrong thing.
|
|
222
|
+
|
|
223
|
+
``public_log_url`` is pinned because a receipt names where the entry now
|
|
224
|
+
lives, and an entry address the participant did not already expect is an
|
|
225
|
+
address the answering server chose. The receipt's address is checked to be
|
|
226
|
+
on this origin before the receipt is written down.
|
|
227
|
+
|
|
228
|
+
``network_key`` is pinned because a countersignature is only worth the
|
|
229
|
+
prior knowledge of which key made it.
|
|
230
|
+
"""
|
|
231
|
+
|
|
232
|
+
submission_endpoint: PlainHttpsUrl
|
|
233
|
+
public_log_url: PlainHttpsUrl
|
|
234
|
+
network_key: PinnedNetworkKey
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
class ReleaseCore(ProtocolModel):
|
|
238
|
+
"""The frozen coordinates of one Climb release. Spec section 6.6.
|
|
239
|
+
|
|
240
|
+
Every field is required and concrete. There is no field that describes the
|
|
241
|
+
wheel, the plugin, or the commit this document was authored at: those are
|
|
242
|
+
facts about artifacts that do not exist yet when the release is written,
|
|
243
|
+
and decision 0026 puts them where they can be known — stamped into the
|
|
244
|
+
wheel, and witnessed by the website's bootstrap document.
|
|
245
|
+
|
|
246
|
+
``starter_skill_digest`` and ``starter_skill_object_url`` are two halves of
|
|
247
|
+
one coordinate and neither substitutes for the other. The digest says
|
|
248
|
+
*which* Skill the release measured — spec section 4.1 and decisions 0008
|
|
249
|
+
make it the ordered content-tree digest, not a file hash — and the URL says
|
|
250
|
+
where a machine that does not already hold those bytes can obtain them, and
|
|
251
|
+
which bytes the address itself promises.
|
|
252
|
+
"""
|
|
253
|
+
|
|
254
|
+
schema_version: Literal["techtree.release-core.v1"]
|
|
255
|
+
release_id: ReleaseId
|
|
256
|
+
cli_version: Version
|
|
257
|
+
protocol_version: NonEmptyString
|
|
258
|
+
engine_digest: ConcreteDigest
|
|
259
|
+
catalog_digest: ConcreteDigest
|
|
260
|
+
intro_climb_reference: NonEmptyString
|
|
261
|
+
starter_skill_digest: ConcreteDigest
|
|
262
|
+
starter_skill_object_url: ObjectUrl
|
|
263
|
+
skill_improver_digest: ConcreteDigest
|
|
264
|
+
minimum_host_hermes_version: Version
|
|
265
|
+
maximum_tested_host_hermes_version: Version
|
|
266
|
+
subject_hermes_version: Version
|
|
267
|
+
publication: PublicationCoordinates
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
class ReleaseInputs(ProtocolModel):
|
|
271
|
+
"""The founder-owned half of a ReleaseCore. Spec section 4.5.
|
|
272
|
+
|
|
273
|
+
Every value here is a decision a person makes and freezes — the published
|
|
274
|
+
version, the release identifier, the Skill artifacts, the host Hermes
|
|
275
|
+
range. None of them can be derived from this repository, so they are held
|
|
276
|
+
in one hand-edited file that the generator reads, rather than passed as
|
|
277
|
+
command options that would leave the generated document unreproducible.
|
|
278
|
+
|
|
279
|
+
The remaining ReleaseCore coordinates are absent on purpose. A release
|
|
280
|
+
input file that tried to state the engine digest or the protocol version
|
|
281
|
+
would be asserting something the source tree already answers.
|
|
282
|
+
"""
|
|
283
|
+
|
|
284
|
+
schema_version: Literal["techtree.release-inputs.v1"]
|
|
285
|
+
release_id: ReleaseId
|
|
286
|
+
cli_version: Version
|
|
287
|
+
intro_climb_reference: NonEmptyString
|
|
288
|
+
starter_skill_digest: ConcreteDigest
|
|
289
|
+
starter_skill_object_url: ObjectUrl
|
|
290
|
+
skill_improver_digest: ConcreteDigest
|
|
291
|
+
minimum_host_hermes_version: Version
|
|
292
|
+
maximum_tested_host_hermes_version: Version
|
|
293
|
+
publication: PublicationCoordinates
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Which commit a built artifact came from. Decisions document 0026.
|
|
2
|
+
|
|
3
|
+
An artifact never describes its own identity. The ReleaseCore is written before
|
|
4
|
+
the wheel exists and cannot name the commit the wheel is built from without
|
|
5
|
+
inventing it, so the commit is *stamped onto the wheel while the wheel is being
|
|
6
|
+
built* and read back out of it here.
|
|
7
|
+
|
|
8
|
+
The stamp is one small document, ``build-provenance.json``, written into the
|
|
9
|
+
package's release resources by the build hook in ``tools/stamp_provenance.py``
|
|
10
|
+
and never present in the committed tree. Two properties of that hook are what
|
|
11
|
+
make this module's answer worth anything, and both live there rather than here:
|
|
12
|
+
the commit it writes is the commit the packaged sources are, and a build that
|
|
13
|
+
cannot establish that fails instead of guessing.
|
|
14
|
+
|
|
15
|
+
Reading it back has three possible answers and only two of them are values. A
|
|
16
|
+
wheel carries the stamp. A source checkout — an editable install, the test
|
|
17
|
+
suite, ``uv run techtree`` — is not a built artifact and carries none, and says
|
|
18
|
+
so rather than reporting a commit nobody stamped.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import zipfile
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Annotated, Final, Literal
|
|
26
|
+
|
|
27
|
+
from pydantic import StringConstraints
|
|
28
|
+
|
|
29
|
+
from techtree.errors import ValidationError
|
|
30
|
+
from techtree.models.base import ProtocolModel
|
|
31
|
+
from techtree.release.document import (
|
|
32
|
+
packaged_release_root,
|
|
33
|
+
render_document,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
"BUILD_PROVENANCE_FILENAME",
|
|
38
|
+
"BUILD_PROVENANCE_SCHEMA_VERSION",
|
|
39
|
+
"COMMIT_PATTERN",
|
|
40
|
+
"BuildProvenance",
|
|
41
|
+
"Commit",
|
|
42
|
+
"packaged_build_provenance",
|
|
43
|
+
"parse_build_provenance",
|
|
44
|
+
"render_build_provenance",
|
|
45
|
+
"wheel_build_provenance",
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
#: Bumped only if the stamped shape changes. The website, the plugin, and the
|
|
49
|
+
#: bootstrap checker all read it.
|
|
50
|
+
BUILD_PROVENANCE_SCHEMA_VERSION: Final = "techtree.build-provenance.v1"
|
|
51
|
+
|
|
52
|
+
#: The stamped file, wherever it is read from: the installed package, or a
|
|
53
|
+
#: wheel that has not been installed yet.
|
|
54
|
+
BUILD_PROVENANCE_FILENAME: Final = "build-provenance.json"
|
|
55
|
+
|
|
56
|
+
#: A full git commit, lowercase, never abbreviated. Spec section 9.3.2 pins
|
|
57
|
+
#: exact commits, and an abbreviation is not one.
|
|
58
|
+
COMMIT_PATTERN: Final = r"^[0-9a-f]{40}$"
|
|
59
|
+
|
|
60
|
+
type Commit = Annotated[str, StringConstraints(pattern=COMMIT_PATTERN)]
|
|
61
|
+
|
|
62
|
+
#: Where the stamp sits inside a wheel, which stores the package tree flat
|
|
63
|
+
#: under its import name.
|
|
64
|
+
_WHEEL_MEMBER: Final = f"techtree/resources/release/{BUILD_PROVENANCE_FILENAME}"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class BuildProvenance(ProtocolModel):
|
|
68
|
+
"""The commit one built artifact was built from, and nothing else."""
|
|
69
|
+
|
|
70
|
+
schema_version: Literal["techtree.build-provenance.v1"]
|
|
71
|
+
source_commit: Commit
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def render_build_provenance(provenance: BuildProvenance) -> bytes:
|
|
75
|
+
"""Return the one byte spelling of a stamp.
|
|
76
|
+
|
|
77
|
+
The same commit produces the same bytes, which is what keeps two builds of
|
|
78
|
+
one commit byte-identical.
|
|
79
|
+
"""
|
|
80
|
+
return render_document(provenance.model_dump(mode="json"))
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def parse_build_provenance(raw: bytes) -> BuildProvenance:
|
|
84
|
+
"""Load and validate a stamp from stored bytes."""
|
|
85
|
+
try:
|
|
86
|
+
return BuildProvenance.model_validate_json(raw)
|
|
87
|
+
except ValueError as error:
|
|
88
|
+
raise ValidationError(
|
|
89
|
+
f"this is not a valid build provenance stamp: {error}",
|
|
90
|
+
code="build_provenance_invalid",
|
|
91
|
+
) from error
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def packaged_build_provenance() -> BuildProvenance | None:
|
|
95
|
+
"""Return what this build was stamped with, or None if it is not a build."""
|
|
96
|
+
stamp = packaged_release_root() / BUILD_PROVENANCE_FILENAME
|
|
97
|
+
if not stamp.is_file():
|
|
98
|
+
return None
|
|
99
|
+
return parse_build_provenance(stamp.read_bytes())
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def wheel_build_provenance(wheel: Path) -> BuildProvenance | None:
|
|
103
|
+
"""Return what a wheel on disk was stamped with, without installing it."""
|
|
104
|
+
with zipfile.ZipFile(wheel) as archive:
|
|
105
|
+
try:
|
|
106
|
+
raw = archive.read(_WHEEL_MEMBER)
|
|
107
|
+
except KeyError:
|
|
108
|
+
return None
|
|
109
|
+
return parse_build_provenance(raw)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"agents":{"subject":{"harness":{"id":"hermes-agent","skills":[],"use_bundled_skill":false,"version":"0.19.0"},"model":{"credential_env":"PRIME_API_KEY","model_id":"qwen/qwen3.7-flash","provider":"prime","revision":null},"runtime":{"cpu":2,"image":"python@sha256:90744cff8f32887f075c47d747a173ff333e9e98801667af93c357fa9f5e28ff","image_platform_digests":{"linux/amd64":"sha256:78b39ef14d8e2b4d71f8dc304f1328c37df95fe0ef99477c2ae6bd3d03784553","linux/arm64":"sha256:20eadabc42589e6543b24a64ab305b9895e9fcf6dbb2cadb14812f394ecdbadf"},"memory_gb":4,"network_policy":"restricted","supported_platforms":["linux/amd64","linux/arm64"],"type":"docker"},"sampling":{"max_tokens":4096,"temperature":0},"trainable":false}},"budgets":{"maximum_input_tokens":900000,"maximum_model_calls":44,"maximum_output_tokens":16000,"maximum_usd":2.5},"context":{"outcome_contract_digest":null,"program_ref":null},"data_policy_digest":"sha256:6c532a43d595286a08260481890bbbffa16d1b4dd89465d1cc8395099d9ebcf9","environment":{"id":"single-agent"},"evaluation_backend":{"attestation":"participant","executor_identity":null,"kind":"local_techtree","provider_run_ref":null,"schema_version":"techtree.evaluation-backend.v1alpha1","workspace_ref":null},"evidence":{"runtime_evidence":"not_required","verifiers_episode":"required"},"execution":{"max_concurrent":4,"order":"parallel_variants","retry_limit":0,"timeout_seconds":600},"kind":"Campaign","metadata":{"id":"campaign_4e8e01a28126c92a906b5cf10b46ee21","purpose":"component_uplift","version":1},"mutation_contract":{"allowed_differences":["/agents/subject/harness/skills"],"kind":"skill_insertion","maximum_skills":1,"minimum_skills":1,"target_agent":"subject"},"schema_version":"techtree.campaign.v1alpha1","scoring":{"aggregation":"mean","minimum_absolute_delta":0,"primary_reward":"exact_match","require_candidate_above_baseline":true},"taskset":{"membership":{"membership_digest":"sha256:56f697fb182cc31691c2e1fff20471acbd549a06e37232227c9491c03aa8f9aa","mode":"committed","ordered_task_hashes":["sha256:af8a43bbaf38d332131d73ef9a5b995d9f330fdc37a777a12bef7c51c9fa0ed1","sha256:e9a51458548dcfb10572fd9f032f00abdb298425114f68acdc074faa79229232","sha256:bafa30550e9c0b09281b3b4b99cb467b9522babc9c2b9d97fee93abef26933b0","sha256:1e74b2f5d62a6b25288d1d8ea3f85b6a0daf4a7697fe36d3f54b8534bb67b40d","sha256:c05fd9db26808494349e9292ecde51a2c6b19e6d87e6f7d7080e2089829504f5","sha256:02725a29cc06f2c883ad17e11a7fc2a09bd2b214df08f3c5c79e4941b54e315e","sha256:206eb5646df5b41b67d28f0e42d370ceb3eb0d72a78cea97e5bbecb7e3841a9f","sha256:c600be21a92a8c5401eaf645b8591b09f34052afe2ab61898d3be2edf8027cad","sha256:b11c2add8339b16dbe85804f9b046e203a0b05db2b7fe9412a4dc45172c6555b","sha256:bd835a69bbeb90d2b1c529aef6bac0c47a8b83750967bf4b64373a90540a07a4","sha256:0a5bde37b05e74be39c62f4418d4812a123d627bc627591fb02dde124b246426","sha256:f15ce47698160cd33fe7a0eb5e40a4cc7977624dd2bdf481ffee69b6204f26b3","sha256:4fb4ddcab900a74eb7cb6ff6a730cc07b5a244535f40f6ddb6aa726cb2796596","sha256:84d83849562386b699e048a91c5671e0f55b49098c9dfac9711a0895766caef3","sha256:e08bb7c7a18a57a83d2ad8dba412c416a8746f708f54b8a29efc0355cabbdb45","sha256:775f95710c5b09fd79e5720554b764f8a1c938063e5743977efe9a4f9d6dc12b","sha256:832e70ff2ab29bfc83da704344417f0c9be2af2935247178ecd1cedc00477097","sha256:5ef9194a49f5ada8a059b52cee70090a53df2bd5b415bf9ed8533287f3020dfc","sha256:b195109ade431bf058e12c4ae1dff6c7bf05d4770be9af9ed27d8f588e95c910","sha256:67eae183c9a688ab5172104c8f8e6380a3c052d367e5bd05964618f5f575bcd8","sha256:cb5651849ccdc4309b9931918392831a2afc7d1b82e6a976a8777a23e73057aa","sha256:391c080f51aa41130367d01e43ff21f80ef915274dd19b2f1a4aaf76380f8f13","sha256:6c39dc6c8c62d0b471e3e40cc560d6cf105632c62e40de8ab21f00e1f3dcd8dc","sha256:f5d73f388d604fc4df9afe63c70ca81f22646faad2bae99e24b1be0b5b6e239a","sha256:c6bb62c1b970b25d39823748dd9cb087884cfb7360668f8c405d870af26e223e","sha256:23c6f7a31ede5aa224788e4408ae1462ca57c559175232d740de6a139b02a5b4","sha256:13fe52ddfdeab29466e3ed21db540a08d169e09d338e86852f96387f2272795d","sha256:702eb0abb56d4554d7e9b8d75f15cf3d7ecfc0c8c9f83cf1c6c3caa25c5865f5","sha256:aee7f9a4999dce081dbd0ce6ab65db37ffe9bb88d9b4a07b944f376fef0a14ad","sha256:4b6a3d718933e312c05adff201cd14ab1d58b0eb5c0a0d32faccedc09cb9af68","sha256:8b5ae57c98f3963e91dd611d4cfc37da07c80d4b3c0091ed7b4a76175376381a","sha256:98f86f8d3663dcf3d0f32b6b1f9ef44913bb144355ecbcd89b8e4c96337b6964","sha256:3efcd8d42d8adfce2038f5e5d8c88c3ec1d30d6ae3379c4727ea711b28e1929d","sha256:bd2815069c78bced9ef23d36770c941b27753dad15db3e6c3a90c197181202a1","sha256:08c8263a76b1d93beb586c8c39f0469721455cd08d517b15bcf827cf09ac0755","sha256:0777a89a3da39284a1765937c3ce1cea7af2ac27a98ffee32e86a2a1fccca9b5"]},"ref":{"config":{},"id":"procedure-transfer-v1","kind":"verifiers","package":{"digest":"sha256:f42a0d5e968478af40c703645d247ef043f115f6801d3b497eca5fab47261a7c","kind":"embedded","name":"procedure-transfer-v1","revision":"0.1.0"}},"selection":{"num_rollouts":1,"num_tasks":36,"shuffle":false},"validation_receipt_digest":"sha256:4944bd71caa1a295e03325b18a7af753d0d8fcf787189c89244209171cda1302"}}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"climbs": [
|
|
3
|
+
{
|
|
4
|
+
"digest": "sha256:a3a5e9c5f9b40d4f08fad54852377e201fd0d6dd4acfa4c565a0edfac324a236",
|
|
5
|
+
"path": "climbs/hello-world-climb.json",
|
|
6
|
+
"reference": "hello-world-climb@1"
|
|
7
|
+
}
|
|
8
|
+
],
|
|
9
|
+
"objects": {
|
|
10
|
+
"sha256:4944bd71caa1a295e03325b18a7af753d0d8fcf787189c89244209171cda1302": {
|
|
11
|
+
"kind": "taskset_validation",
|
|
12
|
+
"media_type": "application/json",
|
|
13
|
+
"path": "taskset-validations/hello-world-climb.json"
|
|
14
|
+
},
|
|
15
|
+
"sha256:6c532a43d595286a08260481890bbbffa16d1b4dd89465d1cc8395099d9ebcf9": {
|
|
16
|
+
"kind": "data_policy",
|
|
17
|
+
"media_type": "application/json",
|
|
18
|
+
"path": "data-policies/hello-world-climb.json"
|
|
19
|
+
},
|
|
20
|
+
"sha256:991be5e42acbd48c6bd9aaf8e1f5a5a2478c0a0e4bf04ad4f2be84b702adee43": {
|
|
21
|
+
"kind": "validation_evidence",
|
|
22
|
+
"media_type": "application/json",
|
|
23
|
+
"path": "validation-evidence/hello-world-climb.json"
|
|
24
|
+
},
|
|
25
|
+
"sha256:ebf029abb266ca74c2def50eb23030511bab0e929c6bf4a68691f9b5afd554b1": {
|
|
26
|
+
"kind": "campaign",
|
|
27
|
+
"media_type": "application/json",
|
|
28
|
+
"path": "campaigns/hello-world-climb.json"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"schema_version": "techtree.catalog.v1alpha1"
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"campaign_spec_digest":"sha256:ebf029abb266ca74c2def50eb23030511bab0e929c6bf4a68691f9b5afd554b1","candidate_policy":{"constraints":{"format":"techtree-instruction-skill-v1","max_skills":1,"min_skills":1},"required_mutation":"skill_insertion","skill_visibility":"public"},"kind":"Climb","leaderboard":{"enabled":false,"evidence_required":"not_required"},"metadata":{"closes_at":null,"id":"climb_8475477d861d884f5dd2c046aeedb025","opens_at":null,"slug":"hello-world-climb","status":"development","summary":"A toy Skill-uplift Climb. It runs the synthetic BranchCode v1 task family twice — once without a Skill, once with one — so you can see what writing a procedure down changes. This is an introductory demonstration of the mechanism, not a measure of broad capability.","title":"Techtree Hello World","version":1},"publication":{"proof_grade":"development_only","public_trace_projection":"redacted","raw_episode_visibility":"prohibited","report_visibility":"public"},"schema_version":"techtree.climb.v1alpha1"}
|