diff --git a/CLAUDE.md b/CLAUDE.md index 4f65c01..73bbb2b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -100,10 +100,10 @@ packages// - supertest for Express middleware integration tests ### current status -Tests are not yet implemented across packages. Priority order for adding tests: -1. proxyabl-core (security-critical: SSRF, auth modes, proxy execution) -2. transformabl-core (correctness-critical: PII regex patterns) -3. validatabl-core (security-critical: policy enforcement) -4. identifiabl-core (security-critical: JWT verification) -5. limitabl-core (correctness-critical: rate limiting, budget tracking) -6. request-context, explicabl, middleware wrappers +Every `-core` package and the middleware wrappers have vitest suites under +`packages//__tests__/` (note: `__tests__/`, not `tests/`). `npm test` at +the repo root runs the whole suite; `npm run build` must pass first (the tests +import built workspace packages). Security-critical coverage is real: +proxyabl-core SSRF, transformabl-core PII regex + redaction, validatabl-core +policy enforcement, identifiabl-core JWT verification, limitabl-core +rate/budget/agent-guard. diff --git a/README.md b/README.md index d4b42b4..923b522 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,16 @@ Hermes Agent uses a native Python plugin instead ([why](https://agenticcontrolpl pip install hermes-acp && hermes plugins enable acp ``` +**Prefer to see the guarantees work first — zero config, no IdP, no backend?** + +```bash +git clone https://github.com/agentic-control-plane/GatewayStack +cd GatewayStack/examples/quickstart && npm install && npm start +``` + +Prints three real decisions — a deny-by-default policy, PII redaction, and a +rate limit — as pure local library code ([`examples/quickstart`](examples/quickstart)). + Your own framework code — drop in a package: ```bash diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..d9d30f0 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,12 @@ +# Examples + +Runnable GatewayStack examples. + +| Example | What it shows | Setup | +|---------|---------------|-------| +| [`quickstart/`](./quickstart) | Deny-by-default policy, PII redaction, and rate limiting — three real decisions printed to your console | **None.** `npm install && npm start` | + +The quickstart uses only the framework-agnostic `-core` packages, so it runs +anywhere Node runs, with zero external services. Wiring the same layers as +Express middleware (which does need an IdP for the identity layer) is covered in +the [root README](../README.md#full-stack-example). diff --git a/examples/quickstart/README.md b/examples/quickstart/README.md new file mode 100644 index 0000000..0c43835 --- /dev/null +++ b/examples/quickstart/README.md @@ -0,0 +1,32 @@ +# Quickstart — governance in 30 seconds + +Three real GatewayStack decisions running on your machine. **No IdP, no backend, +no config.** + +```bash +npm install +npm start +``` + +You'll see: + +``` +1 · Deny-by-default policy (validatabl) + ✅ ALLOW read_file → Matched rule: allow-reads (allow) + 🛑 DENY delete_database → No rules matched; default: deny + +2 · PII redaction (transformabl) + in : Email me at john@acme.com or call about SSN 123-45-6789. + out: Email me at [EMAIL] or call about SSN [SSN]. + detected: email, ssn + +3 · Rate limit + budget guard (limitabl) + call 1: ✅ ALLOW + ... + call 4: 🛑 DENY → Rate limited. Retry after 60s +``` + +Everything here is pure `-core` library code — the same logic the Express +middleware wraps. See [`index.ts`](./index.ts); it's ~50 lines. To wire these as +HTTP middleware in front of your own tools/models, see the +[full-stack example](../../README.md#full-stack-example). diff --git a/examples/quickstart/index.ts b/examples/quickstart/index.ts new file mode 100644 index 0000000..8568c57 --- /dev/null +++ b/examples/quickstart/index.ts @@ -0,0 +1,69 @@ +/** + * GatewayStack — 30-second quickstart. + * + * Three real governance decisions, running entirely on your machine. No IdP, no + * backend, no config. Run it: + * + * npm install && npm start + * + * Everything here is pure library code from the `-core` packages — the same + * logic the Express middleware wraps. If you can see these decisions happen, + * you can drop them into your own gateway. + */ + +import { applyPolicies, type PolicySet } from "@gatewaystack/validatabl-core"; +import { transformContent } from "@gatewaystack/transformabl-core"; +import { LimitablEngine } from "@gatewaystack/limitabl-core"; + +const ALLOW = "\x1b[32m✅ ALLOW\x1b[0m"; +const DENY = "\x1b[31m🛑 DENY \x1b[0m"; +const h = (s: string) => console.log(`\n\x1b[1m${s}\x1b[0m`); + +// ───────────────────────────────────────────────────────────────────────── +// 1. Deny-by-default policy — a tool call is refused unless a rule allows it. +// ───────────────────────────────────────────────────────────────────────── +h("1 · Deny-by-default policy (validatabl)"); + +const policy: PolicySet = { + defaultEffect: "deny", // nothing is allowed unless a rule says so + rules: [ + { + id: "allow-reads", + effect: "allow", + conditions: [{ field: "tool", operator: "in", value: ["read_file", "list_files"] }], + }, + ], +}; + +for (const tool of ["read_file", "delete_database"]) { + const d = applyPolicies(policy, { identity: { sub: "agent-1" }, tool }); + console.log(` ${d.allowed ? ALLOW : DENY} ${tool.padEnd(16)} → ${d.reason}`); +} + +// ───────────────────────────────────────────────────────────────────────── +// 2. PII redaction — sensitive values are stripped before they leave. +// ───────────────────────────────────────────────────────────────────────── +h("2 · PII redaction (transformabl)"); + +const raw = "Email me at john@acme.com or call about SSN 123-45-6789."; +const result = transformContent(raw, { redaction: { mode: "placeholder" } }); + +console.log(` in : ${raw}`); +console.log(` out: ${result.content}`); +console.log(` detected: ${result.piiMatches.map((m) => m.type).join(", ") || "none"}`); + +// ───────────────────────────────────────────────────────────────────────── +// 3. Rate limit — the 4th call in the window is refused. +// ───────────────────────────────────────────────────────────────────────── +h("3 · Rate limit + budget guard (limitabl)"); + +const engine = new LimitablEngine({ rateLimit: { windowMs: 60_000, maxRequests: 3 } }); +const key = { sub: "agent-1" }; + +for (let i = 1; i <= 5; i++) { + const r = engine.preflight(key); + console.log(` call ${i}: ${r.allowed ? ALLOW : DENY} ${r.allowed ? "" : "→ " + r.reason}`); +} + +h("Next → wire these as Express middleware: see ../../README.md#full-stack-example"); +console.log(); diff --git a/examples/quickstart/package.json b/examples/quickstart/package.json new file mode 100644 index 0000000..70abf75 --- /dev/null +++ b/examples/quickstart/package.json @@ -0,0 +1,17 @@ +{ + "name": "gatewaystack-quickstart", + "private": true, + "type": "module", + "description": "30-second, zero-config GatewayStack governance demo", + "scripts": { + "start": "tsx index.ts" + }, + "dependencies": { + "@gatewaystack/validatabl-core": "^0.1.0", + "@gatewaystack/transformabl-core": "^0.4.0", + "@gatewaystack/limitabl-core": "^0.1.0" + }, + "devDependencies": { + "tsx": "^4.19.2" + } +}