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