@absolutejs/policy 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,3 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AbsoluteJS
package/README.md ADDED
@@ -0,0 +1,10 @@
1
+ # @absolutejs/policy
2
+
3
+ Immutable, provider-neutral policy lifecycle for agent actions. Drafts are
4
+ validated and content-digested, versions only increase, activation atomically
5
+ retires the previous version, evaluations record the exact version and digest,
6
+ and simulation evaluates an unpersisted candidate without changing live policy.
7
+
8
+ `createAuthzenAdapter()` speaks the OpenID AuthZEN evaluation API while custom
9
+ adapters can target Cedar, OPA, cloud IAM, or an embedded rules engine. PostgreSQL
10
+ enforces one active version per policy with a partial unique index.
@@ -0,0 +1,70 @@
1
+ export type PolicyStatus = "draft" | "active" | "retired";
2
+ export type PolicyDocument = {
3
+ createdAt: number;
4
+ createdBy: string;
5
+ digest: string;
6
+ policyId: string;
7
+ provider: string;
8
+ source: unknown;
9
+ status: PolicyStatus;
10
+ version: number;
11
+ };
12
+ export type PolicyDecision = {
13
+ allowed: boolean;
14
+ decisionId: string;
15
+ evaluatedAt: number;
16
+ metadata?: Record<string, unknown>;
17
+ policyDigest: string;
18
+ policyId: string;
19
+ policyVersion: number;
20
+ reason?: string;
21
+ };
22
+ export type PolicyAdapter<Input = unknown> = {
23
+ evaluate: (document: PolicyDocument, input: Input) => Promise<Omit<PolicyDecision, "decisionId" | "evaluatedAt" | "policyDigest" | "policyId" | "policyVersion">>;
24
+ validate?: (source: unknown) => Promise<void> | void;
25
+ };
26
+ export type PolicyStore = {
27
+ activate: (policyId: string, version: number) => Promise<boolean>;
28
+ get: (policyId: string, version: number) => Promise<PolicyDocument | undefined>;
29
+ getActive: (policyId: string) => Promise<PolicyDocument | undefined>;
30
+ list: (policyId: string) => Promise<PolicyDocument[]>;
31
+ save: (document: PolicyDocument) => Promise<boolean>;
32
+ };
33
+ export declare const createMemoryPolicyStore: () => PolicyStore;
34
+ export declare const createPolicyEngine: <Input>({ adapters, now, store, }: {
35
+ adapters: Record<string, PolicyAdapter<Input>>;
36
+ now?: () => number;
37
+ store: PolicyStore;
38
+ }) => {
39
+ activate: (policyId: string, version: number) => Promise<boolean>;
40
+ draft: ({ createdBy, policyId, provider, source, }: {
41
+ createdBy: string;
42
+ policyId: string;
43
+ provider: string;
44
+ source: unknown;
45
+ }) => Promise<PolicyDocument>;
46
+ evaluate: (policyId: string, input: Input) => Promise<PolicyDecision>;
47
+ simulate: ({ provider, source }: {
48
+ provider: string;
49
+ source: unknown;
50
+ }, input: Input) => Promise<PolicyDecision>;
51
+ };
52
+ export type PolicyFetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
53
+ export declare const createAuthzenAdapter: <Input>({ endpoint, fetch: request, headers, mapRequest, }: {
54
+ endpoint: string;
55
+ fetch?: PolicyFetch;
56
+ headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
57
+ mapRequest: (input: Input, document: PolicyDocument) => unknown;
58
+ }) => PolicyAdapter<Input>;
59
+ export type PolicySqlResult<Row> = {
60
+ rows: Row[];
61
+ rowCount?: number;
62
+ };
63
+ export type PolicySqlClient = {
64
+ query: <Row = Record<string, unknown>>(text: string, values?: readonly unknown[]) => Promise<PolicySqlResult<Row>>;
65
+ };
66
+ export declare const policyPostgresSchemaSql: (namespace?: string) => string;
67
+ export declare const createPostgresPolicyStore: ({ client, namespace, }: {
68
+ client: PolicySqlClient;
69
+ namespace?: string;
70
+ }) => PolicyStore;
package/dist/index.js ADDED
@@ -0,0 +1,179 @@
1
+ // @bun
2
+ // src/index.ts
3
+ var canonical = (value) => {
4
+ if (value === null || typeof value !== "object")
5
+ return JSON.stringify(value);
6
+ if (Array.isArray(value))
7
+ return `[${value.map(canonical).join(",")}]`;
8
+ return `{${Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => `${JSON.stringify(key)}:${canonical(item)}`).join(",")}}`;
9
+ };
10
+ var digest = async (value) => {
11
+ const bytes = new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(canonical(value))));
12
+ return [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
13
+ };
14
+ var createMemoryPolicyStore = () => {
15
+ const rows = new Map;
16
+ const key = (id, version) => `${id}:${version}`;
17
+ return {
18
+ activate: async (id, version) => {
19
+ const target = rows.get(key(id, version));
20
+ if (!target)
21
+ return false;
22
+ for (const [rowKey, row] of rows)
23
+ if (row.policyId === id && row.status === "active")
24
+ rows.set(rowKey, { ...row, status: "retired" });
25
+ rows.set(key(id, version), { ...target, status: "active" });
26
+ return true;
27
+ },
28
+ get: async (id, version) => structuredClone(rows.get(key(id, version))),
29
+ getActive: async (id) => structuredClone([...rows.values()].find((row) => row.policyId === id && row.status === "active")),
30
+ list: async (id) => [...rows.values()].filter((row) => row.policyId === id).sort((a, b) => b.version - a.version).map((row) => structuredClone(row)),
31
+ save: async (document) => {
32
+ const rowKey = key(document.policyId, document.version);
33
+ if (rows.has(rowKey))
34
+ return false;
35
+ rows.set(rowKey, structuredClone(document));
36
+ return true;
37
+ }
38
+ };
39
+ };
40
+ var createPolicyEngine = ({
41
+ adapters,
42
+ now = Date.now,
43
+ store
44
+ }) => {
45
+ const draft = async ({
46
+ createdBy,
47
+ policyId,
48
+ provider,
49
+ source
50
+ }) => {
51
+ const adapter = adapters[provider];
52
+ if (!adapter)
53
+ throw new Error(`Unknown policy provider: ${provider}`);
54
+ await adapter.validate?.(source);
55
+ const versions = await store.list(policyId);
56
+ const document = {
57
+ createdAt: now(),
58
+ createdBy,
59
+ digest: await digest({
60
+ policyId,
61
+ provider,
62
+ source,
63
+ version: (versions[0]?.version ?? 0) + 1
64
+ }),
65
+ policyId,
66
+ provider,
67
+ source: structuredClone(source),
68
+ status: "draft",
69
+ version: (versions[0]?.version ?? 0) + 1
70
+ };
71
+ if (!await store.save(document))
72
+ throw new Error("Policy version already exists");
73
+ return document;
74
+ };
75
+ const evaluateDocument = async (document, input) => {
76
+ const adapter = adapters[document.provider];
77
+ if (!adapter)
78
+ throw new Error(`Unknown policy provider: ${document.provider}`);
79
+ return {
80
+ ...await adapter.evaluate(document, input),
81
+ decisionId: `decision_${crypto.randomUUID()}`,
82
+ evaluatedAt: now(),
83
+ policyDigest: document.digest,
84
+ policyId: document.policyId,
85
+ policyVersion: document.version
86
+ };
87
+ };
88
+ return {
89
+ activate: (policyId, version) => store.activate(policyId, version),
90
+ draft,
91
+ evaluate: async (policyId, input) => {
92
+ const active = await store.getActive(policyId);
93
+ if (!active)
94
+ throw new Error("No active policy");
95
+ return evaluateDocument(active, input);
96
+ },
97
+ simulate: async ({ provider, source }, input) => evaluateDocument({
98
+ createdAt: now(),
99
+ createdBy: "simulation",
100
+ digest: await digest({ provider, source }),
101
+ policyId: "simulation",
102
+ provider,
103
+ source,
104
+ status: "draft",
105
+ version: 0
106
+ }, input)
107
+ };
108
+ };
109
+ var createAuthzenAdapter = ({
110
+ endpoint,
111
+ fetch: request = fetch,
112
+ headers,
113
+ mapRequest
114
+ }) => ({
115
+ evaluate: async (document, input) => {
116
+ const supplied = typeof headers === "function" ? await headers() : headers;
117
+ const response = await request(endpoint, {
118
+ body: JSON.stringify(mapRequest(input, document)),
119
+ headers: {
120
+ "content-type": "application/json",
121
+ ...Object.fromEntries(new Headers(supplied))
122
+ },
123
+ method: "POST"
124
+ });
125
+ if (!response.ok)
126
+ throw new Error(`AuthZEN PDP failed: ${response.status}`);
127
+ const body = await response.json();
128
+ if (typeof body.decision !== "boolean")
129
+ throw new Error("Invalid AuthZEN decision response");
130
+ return {
131
+ allowed: body.decision,
132
+ metadata: body.context,
133
+ reason: body.decision ? undefined : "authzen_denied"
134
+ };
135
+ }
136
+ });
137
+ var nsOf = (value) => {
138
+ if (!/^[a-z_][a-z0-9_]*$/.test(value))
139
+ throw new Error("Policy namespace must be a simple identifier");
140
+ return value;
141
+ };
142
+ var policyPostgresSchemaSql = (namespace = "policy") => {
143
+ const ns = nsOf(namespace);
144
+ return `CREATE SCHEMA IF NOT EXISTS ${ns}; CREATE TABLE IF NOT EXISTS ${ns}.versions (policy_id text NOT NULL, version integer NOT NULL, status text NOT NULL, digest text NOT NULL, data jsonb NOT NULL, created_at bigint NOT NULL, PRIMARY KEY (policy_id, version)); CREATE UNIQUE INDEX IF NOT EXISTS policy_one_active_idx ON ${ns}.versions (policy_id) WHERE status = 'active';`;
145
+ };
146
+ var createPostgresPolicyStore = ({
147
+ client,
148
+ namespace = "policy"
149
+ }) => {
150
+ const ns = nsOf(namespace);
151
+ const one = async (sql, values) => {
152
+ const row = (await client.query(sql, values)).rows[0];
153
+ return row ? typeof row.data === "string" ? JSON.parse(row.data) : row.data : undefined;
154
+ };
155
+ return {
156
+ activate: async (id, version) => (await client.query(`WITH target AS (SELECT version FROM ${ns}.versions WHERE policy_id = $1 AND version = $2), retired AS (UPDATE ${ns}.versions SET status = 'retired', data = jsonb_set(data,'{status}','"retired"') WHERE policy_id = $1 AND status = 'active' AND EXISTS (SELECT 1 FROM target)), activated AS (UPDATE ${ns}.versions SET status = 'active', data = jsonb_set(data,'{status}','"active"') WHERE policy_id = $1 AND version = $2 RETURNING version) SELECT version FROM activated`, [id, version])).rows.length === 1,
157
+ get: (id, version) => one(`SELECT data FROM ${ns}.versions WHERE policy_id=$1 AND version=$2`, [
158
+ id,
159
+ version
160
+ ]),
161
+ getActive: (id) => one(`SELECT data FROM ${ns}.versions WHERE policy_id=$1 AND status='active'`, [id]),
162
+ list: async (id) => (await client.query(`SELECT data FROM ${ns}.versions WHERE policy_id=$1 ORDER BY version DESC`, [id])).rows.map((row) => typeof row.data === "string" ? JSON.parse(row.data) : row.data),
163
+ save: async (document) => (await client.query(`INSERT INTO ${ns}.versions (policy_id,version,status,digest,data,created_at) VALUES ($1,$2,$3,$4,$5::jsonb,$6) ON CONFLICT DO NOTHING RETURNING version`, [
164
+ document.policyId,
165
+ document.version,
166
+ document.status,
167
+ document.digest,
168
+ JSON.stringify(document),
169
+ document.createdAt
170
+ ])).rows.length === 1
171
+ };
172
+ };
173
+ export {
174
+ policyPostgresSchemaSql,
175
+ createPostgresPolicyStore,
176
+ createPolicyEngine,
177
+ createMemoryPolicyStore,
178
+ createAuthzenAdapter
179
+ };
@@ -0,0 +1 @@
1
+ export declare const manifest: import("@absolutejs/manifest").PackageManifest<Record<string, never>, never>;