From a608406e5634cf75c0441b50ed64e6b82bbc1108 Mon Sep 17 00:00:00 2001 From: "operator-stack-publisher[bot]" Date: Fri, 31 Jul 2026 19:38:37 +0000 Subject: [PATCH] Sync Pitot from Intelligence Flow @ ef3ef32beeb7 --- UPSTREAM.json | 12 ++++++-- runtime/request_test.go | 6 ++-- sdk/csharp/README.md | 21 +++++++++++++ sdk/java/README.md | 21 +++++++++++++ sdk/python/README.md | 65 +++++++++++++++++++++++++++++++++++++++ sdk/rust/.gitignore | 5 +++ sdk/rust/README.md | 60 ++++++++++++++++++++++++++++++++++++ sdk/tests/test_runners.py | 2 +- sdk/typescript/README.md | 59 +++++++++++++++++++++++++++++++++++ 9 files changed, 244 insertions(+), 7 deletions(-) create mode 100644 sdk/csharp/README.md create mode 100644 sdk/java/README.md create mode 100644 sdk/python/README.md create mode 100644 sdk/rust/.gitignore create mode 100644 sdk/rust/README.md create mode 100644 sdk/typescript/README.md diff --git a/UPSTREAM.json b/UPSTREAM.json index 798b80c..2796b8f 100644 --- a/UPSTREAM.json +++ b/UPSTREAM.json @@ -82,7 +82,7 @@ "runtime/descriptor_unix.go": "df41b6867e9840933f186c93b6bc61861e7ea5252a365455686ea0414bfa0044", "runtime/descriptor_windows.go": "2d9ffefe3af0154fa8042de6b67460d4e86dd3f4cdd9e986f180f7d0c535c9a5", "runtime/request.go": "198c44fd6c547022a15b6d0d48e4d0130fa8afb687994365115576e4d874550d", - "runtime/request_test.go": "86d8a2feb4ec72e8ed675b9567da2d1f5d628950eeec907c10b9cc1675aa1904", + "runtime/request_test.go": "7e1cafab3de6432af4ab60999c83e8e81c1a7e7c985e256e56cec2f24ce154cb", "runtime/runtime.go": "36fdf4643c04d058fef380333a0a3942913d1dd60e910b893dc134da4b65d6bf", "runtime/runtime_test.go": "afd78d122af20148bf30d0db873ff002544189df0dfec0f6167b8cf5cd0d42b1", "runtime/strict_test.go": "d2eb2b78563733e3279289d8b6d17a4276e831ed3c78f8b3ec9d6706992ffaec", @@ -91,6 +91,8 @@ "runtime/transport_test.go": "9b69f590f1e258470adea249b3ac6d4a00f1001f10bb08dfa7b56c6e2d6709ae", "schema/schema.go": "fd5c3b76979c88aeed75fadb7e3c94abcad62e067d77595f021fb422a60f2211", "sdk/csharp/Pitot/Types.cs": "3fecc1eabca6e3ad218c73741d185f815d868a618d1e7ed3b9320eeb6942977d", + "sdk/csharp/README.md": "dce102e6176e1a0ee8858fcd784bd9420a9603fb7f2079445e88f4b1e78df45d", + "sdk/java/README.md": "a5c3a6b350098473d7513126db2accd5b790128f6dcf5454d0547cc8284621ce", "sdk/java/src/main/java/com/operatorstack/pitot/Action.java": "2d7974afc2f687921c5e8009077f7b812a8973cdb174de5020cb73f41e3c6570", "sdk/java/src/main/java/com/operatorstack/pitot/BoundaryFault.java": "926c93d9a77b8ef1d94f4674e73e1b8c28aa19082e8f0261077be95ea975abb8", "sdk/java/src/main/java/com/operatorstack/pitot/Content.java": "a430934623b0edfdc3a96f8b17da3deb1502b29e534c18fb580b4cd672b60f71", @@ -102,16 +104,20 @@ "sdk/java/src/main/java/com/operatorstack/pitot/Observation.java": "179982beed89b31cc0a8218c46d8f1bfe6d9c7b1ecb7da82fbc26924d5051df7", "sdk/java/src/main/java/com/operatorstack/pitot/Types.java": "26f8722e6b61faaa093e5a8b92388d3f93a2228026dc04b4b68d10340874a135", "sdk/java/src/main/java/com/operatorstack/pitot/Usage.java": "bbddf4cb0fb7550ac14817600d9f233e68bfd84b72f73d5fbbf9a6cce6e3253d", + "sdk/python/README.md": "c49c15c48bbc064a4596815a94710c9f5270b38216c6b796cbd6fb38cf90fa34", "sdk/python/pitot/__init__.py": "9cab11b333536f167d4e7bb6089ef00488690ec7b5cd5701f08e8bbb13cef0f7", "sdk/python/pitot/runner.py": "8d4537ffc3aee2ea22b1d691559ac1eb1e95521df9ad5bf241ee6ac54c9925b1", "sdk/python/pitot/types.py": "c9a7221f1ad6627f152f26148155d3c74f249c52262c73a515251f469e8eb4ad", "sdk/python/pyproject.toml": "b7990a7a4d2a3db874a5f8c6f9e09673bf928936b64cf0bd4d4a1727aae3305d", "sdk/runner.go": "e9a8db96d3cf6df7ea7e661e6755650f97e580970995dfe881b4268db0c3f832", + "sdk/rust/.gitignore": "6c135f35993002b30f7bdae3d88e923bbf33e4a25faa35dde452f6ab6fd4a6b2", "sdk/rust/Cargo.toml": "1c42ed7ff351ad54ab87c29bbbef96a7bd574b4109ca12eb84d3f8daf42dcc77", + "sdk/rust/README.md": "a548821ca40287e4d17ffb12cd844f68ffc5d0f4358d3459f7bdafc8a55a0b2e", "sdk/rust/src/lib.rs": "fd2dcb9bf9df47fb58e52e4e94136f95867616d941ba66b4b324413d1c5b1777", "sdk/rust/src/runner.rs": "168eb3f926aed4b86aa27ad66ab605326974b6a345b07a35126d4b66e0968b33", "sdk/rust/src/types.rs": "82cd5a5ec12effa6688e8b9b6028bbef67ec43f24d9fd9d19b4235934061ffb8", - "sdk/tests/test_runners.py": "9e86a03b53b108b70204e797b4c65b3d157c6765543a9bb9d7bb892daaf4d8c4", + "sdk/tests/test_runners.py": "28af8d9c235afe9c61e1d6e5e9b70ffe7054e36ec04310daaba4fd7488bda0e0", + "sdk/typescript/README.md": "5fc46f917ef30e944e1b20efae04d0a53ccc60fb868d143e44b04bcad684a51c", "sdk/typescript/package.json": "747b5bab8df073d39b9ef92f28b6b7b43eb00bc127c0647cda4e4003b3db7295", "sdk/typescript/src/index.ts": "bddb336bc120ecd979de0288e432ed2b41749d39f11c697f019af5e54c2e9fba", "sdk/typescript/src/pitot.ts": "9c243824cbb7edc54b1e125abfd828bf2ed77e7151a0bd5c5d4f63e81ca9b00c", @@ -151,7 +157,7 @@ "generator": "operatorstack/pitot:project", "schema_version": 1, "source": { - "commit": "b6b7e44f9b2c48ba6a48ef373adc624610582c57", + "commit": "ef3ef32beeb743ca17afe3bd74706798e3e72c04", "path": "labs/15-pitot", "repository": "operatorstack/intelligence-flow" } diff --git a/runtime/request_test.go b/runtime/request_test.go index 608868a..3c9da57 100644 --- a/runtime/request_test.go +++ b/runtime/request_test.go @@ -12,7 +12,7 @@ func TestNewControlRequestFillsEnvelopeAndMarshalsPayload(t *testing.T) { Actor string `json:"actor"` Operation string `json:"operation"` } - req, err := NewControlRequest("interlock.decide", body{Actor: "publisher", Operation: "artifact.publish"}) + req, err := NewControlRequest("interlock.effect", body{Actor: "publisher", Operation: "artifact.publish"}) if err != nil { t.Fatalf("NewControlRequest: %v", err) } @@ -22,8 +22,8 @@ func TestNewControlRequestFillsEnvelopeAndMarshalsPayload(t *testing.T) { if req.Type != schema.TypeControlRequested { t.Errorf("type = %q, want %q", req.Type, schema.TypeControlRequested) } - if req.Kind != "interlock.decide" { - t.Errorf("kind = %q, want interlock.decide", req.Kind) + if req.Kind != "interlock.effect" { + t.Errorf("kind = %q, want interlock.effect", req.Kind) } if req.ActionID == "" { t.Error("ActionID was not minted") diff --git a/sdk/csharp/README.md b/sdk/csharp/README.md new file mode 100644 index 0000000..bfd0e8d --- /dev/null +++ b/sdk/csharp/README.md @@ -0,0 +1,21 @@ +# Pitot C# SDK — preview (not yet runnable) + +**Status: types-only preview. Not a shipping SDK.** `Pitot/Types.cs` holds the +generated C# data types for the Pitot wire protocol, emitted by +[`scripts/generate_types.sh`](../../../scripts/generate_types.sh) from the same +schema as every other binding. They let you *read the shapes* — they do **not** yet +give you a runnable Controller or Consumer. + +What is **not** here yet, and what a first-class SDK requires: + +- no build manifest (`.csproj` / solution), so the package does not build; +- no `runner` (the stdio JSON-Lines Controller/Consumer loop the other SDKs ship); +- no entry in the cross-language conformance suite + ([`../tests/test_runners.py`](../tests/test_runners.py)), which is what proves a + binding's wire behavior is identical to the others; +- not covered by the version-lockstep gate — it carries no release version. + +The first-class, runnable SDKs are **Python**, **TypeScript**, **Go**, and +**Rust**. Use one of those to build against Pitot today; see each SDK's `README.md` +for a quickstart. This preview will graduate only once it clears the same bar: a +runner, a conformance entry, a build, and version lockstep. diff --git a/sdk/java/README.md b/sdk/java/README.md new file mode 100644 index 0000000..e6ce63f --- /dev/null +++ b/sdk/java/README.md @@ -0,0 +1,21 @@ +# Pitot Java SDK — preview (not yet runnable) + +**Status: types-only preview. Not a shipping SDK.** These files are the generated +Java data types for the Pitot wire protocol (`com.operatorstack.pitot.*`), emitted +by [`scripts/generate_types.sh`](../../../scripts/generate_types.sh) from the same +schema as every other binding. They let you *read the shapes* — they do **not** yet +give you a runnable Controller or Consumer. + +What is **not** here yet, and what a first-class SDK requires: + +- no build manifest (`pom.xml` / `build.gradle`), so the package does not build; +- no `runner` (the stdio JSON-Lines Controller/Consumer loop the other SDKs ship); +- no entry in the cross-language conformance suite + ([`../tests/test_runners.py`](../tests/test_runners.py)), which is what proves a + binding's wire behavior is identical to the others; +- not covered by the version-lockstep gate — it carries no release version. + +The first-class, runnable SDKs are **Python**, **TypeScript**, **Go**, and +**Rust**. Use one of those to build against Pitot today; see each SDK's `README.md` +for a quickstart. This preview will graduate only once it clears the same bar: a +runner, a conformance entry, a build, and version lockstep. diff --git a/sdk/python/README.md b/sdk/python/README.md new file mode 100644 index 0000000..7ab1d1b --- /dev/null +++ b/sdk/python/README.md @@ -0,0 +1,65 @@ +# Pitot Python SDK + +The Python binding for Pitot: typed wire types plus a tiny stdio runner for +building a **Controller** (decides `control.requested` → allow/deny) or a passive +**Consumer** (reads the observed action/event stream). It speaks the same +JSON-Lines protocol as every other Pitot SDK — proven identical by the +cross-language conformance suite ([`../tests/test_runners.py`](../tests/test_runners.py)). + +## Install + +Pitot SDKs install from the project's own registry through the CLI front door, +pinned to the CLI's version — never public PyPI: + +```bash +pitot install python # writes .pitot/registry + operatorstack-pitot== +``` + +`pitot init --language python` scaffolds a complete, runnable project and registers +it as a tenant fragment; the snippets below are the core of what it writes. + +## Build a Controller + +A Controller receives a typed `ControlRequested` and returns an `Outcome`. Return +`allow(...)` or `deny(...)`; the runner serializes one `control.response` per line, +in request order. + +```python +from pitot.runner import run_controller, allow, deny +from pitot.types import ControlRequested + +def handler(req: ControlRequested): + # decide from req.data, req.kind, etc. + if req.kind == "shell" and "rm -rf" in str(req.data): + return deny("blocked a destructive shell command") + return allow("approved") + +if __name__ == "__main__": + run_controller("my-controller", handler) +``` + +## Build a Consumer + +A Consumer passively reads the event stream. Keep stdout clean — a Consumer must +never write to stdout (the conformance suite enforces this); log to stderr. + +```python +import sys +from pitot.runner import run_consumer +from pitot.types import Event + +def handler(event: Event): + print("observed", event.type, file=sys.stderr) + +if __name__ == "__main__": + run_consumer(handler) +``` + +## Run it behind an agent + +```bash +pitot dev --host claude -- claude -p "…" # discovers your fragment, starts the controller +``` + +See the repository [README](../../../public-readme-preview/README.md) for the full +runtime, host-hook wiring, and the enforcement boundary. diff --git a/sdk/rust/.gitignore b/sdk/rust/.gitignore new file mode 100644 index 0000000..6850c8b --- /dev/null +++ b/sdk/rust/.gitignore @@ -0,0 +1,5 @@ +# Rust build artifacts — the crate is a library; a resolved lockfile and the +# compiled target/ tree are produced by `cargo build`/`cargo test` (locally and +# in the conformance CI job) and are never part of the published SDK. +/target +Cargo.lock diff --git a/sdk/rust/README.md b/sdk/rust/README.md new file mode 100644 index 0000000..bcc89f7 --- /dev/null +++ b/sdk/rust/README.md @@ -0,0 +1,60 @@ +# Pitot Rust SDK + +The Rust binding for Pitot: typed wire types plus a tiny stdio runner for building a +**Controller** (decides `control.requested` → allow/deny) or a passive **Consumer** +(reads the observed action/event stream). It speaks the same JSON-Lines protocol as +every other Pitot SDK — proven identical by the cross-language conformance suite +([`../tests/test_runners.py`](../tests/test_runners.py)). + +## Add the dependency + +The crate is `pitot`. During local development, depend on it by path; a released +version installs from the project registry pinned to the CLI's version. + +```toml +[dependencies] +pitot = { path = "path/to/sdk/rust" } # or the pinned registry version +``` + +## Build a Controller + +A Controller receives a typed `ControlRequested` and returns an `Outcome`. Return +`allow(...)` or `deny(...)`; the runner serializes one `control.response` per line, +in request order. + +```rust +use pitot::{run_controller, allow, deny, ControlRequested, Outcome}; + +fn handler(req: ControlRequested) -> Outcome { + if req.kind == "shell" { + return deny(Some("blocked a shell command".to_string())); + } + allow(Some("approved".to_string())) +} + +fn main() { + run_controller("my-controller", Box::new(handler)); +} +``` + +## Build a Consumer + +A Consumer passively reads the event stream. Keep stdout clean — a Consumer must +never write to stdout (the conformance suite enforces this); log to stderr. Note the +event's protocol `type` field is `event_type` in Rust (it is `#[serde(rename = +"type")]`, since `type` is a reserved word). + +```rust +use pitot::{run_consumer, Event}; + +fn handler(event: Event) { + eprintln!("observed {}", event.event_type); +} + +fn main() { + run_consumer(Box::new(handler)); +} +``` + +See the repository [README](../../../public-readme-preview/README.md) for the full +runtime, host-hook wiring, and the enforcement boundary. diff --git a/sdk/tests/test_runners.py b/sdk/tests/test_runners.py index eb82faf..be1e02c 100644 --- a/sdk/tests/test_runners.py +++ b/sdk/tests/test_runners.py @@ -163,7 +163,7 @@ def handler(event: Event): RUST_CONSUMER = """use pitot::{run_consumer, Event}; fn handler(event: Event) { - eprintln!("consumed {}", event.type_field); + eprintln!("consumed {}", event.event_type); } fn main() { diff --git a/sdk/typescript/README.md b/sdk/typescript/README.md new file mode 100644 index 0000000..10b2cde --- /dev/null +++ b/sdk/typescript/README.md @@ -0,0 +1,59 @@ +# Pitot TypeScript SDK + +The TypeScript binding for Pitot: typed wire types plus a tiny stdio runner for +building a **Controller** (decides `control.requested` → allow/deny) or a passive +**Consumer** (reads the observed action/event stream). It speaks the same +JSON-Lines protocol as every other Pitot SDK — proven identical by the +cross-language conformance suite ([`../tests/test_runners.py`](../tests/test_runners.py)). + +## Install + +Pitot SDKs install from the project's own registry through the CLI front door, +pinned to the CLI's version — never public npm: + +```bash +pitot install typescript # scoped .npmrc + @operatorstack/pitot@ +``` + +`pitot init --language typescript` scaffolds a complete, runnable project and +registers it as a tenant fragment; the snippets below are the core of what it +writes. + +## Build a Controller + +A Controller receives a typed `ControlRequested` and returns an `Outcome`. Return +`allow(...)` or `deny(...)`; the runner serializes one `control.response` per line, +in request order. + +```typescript +import { runController, allow, deny, ControlRequested } from "@operatorstack/pitot"; + +runController("my-controller", async (req: ControlRequested) => { + if (req.kind === "shell" && String(req.data).includes("rm -rf")) { + return deny("blocked a destructive shell command"); + } + return allow("approved"); +}); +``` + +## Build a Consumer + +A Consumer passively reads the event stream. Keep stdout clean — a Consumer must +never write to stdout (the conformance suite enforces this); log to stderr. + +```typescript +import { runConsumer, Event } from "@operatorstack/pitot"; + +runConsumer(async (event: Event) => { + console.error("observed", event.type); +}); +``` + +## Run it behind an agent + +```bash +pitot dev --host claude -- claude -p "…" # discovers your fragment, starts the controller +``` + +See the repository [README](../../../public-readme-preview/README.md) for the full +runtime, host-hook wiring, and the enforcement boundary.