pgc-runtime 3.0.0__tar.gz → 5.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 (48) hide show
  1. {pgc_runtime-3.0.0/pgc_runtime.egg-info → pgc_runtime-5.0.0}/PKG-INFO +54 -17
  2. {pgc_runtime-3.0.0 → pgc_runtime-5.0.0}/README.md +51 -16
  3. {pgc_runtime-3.0.0 → pgc_runtime-5.0.0/pgc_runtime.egg-info}/PKG-INFO +54 -17
  4. {pgc_runtime-3.0.0 → pgc_runtime-5.0.0}/pgc_runtime.egg-info/SOURCES.txt +9 -4
  5. {pgc_runtime-3.0.0 → pgc_runtime-5.0.0}/pgc_runtime.egg-info/requires.txt +3 -0
  6. {pgc_runtime-3.0.0 → pgc_runtime-5.0.0}/pyproject.toml +4 -1
  7. pgc_runtime-5.0.0/runtime/api.py +173 -0
  8. {pgc_runtime-3.0.0 → pgc_runtime-5.0.0}/runtime/boot.py +37 -24
  9. {pgc_runtime-3.0.0 → pgc_runtime-5.0.0}/runtime/cli.py +136 -15
  10. pgc_runtime-5.0.0/runtime/conformance.py +353 -0
  11. pgc_runtime-5.0.0/runtime/coordinator.py +184 -0
  12. {pgc_runtime-3.0.0 → pgc_runtime-5.0.0}/runtime/ct_execute.py +3 -1
  13. {pgc_runtime-3.0.0 → pgc_runtime-5.0.0}/runtime/ct_executor.py +127 -24
  14. {pgc_runtime-3.0.0 → pgc_runtime-5.0.0}/runtime/dispatcher.py +12 -4
  15. {pgc_runtime-3.0.0 → pgc_runtime-5.0.0}/runtime/evidence.py +62 -9
  16. pgc_runtime-5.0.0/runtime/examine/__init__.py +49 -0
  17. pgc_runtime-5.0.0/runtime/examine/parser.py +73 -0
  18. pgc_runtime-5.0.0/runtime/examine/reporter.py +57 -0
  19. pgc_runtime-5.0.0/runtime/federation/__init__.py +16 -0
  20. pgc_runtime-5.0.0/runtime/federation/client.py +106 -0
  21. pgc_runtime-5.0.0/runtime/federation/coordinator.py +106 -0
  22. pgc_runtime-5.0.0/runtime/federation/defaults.py +22 -0
  23. pgc_runtime-5.0.0/runtime/federation/store.py +85 -0
  24. pgc_runtime-5.0.0/runtime/federation/worker.py +72 -0
  25. {pgc_runtime-3.0.0 → pgc_runtime-5.0.0}/runtime/loader.py +28 -33
  26. pgc_runtime-5.0.0/runtime/replay.py +77 -0
  27. {pgc_runtime-3.0.0 → pgc_runtime-5.0.0}/runtime/scheduler.py +36 -27
  28. pgc_runtime-5.0.0/runtime/trace_viz.py +233 -0
  29. pgc_runtime-3.0.0/runtime/api.py +0 -71
  30. pgc_runtime-3.0.0/runtime/conformance.py +0 -249
  31. pgc_runtime-3.0.0/runtime/examine/__init__.py +0 -126
  32. pgc_runtime-3.0.0/runtime/examine/classifier.py +0 -350
  33. pgc_runtime-3.0.0/runtime/examine/hint_engine.py +0 -245
  34. pgc_runtime-3.0.0/runtime/examine/locator.py +0 -148
  35. pgc_runtime-3.0.0/runtime/examine/parser.py +0 -223
  36. pgc_runtime-3.0.0/runtime/examine/reporter.py +0 -110
  37. pgc_runtime-3.0.0/runtime/trace_viz.py +0 -276
  38. {pgc_runtime-3.0.0 → pgc_runtime-5.0.0}/LICENSE +0 -0
  39. {pgc_runtime-3.0.0 → pgc_runtime-5.0.0}/MANIFEST.in +0 -0
  40. {pgc_runtime-3.0.0 → pgc_runtime-5.0.0}/NOTICE +0 -0
  41. {pgc_runtime-3.0.0 → pgc_runtime-5.0.0}/pgc_runtime.egg-info/dependency_links.txt +0 -0
  42. {pgc_runtime-3.0.0 → pgc_runtime-5.0.0}/pgc_runtime.egg-info/entry_points.txt +0 -0
  43. {pgc_runtime-3.0.0 → pgc_runtime-5.0.0}/pgc_runtime.egg-info/top_level.txt +0 -0
  44. {pgc_runtime-3.0.0 → pgc_runtime-5.0.0}/runtime/__init__.py +0 -0
  45. {pgc_runtime-3.0.0 → pgc_runtime-5.0.0}/runtime/ct_errors.py +0 -0
  46. {pgc_runtime-3.0.0 → pgc_runtime-5.0.0}/runtime/examine/cli.py +0 -0
  47. {pgc_runtime-3.0.0 → pgc_runtime-5.0.0}/runtime/memory.py +0 -0
  48. {pgc_runtime-3.0.0 → pgc_runtime-5.0.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pgc-runtime
3
- Version: 3.0.0
3
+ Version: 5.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
@@ -22,6 +22,8 @@ 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>=5.0.0; extra == "render"
25
27
  Dynamic: license-file
26
28
 
27
29
  # protocol_runtime
@@ -78,17 +80,17 @@ payload external input (JSON)
78
80
  data-root the state storage boundary; one data root is one instance
79
81
  ```
80
82
 
81
- A run writes an append-only trace alongside the state its declared side effects produce:
83
+ A run writes an append-only trace beside the state its declared side effects produce, both under
84
+ the data root:
82
85
 
83
86
  ```
84
- traces/<TRACE_ID>/
85
- <TRACE_ID>.jsonl append-only execution log
86
- <TRACE_ID>.md human-readable summary
87
- <TRACE_ID>.png the execution path, rendered
88
-
89
- data/
90
- registry/ idempotent state
91
- events/ append-only history
87
+ <data-root>/traces/<domain>/<WF>/<TRACE_ID>/
88
+ <TRACE_ID>.jsonl append-only execution log, one SCHEMA_TRACE_EVENT_V1 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
+
92
+ <data-root>/<domain>/<subdomain>/
93
+ the stores the domain's runtime binding declares
92
94
  ```
93
95
 
94
96
  ## Running
@@ -100,15 +102,18 @@ data/
100
102
  ./run.sh examine /abs/trace.jsonl
101
103
  ```
102
104
 
103
- `run.sh` wraps the CLI, also installed as the `protocol_runtime` console script, with four
104
- subcommands:
105
+ `run.sh` wraps the CLI, also installed as the `protocol_runtime` console script:
105
106
 
106
107
  | command | what it does |
107
108
  |---|---|
108
109
  | `run` | execute a workflow against a data root |
110
+ | `replay` | re-execute a run from its recorded outcomes and compare it with the original |
111
+ | `conformance` | run a compiled domain's test vectors; report each transform proven, unproven or refused |
109
112
  | `boot` | warm-boot the assembled snapshot — load and hash-verify every manifest domain |
110
- | `examine` | analyze a completed trace file |
111
- | `behavior-logic` | render the execution path from a completed trace as a PNG |
113
+ | `coordinator` | serve as the coordinating node of a federated node group |
114
+ | `worker` | serve as a worker node of a federated node group |
115
+ | `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]`) |
112
117
 
113
118
  `PGC_SNAPSHOT_ROOT` overrides the snapshot location; `PGC_IMPL_ROOTS` is the colon-separated set of
114
119
  roots on `PYTHONPATH` for domain capability implementations.
@@ -117,6 +122,12 @@ roots on `PYTHONPATH` for domain capability implementations.
117
122
  establishes that the snapshot is intact and executable before any workflow runs. A surface-only
118
123
  snapshot has no workflow to traverse, and warm reboot is exactly what proves it sound anyway.
119
124
 
125
+ **A process verifies a snapshot once.** `runtime.api` keeps a booted snapshot resident and runs
126
+ against it as often as it is asked. A rewritten manifest, or a different trust anchor or profile
127
+ root, is verified afresh, and a snapshot that no longer verifies is refused. `boot` itself always
128
+ performs the full determination. A governed run then costs a few milliseconds, not a third of a
129
+ second.
130
+
120
131
  **A data root is an instance, not an interface.** Two data roots against the same snapshot are two
121
132
  independent instances of the same governed behavior.
122
133
 
@@ -128,6 +139,21 @@ invoking transforms, applying side effects — and routes on the declared outcom
128
139
  nothing by name at execution time: the compiler assigned integer addresses, and traversal operates
129
140
  on those.
130
141
 
142
+ **A workflow may run one contract at several places.** Each place is a node key, and the contract
143
+ it runs is named separately. The compiler seals each place's routing on its own, and refuses a sealed
144
+ dispatch that does not realize every transition declared at that node. Routing is by node, and the
145
+ trace names the place that ran.
146
+
147
+ **Not every step is determined by its inputs.** A transform is a deterministic atom, a
148
+ non-deterministic atom, or a molecule, and a different constitution governs each. A molecule has no
149
+ implementation: the runtime runs its declared steps. A non-deterministic atom's result is recorded
150
+ where it is produced, and `replay` substitutes the recorded result, so a run is reproducible even
151
+ when a step is not.
152
+
153
+ **One snapshot can run across nodes.** Under a federated placement, a coordinator schedules units of
154
+ work to workers over a shared store. Under the signed federated profile, every node verifies the
155
+ snapshot's signature before it runs anything.
156
+
131
157
  Every step emits evidence. The trace is not a log the runtime chose to write; it is the record of
132
158
  the path actually taken through a graph that was fixed before the run began, which is what makes a
133
159
  run reproducible and reviewable after the fact.
@@ -164,8 +190,19 @@ export PGC_PLATFORM_ROOT=$PWD/software_governance
164
190
  pgc # reports what is installed and whether the anchor resolves
165
191
  ```
166
192
 
167
- `PGC_BUILD_ROOT` (compiled output, keeping the governance repo read-only) and `PGC_DOMAIN_ROOTS`
168
- (additional domains contributing their own `registry/structures`) are optional.
193
+ `PGC_DOMAIN_ROOTS` names an additional domain contributing its own `registry/structures` — the
194
+ directory that *directly contains* it, not the repository above it; pointing one level too high is a
195
+ silent no-op. `PGC_SNAPSHOT_PROFILES` is the directory holding snapshot profiles, required by the
196
+ assembler and the runtime alike.
197
+
198
+ **Where a build writes is declared, not supplied.** Each build configuration names its root in
199
+ `output_configuration.root`, and every layer's output consolidates there. The compiler does not read
200
+ `PGC_SNAPSHOT_ROOT`; the runtime does, with its own meaning — the assembled snapshot to execute.
201
+
202
+ `PGC_BUILD_ROOT` is accepted and reported and **nothing reads it**.
203
+
204
+ The full sequence, with the repositories it needs, is in
205
+ [`pgc_install`](https://github.com/protocol-governed-computing/pgc_install).
169
206
 
170
207
  **Versioning.** Two schemes, and the published version follows the second.
171
208
 
@@ -175,7 +212,7 @@ pgc # reports what is installed and whether the anchor resolves
175
212
  cycle is tagged `release-<N>`. This is not published.
176
213
  - **Public** — `PUBLIC_VERSION`, tagged on every component repository. The platform is at **`v3`**.
177
214
 
178
- **The published version is the public one: `v3` is `3.0.0`.** The standard the packages implement is a
215
+ **The published version is the public one: `v4` is `4.0.0`.** The standard the packages implement is a
179
216
  separate artifact on its own track and is not this number.
180
217
 
181
218
  The standard these packages implement is published separately: https://doi.org/10.5281/zenodo.22150616
@@ -52,17 +52,17 @@ payload external input (JSON)
52
52
  data-root the state storage boundary; one data root is one instance
53
53
  ```
54
54
 
55
- A run writes an append-only trace alongside the state its declared side effects produce:
55
+ A run writes an append-only trace beside the state its declared side effects produce, both under
56
+ the data root:
56
57
 
57
58
  ```
58
- traces/<TRACE_ID>/
59
- <TRACE_ID>.jsonl append-only execution log
60
- <TRACE_ID>.md human-readable summary
61
- <TRACE_ID>.png the execution path, rendered
62
-
63
- data/
64
- registry/ idempotent state
65
- events/ append-only history
59
+ <data-root>/traces/<domain>/<WF>/<TRACE_ID>/
60
+ <TRACE_ID>.jsonl append-only execution log, one SCHEMA_TRACE_EVENT_V1 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
+
64
+ <data-root>/<domain>/<subdomain>/
65
+ the stores the domain's runtime binding declares
66
66
  ```
67
67
 
68
68
  ## Running
@@ -74,15 +74,18 @@ data/
74
74
  ./run.sh examine /abs/trace.jsonl
75
75
  ```
76
76
 
77
- `run.sh` wraps the CLI, also installed as the `protocol_runtime` console script, with four
78
- subcommands:
77
+ `run.sh` wraps the CLI, also installed as the `protocol_runtime` console script:
79
78
 
80
79
  | command | what it does |
81
80
  |---|---|
82
81
  | `run` | execute a workflow against a data root |
82
+ | `replay` | re-execute a run from its recorded outcomes and compare it with the original |
83
+ | `conformance` | run a compiled domain's test vectors; report each transform proven, unproven or refused |
83
84
  | `boot` | warm-boot the assembled snapshot — load and hash-verify every manifest domain |
84
- | `examine` | analyze a completed trace file |
85
- | `behavior-logic` | render the execution path from a completed trace as a PNG |
85
+ | `coordinator` | serve as the coordinating node of a federated node group |
86
+ | `worker` | serve as a worker node of a federated node group |
87
+ | `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]`) |
86
89
 
87
90
  `PGC_SNAPSHOT_ROOT` overrides the snapshot location; `PGC_IMPL_ROOTS` is the colon-separated set of
88
91
  roots on `PYTHONPATH` for domain capability implementations.
@@ -91,6 +94,12 @@ roots on `PYTHONPATH` for domain capability implementations.
91
94
  establishes that the snapshot is intact and executable before any workflow runs. A surface-only
92
95
  snapshot has no workflow to traverse, and warm reboot is exactly what proves it sound anyway.
93
96
 
97
+ **A process verifies a snapshot once.** `runtime.api` keeps a booted snapshot resident and runs
98
+ against it as often as it is asked. A rewritten manifest, or a different trust anchor or profile
99
+ root, is verified afresh, and a snapshot that no longer verifies is refused. `boot` itself always
100
+ performs the full determination. A governed run then costs a few milliseconds, not a third of a
101
+ second.
102
+
94
103
  **A data root is an instance, not an interface.** Two data roots against the same snapshot are two
95
104
  independent instances of the same governed behavior.
96
105
 
@@ -102,6 +111,21 @@ invoking transforms, applying side effects — and routes on the declared outcom
102
111
  nothing by name at execution time: the compiler assigned integer addresses, and traversal operates
103
112
  on those.
104
113
 
114
+ **A workflow may run one contract at several places.** Each place is a node key, and the contract
115
+ it runs is named separately. The compiler seals each place's routing on its own, and refuses a sealed
116
+ dispatch that does not realize every transition declared at that node. Routing is by node, and the
117
+ trace names the place that ran.
118
+
119
+ **Not every step is determined by its inputs.** A transform is a deterministic atom, a
120
+ non-deterministic atom, or a molecule, and a different constitution governs each. A molecule has no
121
+ implementation: the runtime runs its declared steps. A non-deterministic atom's result is recorded
122
+ where it is produced, and `replay` substitutes the recorded result, so a run is reproducible even
123
+ when a step is not.
124
+
125
+ **One snapshot can run across nodes.** Under a federated placement, a coordinator schedules units of
126
+ work to workers over a shared store. Under the signed federated profile, every node verifies the
127
+ snapshot's signature before it runs anything.
128
+
105
129
  Every step emits evidence. The trace is not a log the runtime chose to write; it is the record of
106
130
  the path actually taken through a graph that was fixed before the run began, which is what makes a
107
131
  run reproducible and reviewable after the fact.
@@ -138,8 +162,19 @@ export PGC_PLATFORM_ROOT=$PWD/software_governance
138
162
  pgc # reports what is installed and whether the anchor resolves
139
163
  ```
140
164
 
141
- `PGC_BUILD_ROOT` (compiled output, keeping the governance repo read-only) and `PGC_DOMAIN_ROOTS`
142
- (additional domains contributing their own `registry/structures`) are optional.
165
+ `PGC_DOMAIN_ROOTS` names an additional domain contributing its own `registry/structures` — the
166
+ directory that *directly contains* it, not the repository above it; pointing one level too high is a
167
+ silent no-op. `PGC_SNAPSHOT_PROFILES` is the directory holding snapshot profiles, required by the
168
+ assembler and the runtime alike.
169
+
170
+ **Where a build writes is declared, not supplied.** Each build configuration names its root in
171
+ `output_configuration.root`, and every layer's output consolidates there. The compiler does not read
172
+ `PGC_SNAPSHOT_ROOT`; the runtime does, with its own meaning — the assembled snapshot to execute.
173
+
174
+ `PGC_BUILD_ROOT` is accepted and reported and **nothing reads it**.
175
+
176
+ The full sequence, with the repositories it needs, is in
177
+ [`pgc_install`](https://github.com/protocol-governed-computing/pgc_install).
143
178
 
144
179
  **Versioning.** Two schemes, and the published version follows the second.
145
180
 
@@ -149,7 +184,7 @@ pgc # reports what is installed and whether the anchor resolves
149
184
  cycle is tagged `release-<N>`. This is not published.
150
185
  - **Public** — `PUBLIC_VERSION`, tagged on every component repository. The platform is at **`v3`**.
151
186
 
152
- **The published version is the public one: `v3` is `3.0.0`.** The standard the packages implement is a
187
+ **The published version is the public one: `v4` is `4.0.0`.** The standard the packages implement is a
153
188
  separate artifact on its own track and is not this number.
154
189
 
155
190
  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: 3.0.0
3
+ Version: 5.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
@@ -22,6 +22,8 @@ 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>=5.0.0; extra == "render"
25
27
  Dynamic: license-file
26
28
 
27
29
  # protocol_runtime
@@ -78,17 +80,17 @@ payload external input (JSON)
78
80
  data-root the state storage boundary; one data root is one instance
79
81
  ```
80
82
 
81
- A run writes an append-only trace alongside the state its declared side effects produce:
83
+ A run writes an append-only trace beside the state its declared side effects produce, both under
84
+ the data root:
82
85
 
83
86
  ```
84
- traces/<TRACE_ID>/
85
- <TRACE_ID>.jsonl append-only execution log
86
- <TRACE_ID>.md human-readable summary
87
- <TRACE_ID>.png the execution path, rendered
88
-
89
- data/
90
- registry/ idempotent state
91
- events/ append-only history
87
+ <data-root>/traces/<domain>/<WF>/<TRACE_ID>/
88
+ <TRACE_ID>.jsonl append-only execution log, one SCHEMA_TRACE_EVENT_V1 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
+
92
+ <data-root>/<domain>/<subdomain>/
93
+ the stores the domain's runtime binding declares
92
94
  ```
93
95
 
94
96
  ## Running
@@ -100,15 +102,18 @@ data/
100
102
  ./run.sh examine /abs/trace.jsonl
101
103
  ```
102
104
 
103
- `run.sh` wraps the CLI, also installed as the `protocol_runtime` console script, with four
104
- subcommands:
105
+ `run.sh` wraps the CLI, also installed as the `protocol_runtime` console script:
105
106
 
106
107
  | command | what it does |
107
108
  |---|---|
108
109
  | `run` | execute a workflow against a data root |
110
+ | `replay` | re-execute a run from its recorded outcomes and compare it with the original |
111
+ | `conformance` | run a compiled domain's test vectors; report each transform proven, unproven or refused |
109
112
  | `boot` | warm-boot the assembled snapshot — load and hash-verify every manifest domain |
110
- | `examine` | analyze a completed trace file |
111
- | `behavior-logic` | render the execution path from a completed trace as a PNG |
113
+ | `coordinator` | serve as the coordinating node of a federated node group |
114
+ | `worker` | serve as a worker node of a federated node group |
115
+ | `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]`) |
112
117
 
113
118
  `PGC_SNAPSHOT_ROOT` overrides the snapshot location; `PGC_IMPL_ROOTS` is the colon-separated set of
114
119
  roots on `PYTHONPATH` for domain capability implementations.
@@ -117,6 +122,12 @@ roots on `PYTHONPATH` for domain capability implementations.
117
122
  establishes that the snapshot is intact and executable before any workflow runs. A surface-only
118
123
  snapshot has no workflow to traverse, and warm reboot is exactly what proves it sound anyway.
119
124
 
125
+ **A process verifies a snapshot once.** `runtime.api` keeps a booted snapshot resident and runs
126
+ against it as often as it is asked. A rewritten manifest, or a different trust anchor or profile
127
+ root, is verified afresh, and a snapshot that no longer verifies is refused. `boot` itself always
128
+ performs the full determination. A governed run then costs a few milliseconds, not a third of a
129
+ second.
130
+
120
131
  **A data root is an instance, not an interface.** Two data roots against the same snapshot are two
121
132
  independent instances of the same governed behavior.
122
133
 
@@ -128,6 +139,21 @@ invoking transforms, applying side effects — and routes on the declared outcom
128
139
  nothing by name at execution time: the compiler assigned integer addresses, and traversal operates
129
140
  on those.
130
141
 
142
+ **A workflow may run one contract at several places.** Each place is a node key, and the contract
143
+ it runs is named separately. The compiler seals each place's routing on its own, and refuses a sealed
144
+ dispatch that does not realize every transition declared at that node. Routing is by node, and the
145
+ trace names the place that ran.
146
+
147
+ **Not every step is determined by its inputs.** A transform is a deterministic atom, a
148
+ non-deterministic atom, or a molecule, and a different constitution governs each. A molecule has no
149
+ implementation: the runtime runs its declared steps. A non-deterministic atom's result is recorded
150
+ where it is produced, and `replay` substitutes the recorded result, so a run is reproducible even
151
+ when a step is not.
152
+
153
+ **One snapshot can run across nodes.** Under a federated placement, a coordinator schedules units of
154
+ work to workers over a shared store. Under the signed federated profile, every node verifies the
155
+ snapshot's signature before it runs anything.
156
+
131
157
  Every step emits evidence. The trace is not a log the runtime chose to write; it is the record of
132
158
  the path actually taken through a graph that was fixed before the run began, which is what makes a
133
159
  run reproducible and reviewable after the fact.
@@ -164,8 +190,19 @@ export PGC_PLATFORM_ROOT=$PWD/software_governance
164
190
  pgc # reports what is installed and whether the anchor resolves
165
191
  ```
166
192
 
167
- `PGC_BUILD_ROOT` (compiled output, keeping the governance repo read-only) and `PGC_DOMAIN_ROOTS`
168
- (additional domains contributing their own `registry/structures`) are optional.
193
+ `PGC_DOMAIN_ROOTS` names an additional domain contributing its own `registry/structures` — the
194
+ directory that *directly contains* it, not the repository above it; pointing one level too high is a
195
+ silent no-op. `PGC_SNAPSHOT_PROFILES` is the directory holding snapshot profiles, required by the
196
+ assembler and the runtime alike.
197
+
198
+ **Where a build writes is declared, not supplied.** Each build configuration names its root in
199
+ `output_configuration.root`, and every layer's output consolidates there. The compiler does not read
200
+ `PGC_SNAPSHOT_ROOT`; the runtime does, with its own meaning — the assembled snapshot to execute.
201
+
202
+ `PGC_BUILD_ROOT` is accepted and reported and **nothing reads it**.
203
+
204
+ The full sequence, with the repositories it needs, is in
205
+ [`pgc_install`](https://github.com/protocol-governed-computing/pgc_install).
169
206
 
170
207
  **Versioning.** Two schemes, and the published version follows the second.
171
208
 
@@ -175,7 +212,7 @@ pgc # reports what is installed and whether the anchor resolves
175
212
  cycle is tagged `release-<N>`. This is not published.
176
213
  - **Public** — `PUBLIC_VERSION`, tagged on every component repository. The platform is at **`v3`**.
177
214
 
178
- **The published version is the public one: `v3` is `3.0.0`.** The standard the packages implement is a
215
+ **The published version is the public one: `v4` is `4.0.0`.** The standard the packages implement is a
179
216
  separate artifact on its own track and is not this number.
180
217
 
181
218
  The standard these packages implement is published separately: https://doi.org/10.5281/zenodo.22150616
@@ -14,6 +14,7 @@ runtime/api.py
14
14
  runtime/boot.py
15
15
  runtime/cli.py
16
16
  runtime/conformance.py
17
+ runtime/coordinator.py
17
18
  runtime/ct_errors.py
18
19
  runtime/ct_execute.py
19
20
  runtime/ct_executor.py
@@ -21,12 +22,16 @@ runtime/dispatcher.py
21
22
  runtime/evidence.py
22
23
  runtime/loader.py
23
24
  runtime/memory.py
25
+ runtime/replay.py
24
26
  runtime/scheduler.py
25
27
  runtime/trace_viz.py
26
28
  runtime/examine/__init__.py
27
- runtime/examine/classifier.py
28
29
  runtime/examine/cli.py
29
- runtime/examine/hint_engine.py
30
- runtime/examine/locator.py
31
30
  runtime/examine/parser.py
32
- runtime/examine/reporter.py
31
+ runtime/examine/reporter.py
32
+ runtime/federation/__init__.py
33
+ runtime/federation/client.py
34
+ runtime/federation/coordinator.py
35
+ runtime/federation/defaults.py
36
+ runtime/federation/store.py
37
+ runtime/federation/worker.py
@@ -1,3 +1,6 @@
1
1
 
2
2
  [reference]
3
3
  pgc-governance>=2.0.0
4
+
5
+ [render]
6
+ pgc-inspector>=5.0.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pgc-runtime"
7
- version = "3.0.0"
7
+ version = "5.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,6 +32,9 @@ 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>=5.0.0"]
35
38
 
36
39
  [project.scripts]
37
40
  protocol_runtime = "runtime.cli:main"
@@ -0,0 +1,173 @@
1
+ """Programmatic runtime entry — run a workflow against a snapshot and return its result surface.
2
+
3
+ This is the in-process core behind `runtime run`: it loads the domain snapshot, opens a trace, drives
4
+ the workflow topology via `scheduler.run_wf`, and returns the terminal status **and result surface**. The
5
+ CLI is a thin wrapper over it, and out-of-process consumers that need the workflow surface programmatically
6
+ (e.g. the change-management validation pipeline) call this directly instead of shelling out and parsing
7
+ stdout or data files.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import functools
12
+ import os
13
+ import threading
14
+ from dataclasses import dataclass
15
+ from pathlib import Path
16
+ from typing import Any
17
+
18
+ from runtime.boot import BootedSnapshot, boot, default_snapshot_root
19
+ from runtime.evidence import TraceWriter, make_trace_id
20
+ from runtime.scheduler import run_wf
21
+
22
+
23
+ @dataclass(frozen=True)
24
+ class RunResult:
25
+ status: str # terminal workflow outcome (e.g. "SUCCESS", "ACK", "VIOLATION")
26
+ surface: dict[str, Any] # workflow result surface (the observable outputs)
27
+ trace_id: str
28
+ trace_dir: Path
29
+
30
+
31
+ def run_workflow(
32
+ *,
33
+ wf_fqdn: str,
34
+ payload: dict[str, Any],
35
+ data_root: str | Path,
36
+ snapshot_root: str | Path | None = None,
37
+ replay_trace: str | Path | None = None,
38
+ ) -> RunResult:
39
+ """Warm-boot the assembled snapshot and execute a workflow; return `(status, surface, trace)`.
40
+
41
+ The snapshot (assembled product) is read-only input, verified via its manifest (root of trust).
42
+ All mutable output is scoped to the instance root `data_root`: CS state and `data_root/traces/`.
43
+ `snapshot_root` defaults to the sibling `../snapshot` when None.
44
+ Raises on load/vocab errors and propagates runtime exceptions (after recording them to the trace).
45
+ """
46
+ data_root = Path(data_root)
47
+ domain = wf_fqdn.split("::")[0]
48
+
49
+ booted = resident(snapshot_root)
50
+ pkg = booted.domains.get(domain)
51
+ if pkg is None:
52
+ raise RuntimeError(
53
+ f"Domain {domain!r} is not in the assembled snapshot "
54
+ f"(manifest domains: {sorted(booted.domains)})."
55
+ )
56
+
57
+ trace_id = make_trace_id(domain, wf_fqdn, payload)
58
+ wf_code = wf_fqdn.split("::")[-1]
59
+ trace_dir = data_root / "traces" / domain / wf_code / trace_id
60
+ trace_dir.mkdir(parents=True, exist_ok=True)
61
+
62
+ wf_addr = pkg.vocab.addr(wf_fqdn) # KeyError if the WF is not in the snapshot vocab
63
+ writer = TraceWriter(trace_dir=trace_dir, trace_id=trace_id, domain=domain,
64
+ wf_addr=wf_addr, wf_fqdn=wf_fqdn, snapshot_root=booted.snapshot_root,
65
+ snapshot_id=booted.snapshot_id)
66
+ if replay_trace is not None:
67
+ # A replay substitutes each recorded outcome of an atom declared not deterministic, so it
68
+ # reproduces the original determination rather than drawing a new one.
69
+ from runtime.replay import recorded_outcomes
70
+ writer.replay_from(recorded_outcomes(Path(replay_trace)))
71
+ try:
72
+ status, surface = run_wf(wf_fqdn=wf_fqdn, payload=payload, pkg=pkg,
73
+ writer=writer, data_root=str(data_root))
74
+ except Exception as exc:
75
+ writer.error(str(exc))
76
+ raise
77
+ finally:
78
+ # The JSONL is the evidence. A picture of the path is a view of it, drawn on request
79
+ # (`run --behavior-logic`, or `behavior-logic <trace>`), never as a cost of every run.
80
+ writer.close()
81
+
82
+ return RunResult(status=status, surface=surface or {}, trace_id=trace_id, trace_dir=trace_dir)
83
+
84
+
85
+ # Warm boot is load once, verify once. Every call booting afresh re-verified the whole snapshot from
86
+ # its bytes on each run — about 60% of a run's time, repeated by every server, worker and suite that
87
+ # calls this in a loop. The package a boot produces is frozen, and nothing reads the snapshot again
88
+ # after it, so one verified boot serves every run against the same snapshot.
89
+ #
90
+ # What makes it the same snapshot is the manifest — its identity, and the file itself — together
91
+ # with what acceptance was judged against: the trust anchor and the profile root. A rebuild in place
92
+ # rewrites the manifest, so it is verified afresh. `boot()` itself is never cached: the CLI's `boot`
93
+ # and anything asking for acceptance get the full determination every time.
94
+ _RESIDENT: dict[tuple, BootedSnapshot] = {}
95
+ _RESIDENT_LOCK = threading.Lock()
96
+ _RESIDENT_LIMIT = 4
97
+
98
+
99
+ def _resident_key(root: Path) -> tuple | None:
100
+ manifest = root / "manifest.json"
101
+ try:
102
+ stat = manifest.stat()
103
+ except FileNotFoundError:
104
+ return None
105
+ return (str(root), stat.st_mtime_ns, stat.st_size,
106
+ os.environ.get("PGC_TRUST_ROOT_PUBKEY"), os.environ.get("PGC_SNAPSHOT_PROFILES"))
107
+
108
+
109
+ def resident(snapshot_root: str | Path | None = None) -> BootedSnapshot:
110
+ """The booted snapshot at `snapshot_root`, verified once per process and per manifest."""
111
+ given = Path(snapshot_root) if snapshot_root is not None else default_snapshot_root()
112
+ key = _resident_key(given.resolve())
113
+ if key is None:
114
+ return boot(given) # no manifest: let boot refuse it, and say why
115
+ with _RESIDENT_LOCK:
116
+ booted = _RESIDENT.get(key)
117
+ if booted is None:
118
+ booted = boot(given)
119
+ if len(_RESIDENT) >= _RESIDENT_LIMIT:
120
+ _RESIDENT.pop(next(iter(_RESIDENT)))
121
+ _RESIDENT[key] = booted
122
+ return booted
123
+
124
+
125
+ @functools.lru_cache(maxsize=8)
126
+ def _placement(snapshot_root: Path) -> str | None:
127
+ # The snapshot is immutable, so what it was permitted cannot change under a running process.
128
+ from runtime.coordinator import sealed_placement_mode
129
+ return sealed_placement_mode(snapshot_root)
130
+
131
+
132
+ def invoke_workflow(
133
+ *,
134
+ wf_fqdn: str,
135
+ payload: dict[str, Any],
136
+ data_root: str | Path,
137
+ snapshot_root: str | Path | None = None,
138
+ ) -> RunResult:
139
+ """Have a workflow executed wherever the sealed composition places execution.
140
+
141
+ The entry point for a party that receives work but is not itself where work is placed — the
142
+ interaction boundary. Under `FEDERATED_NODE` the unit goes to the coordinator named by
143
+ `PGC_COORDINATOR_URL` and a worker executes it; under every other placement it runs here through
144
+ `run_workflow`. Either way the result is the same `RunResult`, with `trace_dir` under the shared
145
+ `data_root`.
146
+
147
+ Under `FEDERATED_NODE` this waits for the unit's outcome without limit once it is admitted, so
148
+ a failure it raises is always one in which nothing was admitted (see `submit_and_wait`).
149
+
150
+ Placement is read from the snapshot, not from configuration: a boundary that chose its own
151
+ arrangement would be granting itself what the composition was or was not permitted. Execution
152
+ itself never consults placement — this decides only where `run_workflow` is called.
153
+ """
154
+ root = Path(snapshot_root) if snapshot_root is not None else default_snapshot_root()
155
+ if _placement(root.resolve()) != "FEDERATED_NODE":
156
+ return run_workflow(wf_fqdn=wf_fqdn, payload=payload, data_root=data_root, snapshot_root=root)
157
+
158
+ from runtime.federation.client import submit_and_wait
159
+
160
+ coordinator_url = os.environ.get("PGC_COORDINATOR_URL")
161
+ if not coordinator_url:
162
+ raise RuntimeError(
163
+ "the snapshot places execution on FEDERATED_NODE and PGC_COORDINATOR_URL is not set: "
164
+ "this node does not execute, and has nowhere to send the work."
165
+ )
166
+ booted = resident(root) # a boundary acts on no snapshot it has not authenticated (OB-1)
167
+ outcome = submit_and_wait(coordinator_url.rstrip("/"), wf_fqdn=wf_fqdn, payload=payload,
168
+ snapshot_id=booted.snapshot_id)
169
+ if outcome.get("state") != "executed":
170
+ raise RuntimeError(f"unit {outcome.get('unit_id')} failed on {outcome.get('worker')}: "
171
+ f"{outcome.get('detail')}")
172
+ return RunResult(status=outcome["status"], surface=outcome.get("surface") or {},
173
+ trace_id=outcome["trace_id"], trace_dir=Path(data_root) / outcome["trace_dir"])