pgc-runtime 2.0.0__tar.gz → 2.0.1__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 (34) hide show
  1. {pgc_runtime-2.0.0/pgc_runtime.egg-info → pgc_runtime-2.0.1}/PKG-INFO +1 -1
  2. {pgc_runtime-2.0.0 → pgc_runtime-2.0.1/pgc_runtime.egg-info}/PKG-INFO +1 -1
  3. {pgc_runtime-2.0.0 → pgc_runtime-2.0.1}/pyproject.toml +1 -1
  4. {pgc_runtime-2.0.0 → pgc_runtime-2.0.1}/runtime/ct_executor.py +50 -2
  5. {pgc_runtime-2.0.0 → pgc_runtime-2.0.1}/runtime/dispatcher.py +119 -10
  6. {pgc_runtime-2.0.0 → pgc_runtime-2.0.1}/LICENSE +0 -0
  7. {pgc_runtime-2.0.0 → pgc_runtime-2.0.1}/MANIFEST.in +0 -0
  8. {pgc_runtime-2.0.0 → pgc_runtime-2.0.1}/NOTICE +0 -0
  9. {pgc_runtime-2.0.0 → pgc_runtime-2.0.1}/README.md +0 -0
  10. {pgc_runtime-2.0.0 → pgc_runtime-2.0.1}/pgc_runtime.egg-info/SOURCES.txt +0 -0
  11. {pgc_runtime-2.0.0 → pgc_runtime-2.0.1}/pgc_runtime.egg-info/dependency_links.txt +0 -0
  12. {pgc_runtime-2.0.0 → pgc_runtime-2.0.1}/pgc_runtime.egg-info/entry_points.txt +0 -0
  13. {pgc_runtime-2.0.0 → pgc_runtime-2.0.1}/pgc_runtime.egg-info/requires.txt +0 -0
  14. {pgc_runtime-2.0.0 → pgc_runtime-2.0.1}/pgc_runtime.egg-info/top_level.txt +0 -0
  15. {pgc_runtime-2.0.0 → pgc_runtime-2.0.1}/runtime/__init__.py +0 -0
  16. {pgc_runtime-2.0.0 → pgc_runtime-2.0.1}/runtime/api.py +0 -0
  17. {pgc_runtime-2.0.0 → pgc_runtime-2.0.1}/runtime/boot.py +0 -0
  18. {pgc_runtime-2.0.0 → pgc_runtime-2.0.1}/runtime/cli.py +0 -0
  19. {pgc_runtime-2.0.0 → pgc_runtime-2.0.1}/runtime/conformance.py +0 -0
  20. {pgc_runtime-2.0.0 → pgc_runtime-2.0.1}/runtime/ct_errors.py +0 -0
  21. {pgc_runtime-2.0.0 → pgc_runtime-2.0.1}/runtime/ct_execute.py +0 -0
  22. {pgc_runtime-2.0.0 → pgc_runtime-2.0.1}/runtime/evidence.py +0 -0
  23. {pgc_runtime-2.0.0 → pgc_runtime-2.0.1}/runtime/examine/__init__.py +0 -0
  24. {pgc_runtime-2.0.0 → pgc_runtime-2.0.1}/runtime/examine/classifier.py +0 -0
  25. {pgc_runtime-2.0.0 → pgc_runtime-2.0.1}/runtime/examine/cli.py +0 -0
  26. {pgc_runtime-2.0.0 → pgc_runtime-2.0.1}/runtime/examine/hint_engine.py +0 -0
  27. {pgc_runtime-2.0.0 → pgc_runtime-2.0.1}/runtime/examine/locator.py +0 -0
  28. {pgc_runtime-2.0.0 → pgc_runtime-2.0.1}/runtime/examine/parser.py +0 -0
  29. {pgc_runtime-2.0.0 → pgc_runtime-2.0.1}/runtime/examine/reporter.py +0 -0
  30. {pgc_runtime-2.0.0 → pgc_runtime-2.0.1}/runtime/loader.py +0 -0
  31. {pgc_runtime-2.0.0 → pgc_runtime-2.0.1}/runtime/memory.py +0 -0
  32. {pgc_runtime-2.0.0 → pgc_runtime-2.0.1}/runtime/scheduler.py +0 -0
  33. {pgc_runtime-2.0.0 → pgc_runtime-2.0.1}/runtime/trace_viz.py +0 -0
  34. {pgc_runtime-2.0.0 → pgc_runtime-2.0.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pgc-runtime
3
- Version: 2.0.0
3
+ Version: 2.0.1
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pgc-runtime
3
- Version: 2.0.0
3
+ Version: 2.0.1
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pgc-runtime"
7
- version = "2.0.0"
7
+ version = "2.0.1"
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"
@@ -17,6 +17,22 @@ class CTExecutionError(StructuredError):
17
17
  )
18
18
 
19
19
 
20
+ class CTArtifactNotFound(StructuredError):
21
+ """The sealed handler_ref names something that is not present.
22
+
23
+ Distinct from CT_EXECUTION_FAILED: nothing was executed and nothing could be. The snapshot
24
+ named a module or callable, and resolution of that name failed.
25
+ """
26
+
27
+ def __init__(self, message: str, cause: Exception | None = None):
28
+ super().__init__(
29
+ error_code="CT_ARTIFACT_NOT_FOUND",
30
+ node_category="CT",
31
+ message=message,
32
+ cause=cause,
33
+ )
34
+
35
+
20
36
  class CTExecutor:
21
37
  def __init__(self):
22
38
  pass
@@ -83,8 +99,40 @@ class CTExecutor:
83
99
  if not module_path or not callable_name:
84
100
  raise CTExecutionError(f"Incomplete handler_ref on step: {step.get('atom')}")
85
101
 
86
- mod = importlib.import_module(module_path)
87
- execute_fn = getattr(mod, callable_name)
102
+ # Importing a sealed handler_ref is a resolution step, and it fails in two ways that mean
103
+ # different things. The module named by the snapshot may be absent — a closure failure. Or
104
+ # the module may be present and one of *its* imports absent, which is what happens when a
105
+ # domain's optional dependency is not installed. Neither may escape as a bare
106
+ # ModuleNotFoundError: an ungoverned crash is not a declared outcome.
107
+ try:
108
+ mod = importlib.import_module(module_path)
109
+ except ModuleNotFoundError as exc:
110
+ missing = exc.name or ""
111
+ if missing == module_path or module_path.startswith(missing + "."):
112
+ raise CTArtifactNotFound(
113
+ f"handler_ref names module {module_path!r} for atom "
114
+ f"{step.get('atom')!r} and it is not importable",
115
+ cause=exc,
116
+ ) from exc
117
+ raise CTExecutionError(
118
+ f"handler_ref module {module_path!r} for atom {step.get('atom')!r} requires "
119
+ f"{missing!r}, which is not installed — the domain's optional dependency is "
120
+ f"missing, not the transform"
121
+ ) from exc
122
+ except ImportError as exc:
123
+ raise CTExecutionError(
124
+ f"handler_ref module {module_path!r} for atom {step.get('atom')!r} "
125
+ f"failed to import: {exc}"
126
+ ) from exc
127
+
128
+ try:
129
+ execute_fn = getattr(mod, callable_name)
130
+ except AttributeError as exc:
131
+ raise CTArtifactNotFound(
132
+ f"handler_ref names callable {callable_name!r} in {module_path!r} for atom "
133
+ f"{step.get('atom')!r} and the module does not define it",
134
+ cause=exc,
135
+ ) from exc
88
136
 
89
137
  # Adapter logic (migrated from atom_registry._register_execute_atom):
90
138
  # Resolve $.path references; skip reserved and metadata keys.
@@ -48,6 +48,41 @@ from typing import Any
48
48
  from runtime.loader import RuntimePackage
49
49
  from runtime.evidence import TraceWriter
50
50
  from runtime.ct_execute import execute_ct
51
+ from runtime.ct_errors import StructuredError
52
+
53
+
54
+ def _violation_payload(exc: StructuredError) -> dict[str, Any]:
55
+ """A structured refusal, rendered into the shape a step result carries.
56
+
57
+ Refusals reach the workflow as a VIOLATION result and route on it — the same channel the reach
58
+ refusal uses. An exception escaping the dispatcher would bypass routing and the trace both.
59
+ """
60
+ return {
61
+ "result_status": "VIOLATION",
62
+ "refusal": exc.error_code,
63
+ "node_category": exc.node_category,
64
+ "message": str(exc),
65
+ }
66
+
67
+
68
+ class CSExecutionError(StructuredError):
69
+ """A CS step could not be executed.
70
+
71
+ `CS_EXECUTION_FAILED` is the only CS code the trace schema admits, so the distinction between
72
+ an absent handler, an absent callable, a missing optional dependency and a failing capability
73
+ is carried in the message rather than in a code this snapshot does not declare.
74
+
75
+ Distinct from a VIOLATION return: a violation is governance deciding no, and it routes through
76
+ the workflow. This is the environment being unable to run the step at all.
77
+ """
78
+
79
+ def __init__(self, message: str, cause: Exception | None = None):
80
+ super().__init__(
81
+ error_code="CS_EXECUTION_FAILED",
82
+ node_category="CS",
83
+ message=message,
84
+ cause=cause,
85
+ )
51
86
 
52
87
 
53
88
  # ---------------------------------------------------------------------------
@@ -112,9 +147,14 @@ def execute_cc(
112
147
  result_status, raw_result = _execute_ct_step(step_addr, resolved_inputs, pkg)
113
148
  else:
114
149
  # CS step — controlled side effect via declared handler
115
- result_status, raw_result = _execute_cs_step(
116
- step_addr, op, resolved_inputs, rb_addr, pkg, data_root, wf_executor, wf_addr
117
- )
150
+ try:
151
+ result_status, raw_result = _execute_cs_step(
152
+ step_addr, op, resolved_inputs, rb_addr, pkg, data_root, wf_executor, wf_addr
153
+ )
154
+ except StructuredError as exc:
155
+ # Symmetry with the CT branch: a CS that cannot be loaded or that raises is a
156
+ # VIOLATION the workflow routes on, not a traceback the caller receives.
157
+ result_status, raw_result = "VIOLATION", _violation_payload(exc)
118
158
 
119
159
  # Apply outputs mapping: {cc_field: "$.capability_result.<ct_field>"} → surface fragment
120
160
  surface_fragment = _apply_outputs(outputs_spec, raw_result, step_results)
@@ -181,9 +221,19 @@ def _execute_ct_step(
181
221
  try:
182
222
  raw_result = execute_ct(ct_ir, resolved_inputs)
183
223
  return "SUCCESS", (raw_result if isinstance(raw_result, dict) else {})
184
- except Exception:
185
- # CT exception → protocol VIOLATION; do not propagate
186
- return "VIOLATION", {}
224
+ except StructuredError as exc:
225
+ # CT refusal → protocol VIOLATION, carrying what was refused. Returning a bare {} here
226
+ # discarded the only account of why the step failed, which left an operator with a status
227
+ # and no cause.
228
+ return "VIOLATION", _violation_payload(exc)
229
+ except Exception as exc:
230
+ # An unstructured exception from a transform is still a VIOLATION and still must not
231
+ # propagate, but it is named rather than swallowed.
232
+ return "VIOLATION", {
233
+ "result_status": "VIOLATION",
234
+ "refusal": "CT_EXECUTION_FAILED",
235
+ "message": f"{type(exc).__name__}: {exc}",
236
+ }
187
237
 
188
238
 
189
239
  def _make_workflow_executor(
@@ -283,9 +333,57 @@ def _execute_cs_step(
283
333
  cs_metadata = cs_entry.get("cs_metadata", {})
284
334
  cs_fqdn = pkg.vocab.fqdn(cs_addr)
285
335
 
286
- mod = importlib.import_module(handler_ref["module"])
287
- cls = getattr(mod, handler_ref["callable"])
288
- runtime = cls(config=policy, metadata=cs_metadata, capability_code=cs_fqdn)
336
+ # Loading a sealed handler_ref runs code this package does not own. Every step of that —
337
+ # importing the module, finding the callable, constructing it — may raise, and none of it may
338
+ # escape as a bare ModuleNotFoundError or AttributeError. An ungoverned crash is not a declared
339
+ # outcome, and the trace cannot record what it never sees.
340
+ module_path = handler_ref.get("module")
341
+ callable_name = handler_ref.get("callable")
342
+ if not module_path or not callable_name:
343
+ raise CSExecutionError(
344
+ f"incomplete handler_ref for CS {cs_fqdn}: module={module_path!r} "
345
+ f"callable={callable_name!r}"
346
+ )
347
+
348
+ try:
349
+ mod = importlib.import_module(module_path)
350
+ except ModuleNotFoundError as exc:
351
+ missing = exc.name or ""
352
+ if missing == module_path or module_path.startswith(missing + "."):
353
+ raise CSExecutionError(
354
+ f"handler_ref names module {module_path!r} for CS {cs_fqdn} and it is not "
355
+ f"importable — the snapshot names an implementation this environment does not carry",
356
+ cause=exc,
357
+ ) from exc
358
+ raise CSExecutionError(
359
+ f"handler_ref module {module_path!r} for CS {cs_fqdn} requires {missing!r}, which is "
360
+ f"not installed — the domain's optional dependency is missing, not the capability",
361
+ cause=exc,
362
+ ) from exc
363
+ except ImportError as exc:
364
+ raise CSExecutionError(
365
+ f"handler_ref module {module_path!r} for CS {cs_fqdn} failed to import: {exc}",
366
+ cause=exc,
367
+ ) from exc
368
+
369
+ try:
370
+ cls = getattr(mod, callable_name)
371
+ except AttributeError as exc:
372
+ raise CSExecutionError(
373
+ f"handler_ref names callable {callable_name!r} in {module_path!r} for CS {cs_fqdn} "
374
+ f"and the module does not define it",
375
+ cause=exc,
376
+ ) from exc
377
+
378
+ try:
379
+ runtime = cls(config=policy, metadata=cs_metadata, capability_code=cs_fqdn)
380
+ except StructuredError:
381
+ raise
382
+ except Exception as exc:
383
+ raise CSExecutionError(
384
+ f"CS {cs_fqdn} ({module_path}.{callable_name}) raised while being constructed: {exc}",
385
+ cause=exc,
386
+ ) from exc
289
387
 
290
388
  # Translate __store__ (compiler-emitted entity tag) to __pgs_store_entity__ (CS protocol key)
291
389
  store_entity = resolved_inputs.get("__store__")
@@ -293,7 +391,18 @@ def _execute_cs_step(
293
391
  if store_entity:
294
392
  cs_inputs["__pgs_store_entity__"] = store_entity
295
393
 
296
- raw_result = runtime.execute(op=op, payload=cs_inputs)
394
+ # The capability itself is external code. The CT path already wraps its atom call; this is the
395
+ # same boundary, and it was the one place a domain exception could still reach the caller
396
+ # unstructured.
397
+ try:
398
+ raw_result = runtime.execute(op=op, payload=cs_inputs)
399
+ except StructuredError:
400
+ raise
401
+ except Exception as exc:
402
+ raise CSExecutionError(
403
+ f"CS {cs_fqdn} raised during op {op!r}: {type(exc).__name__}: {exc}",
404
+ cause=exc,
405
+ ) from exc
297
406
  if not isinstance(raw_result, dict):
298
407
  raw_result = {}
299
408
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes