OSS-first docs

These docs teach the open system first: contracts, generated surfaces, runtimes, governance, and incremental adoption. Studio shows up as the operating layer on top, not as the source of truth.

PolicySpec & PolicyEngine

PolicySpec gives a declarative, typed home for access-control logic covering:

field.key.label
PolicySpec & PolicyEngine
field.version.label
field.type.label
field.title.label
PolicySpec & PolicyEngine
field.description.label

PolicySpec gives a declarative, typed home for access-control logic covering:

field.tags.label
tech,contracts,policy
field.owners.label
field.stability.label
public

Purpose

`PolicySpec` gives a declarative, typed home for access-control logic covering:

**Who** can perform an action (ABAC/ReBAC style rules)

**What** they can access (resources + optional field-level overrides)

**When** special conditions apply (contextual expressions)

**How** PII should be handled (consent/retention hints)

`PolicyEngine` evaluates one or more policies and returns an `allow`/`deny` decision, field-level outcomes, and PII metadata suitable for downstream enforcement (`OperationSpecRegistry` → `ctx.decide`).

Location

Types & registry: `packages/libs/contracts/src/policy/spec.ts`

Runtime evaluation: `packages/libs/contracts/src/policy/engine.ts`

Tests: `packages/.../policy/engine.test.ts`

`PolicySpec`

export interface PolicySpec {
  meta: PolicyMeta;          // ownership metadata + { name, version, scope? }
  rules: PolicyRule[];       // allow/deny rules for actions
  fieldPolicies?: FieldPolicyRule[];
  pii?: { fields: string[]; consentRequired?: boolean; retentionDays?: number };
  relationships?: RelationshipDefinition[];
  consents?: ConsentDefinition[];
  rateLimits?: RateLimitDefinition[];
  opa?: { package: string; decision?: string };
}

`PolicyRule`

`effect`: `'allow' | 'deny'`

`actions`: e.g., `['read', 'write', 'delete']` (string namespace is flexible)

`subject`: `{ roles?: string[]; attributes?: { attr: matcher } }`

`resource`: `{ type: string; fields?: string[]; attributes?: {...} }`

`relationships`: `{ relation, objectId?, objectType? }[]` → ReBAC checks (use `objectId: '$resource'` to target the current resource)

`requiresConsent`: `['consent_id']` → references spec-level consent definitions

`flags`: feature flags that must be enabled (`DecisionContext.flags`)

`rateLimit`: string reference to `rateLimits` entry or inline object `{ rpm, key?, windowSeconds?, burst? }`

`escalate`: `'human_review' | null` to indicate manual approval

`conditions`: optional expression snippets evaluated against `{ subject, resource, context }`

`FieldPolicyRule`

`field`: dot-path string (e.g., `contact.email`)

`actions`: subset of `['read', 'write']`

Same `subject` / `resource` / `conditions` shape

Useful for redacting specific fields, even when the global action is allowed

`RelationshipDefinition`

Canonical tuples for relationship graph (`subjectType`, `relation`, `objectType`, `transitive?`)

`ConsentDefinition`

`{ id, scope, purpose, lawfulBasis?, expiresInDays?, required? }`

`RateLimitDefinition`

`{ id, rpm, key?, windowSeconds?, burst? }`

`PolicyRef`

`{ name: string; version: string }` → attach to contract specs / workflows

Registry

const registry = new PolicyRegistry();
registry.register(CorePolicySpec);
const spec = registry.get('core.default', 1);

Guarantees uniqueness per `(name, version)` and exposes helpers to resolve highest versions.

Engine

const engine = new PolicyEngine(policyRegistry);

const decision = engine.decide({
  action: 'read',
  subject: { roles: ['admin'] },
  resource: { type: 'resident', fields: ['contact.email'] },
  policies: [{ name: 'core.default', version: '1.0.0' }],
});
/*
{
  effect: 'allow',
  reason: 'core.default',
  fieldDecisions: [{ field: 'contact.email', effect: 'allow' }],
  pii: { fields: ['contact.email'], consentRequired: true }
}
*/

First matching **deny** wins; otherwise the first **allow** is returned.

Field policies are aggregated across referenced policies:

Later denies override earlier allows for a given field.

Returned as `fieldDecisions` to simplify downstream masking.

PII metadata is surfaced when defined to help adapt logging/telemetry.

Expression Support

Conditions accept small JS snippets (e.g., `subject.attributes.orgId === context.orgId`). The engine runs them in a constrained scope (`subject`, `resource`, `context`) without access to global state.

ReBAC & Relationships

Provide relationship tuples via `PolicySpec.relationships` for documentation/validation.

Reference them inside rules with `relationships: [{ relation: 'manager_of', objectType: 'resident', objectId: '$resource' }]`.

The execution context must populate `subject.relationships` (`[{ relation, object, objectType }]`) for the engine to evaluate ReBAC guards.

Consent & Rate Limits

Declare reusable consent definitions under `consents`. Rules list the IDs they require; if a user session lacks the consent (`DecisionContext.consents`), the engine returns `effect: 'deny'` with `reason: 'consent_required'` and enumerates missing consents.

Attach rate limits either inline or via `rateLimits` references. When a rule matches, the engine surfaces `{ rpm, key, windowSeconds?, burst? }` so callers can feed it to shared limiters.

OPA Adapter

`OPAPolicyAdapter` bridges engine decisions to Open Policy Agent (OPA). It forwards the evaluation context + policies to OPA and merges any override result (`effect`, `reason`, `fieldDecisions`, `requiredConsents`).

Use when migrating to OPA policies or running defense-in-depth: call `engine.decide()`, then pass the preliminary decision to `adapter.evaluate(...)`. The adapter marks merged decisions with `evaluatedBy: 'opa'`.

OPA inputs include meta, rules, relationships, rate limits, and consent catalogs to simplify policy authoring on the OPA side.

Contract Integration

`ContractSpec.policy` now supports:

policy: {
  auth: 'anonymous' | 'user' | 'admin';
  ...
  policies?: PolicyRef[];                // policies evaluated before execution
  fieldPolicies?: {                      // field hints (read/write) per policy
    field: string;
    actions: ('read' | 'write')[];
    policy?: PolicyRef;
  }[];
}

Adapters can resolve refs through a shared `PolicyEngine` and populate `ctx.decide` so `OperationSpecRegistry.execute` benefits from centralized enforcement.

Authoring Guidelines

1.

Prefer **allow-by-default** policies but explicitly deny sensitive flows (defense-in-depth).

2.

Keep rule scopes narrow (per feature/operation) and compose multiple `PolicyRef`s when necessary.

3.

Store PII field lists here to avoid duplication across logs/telemetry.

4.

Use explicit rule reasons for auditability and better developer feedback.

5.

Treat versioning seriously; bump `meta.version` whenever behavior changes.

Future Enhancements

Richer expression language (composable predicates, time-based conditions).

Multi-tenant relationship graph services (store/resolve relationships at scale).

Tooling that auto-generates docs/tests for policies referenced in specs.