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,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()
|