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,140 @@
1
+ """What executes a run, and where it may be interrupted. Spec PR8 §8.5.
2
+
3
+ :class:`RunExecutor` is the seam the whole slice exists to establish. A future
4
+ real executor — Verifiers evaluation, a Hermes harness, a Docker subject —
5
+ implements this one method and inherits the entire lifecycle around it: the
6
+ request, the staged inputs, the validation provider, the event journal, the
7
+ cancellation boundaries, and the CLI that watches all of it. Only the body
8
+ changes.
9
+
10
+ Cancellation is cooperative and deliberately so. A worker that could be killed
11
+ mid-write would leave a run whose journal and artifacts disagreed, so the CLI
12
+ never kills it: it appends ``cancel.requested`` and signals the process, and
13
+ the executor notices at a boundary of its own choosing and unwinds. The two
14
+ things it notices are here.
15
+
16
+ :func:`raise_if_cancel_requested` reads the run's projected phase, which is how
17
+ a cancellation requested by a *different process* reaches this one.
18
+ :func:`request_local_cancellation` is the other direction: a signal handler in
19
+ this process cannot safely read a file or take a lock, so it sets a flag that
20
+ the same check consults. Either is enough to stop the run, and both funnel into
21
+ one :class:`~techtree.errors.CancellationError` so that no executor has to know
22
+ which of them happened.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import threading
28
+ import time
29
+ from collections.abc import Callable
30
+ from dataclasses import dataclass
31
+ from datetime import datetime
32
+ from typing import Final, Protocol
33
+
34
+ from techtree.errors import CancellationError
35
+ from techtree.models.run import RunPhase, RunRequest
36
+ from techtree.models.uplift_report import UpliftReport
37
+ from techtree.runs.artifacts import RunArtifactStore
38
+ from techtree.runs.store import RunStore
39
+ from techtree.runs.validation import TasksetValidationProvider
40
+
41
+ __all__ = [
42
+ "DEFAULT_CANCELLATION_QUANTUM_SECONDS",
43
+ "ExecutionContext",
44
+ "RunExecutor",
45
+ "clear_local_cancellation",
46
+ "interruptible_sleep",
47
+ "local_cancellation_requested",
48
+ "raise_if_cancel_requested",
49
+ "request_local_cancellation",
50
+ ]
51
+
52
+ #: How often an interruptible delay looks up from what it is doing. Short
53
+ #: enough that a cancelled run stops promptly, long enough that waiting is not
54
+ #: itself the work.
55
+ DEFAULT_CANCELLATION_QUANTUM_SECONDS: Final = 0.05
56
+
57
+ #: Set by this process's signal handlers. Process-wide rather than passed
58
+ #: around because a signal arrives at the process, not at an object, and the
59
+ #: handler must do nothing but set it.
60
+ _LOCAL_CANCELLATION: Final[threading.Event] = threading.Event()
61
+
62
+
63
+ @dataclass(frozen=True)
64
+ class ExecutionContext:
65
+ """Everything one execution of one run is given."""
66
+
67
+ request: RunRequest
68
+ run_store: RunStore
69
+ artifact_store: RunArtifactStore
70
+ validation_provider: TasksetValidationProvider
71
+ clock: Callable[[], datetime]
72
+
73
+
74
+ class RunExecutor(Protocol):
75
+ """Executes one run to a terminal scientific artifact."""
76
+
77
+ def execute(self, context: ExecutionContext) -> UpliftReport:
78
+ """Execute one run and return the report it produced."""
79
+ ...
80
+
81
+
82
+ def request_local_cancellation() -> None:
83
+ """Record that this process has been asked to stop.
84
+
85
+ Safe to call from a signal handler: it sets an event and returns.
86
+ """
87
+ _LOCAL_CANCELLATION.set()
88
+
89
+
90
+ def clear_local_cancellation() -> None:
91
+ """Forget any local cancellation request.
92
+
93
+ A worker process handles exactly one run, so this exists for tests, which
94
+ share one process across many runs.
95
+ """
96
+ _LOCAL_CANCELLATION.clear()
97
+
98
+
99
+ def local_cancellation_requested() -> bool:
100
+ """Return whether this process has been signalled to stop."""
101
+ return _LOCAL_CANCELLATION.is_set()
102
+
103
+
104
+ def raise_if_cancel_requested(run_store: RunStore, run_id: str) -> None:
105
+ """Raise when this run has been asked to stop, from anywhere."""
106
+ if local_cancellation_requested():
107
+ raise CancellationError(
108
+ f"run {run_id} was signalled to stop",
109
+ details={"run_id": run_id, "requested_by": "signal"},
110
+ )
111
+ if run_store.state(run_id).phase is RunPhase.CANCEL_REQUESTED:
112
+ raise CancellationError(
113
+ f"run {run_id} was asked to stop",
114
+ details={"run_id": run_id, "requested_by": "run_journal"},
115
+ )
116
+
117
+
118
+ def interruptible_sleep(
119
+ *,
120
+ seconds: float,
121
+ check: Callable[[], None],
122
+ quantum: float = DEFAULT_CANCELLATION_QUANTUM_SECONDS,
123
+ ) -> None:
124
+ """Wait, looking up every ``quantum`` seconds to see whether to stop.
125
+
126
+ ``check`` is called before the first wait and after every one of them, so
127
+ a delay of zero still costs one check and a cancellation never has to wait
128
+ out a whole interval to be noticed.
129
+ """
130
+ if quantum <= 0:
131
+ raise ValueError("an interruptible delay needs a positive quantum")
132
+
133
+ check()
134
+ deadline = time.monotonic() + max(seconds, 0.0)
135
+ while True:
136
+ remaining = deadline - time.monotonic()
137
+ if remaining <= 0:
138
+ return
139
+ time.sleep(min(quantum, remaining))
140
+ check()