@watchlight/engine
v0.2.1
Published
Watchlight Developer Edition authorization engine for Node — the real wl-apdp Cedar pipeline (delegation → intent → goal → policy selection → Cedar → strict-subset attenuation → enforcement effects) compiled to WebAssembly. In-process, fail-closed, zero i
Downloads
798
Maintainers
Readme
@watchlight/engine
The Watchlight Developer Edition authorization engine for Node — the real
wl-apdp Cedar pipeline (delegation → intent → goal → policy selection → Cedar
→ strict-subset sub-agent attenuation → enforcement effects) compiled to
WebAssembly. In-process, fail-closed, zero infrastructure.
This is the same authorization core as the Python engine
(watchlight-engine) — a
conformance test asserts both bindings return identical decisions for identical
inputs. No sidecar, no network, no policy re-implementation in JS.
Install
npm install @watchlight/engineNode ≥ 18 (uses WebAssembly bulk-memory + Web Crypto, both stable there).
Use
const { Engine } = require("@watchlight/engine");
const engine = await Engine.create();
await engine.addPolicy({
name: "allow-read",
code: 'permit(principal, action == Action::"read", resource);',
});
// Authorize — context defaults to {}. Fail-closed: unmatched → Deny.
const resp = await engine.authorize({
principal: 'User::"alice"',
action: 'Action::"read"',
resource: 'Document::"doc1"',
});
console.log(resp.decision); // "Allow"
// Strict-subset sub-agent scope attenuation (synchronous).
const atten = engine.attenuateScope(
{ allowed_tools: ["read", "search"], allowed_resources: [], allowed_intents: [], max_depth: 5, time_budget_seconds: 600, depth: 0 },
{ allowed_tools: ["read"], allowed_resources: [], allowed_intents: [], max_depth: 2, time_budget_seconds: 300 }
);
console.log(atten.decision); // "Allow" (child ⊆ parent) — a superset would Denyauthorize / addPolicy are async (the engine's pipeline is async;
WebAssembly has no blocking wait, so they return Promises). attenuateScope
is synchronous. TypeScript types ship in index.d.ts.
Obligations on permits
A permit can attach constraints to its own Allow with @obligate_* Cedar
annotations. They are validated at addPolicy time and returned on the
decision — per policy on details.policy_results[].obligations (applicable
permits only) and merged on details.obligations — on Allow only. A Deny
carries none; every field is omitted when unset.
await engine.addPolicy({
name: "list-customers",
code: `
@obligate_redact("ssn, card.pan") // comma-separated field names (at least one)
@obligate_max_items("8") // decimal integer 1..=4294967295
@obligate_log_values("false") // exactly "true" or "false"
@obligate_watermark("acme") // unknown keys pass through raw: extra.watermark
permit(principal, action == Action::"list_customers", resource);
`,
});
const resp = await engine.authorize({ principal: 'User::"carol"', action: 'Action::"list_customers"', resource: 'Document::"crm"' });
resp.details.obligations;
// { redact: ["ssn", "card.pan"], max_items: 8, log_values: false }
resp.details.policy_results[0].obligations.extra; // { watermark: "acme" }Merge across several applicable permits is deterministic: redact = union,
max_items = min, log_values = logical AND; extra is never merged (read
it per policy). A malformed value — @obligate_max_items("eight"),
@obligate_log_values("yes"), an empty @obligate_redact — or any
@obligate_* on a forbid rejects the policy at addPolicy; an
obligation is never silently dropped.
Graduation to Enterprise
The same code moves to the networked Watchlight control plane (signed lineage, cross-tenant isolation, IdP/mTLS attestation) by pointing at it — your policies and call sites do not change.
Build from source
Requires the wasm32-unknown-unknown target, wasm-pack,
and (optionally, for a smaller artifact) a modern binaryen wasm-opt:
rustup target add wasm32-unknown-unknown
npm run build # → ./wasm (regenerated; not committed)
npm test # Node conformance (decisions, obligations, load-time rejection)
npm run conformance # Node + Python parity (needs: pip install watchlight-engine)License
Watchlight Engine — Developer Edition License (see LICENSE). Free for
development, testing, and production, including commercially.
