@undefineds.co/linx 0.2.12 → 0.2.14

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.
@@ -1,19 +1,18 @@
1
1
  import { setTimeout as delay } from 'node:timers/promises';
2
- import { getClientCredentialId, getClientCredentialKey } from '../credentials-store.js';
3
- import { AS_ACTOR, AS_ANNOUNCE, AS_OBJECT, DCT_CREATED, ODRL_ACTION, ODRL_POLICY, ODRL_TARGET, RDF_TYPE, UDFS_ACTION, UDFS_ACTOR, UDFS_ACTOR_ROLE, UDFS_APPROVAL_REQUEST, UDFS_ASSIGNED_TO, UDFS_AUDIT_ENTRY, UDFS_AUTONOMY_GRANT, UDFS_CONTEXT, UDFS_DECISION_BY, UDFS_DECISION_ROLE, UDFS_EFFECT, UDFS_ON_BEHALF_OF, UDFS_POLICY_VERSION, UDFS_REASON, UDFS_RESOLVED_AT, UDFS_REVOKED_AT, UDFS_RISK, UDFS_RISK_CEILING, UDFS_SESSION, UDFS_STATUS, UDFS_TOOL_CALL_ID, UDFS_TOOL_NAME, buildApprovalResourceUrl, buildAuditResourceUrl, buildGrantResourceUrl, buildInboxResourceUrl, firstIri, firstLiteral, iri, listTurtleResources, literal, parseManagedTurtleBlocks, readTurtleResource, subjectIdFromResourceUrl, upsertManagedTurtleBlock, } from '../pi-adapter/pod-native.js';
2
+ import { getDefaultPodDataSession } from '../pod-data-session.js';
3
+ import { AS, ODRL, UDFS } from '@undefineds.co/models/namespaces';
4
+ import { ApprovalVocab, AuditVocab, GrantVocab, InboxNotificationVocab } from '@undefineds.co/models/vocab/sidecar';
5
+ import { buildApprovalDocumentUrl, RDF_TYPE, buildApprovalResourceUrl, buildAuditDocumentUrl, buildAuditResourceUrl, buildGrantResourceUrl, buildInboxResourceUrl, firstIri, firstLiteral, iri, listTurtleResources, listTurtleResourcesRecursive, literal, parseManagedTurtleBlocks, readTurtleResource, subjectIdFromResourceUrl, upsertManagedTurtleBlock, } from '../pi-adapter/pod-native.js';
4
6
  const WATCH_CHAT_ID = 'linx-watch';
5
7
  const WATCH_AGENT_ID = 'linx-watch-assistant';
6
8
  const REMOTE_APPROVAL_POLICY_VERSION = 'linx-watch-remote-approval/v1';
7
9
  const DEFAULT_REMOTE_APPROVAL_POLL_MS = 1000;
10
+ const remoteApprovalClientCache = new WeakMap();
8
11
  function createAbortError() {
9
12
  const error = new Error('The operation was aborted.');
10
13
  error.name = 'AbortError';
11
14
  return error;
12
15
  }
13
- async function dynamicImport(specifier) {
14
- const loader = new Function('modulePath', 'return import(modulePath)');
15
- return loader(specifier);
16
- }
17
16
  function normalizeString(value) {
18
17
  return typeof value === 'string' && value.trim() ? value.trim() : undefined;
19
18
  }
@@ -43,10 +42,16 @@ function buildThreadUri(webId, threadId) {
43
42
  return `${getPodBaseUrl(webId)}/.data/chat/${WATCH_CHAT_ID}/index.ttl#${threadId}`;
44
43
  }
45
44
  function buildApprovalUri(webIdOrUri, approvalId) {
46
- return `${getPodBaseUrl(webIdOrUri)}/.data/approvals/${approvalId}.ttl`;
45
+ return buildApprovalResourceUrl(webIdOrUri, approvalId);
46
+ }
47
+ function buildApprovalUriForDate(webIdOrUri, approvalId, createdAt) {
48
+ return buildApprovalResourceUrl(webIdOrUri, approvalId, createdAt);
47
49
  }
48
50
  function buildGrantUri(webIdOrUri, grantId) {
49
- return `${getPodBaseUrl(webIdOrUri)}/settings/autonomy/grants/${grantId}.ttl`;
51
+ return buildGrantResourceUrl(webIdOrUri, grantId);
52
+ }
53
+ function buildGrantDocumentUrl(webIdOrUri) {
54
+ return `${getPodBaseUrl(webIdOrUri)}/settings/autonomy/grants.ttl`;
50
55
  }
51
56
  function buildAgentUri(webId) {
52
57
  return `${getPodBaseUrl(webId)}/.data/agents/${WATCH_AGENT_ID}.ttl`;
@@ -105,16 +110,6 @@ function buildRequestMessage(request) {
105
110
  }
106
111
  return request.message;
107
112
  }
108
- function buildRequestAuditContext(record, request) {
109
- return {
110
- kind: request.kind,
111
- message: buildRequestMessage(request),
112
- ...(request.kind === 'command-approval' && request.command ? { command: request.command } : {}),
113
- ...(request.kind === 'command-approval' && request.cwd ? { cwd: request.cwd } : {}),
114
- backend: record.backend,
115
- sessionId: record.id,
116
- };
117
- }
118
113
  function extractToolCallId(request) {
119
114
  if (!isRecord(request.raw)) {
120
115
  return crypto.randomUUID();
@@ -126,7 +121,7 @@ function extractToolCallId(request) {
126
121
  ?? crypto.randomUUID();
127
122
  }
128
123
  function encodeDecisionReason(decision, note) {
129
- return JSON.stringify({
124
+ return safeJsonStringify({
130
125
  decision,
131
126
  ...(note?.trim() ? { note: note.trim() } : {}),
132
127
  });
@@ -153,34 +148,25 @@ function parseDecisionReason(value) {
153
148
  return null;
154
149
  }
155
150
  }
156
- function parseRequestAuditContext(value) {
157
- if (typeof value !== 'string' || !value.trim()) {
158
- return null;
159
- }
151
+ async function warnOnly(runtime, task) {
160
152
  try {
161
- const parsed = JSON.parse(value);
162
- if (!isRecord(parsed)) {
163
- return null;
164
- }
165
- const kind = normalizeString(parsed.kind);
166
- const message = normalizeString(parsed.message);
167
- const sessionId = normalizeString(parsed.sessionId);
168
- if (!kind || !message || !sessionId) {
169
- return null;
153
+ await task();
154
+ }
155
+ catch (error) {
156
+ if (runtime.onWarning) {
157
+ runtime.onWarning(error);
158
+ return;
170
159
  }
171
- return {
172
- kind: kind,
173
- message,
174
- sessionId,
175
- ...(normalizeString(parsed.backend) ? { backend: normalizeString(parsed.backend) } : {}),
176
- ...(normalizeString(parsed.runtime) ? { runtime: normalizeString(parsed.runtime) } : {}),
177
- ...(normalizeString(parsed.toolName) ? { toolName: normalizeString(parsed.toolName) } : {}),
178
- ...(normalizeString(parsed.command) ? { command: normalizeString(parsed.command) } : {}),
179
- ...(normalizeString(parsed.cwd) ? { cwd: normalizeString(parsed.cwd) } : {}),
180
- };
160
+ const message = error instanceof Error ? error.message : String(error);
161
+ process.emitWarning(`LinX Pod sync failed: ${message}`);
162
+ }
163
+ }
164
+ function safeJsonStringify(value) {
165
+ try {
166
+ return JSON.stringify(value);
181
167
  }
182
168
  catch {
183
- return null;
169
+ return JSON.stringify({ error: 'unserializable_context' });
184
170
  }
185
171
  }
186
172
  function extractSessionId(sessionUri) {
@@ -203,15 +189,7 @@ function decisionFromApprovalRow(row) {
203
189
  }
204
190
  return 'accept';
205
191
  }
206
- function requestAuditForApproval(approvalUri, audits) {
207
- const matches = audits.filter((audit) => audit.approval === approvalUri && audit.action === 'approval_requested');
208
- matches.sort((left, right) => toIsoString(right.createdAt, '').localeCompare(toIsoString(left.createdAt, '')));
209
- return matches[0];
210
- }
211
- function normalizeApprovalSummary(row, audits) {
212
- const approvalUri = buildApprovalUri(row.session, row.id);
213
- const requestAudit = requestAuditForApproval(approvalUri, audits);
214
- const requestContext = parseRequestAuditContext(requestAudit?.context);
192
+ function normalizeApprovalSummary(row) {
215
193
  const createdAt = toIsoString(row.createdAt, new Date(0).toISOString());
216
194
  const sessionUri = row.session;
217
195
  const decision = decisionFromApprovalRow(row);
@@ -223,9 +201,7 @@ function normalizeApprovalSummary(row, audits) {
223
201
  toolName: row.toolName,
224
202
  risk: normalizeString(row.risk) ?? 'medium',
225
203
  status: normalizeString(row.status) ?? 'pending',
226
- message: requestContext?.message ?? row.toolName,
227
- ...(requestContext?.command ? { command: requestContext.command } : {}),
228
- ...(requestContext?.cwd ? { cwd: requestContext.cwd } : {}),
204
+ message: formatApprovalMessage(row),
229
205
  ...(normalizeString(row.assignedTo) ? { assignedTo: normalizeString(row.assignedTo) } : {}),
230
206
  ...(normalizeString(row.decisionBy) ? { decisionBy: normalizeString(row.decisionBy) } : {}),
231
207
  ...(decision ? { decision } : {}),
@@ -233,6 +209,18 @@ function normalizeApprovalSummary(row, audits) {
233
209
  ...(row.resolvedAt ? { resolvedAt: toIsoString(row.resolvedAt, createdAt) } : {}),
234
210
  };
235
211
  }
212
+ function formatApprovalMessage(row) {
213
+ if (row.toolName === 'commandExecution') {
214
+ return 'Command execution approval';
215
+ }
216
+ if (row.toolName === 'fileChange') {
217
+ return 'File change approval';
218
+ }
219
+ if (row.toolName === 'permissionRequest') {
220
+ return 'Permission approval';
221
+ }
222
+ return row.toolName;
223
+ }
236
224
  function formatSummaryHeadline(summary) {
237
225
  return `${summary.id} | ${summary.status} | ${summary.risk} | session=${summary.sessionId}`;
238
226
  }
@@ -251,37 +239,11 @@ export function isRemoteApprovalAbortError(error) {
251
239
  function missingRemoteApprovalCredentialsMessage() {
252
240
  return 'LinX remote approval requires `linx login` first.';
253
241
  }
254
- function unsupportedRemoteApprovalAuthMessage() {
255
- return 'LinX remote approval requires a valid LinX login in `~/.linx`.';
256
- }
257
242
  async function createDefaultRuntime() {
258
- const [credentialsStore, solidAuth, oidcAuth] = await Promise.all([
259
- dynamicImport(new URL('../credentials-store.js', import.meta.url).href),
260
- dynamicImport(new URL('../solid-auth.js', import.meta.url).href),
261
- dynamicImport(new URL('../oidc-auth.js', import.meta.url).href),
262
- ]);
263
243
  return {
264
- loadCredentials: credentialsStore.loadCredentials,
265
- getClientCredentials: credentialsStore.getClientCredentials,
266
- getOidcAccessToken: oidcAuth.getOidcAccessToken,
267
- authenticate: solidAuth.authenticate,
268
- authenticatedFetch(url, token, init) {
269
- const headers = new Headers(init?.headers);
270
- headers.set('Authorization', `Bearer ${token}`);
271
- return fetch(url, { ...init, headers });
272
- },
273
- createStore(sessionOrWebId, fetcher) {
274
- const webId = typeof sessionOrWebId === 'string' ? sessionOrWebId : sessionOrWebId.info.webId;
275
- if (!webId) {
276
- throw new Error('Remote approval authentication succeeded without a WebID.');
277
- }
278
- const activeFetcher = fetcher ?? (typeof sessionOrWebId === 'string'
279
- ? undefined
280
- : ((url, init) => sessionOrWebId.fetch(url, init)));
281
- if (!activeFetcher) {
282
- throw new Error('Remote approval authentication succeeded without a Pod fetcher.');
283
- }
284
- return createNativeRemoteApprovalStore(webId, activeFetcher);
244
+ getPodDataSession: getDefaultPodDataSession,
245
+ createStore(webId, fetcher) {
246
+ return createNativeRemoteApprovalStore(webId, fetcher);
285
247
  },
286
248
  sleep(ms) {
287
249
  return delay(ms);
@@ -292,39 +254,43 @@ async function createDefaultRuntime() {
292
254
  };
293
255
  }
294
256
  async function withRemoteApprovalStore(runtime, fn) {
295
- const stored = runtime.loadCredentials();
296
- if (!stored) {
257
+ const client = await getRemoteApprovalClient(runtime);
258
+ if (!client) {
297
259
  throw new Error(missingRemoteApprovalCredentialsMessage());
298
260
  }
299
- const clientCredentials = runtime.getClientCredentials(stored);
300
- if (clientCredentials) {
301
- const { session } = await runtime.authenticate(getClientCredentialId(clientCredentials), getClientCredentialKey(clientCredentials), stored.url);
302
- const webId = session.info.webId ?? stored.webId;
303
- if (!webId) {
304
- await session.logout().catch(() => undefined);
305
- throw new Error('Remote approval authentication succeeded without a WebID.');
306
- }
307
- try {
308
- return await fn({
309
- store: runtime.createStore(session),
310
- webId,
311
- stored,
312
- });
313
- }
314
- finally {
315
- await session.logout().catch(() => undefined);
316
- }
317
- }
318
- const accessToken = await runtime.getOidcAccessToken?.(stored);
319
- if (accessToken && stored.webId && runtime.authenticatedFetch) {
320
- const fetcher = (url, init) => runtime.authenticatedFetch(url, accessToken, init);
321
- return await fn({
322
- store: runtime.createStore(stored.webId, fetcher),
323
- webId: stored.webId,
324
- stored,
261
+ return await fn({
262
+ store: client.store,
263
+ webId: client.session.webId,
264
+ stored: client.session.credentials,
265
+ });
266
+ }
267
+ async function getRemoteApprovalClient(runtime) {
268
+ let promise = remoteApprovalClientCache.get(runtime);
269
+ if (!promise) {
270
+ promise = createRemoteApprovalClient(runtime)
271
+ .then((client) => {
272
+ if (!client) {
273
+ remoteApprovalClientCache.delete(runtime);
274
+ }
275
+ return client;
276
+ })
277
+ .catch((error) => {
278
+ remoteApprovalClientCache.delete(runtime);
279
+ throw error;
325
280
  });
281
+ remoteApprovalClientCache.set(runtime, promise);
326
282
  }
327
- throw new Error(unsupportedRemoteApprovalAuthMessage());
283
+ return promise;
284
+ }
285
+ async function createRemoteApprovalClient(runtime) {
286
+ const session = await runtime.getPodDataSession();
287
+ if (!session) {
288
+ return null;
289
+ }
290
+ return {
291
+ session,
292
+ store: runtime.createStore(session.webId, session.fetch),
293
+ };
328
294
  }
329
295
  function createNativeRemoteApprovalStore(webId, fetcher) {
330
296
  return {
@@ -345,99 +311,109 @@ function createNativeRemoteApprovalStore(webId, fetcher) {
345
311
  };
346
312
  }
347
313
  async function listApprovalRows(webId, fetcher) {
348
- const urls = await listTurtleResources(fetcher, `${getPodBaseUrl(webId)}/.data/approvals/`);
314
+ const [currentUrls, legacyUrls] = await Promise.all([
315
+ listTurtleResourcesRecursive(fetcher, `${getPodBaseUrl(webId)}/.data/approvals/`).catch(() => []),
316
+ listTurtleResources(fetcher, `${getPodBaseUrl(webId)}/.data/approvals/`).catch(() => []),
317
+ ]);
318
+ const urls = [...new Set([...currentUrls, ...legacyUrls])];
349
319
  const rows = [];
350
320
  for (const url of urls.filter((entry) => entry.endsWith('.ttl'))) {
351
321
  const turtle = await readTurtleResource(fetcher, url).catch(() => null);
352
322
  if (!turtle)
353
323
  continue;
354
- const predicates = parseManagedTurtleBlocks(turtle, url).get(url);
355
- if (!predicates)
356
- continue;
357
- const row = approvalRowFromPredicates(url, predicates);
358
- if (row)
359
- rows.push(row);
324
+ for (const [subject, predicates] of parseManagedTurtleBlocks(turtle, url)) {
325
+ const row = approvalRowFromPredicates(subject, predicates);
326
+ if (row)
327
+ rows.push(row);
328
+ }
360
329
  }
361
330
  return rows;
362
331
  }
363
332
  async function writeApprovalRow(webId, fetcher, row) {
364
- const url = buildApprovalResourceUrl(webId, row.id);
365
- await upsertManagedTurtleBlock(fetcher, url, {
366
- subject: url,
333
+ const createdAt = new Date(toIsoString(row.createdAt, new Date().toISOString()));
334
+ const documentUrl = buildApprovalDocumentUrl(webId, createdAt);
335
+ const subjectUrl = buildApprovalResourceUrl(webId, row.id, createdAt);
336
+ await upsertManagedTurtleBlock(fetcher, documentUrl, {
337
+ subject: subjectUrl,
367
338
  triples: [
368
- { predicate: RDF_TYPE, object: iri(UDFS_APPROVAL_REQUEST) },
369
- { predicate: UDFS_SESSION, object: iri(row.session) },
370
- { predicate: UDFS_TOOL_CALL_ID, object: literal(row.toolCallId) },
371
- { predicate: UDFS_TOOL_NAME, object: literal(row.toolName) },
372
- { predicate: ODRL_TARGET, object: iri(row.target) },
373
- { predicate: ODRL_ACTION, object: iri(row.action) },
374
- { predicate: UDFS_RISK, object: literal(row.risk) },
375
- { predicate: UDFS_STATUS, object: literal(row.status) },
376
- ...(row.assignedTo ? [{ predicate: UDFS_ASSIGNED_TO, object: iri(row.assignedTo) }] : []),
377
- ...(row.decisionBy ? [{ predicate: UDFS_DECISION_BY, object: iri(row.decisionBy) }] : []),
378
- ...(row.decisionRole ? [{ predicate: UDFS_DECISION_ROLE, object: literal(row.decisionRole) }] : []),
379
- ...(row.onBehalfOf ? [{ predicate: UDFS_ON_BEHALF_OF, object: iri(row.onBehalfOf) }] : []),
380
- ...(row.reason ? [{ predicate: UDFS_REASON, object: literal(row.reason) }] : []),
381
- ...(row.policyVersion ? [{ predicate: UDFS_POLICY_VERSION, object: literal(row.policyVersion) }] : []),
382
- { predicate: DCT_CREATED, object: literal(toIsoString(row.createdAt, new Date().toISOString())) },
383
- ...(row.resolvedAt ? [{ predicate: UDFS_RESOLVED_AT, object: literal(toIsoString(row.resolvedAt, new Date().toISOString())) }] : []),
339
+ { predicate: RDF_TYPE, object: iri(UDFS.ApprovalRequest) },
340
+ { predicate: ApprovalVocab.session, object: iri(row.session) },
341
+ { predicate: ApprovalVocab.toolCallId, object: literal(row.toolCallId) },
342
+ { predicate: ApprovalVocab.toolName, object: literal(row.toolName) },
343
+ { predicate: ApprovalVocab.target, object: iri(row.target) },
344
+ { predicate: ApprovalVocab.action, object: iri(row.action) },
345
+ { predicate: ApprovalVocab.risk, object: literal(row.risk) },
346
+ { predicate: ApprovalVocab.status, object: literal(row.status) },
347
+ ...(row.assignedTo ? [{ predicate: ApprovalVocab.assignedTo, object: iri(row.assignedTo) }] : []),
348
+ ...(row.decisionBy ? [{ predicate: ApprovalVocab.decisionBy, object: iri(row.decisionBy) }] : []),
349
+ ...(row.decisionRole ? [{ predicate: ApprovalVocab.decisionRole, object: literal(row.decisionRole) }] : []),
350
+ ...(row.onBehalfOf ? [{ predicate: ApprovalVocab.onBehalfOf, object: iri(row.onBehalfOf) }] : []),
351
+ ...(row.reason ? [{ predicate: ApprovalVocab.reason, object: literal(row.reason) }] : []),
352
+ ...(row.policyVersion ? [{ predicate: ApprovalVocab.policyVersion, object: literal(row.policyVersion) }] : []),
353
+ { predicate: ApprovalVocab.createdAt, object: literal(toIsoString(row.createdAt, new Date().toISOString())) },
354
+ ...(row.resolvedAt ? [{ predicate: ApprovalVocab.resolvedAt, object: literal(toIsoString(row.resolvedAt, new Date().toISOString())) }] : []),
384
355
  ],
385
356
  });
386
357
  }
387
358
  async function listAuditRows(webId, fetcher) {
388
- const urls = await listTurtleResources(fetcher, `${getPodBaseUrl(webId)}/.data/audit/`);
359
+ const urls = await listTurtleResourcesRecursive(fetcher, `${getPodBaseUrl(webId)}/.data/audits/`);
389
360
  const rows = [];
390
361
  for (const url of urls.filter((entry) => entry.endsWith('.ttl'))) {
391
362
  const turtle = await readTurtleResource(fetcher, url).catch(() => null);
392
363
  if (!turtle)
393
364
  continue;
394
- const predicates = parseManagedTurtleBlocks(turtle, url).get(url);
395
- if (!predicates)
396
- continue;
397
- const row = auditRowFromPredicates(url, predicates);
398
- if (row)
399
- rows.push(row);
365
+ for (const [subject, predicates] of parseManagedTurtleBlocks(turtle, url)) {
366
+ const row = auditRowFromPredicates(subject, predicates);
367
+ if (row)
368
+ rows.push(row);
369
+ }
400
370
  }
401
371
  return rows;
402
372
  }
403
373
  async function writeAuditRow(webId, fetcher, row) {
404
- const url = buildAuditResourceUrl(webId, row.id);
405
- await upsertManagedTurtleBlock(fetcher, url, {
406
- subject: url,
374
+ const createdAt = new Date(toIsoString(row.createdAt, new Date().toISOString()));
375
+ const documentUrl = buildAuditDocumentUrl(webId, createdAt);
376
+ const subjectUrl = buildAuditResourceUrl(webId, row.id, createdAt);
377
+ await upsertManagedTurtleBlock(fetcher, documentUrl, {
378
+ subject: subjectUrl,
407
379
  triples: [
408
- { predicate: RDF_TYPE, object: iri(UDFS_AUDIT_ENTRY) },
409
- { predicate: UDFS_ACTION, object: literal(row.action) },
410
- { predicate: UDFS_ACTOR, object: iri(row.actor) },
411
- { predicate: UDFS_ACTOR_ROLE, object: literal(row.actorRole) },
412
- ...(row.onBehalfOf ? [{ predicate: UDFS_ON_BEHALF_OF, object: iri(row.onBehalfOf) }] : []),
413
- ...(row.session ? [{ predicate: UDFS_SESSION, object: iri(row.session) }] : []),
414
- ...(row.toolCallId ? [{ predicate: UDFS_TOOL_CALL_ID, object: literal(row.toolCallId) }] : []),
415
- ...(row.approval ? [{ predicate: 'https://undefineds.co/ns#approval', object: iri(row.approval) }] : []),
416
- ...(row.context ? [{ predicate: UDFS_CONTEXT, object: literal(row.context) }] : []),
417
- ...(row.policyVersion ? [{ predicate: UDFS_POLICY_VERSION, object: literal(row.policyVersion) }] : []),
418
- { predicate: DCT_CREATED, object: literal(toIsoString(row.createdAt, new Date().toISOString())) },
380
+ { predicate: RDF_TYPE, object: iri(UDFS.AuditEntry) },
381
+ { predicate: AuditVocab.action, object: literal(row.action) },
382
+ { predicate: AuditVocab.actor, object: iri(row.actor) },
383
+ { predicate: AuditVocab.actorRole, object: literal(row.actorRole) },
384
+ ...(row.onBehalfOf ? [{ predicate: AuditVocab.onBehalfOf, object: iri(row.onBehalfOf) }] : []),
385
+ ...(row.session ? [{ predicate: AuditVocab.session, object: iri(row.session) }] : []),
386
+ ...(row.entry ? [{ predicate: AuditVocab.entry, object: iri(row.entry) }] : []),
387
+ ...(row.toolCallId ? [{ predicate: AuditVocab.toolCallId, object: literal(row.toolCallId) }] : []),
388
+ ...(row.toolName ? [{ predicate: AuditVocab.toolName, object: literal(row.toolName) }] : []),
389
+ ...(row.approval ? [{ predicate: AuditVocab.approval, object: iri(row.approval) }] : []),
390
+ ...(row.policyVersion ? [{ predicate: AuditVocab.policyVersion, object: literal(row.policyVersion) }] : []),
391
+ { predicate: AuditVocab.createdAt, object: literal(toIsoString(row.createdAt, new Date().toISOString())) },
419
392
  ],
420
393
  });
421
394
  }
422
395
  async function listGrantRows(webId, fetcher) {
423
- const urls = await listTurtleResources(fetcher, `${getPodBaseUrl(webId)}/settings/autonomy/grants/`);
396
+ const urls = [
397
+ `${getPodBaseUrl(webId)}/settings/autonomy/grants.ttl`,
398
+ ...await listTurtleResources(fetcher, `${getPodBaseUrl(webId)}/settings/autonomy/grants/`).catch(() => []),
399
+ ];
424
400
  const rows = [];
425
401
  for (const url of urls.filter((entry) => entry.endsWith('.ttl'))) {
426
402
  const turtle = await readTurtleResource(fetcher, url).catch(() => null);
427
403
  if (!turtle)
428
404
  continue;
429
- const predicates = parseManagedTurtleBlocks(turtle, url).get(url);
430
- if (!predicates)
431
- continue;
432
- const row = grantRowFromPredicates(url, predicates);
433
- if (row)
434
- rows.push(row);
405
+ for (const [subject, predicates] of parseManagedTurtleBlocks(turtle, url)) {
406
+ const row = grantRowFromPredicates(subject, predicates);
407
+ if (row)
408
+ rows.push(row);
409
+ }
435
410
  }
436
411
  return rows;
437
412
  }
438
413
  async function writeGrantRow(webId, fetcher, row) {
439
414
  const id = normalizeString(row.id) ?? crypto.randomUUID();
440
- const url = buildGrantResourceUrl(webId, id);
415
+ const documentUrl = buildGrantDocumentUrl(webId);
416
+ const subjectUrl = buildGrantResourceUrl(webId, id);
441
417
  const target = normalizeString(row.target);
442
418
  const action = normalizeString(row.action);
443
419
  const effect = normalizeString(row.effect);
@@ -446,20 +422,20 @@ async function writeGrantRow(webId, fetcher, row) {
446
422
  if (!target || !action || !effect || !decisionBy || !decisionRole) {
447
423
  throw new Error(`Invalid remote approval grant row: ${id}`);
448
424
  }
449
- await upsertManagedTurtleBlock(fetcher, url, {
450
- subject: url,
425
+ await upsertManagedTurtleBlock(fetcher, documentUrl, {
426
+ subject: subjectUrl,
451
427
  triples: [
452
- { predicate: RDF_TYPE, object: iri(ODRL_POLICY) },
453
- { predicate: RDF_TYPE, object: iri(UDFS_AUTONOMY_GRANT) },
454
- { predicate: ODRL_TARGET, object: iri(target) },
455
- { predicate: ODRL_ACTION, object: iri(action) },
456
- { predicate: UDFS_EFFECT, object: literal(effect) },
457
- ...(normalizeString(row.riskCeiling) ? [{ predicate: UDFS_RISK_CEILING, object: literal(normalizeString(row.riskCeiling)) }] : []),
458
- { predicate: UDFS_DECISION_BY, object: iri(decisionBy) },
459
- { predicate: UDFS_DECISION_ROLE, object: literal(decisionRole) },
460
- ...(normalizeString(row.onBehalfOf) ? [{ predicate: UDFS_ON_BEHALF_OF, object: iri(normalizeString(row.onBehalfOf)) }] : []),
461
- { predicate: DCT_CREATED, object: literal(toIsoString(row.createdAt, new Date().toISOString())) },
462
- ...(normalizeString(row.revokedAt) ? [{ predicate: UDFS_REVOKED_AT, object: literal(normalizeString(row.revokedAt)) }] : []),
428
+ { predicate: RDF_TYPE, object: iri(ODRL.Policy) },
429
+ { predicate: RDF_TYPE, object: iri(UDFS.AutonomyGrant) },
430
+ { predicate: GrantVocab.target, object: iri(target) },
431
+ { predicate: GrantVocab.action, object: iri(action) },
432
+ { predicate: GrantVocab.effect, object: literal(effect) },
433
+ ...(normalizeString(row.riskCeiling) ? [{ predicate: GrantVocab.riskCeiling, object: literal(normalizeString(row.riskCeiling)) }] : []),
434
+ { predicate: GrantVocab.decisionBy, object: iri(decisionBy) },
435
+ { predicate: GrantVocab.decisionRole, object: literal(decisionRole) },
436
+ ...(normalizeString(row.onBehalfOf) ? [{ predicate: GrantVocab.onBehalfOf, object: iri(normalizeString(row.onBehalfOf)) }] : []),
437
+ { predicate: GrantVocab.createdAt, object: literal(toIsoString(row.createdAt, new Date().toISOString())) },
438
+ ...(normalizeString(row.revokedAt) ? [{ predicate: GrantVocab.revokedAt, object: literal(normalizeString(row.revokedAt)) }] : []),
463
439
  ],
464
440
  });
465
441
  }
@@ -468,22 +444,22 @@ async function writeInboxNotificationRow(webId, fetcher, row) {
468
444
  await upsertManagedTurtleBlock(fetcher, url, {
469
445
  subject: url,
470
446
  triples: [
471
- { predicate: RDF_TYPE, object: iri(AS_ANNOUNCE) },
472
- ...(row.actor ? [{ predicate: AS_ACTOR, object: iri(row.actor) }] : []),
473
- { predicate: AS_OBJECT, object: iri(row.object) },
474
- { predicate: DCT_CREATED, object: literal(toIsoString(row.createdAt, new Date().toISOString())) },
447
+ { predicate: RDF_TYPE, object: iri(AS.Announce) },
448
+ ...(row.actor ? [{ predicate: InboxNotificationVocab.actor, object: iri(row.actor) }] : []),
449
+ { predicate: InboxNotificationVocab.object, object: iri(row.object) },
450
+ { predicate: InboxNotificationVocab.createdAt, object: literal(toIsoString(row.createdAt, new Date().toISOString())) },
475
451
  ],
476
452
  });
477
453
  }
478
454
  function approvalRowFromPredicates(url, predicates) {
479
- const session = firstIri(predicates, UDFS_SESSION);
480
- const toolCallId = firstLiteral(predicates, UDFS_TOOL_CALL_ID);
481
- const toolName = firstLiteral(predicates, UDFS_TOOL_NAME);
482
- const target = firstIri(predicates, ODRL_TARGET);
483
- const action = firstIri(predicates, ODRL_ACTION);
484
- const risk = firstLiteral(predicates, UDFS_RISK);
485
- const status = firstLiteral(predicates, UDFS_STATUS);
486
- const createdAt = firstLiteral(predicates, DCT_CREATED);
455
+ const session = firstIri(predicates, ApprovalVocab.session);
456
+ const toolCallId = firstLiteral(predicates, ApprovalVocab.toolCallId);
457
+ const toolName = firstLiteral(predicates, ApprovalVocab.toolName);
458
+ const target = firstIri(predicates, ApprovalVocab.target);
459
+ const action = firstIri(predicates, ApprovalVocab.action);
460
+ const risk = firstLiteral(predicates, ApprovalVocab.risk);
461
+ const status = firstLiteral(predicates, ApprovalVocab.status);
462
+ const createdAt = firstLiteral(predicates, ApprovalVocab.createdAt);
487
463
  if (!session || !toolCallId || !toolName || !target || !action || !risk || !status || !createdAt) {
488
464
  return null;
489
465
  }
@@ -496,21 +472,21 @@ function approvalRowFromPredicates(url, predicates) {
496
472
  action,
497
473
  risk,
498
474
  status,
499
- assignedTo: firstIri(predicates, UDFS_ASSIGNED_TO),
500
- decisionBy: firstIri(predicates, UDFS_DECISION_BY),
501
- decisionRole: firstLiteral(predicates, UDFS_DECISION_ROLE),
502
- onBehalfOf: firstIri(predicates, UDFS_ON_BEHALF_OF),
503
- reason: firstLiteral(predicates, UDFS_REASON),
504
- policyVersion: firstLiteral(predicates, UDFS_POLICY_VERSION),
475
+ assignedTo: firstIri(predicates, ApprovalVocab.assignedTo),
476
+ decisionBy: firstIri(predicates, ApprovalVocab.decisionBy),
477
+ decisionRole: firstLiteral(predicates, ApprovalVocab.decisionRole),
478
+ onBehalfOf: firstIri(predicates, ApprovalVocab.onBehalfOf),
479
+ reason: firstLiteral(predicates, ApprovalVocab.reason),
480
+ policyVersion: firstLiteral(predicates, ApprovalVocab.policyVersion),
505
481
  createdAt,
506
- resolvedAt: firstLiteral(predicates, UDFS_RESOLVED_AT),
482
+ resolvedAt: firstLiteral(predicates, ApprovalVocab.resolvedAt),
507
483
  };
508
484
  }
509
485
  function auditRowFromPredicates(url, predicates) {
510
- const action = firstLiteral(predicates, UDFS_ACTION);
511
- const actor = firstIri(predicates, UDFS_ACTOR);
512
- const actorRole = firstLiteral(predicates, UDFS_ACTOR_ROLE);
513
- const createdAt = firstLiteral(predicates, DCT_CREATED);
486
+ const action = firstLiteral(predicates, AuditVocab.action);
487
+ const actor = firstIri(predicates, AuditVocab.actor);
488
+ const actorRole = firstLiteral(predicates, AuditVocab.actorRole);
489
+ const createdAt = firstLiteral(predicates, AuditVocab.createdAt);
514
490
  if (!action || !actor || !actorRole || !createdAt) {
515
491
  return null;
516
492
  }
@@ -519,22 +495,23 @@ function auditRowFromPredicates(url, predicates) {
519
495
  action,
520
496
  actor,
521
497
  actorRole,
522
- onBehalfOf: firstIri(predicates, UDFS_ON_BEHALF_OF),
523
- session: firstIri(predicates, UDFS_SESSION),
524
- toolCallId: firstLiteral(predicates, UDFS_TOOL_CALL_ID),
525
- approval: firstIri(predicates, 'https://undefineds.co/ns#approval'),
526
- context: firstLiteral(predicates, UDFS_CONTEXT),
527
- policyVersion: firstLiteral(predicates, UDFS_POLICY_VERSION),
498
+ onBehalfOf: firstIri(predicates, AuditVocab.onBehalfOf),
499
+ session: firstIri(predicates, AuditVocab.session),
500
+ entry: firstIri(predicates, AuditVocab.entry),
501
+ toolCallId: firstLiteral(predicates, AuditVocab.toolCallId),
502
+ toolName: firstLiteral(predicates, AuditVocab.toolName),
503
+ approval: firstIri(predicates, AuditVocab.approval),
504
+ policyVersion: firstLiteral(predicates, AuditVocab.policyVersion),
528
505
  createdAt,
529
506
  };
530
507
  }
531
508
  function grantRowFromPredicates(url, predicates) {
532
- const target = firstIri(predicates, ODRL_TARGET);
533
- const action = firstIri(predicates, ODRL_ACTION);
534
- const effect = firstLiteral(predicates, UDFS_EFFECT);
535
- const decisionBy = firstIri(predicates, UDFS_DECISION_BY);
536
- const decisionRole = firstLiteral(predicates, UDFS_DECISION_ROLE);
537
- const createdAt = firstLiteral(predicates, DCT_CREATED);
509
+ const target = firstIri(predicates, GrantVocab.target);
510
+ const action = firstIri(predicates, GrantVocab.action);
511
+ const effect = firstLiteral(predicates, GrantVocab.effect);
512
+ const decisionBy = firstIri(predicates, GrantVocab.decisionBy);
513
+ const decisionRole = firstLiteral(predicates, GrantVocab.decisionRole);
514
+ const createdAt = firstLiteral(predicates, GrantVocab.createdAt);
538
515
  if (!target || !action || !effect || !decisionBy || !decisionRole || !createdAt) {
539
516
  return null;
540
517
  }
@@ -543,12 +520,12 @@ function grantRowFromPredicates(url, predicates) {
543
520
  target,
544
521
  action,
545
522
  effect,
546
- riskCeiling: firstLiteral(predicates, UDFS_RISK_CEILING),
523
+ riskCeiling: firstLiteral(predicates, GrantVocab.riskCeiling),
547
524
  decisionBy,
548
525
  decisionRole,
549
- onBehalfOf: firstIri(predicates, UDFS_ON_BEHALF_OF),
526
+ onBehalfOf: firstIri(predicates, GrantVocab.onBehalfOf),
550
527
  createdAt,
551
- revokedAt: firstLiteral(predicates, UDFS_REVOKED_AT),
528
+ revokedAt: firstLiteral(predicates, GrantVocab.revokedAt),
552
529
  };
553
530
  }
554
531
  export async function createRemoteWatchApproval(options) {
@@ -568,7 +545,7 @@ export async function createRemoteWatchApproval(options) {
568
545
  risk: buildRisk(options.request),
569
546
  ...(options.request.kind === 'command-approval' && options.request.command ? { command: options.request.command } : {}),
570
547
  ...(options.request.kind === 'command-approval' && options.request.cwd ? { cwd: options.request.cwd } : {}),
571
- context: buildRequestAuditContext(options.record, options.request),
548
+ entry: sessionUri,
572
549
  }),
573
550
  runtime: activeRuntime,
574
551
  });
@@ -585,19 +562,12 @@ export async function createRemoteApproval(options) {
585
562
  const approvalId = crypto.randomUUID();
586
563
  const now = activeRuntime.now();
587
564
  const sessionUri = subject.sessionUri;
588
- const approvalUri = buildApprovalUri(webId, approvalId);
565
+ const approvalUri = buildApprovalUriForDate(webId, approvalId, now);
589
566
  const targetUri = subject.targetUri ?? sessionUri;
590
567
  const assignedTo = subject.assignedTo ?? webId;
591
568
  const onBehalfOf = subject.onBehalfOf ?? webId;
592
569
  const policyVersion = subject.policyVersion ?? REMOTE_APPROVAL_POLICY_VERSION;
593
- const requestContext = request.context ?? {
594
- kind: request.kind,
595
- message: request.message,
596
- sessionId: extractSessionId(sessionUri),
597
- toolName: request.toolName,
598
- ...(request.command ? { command: request.command } : {}),
599
- ...(request.cwd ? { cwd: request.cwd } : {}),
600
- };
570
+ const requestEntry = request.entry ?? approvalUri;
601
571
  await store.insertApproval({
602
572
  id: approvalId,
603
573
  session: sessionUri,
@@ -611,25 +581,27 @@ export async function createRemoteApproval(options) {
611
581
  policyVersion,
612
582
  createdAt: now,
613
583
  });
614
- await store.insertAudit({
584
+ const requestAudit = {
615
585
  id: crypto.randomUUID(),
616
586
  action: 'approval_requested',
617
587
  actor: subject.actorUri,
618
588
  actorRole: 'secretary',
619
589
  onBehalfOf,
620
590
  session: sessionUri,
591
+ entry: requestEntry,
621
592
  toolCallId: request.toolCallId,
593
+ toolName: request.toolName,
622
594
  approval: approvalUri,
623
- context: JSON.stringify(requestContext),
624
595
  policyVersion,
625
596
  createdAt: now,
626
- });
627
- await store.insertInboxNotification({
597
+ };
598
+ await warnOnly(activeRuntime, () => store.insertAudit(requestAudit));
599
+ await warnOnly(activeRuntime, () => store.insertInboxNotification({
628
600
  id: crypto.randomUUID(),
629
601
  actor: subject.actorUri,
630
602
  object: approvalUri,
631
603
  createdAt: now,
632
- }).catch(() => undefined);
604
+ }));
633
605
  return normalizeApprovalSummary({
634
606
  id: approvalId,
635
607
  session: sessionUri,
@@ -642,19 +614,7 @@ export async function createRemoteApproval(options) {
642
614
  assignedTo,
643
615
  policyVersion,
644
616
  createdAt: now,
645
- }, [{
646
- id: crypto.randomUUID(),
647
- action: 'approval_requested',
648
- actor: subject.actorUri,
649
- actorRole: 'secretary',
650
- onBehalfOf,
651
- session: sessionUri,
652
- toolCallId: request.toolCallId,
653
- approval: approvalUri,
654
- context: JSON.stringify(requestContext),
655
- policyVersion,
656
- createdAt: now,
657
- }]);
617
+ });
658
618
  });
659
619
  }
660
620
  export async function waitForRemoteWatchApproval(options) {
@@ -741,12 +701,9 @@ export async function listRemoteWatchApprovals(options = {}) {
741
701
  const activeRuntime = options.runtime ?? await createDefaultRuntime();
742
702
  const requestedStatus = options.status ?? 'pending';
743
703
  return withRemoteApprovalStore(activeRuntime, async ({ store, webId }) => {
744
- const [approvals, audits] = await Promise.all([
745
- store.listApprovals(),
746
- store.listAudits(),
747
- ]);
704
+ const approvals = await store.listApprovals();
748
705
  return approvals
749
- .map((row) => normalizeApprovalSummary(row, audits))
706
+ .map((row) => normalizeApprovalSummary(row))
750
707
  .filter((summary) => !summary.assignedTo || summary.assignedTo === webId)
751
708
  .filter((summary) => requestedStatus === 'all' || summary.status === requestedStatus)
752
709
  .sort((left, right) => right.createdAt.localeCompare(left.createdAt));
@@ -761,11 +718,11 @@ export async function resolveRemoteWatchApproval(options) {
761
718
  throw new Error(`Remote approval not found: ${options.approvalId}`);
762
719
  }
763
720
  if (row.status !== 'pending') {
764
- const audits = await store.listAudits();
765
- return normalizeApprovalSummary(row, audits);
721
+ return normalizeApprovalSummary(row);
766
722
  }
767
723
  const now = activeRuntime.now();
768
- const approvalUri = buildApprovalUri(row.session, row.id);
724
+ const approvalCreatedAt = new Date(toIsoString(row.createdAt, now.toISOString()));
725
+ const approvalUri = buildApprovalUriForDate(row.session, row.id, approvalCreatedAt);
769
726
  const nextStatus = options.decision === 'accept' || options.decision === 'accept_for_session'
770
727
  ? 'approved'
771
728
  : 'rejected';
@@ -777,22 +734,20 @@ export async function resolveRemoteWatchApproval(options) {
777
734
  reason: encodeDecisionReason(options.decision, options.note),
778
735
  resolvedAt: now,
779
736
  });
780
- await store.insertAudit({
737
+ await warnOnly(activeRuntime, () => store.insertAudit({
781
738
  id: crypto.randomUUID(),
782
739
  action: nextStatus === 'approved' ? 'approval_approved' : 'approval_rejected',
783
740
  actor: webId,
784
741
  actorRole: 'human',
785
742
  onBehalfOf: webId,
786
743
  session: row.session,
744
+ entry: approvalUri,
787
745
  toolCallId: row.toolCallId,
746
+ toolName: row.toolName,
788
747
  approval: approvalUri,
789
- context: JSON.stringify({
790
- decision: options.decision,
791
- ...(options.note?.trim() ? { note: options.note.trim() } : {}),
792
- }),
793
748
  policyVersion: REMOTE_APPROVAL_POLICY_VERSION,
794
749
  createdAt: now,
795
- });
750
+ }));
796
751
  if (options.decision === 'accept_for_session') {
797
752
  const grantId = crypto.randomUUID();
798
753
  await store.insertGrant({
@@ -806,19 +761,19 @@ export async function resolveRemoteWatchApproval(options) {
806
761
  onBehalfOf: webId,
807
762
  createdAt: now,
808
763
  });
809
- await store.insertInboxNotification({
764
+ await warnOnly(activeRuntime, () => store.insertInboxNotification({
810
765
  id: crypto.randomUUID(),
811
766
  actor: webId,
812
767
  object: buildGrantUri(row.session, grantId),
813
768
  createdAt: now,
814
- }).catch(() => undefined);
769
+ }));
815
770
  }
816
- await store.insertInboxNotification({
771
+ await warnOnly(activeRuntime, () => store.insertInboxNotification({
817
772
  id: crypto.randomUUID(),
818
773
  actor: webId,
819
774
  object: approvalUri,
820
775
  createdAt: now,
821
- }).catch(() => undefined);
776
+ }));
822
777
  const nextRow = {
823
778
  ...row,
824
779
  status: nextStatus,
@@ -828,15 +783,13 @@ export async function resolveRemoteWatchApproval(options) {
828
783
  reason: encodeDecisionReason(options.decision, options.note),
829
784
  resolvedAt: now,
830
785
  };
831
- const audits = await store.listAudits();
832
- return normalizeApprovalSummary(nextRow, audits);
786
+ return normalizeApprovalSummary(nextRow);
833
787
  });
834
788
  }
835
789
  export const __podApprovalInternal = {
836
790
  createAbortError,
837
791
  createDefaultRuntime,
838
792
  buildActionUri,
839
- buildRequestAuditContext,
840
793
  buildRisk,
841
794
  buildToolName,
842
795
  createNativeRemoteApprovalStore,
@@ -847,6 +800,5 @@ export const __podApprovalInternal = {
847
800
  isRemoteApprovalAbortError,
848
801
  normalizeApprovalSummary,
849
802
  parseDecisionReason,
850
- parseRequestAuditContext,
851
803
  };
852
804
  //# sourceMappingURL=pod-approval.js.map