pgc-transport 6.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pgc_transport-6.0.0/LICENSE +67 -0
- pgc_transport-6.0.0/NOTICE +11 -0
- pgc_transport-6.0.0/PKG-INFO +131 -0
- pgc_transport-6.0.0/README.md +107 -0
- pgc_transport-6.0.0/pgc_transport/__init__.py +1 -0
- pgc_transport-6.0.0/pgc_transport/adapters/__init__.py +1 -0
- pgc_transport-6.0.0/pgc_transport/adapters/cli/__init__.py +1 -0
- pgc_transport-6.0.0/pgc_transport/adapters/cli/main.py +99 -0
- pgc_transport-6.0.0/pgc_transport/adapters/http/__init__.py +1 -0
- pgc_transport-6.0.0/pgc_transport/adapters/http/binding.py +81 -0
- pgc_transport-6.0.0/pgc_transport/adapters/http/server.py +229 -0
- pgc_transport-6.0.0/pgc_transport/request.py +26 -0
- pgc_transport-6.0.0/pgc_transport/resolver/__init__.py +1 -0
- pgc_transport-6.0.0/pgc_transport/resolver/registry.py +73 -0
- pgc_transport-6.0.0/pgc_transport/resolver/resolver.py +282 -0
- pgc_transport-6.0.0/pgc_transport.egg-info/PKG-INFO +131 -0
- pgc_transport-6.0.0/pgc_transport.egg-info/SOURCES.txt +20 -0
- pgc_transport-6.0.0/pgc_transport.egg-info/dependency_links.txt +1 -0
- pgc_transport-6.0.0/pgc_transport.egg-info/entry_points.txt +2 -0
- pgc_transport-6.0.0/pgc_transport.egg-info/top_level.txt +1 -0
- pgc_transport-6.0.0/pyproject.toml +55 -0
- pgc_transport-6.0.0/setup.cfg +4 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
Copyright 2026 Bhash Ganti aka Bachi
|
|
6
|
+
|
|
7
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
you may not use this file except in compliance with the License.
|
|
9
|
+
You may obtain a copy of the License at
|
|
10
|
+
|
|
11
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
|
|
13
|
+
Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
See the License for the specific language governing permissions and
|
|
17
|
+
limitations under the License.
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
------------------------
|
|
21
|
+
FULL LICENSE TEXT BELOW
|
|
22
|
+
------------------------
|
|
23
|
+
|
|
24
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
25
|
+
|
|
26
|
+
1. Definitions.
|
|
27
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
28
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
29
|
+
"Licensor" shall mean the copyright owner.
|
|
30
|
+
"Legal Entity" shall mean the union of the acting entity and all other
|
|
31
|
+
entities that control, are controlled by, or are under common control.
|
|
32
|
+
"You" shall mean an individual or Legal Entity exercising permissions.
|
|
33
|
+
"Source" form shall mean the preferred form for making modifications.
|
|
34
|
+
"Object" form shall mean any form resulting from mechanical transformation.
|
|
35
|
+
"Work" shall mean the work of authorship.
|
|
36
|
+
"Derivative Works" shall mean any work based on the Work.
|
|
37
|
+
"Contribution" shall mean any work intentionally submitted.
|
|
38
|
+
"Contributor" shall mean Licensor and any individual submitting Contributions.
|
|
39
|
+
|
|
40
|
+
2. Grant of Copyright License.
|
|
41
|
+
Each Contributor grants You a perpetual, worldwide, non-exclusive,
|
|
42
|
+
no-charge, royalty-free copyright license.
|
|
43
|
+
|
|
44
|
+
3. Grant of Patent License.
|
|
45
|
+
Each Contributor grants a patent license to make, use, sell, etc.
|
|
46
|
+
|
|
47
|
+
4. Redistribution.
|
|
48
|
+
You may reproduce and distribute copies provided that:
|
|
49
|
+
- You include a copy of this License
|
|
50
|
+
- You retain notices
|
|
51
|
+
- You state modifications
|
|
52
|
+
- You include NOTICE file if present
|
|
53
|
+
|
|
54
|
+
5. Submission of Contributions.
|
|
55
|
+
Contributions are under this License unless stated otherwise.
|
|
56
|
+
|
|
57
|
+
6. Trademarks.
|
|
58
|
+
This License does not grant trademark rights.
|
|
59
|
+
|
|
60
|
+
7. Disclaimer of Warranty.
|
|
61
|
+
Provided "AS IS", without warranties.
|
|
62
|
+
|
|
63
|
+
8. Limitation of Liability.
|
|
64
|
+
No liability for damages.
|
|
65
|
+
|
|
66
|
+
9. Accepting Warranty or Additional Liability.
|
|
67
|
+
You may offer support/warranty on your own behalf only.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
PGC — Protocol-Governed Computing
|
|
2
|
+
Copyright 2026 Bhash Ganti aka Bachi
|
|
3
|
+
|
|
4
|
+
This project introduces a protocol-first execution model in which:
|
|
5
|
+
|
|
6
|
+
- Behavior is declared in protocol artifacts
|
|
7
|
+
- Execution is performed by a deterministic runtime
|
|
8
|
+
- Capability implementations are bound at compile time
|
|
9
|
+
- Governance is enforced through invariants and assertions
|
|
10
|
+
|
|
11
|
+
Extensibility is achieved by declaration, not refactor.
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pgc-transport
|
|
3
|
+
Version: 6.0.0
|
|
4
|
+
Summary: Protocol-Governed Computing — domain-neutral transport boundary engine (canonical contracts, generic resolver, protocol adapters)
|
|
5
|
+
Author-email: Bhash Ganti <bachipeachy@gmail.com>
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://omnibachi.org/
|
|
8
|
+
Project-URL: Repository, https://github.com/protocol-governed-computing/protocol_transport
|
|
9
|
+
Keywords: protocol-governed-computing,pgc,transport,ingress,egress,http,gateway
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Topic :: Software Development
|
|
18
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
License-File: NOTICE
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# protocol_transport
|
|
26
|
+
|
|
27
|
+
**The PGC transport boundary engine** — a domain-neutral, protocol-neutral governed
|
|
28
|
+
boundary between external interaction protocols (HTTP, RPC, CLI) and a PGC execution
|
|
29
|
+
universe.
|
|
30
|
+
|
|
31
|
+
It knows *how* to transport. It declares nothing about *what* is transported: every route,
|
|
32
|
+
operation, and workflow binding arrives as configuration the engine is **pointed at**, never
|
|
33
|
+
hardcoded. The domain/platform declares what may be transported.
|
|
34
|
+
|
|
35
|
+
> **Semantic authority:** [`TRANSPORT_STANDARD_V0.md`](TRANSPORT_STANDARD_V0.md)
|
|
36
|
+
> (Phase-1 frozen). Read it first. For working in this repo, see
|
|
37
|
+
> [`CLAUDE.md`](CLAUDE.md).
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## The invariant
|
|
42
|
+
|
|
43
|
+
> Many external protocols may bind to one **Operation Identity**; one Operation Identity
|
|
44
|
+
> resolves to one governed PGC invocation contract; the external protocol is replaceable,
|
|
45
|
+
> while the governed interaction remains stable.
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
HTTP / RPC / CLI
|
|
49
|
+
│ (external protocol)
|
|
50
|
+
▼
|
|
51
|
+
Protocol Adapter ─ translates wire ↔ Canonical Transport Request/Response (owns no domain meaning)
|
|
52
|
+
▼
|
|
53
|
+
Operation Identity ─ stable public name (e.g. collatz.compute); NEVER a workflow id
|
|
54
|
+
▼
|
|
55
|
+
TI → handler → TE ─ ingress contract → invocation → egress classification
|
|
56
|
+
▼
|
|
57
|
+
Canonical Response ─ protocol-neutral { outcome, result_class, result, evidence, errors }
|
|
58
|
+
▼
|
|
59
|
+
Response Projection ─ adapter maps Result Class → HTTP status / RPC error / CLI exit
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`TI`/`TE` and the workflows they bind to are **domain-governed and live outside this repo**
|
|
63
|
+
(in the platform/domain surface). This repo is only the engine.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Layout
|
|
68
|
+
|
|
69
|
+
| Path | Role |
|
|
70
|
+
|------|------|
|
|
71
|
+
| `contracts/` | Canonical, protocol-neutral interop objects — `TRANSPORT_REQUEST_V0`, `TRANSPORT_RESPONSE_V0` |
|
|
72
|
+
| `pgc_transport/request.py` | The Canonical Transport Request every adapter builds. No protocol in it. |
|
|
73
|
+
| `pgc_transport/resolver/` | The generic resolution engine: `operation → TI → handler → TE → Canonical Response`. Zero domain knowledge. |
|
|
74
|
+
| `pgc_transport/adapters/http/` | HTTP mechanics: static mounts, the External Protocol Binding table (data), Response Projection |
|
|
75
|
+
| `pgc_transport/adapters/cli/` | The `pgc_transport` command: one request from the command line, and the HTTP adapter's launcher |
|
|
76
|
+
| `TRANSPORT_STANDARD_V0.md` | The frozen semantic model |
|
|
77
|
+
| `run_http.sh` | Domain-neutral launcher (env-provisioned; names no workload) |
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## The acid test
|
|
82
|
+
|
|
83
|
+
The engine holds **no domain knowledge**. Grep `pgc_transport/` for any workload
|
|
84
|
+
or field name (`collatz`, `number`, `steps`, a route literal) and you find nothing. Everything
|
|
85
|
+
domain-specific is data the engine is pointed at:
|
|
86
|
+
|
|
87
|
+
- **operations** (`TI`/`TE` declarations) via `PGC_OPERATIONS_ROOTS`
|
|
88
|
+
- **routes → operations** via the External Protocol Binding JSON (`PGC_HTTP_BINDINGS`)
|
|
89
|
+
- **static content** via `PGC_STATIC_MOUNTS`
|
|
90
|
+
|
|
91
|
+
Adding an operation is additive — a new `TI`/`TE` pair plus a binding line — with **no engine
|
|
92
|
+
change**.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Running
|
|
97
|
+
|
|
98
|
+
`run_http.sh` is generic and requires the environment to point it at roots. A concrete
|
|
99
|
+
composition (the platform reference surface) lives at
|
|
100
|
+
`conformance_workloads/workloads/collatz/client/serve.sh`, which supplies every `PGC_*` variable and execs this
|
|
101
|
+
launcher:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
./conformance_workloads/workloads/collatz/client/serve.sh # serves the reference surface on :8000
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Installed as `pgc-transport`, the same boundary is the `pgc_transport` command. It is pointed at a
|
|
108
|
+
snapshot and an instance root, like the HTTP adapter:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
export PGC_SNAPSHOT_ROOT=snapshot PGC_DATA_ROOT=data
|
|
112
|
+
pgc_transport call collatz.compute '{"number": 27}' # prints the Canonical Transport Response
|
|
113
|
+
pgc_transport http # serves the HTTP adapter, as run_http.sh does
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`call` exits with the adapter's projection of the Result Class: `0` SUCCESS, `1` VIOLATION, `3`
|
|
117
|
+
UNAUTHORIZED, `4` NOT_FOUND, `5` OPERATION_NOT_FOUND, `6` EXECUTION_FAILURE. `2` means the
|
|
118
|
+
invocation itself was malformed and never reached the boundary.
|
|
119
|
+
|
|
120
|
+
See [`CLAUDE.md`](CLAUDE.md) for the full configuration surface and architectural rules.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Layer position
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
external world → transport (adapter → resolver) → runtime (executes) → transport (TE → adapter) → external world
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
The engine's only cross-repo dependency is the runtime **execution interface**
|
|
131
|
+
(`runtime.api.invoke_workflow`) — it never imports the compiler, protocol, or any domain.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# protocol_transport
|
|
2
|
+
|
|
3
|
+
**The PGC transport boundary engine** — a domain-neutral, protocol-neutral governed
|
|
4
|
+
boundary between external interaction protocols (HTTP, RPC, CLI) and a PGC execution
|
|
5
|
+
universe.
|
|
6
|
+
|
|
7
|
+
It knows *how* to transport. It declares nothing about *what* is transported: every route,
|
|
8
|
+
operation, and workflow binding arrives as configuration the engine is **pointed at**, never
|
|
9
|
+
hardcoded. The domain/platform declares what may be transported.
|
|
10
|
+
|
|
11
|
+
> **Semantic authority:** [`TRANSPORT_STANDARD_V0.md`](TRANSPORT_STANDARD_V0.md)
|
|
12
|
+
> (Phase-1 frozen). Read it first. For working in this repo, see
|
|
13
|
+
> [`CLAUDE.md`](CLAUDE.md).
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## The invariant
|
|
18
|
+
|
|
19
|
+
> Many external protocols may bind to one **Operation Identity**; one Operation Identity
|
|
20
|
+
> resolves to one governed PGC invocation contract; the external protocol is replaceable,
|
|
21
|
+
> while the governed interaction remains stable.
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
HTTP / RPC / CLI
|
|
25
|
+
│ (external protocol)
|
|
26
|
+
▼
|
|
27
|
+
Protocol Adapter ─ translates wire ↔ Canonical Transport Request/Response (owns no domain meaning)
|
|
28
|
+
▼
|
|
29
|
+
Operation Identity ─ stable public name (e.g. collatz.compute); NEVER a workflow id
|
|
30
|
+
▼
|
|
31
|
+
TI → handler → TE ─ ingress contract → invocation → egress classification
|
|
32
|
+
▼
|
|
33
|
+
Canonical Response ─ protocol-neutral { outcome, result_class, result, evidence, errors }
|
|
34
|
+
▼
|
|
35
|
+
Response Projection ─ adapter maps Result Class → HTTP status / RPC error / CLI exit
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`TI`/`TE` and the workflows they bind to are **domain-governed and live outside this repo**
|
|
39
|
+
(in the platform/domain surface). This repo is only the engine.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Layout
|
|
44
|
+
|
|
45
|
+
| Path | Role |
|
|
46
|
+
|------|------|
|
|
47
|
+
| `contracts/` | Canonical, protocol-neutral interop objects — `TRANSPORT_REQUEST_V0`, `TRANSPORT_RESPONSE_V0` |
|
|
48
|
+
| `pgc_transport/request.py` | The Canonical Transport Request every adapter builds. No protocol in it. |
|
|
49
|
+
| `pgc_transport/resolver/` | The generic resolution engine: `operation → TI → handler → TE → Canonical Response`. Zero domain knowledge. |
|
|
50
|
+
| `pgc_transport/adapters/http/` | HTTP mechanics: static mounts, the External Protocol Binding table (data), Response Projection |
|
|
51
|
+
| `pgc_transport/adapters/cli/` | The `pgc_transport` command: one request from the command line, and the HTTP adapter's launcher |
|
|
52
|
+
| `TRANSPORT_STANDARD_V0.md` | The frozen semantic model |
|
|
53
|
+
| `run_http.sh` | Domain-neutral launcher (env-provisioned; names no workload) |
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## The acid test
|
|
58
|
+
|
|
59
|
+
The engine holds **no domain knowledge**. Grep `pgc_transport/` for any workload
|
|
60
|
+
or field name (`collatz`, `number`, `steps`, a route literal) and you find nothing. Everything
|
|
61
|
+
domain-specific is data the engine is pointed at:
|
|
62
|
+
|
|
63
|
+
- **operations** (`TI`/`TE` declarations) via `PGC_OPERATIONS_ROOTS`
|
|
64
|
+
- **routes → operations** via the External Protocol Binding JSON (`PGC_HTTP_BINDINGS`)
|
|
65
|
+
- **static content** via `PGC_STATIC_MOUNTS`
|
|
66
|
+
|
|
67
|
+
Adding an operation is additive — a new `TI`/`TE` pair plus a binding line — with **no engine
|
|
68
|
+
change**.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Running
|
|
73
|
+
|
|
74
|
+
`run_http.sh` is generic and requires the environment to point it at roots. A concrete
|
|
75
|
+
composition (the platform reference surface) lives at
|
|
76
|
+
`conformance_workloads/workloads/collatz/client/serve.sh`, which supplies every `PGC_*` variable and execs this
|
|
77
|
+
launcher:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
./conformance_workloads/workloads/collatz/client/serve.sh # serves the reference surface on :8000
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Installed as `pgc-transport`, the same boundary is the `pgc_transport` command. It is pointed at a
|
|
84
|
+
snapshot and an instance root, like the HTTP adapter:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
export PGC_SNAPSHOT_ROOT=snapshot PGC_DATA_ROOT=data
|
|
88
|
+
pgc_transport call collatz.compute '{"number": 27}' # prints the Canonical Transport Response
|
|
89
|
+
pgc_transport http # serves the HTTP adapter, as run_http.sh does
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`call` exits with the adapter's projection of the Result Class: `0` SUCCESS, `1` VIOLATION, `3`
|
|
93
|
+
UNAUTHORIZED, `4` NOT_FOUND, `5` OPERATION_NOT_FOUND, `6` EXECUTION_FAILURE. `2` means the
|
|
94
|
+
invocation itself was malformed and never reached the boundary.
|
|
95
|
+
|
|
96
|
+
See [`CLAUDE.md`](CLAUDE.md) for the full configuration surface and architectural rules.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Layer position
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
external world → transport (adapter → resolver) → runtime (executes) → transport (TE → adapter) → external world
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
The engine's only cross-repo dependency is the runtime **execution interface**
|
|
107
|
+
(`runtime.api.invoke_workflow`) — it never imports the compiler, protocol, or any domain.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""PGC transport boundary: the protocol-neutral resolver and its protocol adapters."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Protocol adapters (Layer 2) — thin, non-authorial boundary translators."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Command-line protocol adapter."""
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Command-line Protocol Adapter — the `pgc_transport` command.
|
|
2
|
+
|
|
3
|
+
Layer 2 (Transport Standard V0), the command-line counterpart of the HTTP adapter:
|
|
4
|
+
|
|
5
|
+
pgc_transport call <operation> '<input as JSON>'
|
|
6
|
+
Translate one invocation into a Canonical Transport Request, hand it to the
|
|
7
|
+
protocol-neutral resolver, and print the Canonical Transport Response as JSON. The
|
|
8
|
+
exit status is this adapter's Response Projection (§7, RESPONSE_PROJECTION_EXTERNAL):
|
|
9
|
+
the Result-Class → exit-status table lives HERE, never in a TE.
|
|
10
|
+
|
|
11
|
+
pgc_transport http
|
|
12
|
+
Serve the HTTP adapter (`pgc_transport.adapters.http.server`), configured exactly as `run_http.sh`
|
|
13
|
+
configures it.
|
|
14
|
+
|
|
15
|
+
Both are POINTED at everything through the environment (ADAPTER_NON_AUTHORIAL):
|
|
16
|
+
PGC_SNAPSHOT_ROOT the assembled snapshot the boundary is read from
|
|
17
|
+
PGC_DATA_ROOT the instance root execution writes traces and stored state under
|
|
18
|
+
plus, for `http`, the variables `run_http.sh` documents. Nothing here names a workload,
|
|
19
|
+
an operation or a field: the operation is whatever the caller names, and the snapshot's TI
|
|
20
|
+
alone decides whether to admit its input.
|
|
21
|
+
|
|
22
|
+
Stdlib only.
|
|
23
|
+
"""
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import argparse
|
|
27
|
+
import json
|
|
28
|
+
import os
|
|
29
|
+
import sys
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
|
|
32
|
+
from pgc_transport.request import to_canonical_request
|
|
33
|
+
from pgc_transport.resolver.registry import load_registry
|
|
34
|
+
from pgc_transport.resolver.resolver import resolve
|
|
35
|
+
|
|
36
|
+
# Response Projection (adapter-owned; §7). Result Class -> exit status. Distinct statuses keep
|
|
37
|
+
# the classes apart for a caller that cannot read the body; the body carries the class itself.
|
|
38
|
+
_EXIT_STATUS = {
|
|
39
|
+
"SUCCESS": 0,
|
|
40
|
+
"VIOLATION": 1,
|
|
41
|
+
"UNAUTHORIZED": 3,
|
|
42
|
+
"NOT_FOUND": 4,
|
|
43
|
+
"OPERATION_NOT_FOUND": 5,
|
|
44
|
+
"EXECUTION_FAILURE": 6,
|
|
45
|
+
}
|
|
46
|
+
_USAGE = 2 # as argparse uses it: the invocation never reached the boundary
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _refuse(message: str) -> None:
|
|
50
|
+
print(f"pgc_transport: {message}", file=sys.stderr)
|
|
51
|
+
raise SystemExit(_USAGE)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _required_env(name: str) -> str:
|
|
55
|
+
value = os.environ.get(name)
|
|
56
|
+
if not value:
|
|
57
|
+
_refuse(f"{name} is not set. The adapter reads nothing it is not pointed at.")
|
|
58
|
+
return value
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _call(operation: str, raw_input: str) -> int:
|
|
62
|
+
try:
|
|
63
|
+
canonical_input = json.loads(raw_input)
|
|
64
|
+
except json.JSONDecodeError as exc:
|
|
65
|
+
_refuse(f"the input is not JSON: {exc}")
|
|
66
|
+
if not isinstance(canonical_input, dict):
|
|
67
|
+
_refuse("the input must be a JSON object")
|
|
68
|
+
|
|
69
|
+
snapshot_root = _required_env("PGC_SNAPSHOT_ROOT")
|
|
70
|
+
data_root = _required_env("PGC_DATA_ROOT")
|
|
71
|
+
registry = load_registry(Path(snapshot_root))
|
|
72
|
+
response = resolve(to_canonical_request(operation, canonical_input), registry,
|
|
73
|
+
data_root=data_root, snapshot_root=snapshot_root)
|
|
74
|
+
json.dump(response, sys.stdout, indent=2)
|
|
75
|
+
sys.stdout.write("\n")
|
|
76
|
+
return _EXIT_STATUS.get(response["result_class"], _EXIT_STATUS["EXECUTION_FAILURE"])
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def main(argv: list[str] | None = None) -> int:
|
|
80
|
+
parser = argparse.ArgumentParser(
|
|
81
|
+
prog="pgc_transport",
|
|
82
|
+
description="The PGC transport boundary, from the command line.")
|
|
83
|
+
commands = parser.add_subparsers(dest="command", required=True, metavar="<command>")
|
|
84
|
+
call = commands.add_parser("call", help="send one request through the boundary")
|
|
85
|
+
call.add_argument("operation", help="the Operation Identity, e.g. one a TI in the snapshot declares")
|
|
86
|
+
call.add_argument("input", nargs="?", default="{}", help="the operation's input, a JSON object")
|
|
87
|
+
commands.add_parser("http", help="serve the HTTP adapter")
|
|
88
|
+
args = parser.parse_args(argv)
|
|
89
|
+
|
|
90
|
+
if args.command == "call":
|
|
91
|
+
return _call(args.operation, args.input)
|
|
92
|
+
# Imported only when asked for: the HTTP adapter reads its configuration at import.
|
|
93
|
+
from pgc_transport.adapters.http import server
|
|
94
|
+
server.main()
|
|
95
|
+
return 0
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
if __name__ == "__main__":
|
|
99
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""HTTP protocol adapter (cut #1)."""
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""External Protocol Binding (HTTP) — adapter-owned config, NOT a PGC artifact (Patch 2).
|
|
2
|
+
|
|
3
|
+
The binding is **data**: a table of entries loaded from a JSON file the adapter is pointed at.
|
|
4
|
+
It maps an HTTP protocol selector (method + route) to an Operation Identity. It is deliberately
|
|
5
|
+
*not* imperative routing logic, so it can later be promoted to a governed `XB_` artifact with no
|
|
6
|
+
change to the transport engine.
|
|
7
|
+
|
|
8
|
+
Two entry forms coexist, both governed — an operation resolves only if a matching TI/TE pair is
|
|
9
|
+
registered in the snapshot:
|
|
10
|
+
|
|
11
|
+
fixed operation {"method", "path", "operation"}
|
|
12
|
+
operation in body {"method", "path", "operation_in_body": true, "namespace": "…"}
|
|
13
|
+
|
|
14
|
+
The second form exists because a catalog of many sibling identities would otherwise need one
|
|
15
|
+
route each. **The namespace is an ADMISSION CONSTRAINT, not a dispatcher.** The adapter checks
|
|
16
|
+
that the submitted identity falls inside the declared namespace and does nothing else with it:
|
|
17
|
+
the value is passed through as an Operation Identity and resolved through the same governed
|
|
18
|
+
registry as a fixed operation, against its own TI/TE pair. Branching on operation semantics here
|
|
19
|
+
is forbidden (`ADAPTER_NON_AUTHORIAL`) — that would make the adapter an RPC router.
|
|
20
|
+
|
|
21
|
+
This module contains no workload knowledge — the concrete routes, operations and namespaces live
|
|
22
|
+
entirely in the JSON data file.
|
|
23
|
+
"""
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import json
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
from typing import Any, NamedTuple
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Binding(NamedTuple):
|
|
32
|
+
"""One bound route. Exactly one of `operation` / `namespace` is meaningful."""
|
|
33
|
+
|
|
34
|
+
operation: str | None # fixed form: the identity this route always carries
|
|
35
|
+
namespace: str | None # in-body form: the namespace an identity must fall inside
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class AdmissionError(ValueError):
|
|
39
|
+
"""The submitted identity is absent or outside the namespace this route admits."""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def load_bindings(path: Path) -> dict[tuple[str, str], Binding]:
|
|
43
|
+
"""Load the HTTP binding table → {(METHOD, path): Binding}."""
|
|
44
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
45
|
+
table: dict[tuple[str, str], Binding] = {}
|
|
46
|
+
for entry in data.get("bindings", []):
|
|
47
|
+
key = (entry["method"].upper(), entry["path"])
|
|
48
|
+
if key in table:
|
|
49
|
+
raise ValueError(f"duplicate HTTP binding for {key}")
|
|
50
|
+
if entry.get("operation_in_body"):
|
|
51
|
+
namespace = entry.get("namespace")
|
|
52
|
+
if not namespace:
|
|
53
|
+
raise ValueError(f"binding {key} declares operation_in_body without a namespace")
|
|
54
|
+
table[key] = Binding(operation=None, namespace=namespace)
|
|
55
|
+
else:
|
|
56
|
+
table[key] = Binding(operation=entry["operation"], namespace=None)
|
|
57
|
+
return table
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def select_operation(binding: Binding, body: dict[str, Any]) -> tuple[str, dict[str, Any]]:
|
|
61
|
+
"""Determine (Operation Identity, canonical input) for a bound route.
|
|
62
|
+
|
|
63
|
+
Fixed form: the route carries the identity and the whole body IS the input.
|
|
64
|
+
In-body form: the body carries `operation` plus its `params`, and the identity is admitted
|
|
65
|
+
iff it falls inside the route's declared namespace. The check is textual containment — the
|
|
66
|
+
adapter neither knows nor asks what the identity means.
|
|
67
|
+
"""
|
|
68
|
+
if binding.operation is not None:
|
|
69
|
+
return binding.operation, body
|
|
70
|
+
|
|
71
|
+
operation = body.get("operation")
|
|
72
|
+
if not isinstance(operation, str) or not operation:
|
|
73
|
+
raise AdmissionError("request body declares no operation identity")
|
|
74
|
+
if not operation.startswith(binding.namespace or ""):
|
|
75
|
+
raise AdmissionError(
|
|
76
|
+
f"operation '{operation}' is outside the namespace "
|
|
77
|
+
f"'{binding.namespace}' admitted by this route"
|
|
78
|
+
)
|
|
79
|
+
params = body.get("params")
|
|
80
|
+
return operation, params if isinstance(params, dict) else {}
|
|
81
|
+
|