@absolutejs/secrets 0.6.1 → 0.7.1

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/README.md CHANGED
@@ -28,35 +28,42 @@ operation. Results and audit events contain digests and identifiers, never the
28
28
  credential.
29
29
 
30
30
  ```ts
31
+ import {
32
+ createCredentialOperationBroker,
33
+ createMemoryCredentialGrantStore,
34
+ } from "@absolutejs/secrets/agent";
35
+
31
36
  const grants = createMemoryCredentialGrantStore();
32
37
  await grants.put({
33
- agentId: 'research-agent',
34
- allowedOrigins: ['https://api.example.com'],
38
+ agentId: "research-agent",
39
+ allowedOrigins: ["https://api.example.com"],
35
40
  createdAt: Date.now(),
36
41
  expiresAt: Date.now() + 60_000,
37
- grantId: 'grant_123',
42
+ grantId: "grant_123",
38
43
  maximumUses: 1,
39
- provider: 'example',
40
- scopes: ['create-report'],
41
- secretName: 'EXAMPLE_API_KEY',
44
+ provider: "example",
45
+ scopes: ["create-report"],
46
+ secretName: "EXAMPLE_API_KEY",
42
47
  used: 0,
43
- userId: 'user_123',
48
+ userId: "user_123",
44
49
  });
45
50
 
46
51
  const operations = createCredentialOperationBroker({
47
52
  agency,
48
- providers: [{
49
- provider: 'example',
50
- operations: {
51
- 'create-report': ({ credential, destination, input, signal }) =>
52
- fetch(new URL('/reports', destination), {
53
- body: JSON.stringify(input),
54
- headers: { authorization: `Bearer ${credential}` },
55
- method: 'POST',
56
- signal,
57
- }).then((response) => response.json()),
53
+ providers: [
54
+ {
55
+ provider: "example",
56
+ operations: {
57
+ "create-report": ({ credential, destination, input, signal }) =>
58
+ fetch(new URL("/reports", destination), {
59
+ body: JSON.stringify(input),
60
+ headers: { authorization: `Bearer ${credential}` },
61
+ method: "POST",
62
+ signal,
63
+ }).then((response) => response.json()),
64
+ },
58
65
  },
59
- }],
66
+ ],
60
67
  secrets: broker,
61
68
  store: grants,
62
69
  });
@@ -68,50 +75,55 @@ Requestable denials can be resumed after approval with
68
75
  `operations.resume(actionId)`.
69
76
 
70
77
  ```ts
71
- import { createSecretBroker, envAdapter, inMemoryAdapter, compositeAdapter } from '@absolutejs/secrets';
78
+ import {
79
+ createSecretBroker,
80
+ envAdapter,
81
+ inMemoryAdapter,
82
+ compositeAdapter,
83
+ } from "@absolutejs/secrets/broker";
72
84
 
73
85
  const broker = createSecretBroker({
74
86
  adapter: compositeAdapter([
75
- inMemoryAdapter({ initial: { TEST_KEY: 'sk_test_local_value' } }),
76
- envAdapter({ prefix: 'ABS_SECRET_' }),
87
+ inMemoryAdapter({ initial: { TEST_KEY: "sk_test_local_value" } }),
88
+ envAdapter({ prefix: "ABS_SECRET_" }),
77
89
  ]),
78
90
  audit: (event) => observabilitySink.write(event),
79
91
  cacheTtlMs: 60_000,
80
92
  });
81
93
 
82
94
  // In bridgeFetch.authorization():
83
- const { value, fingerprint } = (await broker.resolve('STRIPE_KEY'))!;
84
- logger.info('charging', { tenant, fingerprint }); // safe — no plaintext
85
- return { 'Authorization': `Bearer ${value}` };
95
+ const { value, fingerprint } = (await broker.resolve("STRIPE_KEY"))!;
96
+ logger.info("charging", { tenant, fingerprint }); // safe — no plaintext
97
+ return { Authorization: `Bearer ${value}` };
86
98
 
87
99
  // In a log sink, before text leaves the host:
88
- const sanitized = broker.redact(line); // [REDACTED:STRIPE_KEY] replaces plaintext
100
+ const sanitized = broker.redact(line); // [REDACTED:STRIPE_KEY] replaces plaintext
89
101
  sinkToCustomerVisibleLog(sanitized);
90
102
 
91
103
  // Rotate:
92
- const next = await broker.rotate('STRIPE_KEY');
93
- notifyDependents(next.fingerprint); // tell consumers a new key is in cache
104
+ const next = await broker.rotate("STRIPE_KEY");
105
+ notifyDependents(next.fingerprint); // tell consumers a new key is in cache
94
106
  ```
95
107
 
96
108
  ## v0.0.1 surface
97
109
 
98
- | API | Purpose |
99
- |---|---|
100
- | `createSecretBroker(options)` | Factory. Returns a `SecretBroker`. |
101
- | `broker.resolve(name)` | Returns `{ value, fingerprint } | null`. Uses cache within `cacheTtlMs`. |
102
- | `broker.fingerprint(value)` | Pure helper — sha256 prefix of any string. No adapter call. |
103
- | `broker.redact(text)` | Rewrite arbitrary text, replacing every cached value (longer-first) with `[REDACTED:NAME]`. Skips values shorter than `redactionMinLength`. |
104
- | `broker.rotate(name)` | Calls `adapter.rotate?`, caches the result, returns it. Throws if the adapter doesn't support rotation. |
105
- | `broker.invalidate(name?)` | Clear one entry or the whole cache. |
106
- | `broker.dispose()` | Tear down — clears cache; subsequent resolves return `null`. |
110
+ | API | Purpose |
111
+ | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- |
112
+ | `createSecretBroker(options)` | Factory. Returns a `SecretBroker`. |
113
+ | `broker.resolve(name)` | Returns `{ value, fingerprint } | null`. Uses cache within `cacheTtlMs`. |
114
+ | `broker.fingerprint(value)` | Pure helper — sha256 prefix of any string. No adapter call. |
115
+ | `broker.redact(text)` | Rewrite arbitrary text, replacing every cached value (longer-first) with `[REDACTED:NAME]`. Skips values shorter than `redactionMinLength`. |
116
+ | `broker.rotate(name)` | Calls `adapter.rotate?`, caches the result, returns it. Throws if the adapter doesn't support rotation. |
117
+ | `broker.invalidate(name?)` | Clear one entry or the whole cache. |
118
+ | `broker.dispose()` | Tear down — clears cache; subsequent resolves return `null`. |
107
119
 
108
120
  ### Bundled adapters
109
121
 
110
- | Adapter | Use |
111
- |---|---|
112
- | `inMemoryAdapter({ initial?, rotate? })` | Tests, dev, and starter templates. Supports every operation. Default rotate = random base36. |
113
- | `envAdapter({ prefix?, env? })` | Reads `process.env` (or any injected `env` map). Prefix-scoped to avoid leaking unrelated env vars via `list`. Read-only. |
114
- | `compositeAdapter([...])` | Fan-out / fallback. `fetch` falls through; writes go to the first writeable adapter. |
122
+ | Adapter | Use |
123
+ | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
124
+ | `inMemoryAdapter({ initial?, rotate? })` | Tests, dev, and starter templates. Supports every operation. Default rotate = random base36. |
125
+ | `envAdapter({ prefix?, env? })` | Reads `process.env` (or any injected `env` map). Prefix-scoped to avoid leaking unrelated env vars via `list`. Read-only. |
126
+ | `compositeAdapter([...])` | Fan-out / fallback. `fetch` falls through; writes go to the first writeable adapter. |
115
127
 
116
128
  AWS Secrets Manager / HashiCorp Vault / Doppler / Infisical / GCP Secret
117
129
  Manager / Azure Key Vault adapters ship later as siblings — they're the
@@ -0,0 +1,2 @@
1
+ export { createCredentialOperationBroker, createMemoryCredentialGrantStore, type CredentialGrant, type CredentialGrantStore, type CredentialOperationBroker, type CredentialOperationBrokerOptions, type CredentialOperationContext, type CredentialOperationEvent, type CredentialOperationInput, type CredentialOperationResult, type CredentialProvider, type PendingCredentialOperation, } from "./operations";
2
+ export { createPostgresCredentialGrantStore, credentialGrantsPostgresSchemaSql, type SecretsSqlClient, type SecretsSqlResult, } from "./postgres";
package/dist/agent.js ADDED
@@ -0,0 +1,264 @@
1
+ // @bun
2
+ var __defProp = Object.defineProperty;
3
+ var __returnValue = (v) => v;
4
+ function __exportSetter(name, newValue) {
5
+ this[name] = __returnValue.bind(null, newValue);
6
+ }
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, {
10
+ get: all[name],
11
+ enumerable: true,
12
+ configurable: true,
13
+ set: __exportSetter.bind(all, name)
14
+ });
15
+ };
16
+ var __require = import.meta.require;
17
+
18
+ // node_modules/@absolutejs/agency/dist/index.js
19
+ var normalize = (value) => {
20
+ if (Array.isArray(value))
21
+ return value.map(normalize);
22
+ if (value !== null && typeof value === "object") {
23
+ return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, normalize(entry)]));
24
+ }
25
+ return value;
26
+ };
27
+ var canonicalJson = (value) => JSON.stringify(normalize(value));
28
+ var digest = async (value) => {
29
+ const encoded = new TextEncoder().encode(canonicalJson(value));
30
+ const result = await crypto.subtle.digest("SHA-256", encoded);
31
+ return [...new Uint8Array(result)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
32
+ };
33
+
34
+ // src/operations.ts
35
+ var normalizeOrigin = (destination) => new URL(destination).origin;
36
+ var validateGrant = (grant, input, now) => {
37
+ if (grant.agentId !== input.actor.agentId || grant.userId !== input.actor.userId)
38
+ throw new Error("Credential grant actor mismatch");
39
+ if (grant.expiresAt <= now)
40
+ throw new Error("Credential grant has expired");
41
+ if (!grant.scopes.includes(input.operation))
42
+ throw new Error("Credential operation is outside grant scope");
43
+ const origin = normalizeOrigin(input.destination);
44
+ if (!grant.allowedOrigins.map(normalizeOrigin).includes(origin))
45
+ throw new Error("Credential destination is outside grant scope");
46
+ if (grant.used >= grant.maximumUses)
47
+ throw new Error("Credential grant has been exhausted");
48
+ return origin;
49
+ };
50
+ var createMemoryCredentialGrantStore = () => {
51
+ const grants = new Map;
52
+ const pending = new Map;
53
+ let lock = Promise.resolve();
54
+ const exclusive = async (run) => {
55
+ const previous = lock;
56
+ let release = () => {};
57
+ lock = new Promise((resolve) => {
58
+ release = resolve;
59
+ });
60
+ await previous;
61
+ try {
62
+ return await run();
63
+ } finally {
64
+ release();
65
+ }
66
+ };
67
+ return {
68
+ consume: (grantId, now) => exclusive(() => {
69
+ const grant = grants.get(grantId);
70
+ if (grant === undefined || grant.expiresAt <= now || grant.used >= grant.maximumUses)
71
+ return;
72
+ const consumed = { ...grant, used: grant.used + 1 };
73
+ grants.set(grantId, consumed);
74
+ return { ...consumed };
75
+ }),
76
+ get: async (grantId) => {
77
+ const grant = grants.get(grantId);
78
+ return grant === undefined ? undefined : { ...grant };
79
+ },
80
+ getPending: async (actionId) => pending.get(actionId),
81
+ put: async (grant) => {
82
+ if (grant.maximumUses < 1)
83
+ throw new Error("maximumUses must be positive");
84
+ if (grant.used < 0 || grant.used > grant.maximumUses)
85
+ throw new Error("Invalid credential grant usage");
86
+ grants.set(grant.grantId, { ...grant });
87
+ },
88
+ putPending: async (operation) => {
89
+ pending.set(operation.actionId, operation);
90
+ },
91
+ revoke: async (grantId) => grants.delete(grantId)
92
+ };
93
+ };
94
+ var createCredentialOperationBroker = ({
95
+ agency,
96
+ emit,
97
+ now = Date.now,
98
+ providers,
99
+ secrets,
100
+ store
101
+ }) => {
102
+ const providerMap = new Map(providers.map((provider) => [provider.provider, provider]));
103
+ const perform = async (input, actionId, signal) => {
104
+ const preview = await store.get(input.grantId);
105
+ if (preview === undefined)
106
+ throw new Error("Unknown credential grant");
107
+ const origin = validateGrant(preview, input, now());
108
+ const provider = providerMap.get(preview.provider);
109
+ const operation = provider?.operations[input.operation];
110
+ if (provider === undefined || operation === undefined)
111
+ throw new Error("Credential provider operation is not registered");
112
+ await emit?.({
113
+ actionId,
114
+ destinationOrigin: origin,
115
+ grantId: preview.grantId,
116
+ operation: input.operation,
117
+ provider: preview.provider,
118
+ type: "credential.operation.requested"
119
+ });
120
+ const consumed = await store.consume(input.grantId, now());
121
+ if (consumed === undefined)
122
+ throw new Error("Credential grant has been exhausted");
123
+ validateGrant({ ...consumed, used: consumed.used - 1 }, input, now());
124
+ const secret = await secrets.resolve(consumed.secretName);
125
+ if (secret === null)
126
+ throw new Error("Credential is not configured");
127
+ try {
128
+ const result = await operation({
129
+ credential: secret.value,
130
+ destination: new URL(input.destination),
131
+ input: input.input,
132
+ signal
133
+ });
134
+ const resultDigest = await digest(result);
135
+ await emit?.({
136
+ actionId,
137
+ destinationOrigin: origin,
138
+ grantId: consumed.grantId,
139
+ operation: input.operation,
140
+ provider: consumed.provider,
141
+ resultDigest,
142
+ status: "succeeded",
143
+ type: "credential.operation.completed"
144
+ });
145
+ return { actionId, kind: "completed", result, resultDigest };
146
+ } catch (error) {
147
+ await emit?.({
148
+ actionId,
149
+ destinationOrigin: origin,
150
+ grantId: consumed.grantId,
151
+ operation: input.operation,
152
+ provider: consumed.provider,
153
+ status: "failed",
154
+ type: "credential.operation.completed"
155
+ });
156
+ throw error;
157
+ }
158
+ };
159
+ const request = async (input, signal) => {
160
+ const grant = await store.get(input.grantId);
161
+ if (grant === undefined)
162
+ throw new Error("Unknown credential grant");
163
+ const origin = validateGrant(grant, input, now());
164
+ if (agency === undefined)
165
+ return perform(input, undefined, signal);
166
+ const { action, decision } = await agency.request({
167
+ action: `credential.${grant.provider}.${input.operation}`,
168
+ actor: input.actor,
169
+ context: { destinationOrigin: origin, grantId: grant.grantId },
170
+ effects: ["external-network"],
171
+ expiresAt: grant.expiresAt,
172
+ idempotencyKey: input.idempotencyKey,
173
+ input: { destination: input.destination, input: input.input },
174
+ resource: { id: grant.grantId, type: "credential-grant" }
175
+ });
176
+ if (decision.kind !== "allow") {
177
+ await store.putPending({ actionId: action.actionId, input });
178
+ return { actionId: action.actionId, decision, kind: "pending" };
179
+ }
180
+ const lease = await agency.issueLease(action.actionId);
181
+ const execution = await agency.execute({
182
+ executor: `credential-provider:${grant.provider}`,
183
+ leaseId: lease.leaseId,
184
+ run: () => perform(input, action.actionId, signal)
185
+ });
186
+ return execution.result;
187
+ };
188
+ const resume = async (actionId, signal) => {
189
+ if (agency === undefined)
190
+ throw new Error("Agency is not configured");
191
+ const pending = await store.getPending(actionId);
192
+ if (pending === undefined)
193
+ throw new Error("Unknown pending credential operation");
194
+ const lease = await agency.issueLease(actionId);
195
+ const execution = await agency.execute({
196
+ executor: "credential-provider",
197
+ leaseId: lease.leaseId,
198
+ run: () => perform(pending.input, actionId, signal)
199
+ });
200
+ return execution.result;
201
+ };
202
+ return { request, resume, revoke: store.revoke };
203
+ };
204
+ // src/postgres.ts
205
+ var namespaceOf = (namespace) => {
206
+ if (!/^[a-z_][a-z0-9_]*$/.test(namespace))
207
+ throw new Error("Secrets PostgreSQL namespace must be a simple identifier");
208
+ return namespace;
209
+ };
210
+ var credentialGrantsPostgresSchemaSql = (namespace = "secrets") => {
211
+ const ns = namespaceOf(namespace);
212
+ return `CREATE SCHEMA IF NOT EXISTS ${ns};
213
+ CREATE TABLE IF NOT EXISTS ${ns}.credential_grants (
214
+ grant_id text PRIMARY KEY, agent_id text NOT NULL, user_id text NOT NULL,
215
+ provider text NOT NULL, expires_at bigint NOT NULL, maximum_uses integer NOT NULL CHECK (maximum_uses > 0),
216
+ used integer NOT NULL DEFAULT 0 CHECK (used >= 0 AND used <= maximum_uses), data jsonb NOT NULL
217
+ );
218
+ CREATE INDEX IF NOT EXISTS credential_grants_agent_idx ON ${ns}.credential_grants (agent_id, expires_at);
219
+ CREATE TABLE IF NOT EXISTS ${ns}.pending_operations (
220
+ action_id text PRIMARY KEY, grant_id text NOT NULL REFERENCES ${ns}.credential_grants(grant_id) ON DELETE CASCADE,
221
+ created_at timestamptz NOT NULL DEFAULT now(), data jsonb NOT NULL
222
+ );`;
223
+ };
224
+ var createPostgresCredentialGrantStore = ({
225
+ client,
226
+ namespace = "secrets"
227
+ }) => {
228
+ const ns = namespaceOf(namespace);
229
+ return {
230
+ consume: async (grantId, now) => {
231
+ const result = await client.query(`UPDATE ${ns}.credential_grants SET used = used + 1, data = jsonb_set(data, '{used}', to_jsonb(used + 1), true) WHERE grant_id = $1 AND expires_at > $2 AND used < maximum_uses RETURNING data`, [grantId, now]);
232
+ return result.rows[0]?.data;
233
+ },
234
+ get: async (grantId) => (await client.query(`SELECT data FROM ${ns}.credential_grants WHERE grant_id = $1`, [grantId])).rows[0]?.data,
235
+ getPending: async (actionId) => (await client.query(`SELECT data FROM ${ns}.pending_operations WHERE action_id = $1`, [actionId])).rows[0]?.data,
236
+ put: async (grant) => {
237
+ if (grant.maximumUses < 1 || grant.used < 0 || grant.used > grant.maximumUses)
238
+ throw new Error("Invalid credential grant usage");
239
+ await client.query(`INSERT INTO ${ns}.credential_grants (grant_id, agent_id, user_id, provider, expires_at, maximum_uses, used, data) VALUES ($1,$2,$3,$4,$5,$6,$7,$8::jsonb) ON CONFLICT (grant_id) DO UPDATE SET expires_at=EXCLUDED.expires_at, maximum_uses=EXCLUDED.maximum_uses, used=EXCLUDED.used, data=EXCLUDED.data`, [
240
+ grant.grantId,
241
+ grant.agentId,
242
+ grant.userId,
243
+ grant.provider,
244
+ grant.expiresAt,
245
+ grant.maximumUses,
246
+ grant.used,
247
+ JSON.stringify(grant)
248
+ ]);
249
+ },
250
+ putPending: async (pending) => {
251
+ await client.query(`INSERT INTO ${ns}.pending_operations (action_id, grant_id, data) VALUES ($1,$2,$3::jsonb) ON CONFLICT (action_id) DO UPDATE SET data=EXCLUDED.data`, [pending.actionId, pending.input.grantId, JSON.stringify(pending)]);
252
+ },
253
+ revoke: async (grantId) => (await client.query(`DELETE FROM ${ns}.credential_grants WHERE grant_id = $1`, [grantId])).rowCount === 1
254
+ };
255
+ };
256
+ export {
257
+ credentialGrantsPostgresSchemaSql,
258
+ createPostgresCredentialGrantStore,
259
+ createMemoryCredentialGrantStore,
260
+ createCredentialOperationBroker
261
+ };
262
+
263
+ //# debugId=2D416FD568D31C1164756E2164756E21
264
+ //# sourceMappingURL=agent.js.map
@@ -0,0 +1,12 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../node_modules/@absolutejs/agency/dist/index.js", "../src/operations.ts", "../src/postgres.ts"],
4
+ "sourcesContent": [
5
+ "// @bun\nvar __defProp = Object.defineProperty;\nvar __returnValue = (v) => v;\nfunction __exportSetter(name, newValue) {\n this[name] = __returnValue.bind(null, newValue);\n}\nvar __export = (target, all) => {\n for (var name in all)\n __defProp(target, name, {\n get: all[name],\n enumerable: true,\n configurable: true,\n set: __exportSetter.bind(all, name)\n });\n};\n\n// src/canonical.ts\nvar normalize = (value) => {\n if (Array.isArray(value))\n return value.map(normalize);\n if (value !== null && typeof value === \"object\") {\n return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, normalize(entry)]));\n }\n return value;\n};\nvar canonicalJson = (value) => JSON.stringify(normalize(value));\nvar digest = async (value) => {\n const encoded = new TextEncoder().encode(canonicalJson(value));\n const result = await crypto.subtle.digest(\"SHA-256\", encoded);\n return [...new Uint8Array(result)].map((byte) => byte.toString(16).padStart(2, \"0\")).join(\"\");\n};\nvar actionBinding = (action) => digest({\n action: action.action,\n actionId: action.actionId,\n actor: action.actor,\n effects: action.effects,\n expiresAt: action.expiresAt,\n inputDigest: action.inputDigest,\n resource: action.resource,\n spend: action.spend\n});\n// src/engine.ts\nvar DEFAULT_LEASE_TTL_MS = 60000;\nvar required = (value, message) => {\n if (value === undefined)\n throw new Error(message);\n return value;\n};\nvar createAgency = ({\n control,\n defaultLeaseTtlMs = DEFAULT_LEASE_TTL_MS,\n emit,\n now = Date.now,\n policy,\n store\n}) => {\n const decide = async (action, approval) => {\n const decision = await policy.evaluate({ action, approval, now: now() });\n await emit?.({\n actionId: action.actionId,\n decision,\n type: \"action.decided\"\n });\n return decision;\n };\n const request = async (input) => {\n await control?.assertActive(input.actor.agentId);\n const createdAt = now();\n const action = {\n ...input,\n actionId: `act_${crypto.randomUUID()}`,\n createdAt,\n inputDigest: await digest(input.input ?? null)\n };\n await store.saveAction(action);\n await emit?.({ action, type: \"action.requested\" });\n return { action, decision: await decide(action) };\n };\n const approve = async ({\n actionId,\n approvedBy,\n approvedUntil,\n conditions,\n state\n }) => {\n const action = required(await store.getAction(actionId), \"Unknown action\");\n const approvedAt = now();\n if (approvedUntil <= approvedAt)\n throw new Error(\"Approval must expire in the future\");\n const approval = {\n actionId,\n approvalId: `apr_${crypto.randomUUID()}`,\n approvedAt,\n approvedBy,\n approvedUntil,\n bindingDigest: await actionBinding(action),\n conditions,\n state\n };\n await store.saveApproval(approval);\n await emit?.({ actionId, approval, type: \"action.approved\" });\n return approval;\n };\n const issueLease = async (actionId) => {\n const action = required(await store.getAction(actionId), \"Unknown action\");\n await control?.assertActive(action.actor.agentId);\n const approval = await store.getApproval(actionId);\n const currentTime = now();\n if (action.expiresAt !== undefined && action.expiresAt <= currentTime) {\n throw new Error(\"Action request has expired\");\n }\n if (approval !== undefined) {\n if (approval.approvedUntil <= currentTime)\n throw new Error(\"Approval has expired\");\n if (approval.bindingDigest !== await actionBinding(action)) {\n throw new Error(\"Approval is not bound to the current action\");\n }\n }\n const decision = await decide(action, approval);\n if (decision.kind !== \"allow\")\n throw new Error(`Action denied: ${decision.reason}`);\n const issuedAt = now();\n const lease = {\n actionId,\n bindingDigest: await actionBinding(action),\n expiresAt: Math.min(issuedAt + defaultLeaseTtlMs, action.expiresAt ?? Number.POSITIVE_INFINITY, approval?.approvedUntil ?? Number.POSITIVE_INFINITY, decision.expiresAt ?? Number.POSITIVE_INFINITY),\n issuedAt,\n leaseId: `lease_${crypto.randomUUID()}`,\n maximumUses: 1\n };\n await store.saveLease(lease);\n await emit?.({ actionId, lease, type: \"action.lease-issued\" });\n return lease;\n };\n const execute = async ({\n costs,\n executor,\n leaseId,\n run\n }) => {\n const lease = required(await store.getLease(leaseId), \"Unknown execution lease\");\n const action = required(await store.getAction(lease.actionId), \"Unknown action\");\n await control?.assertActive(action.actor.agentId);\n const startedAt = now();\n if (lease.expiresAt <= startedAt)\n throw new Error(\"Execution lease has expired\");\n if (lease.bindingDigest !== await actionBinding(action)) {\n throw new Error(\"Execution lease is not bound to the current action\");\n }\n if (!await store.consumeLease(leaseId, startedAt)) {\n throw new Error(\"Execution lease has already been consumed\");\n }\n let result;\n let receipt;\n try {\n result = await run();\n receipt = {\n actionId: action.actionId,\n completedAt: now(),\n costs,\n executor,\n leaseId,\n receiptId: `rcpt_${crypto.randomUUID()}`,\n resultDigest: await digest(result),\n startedAt,\n status: \"succeeded\"\n };\n } catch (error) {\n receipt = {\n actionId: action.actionId,\n completedAt: now(),\n costs,\n error: error instanceof Error ? error.message : \"Action failed\",\n executor,\n leaseId,\n receiptId: `rcpt_${crypto.randomUUID()}`,\n startedAt,\n status: \"failed\"\n };\n await store.saveReceipt(receipt);\n await emit?.({\n actionId: action.actionId,\n receipt,\n type: \"action.completed\"\n });\n throw error;\n }\n await store.saveReceipt(receipt);\n await emit?.({\n actionId: action.actionId,\n receipt,\n type: \"action.completed\"\n });\n return { receipt, result };\n };\n const inspect = async (actorId) => ({\n actions: await store.listActions(actorId),\n approvals: await store.listApprovals(actorId),\n leases: await store.listLeases(actorId),\n receipts: await store.listReceipts(actorId)\n });\n return { approve, execute, inspect, issueLease, request };\n};\n// src/memory.ts\nvar clone = (value) => structuredClone(value);\nvar createMemoryAgencyStore = () => {\n const actions = new Map;\n const approvals = new Map;\n const leases = new Map;\n const receipts = new Map;\n const actorForAction = (actionId) => actions.get(actionId)?.actor.agentId;\n return {\n consumeLease: async (leaseId, consumedAt) => {\n const lease = leases.get(leaseId);\n if (lease === undefined || lease.consumedAt !== undefined)\n return false;\n leases.set(leaseId, { ...lease, consumedAt });\n return true;\n },\n getAction: async (actionId) => {\n const action = actions.get(actionId);\n return action === undefined ? undefined : clone(action);\n },\n getApproval: async (actionId) => {\n const approval = approvals.get(actionId);\n return approval === undefined ? undefined : clone(approval);\n },\n getLease: async (leaseId) => {\n const lease = leases.get(leaseId);\n return lease === undefined ? undefined : clone(lease);\n },\n listActions: async (actorId) => [...actions.values()].filter((action) => actorId === undefined || action.actor.agentId === actorId).map(clone),\n listApprovals: async (actorId) => [...approvals.values()].filter((approval) => actorId === undefined || actorForAction(approval.actionId) === actorId).map(clone),\n listLeases: async (actorId) => [...leases.values()].filter((lease) => actorId === undefined || actorForAction(lease.actionId) === actorId).map(clone),\n listReceipts: async (actorId) => [...receipts.values()].filter((receipt) => actorId === undefined || actorForAction(receipt.actionId) === actorId).map(clone),\n saveAction: async (action) => {\n actions.set(action.actionId, clone(action));\n },\n saveApproval: async (approval) => {\n approvals.set(approval.actionId, clone(approval));\n },\n saveLease: async (lease) => {\n leases.set(lease.leaseId, clone(lease));\n },\n saveReceipt: async (receipt) => {\n receipts.set(receipt.receiptId, clone(receipt));\n }\n };\n};\n// src/policies.ts\nvar allowAllPolicy = () => ({\n evaluate: ({ now }) => ({\n decisionId: `decision_${crypto.randomUUID()}`,\n evaluatedAt: now,\n kind: \"allow\"\n })\n});\nvar denyAllPolicy = (reason = \"default_deny\") => ({\n evaluate: ({ now }) => ({\n decisionId: `decision_${crypto.randomUUID()}`,\n evaluatedAt: now,\n kind: \"deny\",\n reason,\n requestable: false\n })\n});\n// src/control.ts\nvar createMemoryAgentControlStore = () => {\n const switches = new Map;\n return {\n clearKillSwitch: async (agentId) => {\n switches.delete(agentId);\n },\n getKillSwitch: async (agentId) => switches.get(agentId),\n setKillSwitch: async (killSwitch) => {\n switches.set(killSwitch.agentId, structuredClone(killSwitch));\n }\n };\n};\nvar createAgentControlPlane = ({\n now = Date.now,\n sources,\n store\n}) => {\n const status = async (agentId) => store.getKillSwitch(agentId);\n const assertActive = async (agentId) => {\n const killSwitch = await status(agentId);\n if (killSwitch !== undefined)\n throw new Error(`Agent is disabled: ${killSwitch.reason}`);\n };\n const inventory = async (agentId) => {\n const settled = await Promise.allSettled(sources.map(async (source) => ({\n items: await source.inventory(agentId),\n source: source.name\n })));\n return {\n agentId,\n items: settled.flatMap((result) => result.status === \"fulfilled\" ? result.value.items : []),\n killSwitch: await status(agentId),\n sourceErrors: settled.flatMap((result, index) => result.status === \"rejected\" ? [\n {\n error: result.reason instanceof Error ? result.reason.message : \"Control source failed\",\n source: sources[index]?.name ?? \"unknown\"\n }\n ] : [])\n };\n };\n const revoke = async ({\n activatedBy,\n agentId,\n reason\n }) => {\n const killSwitch = {\n activatedAt: now(),\n activatedBy,\n agentId,\n reason\n };\n await store.setKillSwitch(killSwitch);\n const settled = await Promise.allSettled(sources.map(async (source) => ({\n revoked: await source.revoke(agentId, reason),\n source: source.name\n })));\n return {\n killSwitch,\n results: settled.map((result, index) => result.status === \"fulfilled\" ? { ...result.value, status: \"fulfilled\" } : {\n error: result.reason instanceof Error ? result.reason.message : \"Control source failed\",\n source: sources[index]?.name ?? \"unknown\",\n status: \"rejected\"\n })\n };\n };\n return {\n assertActive,\n inventory,\n restore: store.clearKillSwitch,\n revoke,\n status\n };\n};\n// src/handoff.ts\nvar encode = (value) => btoa(String.fromCharCode(...new Uint8Array(value))).replaceAll(\"+\", \"-\").replaceAll(\"/\", \"_\").replace(/=+$/, \"\");\nvar keyBytes = (key) => typeof key === \"string\" ? new TextEncoder().encode(key) : key;\nvar constantTimeEqual = (left, right) => {\n if (left.length !== right.length)\n return false;\n let difference = 0;\n for (let index = 0;index < left.length; index += 1)\n difference |= (left[index] ?? 0) ^ (right[index] ?? 0);\n return difference === 0;\n};\nvar sign = async (claims, keyId, key) => {\n const rawKey = Uint8Array.from(keyBytes(key));\n const cryptoKey = await crypto.subtle.importKey(\"raw\", rawKey.buffer, { hash: \"SHA-256\", name: \"HMAC\" }, false, [\"sign\"]);\n return encode(await crypto.subtle.sign(\"HMAC\", cryptoKey, new TextEncoder().encode(canonicalJson({ claims, keyId }))));\n};\nvar signAgentHandoff = async ({\n claims,\n key,\n keyId\n}) => {\n const versioned = {\n ...claims,\n version: \"absolute.agent-handoff/1\"\n };\n return {\n algorithm: \"HS256\",\n claims: versioned,\n keyId,\n signature: await sign(versioned, keyId, key)\n };\n};\nvar verifyAgentHandoff = async ({\n expectedAudience,\n handoff,\n key,\n now = Date.now,\n replayStore\n}) => {\n if (handoff.algorithm !== \"HS256\")\n throw new Error(\"Unsupported handoff algorithm\");\n if (handoff.claims.version !== \"absolute.agent-handoff/1\")\n throw new Error(\"Unsupported handoff version\");\n if (handoff.claims.audience !== expectedAudience)\n throw new Error(\"Agent handoff audience mismatch\");\n if (handoff.claims.expiresAt <= now())\n throw new Error(\"Agent handoff has expired\");\n const expected = await sign(handoff.claims, handoff.keyId, key);\n const actualBytes = new TextEncoder().encode(handoff.signature);\n const expectedBytes = new TextEncoder().encode(expected);\n if (!constantTimeEqual(actualBytes, expectedBytes))\n throw new Error(\"Invalid agent handoff signature\");\n if (!await replayStore.consume(handoff.claims.nonce, handoff.claims.expiresAt))\n throw new Error(\"Agent handoff has already been consumed\");\n return structuredClone(handoff.claims);\n};\nvar createMemoryHandoffReplayStore = (now = Date.now) => {\n const nonces = new Map;\n return {\n consume: async (nonce, expiresAt) => {\n for (const [storedNonce, expiry] of nonces)\n if (expiry <= now())\n nonces.delete(storedNonce);\n if (nonces.has(nonce))\n return false;\n nonces.set(nonce, expiresAt);\n return true;\n }\n };\n};\nvar attenuateAgentHandoff = (parent, child) => {\n if (child.userId !== parent.userId)\n throw new Error(\"Agent handoff cannot change users\");\n if (child.expiresAt > parent.expiresAt)\n throw new Error(\"Agent handoff cannot extend expiry\");\n if (child.scopes.some((scope) => !parent.scopes.includes(scope)))\n throw new Error(\"Agent handoff cannot escalate scopes\");\n if (parent.spendLimit !== undefined) {\n if (child.spendLimit === undefined || child.spendLimit.currency !== parent.spendLimit.currency || child.spendLimit.amountMinor > parent.spendLimit.amountMinor)\n throw new Error(\"Agent handoff cannot escalate spend\");\n }\n return child;\n};\n// src/simulation.ts\nvar simulateAction = async ({\n approval,\n input,\n now = Date.now,\n policy\n}) => {\n const evaluatedAt = now();\n const action = {\n ...input,\n actionId: `sim_${await digest(input)}`,\n createdAt: evaluatedAt,\n inputDigest: await digest(input.input ?? null)\n };\n const decision = await policy.evaluate({\n action,\n approval,\n now: evaluatedAt\n });\n return {\n action,\n bindingDigest: await actionBinding(action),\n decision,\n dryRun: true,\n wouldExecute: decision.kind === \"allow\"\n };\n};\n// src/postgres.ts\nvar namespaceOf = (namespace) => {\n if (!/^[a-z_][a-z0-9_]*$/.test(namespace))\n throw new Error(\"Agency PostgreSQL namespace must be a simple identifier\");\n return namespace;\n};\nvar agencyPostgresSchemaSql = (namespace = \"agency\") => {\n const ns = namespaceOf(namespace);\n return `CREATE SCHEMA IF NOT EXISTS ${ns};\nCREATE TABLE IF NOT EXISTS ${ns}.actions (\n action_id text PRIMARY KEY,\n actor_id text NOT NULL,\n created_at bigint NOT NULL,\n data jsonb NOT NULL\n);\nCREATE INDEX IF NOT EXISTS actions_actor_created_idx ON ${ns}.actions (actor_id, created_at DESC);\nCREATE TABLE IF NOT EXISTS ${ns}.approvals (\n action_id text PRIMARY KEY REFERENCES ${ns}.actions(action_id) ON DELETE CASCADE,\n actor_id text NOT NULL,\n approved_at bigint NOT NULL,\n data jsonb NOT NULL\n);\nCREATE TABLE IF NOT EXISTS ${ns}.leases (\n lease_id text PRIMARY KEY,\n action_id text NOT NULL REFERENCES ${ns}.actions(action_id) ON DELETE CASCADE,\n actor_id text NOT NULL,\n issued_at bigint NOT NULL,\n consumed_at bigint,\n data jsonb NOT NULL\n);\nCREATE TABLE IF NOT EXISTS ${ns}.receipts (\n receipt_id text PRIMARY KEY,\n action_id text NOT NULL REFERENCES ${ns}.actions(action_id) ON DELETE CASCADE,\n actor_id text NOT NULL,\n completed_at bigint NOT NULL,\n data jsonb NOT NULL\n);\nCREATE TABLE IF NOT EXISTS ${ns}.kill_switches (\n agent_id text PRIMARY KEY,\n activated_at bigint NOT NULL,\n data jsonb NOT NULL\n);\nCREATE TABLE IF NOT EXISTS ${ns}.handoff_nonces (\n nonce_hash text PRIMARY KEY,\n expires_at bigint NOT NULL\n);\nCREATE INDEX IF NOT EXISTS handoff_nonces_expiry_idx ON ${ns}.handoff_nonces (expires_at);`;\n};\nvar createPostgresAgencyStore = ({\n client,\n namespace = \"agency\"\n}) => {\n const ns = namespaceOf(namespace);\n const one = async (sql, parameters) => (await client.query(sql, parameters)).rows[0]?.data;\n const many = async (sql, parameters) => (await client.query(sql, parameters)).rows.map(({ data }) => data);\n const actorFor = async (actionId) => {\n const row = await client.query(`SELECT actor_id FROM ${ns}.actions WHERE action_id = $1`, [actionId]);\n if (row.rows[0] === undefined)\n throw new Error(\"Unknown action\");\n return row.rows[0].actor_id;\n };\n return {\n consumeLease: async (leaseId, consumedAt) => (await client.query(`UPDATE ${ns}.leases SET consumed_at = $2, data = jsonb_set(data, '{consumedAt}', to_jsonb($2::bigint), true) WHERE lease_id = $1 AND consumed_at IS NULL`, [leaseId, consumedAt])).rowCount === 1,\n getAction: (id) => one(`SELECT data FROM ${ns}.actions WHERE action_id = $1`, [id]),\n getApproval: (id) => one(`SELECT data FROM ${ns}.approvals WHERE action_id = $1`, [id]),\n getLease: (id) => one(`SELECT data FROM ${ns}.leases WHERE lease_id = $1`, [id]),\n listActions: (actorId) => many(`SELECT data FROM ${ns}.actions WHERE ($1::text IS NULL OR actor_id = $1) ORDER BY created_at DESC`, [actorId ?? null]),\n listApprovals: (actorId) => many(`SELECT data FROM ${ns}.approvals WHERE ($1::text IS NULL OR actor_id = $1) ORDER BY approved_at DESC`, [actorId ?? null]),\n listLeases: (actorId) => many(`SELECT data FROM ${ns}.leases WHERE ($1::text IS NULL OR actor_id = $1) ORDER BY issued_at DESC`, [actorId ?? null]),\n listReceipts: (actorId) => many(`SELECT data FROM ${ns}.receipts WHERE ($1::text IS NULL OR actor_id = $1) ORDER BY completed_at DESC`, [actorId ?? null]),\n saveAction: async (action) => {\n await client.query(`INSERT INTO ${ns}.actions (action_id, actor_id, created_at, data) VALUES ($1, $2, $3, $4::jsonb) ON CONFLICT (action_id) DO UPDATE SET data = EXCLUDED.data`, [action.actionId, action.actor.agentId, action.createdAt, JSON.stringify(action)]);\n },\n saveApproval: async (approval) => {\n await client.query(`INSERT INTO ${ns}.approvals (action_id, actor_id, approved_at, data) VALUES ($1, $2, $3, $4::jsonb) ON CONFLICT (action_id) DO UPDATE SET approved_at = EXCLUDED.approved_at, data = EXCLUDED.data`, [approval.actionId, await actorFor(approval.actionId), approval.approvedAt, JSON.stringify(approval)]);\n },\n saveLease: async (lease) => {\n await client.query(`INSERT INTO ${ns}.leases (lease_id, action_id, actor_id, issued_at, consumed_at, data) VALUES ($1, $2, $3, $4, $5, $6::jsonb) ON CONFLICT (lease_id) DO NOTHING`, [lease.leaseId, lease.actionId, await actorFor(lease.actionId), lease.issuedAt, lease.consumedAt ?? null, JSON.stringify(lease)]);\n },\n saveReceipt: async (receipt) => {\n await client.query(`INSERT INTO ${ns}.receipts (receipt_id, action_id, actor_id, completed_at, data) VALUES ($1, $2, $3, $4, $5::jsonb) ON CONFLICT (receipt_id) DO NOTHING`, [receipt.receiptId, receipt.actionId, await actorFor(receipt.actionId), receipt.completedAt, JSON.stringify(receipt)]);\n }\n };\n};\nvar createPostgresAgentControlStore = ({\n client,\n namespace = \"agency\"\n}) => {\n const ns = namespaceOf(namespace);\n return {\n clearKillSwitch: async (agentId) => {\n await client.query(`DELETE FROM ${ns}.kill_switches WHERE agent_id = $1`, [agentId]);\n },\n getKillSwitch: async (agentId) => (await client.query(`SELECT data FROM ${ns}.kill_switches WHERE agent_id = $1`, [agentId])).rows[0]?.data,\n setKillSwitch: async (killSwitch) => {\n await client.query(`INSERT INTO ${ns}.kill_switches (agent_id, activated_at, data) VALUES ($1, $2, $3::jsonb) ON CONFLICT (agent_id) DO UPDATE SET activated_at = EXCLUDED.activated_at, data = EXCLUDED.data`, [killSwitch.agentId, killSwitch.activatedAt, JSON.stringify(killSwitch)]);\n }\n };\n};\nvar createPostgresHandoffReplayStore = ({\n client,\n namespace = \"agency\",\n now = Date.now\n}) => {\n const ns = namespaceOf(namespace);\n return {\n consume: async (nonce, expiresAt) => {\n if (expiresAt <= now())\n return false;\n const nonceHash = await digest(nonce);\n const result = await client.query(`INSERT INTO ${ns}.handoff_nonces (nonce_hash, expires_at) VALUES ($1, $2) ON CONFLICT (nonce_hash) DO NOTHING`, [nonceHash, expiresAt]);\n return result.rowCount === 1;\n }\n };\n};\n// src/telemetry.ts\nvar agencyEventToTelemetry = (event, timestamp = Date.now()) => {\n const actionId = \"action\" in event ? event.action.actionId : event.actionId;\n const attributes = {\n \"agent.action.id\": actionId,\n \"agent.event.type\": event.type\n };\n if (event.type === \"action.requested\") {\n attributes[\"agent.id\"] = event.action.actor.agentId;\n attributes[\"agent.action.name\"] = event.action.action;\n attributes[\"agent.action.effects\"] = event.action.effects.join(\",\");\n attributes[\"agent.user.id\"] = event.action.actor.userId;\n } else if (event.type === \"action.decided\") {\n attributes[\"agent.decision.kind\"] = event.decision.kind;\n attributes[\"agent.decision.requestable\"] = event.decision.kind === \"deny\" && event.decision.requestable;\n } else if (event.type === \"action.lease-issued\") {\n attributes[\"agent.lease.id\"] = event.lease.leaseId;\n attributes[\"agent.lease.expires_at\"] = event.lease.expiresAt;\n } else if (event.type === \"action.completed\") {\n attributes[\"agent.receipt.id\"] = event.receipt.receiptId;\n attributes[\"agent.execution.status\"] = event.receipt.status;\n } else {\n attributes[\"agent.approval.id\"] = event.approval.approvalId;\n attributes[\"agent.approval.by\"] = event.approval.approvedBy;\n }\n return { attributes, name: `agent.${event.type}`, timestamp };\n};\nvar createAgencyTelemetryEmitter = (emit, now = Date.now) => (event) => emit(agencyEventToTelemetry(event, now()));\nexport {\n verifyAgentHandoff,\n simulateAction,\n signAgentHandoff,\n digest,\n denyAllPolicy,\n createPostgresHandoffReplayStore,\n createPostgresAgentControlStore,\n createPostgresAgencyStore,\n createMemoryHandoffReplayStore,\n createMemoryAgentControlStore,\n createMemoryAgencyStore,\n createAgentControlPlane,\n createAgencyTelemetryEmitter,\n createAgency,\n canonicalJson,\n attenuateAgentHandoff,\n allowAllPolicy,\n agencyPostgresSchemaSql,\n agencyEventToTelemetry,\n actionBinding\n};\n\n//# debugId=9228E134AB6761D064756E2164756E21\n//# sourceMappingURL=index.js.map\n",
6
+ "import {\n type ActionDecision,\n type Agency,\n type AgentActor,\n digest,\n} from \"@absolutejs/agency\";\nimport type { SecretBroker } from \"./broker\";\n\nexport type CredentialGrant = {\n agentId: string;\n allowedOrigins: ReadonlyArray<string>;\n createdAt: number;\n expiresAt: number;\n grantId: string;\n maximumUses: number;\n provider: string;\n scopes: ReadonlyArray<string>;\n secretName: string;\n used: number;\n userId: string;\n};\n\nexport type CredentialOperationInput = {\n actor: AgentActor;\n destination: string;\n grantId: string;\n idempotencyKey?: string;\n input?: unknown;\n operation: string;\n};\n\nexport type CredentialOperationContext = {\n credential: string;\n destination: URL;\n input: unknown;\n signal?: AbortSignal;\n};\n\nexport type CredentialProvider = {\n operations: Record<\n string,\n (context: CredentialOperationContext) => Promise<unknown> | unknown\n >;\n provider: string;\n};\n\nexport type PendingCredentialOperation = {\n actionId: string;\n input: CredentialOperationInput;\n};\n\nexport type CredentialGrantStore = {\n consume: (\n grantId: string,\n now: number,\n ) => Promise<CredentialGrant | undefined>;\n get: (grantId: string) => Promise<CredentialGrant | undefined>;\n getPending: (\n actionId: string,\n ) => Promise<PendingCredentialOperation | undefined>;\n put: (grant: CredentialGrant) => Promise<void>;\n putPending: (pending: PendingCredentialOperation) => Promise<void>;\n revoke: (grantId: string) => Promise<boolean>;\n};\n\nexport type CredentialOperationEvent =\n | {\n actionId?: string;\n destinationOrigin: string;\n grantId: string;\n operation: string;\n provider: string;\n type: \"credential.operation.requested\";\n }\n | {\n actionId?: string;\n destinationOrigin: string;\n grantId: string;\n operation: string;\n provider: string;\n resultDigest?: string;\n status: \"failed\" | \"succeeded\";\n type: \"credential.operation.completed\";\n };\n\nexport type CredentialOperationResult =\n | {\n actionId: string;\n decision: ActionDecision;\n kind: \"pending\";\n }\n | {\n actionId?: string;\n kind: \"completed\";\n result: unknown;\n resultDigest: string;\n };\n\nexport type CredentialOperationBrokerOptions = {\n agency?: Agency;\n emit?: (event: CredentialOperationEvent) => Promise<void> | void;\n now?: () => number;\n providers: ReadonlyArray<CredentialProvider>;\n secrets: SecretBroker;\n store: CredentialGrantStore;\n};\n\nconst normalizeOrigin = (destination: string) => new URL(destination).origin;\n\nconst validateGrant = (\n grant: CredentialGrant,\n input: CredentialOperationInput,\n now: number,\n) => {\n if (\n grant.agentId !== input.actor.agentId ||\n grant.userId !== input.actor.userId\n )\n throw new Error(\"Credential grant actor mismatch\");\n if (grant.expiresAt <= now) throw new Error(\"Credential grant has expired\");\n if (!grant.scopes.includes(input.operation))\n throw new Error(\"Credential operation is outside grant scope\");\n const origin = normalizeOrigin(input.destination);\n if (!grant.allowedOrigins.map(normalizeOrigin).includes(origin))\n throw new Error(\"Credential destination is outside grant scope\");\n if (grant.used >= grant.maximumUses)\n throw new Error(\"Credential grant has been exhausted\");\n\n return origin;\n};\n\nexport const createMemoryCredentialGrantStore = (): CredentialGrantStore => {\n const grants = new Map<string, CredentialGrant>();\n const pending = new Map<string, PendingCredentialOperation>();\n let lock = Promise.resolve();\n\n const exclusive = async <Result>(run: () => Result | Promise<Result>) => {\n const previous = lock;\n let release = () => {};\n lock = new Promise<void>((resolve) => {\n release = resolve;\n });\n await previous;\n try {\n return await run();\n } finally {\n release();\n }\n };\n\n return {\n consume: (grantId, now) =>\n exclusive(() => {\n const grant = grants.get(grantId);\n if (\n grant === undefined ||\n grant.expiresAt <= now ||\n grant.used >= grant.maximumUses\n )\n return undefined;\n const consumed = { ...grant, used: grant.used + 1 };\n grants.set(grantId, consumed);\n return { ...consumed };\n }),\n get: async (grantId) => {\n const grant = grants.get(grantId);\n return grant === undefined ? undefined : { ...grant };\n },\n getPending: async (actionId) => pending.get(actionId),\n put: async (grant) => {\n if (grant.maximumUses < 1)\n throw new Error(\"maximumUses must be positive\");\n if (grant.used < 0 || grant.used > grant.maximumUses)\n throw new Error(\"Invalid credential grant usage\");\n grants.set(grant.grantId, { ...grant });\n },\n putPending: async (operation) => {\n pending.set(operation.actionId, operation);\n },\n revoke: async (grantId) => grants.delete(grantId),\n };\n};\n\nexport const createCredentialOperationBroker = ({\n agency,\n emit,\n now = Date.now,\n providers,\n secrets,\n store,\n}: CredentialOperationBrokerOptions) => {\n const providerMap = new Map(\n providers.map((provider) => [provider.provider, provider]),\n );\n\n const perform = async (\n input: CredentialOperationInput,\n actionId?: string,\n signal?: AbortSignal,\n ): Promise<CredentialOperationResult> => {\n const preview = await store.get(input.grantId);\n if (preview === undefined) throw new Error(\"Unknown credential grant\");\n const origin = validateGrant(preview, input, now());\n const provider = providerMap.get(preview.provider);\n const operation = provider?.operations[input.operation];\n if (provider === undefined || operation === undefined)\n throw new Error(\"Credential provider operation is not registered\");\n await emit?.({\n actionId,\n destinationOrigin: origin,\n grantId: preview.grantId,\n operation: input.operation,\n provider: preview.provider,\n type: \"credential.operation.requested\",\n });\n const consumed = await store.consume(input.grantId, now());\n if (consumed === undefined)\n throw new Error(\"Credential grant has been exhausted\");\n validateGrant({ ...consumed, used: consumed.used - 1 }, input, now());\n const secret = await secrets.resolve(consumed.secretName);\n if (secret === null) throw new Error(\"Credential is not configured\");\n try {\n const result = await operation({\n credential: secret.value,\n destination: new URL(input.destination),\n input: input.input,\n signal,\n });\n const resultDigest = await digest(result);\n await emit?.({\n actionId,\n destinationOrigin: origin,\n grantId: consumed.grantId,\n operation: input.operation,\n provider: consumed.provider,\n resultDigest,\n status: \"succeeded\",\n type: \"credential.operation.completed\",\n });\n return { actionId, kind: \"completed\", result, resultDigest };\n } catch (error) {\n await emit?.({\n actionId,\n destinationOrigin: origin,\n grantId: consumed.grantId,\n operation: input.operation,\n provider: consumed.provider,\n status: \"failed\",\n type: \"credential.operation.completed\",\n });\n throw error;\n }\n };\n\n const request = async (\n input: CredentialOperationInput,\n signal?: AbortSignal,\n ): Promise<CredentialOperationResult> => {\n const grant = await store.get(input.grantId);\n if (grant === undefined) throw new Error(\"Unknown credential grant\");\n const origin = validateGrant(grant, input, now());\n if (agency === undefined) return perform(input, undefined, signal);\n const { action, decision } = await agency.request({\n action: `credential.${grant.provider}.${input.operation}`,\n actor: input.actor,\n context: { destinationOrigin: origin, grantId: grant.grantId },\n effects: [\"external-network\"],\n expiresAt: grant.expiresAt,\n idempotencyKey: input.idempotencyKey,\n input: { destination: input.destination, input: input.input },\n resource: { id: grant.grantId, type: \"credential-grant\" },\n });\n if (decision.kind !== \"allow\") {\n await store.putPending({ actionId: action.actionId, input });\n return { actionId: action.actionId, decision, kind: \"pending\" };\n }\n const lease = await agency.issueLease(action.actionId);\n const execution = await agency.execute({\n executor: `credential-provider:${grant.provider}`,\n leaseId: lease.leaseId,\n run: () => perform(input, action.actionId, signal),\n });\n return execution.result;\n };\n\n const resume = async (actionId: string, signal?: AbortSignal) => {\n if (agency === undefined) throw new Error(\"Agency is not configured\");\n const pending = await store.getPending(actionId);\n if (pending === undefined)\n throw new Error(\"Unknown pending credential operation\");\n const lease = await agency.issueLease(actionId);\n const execution = await agency.execute({\n executor: \"credential-provider\",\n leaseId: lease.leaseId,\n run: () => perform(pending.input, actionId, signal),\n });\n return execution.result;\n };\n\n return { request, resume, revoke: store.revoke };\n};\n\nexport type CredentialOperationBroker = ReturnType<\n typeof createCredentialOperationBroker\n>;\n",
7
+ "import type {\n CredentialGrant,\n CredentialGrantStore,\n PendingCredentialOperation,\n} from \"./operations\";\n\nexport type SecretsSqlResult<Row> = {\n rowCount: number;\n rows: ReadonlyArray<Row>;\n};\nexport type SecretsSqlClient = {\n query: <Row = Record<string, unknown>>(\n sql: string,\n parameters?: ReadonlyArray<unknown>,\n ) => Promise<SecretsSqlResult<Row>>;\n};\n\nconst namespaceOf = (namespace: string) => {\n if (!/^[a-z_][a-z0-9_]*$/.test(namespace))\n throw new Error(\"Secrets PostgreSQL namespace must be a simple identifier\");\n return namespace;\n};\n\nexport const credentialGrantsPostgresSchemaSql = (namespace = \"secrets\") => {\n const ns = namespaceOf(namespace);\n return `CREATE SCHEMA IF NOT EXISTS ${ns};\nCREATE TABLE IF NOT EXISTS ${ns}.credential_grants (\n grant_id text PRIMARY KEY, agent_id text NOT NULL, user_id text NOT NULL,\n provider text NOT NULL, expires_at bigint NOT NULL, maximum_uses integer NOT NULL CHECK (maximum_uses > 0),\n used integer NOT NULL DEFAULT 0 CHECK (used >= 0 AND used <= maximum_uses), data jsonb NOT NULL\n);\nCREATE INDEX IF NOT EXISTS credential_grants_agent_idx ON ${ns}.credential_grants (agent_id, expires_at);\nCREATE TABLE IF NOT EXISTS ${ns}.pending_operations (\n action_id text PRIMARY KEY, grant_id text NOT NULL REFERENCES ${ns}.credential_grants(grant_id) ON DELETE CASCADE,\n created_at timestamptz NOT NULL DEFAULT now(), data jsonb NOT NULL\n);`;\n};\n\ntype DataRow<Value> = { data: Value };\n\nexport const createPostgresCredentialGrantStore = ({\n client,\n namespace = \"secrets\",\n}: {\n client: SecretsSqlClient;\n namespace?: string;\n}): CredentialGrantStore => {\n const ns = namespaceOf(namespace);\n return {\n consume: async (grantId, now) => {\n const result = await client.query<DataRow<CredentialGrant>>(\n `UPDATE ${ns}.credential_grants SET used = used + 1, data = jsonb_set(data, '{used}', to_jsonb(used + 1), true) WHERE grant_id = $1 AND expires_at > $2 AND used < maximum_uses RETURNING data`,\n [grantId, now],\n );\n return result.rows[0]?.data;\n },\n get: async (grantId) =>\n (\n await client.query<DataRow<CredentialGrant>>(\n `SELECT data FROM ${ns}.credential_grants WHERE grant_id = $1`,\n [grantId],\n )\n ).rows[0]?.data,\n getPending: async (actionId) =>\n (\n await client.query<DataRow<PendingCredentialOperation>>(\n `SELECT data FROM ${ns}.pending_operations WHERE action_id = $1`,\n [actionId],\n )\n ).rows[0]?.data,\n put: async (grant) => {\n if (\n grant.maximumUses < 1 ||\n grant.used < 0 ||\n grant.used > grant.maximumUses\n )\n throw new Error(\"Invalid credential grant usage\");\n await client.query(\n `INSERT INTO ${ns}.credential_grants (grant_id, agent_id, user_id, provider, expires_at, maximum_uses, used, data) VALUES ($1,$2,$3,$4,$5,$6,$7,$8::jsonb) ON CONFLICT (grant_id) DO UPDATE SET expires_at=EXCLUDED.expires_at, maximum_uses=EXCLUDED.maximum_uses, used=EXCLUDED.used, data=EXCLUDED.data`,\n [\n grant.grantId,\n grant.agentId,\n grant.userId,\n grant.provider,\n grant.expiresAt,\n grant.maximumUses,\n grant.used,\n JSON.stringify(grant),\n ],\n );\n },\n putPending: async (pending) => {\n await client.query(\n `INSERT INTO ${ns}.pending_operations (action_id, grant_id, data) VALUES ($1,$2,$3::jsonb) ON CONFLICT (action_id) DO UPDATE SET data=EXCLUDED.data`,\n [pending.actionId, pending.input.grantId, JSON.stringify(pending)],\n );\n },\n revoke: async (grantId) =>\n (\n await client.query(\n `DELETE FROM ${ns}.credential_grants WHERE grant_id = $1`,\n [grantId],\n )\n ).rowCount === 1,\n };\n};\n"
8
+ ],
9
+ "mappings": "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",
10
+ "debugId": "2D416FD568D31C1164756E2164756E21",
11
+ "names": []
12
+ }