@kindgi/policy-contract
npm install @kindgi/policy-contract · source
Type contract for the tenant policy catalog: the Policy wire shape, the PolicyRegistryBinding storage interface, the executor interfaces that apply one policy kind at runtime, and the specs of the retention, tool-errors and hitl kinds with their validators. createApp in @kindgi/api mounts the /v1/policies routes when given a PolicyRegistryBinding, and @kindgi/agents accepts a PolicyRegistry to apply model-routing, tool-errors and hitl policies. The bindings and executors themselves are implemented by the Kindgi runtime or by the embedding application.
Purpose
Section titled “Purpose”Keep storing policies separate from enforcing them. The registry is a versioned, tenant-scoped catalog that treats spec as opaque JSON selected by kind. Each enforcement point binds a PolicyExecutor for one kind; the executor alone knows how to read that kind's spec and reduce a tenant's policies to one decision. The package depends only on @kindgi/types, so authoring code can reference these interfaces without pulling in a runtime.
Exports
Section titled “Exports”PolicyRegistryBinding— versioned CRUD with the same shape as the agent and flow registries; opaque cursors.list(input)— latest version per policy id, sorted by id; optionalpolicyKind(exact) andnameFilter(prefix on id).get(input)/getVersion(input)— latest active version, or one specific version including tombstoned ones;nullwhen unknown.headExists(input)— whether the id exists at all, so callers can tell a retired policy (no active version) from an unknown one.listVersions(input)— versions of one id; active only unlessincludeTombstoned: true, in which case tombstoned rows carryunregisteredAt.publish(input)—ok, oralready-registeredwhen the same(policyId, version)is published again.unregister(input)— soft-tombstones one version; with no active version left the policy is retired until the next publish.reinstateVersion(input)— idempotent un-tombstone;ok(withwasTombstoned) ornot-found.
Policy—{ id, tenantId, version, kind, description?, spec }.PolicyVersionRowaddsunregisteredAt?.- I/O shapes —
PolicyListInput,PolicyGetInput,PolicyGetVersionInput,PolicyListVersionsInput,PolicyPublishInput,PolicyUnregisterInput,PolicyReinstateVersionInput,PolicyPage,PolicyVersionPage,PolicyPublishOutcome,PolicyUnregisterOutcome,PolicyReinstateVersionOutcome. POLICY_KINDS/PolicyKind— closed set:access-control,model-routing,adapter-allowlist,rate-limit,retention,compliance,tool-errors,hitl.PolicyExecutor<K, TCtx, TResult>—kindplusevaluate(ctx). Fetches the tenant's policies of its kind (caching as it sees fit), merges them, and returns the decision for the caller to apply.PolicyRegistry— executors keyed by kind:register(executor),evaluate(kind, ctx)(resolves toundefinedwhen no executor is bound, so callers keep their default behavior),boundKinds().PolicyEvalContext— the minimum context every executor receives:{ tenantId }.- Retention —
RetentionSpec({ domain, graceSeconds, mode }),RETENTION_DOMAINS/RetentionDomain(the tombstoning domains plus a'*'wildcard), andvalidateRetentionSpec(input), which returns{ kind: 'ok', value }or{ kind: 'err', error: RetentionSpecValidationError }.graceSecondscounts from the tombstoned row'sunregisteredAt:0purges on the next sweep,-1never purges.mode: 'archive'is reserved and rejected withunsupported-mode. - Tool errors —
ToolErrorsSpec({ maxRetries?, retryOn? }), the spec of atool-errorspolicy and an agent'stoolErrors: how many failed tool calls a turn sends back to the model, for whichTOOL_ERROR_KINDS.MAX_TOOL_ERROR_RETRIES(10),validateToolErrorsSpec(input). - HITL —
HitlSpec({ maxTimeoutMs?, minReviewerRole?, tools? }), the spec of ahitlpolicy: approval rules a tenant holds every agent to, only stricter than the agent's own — the shorter timeout, the higher reviewer role, and per tool id the stricter gate (toolsmaps a tool id to aToolHitlModeor aToolHitlRule{ mode, requiredRole? }).TOOL_HITL_MODES(never_ask,ask_on_first_use,always_ask, loosest first),REVIEWER_ROLES(standard,senior,admin, lowest first),validateHitlSpec(input)(issues carry JSON Pointer paths),combineHitlSpecs(specs)(one spec at least as strict as each), and the helperstoolHitlRule,stricterToolHitlRule,higherRole. validatePolicySpec(kind, spec)— thePolicySpecIssues of a policy'sspecunder its kind's contract, fortool-errorsandhitl; other kinds pass. The/v1/policiesroutes refuse a spec with issues.
Example
Section titled “Example”import { validateRetentionSpec, type PolicyEvalContext, type PolicyExecutor, type PolicyRegistryBinding, type RetentionDomain, type RetentionSpec,} from '@kindgi/policy-contract';
type RetentionPlan = ReadonlyMap<RetentionDomain, RetentionSpec>;
// Reduce a tenant's `retention` policies to one spec per domain.function createRetentionExecutor( store: PolicyRegistryBinding,): PolicyExecutor<'retention', PolicyEvalContext, RetentionPlan> { return { kind: 'retention', async evaluate({ tenantId }) { const plan = new Map<RetentionDomain, RetentionSpec>(); const page = await store.list({ tenantId, policyKind: 'retention', limit: 100 }); for (const policy of page.data) { // Retention specs live in a `{ v: 1, doc }` envelope on `Policy.spec`. const checked = validateRetentionSpec(policy.spec.doc); if (checked.kind === 'ok') plan.set(checked.value.domain, checked.value); } return plan; }, };}
// `store`, `policies` (a PolicyRegistry) and `tenantId` are supplied by the runtime.await store.publish({ tenantId, policy: { id: 'acme.retention.tools', tenantId, version: '1.0.0', kind: 'retention', spec: { v: 1, doc: { domain: 'tool', graceSeconds: 30 * 86_400, mode: 'purge' } }, },});
policies.register(createRetentionExecutor(store));const plan = await policies.evaluate<PolicyEvalContext, RetentionPlan>('retention', { tenantId });Non-goals
Section titled “Non-goals”- Enforcement in the registry.
PolicyRegistryBindingis publish and read only; each consumer applies policy at its own boundary. - Validating every kind's
spec. The registry treatsspecas opaque; deeper validation belongs to the executor for that kind. This package ships validators forretention,tool-errorsandhitlonly. - Implementations. No binding, registry, or executor implementation is included — interfaces, constants, and pure validators and combinators.
- Fetching or caching in
PolicyRegistry. Each executor decides how it loads and caches policies.
Related
Section titled “Related”@kindgi/api— the/v1/policiesroutes and re-exports of the catalog types.@kindgi/agents— evaluatesmodel-routing,tool-errorsandhitlthrough a boundPolicyRegistry.@kindgi/types—TenantIdandCursor.