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,303 @@
|
|
|
1
|
+
"""``techtree release info|verify``. Spec section 9.5.
|
|
2
|
+
|
|
3
|
+
Two read-only commands, and read-only is a contract rather than an accident:
|
|
4
|
+
between them they open the ReleaseCore this build ships, the engine bundle it
|
|
5
|
+
ships, and the catalog it ships, and they write nothing, prompt for nothing,
|
|
6
|
+
contact nothing, and call no model. A host agent runs ``release verify``
|
|
7
|
+
immediately after installing the CLI, before anything has been set up, so
|
|
8
|
+
needing state would make the command useless exactly when it matters.
|
|
9
|
+
|
|
10
|
+
``release info`` answers "what is this build?" from the release document and
|
|
11
|
+
from the commit stamped into this artifact when it was built (decisions 0026).
|
|
12
|
+
``release verify`` answers "is it still that?" by comparing every coordinate
|
|
13
|
+
against the thing it names, and — when the caller supplies the digest the
|
|
14
|
+
website published — against that too.
|
|
15
|
+
|
|
16
|
+
The stamped commit is the one thing here that is not in the release document,
|
|
17
|
+
because it is the one thing the release document cannot honestly contain: it
|
|
18
|
+
is a fact about this artifact rather than about the release. A source checkout
|
|
19
|
+
carries no stamp, and ``release info`` says that rather than naming a commit
|
|
20
|
+
nobody stamped.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
from typing import Annotated, Final
|
|
26
|
+
|
|
27
|
+
import typer
|
|
28
|
+
from rich.console import Console
|
|
29
|
+
from rich.table import Table
|
|
30
|
+
|
|
31
|
+
from techtree.canonical import validate_digest
|
|
32
|
+
from techtree.cli.context import cli_context
|
|
33
|
+
from techtree.cli.invoke import CommandResult, invoke_command
|
|
34
|
+
from techtree.cli.output import render_pairs
|
|
35
|
+
from techtree.errors import VerificationError
|
|
36
|
+
from techtree.models.base import Digest, JsonValue, NonEmptyString, ProtocolModel
|
|
37
|
+
from techtree.models.cli import CliMessage, MessageLevel, NextAction
|
|
38
|
+
from techtree.release.checks import (
|
|
39
|
+
ReleaseCheck,
|
|
40
|
+
ReleaseVerification,
|
|
41
|
+
local_release_facts,
|
|
42
|
+
verify_release_core,
|
|
43
|
+
)
|
|
44
|
+
from techtree.release.document import (
|
|
45
|
+
document_digest,
|
|
46
|
+
packaged_release_core_bytes,
|
|
47
|
+
parse_release_core,
|
|
48
|
+
)
|
|
49
|
+
from techtree.release.generate import packaged_sources
|
|
50
|
+
from techtree.release.provenance import (
|
|
51
|
+
BuildProvenance,
|
|
52
|
+
Commit,
|
|
53
|
+
packaged_build_provenance,
|
|
54
|
+
)
|
|
55
|
+
from techtree.version import package_version
|
|
56
|
+
|
|
57
|
+
__all__ = [
|
|
58
|
+
"INFO_COMMAND",
|
|
59
|
+
"RELEASE_NOT_VERIFIED",
|
|
60
|
+
"VERIFY_COMMAND",
|
|
61
|
+
"ReleaseInfoPayload",
|
|
62
|
+
"ReleaseVerificationPayload",
|
|
63
|
+
"info_release_command",
|
|
64
|
+
"verify_release_command",
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
INFO_COMMAND: Final = "release info"
|
|
68
|
+
VERIFY_COMMAND: Final = "release verify"
|
|
69
|
+
|
|
70
|
+
#: Stable error code for a release whose coordinates no longer agree.
|
|
71
|
+
RELEASE_NOT_VERIFIED: Final = "release_not_verified"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class ReleaseInfoPayload(ProtocolModel):
|
|
75
|
+
"""The coordinates of the release this build belongs to.
|
|
76
|
+
|
|
77
|
+
``source_commit`` is stamped into a built artifact and is absent from a
|
|
78
|
+
source checkout, which is why it is the one field here that can be null.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
release_id: NonEmptyString
|
|
82
|
+
cli_version: NonEmptyString
|
|
83
|
+
package_version: NonEmptyString
|
|
84
|
+
source_commit: Commit | None
|
|
85
|
+
protocol_version: NonEmptyString
|
|
86
|
+
release_core_digest: Digest
|
|
87
|
+
engine_digest: Digest
|
|
88
|
+
catalog_digest: Digest
|
|
89
|
+
intro_climb_reference: NonEmptyString
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class ReleaseVerificationPayload(ProtocolModel):
|
|
93
|
+
"""What was verified about this build's release, check by check."""
|
|
94
|
+
|
|
95
|
+
release_core_digest: Digest
|
|
96
|
+
expected_digest: Digest | None
|
|
97
|
+
verified: bool
|
|
98
|
+
checks: list[ReleaseCheck]
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def info_release_command(ctx: typer.Context) -> None:
|
|
102
|
+
"""Report the release coordinates this build was generated with."""
|
|
103
|
+
context = cli_context(ctx)
|
|
104
|
+
|
|
105
|
+
def action() -> CommandResult[ReleaseInfoPayload]:
|
|
106
|
+
raw = packaged_release_core_bytes()
|
|
107
|
+
core = parse_release_core(raw)
|
|
108
|
+
stamp = packaged_build_provenance()
|
|
109
|
+
payload = ReleaseInfoPayload(
|
|
110
|
+
release_id=core.release_id,
|
|
111
|
+
cli_version=core.cli_version,
|
|
112
|
+
package_version=package_version(),
|
|
113
|
+
source_commit=None if stamp is None else stamp.source_commit,
|
|
114
|
+
protocol_version=core.protocol_version,
|
|
115
|
+
release_core_digest=document_digest(raw),
|
|
116
|
+
engine_digest=core.engine_digest,
|
|
117
|
+
catalog_digest=core.catalog_digest,
|
|
118
|
+
intro_climb_reference=core.intro_climb_reference,
|
|
119
|
+
)
|
|
120
|
+
return CommandResult(
|
|
121
|
+
data=payload,
|
|
122
|
+
warnings=_unstamped_warnings(stamp),
|
|
123
|
+
next_actions=[_verify_action()],
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
invoke_command(context, INFO_COMMAND, action, render_data=_render_info)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def verify_release_command(
|
|
130
|
+
ctx: typer.Context,
|
|
131
|
+
expected: Annotated[
|
|
132
|
+
str | None,
|
|
133
|
+
typer.Option(
|
|
134
|
+
"--expected",
|
|
135
|
+
metavar="DIGEST",
|
|
136
|
+
help="The ReleaseCore digest the website published for this release.",
|
|
137
|
+
),
|
|
138
|
+
] = None,
|
|
139
|
+
) -> None:
|
|
140
|
+
"""Check every release coordinate against the thing it names."""
|
|
141
|
+
context = cli_context(ctx)
|
|
142
|
+
|
|
143
|
+
def action() -> CommandResult[ReleaseVerificationPayload]:
|
|
144
|
+
expected_digest = None if expected is None else validate_digest(expected)
|
|
145
|
+
raw = packaged_release_core_bytes()
|
|
146
|
+
result = verify_release_core(
|
|
147
|
+
raw,
|
|
148
|
+
local_release_facts(packaged_sources()),
|
|
149
|
+
expected_digest=expected_digest,
|
|
150
|
+
)
|
|
151
|
+
payload = ReleaseVerificationPayload(
|
|
152
|
+
release_core_digest=document_digest(raw),
|
|
153
|
+
expected_digest=expected_digest,
|
|
154
|
+
verified=result.verified,
|
|
155
|
+
checks=list(result.checks),
|
|
156
|
+
)
|
|
157
|
+
return CommandResult(
|
|
158
|
+
data=payload,
|
|
159
|
+
messages=_verified_messages(result),
|
|
160
|
+
warnings=_unstamped_warnings(packaged_build_provenance()),
|
|
161
|
+
next_actions=[
|
|
162
|
+
_check_environment() if result.verified else _inspect_action()
|
|
163
|
+
],
|
|
164
|
+
error=None if result.verified else _failure(result),
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
invoke_command(context, VERIFY_COMMAND, action, render_data=_render_verification)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
# ---------------------------------------------------------------------------
|
|
171
|
+
# Saying what happened
|
|
172
|
+
# ---------------------------------------------------------------------------
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _failure(result: ReleaseVerification) -> VerificationError:
|
|
176
|
+
"""Return the typed failure a drifted release reports."""
|
|
177
|
+
first = result.failures[0]
|
|
178
|
+
return VerificationError(
|
|
179
|
+
f"this release does not verify: {first.detail}",
|
|
180
|
+
code=RELEASE_NOT_VERIFIED,
|
|
181
|
+
details={
|
|
182
|
+
"failed_checks": _identifiers(result),
|
|
183
|
+
"codes": _codes(result),
|
|
184
|
+
},
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _identifiers(result: ReleaseVerification) -> list[JsonValue]:
|
|
189
|
+
"""Return the failed checks in the shape a typed error's details carry."""
|
|
190
|
+
return [check.id for check in result.failures]
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _codes(result: ReleaseVerification) -> list[JsonValue]:
|
|
194
|
+
"""Return the distinct codes a failed verification reports under."""
|
|
195
|
+
return [code for code in sorted({check.code for check in result.failures})]
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _verified_messages(result: ReleaseVerification) -> list[CliMessage]:
|
|
199
|
+
if not result.verified:
|
|
200
|
+
return []
|
|
201
|
+
return [
|
|
202
|
+
CliMessage(
|
|
203
|
+
level=MessageLevel.INFO,
|
|
204
|
+
code="release_verified",
|
|
205
|
+
text=(
|
|
206
|
+
f"This release verifies: {len(result.checks)} checks, "
|
|
207
|
+
f"{len(result.skipped)} of them not applicable to an installed "
|
|
208
|
+
"CLI, with nothing fetched."
|
|
209
|
+
),
|
|
210
|
+
)
|
|
211
|
+
]
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _unstamped_warnings(stamp: BuildProvenance | None) -> list[CliMessage]:
|
|
215
|
+
"""Say when this is not a built artifact, so nothing claims a commit."""
|
|
216
|
+
if stamp is not None:
|
|
217
|
+
return []
|
|
218
|
+
return [
|
|
219
|
+
CliMessage(
|
|
220
|
+
level=MessageLevel.WARNING,
|
|
221
|
+
code="release_source_commit_unstamped",
|
|
222
|
+
text=(
|
|
223
|
+
"This is running from a source checkout rather than an "
|
|
224
|
+
"installed build, so no source commit was stamped into it and "
|
|
225
|
+
"none is reported."
|
|
226
|
+
),
|
|
227
|
+
)
|
|
228
|
+
]
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _inspect_action() -> NextAction:
|
|
232
|
+
return NextAction(
|
|
233
|
+
id="release_checks",
|
|
234
|
+
label="See every release check, including the ones that passed",
|
|
235
|
+
reason="Machine output lists each check with its own stable code.",
|
|
236
|
+
cli=["techtree", "release", "verify", "--json"],
|
|
237
|
+
hermes_tool=None,
|
|
238
|
+
hermes_args=None,
|
|
239
|
+
requires_user_confirmation=False,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _verify_action() -> NextAction:
|
|
244
|
+
return NextAction(
|
|
245
|
+
id="verify_release",
|
|
246
|
+
label="Check this build against the release it names",
|
|
247
|
+
reason="Every coordinate above is checked against the thing it points at.",
|
|
248
|
+
cli=["techtree", "release", "verify"],
|
|
249
|
+
hermes_tool=None,
|
|
250
|
+
hermes_args=None,
|
|
251
|
+
requires_user_confirmation=False,
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _check_environment() -> NextAction:
|
|
256
|
+
return NextAction(
|
|
257
|
+
id="check_environment",
|
|
258
|
+
label="Check that this machine is ready",
|
|
259
|
+
reason="Doctor reports what is installed, what is missing, and what "
|
|
260
|
+
"would block a run.",
|
|
261
|
+
cli=["techtree", "doctor"],
|
|
262
|
+
hermes_tool=None,
|
|
263
|
+
hermes_args=None,
|
|
264
|
+
requires_user_confirmation=False,
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _render_info(data: object, console: Console) -> None:
|
|
269
|
+
if not isinstance(data, ReleaseInfoPayload):
|
|
270
|
+
return
|
|
271
|
+
|
|
272
|
+
render_pairs(
|
|
273
|
+
[
|
|
274
|
+
("Release", data.release_id),
|
|
275
|
+
("CLI version", data.cli_version),
|
|
276
|
+
("Installed package", data.package_version),
|
|
277
|
+
(
|
|
278
|
+
"Source commit",
|
|
279
|
+
data.source_commit or "not stamped: this is a source checkout",
|
|
280
|
+
),
|
|
281
|
+
("Protocol", data.protocol_version),
|
|
282
|
+
("ReleaseCore", data.release_core_digest),
|
|
283
|
+
("Engine", data.engine_digest),
|
|
284
|
+
("Catalog", data.catalog_digest),
|
|
285
|
+
("Introductory Climb", data.intro_climb_reference),
|
|
286
|
+
],
|
|
287
|
+
console,
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def _render_verification(data: object, console: Console) -> None:
|
|
292
|
+
if not isinstance(data, ReleaseVerificationPayload):
|
|
293
|
+
return
|
|
294
|
+
|
|
295
|
+
console.print(f"ReleaseCore: {data.release_core_digest}")
|
|
296
|
+
console.print()
|
|
297
|
+
table = Table(box=None, show_header=False, pad_edge=False, padding=(0, 2))
|
|
298
|
+
table.add_column("status", no_wrap=True)
|
|
299
|
+
table.add_column("check", no_wrap=True)
|
|
300
|
+
table.add_column("detail", overflow="fold")
|
|
301
|
+
for check in data.checks:
|
|
302
|
+
table.add_row(check.status.upper(), check.id, check.detail)
|
|
303
|
+
console.print(table)
|