pgc-runtime 6.0.0__tar.gz → 7.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.
- {pgc_runtime-6.0.0/pgc_runtime.egg-info → pgc_runtime-7.0.0}/PKG-INFO +3 -8
- {pgc_runtime-6.0.0 → pgc_runtime-7.0.0}/README.md +1 -4
- {pgc_runtime-6.0.0 → pgc_runtime-7.0.0/pgc_runtime.egg-info}/PKG-INFO +3 -8
- {pgc_runtime-6.0.0 → pgc_runtime-7.0.0}/pgc_runtime.egg-info/SOURCES.txt +0 -1
- {pgc_runtime-6.0.0 → pgc_runtime-7.0.0}/pgc_runtime.egg-info/requires.txt +0 -3
- {pgc_runtime-6.0.0 → pgc_runtime-7.0.0}/pyproject.toml +2 -5
- {pgc_runtime-6.0.0 → pgc_runtime-7.0.0}/runtime/api.py +2 -2
- {pgc_runtime-6.0.0 → pgc_runtime-7.0.0}/runtime/cli.py +0 -75
- {pgc_runtime-6.0.0 → pgc_runtime-7.0.0}/runtime/evidence.py +2 -2
- pgc_runtime-6.0.0/runtime/trace_viz.py +0 -233
- {pgc_runtime-6.0.0 → pgc_runtime-7.0.0}/LICENSE +0 -0
- {pgc_runtime-6.0.0 → pgc_runtime-7.0.0}/MANIFEST.in +0 -0
- {pgc_runtime-6.0.0 → pgc_runtime-7.0.0}/NOTICE +0 -0
- {pgc_runtime-6.0.0 → pgc_runtime-7.0.0}/pgc_runtime.egg-info/dependency_links.txt +0 -0
- {pgc_runtime-6.0.0 → pgc_runtime-7.0.0}/pgc_runtime.egg-info/entry_points.txt +0 -0
- {pgc_runtime-6.0.0 → pgc_runtime-7.0.0}/pgc_runtime.egg-info/top_level.txt +0 -0
- {pgc_runtime-6.0.0 → pgc_runtime-7.0.0}/runtime/__init__.py +0 -0
- {pgc_runtime-6.0.0 → pgc_runtime-7.0.0}/runtime/boot.py +0 -0
- {pgc_runtime-6.0.0 → pgc_runtime-7.0.0}/runtime/conformance.py +0 -0
- {pgc_runtime-6.0.0 → pgc_runtime-7.0.0}/runtime/coordinator.py +0 -0
- {pgc_runtime-6.0.0 → pgc_runtime-7.0.0}/runtime/ct_errors.py +0 -0
- {pgc_runtime-6.0.0 → pgc_runtime-7.0.0}/runtime/ct_execute.py +0 -0
- {pgc_runtime-6.0.0 → pgc_runtime-7.0.0}/runtime/ct_executor.py +0 -0
- {pgc_runtime-6.0.0 → pgc_runtime-7.0.0}/runtime/dispatcher.py +0 -0
- {pgc_runtime-6.0.0 → pgc_runtime-7.0.0}/runtime/examine/__init__.py +0 -0
- {pgc_runtime-6.0.0 → pgc_runtime-7.0.0}/runtime/examine/cli.py +0 -0
- {pgc_runtime-6.0.0 → pgc_runtime-7.0.0}/runtime/examine/parser.py +0 -0
- {pgc_runtime-6.0.0 → pgc_runtime-7.0.0}/runtime/examine/reporter.py +0 -0
- {pgc_runtime-6.0.0 → pgc_runtime-7.0.0}/runtime/federation/__init__.py +0 -0
- {pgc_runtime-6.0.0 → pgc_runtime-7.0.0}/runtime/federation/client.py +0 -0
- {pgc_runtime-6.0.0 → pgc_runtime-7.0.0}/runtime/federation/coordinator.py +0 -0
- {pgc_runtime-6.0.0 → pgc_runtime-7.0.0}/runtime/federation/defaults.py +0 -0
- {pgc_runtime-6.0.0 → pgc_runtime-7.0.0}/runtime/federation/store.py +0 -0
- {pgc_runtime-6.0.0 → pgc_runtime-7.0.0}/runtime/federation/worker.py +0 -0
- {pgc_runtime-6.0.0 → pgc_runtime-7.0.0}/runtime/loader.py +0 -0
- {pgc_runtime-6.0.0 → pgc_runtime-7.0.0}/runtime/memory.py +0 -0
- {pgc_runtime-6.0.0 → pgc_runtime-7.0.0}/runtime/replay.py +0 -0
- {pgc_runtime-6.0.0 → pgc_runtime-7.0.0}/runtime/scheduler.py +0 -0
- {pgc_runtime-6.0.0 → pgc_runtime-7.0.0}/setup.cfg +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pgc-runtime
|
|
3
|
-
Version:
|
|
3
|
+
Version: 7.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
|
|
7
7
|
Project-URL: Homepage, https://omnibachi.org/
|
|
8
8
|
Project-URL: Repository, https://github.com/protocol-governed-computing/protocol_runtime
|
|
9
|
-
Project-URL: Standard, https://doi.org/10.5281/zenodo.
|
|
9
|
+
Project-URL: Standard, https://doi.org/10.5281/zenodo.22150615
|
|
10
10
|
Keywords: protocol-governed-computing,pgc,protocol,runtime,workflow,execution
|
|
11
11
|
Classifier: Development Status :: 4 - Beta
|
|
12
12
|
Classifier: Intended Audience :: Developers
|
|
@@ -22,8 +22,6 @@ License-File: LICENSE
|
|
|
22
22
|
License-File: NOTICE
|
|
23
23
|
Provides-Extra: reference
|
|
24
24
|
Requires-Dist: pgc-governance>=2.0.0; extra == "reference"
|
|
25
|
-
Provides-Extra: render
|
|
26
|
-
Requires-Dist: pgc-inspector>=6.0.0; extra == "render"
|
|
27
25
|
Dynamic: license-file
|
|
28
26
|
|
|
29
27
|
# protocol_runtime
|
|
@@ -86,8 +84,6 @@ the data root:
|
|
|
86
84
|
```
|
|
87
85
|
<data-root>/traces/<domain>/<WF>/<TRACE_ID>/
|
|
88
86
|
<TRACE_ID>.jsonl append-only execution log, one SCHEMA_TRACE_EVENT_V0 event per line
|
|
89
|
-
<TRACE_ID>.png the path the run took, drawn only on request (`run --behavior-logic`,
|
|
90
|
-
or `behavior-logic <trace>`)
|
|
91
87
|
|
|
92
88
|
<data-root>/<domain>/<subdomain>/
|
|
93
89
|
the stores the domain's runtime binding declares
|
|
@@ -113,7 +109,6 @@ the data root:
|
|
|
113
109
|
| `coordinator` | serve as the coordinating node of a federated node group |
|
|
114
110
|
| `worker` | serve as a worker node of a federated node group |
|
|
115
111
|
| `examine` | read a completed trace: its path by node, contracts, results, events and errors |
|
|
116
|
-
| `behavior-logic` | render a completed trace as a PNG: the path, each route's outcome, and why each node decided as far as the trace records it (drawn from the inspector's `si.execution.explain`; needs `pgc-runtime[render]`) |
|
|
117
112
|
|
|
118
113
|
`PGC_SNAPSHOT_ROOT` overrides the snapshot location; `PGC_IMPL_ROOTS` is the colon-separated set of
|
|
119
114
|
roots on `PYTHONPATH` for domain capability implementations.
|
|
@@ -216,4 +211,4 @@ Each repository's `VERSION` file holds the workspace's cycle ordinal, which name
|
|
|
216
211
|
branch. It is development accounting, and it is never published. The standard the packages
|
|
217
212
|
implement is a separate artifact on its own track, and none of these numbers is its revision.
|
|
218
213
|
|
|
219
|
-
The standard these packages implement is published separately: https://doi.org/10.5281/zenodo.
|
|
214
|
+
The standard these packages implement is published separately: https://doi.org/10.5281/zenodo.22150615
|
|
@@ -58,8 +58,6 @@ the data root:
|
|
|
58
58
|
```
|
|
59
59
|
<data-root>/traces/<domain>/<WF>/<TRACE_ID>/
|
|
60
60
|
<TRACE_ID>.jsonl append-only execution log, one SCHEMA_TRACE_EVENT_V0 event per line
|
|
61
|
-
<TRACE_ID>.png the path the run took, drawn only on request (`run --behavior-logic`,
|
|
62
|
-
or `behavior-logic <trace>`)
|
|
63
61
|
|
|
64
62
|
<data-root>/<domain>/<subdomain>/
|
|
65
63
|
the stores the domain's runtime binding declares
|
|
@@ -85,7 +83,6 @@ the data root:
|
|
|
85
83
|
| `coordinator` | serve as the coordinating node of a federated node group |
|
|
86
84
|
| `worker` | serve as a worker node of a federated node group |
|
|
87
85
|
| `examine` | read a completed trace: its path by node, contracts, results, events and errors |
|
|
88
|
-
| `behavior-logic` | render a completed trace as a PNG: the path, each route's outcome, and why each node decided as far as the trace records it (drawn from the inspector's `si.execution.explain`; needs `pgc-runtime[render]`) |
|
|
89
86
|
|
|
90
87
|
`PGC_SNAPSHOT_ROOT` overrides the snapshot location; `PGC_IMPL_ROOTS` is the colon-separated set of
|
|
91
88
|
roots on `PYTHONPATH` for domain capability implementations.
|
|
@@ -188,4 +185,4 @@ Each repository's `VERSION` file holds the workspace's cycle ordinal, which name
|
|
|
188
185
|
branch. It is development accounting, and it is never published. The standard the packages
|
|
189
186
|
implement is a separate artifact on its own track, and none of these numbers is its revision.
|
|
190
187
|
|
|
191
|
-
The standard these packages implement is published separately: https://doi.org/10.5281/zenodo.
|
|
188
|
+
The standard these packages implement is published separately: https://doi.org/10.5281/zenodo.22150615
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pgc-runtime
|
|
3
|
-
Version:
|
|
3
|
+
Version: 7.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
|
|
7
7
|
Project-URL: Homepage, https://omnibachi.org/
|
|
8
8
|
Project-URL: Repository, https://github.com/protocol-governed-computing/protocol_runtime
|
|
9
|
-
Project-URL: Standard, https://doi.org/10.5281/zenodo.
|
|
9
|
+
Project-URL: Standard, https://doi.org/10.5281/zenodo.22150615
|
|
10
10
|
Keywords: protocol-governed-computing,pgc,protocol,runtime,workflow,execution
|
|
11
11
|
Classifier: Development Status :: 4 - Beta
|
|
12
12
|
Classifier: Intended Audience :: Developers
|
|
@@ -22,8 +22,6 @@ License-File: LICENSE
|
|
|
22
22
|
License-File: NOTICE
|
|
23
23
|
Provides-Extra: reference
|
|
24
24
|
Requires-Dist: pgc-governance>=2.0.0; extra == "reference"
|
|
25
|
-
Provides-Extra: render
|
|
26
|
-
Requires-Dist: pgc-inspector>=6.0.0; extra == "render"
|
|
27
25
|
Dynamic: license-file
|
|
28
26
|
|
|
29
27
|
# protocol_runtime
|
|
@@ -86,8 +84,6 @@ the data root:
|
|
|
86
84
|
```
|
|
87
85
|
<data-root>/traces/<domain>/<WF>/<TRACE_ID>/
|
|
88
86
|
<TRACE_ID>.jsonl append-only execution log, one SCHEMA_TRACE_EVENT_V0 event per line
|
|
89
|
-
<TRACE_ID>.png the path the run took, drawn only on request (`run --behavior-logic`,
|
|
90
|
-
or `behavior-logic <trace>`)
|
|
91
87
|
|
|
92
88
|
<data-root>/<domain>/<subdomain>/
|
|
93
89
|
the stores the domain's runtime binding declares
|
|
@@ -113,7 +109,6 @@ the data root:
|
|
|
113
109
|
| `coordinator` | serve as the coordinating node of a federated node group |
|
|
114
110
|
| `worker` | serve as a worker node of a federated node group |
|
|
115
111
|
| `examine` | read a completed trace: its path by node, contracts, results, events and errors |
|
|
116
|
-
| `behavior-logic` | render a completed trace as a PNG: the path, each route's outcome, and why each node decided as far as the trace records it (drawn from the inspector's `si.execution.explain`; needs `pgc-runtime[render]`) |
|
|
117
112
|
|
|
118
113
|
`PGC_SNAPSHOT_ROOT` overrides the snapshot location; `PGC_IMPL_ROOTS` is the colon-separated set of
|
|
119
114
|
roots on `PYTHONPATH` for domain capability implementations.
|
|
@@ -216,4 +211,4 @@ Each repository's `VERSION` file holds the workspace's cycle ordinal, which name
|
|
|
216
211
|
branch. It is development accounting, and it is never published. The standard the packages
|
|
217
212
|
implement is a separate artifact on its own track, and none of these numbers is its revision.
|
|
218
213
|
|
|
219
|
-
The standard these packages implement is published separately: https://doi.org/10.5281/zenodo.
|
|
214
|
+
The standard these packages implement is published separately: https://doi.org/10.5281/zenodo.22150615
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pgc-runtime"
|
|
7
|
-
version = "
|
|
7
|
+
version = "7.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"
|
|
@@ -32,9 +32,6 @@ dependencies = []
|
|
|
32
32
|
|
|
33
33
|
[project.optional-dependencies]
|
|
34
34
|
reference = ["pgc-governance>=2.0.0"]
|
|
35
|
-
# The PNG projection (`--behavior-logic`, `behavior-logic`) draws the inspector's explanation of a
|
|
36
|
-
# run. Execution never imports the inspector; only `runtime.trace_viz` does.
|
|
37
|
-
render = ["pgc-inspector>=6.0.0"]
|
|
38
35
|
|
|
39
36
|
[project.scripts]
|
|
40
37
|
protocol_runtime = "runtime.cli:main"
|
|
@@ -42,7 +39,7 @@ protocol_runtime = "runtime.cli:main"
|
|
|
42
39
|
[project.urls]
|
|
43
40
|
Homepage = "https://omnibachi.org/"
|
|
44
41
|
Repository = "https://github.com/protocol-governed-computing/protocol_runtime"
|
|
45
|
-
Standard = "https://doi.org/10.5281/zenodo.
|
|
42
|
+
Standard = "https://doi.org/10.5281/zenodo.22150615"
|
|
46
43
|
|
|
47
44
|
[tool.setuptools]
|
|
48
45
|
include-package-data = false
|
|
@@ -78,8 +78,8 @@ def run_workflow(
|
|
|
78
78
|
writer.error(str(exc))
|
|
79
79
|
raise
|
|
80
80
|
finally:
|
|
81
|
-
# The JSONL is the evidence. A
|
|
82
|
-
#
|
|
81
|
+
# The JSONL is the evidence. A drawing of the path is a view of it, made by the inspector
|
|
82
|
+
# on request, never by the runtime and never as a cost of every run.
|
|
83
83
|
writer.close()
|
|
84
84
|
|
|
85
85
|
return RunResult(status=status, surface=surface or {}, trace_id=trace_id, trace_dir=trace_dir)
|
|
@@ -4,7 +4,6 @@ cli.py — Token-native CLI entry point for the runtime.
|
|
|
4
4
|
Commands:
|
|
5
5
|
run — Execute a workflow against the tokenized snapshot.
|
|
6
6
|
examine — Analyze a completed trace file and print a diagnostic report.
|
|
7
|
-
behavior-logic — Render a completed trace, explained, over its workflow graph (PNG).
|
|
8
7
|
|
|
9
8
|
Execution path (run):
|
|
10
9
|
1. Load tokenized snapshot for the domain via loader.load_domain()
|
|
@@ -13,7 +12,6 @@ Execution path (run):
|
|
|
13
12
|
3. Open TraceWriter at traces/<domain>/<wf_code>/<trace_id>/
|
|
14
13
|
4. Drive workflow topology via scheduler.run_wf()
|
|
15
14
|
5. Print result summary; exit 1 on non-SUCCESS
|
|
16
|
-
6. If --behavior-logic: render the explained run as a PNG (trace_viz, a client of the inspector)
|
|
17
15
|
|
|
18
16
|
All runtime behavior comes from the compiled tokenized_snapshot.
|
|
19
17
|
The CLI does not implement any domain logic.
|
|
@@ -72,14 +70,6 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
72
70
|
"Default: sibling ../snapshot",
|
|
73
71
|
)
|
|
74
72
|
|
|
75
|
-
# ── run: optional behavior-logic flag ────────────────────────
|
|
76
|
-
run_p.add_argument(
|
|
77
|
-
"--behavior-logic",
|
|
78
|
-
action="store_true",
|
|
79
|
-
dest="behavior_logic",
|
|
80
|
-
help="Render execution-path PNG after run (requires graphviz)",
|
|
81
|
-
)
|
|
82
|
-
|
|
83
73
|
# ── replay ────────────────────────────────────────────────────
|
|
84
74
|
rp_p = subs.add_parser(
|
|
85
75
|
"replay",
|
|
@@ -136,23 +126,6 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
136
126
|
help="Path to a completed .jsonl trace file",
|
|
137
127
|
)
|
|
138
128
|
|
|
139
|
-
# ── behavior-logic ────────────────────────────────────────────
|
|
140
|
-
bl_p = subs.add_parser(
|
|
141
|
-
"behavior-logic",
|
|
142
|
-
help="Render execution-path PNG from a completed trace file",
|
|
143
|
-
)
|
|
144
|
-
bl_p.add_argument(
|
|
145
|
-
"trace_file",
|
|
146
|
-
metavar="FILE",
|
|
147
|
-
help="Path to a completed .jsonl trace file",
|
|
148
|
-
)
|
|
149
|
-
bl_p.add_argument(
|
|
150
|
-
"--snapshot",
|
|
151
|
-
metavar="PATH",
|
|
152
|
-
help="Absolute path to the assembled snapshot the trace ran against "
|
|
153
|
-
"(or set PGC_SNAPSHOT_ROOT; default: the sibling ../snapshot)",
|
|
154
|
-
)
|
|
155
|
-
|
|
156
129
|
return parser
|
|
157
130
|
|
|
158
131
|
|
|
@@ -208,12 +181,6 @@ def _handle_run(args: argparse.Namespace) -> None:
|
|
|
208
181
|
print(f"[runtime] Trace dir: {trace_dir}")
|
|
209
182
|
print()
|
|
210
183
|
|
|
211
|
-
# Evidence projection: render execution-path PNG if requested
|
|
212
|
-
trace_path = trace_dir / f"{trace_id}.jsonl"
|
|
213
|
-
png_path = None
|
|
214
|
-
if args.behavior_logic:
|
|
215
|
-
png_path = _render_behavior_logic(snapshot_root, trace_path, trace_root=data_root)
|
|
216
|
-
|
|
217
184
|
print("=" * 60)
|
|
218
185
|
print("[runtime] Workflow Complete")
|
|
219
186
|
print("=" * 60)
|
|
@@ -224,10 +191,6 @@ def _handle_run(args: argparse.Namespace) -> None:
|
|
|
224
191
|
if surface:
|
|
225
192
|
print("Output:")
|
|
226
193
|
print(_format_surface(surface))
|
|
227
|
-
if png_path:
|
|
228
|
-
print(f"Graph: {png_path}")
|
|
229
|
-
elif args.behavior_logic:
|
|
230
|
-
print("Graph: (graphviz not available — PNG skipped)")
|
|
231
194
|
print("=" * 60)
|
|
232
195
|
|
|
233
196
|
if result_status not in ("SUCCESS", "ALREADY_EXISTS"):
|
|
@@ -341,27 +304,6 @@ def _health_line(snapshot_root: Path, booted) -> str:
|
|
|
341
304
|
return f"[runtime] ✓ Snapshot healthy — {n} domain(s) resident and hash-verified{gov}. No issues."
|
|
342
305
|
|
|
343
306
|
|
|
344
|
-
def _handle_behavior_logic(args: argparse.Namespace) -> None:
|
|
345
|
-
trace_path = Path(args.trace_file)
|
|
346
|
-
if not trace_path.exists():
|
|
347
|
-
_fatal(f"Trace file not found: {args.trace_file}")
|
|
348
|
-
|
|
349
|
-
snapshot_str = args.snapshot or os.environ.get("PGC_SNAPSHOT_ROOT")
|
|
350
|
-
snapshot_root = Path(snapshot_str) if snapshot_str else default_snapshot_root()
|
|
351
|
-
if not snapshot_root.is_absolute():
|
|
352
|
-
_fatal(f"--snapshot must be an absolute path, got: {snapshot_str}")
|
|
353
|
-
|
|
354
|
-
png_path = _render_behavior_logic(snapshot_root, trace_path)
|
|
355
|
-
if png_path:
|
|
356
|
-
print(f"[runtime] Execution path PNG: {png_path}")
|
|
357
|
-
else:
|
|
358
|
-
print(
|
|
359
|
-
"[runtime] Behavior logic render skipped — graphviz (dot) not available.",
|
|
360
|
-
file=sys.stderr,
|
|
361
|
-
)
|
|
362
|
-
sys.exit(1)
|
|
363
|
-
|
|
364
|
-
|
|
365
307
|
def _handle_examine(args: argparse.Namespace) -> None:
|
|
366
308
|
trace_path = Path(args.trace_file)
|
|
367
309
|
if not trace_path.exists():
|
|
@@ -391,21 +333,6 @@ def _handle_examine(args: argparse.Namespace) -> None:
|
|
|
391
333
|
# Utilities
|
|
392
334
|
# ---------------------------------------------------------------------------
|
|
393
335
|
|
|
394
|
-
def _render_behavior_logic(snapshot_root: Path, trace_path: Path,
|
|
395
|
-
trace_root: Path | None = None) -> "Path | None":
|
|
396
|
-
"""Render the explained run as a PNG. Best-effort: a refusal is reported, never drawn around."""
|
|
397
|
-
from runtime.trace_viz import render_trace_png
|
|
398
|
-
try:
|
|
399
|
-
return render_trace_png(snapshot_root, trace_path, trace_root=trace_root)
|
|
400
|
-
except ModuleNotFoundError as exc:
|
|
401
|
-
print(f"[runtime] Behavior logic render needs the snapshot inspector "
|
|
402
|
-
f"(pgc-runtime[render]): {exc}", file=sys.stderr)
|
|
403
|
-
return None
|
|
404
|
-
except (FileNotFoundError, ValueError) as exc:
|
|
405
|
-
print(f"[runtime] Behavior logic render error: {exc}", file=sys.stderr)
|
|
406
|
-
return None
|
|
407
|
-
|
|
408
|
-
|
|
409
336
|
def _load_payload(payload_path: str | None) -> dict:
|
|
410
337
|
if not payload_path:
|
|
411
338
|
return {}
|
|
@@ -500,8 +427,6 @@ def main() -> None:
|
|
|
500
427
|
_handle_worker(args)
|
|
501
428
|
elif args.command == "examine":
|
|
502
429
|
_handle_examine(args)
|
|
503
|
-
elif args.command == "behavior-logic":
|
|
504
|
-
_handle_behavior_logic(args)
|
|
505
430
|
|
|
506
431
|
|
|
507
432
|
if __name__ == "__main__":
|
|
@@ -19,8 +19,8 @@ The trace file is written to:
|
|
|
19
19
|
|
|
20
20
|
subdomain is not known to this module — caller passes the full trace dir path.
|
|
21
21
|
|
|
22
|
-
This module produces the raw JSONL evidence only.
|
|
23
|
-
|
|
22
|
+
This module produces the raw JSONL evidence only. A reader draws a run from it through the
|
|
23
|
+
inspector: its workflow page marks the path `si.execution.explain` gives (`/workflow/?trace=`).
|
|
24
24
|
"""
|
|
25
25
|
|
|
26
26
|
from __future__ import annotations
|
|
@@ -1,233 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
trace_viz.py — Evidence projection: a run drawn over its workflow, with why it went where it went.
|
|
3
|
-
|
|
4
|
-
A client of the snapshot inspector, never a second reader of the trace. It asks two questions of
|
|
5
|
-
`inspector.api.query` and draws the answers:
|
|
6
|
-
|
|
7
|
-
si.behavior_logic.show the workflow's declared graph (every node and edge)
|
|
8
|
-
si.execution.explain the run: the path, each route's outcome, each node's determination
|
|
9
|
-
as far as the trace records it, captured inputs, how the run ended
|
|
10
|
-
──────────────────────
|
|
11
|
-
→ <trace_id>.png beside the trace
|
|
12
|
-
|
|
13
|
-
It derives nothing. The inspector resolves the workflow's domain, ties the trace to the snapshot
|
|
14
|
-
(and refuses a trace produced under another), and separates what the trace recorded from what it
|
|
15
|
-
joined from the snapshot. This module only chooses how each answer looks:
|
|
16
|
-
|
|
17
|
-
- **recorded** (from the trace) is drawn in red: the path, the outcome on each route taken, a
|
|
18
|
-
gate's failed checks, a node's recorded outcome, errors, captured inputs;
|
|
19
|
-
- **joined** (from the snapshot) is drawn in grey: each node's declared capability;
|
|
20
|
-
- **declared but not taken** is drawn pale, as before;
|
|
21
|
-
- a route the run took that the graph does not declare is drawn dashed.
|
|
22
|
-
|
|
23
|
-
The inspector is imported here and nowhere else in the runtime: execution never depends on it,
|
|
24
|
-
and only this optional projection does (`pgc-runtime[render]`).
|
|
25
|
-
|
|
26
|
-
Uses graphviz (dot) — returns None if dot is not available.
|
|
27
|
-
"""
|
|
28
|
-
|
|
29
|
-
from __future__ import annotations
|
|
30
|
-
|
|
31
|
-
import subprocess
|
|
32
|
-
from html import escape
|
|
33
|
-
from pathlib import Path
|
|
34
|
-
from typing import Any, Optional
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
class ExplanationRefused(ValueError):
|
|
38
|
-
"""The inspector would not explain this trace against this snapshot. `str(exc)` is its reason."""
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
# ---------------------------------------------------------------------------
|
|
42
|
-
# Public API
|
|
43
|
-
# ---------------------------------------------------------------------------
|
|
44
|
-
|
|
45
|
-
def render_trace_png(
|
|
46
|
-
snapshot_root: Path,
|
|
47
|
-
trace_path: Path,
|
|
48
|
-
trace_root: Path | None = None,
|
|
49
|
-
) -> Optional[Path]:
|
|
50
|
-
"""
|
|
51
|
-
Render the explained run over its workflow graph, beside the trace.
|
|
52
|
-
|
|
53
|
-
Args:
|
|
54
|
-
snapshot_root: The assembled snapshot the trace names.
|
|
55
|
-
trace_path: The completed .jsonl trace.
|
|
56
|
-
trace_root: The root the trace is named under — the run's data root. Defaults to the
|
|
57
|
-
trace's own directory, which names the same file.
|
|
58
|
-
|
|
59
|
-
Returns:
|
|
60
|
-
Path to the PNG, or None if graphviz is unavailable.
|
|
61
|
-
|
|
62
|
-
Raises:
|
|
63
|
-
FileNotFoundError: trace_path does not exist.
|
|
64
|
-
ExplanationRefused: the inspector refused the trace or has no graph for its workflow.
|
|
65
|
-
"""
|
|
66
|
-
from inspector.api import query # optional projection; execution never imports this
|
|
67
|
-
|
|
68
|
-
trace_path = Path(trace_path).resolve()
|
|
69
|
-
if not trace_path.is_file():
|
|
70
|
-
raise FileNotFoundError(f"Trace file not found: {trace_path}")
|
|
71
|
-
root = Path(trace_root).resolve() if trace_root is not None else trace_path.parent
|
|
72
|
-
reference = trace_path.relative_to(root).as_posix()
|
|
73
|
-
|
|
74
|
-
status, explanation = query("si.execution.explain", {"trace": reference}, snapshot_root,
|
|
75
|
-
trace_root=root)
|
|
76
|
-
if status != "SUCCESS":
|
|
77
|
-
raise ExplanationRefused(explanation.get("reason", status))
|
|
78
|
-
status, logic = query("si.behavior_logic.show", {"wf": explanation["wf"]}, snapshot_root)
|
|
79
|
-
if status != "SUCCESS":
|
|
80
|
-
raise ExplanationRefused(logic.get("reason", status))
|
|
81
|
-
|
|
82
|
-
dot_content = _generate_dot(logic["graph"], explanation)
|
|
83
|
-
|
|
84
|
-
png_path = trace_path.with_suffix(".png")
|
|
85
|
-
dot_path = trace_path.with_suffix(".dot")
|
|
86
|
-
dot_path.write_text(dot_content, encoding="utf-8")
|
|
87
|
-
try:
|
|
88
|
-
subprocess.run(
|
|
89
|
-
["dot", "-Tpng", str(dot_path), "-o", str(png_path)],
|
|
90
|
-
check=True,
|
|
91
|
-
capture_output=True,
|
|
92
|
-
)
|
|
93
|
-
dot_path.unlink()
|
|
94
|
-
return png_path
|
|
95
|
-
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
96
|
-
dot_path.unlink(missing_ok=True)
|
|
97
|
-
return None
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
# ---------------------------------------------------------------------------
|
|
101
|
-
# What the explanation says, reduced to what is drawn
|
|
102
|
-
# ---------------------------------------------------------------------------
|
|
103
|
-
|
|
104
|
-
def _taken(explanation: dict[str, Any]) -> list[tuple[str, str, str]]:
|
|
105
|
-
"""The path as (from_node, outcome, to_node), in order — read from the explanation's visits.
|
|
106
|
-
|
|
107
|
-
A route that reached neither routing nor an ending has no `to`, and the path stops at the node
|
|
108
|
-
that produced it. A visit with no node key means the trace predates node keys: refused, because
|
|
109
|
-
the path cannot be placed on the graph without guessing.
|
|
110
|
-
"""
|
|
111
|
-
path: list[tuple[str, str, str]] = []
|
|
112
|
-
for visit in explanation.get("visits", []):
|
|
113
|
-
route = visit.get("route")
|
|
114
|
-
if route is None:
|
|
115
|
-
continue
|
|
116
|
-
if not visit.get("node"):
|
|
117
|
-
raise ExplanationRefused("the trace's routes carry no node keys — it predates them; re-run it")
|
|
118
|
-
if route.get("to") is None:
|
|
119
|
-
break
|
|
120
|
-
path.append((visit["node"], route.get("outcome"), route["to"]))
|
|
121
|
-
return path
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
def _recorded_lines(visit: dict[str, Any]) -> list[str]:
|
|
125
|
-
"""What the trace recorded about why this node decided as it did, as label lines."""
|
|
126
|
-
lines: list[str] = []
|
|
127
|
-
determination = visit.get("determination") or {}
|
|
128
|
-
basis = determination.get("basis")
|
|
129
|
-
if basis == "recorded admission checks":
|
|
130
|
-
failed = determination.get("failed") or []
|
|
131
|
-
if failed:
|
|
132
|
-
lines += [f"✗ {c['field']}: {c['rule']} {c['expected']}" for c in failed]
|
|
133
|
-
else:
|
|
134
|
-
lines.append(f"✓ {len(determination.get('checks') or [])} check(s) held")
|
|
135
|
-
elif basis == "capability outcome":
|
|
136
|
-
lines.append(f"→ {determination.get('outcome')}")
|
|
137
|
-
elif basis == "not recorded":
|
|
138
|
-
lines.append("checks not recorded")
|
|
139
|
-
for atom in visit.get("atoms") or []:
|
|
140
|
-
if atom.get("captured"):
|
|
141
|
-
name = (atom.get("atom") or "").split("::")[-1]
|
|
142
|
-
lines.append(f"captured: {name}{' (replayed)' if atom.get('replayed') else ''}")
|
|
143
|
-
for error in visit.get("errors") or []:
|
|
144
|
-
lines.append(f"ERROR: {error.get('message')}")
|
|
145
|
-
return lines
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
# ---------------------------------------------------------------------------
|
|
149
|
-
# DOT generation
|
|
150
|
-
# ---------------------------------------------------------------------------
|
|
151
|
-
|
|
152
|
-
_SHAPES = {"IN": ("ellipse", "lightblue"), "CC": ("box", "lightgreen"),
|
|
153
|
-
"EXIT": ("ellipse", "lightcoral")}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
def _label(node_id: str, joined: str | None, recorded: list[str]) -> str:
|
|
157
|
-
"""An HTML-like label: the node, then what was joined (grey), then what was recorded (red)."""
|
|
158
|
-
rows = [f"<B>{escape(node_id)}</B>"]
|
|
159
|
-
if joined:
|
|
160
|
-
rows.append(f'<FONT POINT-SIZE="10" COLOR="gray35"><I>{escape(joined)}</I></FONT>')
|
|
161
|
-
rows += [f'<FONT POINT-SIZE="10" COLOR="red3">{escape(line)}</FONT>' for line in recorded]
|
|
162
|
-
return "<" + "<BR/>".join(rows) + ">"
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
def _generate_dot(graph: dict[str, Any], explanation: dict[str, Any]) -> str:
|
|
166
|
-
"""
|
|
167
|
-
The declared graph, with the explained run drawn over it.
|
|
168
|
-
|
|
169
|
-
Visited nodes: red border and fill, with what the trace recorded about the node.
|
|
170
|
-
Taken edges: red, bold, labelled with the outcome that selected them.
|
|
171
|
-
Undeclared: a route the run took that the graph does not declare — dashed red.
|
|
172
|
-
The deciding node of a declared ending carries a double border.
|
|
173
|
-
"""
|
|
174
|
-
path = _taken(explanation)
|
|
175
|
-
visits = {v["node"]: v for v in explanation.get("visits", []) if v.get("node")}
|
|
176
|
-
taken_edges = {(f, t, o) for f, o, t in path}
|
|
177
|
-
ending = explanation.get("ending") or {}
|
|
178
|
-
decided_at = ending.get("decided_at") or ending.get("at")
|
|
179
|
-
|
|
180
|
-
tie = (explanation.get("tie") or {}).get("snapshot_id", "")
|
|
181
|
-
title = (f"{explanation.get('wf')} · {explanation.get('status')} · "
|
|
182
|
-
f"{ending.get('kind', '')}\\nsnapshot {tie[:12]} (claimed by the trace)")
|
|
183
|
-
lines = [
|
|
184
|
-
f'digraph "{graph["wf_id"]}" {{',
|
|
185
|
-
" rankdir=LR;",
|
|
186
|
-
f' label="{title}"; labelloc=t; fontname="Arial";',
|
|
187
|
-
' node [fontname="Arial"];',
|
|
188
|
-
"",
|
|
189
|
-
]
|
|
190
|
-
|
|
191
|
-
declared_ids = set()
|
|
192
|
-
for node in graph["nodes"]:
|
|
193
|
-
node_id = node["id"]
|
|
194
|
-
declared_ids.add(node_id)
|
|
195
|
-
shape, fill = _SHAPES.get(node["type"], ("box", "white"))
|
|
196
|
-
visit = visits.get(node_id)
|
|
197
|
-
capability = node.get("capability")
|
|
198
|
-
joined = capability.split("::")[-1] if capability else None
|
|
199
|
-
if visit is None:
|
|
200
|
-
lines.append(f' "{node_id}" [label={_label(node_id, joined, [])}, shape={shape},'
|
|
201
|
-
f" style=filled, fillcolor={fill}, color=gray];")
|
|
202
|
-
continue
|
|
203
|
-
extra = ", peripheries=2" if node_id == decided_at else ""
|
|
204
|
-
style = "filled,dashed" if visit.get("errors") else "filled"
|
|
205
|
-
lines.append(f' "{node_id}" [label={_label(node_id, joined, _recorded_lines(visit))},'
|
|
206
|
-
f' shape={shape}, style="{style}", fillcolor=mistyrose, color=red,'
|
|
207
|
-
f" penwidth=2.5{extra}];")
|
|
208
|
-
|
|
209
|
-
# A place the run reached that the graph does not declare is still drawn: it was recorded.
|
|
210
|
-
for node_id, visit in visits.items():
|
|
211
|
-
if node_id not in declared_ids:
|
|
212
|
-
lines.append(f' "{node_id}" [label={_label(node_id, None, _recorded_lines(visit))},'
|
|
213
|
-
f' shape=box, style="filled,dashed", fillcolor=mistyrose, color=red];')
|
|
214
|
-
|
|
215
|
-
lines.append("")
|
|
216
|
-
|
|
217
|
-
declared_edges = set()
|
|
218
|
-
for edge in graph["edges"]:
|
|
219
|
-
from_id, to_id, condition = edge["from"], edge["to"], edge["condition"]
|
|
220
|
-
declared_edges.add((from_id, to_id, condition))
|
|
221
|
-
if (from_id, to_id, condition) in taken_edges:
|
|
222
|
-
lines.append(f' "{from_id}" -> "{to_id}"'
|
|
223
|
-
f' [label="{condition}", color=red, penwidth=2.5, fontcolor=red];')
|
|
224
|
-
else:
|
|
225
|
-
lines.append(f' "{from_id}" -> "{to_id}"'
|
|
226
|
-
f' [label="{condition}", color=gray, fontcolor=gray];')
|
|
227
|
-
|
|
228
|
-
for from_id, to_id, condition in sorted(taken_edges - declared_edges):
|
|
229
|
-
lines.append(f' "{from_id}" -> "{to_id}"'
|
|
230
|
-
f' [label="{condition} (undeclared)", color=red, style=dashed, fontcolor=red];')
|
|
231
|
-
|
|
232
|
-
lines.append("}")
|
|
233
|
-
return "\n".join(lines)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|