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,203 @@
|
|
|
1
|
+
"""Turning a finished comparison into the next one. Spec section 7.19.
|
|
2
|
+
|
|
3
|
+
A first run answers "does this Skill beat no Skill?". The question after it is
|
|
4
|
+
"does this revision beat the Skill that already worked?", and spec section 3.1
|
|
5
|
+
gives that its own mutation kind rather than its own protocol: it is the same
|
|
6
|
+
Campaign with the mutation contract changed and the baseline given the Skill
|
|
7
|
+
being revised.
|
|
8
|
+
|
|
9
|
+
This module derives that Campaign, and it is written so that the derivation
|
|
10
|
+
cannot quietly become a second experiment.
|
|
11
|
+
|
|
12
|
+
*Every scientific field is copied, deeply.* Taskset, membership, validation
|
|
13
|
+
receipt, environment, model, sampling, harness, runtime, tools, scoring,
|
|
14
|
+
evidence, budgets, DataPolicy digest and OutcomeContract digest come across
|
|
15
|
+
byte-identically. A value this module computed would be a value the source run
|
|
16
|
+
never measured under, and the two reports would not be about the same taskset.
|
|
17
|
+
|
|
18
|
+
*Exactly two things change, and both are named.* The mutation contract becomes
|
|
19
|
+
``skill_replacement`` bounded at exactly one Skill, and the subject harness
|
|
20
|
+
carries Skill v1. Those two are why the derived Campaign has a different
|
|
21
|
+
digest, and they are the whole difference.
|
|
22
|
+
|
|
23
|
+
*The public wrapper does not come across.* A ``ClimbManifest`` can only require
|
|
24
|
+
``skill_insertion`` — :class:`~techtree.models.climb.CandidatePolicy` types it
|
|
25
|
+
that way — so no public Climb wraps a replacement, and the derived Campaign
|
|
26
|
+
carries no public context. Spec section 7.19 allows a separate Climb to
|
|
27
|
+
authorize one later; none exists, so none is invented.
|
|
28
|
+
|
|
29
|
+
The Skill the baseline carries is the one that was *evaluated*, identified by
|
|
30
|
+
its content address. Nothing here reads a directory, so a Skill that has been
|
|
31
|
+
edited since the first run cannot become the baseline the second run claims to
|
|
32
|
+
have measured against.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
|
|
37
|
+
from datetime import datetime
|
|
38
|
+
|
|
39
|
+
from techtree.canonical import digest_object
|
|
40
|
+
from techtree.errors import ValidationError
|
|
41
|
+
from techtree.manifests.builder import (
|
|
42
|
+
build_baseline_manifest,
|
|
43
|
+
build_candidate_manifest,
|
|
44
|
+
build_skill_reference,
|
|
45
|
+
)
|
|
46
|
+
from techtree.manifests.compare import assert_controlled_comparison, compare_manifests
|
|
47
|
+
from techtree.models.base import Digest
|
|
48
|
+
from techtree.models.campaign import (
|
|
49
|
+
SKILL_MUTATION_POINTER,
|
|
50
|
+
SUBJECT_AGENT,
|
|
51
|
+
AgentSpec,
|
|
52
|
+
CampaignSpec,
|
|
53
|
+
HarnessSpec,
|
|
54
|
+
MutationContract,
|
|
55
|
+
MutationKind,
|
|
56
|
+
)
|
|
57
|
+
from techtree.models.experiment import ExperimentManifest, ManifestComparison
|
|
58
|
+
from techtree.models.skill import SkillArtifact
|
|
59
|
+
from techtree.models.uplift_report import UpliftReport
|
|
60
|
+
|
|
61
|
+
__all__ = [
|
|
62
|
+
"REPLACEMENT_DERIVATION_FAILED",
|
|
63
|
+
"REPLACEMENT_PURPOSE",
|
|
64
|
+
"derive_replacement_manifests",
|
|
65
|
+
"derive_skill_replacement_campaign",
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
#: The one code every refusal in this module reports. Spec section 15 fixes the
|
|
69
|
+
#: vocabulary; this is the derivation's own entry in it.
|
|
70
|
+
REPLACEMENT_DERIVATION_FAILED = "replacement_derivation_failed"
|
|
71
|
+
|
|
72
|
+
#: Spec section 7.19: the purpose remains what it was. A Campaign measuring
|
|
73
|
+
#: something else is not one an improvement loop continues from, and changing
|
|
74
|
+
#: its purpose on the way through would be answering a different question under
|
|
75
|
+
#: the first question's name.
|
|
76
|
+
REPLACEMENT_PURPOSE = "component_uplift"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def derive_skill_replacement_campaign(
|
|
80
|
+
*,
|
|
81
|
+
source_campaign: CampaignSpec,
|
|
82
|
+
source_run: UpliftReport,
|
|
83
|
+
baseline_skill: SkillArtifact,
|
|
84
|
+
candidate_skill: SkillArtifact,
|
|
85
|
+
) -> CampaignSpec:
|
|
86
|
+
"""Derive the local Campaign that compares Skill v1 against Skill v2.
|
|
87
|
+
|
|
88
|
+
``source_run`` is the signed report of the run being continued from. It is
|
|
89
|
+
required to be a report *of* ``source_campaign``, so a derivation cannot
|
|
90
|
+
pair one run's evidence with another run's science.
|
|
91
|
+
"""
|
|
92
|
+
_require(
|
|
93
|
+
source_run.campaign_spec_digest == digest_object(source_campaign),
|
|
94
|
+
"this report is not a report of the Campaign it is being continued "
|
|
95
|
+
"from, so the run it describes measured something else",
|
|
96
|
+
expected=source_run.campaign_spec_digest,
|
|
97
|
+
computed=digest_object(source_campaign),
|
|
98
|
+
)
|
|
99
|
+
_require(
|
|
100
|
+
source_campaign.metadata.purpose == REPLACEMENT_PURPOSE,
|
|
101
|
+
"a Skill replacement continues a component uplift, and this Campaign's "
|
|
102
|
+
f"purpose is {source_campaign.metadata.purpose}",
|
|
103
|
+
purpose=source_campaign.metadata.purpose,
|
|
104
|
+
)
|
|
105
|
+
_require(
|
|
106
|
+
baseline_skill.root_digest != candidate_skill.root_digest,
|
|
107
|
+
"the proposed Skill has the same content tree as the Skill it would "
|
|
108
|
+
"replace, so the pair would measure nothing",
|
|
109
|
+
root_digest=baseline_skill.root_digest,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
subject = source_campaign.agents[SUBJECT_AGENT]
|
|
113
|
+
replaced = AgentSpec(
|
|
114
|
+
model=subject.model.model_copy(deep=True),
|
|
115
|
+
sampling=subject.sampling.model_copy(deep=True),
|
|
116
|
+
harness=HarnessSpec(
|
|
117
|
+
id=subject.harness.id,
|
|
118
|
+
version=subject.harness.version,
|
|
119
|
+
use_bundled_skill=subject.harness.use_bundled_skill,
|
|
120
|
+
# The baseline of a replacement is the Skill being revised, named
|
|
121
|
+
# by the content address the first run actually evaluated.
|
|
122
|
+
skills=[build_skill_reference(baseline_skill)],
|
|
123
|
+
),
|
|
124
|
+
runtime=subject.runtime.model_copy(deep=True),
|
|
125
|
+
trainable=subject.trainable,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
return CampaignSpec(
|
|
129
|
+
schema_version=source_campaign.schema_version,
|
|
130
|
+
kind=source_campaign.kind,
|
|
131
|
+
metadata=source_campaign.metadata.model_copy(deep=True),
|
|
132
|
+
context=source_campaign.context.model_copy(deep=True),
|
|
133
|
+
taskset=source_campaign.taskset.model_copy(deep=True),
|
|
134
|
+
environment=source_campaign.environment.model_copy(deep=True),
|
|
135
|
+
agents={SUBJECT_AGENT: replaced},
|
|
136
|
+
mutation_contract=MutationContract(
|
|
137
|
+
kind=MutationKind.SKILL_REPLACEMENT,
|
|
138
|
+
target_agent="subject",
|
|
139
|
+
allowed_differences=[SKILL_MUTATION_POINTER],
|
|
140
|
+
# Both sides carry exactly one Skill: there is nothing to add and
|
|
141
|
+
# nothing to remove, only one tree to swap for another.
|
|
142
|
+
minimum_skills=1,
|
|
143
|
+
maximum_skills=1,
|
|
144
|
+
),
|
|
145
|
+
evaluation_backend=source_campaign.evaluation_backend.model_copy(deep=True),
|
|
146
|
+
execution=source_campaign.execution.model_copy(deep=True),
|
|
147
|
+
scoring=source_campaign.scoring.model_copy(deep=True),
|
|
148
|
+
evidence=source_campaign.evidence.model_copy(deep=True),
|
|
149
|
+
budgets=source_campaign.budgets.model_copy(deep=True),
|
|
150
|
+
data_policy_digest=source_campaign.data_policy_digest,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def derive_replacement_manifests(
|
|
155
|
+
*,
|
|
156
|
+
campaign: CampaignSpec,
|
|
157
|
+
candidate_skill: SkillArtifact,
|
|
158
|
+
campaign_digest: Digest | None = None,
|
|
159
|
+
created_at: datetime | None = None,
|
|
160
|
+
) -> tuple[ExperimentManifest, ExperimentManifest, ManifestComparison]:
|
|
161
|
+
"""Build both variants of a replacement and require the pair to be controlled.
|
|
162
|
+
|
|
163
|
+
The baseline is built from the Campaign alone, because a replacement
|
|
164
|
+
Campaign *is* its own baseline: it carries Skill v1 in the subject harness.
|
|
165
|
+
The candidate is the same configuration with that one list replaced. Both
|
|
166
|
+
carry no public context, and the comparison is required to be controlled
|
|
167
|
+
before either is returned.
|
|
168
|
+
"""
|
|
169
|
+
_require(
|
|
170
|
+
campaign.mutation_contract.kind is MutationKind.SKILL_REPLACEMENT,
|
|
171
|
+
"these manifests would be a Skill replacement and this Campaign "
|
|
172
|
+
f"declares {campaign.mutation_contract.kind.value}",
|
|
173
|
+
mutation_kind=campaign.mutation_contract.kind.value,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
digest = campaign_digest or digest_object(campaign)
|
|
177
|
+
baseline = build_baseline_manifest(
|
|
178
|
+
campaign=campaign,
|
|
179
|
+
campaign_digest=digest,
|
|
180
|
+
public_context=None,
|
|
181
|
+
created_at=created_at,
|
|
182
|
+
)
|
|
183
|
+
candidate = build_candidate_manifest(
|
|
184
|
+
campaign=campaign,
|
|
185
|
+
campaign_digest=digest,
|
|
186
|
+
skill=candidate_skill,
|
|
187
|
+
public_context=None,
|
|
188
|
+
created_at=created_at,
|
|
189
|
+
)
|
|
190
|
+
comparison = compare_manifests(baseline, candidate, campaign.mutation_contract)
|
|
191
|
+
assert_controlled_comparison(comparison)
|
|
192
|
+
return baseline, candidate, comparison
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _require(condition: bool, message: str, **details: str) -> None:
|
|
196
|
+
"""Raise a typed refusal unless the condition holds."""
|
|
197
|
+
if condition:
|
|
198
|
+
return
|
|
199
|
+
raise ValidationError(
|
|
200
|
+
message,
|
|
201
|
+
code=REPLACEMENT_DERIVATION_FAILED,
|
|
202
|
+
details=dict(details),
|
|
203
|
+
)
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""What may be shown about a task, taskset by taskset. Decisions R1, 0014.
|
|
2
|
+
|
|
3
|
+
Ratified decision R1 lets an improvement context carry a task's *public input*
|
|
4
|
+
and forbids it carrying the subject's reply, the expected answer, grader
|
|
5
|
+
source, or any hidden field. It also says the disclosure policy is
|
|
6
|
+
taskset-specific and must never be inferred: "not secret" is not something to
|
|
7
|
+
work out from a taskset nobody wrote a policy for.
|
|
8
|
+
|
|
9
|
+
So this module is a lookup, not a rule. A taskset this build knows the policy
|
|
10
|
+
for gets a projection that names its public input; every other taskset gets
|
|
11
|
+
:func:`~techtree.uplift.context.hash_only_projection`, which names a task by
|
|
12
|
+
its position and the head of its committed hash and shows nothing else. Adding
|
|
13
|
+
a taskset here is a deliberate act, and the absence of one is a safe answer
|
|
14
|
+
rather than a missing feature.
|
|
15
|
+
|
|
16
|
+
Why it matters that the input is shown at all: the introductory Climb's whole
|
|
17
|
+
subject is a Skill with one wrong rule in it, and which tasks it fails is the
|
|
18
|
+
evidence for finding that rule. A reader given only "task 11 failed" and a hash
|
|
19
|
+
cannot see a pattern in the inputs, because it has not been shown any. That is
|
|
20
|
+
not privacy, it is an absence of evidence, and decision 0014 records what it
|
|
21
|
+
cost: two rehearsal attempts in which the model diagnosed a defect the
|
|
22
|
+
membership does not contain.
|
|
23
|
+
|
|
24
|
+
The answer never travels, and it is worth being exact about how. What is read
|
|
25
|
+
here is ``PROVING_INPUTS``, the frozen input list the reference taskset ships.
|
|
26
|
+
The oracle that turns an input into an answer — ``branch_code`` and
|
|
27
|
+
``branch_code_number`` — lives in ``algorithm.py`` in the same package, and
|
|
28
|
+
that module *is* loaded into ``sys.modules``: ``dataset.py`` imports
|
|
29
|
+
``normalize_input`` from it, so the input list cannot be read without it. What
|
|
30
|
+
does not happen is the part that matters. Neither answer function is called,
|
|
31
|
+
no answer is computed, nothing derived from one is stored, and nothing but the
|
|
32
|
+
inputs leaves this module.
|
|
33
|
+
|
|
34
|
+
Saying "the oracle is not imported" would have been the more comfortable
|
|
35
|
+
sentence and it would have been false, which is the worse of the two
|
|
36
|
+
properties a self-declaration can have.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
from __future__ import annotations
|
|
40
|
+
|
|
41
|
+
import importlib.util
|
|
42
|
+
import sys
|
|
43
|
+
from typing import Final
|
|
44
|
+
|
|
45
|
+
from techtree.engines.bundle import embedded_engine_root
|
|
46
|
+
from techtree.errors import ValidationError
|
|
47
|
+
from techtree.models.base import Digest
|
|
48
|
+
from techtree.models.campaign import CampaignSpec
|
|
49
|
+
from techtree.uplift.context import (
|
|
50
|
+
TaskPublicProjection,
|
|
51
|
+
TaskPublicProjectionProvider,
|
|
52
|
+
hash_only_projection,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
__all__ = [
|
|
56
|
+
"public_projection_for",
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
#: The one taskset this build has a disclosure policy for. Spec section 22:
|
|
60
|
+
#: its inputs are single lowercase common tree names, its prompt template is
|
|
61
|
+
#: published verbatim, and its answers are the only part that is withheld.
|
|
62
|
+
_REFERENCE_TASKSET: Final = "procedure-transfer-v1"
|
|
63
|
+
|
|
64
|
+
#: Where the frozen input list lives inside the packaged engine bundle. The
|
|
65
|
+
#: module beside it computes answers and is deliberately not touched.
|
|
66
|
+
_DATASET_MODULE: Final = "dataset.py"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def public_projection_for(campaign: CampaignSpec) -> TaskPublicProjectionProvider:
|
|
70
|
+
"""Return how much of each task this Campaign's taskset may show.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
campaign: The Campaign whose committed membership is being projected.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
A provider naming each task's public input when the taskset has a
|
|
77
|
+
disclosure policy here, and the hash-only provider when it has none.
|
|
78
|
+
"""
|
|
79
|
+
reference = campaign.taskset.ref
|
|
80
|
+
if reference.id != _REFERENCE_TASKSET or reference.package.name != (
|
|
81
|
+
_REFERENCE_TASKSET
|
|
82
|
+
):
|
|
83
|
+
return hash_only_projection
|
|
84
|
+
return _branch_code_projection(campaign)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _branch_code_projection(campaign: CampaignSpec) -> TaskPublicProjectionProvider:
|
|
88
|
+
"""Name each committed task by the public input the subject was given."""
|
|
89
|
+
committed = list(campaign.taskset.membership.ordered_task_hashes)
|
|
90
|
+
inputs = _proving_inputs()
|
|
91
|
+
|
|
92
|
+
def project(*, task_hash: Digest, position: int) -> TaskPublicProjection:
|
|
93
|
+
# The position and the hash have to agree before either is trusted to
|
|
94
|
+
# pick an input. They come from the same receipt, so a disagreement
|
|
95
|
+
# means the receipt and the Campaign are describing different runs, and
|
|
96
|
+
# labelling a task with another task's input would be worse than
|
|
97
|
+
# showing nothing.
|
|
98
|
+
if (
|
|
99
|
+
position >= len(committed)
|
|
100
|
+
or position >= len(inputs)
|
|
101
|
+
or committed[position] != task_hash
|
|
102
|
+
):
|
|
103
|
+
return hash_only_projection(task_hash=task_hash, position=position)
|
|
104
|
+
named = hash_only_projection(task_hash=task_hash, position=position)
|
|
105
|
+
return TaskPublicProjection(
|
|
106
|
+
task_label=named.task_label,
|
|
107
|
+
public_prompt=inputs[position],
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
return project
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _proving_inputs() -> tuple[str, ...]:
|
|
114
|
+
"""Read the reference taskset's frozen public inputs, and nothing else.
|
|
115
|
+
|
|
116
|
+
The module is loaded from the packaged bundle by path rather than
|
|
117
|
+
imported by name: the package's ``__init__`` pulls in Verifiers, which
|
|
118
|
+
belongs to the managed engine environment and is not resolvable here. Only
|
|
119
|
+
the pure input list is wanted, and it has no dependency of its own beyond
|
|
120
|
+
the normalizer in the same package.
|
|
121
|
+
"""
|
|
122
|
+
package = (
|
|
123
|
+
embedded_engine_root()
|
|
124
|
+
/ "packages"
|
|
125
|
+
/ _REFERENCE_TASKSET
|
|
126
|
+
/ _REFERENCE_TASKSET.replace("-", "_")
|
|
127
|
+
)
|
|
128
|
+
module_name = f"{_REFERENCE_TASKSET.replace('-', '_')}.dataset"
|
|
129
|
+
cached = sys.modules.get(module_name)
|
|
130
|
+
if cached is not None:
|
|
131
|
+
return tuple(cached.PROVING_INPUTS)
|
|
132
|
+
|
|
133
|
+
algorithm_name = f"{_REFERENCE_TASKSET.replace('-', '_')}.algorithm"
|
|
134
|
+
for name, filename in (
|
|
135
|
+
(algorithm_name, "algorithm.py"),
|
|
136
|
+
(module_name, _DATASET_MODULE),
|
|
137
|
+
):
|
|
138
|
+
if name in sys.modules:
|
|
139
|
+
continue
|
|
140
|
+
location = package / filename
|
|
141
|
+
specification = importlib.util.spec_from_file_location(name, str(location))
|
|
142
|
+
if specification is None or specification.loader is None:
|
|
143
|
+
raise ValidationError(
|
|
144
|
+
"this build cannot read the reference taskset's public inputs",
|
|
145
|
+
details={"module": name},
|
|
146
|
+
)
|
|
147
|
+
module = importlib.util.module_from_spec(specification)
|
|
148
|
+
sys.modules[name] = module
|
|
149
|
+
specification.loader.exec_module(module)
|
|
150
|
+
|
|
151
|
+
return tuple(sys.modules[module_name].PROVING_INPUTS)
|