npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@fabricorg/policy-opa

v0.4.0

Published

Open Policy Agent (OPA) adapter for @fabricorg/platform. Wraps a Rego decision behind Fabric's PolicyEvaluator contract — Fabric keeps policyId.vN canonical for audit; engine evidence carries OPA provenance (decision path, input hash, bundle revision). Ze

Downloads

504

Readme

@fabricorg/policy-opa

Open Policy Agent (OPA) adapter for @fabricorg/platform. Lets a Rego decision back a Fabric code policy while preserving Fabric's canonical audit identity (policyId.vN, policyVersion) and capturing OPA-side provenance (decisionPath, inputHash, optional version) under PolicyDispatchEvidence.engine.

The platform stays zero-dependency and vendor-neutral; the OPA-specific glue lives here.

Install

pnpm add @fabricorg/platform @fabricorg/policy-opa
# Optionally the official SDK if you want the high-level client shape:
pnpm add @open-policy-agent/opa

@fabricorg/policy-opa has zero runtime dependencies. Two wiring shapes are supported:

  • OpaPolicyExecutor (recommended) — execute(path, input) → OpaPolicyExecution. Surfaces full OPA-side provenance (decisionId, bundleRevision, opaVersion, raw provenance block) under engine.metadata. The package ships executorFromOpaHttp({ baseUrl, ... }) for this.
  • OpaPolicyClientevaluate(path, input) → Promise<unknown>. Structurally compatible with the high-level @open-policy-agent/opa SDK. Simpler to wire but the SDK discards decisionId and provenance from the response, so audit evidence is thinner.

If a service implements both methods, execute is preferred.

Usage — the four pieces

A complete OPA-backed evaluator has four pieces, three of which the vertical owns.

1. The vertical policy manifest

Keep Fabric policy IDs, versions, decision paths, Rego package names, and bundle selectors in one place. Don't inline strings at call sites.

import type { OpaPolicyBinding } from "@fabricorg/policy-opa";

export const LENDING_POLICY_BINDINGS = {
  tcpaContactWindow: {
    policyId: "lending.tcpa_contact_window.v1",
    version: 1,
    decisionPath: "fabric/lending/tcpa/contact_window/decision",
    regoPackage: "fabric.lending.tcpa.contact_window",
    bundleName: "lending",
  },
} satisfies Record<string, OpaPolicyBinding>;

bundleName is optional. Set it when your OPA serves multiple bundles and you need audit evidence to record exactly which bundle's revision produced the decision. If OPA serves only one bundle, omit bundleName and the adapter uses that bundle's revision unambiguously.

2. The fact builder (TypeScript)

Snapshot the facts OPA needs from your database. OPA never touches your DB directly — your TypeScript host owns reads, tenant scoping, and redaction.

import type { PolicyContext } from "@fabricorg/platform/policies";

async function buildContactWindowInput(ctx: PolicyContext) {
  const consent = await ctx.db.tcpaConsent.findUnique({
    where: { partyId: ctx.parameters.partyId },
  });
  return {
    tenantId: ctx.tenantId,
    actionId: ctx.actionId,
    now: (ctx.now ?? new Date()).toISOString(),
    consent: consent
      ? { active: consent.status === "active", scope: consent.scope }
      : null,
    contactWindow: { startHour: 8, endHour: 21, timezone: ctx.parameters.timezone },
  };
}

3. The Rego policy (house style)

Have Rego return a structured decision object, not a bare boolean. The adapter validates against this shape.

package fabric.lending.tcpa.contact_window
import rego.v1

decision := {
  "result": result,
  "reason": reason,
  "conditionResults": condition_results,
  "guidance": {
    "summary": reason,
    "factsUsed": facts_used,
    "correctiveActions": corrective_actions,
  },
}

result := "block" if not consent_active
result := "block" if not within_window
default result := "pass"

# ...rules computing reason, condition_results, facts_used, corrective_actions...

The validator accepts the following fields (TypeScript types: OpaDecision):

{
  result: "pass" | "warn" | "block";  // required
  reason?: string;
  conditionResults?: Array<{
    conditionId: string;
    passed: boolean;
    result: "pass" | "warn" | "block";
    reason?: string;
  }>;
  guidance?: PolicyGuidance;
  obligations?: Record<string, unknown>;
}

Every guidance.correctiveActions[] item must include kind:

  • invoke_action: requires actionId and optional parameters.
  • navigate: optional route or structured target.
  • manual: human instruction with no automated operation.

4. The evaluator factory (this package)

import { createOpaPolicyEvaluator } from "@fabricorg/policy-opa";
import { registerPolicy } from "@fabricorg/platform/policies";

import { LENDING_POLICY_BINDINGS } from "../policies/manifest";

const tcpaPolicy = createOpaPolicyEvaluator({
  binding: LENDING_POLICY_BINDINGS.tcpaContactWindow,
  buildInput: buildContactWindowInput,
  serviceKey: "opa",     // default; ctx.services.opa must hold an OpaPolicyClient
  onError: "block",      // default; honors Fabric's default-deny posture
});

registerPolicy(tcpaPolicy);

Host wiring

The runtime host injects an OPA executor (or client) into PolicyContext.services when it builds a policy context.

Recommended — HTTP executor with full provenance:

import { executorFromOpaHttp } from "@fabricorg/policy-opa";

const opa = executorFromOpaHttp({
  baseUrl: process.env.OPA_URL ?? "http://localhost:8181",
  // provenance: true is the default — `?provenance=true` is appended to
  // every request so OPA returns decision_id and bundle revisions.
  // headers: { authorization: `Bearer ${process.env.OPA_TOKEN}` },
});

// when constructing PolicyContext for evaluatePolicyDefinitions:
const policyCtx = {
  tenantId, spaceId, actionInvocationId, actionId, parameters,
  db,
  mode: "execute",
  services: { opa },
};

Alternative — wrap the high-level SDK:

import { OPAClient } from "@open-policy-agent/opa";
import { executorFromOpaClient } from "@fabricorg/policy-opa";

const opa = executorFromOpaClient(new OPAClient(process.env.OPA_URL ?? "http://localhost:8181"));
// ... services: { opa }

This works (the adapter accepts an OpaPolicyClient directly too, so wrapping isn't strictly required), but the high-level SDK discards decision_id and provenance — audit evidence is thinner than with executorFromOpaHttp.

What lands in audit evidence

For a successful OPA evaluation under the HTTP executor (with provenance enabled), PolicyDispatchEvidence.engine looks like:

{
  "name": "opa",
  "version": "0.65.0",
  "metadata": {
    "decisionPath": "fabric/lending/tcpa/contact_window/decision",
    "regoPackage": "fabric.lending.tcpa.contact_window",
    "inputHash": "sha256:8a3c…",
    "decisionId": "01J0Q9MZ…",
    "bundleName": "lending",
    "bundleRevision": "2026-05-16.3",
    "provenance": {
      "version": "0.65.0",
      "bundles": { "lending": { "revision": "2026-05-16.3" } }
    },
    "conditionResults": [{ "conditionId": "consent_active", "passed": true, "result": "pass" }]
  }
}

Fabric's policyId / policyVersion remain canonical ("what was evaluated"). engine.metadata.{decisionId, bundleName, bundleRevision} answers "which artifact produced the answer", and decisionId is the join key against OPA's own decision-log stream if you ship it to a SIEM. User-facing facts and remediation steps live on PolicyOutcome.guidance, not in engine metadata, so code policies and OPA policies render through the same audit UI contract.

If OPA serves multiple bundles and the binding has no bundleName selector, bundleRevision is omitted and bundleRevisionStatus: "ambiguous" is recorded — audit dashboards can flag these for a manual fix.

Failure modes

| Situation | Result | engine.metadata.error | |---|---|---| | ctx.services[serviceKey] missing or not an OpaPolicyClient | block | client_unavailable | | buildInput(ctx) throws | block | input_build_failed | | OPA call throws (network, timeout, 5xx) | block | evaluation_failed | | OPA returns a shape that doesn't match OpaDecision | block | invalid_response |

Default behavior is onError: "block" — Fabric's default-deny posture. Pass onError: "throw" to propagate the error and let Fabric surface it as a failed invocation instead.

Preview mode

OPA evaluation is a network call, so the adapter defaults previewSafe: false. Fabric's runtime will skip the evaluator in preview mode and yield a warn outcome with structured skip evidence — you don't need to opt out of preview manually. Pass previewSafe: true only if your OPA topology guarantees deterministic, side-effect-free evaluation appropriate for preview.

What this package does NOT do

  • It doesn't talk to your database. buildInput is yours.
  • It doesn't run an OPA server. You bring an OPA sidecar (or remote service) and inject an executor or client.
  • It doesn't ship Rego policies. You author and bundle those.
  • It doesn't auto-discover bundle names. Bind one explicitly with binding.bundleName if OPA serves multiple bundles, or accept the "ambiguous" status in evidence.

License

MIT — see LICENSE.