Pranor Documentation

The complete reference for the Serv ecosystem — language, components, operations, and architecture.


Getting Started

DocDescription
Getting StartedInstall Serv, write your first service, run it
Language GuideFull language tutorial — 30 sections covering all features
ExamplesCategorized code examples with explanations

Reference

DocDescription
Language ReferenceDetailed syntax specification and type system
Built-in Functionslog, db, cache, http, json, ai, store, broker
Standard Library48 importable .pnr modules (auth, jwt, retry, pagination, etc.)
CLI ReferenceAll serv commands with flags and usage

Components

DocDescription
Component CatalogAll 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
ServDocsDocumentation — auto-generated from .pnr source
[Pranor Core](components/Pranor Core.md)Common Library — health probes, OTel, JWT middleware
pranor-lockctlLock CLI — distributed lock acquisition, renewal, and deadlock inspection
pranor-secretctlSecrets 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=0 and follow the OSS/EE build-tag convention. Available natively on main.

DocDescriptionStatus
[Pranor Graph](components/Pranor Graph.md)Virtual entity context assembly — Hot/Warm/Cold 3-tierv2.0.0 GA
[Pranor Decision](components/Pranor Decision.md)6-level AI governance veto ladderv2.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 scoringv2.0.0 GA
v2.0 ArchitectureFull AI Execution Fabric architecture docv2.0.0 GA

Operations

DocDescription
Deployment GuideDocker, TLS, multi-target deploy, production config
Docker Compose GuideRun the full 16-service stack locally
ArchitectureRuntime dependencies, service interactions, layers
RoadmapWhat's done, what's next, maturity matrix

Port Allocation

ServicePortProtocol
Pranor Gate8080HTTP/HTTPS
Pranor Vault8081HTTP (S3)
Pranor Pulse8082 / 61613HTTP + STOMP
Pranor Console8083HTTP
Pranor Cache8084HTTP
Pranor Chrono8085HTTP
Pranor Deploy8086HTTP
Pranor Mesh8087HTTP
Pranor Hub8088HTTP
ServDocs8089HTTP
Pranor Trace8090HTTP (OTLP)
Pranor Notify8094HTTP
Pranor Flow8096HTTP
Pranor Pool8097HTTP
Pranor Auth8098HTTP
Pranor Tunnel8443WebSocket

Pranor v2.0 — AI Execution Fabric Architecture

Release Status: v2.0.0 GA — Officially merged to main and tagged v2.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:

ModuleRoleSprint
std/trace (schema)Canonical OTLP span hierarchy + attribute contractSprint 2
std/graphVirtual entity context assembly (3-tier)Sprint 3
std/flow (agentstep)AgentStep interface, Saga runner, HITL queueSprints 4 + 10
std/decision6-level governed execution veto ladderSprints 5, 6, 9
std/learnPluggable ML inference providerSprint 8
std/evalTrajectory replay and quality scoringSprint 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

TagFile suffixBehavior
//go:build !enterprise_oss.goOSS implementation (stubs, in-memory)
//go:build enterprise_ee.goEnterprise implementation
(no tag)sharedInterfaces 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-dev branch of pranor
  • v1.0 development is frozen on main
  • v2.0-dev merges into main only after v1.0.0 is officially tagged
  • EE stubs in pranor-ee also track v2.0-dev

Sprint Completion Status

SprintIDFeatureStatus
1V2.89.0CI/CD Build Invariants✅ Complete
2V2.89.4Trace OTLP Span Schema✅ Complete
3V2.89.1Pranor Graph Module✅ Complete
4V2.89.3Flow AgentStep & Saga✅ Complete
5V2.89.2Graph Fault Tolerance✅ Complete
6V2.89.5Decision Engine (6-level)✅ Complete
7V2.89.6Decision Fault Tolerance✅ Complete
8V2.90.1Learn Provider Architecture✅ Complete
9V2.90.3Decision Simulation Engine✅ Complete
10V2.90.4HITL Approval Queue✅ Complete
11V2.90.2Pranor 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):

ConventionImplementation
Health probeGET /healthz → 200 OK
Readiness probeGET /readyz → 200 OK
Error format{"error": "msg", "code": "ERR_CODE", "trace_id": "..."}
AuthBearer JWT verified via PRANOR_JWT_SECRET
TracingOTel spans exported to PRANOR_OTLP_ENDPOINT
LoggingStructured JSON to stdout
ShutdownGraceful on SIGTERM (drain + 5s timeout)
API versioning/api/v1/ prefix on all management endpoints

Communication Patterns

PatternUsed 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:// resolverPranor Mesh for inter-service calls
S3 protocolPranor Vault for object storage
OTLP/HTTPPranor 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 .pnr files.
  • 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

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 declarations
  • clear — reset state
  • exit — 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_URL then sqlite://serv.db

Supported connection strings:

FormatExample
SQLitesqlite://app.db
PostgreSQLpostgres://user:pass@localhost/mydb
MySQLmysql://user:pass@localhost/mydb

What it does:

  1. Scans .pnr files for table declarations
  2. Connects to the database
  3. Creates tables that don't exist (CREATE TABLE IF NOT EXISTS)
  4. Adds new columns to existing tables (ALTER TABLE ADD COLUMN)
  5. 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 port
  • LOG_FORMAT=json — JSON log output
  • LOG_LEVEL=debug — Set log level
  • OTEL_ENDPOINT=http://localhost:4318 — Enable OpenTelemetry
  • OTEL_SERVICE_NAME=my-service — Service name for traces
  • DATABASE_URL — Default database connection string
  • PRANOR_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

VariableDefaultDescription
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_ADDRhttp://localhost:8089Target URL of the active Pranor Mesh registry.
PRANOR_OTLP_ENDPOINT""OpenTelemetry collector pipeline receiver endpoint.
LOG_LEVELinfoFilter 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.go evicts nodes after default 10s timeout intervals if heartbeats fail.

Pranor Docker Compose Guide

Prerequisites

  • Podman Desktop (with podman compose or docker-compose plugin) 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

#ServicePortDescription
1Jaeger16686Trace UI
2Pranor Trace8090OTLP/HTTP collector & trace API
3Pranor Vault8081S3-compatible object storage
4Pranor Pulse8082 (HTTP), 61613 (STOMP)Message broker
5Pranor Cache8086Distributed cache
6Pranor Gate8080API gateway / reverse proxy
7Pranor Mesh8089Service mesh registry
8Pranor Chrono8087Distributed scheduler
9Pranor Deploy8085Deployment orchestrator
10Pranor Tunnel8443Tunnel relay server
11Pranor Console8083Observability dashboard (Web UI)
12Pranor Hub8088Package 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_SECRET not set (default in docker-compose) → All requests pass through. No auth required.
  • PRANOR_JWT_SECRET set to any value → All API routes require a valid Authorization: Bearer <jwt> header.
  • /healthz and /readyz → Always accessible without auth regardless of configuration.

Auth requirements per service (local dev mode)

ServiceAuth Required?
All servicesNoPRANOR_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)

ServiceRegistry Path
Pranor Gateghcr.io/vyuvaraj/pranor-gate:latest
Pranor Vaultghcr.io/vyuvaraj/pranor-vault:latest
Pranor Pulseghcr.io/vyuvaraj/pranor-pulse:latest
Pranor Cacheghcr.io/vyuvaraj/pranor-cache:latest
Pranor Consoleghcr.io/vyuvaraj/pranor-console:latest
Pranor Chronoghcr.io/vyuvaraj/pranor-chrono:latest
Pranor Deployghcr.io/vyuvaraj/pranor-deploy:latest
Pranor Meshghcr.io/vyuvaraj/pranor-mesh:latest
Pranor Traceghcr.io/vyuvaraj/pranor-trace:latest
Pranor Tunnelghcr.io/vyuvaraj/pranor-tunnel:latest
Pranor Hubghcr.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.xgo 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):

  1. --port CLI flag: ./pranorice --port 9090
  2. PORT env var: PORT=9090 ./pranorice
  3. Config file: server.port: "9090" in config.yml
  4. 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:

  1. Stop accepting new connections
  2. Wait up to 15 seconds for active requests to complete
  3. Close database connections
  4. 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

  1. Tag the release: git tag v1.0.0 && git push --tags
  2. Create a GitHub Release from the tag
  3. Upload all archives from release/
  4. 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):

  1. Create a Homebrew tap repo: github.com/user/homebrew-pranor
  2. Copy serv.rb into the tap repo
  3. 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:

  1. Update version in serv.rb
  2. Update SHA256 hashes
  3. Push to the tap repo

Scoop (Windows)

Manifest: release-scripts/scoop/serv.json

Setup (one-time):

  1. Create a Scoop bucket repo: github.com/user/scoop-pranor
  2. Copy serv.json into the bucket repo
  3. Update the hash and url fields with actual release URLs

User install:

scoop bucket add serv https://github.com/user/scoop-pranor
scoop install serv

Updating for new releases:

  1. Update version, url, and hash in serv.json
  2. Push to the bucket repo (Scoop's autoupdate handles future versions automatically)

VS Code Extension

Publish script: release-scripts/publish-vscode.sh

Prerequisites:

npm install -g @vscode/vsce

First-time setup:

  1. Get a Personal Access Token from https://dev.azure.com (Marketplace scope)
  2. 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

  1. Run regression tests: powershell test_regression.ps1
  2. Update version in:
    • release-scripts/homebrew/serv.rb
    • release-scripts/scoop/serv.json
    • vscode-support/extension/package.json
  3. Cross-compile: ./release-scripts/build-release.sh v1.x.x
  4. Create GitHub Release, upload archives
  5. Compute SHA256 hashes, update Homebrew formula + Scoop manifest
  6. Push Homebrew tap and Scoop bucket repos
  7. Publish VS Code extension: ./release-scripts/publish-vscode.sh
  8. Build and push Docker image
  9. 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

TypeExample
int42
float3.14
string"hello"
booltrue, false
nilnil
[]T[1, 2, 3]
map{ "key": "value" }
T?Optional (nullable) type
T | UUnion 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

ConstraintSupports
Comparable==, !=
Ordered<, >, <=, >=
Numeric+, -, *, /
IntegerInteger arithmetic
FloatFloating 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

FieldTypeDescription
req.bodystringRequest body (JSON string)
req.methodstringHTTP method
req.pathstringURL path
req.paramsmapURL 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 nil or an error, returns nil from 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

OperatorDescriptionExample
+Addition / concatenationa + b
-Subtractiona - b
*Multiplicationa * b
/Divisiona / 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

OperatorDescriptionExample
&Bitwise ANDa & b
|Bitwise ORa | b
^Bitwise XORa ^ b
<<Left shifta << 2
>>Right shifta >> 1

Comparison

OperatorDescription
==Equal
!=Not equal
<Less than
>Greater than
<=Less than or equal
>=Greater than or equal

Logical

OperatorDescription
andLogical AND
orLogical 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:

  1. stdlib/X — resolved from project root's stdlib/ directory
  2. ./path or ../path — resolved relative to the importing file
  3. 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 == 5assertion failed: got 3, want 5
  • assert x != 0assertion failed: expected value to not equal 0
  • assert x > 10assertion failed: 5 is not > 10
  • assert validassertion 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

AnnotationSQL equivalentNotes
@primaryPRIMARY KEYMark as primary key
@autoincrementAUTOINCREMENTAuto-increment integer (SQLite)
@requiredNOT NULLField cannot be null
@uniqueUNIQUEEnforce unique constraint
@default(value)DEFAULT valueSet default; use now for CURRENT_TIMESTAMP

Serv → SQL Type Mapping

Serv typeSQL type
intINTEGER
floatREAL
boolINTEGER (0/1)
stringTEXT
datetimeDATETIME

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-4
  • anthropic://claude-3-5-sonnet — Anthropic Claude
  • google://gemini-2.0-flash — Google Gemini
  • local://ollama/llama3 — Local Ollama model

Agent configuration keys:

KeyDescription
systemSystem prompt / instruction
modelModel URI
toolsList 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

  1. Project Setup
  2. Unified Application Block
  3. Server & Routes
  4. Request Binding
  5. HTML & Web Responses
  6. Database
  7. Variables & Data Types
  8. Type System
  9. Functions
  10. Control Flow
  11. Structs & Methods
  12. Interfaces
  13. Enums
  14. Generics
  15. Imports & Modules
  16. Authentication
  17. Pub/Sub Messaging
  18. Scheduling & Cron
  19. Object Store (S3)
  20. WebSockets
  21. Middleware
  22. AI Integration
  23. MCP Tools & Agents
  24. Schema Migrations (table DSL)
  25. Error Handling
  26. Concurrency
  27. Testing
  28. External Functions (FFI)
  29. Stream DSL WASM Transforms
  30. Logic Configuration Policy Engine
  31. Observability (OTel)
  32. Environment & Config
  33. 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 withContent-Type set
<html, <!DOCTYPEtext/html; charset=utf-8
<?xml, <rss, <feedapplication/xml; charset=utf-8
{...} or [...]application/json
anything elsetext/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

ConstraintSupports
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 — OpenAI
  • anthropic://claude-3-5-sonnet — Anthropic
  • google://gemini-2.0-flash — Google Gemini
  • local://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

AnnotationSQL equivalent
@primaryPRIMARY KEY
@autoincrementAUTOINCREMENT
@requiredNOT NULL
@uniqueUNIQUE
@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 traceparent propagation)
  • DB queries, cache ops, HTTP client calls, pub/sub, scheduler jobs

Built-in endpoints:

  • GET /metrics — Prometheus metrics
  • GET /health — liveness probe
  • GET /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

CommandDescription
pranor build <file>Compile to native binary
pranor run <file>Compile and run
pranor run <file> --watchRun 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 migrateApply table DSL migrations
serv create "<prompt>"AI-powered scaffolding
serv add <go-package>Generate .pnr.d declaration
serv packagesList installed declarations
serv doctorEcosystem health check
pranor deploy --target <t>Deploy (fly/railway/render/docker)
serv dockerize <file>Generate Dockerfile
serv doc <file>Generate API docs
serv replInteractive REPL
serv debug <file>Debug with Delve
serv auditAudit dependencies for CVEs
serv new <name>Scaffold new project
pranor build --target wasmCompile to WebAssembly

Operators Reference

Arithmetic

OpDescription
+Addition / string concat
-Subtraction
*Multiplication
/Division
%Modulo

Compound Assignment

+=, -=, *=, /=, %=

Bitwise

OpDescription
&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.hostDB_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

ModuleKey Exports
auth.pnrbearerToken, basicAuth, requireAuth
crypto.pnrhashPassword, verifyPassword, randomToken, hmacSign
jwt.pnrjwtEncode, jwtDecode, jwtIsExpired
sanitize.pnrescapeHTML, stripTags, escapeSQL, sanitizeFilename
ratelimit.pnrcreateLimiter, isAllowed, remaining, resetLimiter
mask.pnrmaskEmail, maskPhone, maskCard, maskString, redact
ip.pnrextractIP, isPrivate, isTrustedProxy, anonymizeIP

HTTP

ModuleKey Exports
response.pnrok, created, badRequest, notFound, serverError
pagination.pnroffset, pageResponse, parsePageParams
pagination_cursor.pnrencodeCursor, decodeCursor, cursorResponse
middleware.pnrcorsHeaders, requestId, logRequest
http_client.pnrgetJSON, postJSON, isSuccess, isClientError
url.pnrencodeURI, parseQuery, buildQuery, joinPath
cors.pnrallowOrigin, allowAll, preflightResponse

Utilities

ModuleKey Exports
datetime.pnrnow, timestamp, isExpired, formatDuration
strings_util.pnrslugify, truncate, capitalize, isEmpty
math.pnrmin, max, clamp, abs, percent, sum, average
sort.pnrreverse, minOf, maxOf
collections.pnrunique, flatten, chunk, first, last, countWhere

Data

ModuleKey Exports
csv.pnrparseCSV, parseRow, toCSV
base64.pnrencode, decode, isValid
diff.pnrhasChanged, fieldChanged, changeRecord

Config

ModuleKey Exports
env.pnrrequireEnv, envOrDefault, envInt, envBool
config.pnrgetConfig, requireConfig, configBool, configList
feature_flags.pnrenableFlag, disableFlag, isEnabled, toggleFlag

Resilience

ModuleKey Exports
retry.pnrbackoffDelay, defaultMaxRetries
circuit_breaker.pnrcreateBreaker, isOpen, recordSuccess, recordFailure
timeout.pnrwithDeadline, isTimedOut, remainingTime, elapsed
queue.pnrcreateQueue, enqueue, dequeue, queueSize

Concurrency

ModuleKey Exports
semaphore.pnrcreateSemaphore, tryAcquire, release, available
batch.pnrcreateBatch, addToBatch, isBatchFull, flushBatch

Processing

ModuleKey Exports
job.pnrcreateJob, startJob, completeJob, failJob
scheduler.pnrscheduleAfter, isScheduled, cancelSchedule

Reliability

ModuleKey Exports
idempotency.pnrcheckIdempotency, markProcessed, isProcessed
dlq.pnrcreateDLQ, sendToDLQ, dlqSize, clearDLQ

Integration

ModuleKey Exports
webhook.pnrbuildPayload, sendWebhook, verifySignature
events.pnron, emit, hasHandler

Observability

ModuleKey Exports
metrics.pnrcounter, gauge, recordLatency, trackRequest
tracing.pnrtraceId, startSpan, endSpan, traceContext

Multi-tenancy

ModuleKey Exports
tenant.pnrextractTenant, tenantConfig, isTenantActive, tenantFilter

Compliance

ModuleKey Exports
audit.pnrauditLog, auditAction, auditAccess, auditAuth, auditDenied

Operations

ModuleKey Exports
health.pnrhealthy, unhealthy, degraded, buildHealthResponse
graceful.pnrinitShutdown, isShuttingDown, isDrained
cache_patterns.pnrcacheKey, cacheGet, cacheSet, invalidate, computeIfAbsent

Testing

ModuleKey Exports
testing_helpers.pnrassertEqual, 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

FileDescription
01_scheduler.pnrTimer-based scheduled tasks
02_rest_api.pnrSimple REST API with routes
05_error_handling.pnrTry/catch error handling

HTTP & API

FileDescription
19_rate_limiting.pnrPer-route rate limiting
29_middleware.pnrMiddleware chains
43_request_validation.pnrBody validation with schemas
34_websocket_logging.pnrWebSocket endpoints + structured logging

Database & Cache

FileDescription
07_advanced_features.pnrSQLite + cache + match patterns
08_multi_database.pnrMultiple database connections
24_migrations.pnrDatabase migrations
22_query_hooks.pnrBefore-query hooks

Concurrency & Messaging

FileDescription
03_pubsub_concurrency.pnrPub/sub messaging with spawn
30_async_await.pnrAsync/await patterns
36_channels.pnrGo-style channels
11_concurrent_maps.pnrThread-safe maps

Language Features

FileDescription
25_structs.pnrStructs and methods
28_interfaces_collections.pnrInterfaces + collection methods
32_generics.pnrGeneric functions
46_generic_constraints.pnrConstrained generics (Ordered, Numeric)
33_string_methods.pnrString manipulation
38_destructuring.pnrlet { x, y } = obj
39_optional_chaining.pnruser?.address?.city
40_spread_operator.pnr{ ...defaults, ...overrides }
41_new_features.pnrEnums with values, type aliases

Integration

FileDescription
04_python_binding.pnrPython extern bindings
31_go_packages.pnrImporting Go packages
44_package_usage.pnrUsing serv add packages (uuid)
15_mcp_support.pnrMCP tool definitions

Configuration & Deployment

FileDescription
09_yaml_config.pnrYAML config file loading
37_structured_logging.pnrJSON logging, context loggers
42_config_validation.pnrRequired config validation
45_stdlib_usage.pnrUsing 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:

  1. Generate client and server certificates via Pranor Mesh root CA:
    curl -X POST http://localhost:8089/api/csr -d '{"service":"my-backend", "csr":"..."}'
    
  2. 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_SECRET is at least 32 cryptographically random bytes.
  • Do not expose /readyz or /healthz endpoints to public IP ranges; restrict ingress routing in Pranor 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

  1. Inspect Pranor Console trace list to see which downstream route is bottlenecking:
    serv status --json
    
  2. Check active connection pool state. If pool size matches max_open_conns, verify downstream database health.

2. Resolution

  1. Temporarily increase gateway concurrency limit by updating max_concurrent_requests in config.json and reloading:
    "max_concurrent_requests": 100
    
  2. 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

  1. Query key cache endpoint to check if rotated keys are public:
    curl -i http://localhost:8098/oauth/keys
    
  2. Verify if the JWKS URL :8098/oauth/keys returns valid RSA keys.

2. Resolution

  1. Force JWKS cache invalidation inside Pranor Core client by updating the cache expiration timestamp.
  2. 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

  1. Verify the go.work file is configured at the workspace root to include all local subpackages.
  2. Clean compiler build artifacts:
    go clean -cache -testcache
    
  3. 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

  1. Verify that Pranor Mesh is running:
    serv status
    
  2. Check firewall or Docker network bindings. The default mesh discovery port requires UDP :9999 to be open for multicast discovery.
  3. 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

PortService NameProtocolRole
8080Pranor GateHTTPIngress API Gateway
8081Pranor VaultHTTPS3 Storage Engine
8082Pranor PulseHTTP/STOMPQueue Broker
8083Pranor ConsoleHTTPOperational Dashboard
8084Pranor CacheRESP/HTTPRedis Cache Proxy
8085Pranor ChronoHTTPScheduler Control plane
8089Pranor MeshHTTP/UDPService Registry Node
8090Pranor TraceHTTP/gRPCOpenTelemetry Collector
8094Pranor NotifyHTTPTransactional Mail Agent
8096Pranor FlowHTTPDAG Workflow Engine
8097Pranor PoolHTTPSQL Persistence Proxy
8098Pranor AuthHTTPIdentity and MFA provider
8443Pranor TunnelHTTPSTunnel and Let's Encrypt Ingress

Interaction Flows

1. Ingress Request Authentication

  1. Client hits Pranor Gate on :8080/api/users.
  2. Pranor Gate extracts the token and validates against keys fetched from Pranor Auth OIDC configurations.
  3. If valid, request is forwarded down to the corresponding Pranor Mesh registered target host.

2. Event-Driven Workflow Run

  1. Pranor Chrono triggers a scheduled event on a timer payload.
  2. The execution goes to Pranor Pulse topics.
  3. A listening worker consumer picks up the task, executes a step, and writes artifacts to Pranor Vault S3 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-pulsed server engine?

    1. 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.
    2. 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.
    3. 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)?

    1. Frontend web apps, backend microservices, and mobile clients importing Pranor Pulse libraries must never be subject to copyleft restrictions.
    2. Enables 100% frictionless integration into any proprietary enterprise application stack.

2. Licensing Matrix by Component

ComponentRepository PathLicenseCommercial Exemption Option
Pranor Pulse Core Server Daemon (pranor-pulsed)serv/packages/Pranor PulseGNU AGPLv3Yes (Commercial License)
Pranor Pulse Dual-CLI (pranor-pulse)serv/packages/Pranor Pulse/cmd/pranor-pulseGNU AGPLv3Yes (Commercial License)
Local Browser OPFS WASM Engine (@pranor/queue-wasm)serv/packages/Pranor Pulse/pkg/opfsApache 2.0Included in Apache 2.0
Go & Multi-Language Client SDKsserv/packages/Pranor Pulse/sdks/*Apache 2.0Included in Apache 2.0
Pranor Console Web Inspector & Admin UIpranor-repo/pranor-consoleGNU AGPLv3Yes (Commercial License)
Pranor Pulse Enterprise Commercial Engine (pranor-ee)pranor-ee/src/Pranor PulseCommercial ProprietaryRequires License Key

3. Commercial Tiers & Feature Matrix

Pranor Pulse is structured into three clear commercial tiers:

Feature / ModuleCommunity (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 Review8x5 Email24/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

  1. Pranor Pulse Enterprise daemon startup:
    pranor-pulsed-ee --config=/etc/pranor-pulse/config.yaml --license-key=/etc/pranor-pulse/license.lic
    
  2. The daemon validates the cryptographically signed JWT / RSA license file:
    • Payload Check: Organization Name, Target Tier (enterprise vs sovereign), Max Core Limit, Expiration Date.
    • Signature Verification: Validated via offline public RSA key (no phone-home requirement for air-gapped sovereign environments).
  3. If valid, pranor-ee features (GeoReplication, KafkaAdapter, HSMUnsealer, eBPFXDP) activate seamlessly.

6. Summary Comparison: Licensing Choices

OptionOpen Source EngineClient LibrariesCloud Hyperscaler RiskEnterprise Monetization Potential
Recommended StrategyAGPLv3Apache 2.0Low (Hyperscalers must share SaaS code or buy license)Very High (Direct conversion path to Commercial EE)
Pure Apache 2.0Apache 2.0Apache 2.0High (AWS can re-sell without contributing back)Medium
BSL 1.1 (Business Source)BSL 1.1Apache 2.0LowHigh

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.Pointer mappings, 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.Pointer memory 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 Core database 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 .pnr files 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

  1. 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.
  2. 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.
  3. 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/ast and go/types packages. 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=wasm or 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 Hub can distribute Serv-specific plugins and DSL macros, the core package manager must support resolving standard Go dependencies directly from GitHub/Proxy via standard go.mod integration.

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 .go files. 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 .pnr source lines.