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,222 @@
1
+ """Turn raw validator output into deterministic evidence. Decisions 0003 A1.
2
+
3
+ The Verifiers validator writes four files, and none of them is reproducible.
4
+ ``results.jsonl`` is appended in completion order, so two identical runs put the
5
+ same rows in different orders. Every row carries an elapsed time. The log
6
+ carries wall-clock timestamps and the output directory is a temporary path.
7
+
8
+ The scientific claim inside all that is small and stable: for each task, in
9
+ membership order, did the gold check and the setup check pass, and why. This
10
+ helper extracts exactly that into ``validation-evidence.json``, which is the
11
+ artifact a ``TasksetValidationReceipt`` points at, ships publicly, and is
12
+ byte-compared between the publisher and a participant.
13
+
14
+ Normalization therefore drops elapsed times, timestamps, temporary paths, run
15
+ identifiers, process identifiers, hostnames, output directories, and log
16
+ prefixes, and sorts tasks by position rather than by whichever finished first.
17
+
18
+ It refuses rather than guesses. A run that did not record every task it
19
+ selected has nothing deterministic to normalize, and a run that skipped either
20
+ check is not the validation Techtree pins.
21
+
22
+ This helper lives inside the engine bundle and runs under the engine's own
23
+ interpreter (decisions document 0003 A3), which is also how it can report the
24
+ Verifiers commit that actually produced the output instead of trusting a
25
+ caller's claim about it.
26
+
27
+ Usage, from the host side::
28
+
29
+ <engine-python> <engine-root>/tools/normalize_validation.py \\
30
+ --results-dir <validate output dir> \\
31
+ --taskset-lock-digest sha256:<hex> \\
32
+ --output <path>
33
+ """
34
+
35
+ from __future__ import annotations
36
+
37
+ import argparse
38
+ import json
39
+ import re
40
+ from importlib.metadata import distribution
41
+ from pathlib import Path
42
+ from typing import Any
43
+
44
+ SCHEMA_VERSION = "techtree.validation-evidence.v1alpha1"
45
+
46
+ TECHTREE_DIGEST = re.compile(r"^sha256:[0-9a-f]{64}$")
47
+ RAW_TASK_HASH = re.compile(r"^[0-9a-f]{64}$")
48
+
49
+ #: The one method Techtree pins: both checks, on the subprocess runtime.
50
+ VALIDATION_KIND = "verifiers_validate"
51
+ VALIDATION_MODE = "all"
52
+ VALIDATION_RUNTIME = "subprocess"
53
+
54
+ #: The five outcome counts the upstream summary nests under ``outcomes``.
55
+ OUTCOME_KEYS = ("valid", "invalid", "error", "timeout", "missing")
56
+
57
+
58
+ def parse_args() -> argparse.Namespace:
59
+ """Parse the fixed helper arguments."""
60
+ parser = argparse.ArgumentParser(
61
+ description="Normalize raw Verifiers validation output.",
62
+ )
63
+ parser.add_argument("--results-dir", required=True, type=Path)
64
+ parser.add_argument("--taskset-lock-digest", required=True)
65
+ parser.add_argument("--output", required=True, type=Path)
66
+ return parser.parse_args()
67
+
68
+
69
+ def validator_revision() -> str:
70
+ """Return the Verifiers commit installed in this engine.
71
+
72
+ Read from the distribution's recorded VCS metadata, so the evidence names
73
+ the code that actually ran.
74
+ """
75
+ recorded = distribution("verifiers").read_text("direct_url.json") or "{}"
76
+ commit = json.loads(recorded).get("vcs_info", {}).get("commit_id")
77
+ if not isinstance(commit, str) or not commit:
78
+ raise SystemExit(
79
+ "the installed Verifiers distribution does not record a commit; "
80
+ "this engine was not installed from the pinned source"
81
+ )
82
+ return commit
83
+
84
+
85
+ def read_summary(results_dir: Path) -> dict[str, Any]:
86
+ """Read ``summary.json`` and check it describes the pinned method."""
87
+ document = json.loads((results_dir / "summary.json").read_text(encoding="utf-8"))
88
+
89
+ if document.get("mode") != VALIDATION_MODE:
90
+ raise SystemExit(
91
+ f"validation ran in mode {document.get('mode')!r}; Techtree pins "
92
+ f"mode {VALIDATION_MODE!r} and normalizes nothing else"
93
+ )
94
+
95
+ outcomes = document.get("outcomes")
96
+ if not isinstance(outcomes, dict):
97
+ raise SystemExit("summary.json has no outcomes object")
98
+
99
+ counts = {key: _count(outcomes, key) for key in OUTCOME_KEYS}
100
+ total = _count(document, "total")
101
+ recorded = _count(document, "recorded")
102
+
103
+ if counts["missing"] or recorded != total:
104
+ raise SystemExit(
105
+ f"validation recorded {recorded} of {total} tasks; a run that did "
106
+ "not record every task has no deterministic evidence"
107
+ )
108
+
109
+ return {"total": total, **counts}
110
+
111
+
112
+ def read_rows(results_dir: Path) -> list[dict[str, Any]]:
113
+ """Read ``results.jsonl`` into per-task records, sorted by position.
114
+
115
+ Row order in the file is completion order and varies between identical
116
+ runs, which is why position is read from the row rather than inferred from
117
+ the line number.
118
+ """
119
+ text = (results_dir / "results.jsonl").read_text(encoding="utf-8")
120
+ rows = [json.loads(line) for line in text.splitlines() if line.strip()]
121
+ records = [_task_record(row) for row in rows]
122
+ records.sort(key=lambda record: record["position"])
123
+
124
+ positions = [record["position"] for record in records]
125
+ if positions != list(range(len(records))):
126
+ raise SystemExit(
127
+ "validation rows do not cover positions 0.."
128
+ f"{len(records) - 1} exactly once: {positions}"
129
+ )
130
+ return records
131
+
132
+
133
+ def normalize(
134
+ results_dir: Path, *, taskset_lock_digest: str, revision: str
135
+ ) -> dict[str, Any]:
136
+ """Return the deterministic evidence document."""
137
+ if TECHTREE_DIGEST.fullmatch(taskset_lock_digest) is None:
138
+ raise SystemExit(
139
+ f"--taskset-lock-digest {taskset_lock_digest!r} is not a Techtree digest"
140
+ )
141
+
142
+ summary = read_summary(results_dir)
143
+ tasks = read_rows(results_dir)
144
+
145
+ if len(tasks) != summary["total"]:
146
+ raise SystemExit(
147
+ f"summary reports {summary['total']} tasks but results.jsonl holds "
148
+ f"{len(tasks)}"
149
+ )
150
+
151
+ return {
152
+ "schema_version": SCHEMA_VERSION,
153
+ "taskset_lock_digest": taskset_lock_digest,
154
+ "method": {
155
+ "kind": VALIDATION_KIND,
156
+ "mode": VALIDATION_MODE,
157
+ "runtime": VALIDATION_RUNTIME,
158
+ "validator_revision": revision,
159
+ },
160
+ "tasks": tasks,
161
+ "summary": summary,
162
+ }
163
+
164
+
165
+ def main() -> None:
166
+ """Write one deterministic JSON object to the output path."""
167
+ arguments = parse_args()
168
+ document = normalize(
169
+ Path(arguments.results_dir),
170
+ taskset_lock_digest=arguments.taskset_lock_digest,
171
+ revision=validator_revision(),
172
+ )
173
+ rendered = json.dumps(document, indent=2, sort_keys=True, ensure_ascii=False)
174
+
175
+ output = Path(arguments.output)
176
+ output.parent.mkdir(parents=True, exist_ok=True)
177
+ output.write_text(f"{rendered}\n", encoding="utf-8")
178
+
179
+
180
+ def _count(document: dict[str, Any], key: str) -> int:
181
+ value = document.get(key)
182
+ if not isinstance(value, int) or isinstance(value, bool) or value < 0:
183
+ raise SystemExit(f"summary.json field {key!r} is not a count: {value!r}")
184
+ return value
185
+
186
+
187
+ def _task_record(row: dict[str, Any]) -> dict[str, Any]:
188
+ position = row.get("task_position")
189
+ if not isinstance(position, int) or isinstance(position, bool) or position < 0:
190
+ raise SystemExit(f"validation row has no usable task_position: {position!r}")
191
+
192
+ task_key = row.get("task_key")
193
+ if not isinstance(task_key, str) or RAW_TASK_HASH.fullmatch(task_key) is None:
194
+ raise SystemExit(f"validation row at position {position} has no raw task hash")
195
+
196
+ return {
197
+ "position": position,
198
+ "task_hash": f"sha256:{task_key}",
199
+ "gold": _outcome(row, "gold", position),
200
+ "setup": _outcome(row, "setup", position),
201
+ }
202
+
203
+
204
+ def _outcome(row: dict[str, Any], check: str, position: int) -> dict[str, Any]:
205
+ nested = row.get(check)
206
+ if not isinstance(nested, dict):
207
+ raise SystemExit(
208
+ f"validation row at position {position} has no {check} check; "
209
+ f"Techtree pins mode {VALIDATION_MODE!r}, which runs both"
210
+ )
211
+
212
+ valid = nested.get("valid")
213
+ reason = nested.get("reason")
214
+ if not isinstance(valid, bool) or not isinstance(reason, str) or not reason:
215
+ raise SystemExit(
216
+ f"the {check} check at position {position} reported no verdict"
217
+ )
218
+ return {"valid": valid, "reason": reason}
219
+
220
+
221
+ if __name__ == "__main__":
222
+ main()