pgc-runtime 4.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.
- {pgc_runtime-4.0.0/pgc_runtime.egg-info → pgc_runtime-5.0.0}/PKG-INFO +47 -20
- {pgc_runtime-4.0.0 → pgc_runtime-5.0.0}/README.md +44 -19
- {pgc_runtime-4.0.0 → pgc_runtime-5.0.0/pgc_runtime.egg-info}/PKG-INFO +47 -20
- {pgc_runtime-4.0.0 → pgc_runtime-5.0.0}/pgc_runtime.egg-info/SOURCES.txt +9 -4
- {pgc_runtime-4.0.0 → pgc_runtime-5.0.0}/pgc_runtime.egg-info/requires.txt +3 -0
- {pgc_runtime-4.0.0 → pgc_runtime-5.0.0}/pyproject.toml +4 -1
- pgc_runtime-5.0.0/runtime/api.py +173 -0
- {pgc_runtime-4.0.0 → pgc_runtime-5.0.0}/runtime/boot.py +36 -0
- {pgc_runtime-4.0.0 → pgc_runtime-5.0.0}/runtime/cli.py +136 -15
- pgc_runtime-5.0.0/runtime/conformance.py +353 -0
- pgc_runtime-5.0.0/runtime/coordinator.py +184 -0
- {pgc_runtime-4.0.0 → pgc_runtime-5.0.0}/runtime/ct_execute.py +3 -1
- {pgc_runtime-4.0.0 → pgc_runtime-5.0.0}/runtime/ct_executor.py +127 -24
- {pgc_runtime-4.0.0 → pgc_runtime-5.0.0}/runtime/dispatcher.py +12 -4
- {pgc_runtime-4.0.0 → pgc_runtime-5.0.0}/runtime/evidence.py +62 -9
- pgc_runtime-5.0.0/runtime/examine/__init__.py +49 -0
- pgc_runtime-5.0.0/runtime/examine/parser.py +73 -0
- pgc_runtime-5.0.0/runtime/examine/reporter.py +57 -0
- pgc_runtime-5.0.0/runtime/federation/__init__.py +16 -0
- pgc_runtime-5.0.0/runtime/federation/client.py +106 -0
- pgc_runtime-5.0.0/runtime/federation/coordinator.py +106 -0
- pgc_runtime-5.0.0/runtime/federation/defaults.py +22 -0
- pgc_runtime-5.0.0/runtime/federation/store.py +85 -0
- pgc_runtime-5.0.0/runtime/federation/worker.py +72 -0
- {pgc_runtime-4.0.0 → pgc_runtime-5.0.0}/runtime/loader.py +27 -32
- pgc_runtime-5.0.0/runtime/replay.py +77 -0
- {pgc_runtime-4.0.0 → pgc_runtime-5.0.0}/runtime/scheduler.py +36 -27
- pgc_runtime-5.0.0/runtime/trace_viz.py +233 -0
- pgc_runtime-4.0.0/runtime/api.py +0 -71
- pgc_runtime-4.0.0/runtime/conformance.py +0 -249
- pgc_runtime-4.0.0/runtime/examine/__init__.py +0 -126
- pgc_runtime-4.0.0/runtime/examine/classifier.py +0 -350
- pgc_runtime-4.0.0/runtime/examine/hint_engine.py +0 -245
- pgc_runtime-4.0.0/runtime/examine/locator.py +0 -148
- pgc_runtime-4.0.0/runtime/examine/parser.py +0 -223
- pgc_runtime-4.0.0/runtime/examine/reporter.py +0 -110
- pgc_runtime-4.0.0/runtime/trace_viz.py +0 -276
- {pgc_runtime-4.0.0 → pgc_runtime-5.0.0}/LICENSE +0 -0
- {pgc_runtime-4.0.0 → pgc_runtime-5.0.0}/MANIFEST.in +0 -0
- {pgc_runtime-4.0.0 → pgc_runtime-5.0.0}/NOTICE +0 -0
- {pgc_runtime-4.0.0 → pgc_runtime-5.0.0}/pgc_runtime.egg-info/dependency_links.txt +0 -0
- {pgc_runtime-4.0.0 → pgc_runtime-5.0.0}/pgc_runtime.egg-info/entry_points.txt +0 -0
- {pgc_runtime-4.0.0 → pgc_runtime-5.0.0}/pgc_runtime.egg-info/top_level.txt +0 -0
- {pgc_runtime-4.0.0 → pgc_runtime-5.0.0}/runtime/__init__.py +0 -0
- {pgc_runtime-4.0.0 → pgc_runtime-5.0.0}/runtime/ct_errors.py +0 -0
- {pgc_runtime-4.0.0 → pgc_runtime-5.0.0}/runtime/examine/cli.py +0 -0
- {pgc_runtime-4.0.0 → pgc_runtime-5.0.0}/runtime/memory.py +0 -0
- {pgc_runtime-4.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
|
+
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
|
|
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>.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
data
|
|
90
|
-
|
|
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
|
|
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
|
-
| `
|
|
111
|
-
| `
|
|
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.
|
|
@@ -166,13 +192,14 @@ pgc # reports what is installed and whether the anchor resolves
|
|
|
166
192
|
|
|
167
193
|
`PGC_DOMAIN_ROOTS` names an additional domain contributing its own `registry/structures` — the
|
|
168
194
|
directory that *directly contains* it, not the repository above it; pointing one level too high is a
|
|
169
|
-
silent no-op. `
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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.
|
|
173
201
|
|
|
174
|
-
`PGC_BUILD_ROOT` is accepted and reported and **nothing reads it
|
|
175
|
-
anchor that controls output.
|
|
202
|
+
`PGC_BUILD_ROOT` is accepted and reported and **nothing reads it**.
|
|
176
203
|
|
|
177
204
|
The full sequence, with the repositories it needs, is in
|
|
178
205
|
[`pgc_install`](https://github.com/protocol-governed-computing/pgc_install).
|
|
@@ -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
|
|
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>.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
data
|
|
64
|
-
|
|
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
|
|
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
|
-
| `
|
|
85
|
-
| `
|
|
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.
|
|
@@ -140,13 +164,14 @@ pgc # reports what is installed and whether the anchor resolves
|
|
|
140
164
|
|
|
141
165
|
`PGC_DOMAIN_ROOTS` names an additional domain contributing its own `registry/structures` — the
|
|
142
166
|
directory that *directly contains* it, not the repository above it; pointing one level too high is a
|
|
143
|
-
silent no-op. `
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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.
|
|
147
173
|
|
|
148
|
-
`PGC_BUILD_ROOT` is accepted and reported and **nothing reads it
|
|
149
|
-
anchor that controls output.
|
|
174
|
+
`PGC_BUILD_ROOT` is accepted and reported and **nothing reads it**.
|
|
150
175
|
|
|
151
176
|
The full sequence, with the repositories it needs, is in
|
|
152
177
|
[`pgc_install`](https://github.com/protocol-governed-computing/pgc_install).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pgc-runtime
|
|
3
|
-
Version:
|
|
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
|
|
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>.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
data
|
|
90
|
-
|
|
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
|
|
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
|
-
| `
|
|
111
|
-
| `
|
|
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.
|
|
@@ -166,13 +192,14 @@ pgc # reports what is installed and whether the anchor resolves
|
|
|
166
192
|
|
|
167
193
|
`PGC_DOMAIN_ROOTS` names an additional domain contributing its own `registry/structures` — the
|
|
168
194
|
directory that *directly contains* it, not the repository above it; pointing one level too high is a
|
|
169
|
-
silent no-op. `
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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.
|
|
173
201
|
|
|
174
|
-
`PGC_BUILD_ROOT` is accepted and reported and **nothing reads it
|
|
175
|
-
anchor that controls output.
|
|
202
|
+
`PGC_BUILD_ROOT` is accepted and reported and **nothing reads it**.
|
|
176
203
|
|
|
177
204
|
The full sequence, with the repositories it needs, is in
|
|
178
205
|
[`pgc_install`](https://github.com/protocol-governed-computing/pgc_install).
|
|
@@ -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
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pgc-runtime"
|
|
7
|
-
version = "
|
|
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"])
|
|
@@ -107,6 +107,42 @@ def boot(snapshot_root: str | Path | None = None) -> BootedSnapshot:
|
|
|
107
107
|
raise RuntimeError(f"Snapshot refused at acceptance: {exc}") from exc
|
|
108
108
|
recomputed = manifest.get("snapshot_id")
|
|
109
109
|
|
|
110
|
+
# 2a. AUTHENTICATION — who vouches for what acceptance just established.
|
|
111
|
+
#
|
|
112
|
+
# Acceptance answers whether this snapshot is internally whole. It cannot answer who sealed
|
|
113
|
+
# it: a party who replaced every constituent and recomputed the identity produces a snapshot
|
|
114
|
+
# that accepts perfectly. Authentication is the separate question, and it is answered against
|
|
115
|
+
# a key this node holds rather than anything the snapshot carries — a snapshot bearing the key
|
|
116
|
+
# that authenticates it authenticates nothing, because whoever replaced the snapshot would
|
|
117
|
+
# replace the key with it.
|
|
118
|
+
#
|
|
119
|
+
# Holding an anchor is what makes a node require one. A platform whose profile does not select
|
|
120
|
+
# signing boots an unsigned snapshot exactly as before; this adds a condition to nodes that
|
|
121
|
+
# have taken one on, and none to nodes that have not.
|
|
122
|
+
#
|
|
123
|
+
# Imported from the assembler for the same reason acceptance is: one determination, two
|
|
124
|
+
# callers, rather than a second implementation that can disagree with the first.
|
|
125
|
+
anchor = os.environ.get("PGC_TRUST_ROOT_PUBKEY")
|
|
126
|
+
if anchor:
|
|
127
|
+
from assembler import signing
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
record = signing.read_signature(root)
|
|
131
|
+
except signing.SignatureMalformed as exc:
|
|
132
|
+
raise RuntimeError(f"Snapshot refused at authentication: {exc}") from exc
|
|
133
|
+
if record is None:
|
|
134
|
+
raise RuntimeError(
|
|
135
|
+
"Snapshot refused at authentication: this node anchors a trust root and the "
|
|
136
|
+
"snapshot carries no signature. An unsigned snapshot is not one this node accepts."
|
|
137
|
+
)
|
|
138
|
+
try:
|
|
139
|
+
key = signing.verify_identity(recomputed, record, Path(anchor))
|
|
140
|
+
except (signing.SignatureError, signing.SigningUnavailable, OSError) as exc:
|
|
141
|
+
raise RuntimeError(
|
|
142
|
+
f"Snapshot refused at authentication: {type(exc).__name__}: {exc}"
|
|
143
|
+
) from exc
|
|
144
|
+
print(f"[runtime] Authenticated under trust root {key}")
|
|
145
|
+
|
|
110
146
|
# 3-4. per-domain load, anchored to the manifest's tokenized hash
|
|
111
147
|
domains: dict[str, RuntimePackage] = {}
|
|
112
148
|
for d in domains_meta:
|