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.
Files changed (174) hide show
  1. techtree/__init__.py +35 -0
  2. techtree/__main__.py +14 -0
  3. techtree/canonical.py +239 -0
  4. techtree/catalog/__init__.py +25 -0
  5. techtree/catalog/repository.py +400 -0
  6. techtree/catalog/service.py +419 -0
  7. techtree/cli/__init__.py +1 -0
  8. techtree/cli/app.py +416 -0
  9. techtree/cli/commands/__init__.py +1 -0
  10. techtree/cli/commands/climb.py +1223 -0
  11. techtree/cli/commands/doctor.py +147 -0
  12. techtree/cli/commands/engine.py +207 -0
  13. techtree/cli/commands/proof.py +556 -0
  14. techtree/cli/commands/publish.py +447 -0
  15. techtree/cli/commands/release.py +303 -0
  16. techtree/cli/commands/run.py +1067 -0
  17. techtree/cli/commands/setup.py +181 -0
  18. techtree/cli/commands/skill.py +221 -0
  19. techtree/cli/commands/uplift.py +698 -0
  20. techtree/cli/commands/withdraw.py +212 -0
  21. techtree/cli/confirm.py +47 -0
  22. techtree/cli/context.py +96 -0
  23. techtree/cli/invoke.py +220 -0
  24. techtree/cli/output.py +280 -0
  25. techtree/constants.py +138 -0
  26. techtree/crypto.py +128 -0
  27. techtree/doctor/__init__.py +1 -0
  28. techtree/doctor/checks.py +675 -0
  29. techtree/doctor/execution_checks.py +435 -0
  30. techtree/doctor/service.py +326 -0
  31. techtree/drafts/__init__.py +32 -0
  32. techtree/drafts/source.py +146 -0
  33. techtree/drafts/store.py +992 -0
  34. techtree/engines/__init__.py +1 -0
  35. techtree/engines/bundle.py +251 -0
  36. techtree/engines/installer.py +679 -0
  37. techtree/engines/registry.py +235 -0
  38. techtree/engines/runner.py +170 -0
  39. techtree/errors.py +262 -0
  40. techtree/fs.py +234 -0
  41. techtree/harness.py +108 -0
  42. techtree/identity/__init__.py +41 -0
  43. techtree/identity/models.py +113 -0
  44. techtree/identity/service.py +199 -0
  45. techtree/identity/store.py +263 -0
  46. techtree/ids.py +85 -0
  47. techtree/manifests/__init__.py +39 -0
  48. techtree/manifests/builder.py +433 -0
  49. techtree/manifests/compare.py +376 -0
  50. techtree/models/__init__.py +282 -0
  51. techtree/models/base.py +201 -0
  52. techtree/models/campaign.py +484 -0
  53. techtree/models/catalog.py +227 -0
  54. techtree/models/cli.py +151 -0
  55. techtree/models/climb.py +254 -0
  56. techtree/models/data_policy.py +130 -0
  57. techtree/models/engine.py +156 -0
  58. techtree/models/episode_receipt.py +130 -0
  59. techtree/models/evaluation_backend.py +113 -0
  60. techtree/models/experiment.py +154 -0
  61. techtree/models/run.py +214 -0
  62. techtree/models/skill.py +156 -0
  63. techtree/models/uplift_report.py +158 -0
  64. techtree/models/validation.py +299 -0
  65. techtree/paths.py +116 -0
  66. techtree/presentation/__init__.py +31 -0
  67. techtree/presentation/build.py +1242 -0
  68. techtree/presentation/compact.py +246 -0
  69. techtree/presentation/evidence.py +169 -0
  70. techtree/presentation/models.py +358 -0
  71. techtree/presentation/rich.py +312 -0
  72. techtree/presentation/sanitize.py +156 -0
  73. techtree/publication/__init__.py +44 -0
  74. techtree/publication/address.py +180 -0
  75. techtree/publication/coordinates.py +26 -0
  76. techtree/publication/journal.py +212 -0
  77. techtree/publication/keccak.py +183 -0
  78. techtree/publication/models.py +209 -0
  79. techtree/publication/offer.py +35 -0
  80. techtree/publication/service.py +618 -0
  81. techtree/publication/transport.py +296 -0
  82. techtree/publication/verify.py +242 -0
  83. techtree/publication/withdraw.py +156 -0
  84. techtree/py.typed +0 -0
  85. techtree/receipts/__init__.py +52 -0
  86. techtree/receipts/bundle.py +578 -0
  87. techtree/receipts/compare.py +1065 -0
  88. techtree/receipts/episode.py +672 -0
  89. techtree/receipts/execution.py +630 -0
  90. techtree/receipts/observed.py +474 -0
  91. techtree/receipts/set.py +336 -0
  92. techtree/receipts/uplift.py +655 -0
  93. techtree/receipts/verify.py +1055 -0
  94. techtree/release/__init__.py +9 -0
  95. techtree/release/bootstrap.py +509 -0
  96. techtree/release/checks.py +376 -0
  97. techtree/release/document.py +125 -0
  98. techtree/release/generate.py +221 -0
  99. techtree/release/models.py +293 -0
  100. techtree/release/provenance.py +109 -0
  101. techtree/resources/catalog/campaigns/hello-world-climb.json +1 -0
  102. techtree/resources/catalog/catalog.json +32 -0
  103. techtree/resources/catalog/climbs/hello-world-climb.json +1 -0
  104. techtree/resources/catalog/data-policies/hello-world-climb.json +1 -0
  105. techtree/resources/catalog/taskset-validations/hello-world-climb.json +1 -0
  106. techtree/resources/catalog/validation-evidence/hello-world-climb.json +1 -0
  107. techtree/resources/engines/default/engine.json +20 -0
  108. techtree/resources/engines/default/packages/procedure-transfer-v1/procedure_transfer_v1/__init__.py +7 -0
  109. techtree/resources/engines/default/packages/procedure-transfer-v1/procedure_transfer_v1/algorithm.py +136 -0
  110. techtree/resources/engines/default/packages/procedure-transfer-v1/procedure_transfer_v1/dataset.py +156 -0
  111. techtree/resources/engines/default/packages/procedure-transfer-v1/procedure_transfer_v1/env.py +48 -0
  112. techtree/resources/engines/default/packages/procedure-transfer-v1/procedure_transfer_v1/taskset.py +163 -0
  113. techtree/resources/engines/default/packages/procedure-transfer-v1/pyproject.toml +13 -0
  114. techtree/resources/engines/default/pyproject.toml +23 -0
  115. techtree/resources/engines/default/tools/inspect_taskset.py +124 -0
  116. techtree/resources/engines/default/tools/normalize_eval_output.py +470 -0
  117. techtree/resources/engines/default/tools/normalize_validation.py +222 -0
  118. techtree/resources/engines/default/uv.lock +1758 -0
  119. techtree/resources/harness/hermes-agent-0.19.0.json +69 -0
  120. techtree/resources/release/build-provenance.json +4 -0
  121. techtree/resources/release/release-core.json +24 -0
  122. techtree/runs/__init__.py +31 -0
  123. techtree/runs/artifacts.py +750 -0
  124. techtree/runs/child_registry.py +228 -0
  125. techtree/runs/events.py +478 -0
  126. techtree/runs/executor.py +140 -0
  127. techtree/runs/fake.py +741 -0
  128. techtree/runs/launcher.py +253 -0
  129. techtree/runs/machine.py +489 -0
  130. techtree/runs/real.py +789 -0
  131. techtree/runs/service.py +616 -0
  132. techtree/runs/store.py +555 -0
  133. techtree/runs/validation.py +259 -0
  134. techtree/runs/variants.py +684 -0
  135. techtree/settings.py +143 -0
  136. techtree/skills/__init__.py +14 -0
  137. techtree/skills/archive.py +282 -0
  138. techtree/skills/policy.py +62 -0
  139. techtree/skills/scanner.py +394 -0
  140. techtree/skills/service.py +752 -0
  141. techtree/skills/starter.py +434 -0
  142. techtree/tasksets/__init__.py +1 -0
  143. techtree/tasksets/membership.py +269 -0
  144. techtree/tasksets/provider.py +207 -0
  145. techtree/tasksets/resolver.py +311 -0
  146. techtree/tasksets/service.py +484 -0
  147. techtree/tasksets/verifiers_cli.py +538 -0
  148. techtree/uplift/__init__.py +20 -0
  149. techtree/uplift/context.py +544 -0
  150. techtree/uplift/derive.py +203 -0
  151. techtree/uplift/public_tasks.py +151 -0
  152. techtree/uplift/service.py +719 -0
  153. techtree/uplift/source.py +160 -0
  154. techtree/verifiers/__init__.py +31 -0
  155. techtree/verifiers/budget.py +219 -0
  156. techtree/verifiers/child.py +633 -0
  157. techtree/verifiers/compiler.py +432 -0
  158. techtree/verifiers/config.py +365 -0
  159. techtree/verifiers/credentials.py +321 -0
  160. techtree/verifiers/image.py +126 -0
  161. techtree/verifiers/models.py +527 -0
  162. techtree/verifiers/outputs.py +368 -0
  163. techtree/verifiers/progress.py +192 -0
  164. techtree/verifiers/supervisor.py +341 -0
  165. techtree/verifiers/verify.py +782 -0
  166. techtree/version.py +39 -0
  167. techtree/worker/__init__.py +18 -0
  168. techtree/worker/execute.py +487 -0
  169. techtree/worker/main.py +57 -0
  170. techtree-0.1.0.dist-info/METADATA +344 -0
  171. techtree-0.1.0.dist-info/RECORD +174 -0
  172. techtree-0.1.0.dist-info/WHEEL +4 -0
  173. techtree-0.1.0.dist-info/entry_points.txt +3 -0
  174. 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)