Pranor Documentation
The complete reference for the Serv ecosystem — language, components, operations, and architecture.
Getting Started
| Doc | Description |
|---|---|
| Getting Started | Install Serv, write your first service, run it |
| Language Guide | Full language tutorial — 30 sections covering all features |
| Examples | Categorized code examples with explanations |
Reference
| Doc | Description |
|---|---|
| Language Reference | Detailed syntax specification and type system |
| Built-in Functions | log, db, cache, http, json, ai, store, broker |
| Standard Library | 48 importable .pnr modules (auth, jwt, retry, pagination, etc.) |
| CLI Reference | All serv commands with flags and usage |
Components
| Doc | Description |
|---|---|
| Component Catalog | All 16 services with status, ports, and architecture |
| [Pranor Gate](components/Pranor Gate.md) | API Gateway — WASM middleware, AI routing, MCP support |
| [Pranor Vault](components/Pranor Vault.md) | Object Storage — S3-compatible, semantic search, time-travel |
| [Pranor Pulse](components/Pranor Pulse.md) | Message Broker — STOMP, WASM transforms, DLQ, tiered storage |
| [Pranor Console](components/Pranor Console.md) | Dashboard — unified observability, SQL workbench, alerting |
| [Pranor Mesh](components/Pranor Mesh.md) | Service Mesh — library-level, mTLS, circuit breaking |
| [Pranor Cache](components/Pranor Cache.md) | Cache — Redis/in-memory, namespacing, TTL |
| [Pranor Chrono](components/Pranor Chrono.md) | Scheduler — leader election, cron syntax, Pranor Vault persistence |
| [Pranor Deploy](components/Pranor Deploy.md) | Deployment — process orchestration, Docker, gateway sync |
| [Pranor Trace](components/Pranor Trace.md) | Tracing — OTLP ingestion, waterfall UI, anomaly detection |
| [Pranor Tunnel](components/Pranor Tunnel.md) | Tunneling — WebSocket relay, request inspection, subdomain routing |
| [Pranor Auth](components/Pranor Auth.md) | Identity — OAuth2/OIDC, MFA, RBAC, social login |
| [Pranor Pool](components/Pranor Pool.md) | Database Proxy — pooling, routing, query analytics |
| [Pranor Notify](components/Pranor Notify.md) | Notifications — SMTP, Slack, SMS, templates |
| [Pranor Flow](components/Pranor Flow.md) | Workflows — DAG execution, sagas, approval gates |
| [Pranor Hub](components/Pranor Hub.md) | Packages — semver resolution, signing, Pranor Vault backend |
| ServDocs | Documentation — auto-generated from .pnr source |
| [Pranor Core](components/Pranor Core.md) | Common Library — health probes, OTel, JWT middleware |
| pranor-lockctl | Lock CLI — distributed lock acquisition, renewal, and deadlock inspection |
| pranor-secretctl | Secrets CLI — key rotation, secret injection, and Shamir unseal operations |
v2.0 AI Execution Fabric (v2.0.0 Released — GA)
All v2.0 modules are
CGO_ENABLED=0and follow the OSS/EE build-tag convention. Available natively onmain.
| Doc | Description | Status |
|---|---|---|
| [Pranor Graph](components/Pranor Graph.md) | Virtual entity context assembly — Hot/Warm/Cold 3-tier | v2.0.0 GA |
| [Pranor Decision](components/Pranor Decision.md) | 6-level AI governance veto ladder | v2.0.0 GA |
| [Pranor Learn](components/Pranor Learn.md) | Pluggable ML inference (WASM + gRPC sidecar) | v2.0.0 GA |
| [Pranor Eval](components/Pranor Eval.md) | Trajectory replay and quality scoring | v2.0.0 GA |
| v2.0 Architecture | Full AI Execution Fabric architecture doc | v2.0.0 GA |
Operations
| Doc | Description |
|---|---|
| Deployment Guide | Docker, TLS, multi-target deploy, production config |
| Docker Compose Guide | Run the full 16-service stack locally |
| Architecture | Runtime dependencies, service interactions, layers |
| Roadmap | What's done, what's next, maturity matrix |
Port Allocation
| Service | Port | Protocol |
|---|---|---|
| Pranor Gate | 8080 | HTTP/HTTPS |
| Pranor Vault | 8081 | HTTP (S3) |
| Pranor Pulse | 8082 / 61613 | HTTP + STOMP |
| Pranor Console | 8083 | HTTP |
| Pranor Cache | 8084 | HTTP |
| Pranor Chrono | 8085 | HTTP |
| Pranor Deploy | 8086 | HTTP |
| Pranor Mesh | 8087 | HTTP |
| Pranor Hub | 8088 | HTTP |
| ServDocs | 8089 | HTTP |
| Pranor Trace | 8090 | HTTP (OTLP) |
| Pranor Notify | 8094 | HTTP |
| Pranor Flow | 8096 | HTTP |
| Pranor Pool | 8097 | HTTP |
| Pranor Auth | 8098 | HTTP |
| Pranor Tunnel | 8443 | WebSocket |
Pranor v2.0 — AI Execution Fabric Architecture
Release Status:
v2.0.0GA — Officially merged tomainand taggedv2.0.0.
Overview
Pranor v2.0 introduces a governed AI agent execution layer on top of the v1.x infrastructure. It enables deterministic, auditable, and policy-governed agentic workflows with full observability.
The six new modules form the AI Execution Fabric:
| Module | Role | Sprint |
|---|---|---|
std/trace (schema) | Canonical OTLP span hierarchy + attribute contract | Sprint 2 |
std/graph | Virtual entity context assembly (3-tier) | Sprint 3 |
std/flow (agentstep) | AgentStep interface, Saga runner, HITL queue | Sprints 4 + 10 |
std/decision | 6-level governed execution veto ladder | Sprints 5, 6, 9 |
std/learn | Pluggable ML inference provider | Sprint 8 |
std/eval | Trajectory replay and quality scoring | Sprint 11 |
Module Dependency Graph
graph TD
T[std/trace schema] --> G[std/graph]
T --> D[std/decision]
T --> F[std/flow / agentstep]
G --> D
D --> L[std/learn]
D --> F
F --> E[std/eval]
L --> E
Zero-CGO Constraint
All v2.0 core modules are compiled with CGO_ENABLED=0. No cgo dependencies are permitted in the pranor OSS repo. All heavy ML dependencies (PyTorch, TabPFN) run via:
- Pure-Go WASM using
wazero(no system calls required) - gRPC sidecar binaries over Unix domain sockets or TCP IPC
OSS / EE Build-Tag Convention
| Tag | File suffix | Behavior |
|---|---|---|
//go:build !enterprise | _oss.go | OSS implementation (stubs, in-memory) |
//go:build enterprise | _ee.go | Enterprise implementation |
| (no tag) | shared | Interfaces and types used by both |
EE source lives in the pranor-ee repository under src/Pranor<Module>/.
v2.0-dev Branch Strategy
- All v2.0 features are developed on the
v2.0-devbranch ofpranor - v1.0 development is frozen on
main v2.0-devmerges intomainonly after v1.0.0 is officially tagged- EE stubs in
pranor-eealso trackv2.0-dev
Sprint Completion Status
| Sprint | ID | Feature | Status |
|---|---|---|---|
| 1 | V2.89.0 | CI/CD Build Invariants | ✅ Complete |
| 2 | V2.89.4 | Trace OTLP Span Schema | ✅ Complete |
| 3 | V2.89.1 | Pranor Graph Module | ✅ Complete |
| 4 | V2.89.3 | Flow AgentStep & Saga | ✅ Complete |
| 5 | V2.89.2 | Graph Fault Tolerance | ✅ Complete |
| 6 | V2.89.5 | Decision Engine (6-level) | ✅ Complete |
| 7 | V2.89.6 | Decision Fault Tolerance | ✅ Complete |
| 8 | V2.90.1 | Learn Provider Architecture | ✅ Complete |
| 9 | V2.90.3 | Decision Simulation Engine | ✅ Complete |
| 10 | V2.90.4 | HITL Approval Queue | ✅ Complete |
| 11 | V2.90.2 | Pranor Eval Framework | ✅ Complete |
Pranor Architecture
Layers
┌─────────────────────────────────────────────────────────────────────┐
│ DEVELOPER TOOLS │
│ Pranor Compiler │ VS Code LSP │ ServDocs │ Pranor Hub │
├─────────────────────────────────────────────────────────────────────┤
│ PLATFORM LAYER │
│ Pranor Gate │ Pranor Mesh │ Pranor Deploy │ Pranor Tunnel │ Pranor Console │
├─────────────────────────────────────────────────────────────────────┤
│ INFRASTRUCTURE LAYER │
│ Pranor Vault │ Pranor Pulse │ Pranor Cache │ Pranor Pool │ Pranor Auth │
│ Pranor Notify │ Pranor Chrono │ Pranor Flow │
├─────────────────────────────────────────────────────────────────────┤
│ FOUNDATION │
│ Pranor Core (common library — health, OTel, JWT, logging) │
│ Pranor Trace (distributed tracing backend) │
└─────────────────────────────────────────────────────────────────────┘
Runtime Dependency Flow
graph TD
Client[External Client] -->|HTTPS| Pranor Gate
Pranor Gate -->|route + proxy| Pranor Mesh
Pranor Mesh -->|resolve + LB| Services[Service Instances]
Services -->|persist| Pranor Vault
Services -->|enqueue| Pranor Pulse
Services -->|cache| Pranor Cache
Services -->|query| Pranor Pool
Services -->|authenticate| Pranor Auth
Services -->|notify| Pranor Notify
Services -->|schedule| Pranor Chrono
Services -->|workflow| Pranor Flow
Services -.->|traces| Pranor Trace
Pranor Trace -->|cold tier| Pranor Vault
Pranor Chrono -->|triggers| Services
Pranor Pulse -->|delivers| Services
Pranor Notify -->|DLQ retry| Pranor Pulse
Pranor Flow -->|events| Pranor Pulse
Pranor Flow -->|checkpoints| Pranor Vault
Pranor Auth -->|users| Pranor Vault
Pranor Console -->|aggregates| Pranor Gate
Pranor Console -->|aggregates| Pranor Vault
Pranor Console -->|aggregates| Pranor Pulse
Pranor Console -->|aggregates| Pranor Trace
Pranor Console -->|aggregates| Pranor Auth
Pranor Console -->|aggregates| Pranor Pool
Pranor Console -->|aggregates| Pranor Notify
Pranor Console -->|aggregates| Pranor Flow
Pranor Console -->|aggregates| Pranor Tunnel
Pranor Deploy -->|deploys| Services
Pranor Deploy -->|registers routes| Pranor Gate
Pranor Hub -->|stores packages| Pranor Vault
Service Discovery
All services locate each other via the PRANOR_DISCOVERY environment variable — a JSON manifest (or file path) mapping service names to URLs:
{
"gate": "http://localhost:8080",
"store": "http://localhost:8081",
"queue": "http://localhost:8082",
"console_port": 8083,
"cache": "http://localhost:8084",
"cron": "http://localhost:8085",
"cloud": "http://localhost:8086",
"mesh": "http://localhost:8087",
"registry": "http://localhost:8088",
"docs": "http://localhost:8089",
"trace": "http://localhost:8090",
"mail": "http://localhost:8094",
"flow": "http://localhost:8096",
"db": "http://localhost:8097",
"auth": "http://localhost:8098",
"tunnel": "http://localhost:8443",
"otlp_endpoint": "http://localhost:8090/v1/traces",
"jwt_secret": "shared-secret"
}
Shared Conventions
All services follow these patterns (enforced by Pranor Core):
| Convention | Implementation |
|---|---|
| Health probe | GET /healthz → 200 OK |
| Readiness probe | GET /readyz → 200 OK |
| Error format | {"error": "msg", "code": "ERR_CODE", "trace_id": "..."} |
| Auth | Bearer JWT verified via PRANOR_JWT_SECRET |
| Tracing | OTel spans exported to PRANOR_OTLP_ENDPOINT |
| Logging | Structured JSON to stdout |
| Shutdown | Graceful on SIGTERM (drain + 5s timeout) |
| API versioning | /api/v1/ prefix on all management endpoints |
Communication Patterns
| Pattern | Used By |
|---|---|
| HTTP REST (sync) | All services for API calls |
| STOMP TCP (async) | Pranor Pulse for pub/sub messaging |
| WebSocket (push) | Pranor Console for real-time dashboards, Pranor Tunnel for tunneling |
pranor:// resolver | Pranor Mesh for inter-service calls |
| S3 protocol | Pranor Vault for object storage |
| OTLP/HTTP | Pranor Trace for span ingestion |
Architecture Decision Records (ADRs)
This document contains design logs outlining key architectural decisions in the Pranor ecosystem.
ADR 001: Golang Code Generation Compiler Target
Status: Accepted
Context: Serv needs to compile high-level declarations (routes, workers, agents) into performant executable binaries.
Decision: The pranor compiler parses statements and directly transpiles AST nodes into standard Go files, utilizing go build to generate the final optimized executable.
Consequences:
- Inherits Go's performance, concurrency models, and standard library.
- Allows seamless import of external Go modules directly inside
.pnrfiles. - Build times are bound to Go compiler execution speeds.
ADR 002: Library-Level Service Mesh Integration (Pranor Mesh)
Status: Accepted
Context: Traditional service meshes (like Istio/Envoy) use sidecar proxies that increase container overhead, RAM consumption, and networking hops.
Decision: Implement a library-level service mesh where endpoints query a registry node (Pranor Mesh) and communicate directly over standard mTLS HTTP connections.
Consequences:
- Minimal overhead (no sidecar processes, direct service-to-service connections).
- Low memory footprints on resource-constrained targets.
- Requires compiled-in helpers inside the runtime library (
Pranor Core).
ADR 003: Store-Backed Persistent State Adapters
Status: Accepted
Context: Services like Pranor Auth, Pranor Pool, and Pranor Flow require persistent state journals but running dedicated databases increases service complexity.
Decision: Bind state adapters directly to the Pranor Vault S3 storage API to write state checkpoints as JSON blobs.
Consequences:
- Simple persistence pattern with zero database dependency overhead.
- Recoverable on restart via standard object downloads.
- Not suited for highly concurrent transactional workloads (limitations of object writes).
Getting Started with Serv
Serv is a programming language for building background services, APIs, schedulers, and event-driven applications. It compiles to native binaries via Go.
Installation
From Source (requires Go 1.22+)
git clone https://github.com/vyuvaraj/Pranor.git
cd Pranor
go build -o pranor.exe main.go
Move pranor.exe to a directory in your PATH.
Verify Installation
serv --help
Hello World
Create hello.pnr:
server "8080"
route "GET" "/hello" (req) {
return { "message": "Hello from Serv!" }
}
Build and run:
pranor build hello.pnr -o hello.exe
./hello.exe
Visit http://localhost:8080/hello — you'll see:
{"message": "Hello from Serv!"}
Quick Run (no build step)
pranor run hello.pnr
Hot Reload (watch mode)
pranor run hello.pnr --watch
Changes to .pnr files trigger automatic rebuild and restart.
Your First Real Service
server "3000"
database "sqlite://app.db"
// Declare schema — run `serv migrate` once to create the table
table tasks {
id int @primary @autoincrement
title string @required
done bool @default(0)
}
// Create a task
route "POST" "/tasks" (req) {
let errors = validate(req.body, { "title": "required" })
if errors != nil {
return { "error": errors }
}
db.query("INSERT INTO tasks (title, done) VALUES (?, ?)", req.body, false)
return { "status": "created" }
}
// List tasks
route "GET" "/tasks" (req) {
let tasks = db.query("SELECT * FROM tasks")
return { "tasks": tasks }
}
// Scheduled cleanup
every 1h {
db.query("DELETE FROM tasks WHERE done = true")
log.info("Cleaned up completed tasks")
}
Apply the schema before first run:
serv migrate app.pnr # creates the tasks table
pranor run app.pnr
Next Steps
- Language Guide — Full tutorial covering all 30 features
- Language Reference — Detailed syntax specification
- Built-in Functions —
log,db,cache,http,json,ai - CLI Reference — All commands
- Standard Library — 48 reusable modules
- Deployment Guide — Docker, TLS, observability
- Examples — Categorized code examples
CLI Reference
pranor build
Compile a .pnr file to a native binary.
pranor build <file.pnr> [-o <output>]
Examples:
pranor build app.pnr # → service.exe
pranor build app.pnr -o myapp.exe # Custom output name
pranor run
Compile and run immediately.
pranor run <file.pnr> [--watch]
Options:
--watch— Watch for file changes and hot-reload
pranor test
Run tests defined in a .pnr file.
pranor test <file.pnr> # Run tests
pranor test --cover <file.pnr> # Run tests with coverage report
Runs all test "name" { ... } blocks and reports results.
With --cover: Shows statement coverage percentage and saves a coverage profile to .build/<hash>/coverage.out.
pranor lint
Check syntax and perform static analysis without building.
pranor lint <file.pnr>
Analysis includes:
- Parse error detection with "did you mean?" suggestions
- Unused variable warnings
- Missing return detection for typed functions
- Type mismatch errors (wrong argument types/count)
Exit codes:
0— No errors (may have warnings)1— Has parse errors or type errors
Example output:
warning: variable 'unused' is declared but never used
7 | let unused = 42
^
error: argument 1 of 'add' expects type 'int', got 'string'
6 | let result = add("hello", true)
^
2 error(s), 1 warning(s)
serv fmt
Format a .pnr file (4-space indent, consistent style).
serv fmt <file.pnr> # Format in place
serv fmt --check <file.pnr> # Check only (exit 1 if unformatted)
serv repl
Interactive Serv shell.
serv repl
Commands inside REPL:
- Type any expression to evaluate:
1 + 2,"hello".toUpper() let x = 42— declare variables (persisted across lines)state— show all declarationsclear— reset stateexit— quit
serv add
Generate a .pnr.d declaration file for a Go package.
serv add <go-package-path>
Examples:
serv add github.com/google/uuid
serv add encoding/json
serv add net/url
Downloads the package (if needed) and generates type declarations in declarations/.
serv packages
List installed package declarations.
serv packages
serv remove
Remove a package declaration.
serv remove <package-name>
serv install
Install a community package from Pranor Hub and resolve its transitive dependencies.
serv install <package-name>
Examples:
serv install jwt
serv install retry
serv install pagination@1.2.0
Downloads the package tarball from the configured registry, extracts it to packages/<name>/, then reads its serv.toml [dependencies] section and recursively installs any missing transitive dependencies.
Environment variables:
PRANOR_REGISTRY— Override the registry URL (default:https://registry.pranor.org)
Output example:
Downloading package from https://registry.pranor.org/packages/jwt.tar.gz...
✓ Package 'jwt' installed to packages/jwt/
Resolving 2 dependencies...
↳ Installing dependency: crypto
✓ Package 'crypto' installed to packages/crypto/
↳ Installing dependency: base64
• base64 (already installed)
serv publish
Publish a package directory to Pranor Hub.
serv publish <directory>
Creates a .tar.gz archive of the directory (which should contain a serv.toml) and uploads it to the configured registry. Requires PRANOR_JWT_SECRET environment variable for authentication.
serv dockerize
Generate a Dockerfile for deployment.
serv dockerize <file.pnr>
serv migrate
Apply declarative table schema migrations to the database.
serv migrate [file-or-dir] [--db <connection-string>]
Options:
--db— Database connection string. Falls back to$DATABASE_URLthensqlite://serv.db
Supported connection strings:
| Format | Example |
|---|---|
| SQLite | sqlite://app.db |
| PostgreSQL | postgres://user:pass@localhost/mydb |
| MySQL | mysql://user:pass@localhost/mydb |
What it does:
- Scans
.pnrfiles fortabledeclarations - Connects to the database
- Creates tables that don't exist (
CREATE TABLE IF NOT EXISTS) - Adds new columns to existing tables (
ALTER TABLE ADD COLUMN) - Skips anything already up to date
Example output:
Found 3 table declaration(s):
• users (5 columns)
• posts (6 columns)
• tags (2 columns)
✓ users: schema applied
✓ posts: schema applied
- tags: already up to date
Migration complete: 2 table(s) created/updated.
serv create
AI-scaffold a new .pnr file from a natural language description.
serv create "<prompt describing your service>"
Examples:
serv create "a REST API for managing blog posts with SQLite"
serv create "a webhook receiver that processes Stripe payment events"
Requires PRANOR_AI_KEY environment variable (OpenAI or Gemini API key).
pranor dev
Start the full development environment with hot-reload and infrastructure services.
pranor dev [file.pnr] [--services all]
Starts Pranor Pool, Pranor Cache, Pranor Pulse, and Pranor Mesh locally, then watches .pnr
files for changes and reloads the compiled service automatically.
Runtime Flags
Compiled Serv binaries accept these flags:
./myservice.exe --port 9090 # Override server port
./myservice.exe --mcp # Start as MCP tool server
Environment variables:
PORT— Override server portLOG_FORMAT=json— JSON log outputLOG_LEVEL=debug— Set log levelOTEL_ENDPOINT=http://localhost:4318— Enable OpenTelemetryOTEL_SERVICE_NAME=my-service— Service name for tracesDATABASE_URL— Default database connection stringPRANOR_MESH_ADDR— Pranor Mesh registry address (default:http://localhost:8089)PRANOR_SELF_ADDR— This service's advertised address for mesh registration
Configuration Reference
A complete index of environment variables, default ports, network parameters, and files used across all services in the Pranor ecosystem.
Global Environment Variables
| Variable | Default | Description |
|---|---|---|
PORT | (Service specific) | Primary port the HTTP server binds to. |
PRANOR_JWT_SECRET | "" | Secret key used to sign and verify user JWTs. |
PRANOR_JWKS_URL | "" | JWKS endpoint path to fetch RSA validation keys. |
PRANOR_MESH_ADDR | http://localhost:8089 | Target URL of the active Pranor Mesh registry. |
PRANOR_OTLP_ENDPOINT | "" | OpenTelemetry collector pipeline receiver endpoint. |
LOG_LEVEL | info | Filter log logs: debug, info, warn, error. |
Service Configuration Details
1. Pranor Gate (API Gateway)
- Default Port:
8080 - Configuration File:
config.json - Key settings:
max_concurrent_requests: Rate limit gate concurrency.client_cert_path/root_ca_path: TLS cert files for backend mTLS calls.
2. Pranor Vault (Object Storage)
- Default Port:
8081 - Environment variables:
DATA_DIR: Path to bucket storage contents on local disk (default:./data).
3. Pranor Pulse (Message Broker)
- Default Port:
8082 - Environment variables:
PERSISTENCE_ENABLED: Write messages to journal files before dispatch (default:true).
4. Pranor Mesh (Service Mesh)
- Default Port:
8089(HTTP),9999(UDP discovery) - Key settings:
registry.goevicts nodes after default 10s timeout intervals if heartbeats fail.
Pranor Docker Compose Guide
Prerequisites
- Podman Desktop (with
podman composeordocker-composeplugin) or Docker Desktop - At least 8 GB RAM allocated to the container runtime
- All component repos cloned as siblings to
pranor-repo/:serv/ ├── Pranor Auth/ ├── Pranor Cache/ ├── Pranor Deploy/ ├── Pranor Console/ ├── Pranor Chrono/ ├── Pranor Pool/ ├── Pranor Flow/ ├── Pranor Gate/ ├── Pranor Notify/ ├── Pranor Mesh/ ├── Pranor Pulse/ ├── Pranor Hub/ ├── Pranor Vault/ ├── Pranor Trace/ ├── Pranor Tunnel/ └── pranor-repo/ ← you are here
Running the Stack
Build and start all services
cd pranor-repo
podman compose up --build
Run in detached (background) mode
podman compose up --build -d
Rebuild a single service after code changes
podman compose build pranor-vault --no-cache
podman compose up -d pranor-vault
Stop everything
podman compose down
Full clean restart (remove images + volumes)
podman compose down --rmi local --volumes
podman compose up --build
Service Port Map
| # | Service | Port | Description |
|---|---|---|---|
| 1 | Jaeger | 16686 | Trace UI |
| 2 | Pranor Trace | 8090 | OTLP/HTTP collector & trace API |
| 3 | Pranor Vault | 8081 | S3-compatible object storage |
| 4 | Pranor Pulse | 8082 (HTTP), 61613 (STOMP) | Message broker |
| 5 | Pranor Cache | 8086 | Distributed cache |
| 6 | Pranor Gate | 8080 | API gateway / reverse proxy |
| 7 | Pranor Mesh | 8089 | Service mesh registry |
| 8 | Pranor Chrono | 8087 | Distributed scheduler |
| 9 | Pranor Deploy | 8085 | Deployment orchestrator |
| 10 | Pranor Tunnel | 8443 | Tunnel relay server |
| 11 | Pranor Console | 8083 | Observability dashboard (Web UI) |
| 12 | Pranor Hub | 8088 | Package registry |
Health Check (All Services)
After podman compose up, wait ~30 seconds then verify all containers are healthy:
podman compose ps
All services should show healthy status. Quick curl check:
curl http://localhost:8080/healthz # Pranor Gate
curl http://localhost:8081/healthz # Pranor Vault
curl http://localhost:8082/healthz # Pranor Pulse
curl http://localhost:8083/healthz # Pranor Console
curl http://localhost:8085/healthz # Pranor Deploy
curl http://localhost:8086/healthz # Pranor Cache
curl http://localhost:8087/healthz # Pranor Chrono
curl http://localhost:8088/healthz # Pranor Hub
curl http://localhost:8089/healthz # Pranor Mesh
curl http://localhost:8090/healthz # Pranor Trace
curl http://localhost:8443/healthz # Pranor Tunnel
curl http://localhost:16686/ # Jaeger UI
Testing Each Component
1. Jaeger (Trace UI)
Open http://localhost:16686 in a browser. After other services have processed requests, traces will appear searchable by service name.
2. Pranor Trace (OTLP Collector)
# Send a test trace span via OTLP/HTTP
curl -X POST http://localhost:8090/v1/traces \
-H "Content-Type: application/json" \
-d '{"resourceSpans":[{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"test"}}]},"scopeSpans":[{"spans":[{"traceId":"01020304050607080102030405060708","spanId":"0102030405060708","name":"test-span","startTimeUnixNano":"1700000000000000000","endTimeUnixNano":"1700000001000000000"}]}]}]}'
# Query stored traces
curl http://localhost:8090/api/traces
3. Pranor Vault (Object Storage)
# Create a bucket
curl -X PUT http://localhost:8081/test-bucket
# Upload an object
curl -X PUT http://localhost:8081/test-bucket/hello.json \
-H "Content-Type: application/json" \
-d '{"message": "hello from Pranor Vault"}'
# Download the object
curl http://localhost:8081/test-bucket/hello.json
# List buckets
curl http://localhost:8081/
# Delete the object
curl -X DELETE http://localhost:8081/test-bucket/hello.json
4. Pranor Pulse (Message Broker)
# Publish a message to a topic
curl -X POST http://localhost:8082/api/v1/publish \
-H "Content-Type: application/json" \
-d '{"topic": "orders", "payload": {"order_id": "12345", "amount": 99.99}}'
# List topics
curl http://localhost:8082/api/v1/topics
# Subscribe (poll) for messages
curl http://localhost:8082/api/v1/subscribe?topic=orders
# Check broker stats
curl http://localhost:8082/api/v1/stats
5. Pranor Cache (Distributed Cache)
# Set a cache entry
curl -X PUT http://localhost:8086/api/v1/cache/mykey \
-H "Content-Type: application/json" \
-d '{"value": "hello-world", "ttl": 60}'
# Get a cache entry
curl http://localhost:8086/api/v1/cache/mykey
# Delete a cache entry
curl -X DELETE http://localhost:8086/api/v1/cache/mykey
# Get cache stats
curl http://localhost:8086/api/v1/stats
6. Pranor Gate (API Gateway)
# Check gateway health
curl http://localhost:8080/healthz
# View current routes
curl http://localhost:8080/api/v1/admin/routes
# Test proxying (routes configured in config.json)
curl http://localhost:8080/api/v1/orders
# Gateway metrics
curl http://localhost:8080/api/v1/admin/metrics
7. Pranor Mesh (Service Mesh Registry)
# Register a service instance
curl -X POST http://localhost:8089/api/v1/register \
-H "Content-Type: application/json" \
-d '{"service": "my-service", "address": "10.0.0.1:8080", "tags": ["v1"]}'
# Discover service instances
curl http://localhost:8089/api/v1/services/my-service
# List all registered services
curl http://localhost:8089/api/v1/services
# Deregister
curl -X DELETE http://localhost:8089/api/v1/deregister \
-H "Content-Type: application/json" \
-d '{"service": "my-service", "address": "10.0.0.1:8080"}'
8. Pranor Chrono (Distributed Scheduler)
# Schedule a job
curl -X POST http://localhost:8087/api/v1/jobs \
-H "Content-Type: application/json" \
-d '{"name": "cleanup", "schedule": "*/5 * * * *", "endpoint": "http://pranor-vault:8081/healthz", "method": "GET"}'
# List all jobs
curl http://localhost:8087/api/v1/jobs
# Get job execution history
curl http://localhost:8087/api/v1/jobs/cleanup/history
# Delete a job
curl -X DELETE http://localhost:8087/api/v1/jobs/cleanup
9. Pranor Deploy (Deployment Orchestrator)
# Check available runtimes
curl http://localhost:8085/api/v1/runtimes
# Deploy a service (requires .pnr file or config)
curl -X POST http://localhost:8085/api/v1/deploy \
-H "Content-Type: application/json" \
-d '{"name": "my-app", "source": "main.pnr", "runtime": "go"}'
# List deployments
curl http://localhost:8085/api/v1/deployments
# Get deployment status
curl http://localhost:8085/api/v1/deployments/my-app
10. Pranor Tunnel (Tunnel Relay Server)
# Check relay server status
curl http://localhost:8443/healthz
# The tunnel relay accepts WebSocket connections at:
# ws://localhost:8443/ws/connect
# Use the pranor-tunnel CLI client to establish a tunnel:
# pranor-tunnel client 3000 --relay ws://localhost:8443/ws/connect --subdomain myapp
11. Pranor Console (Observability Dashboard)
Open http://localhost:8083 in a browser. The dashboard provides:
- Real-time service health monitoring
- Log aggregation viewer
- Trace visualization
- Gateway route management
- Cluster node overview
- Database query console
# API: Get service discovery info
curl http://localhost:8083/api/v1/discovery
# API: Get aggregated logs
curl http://localhost:8083/api/v1/logs
# API: Get system metrics
curl http://localhost:8083/api/v1/metrics
12. Pranor Hub (Package Registry)
# Publish a package (multipart form with tarball)
curl -X POST http://localhost:8088/api/v1/publish \
-F "name=my-package" \
-F "version=1.0.0" \
-F "tarball=@my-package-1.0.0.tar.gz"
# Search packages
curl http://localhost:8088/api/packages/search?q=my-package
# Get package info
curl http://localhost:8088/api/v1/packages/my-package
# List all packages
curl http://localhost:8088/api/packages/
# Web dashboard
# Open http://localhost:8088 in browser
End-to-End Integration Test
Run the existing e2e test suite (uses mock servers by default):
cd pranor-repo/tests/e2e
go test -v ./...
Manual integration flow (against live stack)
# 1. Upload config to Pranor Vault (no auth in local dev mode)
curl -X PUT http://localhost:8081/demo-bucket/config.json \
-H "Content-Type: application/json" \
-d '{"app": "pranor-demo", "version": "1.0"}'
# 2. Publish event to Pranor Pulse (no auth in dev mode)
curl -X POST http://localhost:8082/api/v1/publish \
-H "Content-Type: application/json" \
-d '{"topic": "deployments", "payload": {"service": "demo", "action": "deploy"}}'
# 3. Cache the result (no auth)
curl -X PUT http://localhost:8086/api/v1/cache/last-deploy \
-H "Content-Type: application/json" \
-d '{"value": "demo-service-v1.0", "ttl": 300}'
# 4. Verify via Gateway
curl http://localhost:8080/healthz
# 5. Check traces in Jaeger
# Open http://localhost:16686, search for service "pranor-vault" or "pranor-pulse"
# 6. View everything in Pranor Console
# Open http://localhost:8083
Authentication
All services use the standardized Pranor Core.AuthMiddleware for JWT authentication. The behavior is controlled by a single environment variable:
How it works
PRANOR_JWT_SECRETnot set (default in docker-compose) → All requests pass through. No auth required.PRANOR_JWT_SECRETset to any value → All API routes require a validAuthorization: Bearer <jwt>header./healthzand/readyz→ Always accessible without auth regardless of configuration.
Auth requirements per service (local dev mode)
| Service | Auth Required? |
|---|---|
| All services | No — PRANOR_JWT_SECRET is unset in docker-compose |
Enabling JWT auth (production)
Set the shared secret in docker-compose or as an environment variable:
# docker-compose.yml — add to each service:
environment:
- PRANOR_JWT_SECRET=your-strong-production-secret
Or run with an environment variable:
PRANOR_JWT_SECRET=my-secret podman compose up
Generating a token
Use any JWT library to sign a token with HMAC-SHA256 and the shared secret:
{
"username": "admin",
"roles": ["admin"],
"iss": "pranor",
"exp": 1750000000
}
Then use it in requests:
curl -H "Authorization: Bearer <your-jwt-token>" http://localhost:8082/api/v1/topics
Production Release Images (GHCR)
All platform services are compiled, packaged, and published as production-ready container images on GitHub Container Registry (GHCR) whenever a version tag (v*) is released.
Image Registry Paths
All component images are publicly available at:
ghcr.io/vyuvaraj/<service-name>:v0.1.0 (and latest)
| Service | Registry Path |
|---|---|
| Pranor Gate | ghcr.io/vyuvaraj/pranor-gate:latest |
| Pranor Vault | ghcr.io/vyuvaraj/pranor-vault:latest |
| Pranor Pulse | ghcr.io/vyuvaraj/pranor-pulse:latest |
| Pranor Cache | ghcr.io/vyuvaraj/pranor-cache:latest |
| Pranor Console | ghcr.io/vyuvaraj/pranor-console:latest |
| Pranor Chrono | ghcr.io/vyuvaraj/pranor-chrono:latest |
| Pranor Deploy | ghcr.io/vyuvaraj/pranor-deploy:latest |
| Pranor Mesh | ghcr.io/vyuvaraj/pranor-mesh:latest |
| Pranor Trace | ghcr.io/vyuvaraj/pranor-trace:latest |
| Pranor Tunnel | ghcr.io/vyuvaraj/pranor-tunnel:latest |
| Pranor Hub | ghcr.io/vyuvaraj/pranor-hub:latest |
Running the Pre-built Stack (No Source Code Needed)
You can run the entire platform stack using production images from GHCR without cloning all the individual component source repositories.
Create a docker-compose.prod.yml file:
version: '3.8'
services:
jaeger:
image: jaegertracing/all-in-one:latest
ports: ["16686:16686", "4317:4317", "4318:4318"]
pranor-trace:
image: ghcr.io/vyuvaraj/pranor-trace:latest
ports: ["8090:8090"]
pranor-vault:
image: ghcr.io/vyuvaraj/pranor-vault:latest
ports: ["8081:8081"]
command: ["--port", "8081", "--data-dir", "/data"]
environment:
- PRANOR_OTLP_ENDPOINT=http://pranor-trace:8090
depends_on: [pranor-trace]
pranor-pulse:
image: ghcr.io/vyuvaraj/pranor-pulse:latest
ports: ["8082:8082", "61613:61613"]
environment:
- PRANOR_OTLP_ENDPOINT=http://pranor-trace:8090
depends_on: [pranor-trace]
pranor-cache:
image: ghcr.io/vyuvaraj/pranor-cache:latest
ports: ["8086:8086"]
environment:
- PRANOR_OTLP_ENDPOINT=http://pranor-trace:8090
depends_on: [pranor-trace]
pranor-gate:
image: ghcr.io/vyuvaraj/pranor-gate:latest
ports: ["8080:8080"]
environment:
- PRANOR_OTLP_ENDPOINT=http://pranor-trace:8090
depends_on: [pranor-trace]
# Add other services as needed...
Then run:
podman compose -f docker-compose.prod.yml up -d
Troubleshooting
View logs for a specific service
podman compose logs pranor-vault
podman compose logs -f pranor-gate # follow mode
Restart a single service
podman compose restart pranor-cache
Service won't start — check dependencies
# See which services depend on what
podman compose config --services
# Check if a dependency is healthy
podman compose ps
Port already in use
# Find what's using the port (Windows)
netstat -ano | findstr :8080
# Kill the process or change the port mapping in docker-compose.yml
Build fails with Go version errors
All Dockerfiles patch go 1.26.x → go 1.24 at build time via sed. If a new dependency adds a higher Go version constraint, re-vendor locally:
cd ../Pranor Vault # or whichever service
set GOWORK=off
go mod vendor
# Then rebuild
podman compose build pranor-vault --no-cache
Architecture Overview
┌─────────────────────────────────────────────────────────────┐
│ Pranor Console :8083 │
│ (Observability Dashboard) │
└────────────┬───────────────┬────────────────────────────────┘
│ │
┌───────▼───────┐ ┌────▼─────────┐
│ Pranor Gate :8080│ │ Jaeger:16686 │
│ (API Gateway) │ │ (Trace UI) │
└───────┬───────┘ └──────────────┘
│
┌────────┼────────────────────────────┐
│ │ │ │ │
┌───▼──┐ ┌──▼───┐ ┌──▼───┐ ┌──▼──┐ ┌────▼────┐
│Store │ │Queue │ │Cache │ │Cron │ │Registry │
│:8081 │ │:8082 │ │:8086 │ │:8087│ │ :8088 │
└──────┘ └──────┘ └──────┘ └─────┘ └─────────┘
┌────────┐ ┌────────┐ ┌────────┐ ┌───────┐
│ Mesh │ │ Cloud │ │Tunnel │ │ Trace │
│ :8089 │ │ :8085 │ │ :8443 │ │ :8090 │
└────────┘ └────────┘ └────────┘ └───────┘
All services communicate over the pranor-net Docker bridge network and export OpenTelemetry traces to Jaeger via the OTLP endpoint.
Deployment Guide
Building for Production
pranor build app.pnr -o myservice.exe
The output is a single static binary — no runtime dependencies.
Docker
Generate a Dockerfile:
serv dockerize app.pnr
Or manually:
FROM golang:1.23-alpine AS builder
WORKDIR /app
COPY . .
RUN go build -o serv main.go
RUN ./pranor build app.pnr -o service
FROM alpine:latest
COPY --from=builder /app/service /service
EXPOSE 8080
CMD ["/service"]
Port Configuration
Priority (highest to lowest):
--portCLI flag:./pranorice --port 9090PORTenv var:PORT=9090 ./pranorice- Config file:
server.port: "9090"inconfig.yml - Source declaration:
server "8080"
TLS / HTTPS
server "443" tls "cert.pem" "key.pem"
Configuration File
Create config.yml in the working directory:
server:
port: "8080"
db:
host: "localhost"
port: "5432"
name: "myapp"
log:
level: "info"
format: "json"
otel:
endpoint: "http://collector:4318"
service: "my-service"
Access in code: config("db.host") → "localhost"
Config Validation
Fail fast on missing required config:
validate {
required "db.host",
required "db.port",
required "app.secret"
}
If any key is missing at startup, the service exits with an error message showing which keys are missing and how to set them.
OpenTelemetry (Tracing & Metrics)
Set environment variables to enable:
OTEL_ENDPOINT=http://localhost:4318 ./pranorice
OTEL_SERVICE_NAME=my-service ./pranorice
Auto-instrumented:
- HTTP routes (method, path, status, duration)
- Database queries (operation, statement)
- Cache operations (GET/SET, key)
- HTTP client calls (method, URL, status)
- Pub/sub messaging (publish/subscribe, topic)
- Scheduled jobs (every/cron, interval)
- External calls (Python/Go extern functions)
Protocol: OTLP/HTTP JSON — compatible with Jaeger, Tempo, Datadog, Honeycomb, etc.
Health Checks
Auto-generated endpoints (no code needed):
GET /health— Returns{"status": "healthy"}GET /ready— Returns{"status": "ready"}GET /metrics— Prometheus-style metrics
Structured Logging
# JSON output (for log aggregators)
LOG_FORMAT=json ./pranorice
# Set level
LOG_LEVEL=debug ./pranorice
Output format (JSON mode):
{"level":"info","message":"Request handled","timestamp":"2024-01-01T00:00:00Z","request_id":"abc123"}
Graceful Shutdown
Serv services handle SIGINT and SIGTERM:
- Stop accepting new connections
- Wait up to 15 seconds for active requests to complete
- Close database connections
- Exit cleanly
Cross-Compilation
Build for different platforms:
GOOS=linux GOARCH=amd64 go build -o serv-linux main.go
./pranor-linux build app.pnr -o service-linux
CI/CD
GitHub Actions and GitLab CI templates are included:
.github/workflows/ci.yml.gitlab-ci.yml
Both compile all examples, run tests, check formatting, and build release binaries on version tags.
Publishing & Distribution
Release Build (All Platforms)
Cross-compile for macOS, Linux, and Windows:
./release-scripts/build-release.sh v1.0.0
This produces archives in release/:
release/
├── serv-darwin-amd64.tar.gz
├── serv-darwin-arm64.tar.gz
├── serv-linux-amd64.tar.gz
├── serv-linux-arm64.tar.gz
└── serv-windows-amd64.zip
Each archive contains serv (or pranor.exe) and pranor-lsp (or pranor-lsp.exe).
GitHub Release
- Tag the release:
git tag v1.0.0 && git push --tags - Create a GitHub Release from the tag
- Upload all archives from
release/ - Note the SHA256 hashes (needed for Homebrew/Scoop):
shasum -a 256 release/*.tar.gz release/*.zip
Homebrew (macOS/Linux)
Formula: release-scripts/homebrew/serv.rb
Setup (one-time):
- Create a Homebrew tap repo:
github.com/user/homebrew-pranor - Copy
serv.rbinto the tap repo - Update SHA256 hashes and download URLs to point to your GitHub release
User install:
brew tap user/serv
brew install serv
Updating for new releases:
- Update
versioninserv.rb - Update SHA256 hashes
- Push to the tap repo
Scoop (Windows)
Manifest: release-scripts/scoop/serv.json
Setup (one-time):
- Create a Scoop bucket repo:
github.com/user/scoop-pranor - Copy
serv.jsoninto the bucket repo - Update the
hashandurlfields with actual release URLs
User install:
scoop bucket add serv https://github.com/user/scoop-pranor
scoop install serv
Updating for new releases:
- Update
version,url, andhashinserv.json - Push to the bucket repo (Scoop's
autoupdatehandles future versions automatically)
VS Code Extension
Publish script: release-scripts/publish-vscode.sh
Prerequisites:
npm install -g @vscode/vsce
First-time setup:
- Get a Personal Access Token from https://dev.azure.com (Marketplace scope)
- Run:
vsce login pranor
Publishing:
cd vscode-support/extension
vsce package # Creates .vsix file
vsce publish # Publishes to VS Code Marketplace
Or use the script:
./release-scripts/publish-vscode.sh
User install (after publishing):
- VS Code: Search "Serv Language Support" in Extensions
- CLI:
code --install-extension pranor.serv-vscode
Docker Base Image
Dockerfile: release-scripts/docker/Dockerfile.base
Build and push:
docker build -t serv:latest -f release-scripts/docker/Dockerfile.base .
docker tag serv:latest ghcr.io/user/serv:latest
docker push ghcr.io/user/serv:latest
User usage:
FROM ghcr.io/user/serv:latest
WORKDIR /app
COPY myservice.pnr .
RUN pranor build myservice.pnr -o service
CMD ["./pranorice"]
Complete Release Checklist
-
Run regression tests:
powershell test_regression.ps1 -
Update version in:
release-scripts/homebrew/serv.rbrelease-scripts/scoop/serv.jsonvscode-support/extension/package.json
-
Cross-compile:
./release-scripts/build-release.sh v1.x.x - Create GitHub Release, upload archives
- Compute SHA256 hashes, update Homebrew formula + Scoop manifest
- Push Homebrew tap and Scoop bucket repos
-
Publish VS Code extension:
./release-scripts/publish-vscode.sh - Build and push Docker image
- Announce release
Serv Language Reference
Program Structure
A Serv program consists of top-level declarations and statements:
server "8080" // Infrastructure
database "sqlite://app.db" // Database connection
cache "redis://localhost:6379" // Cache connection
broker "nats://localhost:4222" // Message broker
// Routes, functions, scheduled tasks, etc.
Unified Application Block (app)
An app block acts as a namespace to group related servers, databases, and APIs within a single logical service boundary:
app GatewayService {
server "8080"
database "sqlite://app.db"
export route "GET" "/health" (req) {
return { "status": "UP" }
}
}
Variables
let name = "Alice" // Type inferred
let age: int = 30 // Explicit type
let { x, y } = point // Destructuring
let val, err = riskyFunction() // Multi-return
Types
| Type | Example |
|---|---|
int | 42 |
float | 3.14 |
string | "hello" |
bool | true, false |
nil | nil |
[]T | [1, 2, 3] |
map | { "key": "value" } |
T? | Optional (nullable) type |
T | U | Union type |
Type Aliases
type UserID = int
type Email = string
Optional Types (Null Safety)
Types suffixed with ? allow nil values. Without ?, assigning nil is a compile error.
let name: string = "Alice" // Cannot be nil
let email: string? = nil // OK — optional type
fn findUser(id: int) -> User? {
let row = db.query("SELECT * FROM users WHERE id = ?", id)
if row == nil { return nil }
return User { name: row.name }
}
Compile error example:
let x: int = nil // error: cannot assign nil to non-optional type 'int' (use 'int?' to allow nil)
Union Types
Union types allow a value to be one of several types:
fn divide(a: int, b: int) -> int | error {
if b == 0 {
return "division by zero"
}
return a / b
}
fn process(input: string | int) {
log.info(input)
}
Functions
// Basic function
fn greet(name) {
return f"Hello, {name}!"
}
// Typed parameters and return
fn add(a: int, b: int) -> int {
return a + b
}
// Generic function
fn identity[T](value: T) -> T {
return value
}
// Generic with constraints
fn max[T: Ordered](a: T, b: T) -> T {
if a > b { return a }
return b
}
// Arrow functions (closures)
let double = x => x * 2
let add = fn(a, b) { return a + b }
Generic Constraints
| Constraint | Supports |
|---|---|
Comparable | ==, != |
Ordered | <, >, <=, >= |
Numeric | +, -, *, / |
Integer | Integer arithmetic |
Float | Floating point |
Control Flow
If/Else
if condition {
// ...
} else if other {
// ...
} else {
// ...
}
For Loops
// Range-based
for item in items {
log.info(item)
}
// Key-value iteration (maps)
for key, value in config {
log.info(f"{key} = {value}")
}
// Condition-based
for count < 10 {
count += 1
}
Break & Continue
for item in items {
if item == nil { continue }
if item == "stop" { break }
log.info(item)
}
Match (Pattern Matching)
match status {
"active" => { log.info("Active") }
"inactive" => { log.info("Inactive") }
_ => { log.info("Unknown") }
}
Structs
struct User {
name: string,
email: string,
age: int
}
// Methods
fn User.greet() -> string {
return f"Hi, I'm {self.name}"
}
// Instantiation
let user = User { name: "Alice", email: "a@test.com", age: 30 }
log.info(user.greet())
Enums
// Simple (string values)
enum Color { Red, Green, Blue }
// With explicit values
enum HttpStatus {
OK = 200,
NotFound = 404,
ServerError = 500
}
Interfaces
interface Serializable {
fn serialize() -> string
fn deserialize(data: string)
}
HTTP Routes
route "GET" "/users" (req) {
return { "users": [] }
}
route "POST" "/users" (req) {
let body = req.body
return { "created": true }
}
// With rate limiting
route "GET" "/api/data" (req) limit 100/minute {
return { "data": "limited" }
}
// With middleware
route "GET" "/protected" (req) use [auth, logging] {
return { "secret": "data" }
}
Request Object
| Field | Type | Description |
|---|---|---|
req.body | string | Request body (JSON string) |
req.method | string | HTTP method |
req.path | string | URL path |
req.params | map | URL params + headers |
WebSockets
ws "/chat" (conn) {
for true {
let msg = conn.receive()
if msg == nil { break }
conn.send(f"Echo: {msg}")
}
}
Scheduled Tasks
// Fixed interval
every 5s {
log.info("Tick")
}
// Cron expression
cron "0 0 * * *" {
log.info("Midnight job")
}
Pub/Sub Messaging
// Subscribe to a topic
subscribe "orders.new" (msg) {
log.info("New order: ", msg)
}
// Publish a message
publish "notifications" "Order confirmed"
Concurrency
// Fire and forget
spawn processOrder(order)
// With worker pool limit
spawn(5) heavyTask(data)
// Async/await
let result = await fetchData()
let all = await all([task1(), task2(), task3()])
Error Handling
// Try/catch (traditional)
try {
let result = http.get("http://api.example.com/data")
log.info(result.body)
} catch (err) {
log.error("Failed: ", err)
}
// Multi-return error handling
let data, err = riskyCall()
if err != nil {
log.error(err)
}
// ? operator — early return on error (recommended)
fn loadUser(id: int) -> User? {
let row = db.query("SELECT * FROM users WHERE id = ?", id)?
let parsed = json.parse(row)?
return User { name: parsed.name }
}
The ? operator calls the expression and:
- If it returns
nilor an error, returnsnilfrom the enclosing function - If it succeeds, unwraps the value and continues
Middleware
middleware auth(req) {
let token = req.params.authorization
if token == nil {
return { "error": "Unauthorized", "status": 401 }
}
}
route "GET" "/protected" (req) use [auth] {
return { "data": "secret" }
}
Optional Chaining
let city = user?.address?.city // nil if any part is nil
Spread Operator
let defaults = { "timeout": 30, "retries": 3 }
let config = { ...defaults, "timeout": 60 }
Operators
Arithmetic
| Operator | Description | Example |
|---|---|---|
+ | Addition / concatenation | a + b |
- | Subtraction | a - b |
* | Multiplication | a * b |
/ | Division | a / b |
% | Modulo (remainder) | a % b |
Compound Assignment
let count = 0
count += 1 // count = count + 1
count -= 1 // count = count - 1
count *= 2 // count = count * 2
count /= 2 // count = count / 2
count %= 3 // count = count % 3
Bitwise Operators
| Operator | Description | Example |
|---|---|---|
& | Bitwise AND | a & b |
| | Bitwise OR | a | b |
^ | Bitwise XOR | a ^ b |
<< | Left shift | a << 2 |
>> | Right shift | a >> 1 |
Comparison
| Operator | Description |
|---|---|
== | Equal |
!= | Not equal |
< | Less than |
> | Greater than |
<= | Less than or equal |
>= | Greater than or equal |
Logical
| Operator | Description |
|---|---|
and | Logical AND |
or | Logical OR |
! | Logical NOT |
Slice Expressions
let items = [1, 2, 3, 4, 5]
let first3 = items[0:3] // [1, 2, 3]
let rest = items[2:] // [3, 4, 5]
let head = items[:2] // [1, 2]
let text = "hello world"
let sub = text[0:5] // "hello"
Imports & Modules
// Import a local .pnr module (relative path)
import "models/user.pnr"
import { User, Role } from "models/user.pnr"
// Import from stdlib (no relative path needed)
import { ok, notFound } from "stdlib/response"
import { requireAuth } from "stdlib/auth"
import { hashPassword } from "stdlib/crypto"
// Import a Go package
import uuid from "github.com/google/uuid"
let id = uuid.New()
// .pnr extension is optional for stdlib imports
import { maskEmail } from "stdlib/mask.pnr" // also works
Import resolution order:
stdlib/X— resolved from project root'sstdlib/directory./pathor../path— resolved relative to the importing file- Bare path — resolved relative to the importing file
External Function Bindings
// Go package
extern fn generateID() from "go:github.com/google/uuid:NewString"
// Python script
extern fn analyze(data) from "python:./scripts/analyzer.py:analyze"
Testing
test "math works" {
let result = add(2, 3)
assert result == 5 // "got X, want 5" on failure
}
test "comparisons" {
assert 10 > 5 // "10 is not > 5" on failure
assert "hello" != "world" // "expected value to not equal world" on failure
}
test "string methods" {
assert "hello".toUpper() == "HELLO"
assert " hi ".trim() == "hi"
}
Assertion messages:
assert x == 5→assertion failed: got 3, want 5assert x != 0→assertion failed: expected value to not equal 0assert x > 10→assertion failed: 5 is not > 10assert valid→assertion failed: expected truthy value, got false
Config Validation
validate {
required "db.host",
required "db.port",
optional "log.level"
}
Request Validation
let errors = validate(req.body, {
"email": "required,email",
"name": "required,string",
"age": "int"
})
Declarative Schema Migrations (table)
Declare your database schema natively in .pnr files. The compiler generates
the SQL automatically; serv migrate applies it to the live database.
table users {
id int @primary @autoincrement
name string @required
email string @unique
role string @default(user)
createdAt datetime @default(now)
}
table posts {
id int @primary @autoincrement
userId int @required
title string @required
body string
published bool @default(0)
createdAt datetime @default(now)
}
Column Annotations
| Annotation | SQL equivalent | Notes |
|---|---|---|
@primary | PRIMARY KEY | Mark as primary key |
@autoincrement | AUTOINCREMENT | Auto-increment integer (SQLite) |
@required | NOT NULL | Field cannot be null |
@unique | UNIQUE | Enforce unique constraint |
@default(value) | DEFAULT value | Set default; use now for CURRENT_TIMESTAMP |
Serv → SQL Type Mapping
| Serv type | SQL type |
|---|---|
int | INTEGER |
float | REAL |
bool | INTEGER (0/1) |
string | TEXT |
datetime | DATETIME |
serv migrate workflow
# Apply all table declarations to the database (default: sqlite://serv.db)
serv migrate
# Target a specific file or directory
serv migrate ./schemas/
# Override the database connection
serv migrate --db sqlite://production.db
serv migrate --db postgres://user:pass@localhost/mydb
serv migrate will:
- Create tables that don't exist yet (
CREATE TABLE IF NOT EXISTS) - Add missing columns to existing tables (
ALTER TABLE ADD COLUMN) - Skip tables/columns that are already up to date
Note: Column renames and type changes require a manual migration block (see below).
Raw SQL migrations (legacy / advanced)
For custom logic, constraints, or renaming operations use the migration block:
migration "add_users_index" {
db.query("CREATE INDEX idx_users_email ON users (email)")
}
migration "rename_status_column" {
db.query("ALTER TABLE orders RENAME COLUMN status TO order_status")
}
Raw migrations are applied in declaration order and tracked in schema_migrations.
MCP Tools
tool "calculator" "Performs math operations" (args) {
let result = args.a + args.b
return { "result": result }
}
AI Agents (agent)
Declare autonomous AI agents with system prompts, model routing, and tool bindings:
agent SupportBot {
system "You are a helpful customer support assistant."
model "openai://gpt-4o"
tools ["lookup_order", "create_ticket"]
}
tool "lookup_order" "Look up an order by ID" (args) {
let row = db.query("SELECT * FROM orders WHERE id = ?", args.order_id)
return row
}
Supported model URI schemes:
openai://gpt-4o— OpenAI GPT-4anthropic://claude-3-5-sonnet— Anthropic Claudegoogle://gemini-2.0-flash— Google Geminilocal://ollama/llama3— Local Ollama model
Agent configuration keys:
| Key | Description |
|---|---|
system | System prompt / instruction |
model | Model URI |
tools | List of tool block names available to the agent |
Foreign Function Interface (FFI)
Import and call external Go packages or receiver methods directly:
# Import Go packages
extern fn newUUID() -> string from "go:github.com/google/uuid:NewString"
# Bind receiver methods
extern fn decimalToString(d) from "go:github.com/shopspring/decimal:Decimal.String"
Stream DSL WASM Transforms (transform)
Declare inline WASM stream transforms in under 5 lines:
transform "orders.raw" (msg) {
let clean = msg
return clean
}
Logic Configuration Policy Engine (policy)
Define dynamic routing and authorization policies evaluated at proxy speed:
policy rate_limit_policy (ctx) {
let path = ctx["path"]
if path == "/api/admin" {
return false
}
return true
}
Serv-Lang Language Guide
Pranor is a high-level, domain-specific language for building production-grade microservices. It compiles to native Go binaries with zero configuration.
Table of Contents
- Project Setup
- Unified Application Block
- Server & Routes
- Request Binding
- HTML & Web Responses
- Database
- Variables & Data Types
- Type System
- Functions
- Control Flow
- Structs & Methods
- Interfaces
- Enums
- Generics
- Imports & Modules
- Authentication
- Pub/Sub Messaging
- Scheduling & Cron
- Object Store (S3)
- WebSockets
- Middleware
- AI Integration
- MCP Tools & Agents
- Schema Migrations (table DSL)
- Error Handling
- Concurrency
- Testing
- External Functions (FFI)
- Stream DSL WASM Transforms
- Logic Configuration Policy Engine
- Observability (OTel)
- Environment & Config
- CLI Reference
1. Project Setup
Single-file project
# main.pnr
server "9000"
export route "GET" "/" (req) {
return { "message": "Hello, world!" }
}
Unified Application Block
You can wrap configuration nodes and routes inside an app block to create a clean logical boundary:
app GatewayService {
server "9000"
database "sqlite://app.db"
export route "GET" "/health" (req) {
return { "status": "UP" }
}
}
Multi-file project (serv.toml)
# serv.toml
entry = "main.pnr"
name = "my-service"
Build & run
pranor build main.pnr # compile to native binary
pranor run main.pnr # compile + run
pranor run main.pnr --watch # hot-reload on file changes
pranor run main.pnr --port 8080
2. Server & Routes
Declare server port
server "9000"
Route declaration
export route "METHOD" "/path" (req) {
return { "key": "value" }
}
Supported methods: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS
Path parameters
export route "GET" "/users/:id" (req) {
let id = req.params["id"]
return { "id": id }
}
Query parameters
export route "GET" "/search" (req) {
let q = req.query["q"]
return { "query": q }
}
Rate limiting
export route "POST" "/api/login" (req) @rate(5, "m") {
# max 5 requests per minute per route
}
CORS
cors ["https://app.example.com", "https://admin.example.com"]
Global IP rate limiting
rate_limit 100 "m" # 100 req/min per IP globally
3. Request Binding
JSON body parsing
export route "POST" "/api/users" (req) {
let data = req.json() # parse req.body as JSON
let name = data.name
let email = data.email
}
Form body parsing (application/x-www-form-urlencoded)
export route "POST" "/contact" (req) {
let form = req.form()
let message = form.message
}
Safe param lookup (returns nil if missing)
export route "GET" "/items/:id" (req) {
let id = req.param("id")
if id == nil {
return { "error": "id required", "status": 400 }
}
}
Object destructuring
let data = req.json()
let { name, email, age } = data
Object shorthand (DX.S14)
let name = "Alice"
let email = "alice@example.com"
return { name, email } # same as { name: name, email: email }
4. HTML & Web Responses
Inline template
export route "GET" "/" (req) {
let tpl = `<!DOCTYPE html>
<html>
<head><title>{{.title}}</title></head>
<body><h1>Hello, {{.name}}!</h1></body>
</html>`
return html.template(tpl, { "title": "Home", "name": "World" })
}
File template
export route "GET" "/" (req) {
return html.render("views/index.html", { "user": user })
}
Static file server
html.static("/assets", "./public") # serves ./public at /assets/
Redirect
export route "GET" "/old" (req) {
return html.redirect("/new", 301) # permanent
}
export route "GET" "/login-required" (req) {
return html.redirect("/login", 302) # temporary
}
Implicit Content-Type inference (DX.S15)
When a route returns a plain string, Content-Type is automatically set:
| Return string starts with | Content-Type set |
|---|---|
<html, <!DOCTYPE | text/html; charset=utf-8 |
<?xml, <rss, <feed | application/xml; charset=utf-8 |
{...} or [...] | application/json |
| anything else | text/plain; charset=utf-8 |
5. Database
Declare database
database "sqlite://./app.db"
database "postgres://user:pass@localhost/mydb"
Query
let users = db.query("SELECT * FROM users WHERE active = ?", [true])
Execute (insert/update/delete)
db.exec("INSERT INTO users (name, email) VALUES (?, ?)", [name, email])
Schema migrations
migration "create_users_table" {
db.exec(`CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`)
}
6. Variables & Data Types
let name = "Alice" # string
let age = 30 # integer
let score = 9.5 # float
let active = true # boolean
let items = [1, 2, 3] # array
let user = { "name": "Alice", "age": 30 } # map/object
# Shorthand
let user = { name, age } # { name: name, age: age }
# Destructuring
let { name, age } = user
# String interpolation
let msg = f"Hello, {name}! You are {age} years old."
# Multi-line string
let html = `
<h1>Hello</h1>
<p>World</p>
`
7. Type System
Type Annotations
let name: string = "Alice"
let age: int = 30
let score: float = 9.5
let active: bool = true
Type Aliases
type UserID = int
type Email = string
type Handler = fn(Request) -> Response
Null Safety (Optional Types)
Types suffixed with ? allow nil values. Without ?, assigning nil is a compile error.
let name: string = "Alice" # Cannot be nil
let email: string? = nil # OK — optional type
fn findUser(id: int) -> User? {
let row = db.query("SELECT * FROM users WHERE id = ?", id)
if row == nil { return nil }
return User { name: row.name }
}
Union Types
fn divide(a: int, b: int) -> int | error {
if b == 0 { return "division by zero" }
return a / b
}
Optional Chaining
let city = user?.address?.city # nil if any part is nil
Spread Operator
let defaults = { "timeout": 30, "retries": 3 }
let config = { ...defaults, "timeout": 60 }
Slice Expressions
let items = [1, 2, 3, 4, 5]
let first3 = items[0:3] # [1, 2, 3]
let rest = items[2:] # [3, 4, 5]
let head = items[:2] # [1, 2]
8. Functions
fn greet(name) {
return f"Hello, {name}!"
}
# Typed parameters and return
fn add(a: int, b: int) -> int {
return a + b
}
export fn multiply(a, b) { # exported = usable across files
return a * b
}
# Anonymous function
let double = fn(x) { return x * 2 }
# Arrow functions (closures)
let triple = x => x * 3
let sum = (a, b) => a + b
# Higher-order functions
fn apply(val, transform) { return transform(val) }
let result = apply(10, x => x * x)
Collection Methods (Arrow Functions)
let users = [{ "name": "Alice", "active": true }, { "name": "Bob", "active": false }]
let active = users.filter(u => u.active).map(u => u.name)
# ["Alice"]
let items = [1, 2, 3, 4, 5]
items.filter(x => x > 2) # [3, 4, 5]
items.map(x => x * 2) # [2, 4, 6, 8, 10]
items.find(x => x == 3) # 3
items.reduce(fn(a, b) { return a + b }, 0) # 15
items.forEach(x => log.info(x))
items.contains(3) # true
String Methods
"hello world".split(" ") # ["hello", "world"]
" hi ".trim() # "hi"
"hello".replace("l", "L") # "heLLo"
"hello".startsWith("he") # true
"hello".includes("ell") # true
"hello".toUpper() # "HELLO"
"HELLO".toLower() # "hello"
"hello".substring(1, 3) # "el"
"ha".repeat(3) # "hahaha"
9. Control Flow
If / else
if age >= 18 {
return { "access": true }
} else {
return { "access": false }
}
For loop
for i = 0; i < 10; i++ {
log(i)
}
For-in
for item in items {
log(item)
}
# Map iteration
for key, value in config {
log.info(f"{key} = {value}")
}
Break & Continue
for item in items {
if item == nil { continue }
if item == "stop" { break }
process(item)
}
Match (Pattern Matching)
match status {
"active" -> { return { "ok": true } }
"inactive" -> { return { "ok": false } }
_ -> { return { "error": "unknown" } }
}
10. Structs & Methods
struct User {
id: int
name: string
email: string
active: bool
}
# Methods
fn User.fullName() -> string {
return f"{self.name} ({self.email})"
}
fn User.greet() -> string {
return f"Hi, I'm {self.name}"
}
# Instantiation
let u = User { id: 1, name: "Alice", email: "alice@test.com", active: true }
log.info(u.fullName())
11. Interfaces
Structural typing — if a struct has the methods, it satisfies the interface.
interface Serializable {
fn serialize() -> string
fn deserialize(data: string)
}
# User satisfies Serializable if it has serialize() and deserialize()
fn User.serialize() -> string {
return json.stringify(self)
}
fn User.deserialize(data: string) {
let parsed = json.parse(data)
self.name = parsed.name
}
12. Enums
# Simple enum
enum Color { Red, Green, Blue }
# With explicit values
enum HttpStatus {
OK = 200,
NotFound = 404,
ServerError = 500
}
# Usage
let status = HttpStatus.OK
match status {
HttpStatus.OK -> { return { "success": true } }
HttpStatus.NotFound -> { return { "error": "not found" } }
}
13. Generics
# Generic function
fn filter[T](items: []T, pred: fn(T) -> bool) -> []T {
let result: []T = []
for item in items {
if pred(item) { result.push(item) }
}
return result
}
fn map[T, U](items: []T, transform: fn(T) -> U) -> []U {
let result: []U = []
for item in items {
result.push(transform(item))
}
return result
}
# Generic with constraints
fn max[T: Ordered](a: T, b: T) -> T {
if a > b { return a }
return b
}
Constraints
| Constraint | Supports |
|---|---|
Comparable | ==, != |
Ordered | <, >, <=, >= |
Numeric | +, -, *, / |
14. Imports & Modules
import "./handlers/users" # imports users.pnr
import "./utils/validation.pnr"
import { validateEmail } from "./auth/utils" # named import
# Wildcard directory import
import "./handlers/*" # imports all .pnr in ./handlers/
# Stdlib
import "stdlib/auth"
import "stdlib/pagination"
import { ok, notFound } from "stdlib/response"
# Go package (requires .pnr.d declaration)
import uuid from "github.com/google/uuid"
let id = uuid.New()
15. Authentication
auth "my-jwt-secret" # enable JWT auth
# Register & login routes
export route "POST" "/auth/register" (req) {
let data = req.json()
return auth.register(data.username, data.password, data.email)
}
export route "POST" "/auth/login" (req) {
let data = req.json()
return auth.login(data.username, data.password)
}
# Protected route (JWT middleware auto-applied)
export route "GET" "/api/profile" (req) {
let user = auth.currentUser(req)
return { "user": user }
}
# Role-based access control
export route "DELETE" "/admin/users/:id" (req) @middleware("auth.role(\"admin\")") {
# admin only
}
16. Pub/Sub Messaging
broker "pranor-pulse://localhost:4222"
# or in-memory: broker "memory://"
publish "user.created" { "id": userId, "email": email }
subscribe "user.created" (event) {
log(f"New user: {event.email}")
}
17. Scheduling & Cron
every "5m" {
# runs every 5 minutes
let result = db.query("SELECT COUNT(*) as cnt FROM users")
log(f"Total users: {result[0].cnt}")
}
cron "0 9 * * MON-FRI" {
# 9:00 AM weekdays
publish "reports.daily" { "type": "morning" }
}
18. Object Store (S3)
store "s3://access:secret@localhost:9000/my-bucket"
# or: store "file://./data"
store.put("profile/alice.json", { "name": "Alice" })
let profile = store.get("profile/alice.json")
store.delete("profile/alice.json")
store.list("profile/")
19. WebSockets
ws "/chat" (conn) {
conn.send({ "msg": "Welcome!" })
let msg = conn.receive()
while msg != nil {
conn.broadcast(msg)
msg = conn.receive()
}
}
20. Middleware
middleware authMiddleware(req) {
let token = req.headers["authorization"]
if token == nil {
return { "error": "Unauthorized", "status": 401 }
}
# return nil = pass through to handler
}
export route "GET" "/api/data" (req) use [authMiddleware] {
return { "data": "secret" }
}
# Multiple middleware
export route "POST" "/admin" (req) use [authMiddleware, logging, rateLimit] {
return { "admin": true }
}
21. AI Integration
ai "openai://gpt-4o" # OpenAI
# ai "anthropic://claude-3-5-sonnet" # Anthropic
# ai "ollama://llama3" # Local
# Text completion
let response = ai.complete("Summarize this article: " + text)
# Chat with message history
let reply = ai.chat([
{ "role": "system", "content": "You are a helpful assistant." },
{ "role": "user", "content": "What is Serv?" }
])
# Generate embeddings
let vector = ai.embed("distributed systems architecture")
22. MCP Tools & Agents
Tool Declarations
tool "calculator" "Performs math operations" (args) {
let result = args.a + args.b
return { "result": result }
}
tool "lookup_order" "Look up an order by ID" (args) {
let row = db.query("SELECT * FROM orders WHERE id = ?", args.order_id)
return row
}
Agent Declarations
agent SupportBot {
system "You are a helpful customer support assistant."
model "openai://gpt-4o"
tools ["lookup_order", "calculator"]
}
Supported model URI schemes:
openai://gpt-4o— OpenAIanthropic://claude-3-5-sonnet— Anthropicgoogle://gemini-2.0-flash— Google Geminilocal://ollama/llama3— Local Ollama
23. Schema Migrations (table DSL)
Declare database schema natively. The compiler generates SQL; serv migrate applies it.
table users {
id int @primary @autoincrement
name string @required
email string @unique
role string @default(user)
createdAt datetime @default(now)
}
table posts {
id int @primary @autoincrement
userId int @required
title string @required
body string
published bool @default(0)
}
Annotations
| Annotation | SQL equivalent |
|---|---|
@primary | PRIMARY KEY |
@autoincrement | AUTOINCREMENT |
@required | NOT NULL |
@unique | UNIQUE |
@default(value) | DEFAULT value |
Apply migrations
serv migrate # Apply to default db
serv migrate --db postgres://user:pass@host/db
Raw migrations (advanced)
migration "add_index" {
db.exec("CREATE INDEX idx_users_email ON users (email)")
}
24. Error Handling
# Try/catch
try {
let data = db.query("SELECT * FROM users")
return data
} catch (err) {
return { "error": err, "status": 500 }
}
# Multi-return
let data, err = riskyCall()
if err != nil {
log.error(err)
}
# ? operator — early return on nil/error
fn fetchUser(id) {
let user = db.query("SELECT * FROM users WHERE id = ?", [id])?
return user
}
The ? operator: if the expression returns nil or error, returns nil from the enclosing function. Otherwise unwraps and continues.
25. Concurrency
# Async (blocks until done)
let result = await fn() {
return db.query("SELECT * FROM expensive_table")
}
# Parallel fan-out
let [users, orders] = await_all([
fn() { return db.query("SELECT * FROM users") },
fn() { return db.query("SELECT * FROM orders") }
])
# Fire-and-forget (inherits trace context)
spawn fn() {
publish "notifications.send" { "to": email }
}
# Worker pool limit
spawn(5) heavyTask(data)
26. Testing
test "math works" {
let result = add(2, 3)
assert result == 5
}
test "string methods" {
assert "hello".toUpper() == "HELLO"
assert " hi ".trim() == "hi"
}
test "database integration" {
db.exec("INSERT INTO users (name) VALUES (?)", ["Test"])
let rows = db.query("SELECT * FROM users WHERE name = ?", ["Test"])
assert rows.length() > 0
}
Run with: pranor test <file.pnr> [--cover] [--filter name]
27. External Functions (FFI)
# Go package binding
extern fn generateID() from "go:github.com/google/uuid:NewString"
# Python script binding
extern fn analyze(data) from "python:./scripts/analyzer.py:analyze"
# Usage
let id = generateID()
let result = analyze({ "text": "hello world" })
Go Package Declarations (.pnr.d files)
# uuid.pnr.d — generated by `serv add github.com/google/uuid`
declare module "github.com/google/uuid" {
fn New() -> string
fn NewString() -> string
}
Generate with: serv add <go-package-path>
28. Observability (OTel)
otel "my-service-name" # enable OpenTelemetry
Pranor automatically traces:
- Every HTTP request (with
traceparentpropagation) - DB queries, cache ops, HTTP client calls, pub/sub, scheduler jobs
Built-in endpoints:
GET /metrics— Prometheus metricsGET /health— liveness probeGET /ready— readiness probe
Environment: PRANOR_OTLP_ENDPOINT=http://localhost:4318 to set collector.
29. Environment & Config
let port = env("PORT")
let secret = env.secret("JWT_SECRET") # masked in logs
# Config validation (fail-fast on startup)
validate {
required "db.host",
required "db.port",
optional "log.level"
}
Stream DSL WASM Transforms
Pranor provides native stream processing primitives to declare inline WASM message transforms in under 5 lines of code:
# Declare a message transformation for a topic
transform "orders.raw" (msg) {
let clean = msg
# Return value is automatically re-routed or published
return clean
}
Logic Configuration Policy Engine
You can use Pranor as a high-performance configuration and routing policy engine:
# Declare a policy routing rule evaluated at gateway speed
policy rate_limit_policy (ctx) {
let path = ctx["path"]
if path == "/api/admin" {
return false
}
return true
}
33. CLI Reference
| Command | Description |
|---|---|
pranor build <file> | Compile to native binary |
pranor run <file> | Compile and run |
pranor run <file> --watch | Run with hot-reload |
pranor dev <file> | Hot-reload dev server with tests |
pranor test <file> | Run .pnr tests |
pranor test --cover <file> | Run tests with coverage |
serv fmt <file> | Format source file |
pranor lint <file> | Lint and static analysis |
serv migrate | Apply table DSL migrations |
serv create "<prompt>" | AI-powered scaffolding |
serv add <go-package> | Generate .pnr.d declaration |
serv packages | List installed declarations |
serv doctor | Ecosystem health check |
pranor deploy --target <t> | Deploy (fly/railway/render/docker) |
serv dockerize <file> | Generate Dockerfile |
serv doc <file> | Generate API docs |
serv repl | Interactive REPL |
serv debug <file> | Debug with Delve |
serv audit | Audit dependencies for CVEs |
serv new <name> | Scaffold new project |
pranor build --target wasm | Compile to WebAssembly |
Operators Reference
Arithmetic
| Op | Description |
|---|---|
+ | Addition / string concat |
- | Subtraction |
* | Multiplication |
/ | Division |
% | Modulo |
Compound Assignment
+=, -=, *=, /=, %=
Bitwise
| Op | Description |
|---|---|
& | AND |
| | OR |
^ | XOR |
<< | Left shift |
>> | Right shift |
Comparison
==, !=, <, >, <=, >=
Logical
and, or, !
This guide covers pranor v0.1.x. For changelog, see RELEASE_NOTES.md.
Built-in Functions & Objects
Serv provides built-in objects for common service operations. No imports needed.
log — Structured Logging
log.info("Server started")
log.warn("Slow query detected")
log.error("Connection failed: ", err)
log.debug("Processing item: ", id)
// Context logger (fields included in every log)
let logger = log.with("service", "auth", "version", "2.0")
logger.info("Request processed")
// Logger from map
let reqLog = log.fields({ "request_id": id, "user": name })
reqLog.error("Failed")
// Runtime level control
log.setLevel("debug")
let level = log.getLevel()
Environment: LOG_FORMAT=json for JSON output, LOG_LEVEL=debug|info|warn|error
db — Database Operations
database "sqlite://app.db" // SQLite
database "postgres://user:pass@host/db" // PostgreSQL
database "mongodb://localhost:27017/mydb" // MongoDB
// Query (SQL or MongoDB)
let rows = db.query("SELECT * FROM users WHERE active = ?", true)
let result = db.query("INSERT INTO users (name) VALUES (?)", "Alice")
// MongoDB-specific
let page = db.queryPage("users", "{}", 1, 20)
let user = db.findOne("users", "{\"email\": \"a@test.com\"}")
let count = db.count("users", "{\"active\": true}")
let res = db.upsert("users", filter, update)
cache — Caching
cache "redis://localhost:6379" // Redis
cache "in-memory" // Local (dev/testing)
cache.set("key", value, "60s") // Set with TTL
let val = cache.get("key") // Get (nil if expired/missing)
http — HTTP Client
let resp = http.get("https://api.example.com/data")
// resp.status = 200, resp.body = "..."
let resp = http.post("https://api.example.com/users", body)
json — JSON Operations
let obj = json.parse("{\"name\": \"Alice\"}")
let str = json.stringify({ "name": "Alice" })
time — Time Operations
let now = time.now() // ISO 8601 timestamp
let ts = time.unix() // Unix timestamp (int)
time.sleep(1000) // Sleep milliseconds
env — Environment Variables
let port = env("PORT") // Read env var (empty string if not set)
config — Configuration
let host = config("db.host") // Read from config.yml or env
Reads from config.yml in the working directory, or maps dotted keys to env vars (db.host → DB_HOST).
metric — Metrics
metric.inc("requests_total")
metric.gauge("active_connections", 42)
Exposed at GET /metrics endpoint.
publish / subscribe — Messaging
publish "topic" "message"
subscribe "topic" (msg) {
log.info("Received: ", msg)
}
atomic — Atomic Operations
atomic.new("counter", 0)
atomic.inc("counter")
atomic.dec("counter")
let val = atomic.get("counter")
atomic.set("counter", 100)
atomic.cas("counter", 100, 200) // Compare-and-swap
channel — Go Channels
let ch = channel.new("mychan", 10) // Buffered channel
channel.send("mychan", "data")
let msg = channel.receive("mychan")
let msg = channel.tryReceive("mychan") // Non-blocking
channel.close("mychan")
registry — Named Function Registry
registry.set("handler", fn(x) { return x * 2 })
let result = registry.call("handler", 5) // 10
registry.has("handler") // true
registry.list() // ["handler"]
validate — Request Validation
let errors = validate(req.body, {
"email": "required,email",
"name": "required,string",
"age": "int"
})
// Returns nil if valid, or ["email is required", ...] if invalid
Rules: required, string, int, float, bool, email — combine with commas.
String Methods
"hello world".split(" ") // ["hello", "world"]
" hi ".trim() // "hi"
"hello".replace("l", "L") // "heLLo"
"hello".startsWith("he") // true
"hello".endsWith("lo") // true
"hello".includes("ell") // true
"hello".toUpper() // "HELLO"
"HELLO".toLower() // "hello"
"hello".substring(1, 3) // "el"
"hello".indexOf("l") // 2
"ha".repeat(3) // "hahaha"
"hello".length() // 5
Collection Methods
let items = [1, 2, 3, 4, 5]
items.filter(x => x > 2) // [3, 4, 5]
items.map(x => x * 2) // [2, 4, 6, 8, 10]
items.find(x => x == 3) // 3
items.reduce(fn(a, b) { return a + b }, 0) // 15
items.forEach(x => log.info(x))
items.contains(3) // true
items.push(6) // [1, 2, 3, 4, 5, 6]
items.length() // 5
// Slice expressions
let first3 = items[0:3] // [1, 2, 3]
let rest = items[2:] // [3, 4, 5]
let head = items[:2] // [1, 2]
Standard Library
Serv ships with 46 reusable modules in stdlib/. Import what you need:
import { ok, notFound } from "../stdlib/response.pnr"
import { requireAuth } from "../stdlib/auth.pnr"
Quick Reference
Security
| Module | Key Exports |
|---|---|
auth.pnr | bearerToken, basicAuth, requireAuth |
crypto.pnr | hashPassword, verifyPassword, randomToken, hmacSign |
jwt.pnr | jwtEncode, jwtDecode, jwtIsExpired |
sanitize.pnr | escapeHTML, stripTags, escapeSQL, sanitizeFilename |
ratelimit.pnr | createLimiter, isAllowed, remaining, resetLimiter |
mask.pnr | maskEmail, maskPhone, maskCard, maskString, redact |
ip.pnr | extractIP, isPrivate, isTrustedProxy, anonymizeIP |
HTTP
| Module | Key Exports |
|---|---|
response.pnr | ok, created, badRequest, notFound, serverError |
pagination.pnr | offset, pageResponse, parsePageParams |
pagination_cursor.pnr | encodeCursor, decodeCursor, cursorResponse |
middleware.pnr | corsHeaders, requestId, logRequest |
http_client.pnr | getJSON, postJSON, isSuccess, isClientError |
url.pnr | encodeURI, parseQuery, buildQuery, joinPath |
cors.pnr | allowOrigin, allowAll, preflightResponse |
Utilities
| Module | Key Exports |
|---|---|
datetime.pnr | now, timestamp, isExpired, formatDuration |
strings_util.pnr | slugify, truncate, capitalize, isEmpty |
math.pnr | min, max, clamp, abs, percent, sum, average |
sort.pnr | reverse, minOf, maxOf |
collections.pnr | unique, flatten, chunk, first, last, countWhere |
Data
| Module | Key Exports |
|---|---|
csv.pnr | parseCSV, parseRow, toCSV |
base64.pnr | encode, decode, isValid |
diff.pnr | hasChanged, fieldChanged, changeRecord |
Config
| Module | Key Exports |
|---|---|
env.pnr | requireEnv, envOrDefault, envInt, envBool |
config.pnr | getConfig, requireConfig, configBool, configList |
feature_flags.pnr | enableFlag, disableFlag, isEnabled, toggleFlag |
Resilience
| Module | Key Exports |
|---|---|
retry.pnr | backoffDelay, defaultMaxRetries |
circuit_breaker.pnr | createBreaker, isOpen, recordSuccess, recordFailure |
timeout.pnr | withDeadline, isTimedOut, remainingTime, elapsed |
queue.pnr | createQueue, enqueue, dequeue, queueSize |
Concurrency
| Module | Key Exports |
|---|---|
semaphore.pnr | createSemaphore, tryAcquire, release, available |
batch.pnr | createBatch, addToBatch, isBatchFull, flushBatch |
Processing
| Module | Key Exports |
|---|---|
job.pnr | createJob, startJob, completeJob, failJob |
scheduler.pnr | scheduleAfter, isScheduled, cancelSchedule |
Reliability
| Module | Key Exports |
|---|---|
idempotency.pnr | checkIdempotency, markProcessed, isProcessed |
dlq.pnr | createDLQ, sendToDLQ, dlqSize, clearDLQ |
Integration
| Module | Key Exports |
|---|---|
webhook.pnr | buildPayload, sendWebhook, verifySignature |
events.pnr | on, emit, hasHandler |
Observability
| Module | Key Exports |
|---|---|
metrics.pnr | counter, gauge, recordLatency, trackRequest |
tracing.pnr | traceId, startSpan, endSpan, traceContext |
Multi-tenancy
| Module | Key Exports |
|---|---|
tenant.pnr | extractTenant, tenantConfig, isTenantActive, tenantFilter |
Compliance
| Module | Key Exports |
|---|---|
audit.pnr | auditLog, auditAction, auditAccess, auditAuth, auditDenied |
Operations
| Module | Key Exports |
|---|---|
health.pnr | healthy, unhealthy, degraded, buildHealthResponse |
graceful.pnr | initShutdown, isShuttingDown, isDrained |
cache_patterns.pnr | cacheKey, cacheGet, cacheSet, invalidate, computeIfAbsent |
Testing
| Module | Key Exports |
|---|---|
testing_helpers.pnr | assertEqual, assertNotNil, assertContains, assertTrue |
Usage Example
import { requireAuth, bearerToken } from "../stdlib/auth.pnr"
import { ok, badRequest } from "../stdlib/response.pnr"
import { maskEmail } from "../stdlib/mask.pnr"
import { auditLog } from "../stdlib/audit.pnr"
server "8080"
route "GET" "/api/profile" (req) {
let authErr = requireAuth(req)
if authErr != nil { return authErr }
let token = bearerToken(req)
auditLog(token, "view", "profile", nil)
return ok({
"email": maskEmail("alice@example.com"),
"role": "admin"
})
}
Full module documentation: see comments at the top of each file in stdlib/.
Examples
All examples are in the examples/ directory. Build any example:
pranor build examples/<name>.pnr -o demo.exe
By Category
Getting Started
| File | Description |
|---|---|
01_scheduler.pnr | Timer-based scheduled tasks |
02_rest_api.pnr | Simple REST API with routes |
05_error_handling.pnr | Try/catch error handling |
HTTP & API
| File | Description |
|---|---|
19_rate_limiting.pnr | Per-route rate limiting |
29_middleware.pnr | Middleware chains |
43_request_validation.pnr | Body validation with schemas |
34_websocket_logging.pnr | WebSocket endpoints + structured logging |
Database & Cache
| File | Description |
|---|---|
07_advanced_features.pnr | SQLite + cache + match patterns |
08_multi_database.pnr | Multiple database connections |
24_migrations.pnr | Database migrations |
22_query_hooks.pnr | Before-query hooks |
Concurrency & Messaging
| File | Description |
|---|---|
03_pubsub_concurrency.pnr | Pub/sub messaging with spawn |
30_async_await.pnr | Async/await patterns |
36_channels.pnr | Go-style channels |
11_concurrent_maps.pnr | Thread-safe maps |
Language Features
| File | Description |
|---|---|
25_structs.pnr | Structs and methods |
28_interfaces_collections.pnr | Interfaces + collection methods |
32_generics.pnr | Generic functions |
46_generic_constraints.pnr | Constrained generics (Ordered, Numeric) |
33_string_methods.pnr | String manipulation |
38_destructuring.pnr | let { x, y } = obj |
39_optional_chaining.pnr | user?.address?.city |
40_spread_operator.pnr | { ...defaults, ...overrides } |
41_new_features.pnr | Enums with values, type aliases |
Integration
| File | Description |
|---|---|
04_python_binding.pnr | Python extern bindings |
31_go_packages.pnr | Importing Go packages |
44_package_usage.pnr | Using serv add packages (uuid) |
15_mcp_support.pnr | MCP tool definitions |
Configuration & Deployment
| File | Description |
|---|---|
09_yaml_config.pnr | YAML config file loading |
37_structured_logging.pnr | JSON logging, context loggers |
42_config_validation.pnr | Required config validation |
45_stdlib_usage.pnr | Using the standard library |
Walkthrough: Building a REST API
// 1. Declare infrastructure
server "8080"
database "sqlite://todos.db"
// 2. Setup schema
migration "create_todos" {
db.query("CREATE TABLE IF NOT EXISTS todos (id INTEGER PRIMARY KEY, title TEXT, done BOOLEAN DEFAULT 0)")
}
// 3. Define routes
route "GET" "/todos" (req) {
let todos = db.query("SELECT * FROM todos")
return { "todos": todos }
}
route "POST" "/todos" (req) {
let errors = validate(req.body, { "title": "required" })
if errors != nil {
return { "status": 400, "errors": errors }
}
db.query("INSERT INTO todos (title) VALUES (?)", req.body)
return { "status": 201, "message": "Created" }
}
// 4. Background cleanup
every 1h {
let count = db.query("DELETE FROM todos WHERE done = 1")
log.info("Cleaned up done todos")
}
Build and run:
pranor build todo.pnr -o todo.exe
./todo.exe
Test:
curl http://localhost:8080/todos
curl -X POST http://localhost:8080/todos -d '{"title":"Buy milk"}'
curl http://localhost:8080/health
Security Hardening & Compliance Guide
Production security settings for networking, identity validation, and logging.
1. Network Hardening (mTLS)
To restrict endpoints to verified inter-service callers, configure mutual TLS (mTLS) inside your Docker compose environment:
- Generate client and server certificates via
Pranor Meshroot CA:curl -X POST http://localhost:8089/api/csr -d '{"service":"my-backend", "csr":"..."}' - Enable mTLS in service config files:
security: mtls_enabled: true root_ca_path: "/certs/ca.pem" client_cert_path: "/certs/cert.pem" client_key_path: "/certs/key.pem"
2. JWT Signature Verification
- Always verify that
PRANOR_JWT_SECRETis at least 32 cryptographically random bytes. - Do not expose
/readyzor/healthzendpoints to public IP ranges; restrict ingress routing inPranor Gate.
3. Log Redaction
The regex-based log sanitizer in Pranor Core/pkg/middleware/log.go automatically redacts sensitive tokens:
// Output is scrubbed automatically:
log.info("Processing login request with password: " + req.Password)
// Output: [INFO] Processing login request with password: [REDACTED]
Ensure all custom handlers route logs through Pranor Core.SanitizeLog(msg) before emission.
Operational Runbooks — Recovery & Incident Response
This document provides checklists and runbooks for standard operational alert events in the Pranor ecosystem.
Runbook 001: Pranor Gate High Latency / Backpressure
Alert Trigger: Inbound latency on :8080 exceeds 1.5s, or request queues fill.
1. Diagnosis
- Inspect Pranor Console trace list to see which downstream route is bottlenecking:
serv status --json - Check active connection pool state. If pool size matches
max_open_conns, verify downstream database health.
2. Resolution
- Temporarily increase gateway concurrency limit by updating
max_concurrent_requestsinconfig.jsonand reloading:"max_concurrent_requests": 100 - If downstreams are timed out, apply a temporary circuit breaker:
# Add emergency routing override in config.json "target": "http://localhost:8080/error-fallback"
Runbook 002: Pranor Auth Verification Failures (Key Rotation)
Alert Trigger: HTTP 401/403 errors spike globally. Token validation fails on Pranor Core/middleware.go.
1. Diagnosis
- Query key cache endpoint to check if rotated keys are public:
curl -i http://localhost:8098/oauth/keys - Verify if the JWKS URL
:8098/oauth/keysreturns valid RSA keys.
2. Resolution
- Force JWKS cache invalidation inside
Pranor Coreclient by updating the cache expiration timestamp. - If a key leak is suspected, issue an emergency rotation:
# Trigger key generator script ./scripts/rotate-keys.sh
Troubleshooting Guide
Common issues and solutions when building, running, and deploying Pranor modules.
Issue 1: Compiler Mismatched Signatures
Symptom: go test ./... or pranor build fails with:
undefined: SomeFunction or mismatched argument types.
Resolution
- Verify the
go.workfile is configured at the workspace root to include all local subpackages. - Clean compiler build artifacts:
go clean -cache -testcache - Regenerate packages list:
serv packages --update
Issue 2: Service Registry (Pranor Mesh) Offline / Heartbeat Dropped
Symptom: Backend services fail to register, printing:
[ERROR] failed to connect to mesh registry on http://localhost:8089.
Resolution
- Verify that
Pranor Meshis running:serv status - Check firewall or Docker network bindings. The default mesh discovery port requires UDP
:9999to be open for multicast discovery. - If multicast fails in your hosting environment, bypass discovery and explicitly declare the registry target:
export PRANOR_MESH_ADDR="http://127.0.0.1:8089"
Pranor Runtime Dependencies & Integration Matrix
This document maps the complete runtime dependencies and network flow patterns across all 15 operational services of the Pranor ecosystem.
Interaction Architecture Graph
graph TD
%% Clients and Gateway Ingress
Client[Browser / REST Client] -->|HTTP / WebSocket| Pranor Gate[Pranor Gate API Gateway]
%% Gateway to Backend Services
Pranor Gate -->|Routes Requests| Pranor Mesh[Pranor Mesh Service Discovery]
Pranor Gate -->|Loads Config| Pranor Vault[Pranor Vault S3 Object Store]
Pranor Gate -->|Authenticates| Pranor Auth[Pranor Auth Identity & JWT Provider]
%% Service Mesh Routing Instance
Pranor Mesh -->|Discovers Host| Pranor MeshInstances[Running Srv instances]
%% Operational Core Services
Pranor MeshInstances -->|Publishes Events| Pranor Pulse[Pranor Pulse Message Broker]
Pranor MeshInstances -->|Schedules Workloads| Pranor Chrono[Pranor Chrono Scheduler]
Pranor MeshInstances -->|Invokes Pipelines| Pranor Flow[Pranor Flow Workflow Engine]
Pranor MeshInstances -->|Writes telemetry| Pranor Trace[Pranor Trace OTel Collector]
Pranor MeshInstances -->|Queries Data| Pranor Pool[Pranor Pool SQL Proxy Manager]
Pranor MeshInstances -->|Caches Responses| Pranor Cache[Pranor Cache Redis Wrapper]
Pranor MeshInstances -->|Sends Emails| Pranor Notify[Pranor Notify SMTP Agent]
%% Observability Control Center
Pranor Console[Pranor Console Dashboard] -->|Polls Health| Pranor MeshInstances
Pranor Console -->|Reads Logs/Spans| Pranor Trace
Pranor Console -->|Exposes Tunneled Ports| Pranor Tunnel[Pranor Tunnel Local Ingress]
Service Port Registry
| Port | Service Name | Protocol | Role |
|---|---|---|---|
8080 | Pranor Gate | HTTP | Ingress API Gateway |
8081 | Pranor Vault | HTTP | S3 Storage Engine |
8082 | Pranor Pulse | HTTP/STOMP | Queue Broker |
8083 | Pranor Console | HTTP | Operational Dashboard |
8084 | Pranor Cache | RESP/HTTP | Redis Cache Proxy |
8085 | Pranor Chrono | HTTP | Scheduler Control plane |
8089 | Pranor Mesh | HTTP/UDP | Service Registry Node |
8090 | Pranor Trace | HTTP/gRPC | OpenTelemetry Collector |
8094 | Pranor Notify | HTTP | Transactional Mail Agent |
8096 | Pranor Flow | HTTP | DAG Workflow Engine |
8097 | Pranor Pool | HTTP | SQL Persistence Proxy |
8098 | Pranor Auth | HTTP | Identity and MFA provider |
8443 | Pranor Tunnel | HTTPS | Tunnel and Let's Encrypt Ingress |
Interaction Flows
1. Ingress Request Authentication
- Client hits
Pranor Gateon:8080/api/users. Pranor Gateextracts the token and validates against keys fetched fromPranor AuthOIDC configurations.- If valid, request is forwarded down to the corresponding
Pranor Meshregistered target host.
2. Event-Driven Workflow Run
Pranor Chronotriggers a scheduled event on a timer payload.- The execution goes to
Pranor Pulsetopics. - A listening worker consumer picks up the task, executes a step, and writes artifacts to
Pranor VaultS3 buckets.
Pranor Pulse Licensing & Commercial Pricing Strategy
This document outlines the official licensing strategy, dual-licensing policy, client SDK permissions, and commercial tiering model for Pranor Pulse.
1. Executive Summary & Licensing Recommendation
Pranor Pulse uses a Dual-Licensing Open-Core Model designed to maximize open-source developer adoption while building a defensible, high-margin enterprise business.
┌─────────────────────────────────────────────────────────────────────────────┐
│ SERVQUEUE ECOSYSTEM │
└─────────────────────────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐
│ Client SDKs & OPFS│ │ Pranor Pulse Server │ │ Pranor Pulse EE │
│ (@pranor/...) │ │ (`pranor-pulsed`) │ │ (`pranor-ee`) │
├───────────────────┤ ├───────────────────┤ ├───────────────────┤
│ Apache 2.0 │ │ AGPLv3 │ │ Commercial │
│ (Frictionless) │ │ (Copyleft Core) │ │ (Proprietary SLA) │
└───────────────────┘ └───────────────────┘ └───────────────────┘
Recommendation on AGPLv3 (GNU Affero General Public License)
Recommendation: RETAIN AGPLv3 for Server Engine, Use Apache 2.0 for Client SDKs.
-
Why keep AGPLv3 for
pranor-pulsedserver engine?- Hyperscaler Protection: AGPLv3 prevents AWS, GCP, Azure, or third-party cloud vendors from hosting Pranor Pulse as a managed cloud service without contributing modifications back to the open-source community.
- Strong Enterprise Commercial Conversion: Companies that wish to embed or modify Pranor Pulse within closed-source SaaS applications or multi-tenant platforms are required under AGPL to release their source code—or purchase a Pranor Pulse Enterprise Commercial License.
- Industry Standard Precedent: Successfully proven by infrastructure leaders such as MinIO, Grafana, RabbitMQ, and MongoDB (originally).
-
Why use Apache 2.0 / MIT for Client SDKs (
sdks/go,@pranor/queue-wasm)?- Frontend web apps, backend microservices, and mobile clients importing Pranor Pulse libraries must never be subject to copyleft restrictions.
- Enables 100% frictionless integration into any proprietary enterprise application stack.
2. Licensing Matrix by Component
| Component | Repository Path | License | Commercial Exemption Option |
|---|---|---|---|
Pranor Pulse Core Server Daemon (pranor-pulsed) | serv/packages/Pranor Pulse | GNU AGPLv3 | Yes (Commercial License) |
Pranor Pulse Dual-CLI (pranor-pulse) | serv/packages/Pranor Pulse/cmd/pranor-pulse | GNU AGPLv3 | Yes (Commercial License) |
Local Browser OPFS WASM Engine (@pranor/queue-wasm) | serv/packages/Pranor Pulse/pkg/opfs | Apache 2.0 | Included in Apache 2.0 |
| Go & Multi-Language Client SDKs | serv/packages/Pranor Pulse/sdks/* | Apache 2.0 | Included in Apache 2.0 |
| Pranor Console Web Inspector & Admin UI | pranor-repo/pranor-console | GNU AGPLv3 | Yes (Commercial License) |
Pranor Pulse Enterprise Commercial Engine (pranor-ee) | pranor-ee/src/Pranor Pulse | Commercial Proprietary | Requires License Key |
3. Commercial Tiers & Feature Matrix
Pranor Pulse is structured into three clear commercial tiers:
| Feature / Module | Community (Free / AGPLv3) | Enterprise Tier ($30/core/mo) | Sovereign / Financial Tier ($60/core/mo) |
|---|---|---|---|
| Core Broker Engine & STOMP / MQTT 5.0 Protocols | ✅ | ✅ | ✅ |
Local-First Browser OPFS WASM Queue (@pranor/queue-wasm) | ✅ | ✅ | ✅ |
| Point-in-Time Event Replay & Poison-Pill DLQ Engine | ✅ | ✅ | ✅ |
Prometheus /metrics & Basic Grafana Templates | ✅ | ✅ | ✅ |
Cross-Cloud Active-Active Geo-Replication (WAN Sync) (SQ.E15) | ❌ | ✅ | ✅ |
Kafka Wire Protocol Compatibility Adapter (SQ.E16) | ❌ | ✅ | ✅ |
Multi-Cloud S3 / Pranor Vault Cold Tier Compaction (SQ.E20) | ❌ | ✅ | ✅ |
AWS EventBridge & Enterprise Signed Webhooks (HMAC) (SQ.E21) | ❌ | ✅ | ✅ |
FIPS 140-3 PKCS#11 HSM & Merkle Audit Ledger (SQ.E17) | ❌ | ❌ | ✅ |
Post-Quantum Cryptography (NIST Kyber768/Dilithium) (SQ.E17) | ❌ | ❌ | ✅ |
Inline WASM AI Guardrails & Interceptor (ONNX/WASM PII) (SQ.E18) | ❌ | ❌ | ✅ |
eBPF Kernel Bypass & XDP Socket Offload (<10µs Latency) (SQ.E19) | ❌ | ❌ | ✅ |
Multi-Cluster K8s Federation Operator & KEDA Auto-scaler (SQ.E22) | ❌ | ❌ | ✅ |
| Dedicated 24/7 SLA Support & Architecture Review | ❌ | 8x5 Email | 24/7 Phone & Dedicated AM |
4. Commercial Pricing Models
Model A: Per-Core CPU Subscription (Self-Hosted / On-Prem / Kubernetes)
Calculated based on the total number of vCPUs / CPU cores assigned to the pranor-pulsed-ee instances.
- Community Tier: $0 (Free, AGPLv3 Open Source, Unlimited Cores).
- Enterprise Tier: $30 / vCPU Core / Month (billed annually at $360 / core / year).
- Example: A 3-node cluster with 4 vCPUs per node (12 cores total) = $4,320 / year.
- Sovereign & Defense Tier: $60 / vCPU Core / Month (billed annually at $720 / core / year).
- Example: A high-security 5-node cluster with 8 vCPUs per node (40 cores total) = $28,800 / year.
Model B: Pranor Pulse Cloud (Managed Serverless SaaS)
For organizations seeking a fully managed cloud service without infrastructure overhead:
- Data Ingestion & Egress: $0.04 per GB transferred.
- Hot Storage Buffer (SSD Log): $0.025 per GB / month.
- Cold Storage Tier (S3 Archiving): $0.005 per GB / month.
- WASM AI Guardrail Executions: $0.001 per 1,000 payload checks.
5. Technical License Key Enforcement & Verification
In pranor-ee, commercial feature modules are compiled behind the //go:build enterprise build tag.
License Key Validation Flow
- Pranor Pulse Enterprise daemon startup:
pranor-pulsed-ee --config=/etc/pranor-pulse/config.yaml --license-key=/etc/pranor-pulse/license.lic - The daemon validates the cryptographically signed JWT / RSA license file:
- Payload Check: Organization Name, Target Tier (
enterprisevssovereign), Max Core Limit, Expiration Date. - Signature Verification: Validated via offline public RSA key (no phone-home requirement for air-gapped sovereign environments).
- Payload Check: Organization Name, Target Tier (
- If valid,
pranor-eefeatures (GeoReplication,KafkaAdapter,HSMUnsealer,eBPFXDP) activate seamlessly.
6. Summary Comparison: Licensing Choices
| Option | Open Source Engine | Client Libraries | Cloud Hyperscaler Risk | Enterprise Monetization Potential |
|---|---|---|---|---|
| Recommended Strategy | AGPLv3 | Apache 2.0 | Low (Hyperscalers must share SaaS code or buy license) | Very High (Direct conversion path to Commercial EE) |
| Pure Apache 2.0 | Apache 2.0 | Apache 2.0 | High (AWS can re-sell without contributing back) | Medium |
| BSL 1.1 (Business Source) | BSL 1.1 | Apache 2.0 | Low | High |
Document updated: July 2026 | Version 2.0
Component Maturity Analysis & Architectural Roadmap
This document analyzes the external feedback regarding the maturity of Pranor Gate, Pranor Pulse, and Pranor Vault, details the associated production risks, and proposes architectural mitigations divided into Open Source (OSS) and Enterprise (EE) domains.
1. Pranor Gate (API Gateway)
Gaps Identified & Detailed Feedback
- Dynamic Upstream Discovery: Currently relies on hardcoded JSON route maps. Production gateways require dynamic integration with service discovery registries (Consul, Kubernetes CoreDNS) to automatically detect when downstream services scale or crash.
- Distributed Rate Limiting: The current localized rate limiter fails behind a round-robin load balancer. It needs a shared back-end state adapter (such as a Redis Sentinel cluster) using a sliding-window token bucket algorithm to enforce global API thresholds.
- Circuit Breaking & Outage Isolation: Lacks automatic circuit breaking when a downstream service or queue stalls. Without this, pending connections back up, exhausting file descriptors and triggering cascading cluster failure.
- Security & Interconnect: Needs robust mutual TLS (mTLS) with dynamic validation and multi-tenant certificate authority integration.
Production Risk
- Concurrent request storms will trigger memory/CPU spikes, and localized limiters will fail under load-balanced topologies.
- A downstream failure or stall will cascade back to the gateway, causing file descriptor starvation and crashing the edge.
Mitigation Plan
- [OSS] Rate Limiting: Implement sliding-window rate limiting using local memory or Redis backend.
- [OSS] Circuit Breaker: Add a basic circuit-breaker proxy state-machine (Closed, Open, Half-Open).
- [EE] Dynamic Discovery: Integrate with Consul and Kubernetes CoreDNS for dynamic upstream registration.
- [EE] Distributed Rate Limiting: Implement Redis Sentinel integration for shared token-bucket rate limiting.
- [EE] Advanced mTLS: Dynamic certificate handshake and exchange with tenant-based validations.
2. Pranor Pulse (Message Queue)
Gaps Identified & Detailed Feedback
- WASM Resource Sandboxing & Throttling: Running WebAssembly via Wazero is fast, but a faulty user script with an infinite loop or high memory allocation will drain CPU cores and crash the host broker process. Needs strict runtime limiters to terminate slow WASM execution cycles.
- Split-Brain Prevention: For multi-AZ clusters, the broker requires a replication coordinator. A network split will cause partition drift and duplicate message offset consumption without strict consensus.
- Dead Letter Queue (DLQ) Eviction Policies: If a WASM data filter throws an exception or a consumer fails to acknowledge payloads repeatedly, messages must automatically offload to a DLQ with contextual metadata headers describing the failure.
- Memory Safety: The WASM engine relies on raw
unsafe.Pointermappings, creating high vulnerability to crashes (segfaults) during out-of-bounds allocation or uncaught panics. - Unbounded Memory Queues: Internal buffers lack backpressure constraints. If producers flood the queue faster than consumers or filters process them, the broker consumes memory indefinitely until terminated by the OS OOM (Out-Of-Memory) killer.
Production Risk
- A single faulty WASM filter script or incorrect memory address calculation (via
unsafe.Pointer) can trigger a native segmentation fault (SIGSEGV) and instantly crash the primary broker process. - High-throughput producer spikes will exhaust host memory (OOM) if consumers fall behind.
- Network partition events will corrupt message logs or cause duplicate offset commits without a partition coordinator.
Mitigation Plan
- [OSS] Safe WASM Runner: Replace
unsafe.Pointermemory mappings in the WASM runner with safe, bounds-checked slices and explicit memory copies. - [OSS] WASM Execution Limits: Add configurable execution timeouts (e.g., terminate filter if it takes longer than 50ms) to Wazero configuration.
- [OSS] Backpressure & Bounds: Implement strict buffer limits on memory queues to block or throttle producers when consumer limits are reached.
- [OSS] Dead Letter Queue: Implement a secondary DLQ eviction system with failure context headers.
- [EE] Distributed Consensus: Implement Raft-based message replication across broker node clusters.
- [EE] Partition Resilience: Add split-brain prevention and automated broker failover logic.
3. Pranor Vault (State Store)
Gaps Identified & Detailed Feedback
- Formal Raft Consensus Verification: Managing configuration tables requires linearizable consistency. Pranor Vault needs a verified consensus library (such as
hashicorp/raft) to manage state mutations safely and prevent silent database corruption during server restarts. - RBAC & TLS Interconnect: To run securely in shared environments, all service-to-service communication paths must enforce mandatory mutual TLS (mTLS) certificate handshakes, paired with distinct write/read permissions for separate network keys.
Production Risk
- State synchronization bugs can silently corrupt metadata records, leading to systemic failures across downstream applications.
- Lacking TLS interconnect and RBAC exposes sensitive configuration metadata to unauthorized internal nodes.
Mitigation Plan
- [OSS] Lock Backend Stability: Standardize interface abstraction layer for SQL/key-value storage backends.
- [EE] Audited Raft Integration: Integrate an audited, industry-standard Raft implementation (
hashicorp/raft) for state replication. - [EE] TLS Interconnect & RBAC: Enforce mutual TLS handshakes and RBAC permissions per cluster access token.
4. Ecosystem & Shared Middleware (Pranor Core)
Gaps Identified & Detailed Feedback
- Naïve Error Propagation: Critical error paths (such as database handshakes or network calls) are often handled by printing the error or immediately triggering a hard panic/exit, rather than using structured, resilient retry policies.
Production Risk
- Momentary network blips, database restarts, or transient timeouts will cause downstream microservices to crash completely instead of gracefully waiting and reconnecting.
Mitigation Plan
- [OSS] Resilient Retries: Refactor
Pranor Coredatabase and HTTP client middleware to use standard retry adapters (e.g., exponential backoff) to recover from transient outages. - [OSS] Structured Panic Recovery: Enforce standard panic-recovery handlers in all HTTP and queue listeners to avoid dropping the process on individual request errors.
Architecture Verification Checklist
To gauge if the infrastructure is production-ready, verify the following capabilities:
- State Resiliency: Can I pull the power cord on 1 out of 3 running Pranor Vault nodes without corrupting active configurations?
- Edge Protection: Does Pranor Gate reject traffic smoothly with an HTTP 429 error when hit by a simulated DDoS attack?
- WASM Isolation: Does Pranor Pulse terminate a WASM data filter if it takes longer than 50ms to run?
- Ecosystem Resilience: Does a momentary network split or database connection timeout trigger an automatic retry (with backoff) rather than a hard crash/panic?
Pranor Niche Positioning & Developer Experience Analysis
This document details the external critique of Pranor as a language, identifies the primary barriers to adoption ("dealbreakers"), and lays out a strategic roadmap to position Pranor as a de-facto domain-specific language (DSL) for WebAssembly-native edge/broker logic.
1. Identified Gaps ("The Dealbreakers")
1. No "Killer Feature" vs. Go
- The Problem: Pranor compiles to Go binaries. For general-purpose backend services, developers will ask: "Why not just use Go directly?" Pranor lacks a runtime unique advantage (like Erlang's actor fault tolerance or Rust's compile-time memory safety) that Go cannot replicate.
- The Risk: Without a 10x better differentiator for a specific niche, the cost of learning new syntax outweighs its benefits.
2. "Empty Shelf" Ecosystem
- The Problem: Essential libraries (e.g., database drivers, S3 connectors, complex JSON parsers) do not exist. Developers cannot build real products if they must write raw TCP drivers or custom parsers from scratch.
3. Tooling & DX Maturity
- The Problem: Modern language standards require step-through debugging on
.pnrfiles directly (rather than generated Go code), and refactoring capabilities (symbol renaming, code extraction) inside the LSP.
4. Low "Bus Factor"
- The Problem: As a project led by a single creator, teams view adopting it as a significant risk if the maintainer loses interest or becomes unavailable.
2. Niche Positioning Strategy: "TypeScript of Go"
To succeed, Pranor must act as a strict, type-safe, and highly expressive layer over Go—analogous to how TypeScript acts over JavaScript. It transpiles directly into native, standard Go code or WebAssembly bytecode, inheriting the speed, garbage collection, and ecosystem of the Go runtime with zero runtime penalty.
Core DX Design Metrics
- Readable Target Output: Emitted Go source code must be clean, formatted, and idiomatic. A standard Go developer should be able to read and debug the compiled output file without needing to understand Pranor syntax.
- Concurrency Safety Guardrails: Inject static analysis checks at compile-time to intercept and prevent common Go concurrency pitfalls (e.g. race conditions, unhandled channel operations, nil channel writes) before emitting Go source.
- Boilerplate Reduction: Translate brief, declarative Serv code (like declaring tickers, context cancelation traps, and error-handling chains) into standard multi-line Go structures, drastically increasing development speed.
3. Four-Phase Evolution Roadmap
Phase 1: Zero-Friction Go Interop Bridge (FFI)
- Goal: Drop the barrier to entry by 90% by allowing seamless package imports.
- Implementation: Build a zero-overhead FFI to allow Pranor code to import and call Go package symbols directly (
import "github.com/..."). This instantly inherits Go's massive ecosystem of drivers, clients, and parsers.
Phase 2: Concurrency & Sandbox Safety (WASM Stream DSL)
- Goal: Establish Pranor's unique "superpower" in WebAssembly streams.
- Implementation: Build compiler-native syntax and library primitives for WASM stream filters. Emphasize WASM host throttling and memory-safe bounds checks, showing that 5 lines of Serv code can replace 200 lines of boilerplate Go/Rust for filter interceptors, rate limiting, and transformations.
Phase 3: The Configuration Logic "Trojan Horse"
- Goal: Ease adoption inside existing developer stacks.
- Implementation: Market Pranor as a Turing-complete Configuration and Routing Logic language (similar to CUE or Jsonnet but optimized for runtime scripting inside Envoy/Nginx or validating admission webhooks).
Phase 4: Open Governance & Clean Codegen Standard
- Goal: Mitigate "bus factor" concerns and enforce the "TypeScript of Go" output readability standard.
- Implementation: Transition the codebase from the personal GitHub namespace (
vyuvaraj/) to an independent organization (pranor/) to signal long-term community stewardship. Enforce strict transpiler readability rules.
4. Architectural Hard Questions & Resolutions
To pressure-test if Pranor can truly achieve a "TypeScript to Go" status, we must address the fundamental contradictions in its design:
Q1: The Interoperability Paradox
“TypeScript succeeded because JavaScript is dynamically typed and easily wrapped. Go has a rigid, statically typed structural interface system with embedded runtime invariants. How exactly does Pranor transpile and map complex, nested Go structs, pointer dynamics, and channel parameters without breaking standard Go runtime safety or creating unreadable, unmaintainable boilerplate?”
- Resolution Strategy: Pranor will not invent its own type mapping engine. Instead, the Serv compiler will ingest standard Go AST configurations using Go's native
go/astandgo/typespackages. Any imported Go package is treated as a native typing environment, maps 1-to-1 to the transpiled output, and pointer/interface conversions are verified by standard Go type-checker logic during compiling.
Q2: The Multi-Target Identity Crisis
“A true TypeScript-for-Go would emit clean, readable Go source files that any native Go developer could step through with a standard debugger. A compiler targeting pure WASM primitives abstracts those structural lines away entirely. How are you avoiding an architectural split-personality where the language fails to output either high-performance WASM or human-maintainable Go?”
- Resolution Strategy: Establish a clear multi-backend target strategy. The primary target is pure Go source generation (maintaining transpiler readability). The WASM target is handled by feeding the clean generated Go code directly into the standard Go compiler (
GOOS=js GOARCH=wasmor utilizing the Wazero compiler chain). Pranor does not compile directly to WASM binary bytes; it relies on Go's official compiler toolchain to maintain performance parity.
Q3: The Package Manager Isolation Layer
“If the goal is to be a superset layer like TypeScript, why does the ecosystem rely on an isolated, standalone Pranor Hub package server instead of seamlessly resolving directly against standard Go modules via the existing native go proxy infrastructure? Doesn’t an isolated registry completely defeat the friction-free onboarding philosophy that made TypeScript work?”
- Resolution Strategy: Pivot the package system to act as a wrapper. While
Pranor Hubcan distribute Serv-specific plugins and DSL macros, the core package manager must support resolving standard Go dependencies directly from GitHub/Proxy via standardgo.modintegration.
Q4: The Tooling & Diagnostics Gap
“When a complex memory deadlock or data race condition triggers deep within the compiled Go application runtime under a heavy multi-threaded production load, how does the Serv compiler trace that panic back to the original .pnr source file line? Without robust Source Map infrastructure mirroring JavaScript’s architecture, aren’t developers forced to debug the machine-generated Go anyway, destroying the ergonomic value of the language?”
- Resolution Strategy: Implement Go line directives (
//line filename.pnr:line_num) in the generated.gofiles. This is native to the Go compiler and ensures that standard Go panics, stack traces, and debuggers (like Delve) automatically map execution frames directly back to the original.pnrsource lines.