pgc-runtime 5.0.0__tar.gz → 6.0.0__tar.gz

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 (39) hide show
  1. {pgc_runtime-5.0.0 → pgc_runtime-6.0.0}/NOTICE +1 -1
  2. {pgc_runtime-5.0.0/pgc_runtime.egg-info → pgc_runtime-6.0.0}/PKG-INFO +13 -12
  3. {pgc_runtime-5.0.0 → pgc_runtime-6.0.0}/README.md +11 -10
  4. {pgc_runtime-5.0.0 → pgc_runtime-6.0.0/pgc_runtime.egg-info}/PKG-INFO +13 -12
  5. {pgc_runtime-5.0.0 → pgc_runtime-6.0.0}/pgc_runtime.egg-info/requires.txt +1 -1
  6. {pgc_runtime-5.0.0 → pgc_runtime-6.0.0}/pyproject.toml +2 -2
  7. {pgc_runtime-5.0.0 → pgc_runtime-6.0.0}/runtime/api.py +4 -1
  8. {pgc_runtime-5.0.0 → pgc_runtime-6.0.0}/runtime/boot.py +3 -3
  9. {pgc_runtime-5.0.0 → pgc_runtime-6.0.0}/runtime/conformance.py +34 -2
  10. {pgc_runtime-5.0.0 → pgc_runtime-6.0.0}/runtime/coordinator.py +1 -1
  11. {pgc_runtime-5.0.0 → pgc_runtime-6.0.0}/runtime/ct_execute.py +3 -12
  12. {pgc_runtime-5.0.0 → pgc_runtime-6.0.0}/runtime/ct_executor.py +89 -116
  13. {pgc_runtime-5.0.0 → pgc_runtime-6.0.0}/runtime/dispatcher.py +198 -87
  14. {pgc_runtime-5.0.0 → pgc_runtime-6.0.0}/runtime/evidence.py +21 -5
  15. {pgc_runtime-5.0.0 → pgc_runtime-6.0.0}/runtime/examine/__init__.py +1 -1
  16. {pgc_runtime-5.0.0 → pgc_runtime-6.0.0}/runtime/examine/parser.py +3 -4
  17. {pgc_runtime-5.0.0 → pgc_runtime-6.0.0}/runtime/federation/__init__.py +1 -1
  18. {pgc_runtime-5.0.0 → pgc_runtime-6.0.0}/runtime/federation/defaults.py +1 -1
  19. {pgc_runtime-5.0.0 → pgc_runtime-6.0.0}/runtime/memory.py +40 -23
  20. {pgc_runtime-5.0.0 → pgc_runtime-6.0.0}/runtime/scheduler.py +29 -11
  21. {pgc_runtime-5.0.0 → pgc_runtime-6.0.0}/LICENSE +0 -0
  22. {pgc_runtime-5.0.0 → pgc_runtime-6.0.0}/MANIFEST.in +0 -0
  23. {pgc_runtime-5.0.0 → pgc_runtime-6.0.0}/pgc_runtime.egg-info/SOURCES.txt +0 -0
  24. {pgc_runtime-5.0.0 → pgc_runtime-6.0.0}/pgc_runtime.egg-info/dependency_links.txt +0 -0
  25. {pgc_runtime-5.0.0 → pgc_runtime-6.0.0}/pgc_runtime.egg-info/entry_points.txt +0 -0
  26. {pgc_runtime-5.0.0 → pgc_runtime-6.0.0}/pgc_runtime.egg-info/top_level.txt +0 -0
  27. {pgc_runtime-5.0.0 → pgc_runtime-6.0.0}/runtime/__init__.py +0 -0
  28. {pgc_runtime-5.0.0 → pgc_runtime-6.0.0}/runtime/cli.py +0 -0
  29. {pgc_runtime-5.0.0 → pgc_runtime-6.0.0}/runtime/ct_errors.py +0 -0
  30. {pgc_runtime-5.0.0 → pgc_runtime-6.0.0}/runtime/examine/cli.py +0 -0
  31. {pgc_runtime-5.0.0 → pgc_runtime-6.0.0}/runtime/examine/reporter.py +0 -0
  32. {pgc_runtime-5.0.0 → pgc_runtime-6.0.0}/runtime/federation/client.py +0 -0
  33. {pgc_runtime-5.0.0 → pgc_runtime-6.0.0}/runtime/federation/coordinator.py +0 -0
  34. {pgc_runtime-5.0.0 → pgc_runtime-6.0.0}/runtime/federation/store.py +0 -0
  35. {pgc_runtime-5.0.0 → pgc_runtime-6.0.0}/runtime/federation/worker.py +0 -0
  36. {pgc_runtime-5.0.0 → pgc_runtime-6.0.0}/runtime/loader.py +0 -0
  37. {pgc_runtime-5.0.0 → pgc_runtime-6.0.0}/runtime/replay.py +0 -0
  38. {pgc_runtime-5.0.0 → pgc_runtime-6.0.0}/runtime/trace_viz.py +0 -0
  39. {pgc_runtime-5.0.0 → pgc_runtime-6.0.0}/setup.cfg +0 -0
@@ -1,4 +1,4 @@
1
- PGS — Protocol-Governed Systems
1
+ PGC — Protocol-Governed Computing
2
2
  Copyright 2026 Bhash Ganti aka Bachi
3
3
 
4
4
  This project introduces a protocol-first execution model in which:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pgc-runtime
3
- Version: 5.0.0
3
+ Version: 6.0.0
4
4
  Summary: PGC protocol runtime — executes a sealed snapshot as governed DAG traversal and emits traces (import package: runtime)
5
5
  Author-email: Bhash Ganti <bachipeachy@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -23,7 +23,7 @@ License-File: NOTICE
23
23
  Provides-Extra: reference
24
24
  Requires-Dist: pgc-governance>=2.0.0; extra == "reference"
25
25
  Provides-Extra: render
26
- Requires-Dist: pgc-inspector>=5.0.0; extra == "render"
26
+ Requires-Dist: pgc-inspector>=6.0.0; extra == "render"
27
27
  Dynamic: license-file
28
28
 
29
29
  # protocol_runtime
@@ -85,7 +85,7 @@ the data root:
85
85
 
86
86
  ```
87
87
  <data-root>/traces/<domain>/<WF>/<TRACE_ID>/
88
- <TRACE_ID>.jsonl append-only execution log, one SCHEMA_TRACE_EVENT_V1 event per line
88
+ <TRACE_ID>.jsonl append-only execution log, one SCHEMA_TRACE_EVENT_V0 event per line
89
89
  <TRACE_ID>.png the path the run took, drawn only on request (`run --behavior-logic`,
90
90
  or `behavior-logic <trace>`)
91
91
 
@@ -172,7 +172,7 @@ Apache-2.0. See `LICENSE` and `NOTICE`.
172
172
  | `pgc-assembler` | `snapshot_assembler` | projections → sealed snapshot |
173
173
  | `pgc-runtime` | `protocol_runtime` | snapshot → governed execution |
174
174
  | `pgc-inspector` | `snapshot_inspector` | snapshot → read-only inspection |
175
- | `pgc-transformation` | `transformation` | change request → protocol artifacts |
175
+ | `pgc-transformation` | `transformation_compilers` | change request → protocol artifacts |
176
176
  | `pgc-governance` | `software_governance` | the governance surface and its capability implementations |
177
177
  | `pgc-workloads` | `conformance_workloads` | the workloads that make conformance observable |
178
178
  | `pgc-domains` | `business_domains` | the business domain implementations the composed snapshot binds |
@@ -204,15 +204,16 @@ assembler and the runtime alike.
204
204
  The full sequence, with the repositories it needs, is in
205
205
  [`pgc_install`](https://github.com/protocol-governed-computing/pgc_install).
206
206
 
207
- **Versioning.** Two schemes, and the published version follows the second.
207
+ **Versioning.** Two numbers name two different things.
208
208
 
209
- - **Internal** — each repository's `VERSION` file, a monotonic composition ordinal. PGC versions the
210
- composition rather than each repo: they release together and the governance closure forces lockstep,
211
- so the ordinal names which composition a repo belongs to. Development happens on `dev/<N>` and each
212
- cycle is tagged `release-<N>`. This is not published.
213
- - **Public** — `PUBLIC_VERSION`, tagged on every component repository. The platform is at **`v3`**.
209
+ - **Published** — this distribution's `version` in `pyproject.toml`. It moves on its own. A release
210
+ that changes this repository bumps it, and a release that leaves the repository unchanged keeps it.
211
+ - **Composition** — the public identity (`v6`, …) names one composition of the whole family.
212
+ `pgc_release` records which version of each distribution that composition holds, and `pgc_install`
213
+ pins exactly those versions.
214
214
 
215
- **The published version is the public one: `v4` is `4.0.0`.** The standard the packages implement is a
216
- separate artifact on its own track and is not this number.
215
+ Each repository's `VERSION` file holds the workspace's cycle ordinal, which names the `dev/<N>`
216
+ branch. It is development accounting, and it is never published. The standard the packages
217
+ implement is a separate artifact on its own track, and none of these numbers is its revision.
217
218
 
218
219
  The standard these packages implement is published separately: https://doi.org/10.5281/zenodo.22150616
@@ -57,7 +57,7 @@ the data root:
57
57
 
58
58
  ```
59
59
  <data-root>/traces/<domain>/<WF>/<TRACE_ID>/
60
- <TRACE_ID>.jsonl append-only execution log, one SCHEMA_TRACE_EVENT_V1 event per line
60
+ <TRACE_ID>.jsonl append-only execution log, one SCHEMA_TRACE_EVENT_V0 event per line
61
61
  <TRACE_ID>.png the path the run took, drawn only on request (`run --behavior-logic`,
62
62
  or `behavior-logic <trace>`)
63
63
 
@@ -144,7 +144,7 @@ Apache-2.0. See `LICENSE` and `NOTICE`.
144
144
  | `pgc-assembler` | `snapshot_assembler` | projections → sealed snapshot |
145
145
  | `pgc-runtime` | `protocol_runtime` | snapshot → governed execution |
146
146
  | `pgc-inspector` | `snapshot_inspector` | snapshot → read-only inspection |
147
- | `pgc-transformation` | `transformation` | change request → protocol artifacts |
147
+ | `pgc-transformation` | `transformation_compilers` | change request → protocol artifacts |
148
148
  | `pgc-governance` | `software_governance` | the governance surface and its capability implementations |
149
149
  | `pgc-workloads` | `conformance_workloads` | the workloads that make conformance observable |
150
150
  | `pgc-domains` | `business_domains` | the business domain implementations the composed snapshot binds |
@@ -176,15 +176,16 @@ assembler and the runtime alike.
176
176
  The full sequence, with the repositories it needs, is in
177
177
  [`pgc_install`](https://github.com/protocol-governed-computing/pgc_install).
178
178
 
179
- **Versioning.** Two schemes, and the published version follows the second.
179
+ **Versioning.** Two numbers name two different things.
180
180
 
181
- - **Internal** — each repository's `VERSION` file, a monotonic composition ordinal. PGC versions the
182
- composition rather than each repo: they release together and the governance closure forces lockstep,
183
- so the ordinal names which composition a repo belongs to. Development happens on `dev/<N>` and each
184
- cycle is tagged `release-<N>`. This is not published.
185
- - **Public** — `PUBLIC_VERSION`, tagged on every component repository. The platform is at **`v3`**.
181
+ - **Published** — this distribution's `version` in `pyproject.toml`. It moves on its own. A release
182
+ that changes this repository bumps it, and a release that leaves the repository unchanged keeps it.
183
+ - **Composition** — the public identity (`v6`, …) names one composition of the whole family.
184
+ `pgc_release` records which version of each distribution that composition holds, and `pgc_install`
185
+ pins exactly those versions.
186
186
 
187
- **The published version is the public one: `v4` is `4.0.0`.** The standard the packages implement is a
188
- separate artifact on its own track and is not this number.
187
+ Each repository's `VERSION` file holds the workspace's cycle ordinal, which names the `dev/<N>`
188
+ branch. It is development accounting, and it is never published. The standard the packages
189
+ implement is a separate artifact on its own track, and none of these numbers is its revision.
189
190
 
190
191
  The standard these packages implement is published separately: https://doi.org/10.5281/zenodo.22150616
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pgc-runtime
3
- Version: 5.0.0
3
+ Version: 6.0.0
4
4
  Summary: PGC protocol runtime — executes a sealed snapshot as governed DAG traversal and emits traces (import package: runtime)
5
5
  Author-email: Bhash Ganti <bachipeachy@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -23,7 +23,7 @@ License-File: NOTICE
23
23
  Provides-Extra: reference
24
24
  Requires-Dist: pgc-governance>=2.0.0; extra == "reference"
25
25
  Provides-Extra: render
26
- Requires-Dist: pgc-inspector>=5.0.0; extra == "render"
26
+ Requires-Dist: pgc-inspector>=6.0.0; extra == "render"
27
27
  Dynamic: license-file
28
28
 
29
29
  # protocol_runtime
@@ -85,7 +85,7 @@ the data root:
85
85
 
86
86
  ```
87
87
  <data-root>/traces/<domain>/<WF>/<TRACE_ID>/
88
- <TRACE_ID>.jsonl append-only execution log, one SCHEMA_TRACE_EVENT_V1 event per line
88
+ <TRACE_ID>.jsonl append-only execution log, one SCHEMA_TRACE_EVENT_V0 event per line
89
89
  <TRACE_ID>.png the path the run took, drawn only on request (`run --behavior-logic`,
90
90
  or `behavior-logic <trace>`)
91
91
 
@@ -172,7 +172,7 @@ Apache-2.0. See `LICENSE` and `NOTICE`.
172
172
  | `pgc-assembler` | `snapshot_assembler` | projections → sealed snapshot |
173
173
  | `pgc-runtime` | `protocol_runtime` | snapshot → governed execution |
174
174
  | `pgc-inspector` | `snapshot_inspector` | snapshot → read-only inspection |
175
- | `pgc-transformation` | `transformation` | change request → protocol artifacts |
175
+ | `pgc-transformation` | `transformation_compilers` | change request → protocol artifacts |
176
176
  | `pgc-governance` | `software_governance` | the governance surface and its capability implementations |
177
177
  | `pgc-workloads` | `conformance_workloads` | the workloads that make conformance observable |
178
178
  | `pgc-domains` | `business_domains` | the business domain implementations the composed snapshot binds |
@@ -204,15 +204,16 @@ assembler and the runtime alike.
204
204
  The full sequence, with the repositories it needs, is in
205
205
  [`pgc_install`](https://github.com/protocol-governed-computing/pgc_install).
206
206
 
207
- **Versioning.** Two schemes, and the published version follows the second.
207
+ **Versioning.** Two numbers name two different things.
208
208
 
209
- - **Internal** — each repository's `VERSION` file, a monotonic composition ordinal. PGC versions the
210
- composition rather than each repo: they release together and the governance closure forces lockstep,
211
- so the ordinal names which composition a repo belongs to. Development happens on `dev/<N>` and each
212
- cycle is tagged `release-<N>`. This is not published.
213
- - **Public** — `PUBLIC_VERSION`, tagged on every component repository. The platform is at **`v3`**.
209
+ - **Published** — this distribution's `version` in `pyproject.toml`. It moves on its own. A release
210
+ that changes this repository bumps it, and a release that leaves the repository unchanged keeps it.
211
+ - **Composition** — the public identity (`v6`, …) names one composition of the whole family.
212
+ `pgc_release` records which version of each distribution that composition holds, and `pgc_install`
213
+ pins exactly those versions.
214
214
 
215
- **The published version is the public one: `v4` is `4.0.0`.** The standard the packages implement is a
216
- separate artifact on its own track and is not this number.
215
+ Each repository's `VERSION` file holds the workspace's cycle ordinal, which names the `dev/<N>`
216
+ branch. It is development accounting, and it is never published. The standard the packages
217
+ implement is a separate artifact on its own track, and none of these numbers is its revision.
217
218
 
218
219
  The standard these packages implement is published separately: https://doi.org/10.5281/zenodo.22150616
@@ -3,4 +3,4 @@
3
3
  pgc-governance>=2.0.0
4
4
 
5
5
  [render]
6
- pgc-inspector>=5.0.0
6
+ pgc-inspector>=6.0.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pgc-runtime"
7
- version = "5.0.0"
7
+ version = "6.0.0"
8
8
  description = "PGC protocol runtime — executes a sealed snapshot as governed DAG traversal and emits traces (import package: runtime)"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -34,7 +34,7 @@ dependencies = []
34
34
  reference = ["pgc-governance>=2.0.0"]
35
35
  # The PNG projection (`--behavior-logic`, `behavior-logic`) draws the inspector's explanation of a
36
36
  # run. Execution never imports the inspector; only `runtime.trace_viz` does.
37
- render = ["pgc-inspector>=5.0.0"]
37
+ render = ["pgc-inspector>=6.0.0"]
38
38
 
39
39
  [project.scripts]
40
40
  protocol_runtime = "runtime.cli:main"
@@ -16,7 +16,7 @@ from pathlib import Path
16
16
  from typing import Any
17
17
 
18
18
  from runtime.boot import BootedSnapshot, boot, default_snapshot_root
19
- from runtime.evidence import TraceWriter, make_trace_id
19
+ from runtime.evidence import RecordedRefusal, TraceWriter, make_trace_id
20
20
  from runtime.scheduler import run_wf
21
21
 
22
22
 
@@ -71,6 +71,9 @@ def run_workflow(
71
71
  try:
72
72
  status, surface = run_wf(wf_fqdn=wf_fqdn, payload=payload, pkg=pkg,
73
73
  writer=writer, data_root=str(data_root))
74
+ except RecordedRefusal:
75
+ # Recorded where it was determined; recording it again here would count it twice.
76
+ raise
74
77
  except Exception as exc:
75
78
  writer.error(str(exc))
76
79
  raise
@@ -91,9 +91,9 @@ def boot(snapshot_root: str | Path | None = None) -> BootedSnapshot:
91
91
 
92
92
  # 2. ACCEPTANCE — all four conditions of `3b` §7, established from content.
93
93
  #
94
- # The runtime does not carry its own weaker copy of this. It previously recomputed the composite
95
- # over the manifest's RECORDED per-domain hashes, which detects a tampered manifest and not a
96
- # tampered constituent — and a snapshot with an edited projection booted and reported healthy.
94
+ # The runtime does not carry its own weaker copy of this. A composite over the manifest's RECORDED
95
+ # per-domain hashes detects a tampered manifest and not a tampered constituent: a snapshot with an
96
+ # edited projection would boot and report healthy.
97
97
  # `assembler.core.verify_snapshot` recomputes every constituent from its bytes; there is one
98
98
  # acceptance determination and both the assembler and the runtime reach it.
99
99
  #
@@ -6,7 +6,7 @@ the composition sealed it. This runs every case of one build and reports every t
6
6
  supplies as **proven** (every case ran and passed), **unproven** (no vector tests it) or **refused** (a
7
7
  case failed). It never reports success over nothing: a build with no vector is reported, by name, as
8
8
  unproven. The build is a domain's, or the platform's for the transforms the platform supplies.
9
- Governed by `conformance::CONSTITUTION_TEST_DATA_V2`.
9
+ Governed by `conformance::CONSTITUTION_TEST_DATA_V0`.
10
10
 
11
11
  What a build supplies and what it only carries is read from the capabilities its attestation records
12
12
  as imported, never from a transform's name: a carried copy is byte-identical to its supplier's, and the
@@ -27,7 +27,7 @@ from dataclasses import dataclass, field
27
27
  from pathlib import Path
28
28
  from typing import Any
29
29
 
30
- from runtime.ct_executor import CTArtifactNotFound, CTExecutor, CTExecutionError
30
+ from runtime.ct_executor import CTArtifactNotFound, CTExecutor, CTExecutionError, CTFault
31
31
 
32
32
  NONDETERMINISTIC_PURITY = "ct_impure"
33
33
  # Where a build's result is written, beside its compiled projections, so the assembler carries it into
@@ -201,6 +201,31 @@ def _resolve_outputs(ct_ir: dict[str, Any], vars_result: dict[str, Any]) -> dict
201
201
  return actual
202
202
 
203
203
 
204
+ def _undeclared_result(ct_ir: dict[str, Any], vars_result: dict[str, Any]) -> str | None:
205
+ """Say how a completed run's result differs from the outputs the transform declares, if it does.
206
+
207
+ A result read field by field carries exactly the outputs read from it: a key no output names is an
208
+ answer nothing declared, and a declared output it lacks is one the case could only compare against
209
+ the whole result. A result read as one output's value is that value, whatever it holds.
210
+ """
211
+ groups: dict[str, set[str]] = {}
212
+ for output_key, output_spec in (ct_ir.get("outputs") or {}).items():
213
+ groups.setdefault(output_spec.get("from"), set()).add(output_key)
214
+ for from_var, keys in sorted(groups.items(), key=lambda g: str(g[0])):
215
+ if from_var not in vars_result:
216
+ return f"no step produced {from_var!r}, which outputs {sorted(keys)} are read from"
217
+ source = vars_result[from_var]
218
+ if not isinstance(source, dict) or not keys & set(source):
219
+ if len(keys) == 1:
220
+ continue
221
+ return f"{from_var!r} carries none of the declared outputs {sorted(keys)}"
222
+ if set(source) != keys:
223
+ missing, extra = sorted(keys - set(source)), sorted(set(source) - keys)
224
+ return (f"{from_var!r} differs from the declared outputs"
225
+ f"{f' — lacks {missing}' if missing else ''}{f' — undeclared {extra}' if extra else ''}")
226
+ return None
227
+
228
+
204
229
  def _canonical(compiled: Path, kind_dir: str) -> list[dict[str, Any]]:
205
230
  folder = compiled / "canonical" / kind_dir
206
231
  return [json.loads(p.read_text()) for p in sorted(folder.glob("*.json"))] if folder.is_dir() else []
@@ -251,6 +276,10 @@ def _run_case(executor: CTExecutor, case: dict[str, Any]) -> CaseResult:
251
276
  # VIOLATION case by accident. A transform whose implementation is absent is refused and named,
252
277
  # which is a result; escaping as a crash names nothing and stops the other cases.
253
278
  return CaseResult(fqdn, False, f"implementation not present: {e}")
279
+ except CTFault as e:
280
+ # A fault is not the transform's judgement: it fails the case whatever the case expected,
281
+ # so a VIOLATION case is never passed by a transform that broke rather than refused.
282
+ return CaseResult(fqdn, False, f"fault, not a refusal: {e}")
254
283
  except CTExecutionError as e:
255
284
  # A refusal the case expected passes — unless it was the runner refusing a missing record,
256
285
  # which is a defect in the case, not the transform's judgement.
@@ -268,6 +297,9 @@ def _run_case(executor: CTExecutor, case: dict[str, Any]) -> CaseResult:
268
297
  if unused:
269
298
  return CaseResult(fqdn, False, f"recorded results no step used: {unused}")
270
299
 
300
+ undeclared = _undeclared_result(ct_ir, vars_result)
301
+ if undeclared:
302
+ return CaseResult(fqdn, False, f"result differs from the declared outputs: {undeclared}")
271
303
  actual = _resolve_outputs(ct_ir, vars_result)
272
304
  assertion_error = _assert_structural(actual, assertions) if assertions else None
273
305
  if assertion_error:
@@ -1,6 +1,6 @@
1
1
  """A coordinating party that runs several runtimes.
2
2
 
3
- `CONSTITUTION_EXECUTION_PLACEMENT_V1` authorizes `LOCAL_MULTI_WORKER`: more than one worker process
3
+ `CONSTITUTION_EXECUTION_PLACEMENT_V0` authorizes `LOCAL_MULTI_WORKER`: more than one worker process
4
4
  on one host, each drawing work from a coordinating party and executing a governed topology in its
5
5
  own process. This is that party.
6
6
 
@@ -6,8 +6,7 @@ Adapter between the dispatcher and CTExecutor.
6
6
 
7
7
  from typing import Any
8
8
 
9
- from runtime.ct_errors import StructuredError
10
- from runtime.ct_executor import CTExecutor
9
+ from runtime.ct_executor import CTExecutor, CTFault
11
10
 
12
11
  # Module-level executor singleton — avoids re-creating per call,
13
12
  # preserves federated IR directory cache and atom loading.
@@ -48,11 +47,7 @@ def execute_ct(ct_ir: dict[str, Any], inputs: dict[str, Any], observer=None, rec
48
47
  # ---- Output adaptation ----
49
48
  outputs = ct_ir.get("outputs")
50
49
  if not outputs:
51
- raise StructuredError(
52
- error_code="CT_EXECUTION_FAILED",
53
- node_category="CT",
54
- message="CT must declare at least one output",
55
- )
50
+ raise CTFault("CT must declare at least one output")
56
51
 
57
52
  # Build CT outputs by mapping from symbol table
58
53
  ct_outputs = {}
@@ -60,11 +55,7 @@ def execute_ct(ct_ir: dict[str, Any], inputs: dict[str, Any], observer=None, rec
60
55
  from_symbol = spec["from"]
61
56
 
62
57
  if from_symbol not in symbol_table:
63
- raise StructuredError(
64
- error_code="CT_EXECUTION_FAILED",
65
- node_category="CT",
66
- message=f"CT output symbol '{from_symbol}' was not produced by CT execution",
67
- )
58
+ raise CTFault(f"CT output symbol '{from_symbol}' was not produced by CT execution")
68
59
 
69
60
  symbol_value = symbol_table[from_symbol]
70
61