Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/pitot-lab.yml
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,17 @@ jobs:
- uses: actions/setup-python@v6
with:
python-version: "3.11"
- uses: actions/setup-node@v4
with:
node-version: "20"
- name: Require an append-only release message
if: github.event_name == 'pull_request'
run: >-
python labs/15-pitot/scripts/release_notes.py check-policy
--repo .
--base "${{ github.event.pull_request.base.sha }}"
--head "${{ github.event.pull_request.head.sha }}"
- name: Generate Types
run: bash labs/15-pitot/scripts/generate_types.sh
- name: Verify projection and public surface
run: python -m unittest discover -s labs/15-pitot/tests -p 'test_*.py'
5 changes: 4 additions & 1 deletion labs/15-pitot/pitot-distribution/UPSTREAM.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"assets/pitot-two-roles.svg": "05a740b169110b2d6865fe4ecc9bd9e20c8a6c0ee00ceaf948f2c811b9dce0c5",
"bridge/bridge.go": "79ac2e025e16782f3c283b43cbea5b9ba4f837446583864df8f57d6346cae816",
"bridge/bridge_test.go": "23a19b7580d4b1e826224ec8322208ccca44ddd9a97b150efe15cafecc53e47f",
"cmd/generate-schema/main.go": "6e9d0030290d99e36967433f96e38385a122974f899ad9421aac1ef7e50d8fcb",
"cmd/pitot/main.go": "a8ef8a789abe13f41d8a1014de3fb4ad46522b558f0ace70786b904e81892b05",
"cmd/pitot/main_test.go": "ab2894327e7a5e72e9f3d83cae637e3ccb2e1ae05f14458be4fe9016149aeef7",
"conformance/conformance.go": "43b692114f45c8b52958e34b35aee1cee339d8321c90f92ab4f5b963e79935bb",
Expand All @@ -24,14 +25,16 @@
"examples/doc.go": "58f3f9eb7d272d7b6eecdb05f43e1613d5e3ef92d15d97c5440bd4b6990c26f9",
"examples/local-approval/main.go": "51386af324cd7d3bb07fe3ace53503884b02714b96b83073342fde81ce3b83a5",
"examples/token-meter/main.go": "4b1b9c1a43c3cf48b09dba6f607776caced9d2b5b562373496b31ed184582dd1",
"go.mod": "3017c9418f2c6e3beaa14038fef6dfe9ec181819fb60bdbe992f8d3b1c5ba4de",
"go.mod": "7e71b29887a2370c920a3ecad84460130ecc5ad4acba7f62300b5d2568f0ee13",
"go.sum": "2f73a6c3c672f4022f4a618578fac165172095ef590db5d14caa4552675f1980",
"projection/projection.go": "4d3c823fd72a3ca5387dba3683838a1d7e455e9b18309acc839763a39b7bb35f",
"protocol/framing.go": "d4409314b72e09cfd472ad9a21d4c7a223b4341b26f58f6062a7c899e3f87482",
"protocol/framing_test.go": "9fcb13767fdcc7129d2c87bc2133a5800c69d6ac0166016dc266dca5fcfaa1c5",
"schema/schema.go": "fd5c3b76979c88aeed75fadb7e3c94abcad62e067d77595f021fb422a60f2211",
"sensor/decode_fuzz_test.go": "6f3412865c16e3314980a369c36f1f26ba36a991c8b4a8a9d3b776ad9c0de2b0",
"sensor/sensor.go": "939e146eaa51906972f98cf4615747eb75811b0c54d467a938825750336abeae",
"sensor/sensor_test.go": "ea9a58d45ad56d29214a40fb2cfa935e769f85334afcb1856a20fb938d486245",
"types/ts/pitot.ts": "9c243824cbb7edc54b1e125abfd828bf2ed77e7151a0bd5c5d4f63e81ca9b00c",
"windtunnel/doc.go": "44e0bcde632da73e1f8b98beade3a34ca8e0d0ea79cdfb91d131de290b164fc4",
"windtunnel/windtunnel_test.go": "1c8b6c5d56ff5c4e66403baeae0d98108255d153c1275572b8c68e333e091493"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Project native cross-language TypeScript interfaces

Pitot now projects a definitively synced, strongly-typed `types.ts` representation of its JSON API surface into the public distribution alongside the core Go module. By generating these types natively from the internal Go structs using reflection and `quicktype`, Pitot completely eliminates the adoption friction for Node and TypeScript integrations. Instead of maintaining parallel JSON schema definitions, frontend consumers can drop Pitot into their pipeline effortlessly, enjoying instantaneous telemetry ingestion and foolproof TypeScript interfaces for the control plane.
70 changes: 70 additions & 0 deletions labs/15-pitot/pitot-distribution/types/ts/pitot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
export interface Pitot {
boundary_fault: BoundaryFault;
control_requested: ControlRequested;
control_response: ControlResponse;
event: Event;
}

export interface BoundaryFault {
action_id?: string;
host: string;
pitot_version: string;
reason: string;
type: string;
}

export interface ControlRequested {
action_id: string;
data?: unknown;
kind: string;
pitot_version: string;
type: string;
}

export interface ControlResponse {
action_id: string;
controller_id: string;
message?: string;
outcome: string;
pitot_version: string;
type: string;
}

export interface Event {
action?: Action;
content?: Content;
host: Host;
id?: string;
observation: Observation;
pitot_version: string;
session_id?: string;
time?: string;
type: string;
usage?: Usage;
}

export interface Action {
id: string;
kind: string;
}

export interface Content {
full?: unknown;
mode: string;
sha256?: string;
}

export interface Host {
adapter_version?: string;
name: string;
}

export interface Observation {
fidelity: string;
source: string;
}

export interface Usage {
input_tokens: number;
output_tokens: number;
}
34 changes: 34 additions & 0 deletions labs/15-pitot/pitot/cmd/generate-schema/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package main

import (
"encoding/json"
"fmt"
"os"

"github.com/invopop/jsonschema"
"github.com/operatorstack/pitot/schema"
)

// PitotContract acts as a root object to force generation of all these types.
type PitotContract struct {
Event schema.Event `json:"event"`
ControlRequested schema.ControlRequested `json:"control_requested"`
ControlResponse schema.ControlResponse `json:"control_response"`
BoundaryFault schema.BoundaryFault `json:"boundary_fault"`
}

func main() {
reflector := jsonschema.Reflector{
AllowAdditionalProperties: false,
}

sch := reflector.Reflect(&PitotContract{})

b, err := json.MarshalIndent(sch, "", " ")
if err != nil {
fmt.Fprintf(os.Stderr, "failed to marshal schema: %v\n", err)
os.Exit(1)
}

fmt.Println(string(b))
}
8 changes: 8 additions & 0 deletions labs/15-pitot/pitot/go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
module github.com/operatorstack/pitot

go 1.26

require (
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/buger/jsonparser v1.1.2 // indirect
github.com/invopop/jsonschema v0.14.0 // indirect
github.com/pb33f/ordered-map/v2 v2.3.1 // indirect
go.yaml.in/yaml/v4 v4.0.0-rc.2 // indirect
)
10 changes: 10 additions & 0 deletions labs/15-pitot/pitot/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/buger/jsonparser v1.1.2 h1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk=
github.com/buger/jsonparser v1.1.2/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/invopop/jsonschema v0.14.0 h1:MHQqLhvpNUZfw+hM3AZDYK7jxO8FZoQeQM77g8iyZjg=
github.com/invopop/jsonschema v0.14.0/go.mod h1:ygm6C2EaVNMBDPpaPlnOA2pFAxBnxGjFlMZABxm9n2I=
github.com/pb33f/ordered-map/v2 v2.3.1 h1:5319HDO0aw4DA4gzi+zv4FXU9UlSs3xGZ40wcP1nBjY=
github.com/pb33f/ordered-map/v2 v2.3.1/go.mod h1:qxFQgd0PkVUtOMCkTapqotNgzRhMPL7VvaHKbd1HnmQ=
go.yaml.in/yaml/v4 v4.0.0-rc.2 h1:/FrI8D64VSr4HtGIlUtlFMGsm7H7pWTbj6vOLVZcA6s=
go.yaml.in/yaml/v4 v4.0.0-rc.2/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
14 changes: 13 additions & 1 deletion labs/15-pitot/scripts/build_pitot.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,23 @@ def _iter_preview_files(repo: Path) -> list[tuple[str, Path]]:
return entries


def _iter_types_files(repo: Path) -> list[tuple[str, Path]]:
"""Yield (projected_path, source_path) for generated language interfaces."""
entries: list[tuple[str, Path]] = []
types_dir = repo / LAB / "pitot-distribution" / "types"
if types_dir.is_dir():
for path in sorted(types_dir.rglob("*")):
if path.is_file() and not path.is_symlink():
projected = path.relative_to(repo / LAB / "pitot-distribution").as_posix()
entries.append((projected, path))
return entries


def compute_manifest(repo: Path) -> dict[str, str]:
"""Return {projected_path: sha256} for the whole public surface."""
repo = repo.resolve()
surface: dict[str, str] = {}
for projected, source in _iter_module_files(repo) + _iter_preview_files(repo):
for projected, source in _iter_module_files(repo) + _iter_preview_files(repo) + _iter_types_files(repo):
if projected in surface:
raise ValueError(f"projection collision on {projected}")
digest = hashlib.sha256(source.read_bytes()).hexdigest()
Expand Down
16 changes: 16 additions & 0 deletions labs/15-pitot/scripts/generate_types.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/bin/bash
set -euo pipefail

REPO_ROOT=$(git rev-parse --show-toplevel)
cd "$REPO_ROOT/labs/15-pitot/pitot"

go run ./cmd/generate-schema > schema.json

TARGET_DIR="../pitot-distribution/types/ts"
mkdir -p "$TARGET_DIR"

npx -y quicktype -s schema schema.json -o "$TARGET_DIR/pitot.ts" --just-types

rm schema.json

echo "Generated TypeScript types in $TARGET_DIR/pitot.ts"
Loading