@undefineds.co/linx 0.2.16 → 0.2.18

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.
Files changed (42) hide show
  1. package/dist/generated/version.js +3 -0
  2. package/dist/generated/version.js.map +1 -0
  3. package/dist/index.js +10 -17
  4. package/dist/index.js.map +1 -1
  5. package/dist/lib/ai-command.js +169 -301
  6. package/dist/lib/ai-command.js.map +1 -1
  7. package/dist/lib/login-command.js +30 -22
  8. package/dist/lib/login-command.js.map +1 -1
  9. package/dist/lib/models.js +2 -2
  10. package/dist/lib/models.js.map +1 -1
  11. package/dist/lib/oidc-auth.js +59 -12
  12. package/dist/lib/oidc-auth.js.map +1 -1
  13. package/dist/lib/pi-adapter/auth.js +36 -7
  14. package/dist/lib/pi-adapter/auth.js.map +1 -1
  15. package/dist/lib/pi-adapter/branding.js +215 -5
  16. package/dist/lib/pi-adapter/branding.js.map +1 -1
  17. package/dist/lib/pi-adapter/interactive.js +25 -1
  18. package/dist/lib/pi-adapter/interactive.js.map +1 -1
  19. package/dist/lib/pi-adapter/pod-mirror.js +219 -137
  20. package/dist/lib/pi-adapter/pod-mirror.js.map +1 -1
  21. package/dist/lib/pi-adapter/pod-tools.js +104 -0
  22. package/dist/lib/pi-adapter/pod-tools.js.map +1 -0
  23. package/dist/lib/pi-adapter/runtime.js +207 -23
  24. package/dist/lib/pi-adapter/runtime.js.map +1 -1
  25. package/dist/lib/pi-adapter/session.js +63 -182
  26. package/dist/lib/pi-adapter/session.js.map +1 -1
  27. package/dist/lib/pi-adapter/web-fetch.js +154 -0
  28. package/dist/lib/pi-adapter/web-fetch.js.map +1 -0
  29. package/dist/lib/pod-chat-store.js +40 -30
  30. package/dist/lib/pod-chat-store.js.map +1 -1
  31. package/dist/lib/pod-data-session.js.map +1 -1
  32. package/dist/lib/watch/pod-approval.js +275 -303
  33. package/dist/lib/watch/pod-approval.js.map +1 -1
  34. package/dist/lib/watch/pod-persistence.js +37 -35
  35. package/dist/lib/watch/pod-persistence.js.map +1 -1
  36. package/dist/skills/drizzle-solid/SKILL.md +340 -0
  37. package/dist/skills/pod-storage/SKILL.md +60 -0
  38. package/dist/skills/solid-modeling/SKILL.md +274 -0
  39. package/dist/skills/xpod-componentsjs/SKILL.md +284 -0
  40. package/package.json +2 -2
  41. package/dist/lib/pi-adapter/pod-native.js +0 -478
  42. package/dist/lib/pi-adapter/pod-native.js.map +0 -1
@@ -1,15 +1,12 @@
1
1
  import { setTimeout as delay } from 'node:timers/promises';
2
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';
3
+ import { approvalResource, auditResource, drizzle, grantResource, inboxNotificationTable, initSolidTables, solidSchema, } from '../models.js';
5
4
  import { resolveWatchGrantCoverage } from './secretary.js';
6
- 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';
7
5
  const WATCH_CHAT_ID_PREFIX = 'linx-watch';
8
6
  const WATCH_AGENT_ID = 'linx-watch-assistant';
9
7
  const REMOTE_APPROVAL_POLICY_VERSION = 'linx-watch-remote-approval/v1';
10
8
  const DEFAULT_REMOTE_APPROVAL_POLL_MS = 1000;
11
9
  const DEFAULT_WARN_ONLY_TIMEOUT_MS = 5000;
12
- const DEFAULT_APPROVAL_LIST_DAYS = 7;
13
10
  const MAX_GRANT_POLICY_LENGTH = 1200;
14
11
  const MAX_APPROVAL_CONTEXT_LENGTH = 1400;
15
12
  const MIN_GRANT_COVERAGE_CONFIDENCE = 0.75;
@@ -51,17 +48,8 @@ function buildWatchChatId(record) {
51
48
  function buildThreadUri(webId, record) {
52
49
  return `${getPodBaseUrl(webId)}/.data/chat/${buildWatchChatId(record)}/index.ttl#${record.id}`;
53
50
  }
54
- function buildApprovalUri(webIdOrUri, approvalId) {
55
- return buildApprovalResourceUrl(webIdOrUri, approvalId);
56
- }
57
- function buildApprovalUriForDate(webIdOrUri, approvalId, createdAt) {
58
- return buildApprovalResourceUrl(webIdOrUri, approvalId, createdAt);
59
- }
60
- function documentUrlFromResourceUri(resourceUri) {
61
- return resourceUri.split('#', 1)[0] ?? resourceUri;
62
- }
63
- function buildGrantUri(webIdOrUri, grantId) {
64
- return buildGrantResourceUrl(webIdOrUri, grantId);
51
+ function isAbsoluteIri(value) {
52
+ return value.startsWith('http://') || value.startsWith('https://');
65
53
  }
66
54
  function buildGrantSchemaUri(webIdOrUri) {
67
55
  return `${getPodBaseUrl(webIdOrUri)}/settings/autonomy/schema/grant.ttl#GrantWikiPage`;
@@ -265,7 +253,7 @@ function grantWikiTagsFromApproval(row, explicitTags) {
265
253
  }
266
254
  function grantContextFromApproval(row) {
267
255
  return safeCompactJson({
268
- sourceApproval: buildApprovalUriForDate(row.session, row.id, new Date(toIsoString(row.createdAt, new Date().toISOString()))),
256
+ sourceApproval: row.approvalUri ?? row.id,
269
257
  session: row.session,
270
258
  toolCallId: row.toolCallId,
271
259
  toolName: row.toolName,
@@ -275,16 +263,6 @@ function grantContextFromApproval(row) {
275
263
  approvalContext: row.context,
276
264
  }, MAX_APPROVAL_CONTEXT_LENGTH);
277
265
  }
278
- function literalValues(predicates, predicate) {
279
- return (predicates.get(predicate) ?? [])
280
- .map((object) => isRecord(object) && object.type === 'literal' && typeof object.value === 'string' ? object.value : '')
281
- .filter(Boolean);
282
- }
283
- function iriValues(predicates, predicate) {
284
- return (predicates.get(predicate) ?? [])
285
- .map((object) => isRecord(object) && object.type === 'iri' && typeof object.value === 'string' ? object.value : '')
286
- .filter(Boolean);
287
- }
288
266
  function grantSourceHash(row) {
289
267
  return `approval:${row.id}:${row.toolCallId}:${row.risk}`;
290
268
  }
@@ -426,8 +404,8 @@ function missingRemoteApprovalCredentialsMessage() {
426
404
  async function createDefaultRuntime() {
427
405
  return {
428
406
  getPodDataSession: getDefaultPodDataSession,
429
- createStore(webId, fetcher) {
430
- return createNativeRemoteApprovalStore(webId, fetcher);
407
+ createStore(session, db) {
408
+ return createNativeRemoteApprovalStore(session.webId, db);
431
409
  },
432
410
  sleep(ms) {
433
411
  return delay(ms);
@@ -472,170 +450,112 @@ async function createRemoteApprovalClient(runtime) {
472
450
  if (!session) {
473
451
  return null;
474
452
  }
453
+ const db = createRemoteApprovalDb(session);
454
+ await initSolidTables(db, [
455
+ approvalResource,
456
+ auditResource,
457
+ grantResource,
458
+ inboxNotificationTable,
459
+ ]);
475
460
  return {
476
461
  session,
477
- store: runtime.createStore(session.webId, session.fetch),
462
+ store: runtime.createStore(session, db),
478
463
  };
479
464
  }
480
- function createNativeRemoteApprovalStore(webId, fetcher) {
465
+ function createRemoteApprovalDb(session) {
466
+ return drizzle(session.solidSession, {
467
+ logger: false,
468
+ disableInteropDiscovery: true,
469
+ schema: solidSchema,
470
+ });
471
+ }
472
+ function createNativeRemoteApprovalStore(_webId, db) {
481
473
  return {
482
- listApprovals: () => listApprovalRows(webId, fetcher),
483
- findApproval: (id, options) => findApprovalRow(webId, fetcher, id, options),
484
- insertApproval: (row) => writeApprovalRow(webId, fetcher, row),
474
+ listApprovals: () => listApprovalRows(db),
475
+ findApproval: (id, options) => findApprovalRow(db, id, options),
476
+ resolveApprovalReference: (locator) => resolveResourceReference(db, approvalResource, locator),
477
+ insertApproval: (row) => writeApprovalRow(db, row),
485
478
  async updateApproval(id, patch) {
486
- const existing = (await listApprovalRows(webId, fetcher)).find((row) => row.id === id);
487
- if (!existing) {
479
+ const data = normalizeApprovalUpdate(patch);
480
+ const approvalUri = normalizeString(patch.approvalUri);
481
+ const targetIri = approvalUri ?? (isAbsoluteIri(id) ? id : undefined);
482
+ if (targetIri) {
483
+ const updateByIri = db.updateByIri;
484
+ if (typeof updateByIri !== 'function') {
485
+ throw new Error('Solid database does not support updateByIri');
486
+ }
487
+ const updated = await updateByIri.call(db, approvalResource, targetIri, data);
488
+ if (!updated) {
489
+ throw new Error(`Remote approval not found: ${id}`);
490
+ }
491
+ return;
492
+ }
493
+ const updateByLocator = db.updateByLocator;
494
+ if (typeof updateByLocator !== 'function') {
495
+ throw new Error('Solid database does not support updateByLocator');
496
+ }
497
+ const updated = await updateByLocator.call(db, approvalResource, { id }, data);
498
+ if (!updated) {
488
499
  throw new Error(`Remote approval not found: ${id}`);
489
500
  }
490
- await writeApprovalRow(webId, fetcher, { ...existing, ...patch });
491
501
  },
492
- listAudits: () => listAuditRows(webId, fetcher),
493
- insertAudit: (row) => writeAuditRow(webId, fetcher, row),
494
- listGrants: () => listGrantRows(webId, fetcher),
495
- insertGrant: (row) => writeGrantRow(webId, fetcher, row),
496
- insertInboxNotification: (row) => writeInboxNotificationRow(webId, fetcher, row),
502
+ listAudits: () => listAuditRows(db),
503
+ insertAudit: (row) => writeAuditRow(db, row),
504
+ listGrants: () => listGrantRows(db),
505
+ resolveGrantReference: (locator) => resolveResourceReference(db, grantResource, locator),
506
+ insertGrant: (row) => writeGrantRow(db, row),
507
+ insertInboxNotification: (row) => writeInboxNotificationRow(db, row),
497
508
  };
498
509
  }
499
- async function findApprovalRow(webId, fetcher, id, options = {}) {
500
- if (options.resourceUri) {
501
- return readApprovalRowFromResource(fetcher, options.resourceUri);
502
- }
503
- if (options.createdAt) {
504
- const createdAt = new Date(toIsoString(options.createdAt, new Date().toISOString()));
505
- return readApprovalRowFromResource(fetcher, buildApprovalResourceUrl(webId, id, createdAt));
506
- }
507
- return (await listApprovalRows(webId, fetcher)).find((row) => row.id === id) ?? null;
508
- }
509
- async function readApprovalRowFromResource(fetcher, resourceUri) {
510
- const turtle = await readTurtleResource(fetcher, documentUrlFromResourceUri(resourceUri));
511
- if (!turtle) {
512
- return null;
513
- }
514
- for (const [subject, predicates] of parseManagedTurtleBlocks(turtle, documentUrlFromResourceUri(resourceUri))) {
515
- if (subject !== resourceUri) {
516
- continue;
517
- }
518
- const row = approvalRowFromPredicates(subject, predicates);
519
- if (row) {
520
- return row;
521
- }
522
- }
523
- return null;
524
- }
525
- async function listApprovalRows(webId, fetcher) {
526
- const urls = [
527
- ...recentApprovalDocumentUrls(webId),
528
- ...await listTurtleResources(fetcher, `${getPodBaseUrl(webId)}/.data/approvals/`).catch(() => []),
529
- ];
530
- const rows = [];
531
- for (const url of [...new Set(urls)].filter((entry) => entry.endsWith('.ttl'))) {
532
- const turtle = await readTurtleResource(fetcher, url).catch(() => null);
533
- if (!turtle)
534
- continue;
535
- for (const [subject, predicates] of parseManagedTurtleBlocks(turtle, url)) {
536
- const row = approvalRowFromPredicates(subject, predicates);
537
- if (row)
538
- rows.push(row);
510
+ async function findApprovalRow(db, id, options = {}) {
511
+ const approvalUri = normalizeString(options.approvalUri) ?? (isAbsoluteIri(id) ? id : undefined);
512
+ if (approvalUri) {
513
+ const findByIri = db.findByIri;
514
+ if (typeof findByIri !== 'function') {
515
+ throw new Error('Solid database does not support findByIri');
539
516
  }
517
+ const row = await findByIri.call(db, approvalResource, approvalUri);
518
+ return normalizeApprovalRow(row);
540
519
  }
541
- return rows;
542
- }
543
- function recentApprovalDocumentUrls(webId, days = DEFAULT_APPROVAL_LIST_DAYS) {
544
- const urls = [];
545
- const base = Date.now();
546
- for (let offset = 0; offset < days; offset += 1) {
547
- const date = new Date(base - offset * 24 * 60 * 60 * 1000);
548
- urls.push(buildApprovalDocumentUrl(webId, date));
520
+ const findByLocator = db.findByLocator;
521
+ if (typeof findByLocator !== 'function') {
522
+ throw new Error('Solid database does not support findByLocator');
549
523
  }
550
- return urls;
551
- }
552
- async function writeApprovalRow(webId, fetcher, row) {
553
- const createdAt = new Date(toIsoString(row.createdAt, new Date().toISOString()));
554
- const documentUrl = buildApprovalDocumentUrl(webId, createdAt);
555
- const subjectUrl = buildApprovalResourceUrl(webId, row.id, createdAt);
556
- await upsertManagedTurtleBlock(fetcher, documentUrl, {
557
- subject: subjectUrl,
558
- triples: [
559
- { predicate: RDF_TYPE, object: iri(UDFS.ApprovalRequest) },
560
- { predicate: ApprovalVocab.session, object: iri(row.session) },
561
- { predicate: ApprovalVocab.toolCallId, object: literal(row.toolCallId) },
562
- { predicate: ApprovalVocab.toolName, object: literal(row.toolName) },
563
- { predicate: ApprovalVocab.target, object: iri(row.target) },
564
- { predicate: ApprovalVocab.action, object: iri(row.action) },
565
- { predicate: ApprovalVocab.risk, object: literal(row.risk) },
566
- { predicate: ApprovalVocab.status, object: literal(row.status) },
567
- ...(row.assignedTo ? [{ predicate: ApprovalVocab.assignedTo, object: iri(row.assignedTo) }] : []),
568
- ...(row.decisionBy ? [{ predicate: ApprovalVocab.decisionBy, object: iri(row.decisionBy) }] : []),
569
- ...(row.decisionRole ? [{ predicate: ApprovalVocab.decisionRole, object: literal(row.decisionRole) }] : []),
570
- ...(row.onBehalfOf ? [{ predicate: ApprovalVocab.onBehalfOf, object: iri(row.onBehalfOf) }] : []),
571
- ...(row.reason ? [{ predicate: ApprovalVocab.reason, object: literal(row.reason) }] : []),
572
- ...(row.context ? [{ predicate: ApprovalVocab.context, object: literal(row.context) }] : []),
573
- ...(row.approvalOptions ? [{ predicate: ApprovalVocab.approvalOptions, object: literal(row.approvalOptions) }] : []),
574
- ...(row.policyVersion ? [{ predicate: ApprovalVocab.policyVersion, object: literal(row.policyVersion) }] : []),
575
- { predicate: ApprovalVocab.createdAt, object: literal(toIsoString(row.createdAt, new Date().toISOString())) },
576
- ...(row.expiresAt ? [{ predicate: ApprovalVocab.expiresAt, object: literal(toIsoString(row.expiresAt, new Date().toISOString())) }] : []),
577
- ...(row.resolvedAt ? [{ predicate: ApprovalVocab.resolvedAt, object: literal(toIsoString(row.resolvedAt, new Date().toISOString())) }] : []),
578
- ],
524
+ const row = await findByLocator.call(db, approvalResource, {
525
+ id,
526
+ ...(options.createdAt ? { createdAt: options.createdAt } : {}),
579
527
  });
528
+ return normalizeApprovalRow(row);
580
529
  }
581
- async function listAuditRows(webId, fetcher) {
582
- const urls = await listTurtleResourcesRecursive(fetcher, `${getPodBaseUrl(webId)}/.data/audits/`);
583
- const rows = [];
584
- for (const url of urls.filter((entry) => entry.endsWith('.ttl'))) {
585
- const turtle = await readTurtleResource(fetcher, url).catch(() => null);
586
- if (!turtle)
587
- continue;
588
- for (const [subject, predicates] of parseManagedTurtleBlocks(turtle, url)) {
589
- const row = auditRowFromPredicates(subject, predicates);
590
- if (row)
591
- rows.push(row);
592
- }
530
+ function resolveResourceReference(db, resource, locator) {
531
+ if (typeof db.resolveLocatorIri !== 'function' || typeof db.resolveLocatorId !== 'function') {
532
+ throw new Error('Solid database does not support locator reference resolution');
593
533
  }
594
- return rows;
595
- }
596
- async function writeAuditRow(webId, fetcher, row) {
597
- const createdAt = new Date(toIsoString(row.createdAt, new Date().toISOString()));
598
- const documentUrl = buildAuditDocumentUrl(webId, createdAt);
599
- const subjectUrl = buildAuditResourceUrl(webId, row.id, createdAt);
600
- await upsertManagedTurtleBlock(fetcher, documentUrl, {
601
- subject: subjectUrl,
602
- triples: [
603
- { predicate: RDF_TYPE, object: iri(UDFS.AuditEntry) },
604
- { predicate: AuditVocab.action, object: literal(row.action) },
605
- { predicate: AuditVocab.actor, object: iri(row.actor) },
606
- { predicate: AuditVocab.actorRole, object: literal(row.actorRole) },
607
- ...(row.onBehalfOf ? [{ predicate: AuditVocab.onBehalfOf, object: iri(row.onBehalfOf) }] : []),
608
- ...(row.session ? [{ predicate: AuditVocab.session, object: iri(row.session) }] : []),
609
- ...(row.entry ? [{ predicate: AuditVocab.entry, object: iri(row.entry) }] : []),
610
- ...(row.toolCallId ? [{ predicate: AuditVocab.toolCallId, object: literal(row.toolCallId) }] : []),
611
- ...(row.toolName ? [{ predicate: AuditVocab.toolName, object: literal(row.toolName) }] : []),
612
- ...(row.approval ? [{ predicate: AuditVocab.approval, object: iri(row.approval) }] : []),
613
- ...(row.policyVersion ? [{ predicate: AuditVocab.policyVersion, object: literal(row.policyVersion) }] : []),
614
- { predicate: AuditVocab.createdAt, object: literal(toIsoString(row.createdAt, new Date().toISOString())) },
615
- ],
616
- });
534
+ return {
535
+ id: db.resolveLocatorId(resource, locator),
536
+ iri: db.resolveLocatorIri(resource, locator),
537
+ };
617
538
  }
618
- async function listGrantRows(webId, fetcher) {
619
- const urls = [
620
- ...await listTurtleResources(fetcher, `${getPodBaseUrl(webId)}/settings/autonomy/grants/`).catch(() => []),
621
- ];
622
- const rows = [];
623
- for (const url of urls.filter((entry) => entry.endsWith('.ttl'))) {
624
- const turtle = await readTurtleResource(fetcher, url).catch(() => null);
625
- if (!turtle)
626
- continue;
627
- for (const [subject, predicates] of parseManagedTurtleBlocks(turtle, url)) {
628
- const row = grantRowFromPredicates(subject, predicates);
629
- if (row)
630
- rows.push(row);
631
- }
632
- }
633
- return rows;
539
+ async function listApprovalRows(db) {
540
+ const rows = await db.select().from(approvalResource).execute();
541
+ return rows.map((row) => normalizeApprovalRow(row)).filter((row) => row !== null);
634
542
  }
635
- async function writeGrantRow(webId, fetcher, row) {
543
+ async function writeApprovalRow(db, row) {
544
+ await db.insert(approvalResource).values(normalizeApprovalInsert(row)).execute();
545
+ }
546
+ async function listAuditRows(db) {
547
+ const rows = await db.select().from(auditResource).execute();
548
+ return rows.map((row) => normalizeAuditRow(row)).filter((row) => row !== null);
549
+ }
550
+ async function writeAuditRow(db, row) {
551
+ await db.insert(auditResource).values(normalizeAuditInsert(row)).execute();
552
+ }
553
+ async function listGrantRows(db) {
554
+ const rows = await db.select().from(grantResource).execute();
555
+ return rows.map((row) => normalizeGrantRow(row)).filter((row) => row !== null);
556
+ }
557
+ async function writeGrantRow(db, row) {
636
558
  const id = normalizeString(row.id) ?? crypto.randomUUID();
637
- const subjectUrl = buildGrantResourceUrl(webId, id);
638
- const documentUrl = subjectUrl;
639
559
  const target = normalizeString(row.target);
640
560
  const action = normalizeString(row.action);
641
561
  const effect = normalizeString(row.effect);
@@ -644,141 +564,190 @@ async function writeGrantRow(webId, fetcher, row) {
644
564
  if (!target || !action || !effect || !decisionBy || !decisionRole) {
645
565
  throw new Error(`Invalid remote approval grant row: ${id}`);
646
566
  }
647
- await upsertManagedTurtleBlock(fetcher, documentUrl, {
648
- subject: subjectUrl,
649
- triples: [
650
- { predicate: RDF_TYPE, object: iri(ODRL.Policy) },
651
- { predicate: RDF_TYPE, object: iri(UDFS.AutonomyGrant) },
652
- { predicate: GrantVocab.target, object: iri(target) },
653
- { predicate: GrantVocab.action, object: iri(action) },
654
- ...(normalizeString(row.title) ? [{ predicate: GrantVocab.title, object: literal(truncatePodLiteral(normalizeString(row.title), 160)) }] : []),
655
- ...(normalizeString(row.summary) ? [{ predicate: GrantVocab.summary, object: literal(truncatePodLiteral(normalizeString(row.summary), 500)) }] : []),
656
- ...(normalizeString(row.body) ? [{ predicate: GrantVocab.body, object: literal(truncatePodLiteral(normalizeString(row.body), MAX_GRANT_POLICY_LENGTH)) }] : []),
657
- ...(normalizeString(row.schema) ? [{ predicate: GrantVocab.schema, object: iri(normalizeString(row.schema)) }] : []),
658
- ...(normalizeString(row.pageKind) ? [{ predicate: GrantVocab.pageKind, object: literal(normalizeString(row.pageKind)) }] : []),
659
- ...(normalizeString(row.wikiStatus) ? [{ predicate: GrantVocab.wikiStatus, object: literal(normalizeString(row.wikiStatus)) }] : []),
660
- ...(normalizeString(row.tags) ? [{ predicate: GrantVocab.tags, object: literal(truncatePodLiteral(normalizeString(row.tags), 500)) }] : []),
661
- ...(normalizeString(row.source) ? [{ predicate: GrantVocab.source, object: literal(normalizeString(row.source)) }] : []),
662
- ...(normalizeString(row.sourceHash) ? [{ predicate: GrantVocab.sourceHash, object: literal(normalizeString(row.sourceHash)) }] : []),
663
- ...(row.compiledAt ? [{ predicate: GrantVocab.compiledAt, object: literal(toIsoString(row.compiledAt, new Date().toISOString())) }] : []),
664
- ...(row.compiledFrom ?? []).map((value) => ({ predicate: GrantVocab.compiledFrom, object: iri(value) })),
665
- ...(row.related ?? []).map((value) => ({ predicate: GrantVocab.related, object: iri(value) })),
666
- { predicate: GrantVocab.effect, object: literal(effect) },
667
- ...(normalizeString(row.riskCeiling) ? [{ predicate: GrantVocab.riskCeiling, object: literal(normalizeString(row.riskCeiling)) }] : []),
668
- ...(normalizeString(row.policy) ? [{ predicate: GrantVocab.policy, object: literal(truncatePodLiteral(normalizeString(row.policy), MAX_GRANT_POLICY_LENGTH)) }] : []),
669
- ...(normalizeString(row.context) ? [{ predicate: GrantVocab.context, object: literal(truncatePodLiteral(normalizeString(row.context), MAX_APPROVAL_CONTEXT_LENGTH)) }] : []),
670
- { predicate: GrantVocab.decisionBy, object: iri(decisionBy) },
671
- { predicate: GrantVocab.decisionRole, object: literal(decisionRole) },
672
- ...(normalizeString(row.onBehalfOf) ? [{ predicate: GrantVocab.onBehalfOf, object: iri(normalizeString(row.onBehalfOf)) }] : []),
673
- { predicate: GrantVocab.createdAt, object: literal(toIsoString(row.createdAt, new Date().toISOString())) },
674
- ...(normalizeString(row.revokedAt) ? [{ predicate: GrantVocab.revokedAt, object: literal(normalizeString(row.revokedAt)) }] : []),
675
- ],
676
- });
567
+ await db.insert(grantResource).values(normalizeGrantInsert({ ...row, id, target, action, effect, decisionBy, decisionRole })).execute();
677
568
  }
678
- async function writeInboxNotificationRow(webId, fetcher, row) {
679
- const url = buildInboxResourceUrl(webId, row.id);
680
- await upsertManagedTurtleBlock(fetcher, url, {
681
- subject: url,
682
- triples: [
683
- { predicate: RDF_TYPE, object: iri(AS.Announce) },
684
- ...(row.actor ? [{ predicate: InboxNotificationVocab.actor, object: iri(row.actor) }] : []),
685
- { predicate: InboxNotificationVocab.object, object: iri(row.object) },
686
- { predicate: InboxNotificationVocab.createdAt, object: literal(toIsoString(row.createdAt, new Date().toISOString())) },
687
- ],
688
- });
569
+ async function writeInboxNotificationRow(db, row) {
570
+ await db.insert(inboxNotificationTable).values(normalizeInboxNotificationInsert(row)).execute();
689
571
  }
690
- function approvalRowFromPredicates(url, predicates) {
691
- const session = firstIri(predicates, ApprovalVocab.session);
692
- const toolCallId = firstLiteral(predicates, ApprovalVocab.toolCallId);
693
- const toolName = firstLiteral(predicates, ApprovalVocab.toolName);
694
- const target = firstIri(predicates, ApprovalVocab.target);
695
- const action = firstIri(predicates, ApprovalVocab.action);
696
- const risk = firstLiteral(predicates, ApprovalVocab.risk);
697
- const status = firstLiteral(predicates, ApprovalVocab.status);
698
- const createdAt = firstLiteral(predicates, ApprovalVocab.createdAt);
699
- if (!session || !toolCallId || !toolName || !target || !action || !risk || !status || !createdAt) {
572
+ function normalizeApprovalRow(row) {
573
+ if (!row)
700
574
  return null;
701
- }
702
575
  return {
703
- id: subjectIdFromResourceUrl(url),
704
- session,
705
- toolCallId,
706
- toolName,
707
- target,
708
- action,
709
- risk,
710
- status,
711
- assignedTo: firstIri(predicates, ApprovalVocab.assignedTo),
712
- decisionBy: firstIri(predicates, ApprovalVocab.decisionBy),
713
- decisionRole: firstLiteral(predicates, ApprovalVocab.decisionRole),
714
- onBehalfOf: firstIri(predicates, ApprovalVocab.onBehalfOf),
715
- reason: firstLiteral(predicates, ApprovalVocab.reason),
716
- context: firstLiteral(predicates, ApprovalVocab.context),
717
- approvalOptions: firstLiteral(predicates, ApprovalVocab.approvalOptions),
718
- policyVersion: firstLiteral(predicates, ApprovalVocab.policyVersion),
719
- createdAt,
720
- expiresAt: firstLiteral(predicates, ApprovalVocab.expiresAt),
721
- resolvedAt: firstLiteral(predicates, ApprovalVocab.resolvedAt),
576
+ id: String(row.id),
577
+ approvalUri: normalizeString(row['@id']) ?? normalizeString(row.subject) ?? normalizeString(row.uri),
578
+ session: row.session,
579
+ toolCallId: row.toolCallId,
580
+ toolName: row.toolName,
581
+ target: row.target,
582
+ action: row.action,
583
+ risk: row.risk,
584
+ status: row.status,
585
+ assignedTo: row.assignedTo,
586
+ decisionBy: row.decisionBy,
587
+ decisionRole: row.decisionRole,
588
+ onBehalfOf: row.onBehalfOf,
589
+ reason: row.reason,
590
+ context: row.context,
591
+ approvalOptions: row.approvalOptions,
592
+ policyVersion: row.policyVersion,
593
+ createdAt: row.createdAt,
594
+ expiresAt: row.expiresAt,
595
+ resolvedAt: row.resolvedAt,
722
596
  };
723
597
  }
724
- function auditRowFromPredicates(url, predicates) {
725
- const action = firstLiteral(predicates, AuditVocab.action);
726
- const actor = firstIri(predicates, AuditVocab.actor);
727
- const actorRole = firstLiteral(predicates, AuditVocab.actorRole);
728
- const createdAt = firstLiteral(predicates, AuditVocab.createdAt);
729
- if (!action || !actor || !actorRole || !createdAt) {
598
+ function normalizeAuditRow(row) {
599
+ if (!row)
730
600
  return null;
731
- }
732
601
  return {
733
- id: subjectIdFromResourceUrl(url),
734
- action,
735
- actor,
736
- actorRole,
737
- onBehalfOf: firstIri(predicates, AuditVocab.onBehalfOf),
738
- session: firstIri(predicates, AuditVocab.session),
739
- entry: firstIri(predicates, AuditVocab.entry),
740
- toolCallId: firstLiteral(predicates, AuditVocab.toolCallId),
741
- toolName: firstLiteral(predicates, AuditVocab.toolName),
742
- approval: firstIri(predicates, AuditVocab.approval),
743
- policyVersion: firstLiteral(predicates, AuditVocab.policyVersion),
744
- createdAt,
602
+ id: String(row.id),
603
+ action: row.action,
604
+ actor: row.actor,
605
+ actorRole: row.actorRole,
606
+ onBehalfOf: row.onBehalfOf,
607
+ session: row.session,
608
+ entry: row.entry,
609
+ toolCallId: row.toolCallId,
610
+ toolName: row.toolName,
611
+ approval: row.approval,
612
+ policyVersion: row.policyVersion,
613
+ createdAt: row.createdAt,
745
614
  };
746
615
  }
747
- function grantRowFromPredicates(url, predicates) {
748
- const target = firstIri(predicates, GrantVocab.target);
749
- const action = firstIri(predicates, GrantVocab.action);
750
- const effect = firstLiteral(predicates, GrantVocab.effect);
751
- const decisionBy = firstIri(predicates, GrantVocab.decisionBy);
752
- const decisionRole = firstLiteral(predicates, GrantVocab.decisionRole);
753
- const createdAt = firstLiteral(predicates, GrantVocab.createdAt);
754
- if (!target || !action || !effect || !decisionBy || !decisionRole || !createdAt) {
616
+ function normalizeGrantRow(row) {
617
+ if (!row)
755
618
  return null;
619
+ return {
620
+ id: String(row.id),
621
+ target: row.target,
622
+ action: row.action,
623
+ title: row.title,
624
+ summary: row.summary,
625
+ body: row.body,
626
+ schema: row.schema,
627
+ pageKind: row.pageKind,
628
+ wikiStatus: row.wikiStatus,
629
+ tags: row.tags,
630
+ source: row.source,
631
+ sourceHash: row.sourceHash,
632
+ compiledAt: row.compiledAt,
633
+ compiledFrom: row.compiledFrom,
634
+ related: row.related,
635
+ effect: row.effect,
636
+ riskCeiling: row.riskCeiling,
637
+ policy: row.policy,
638
+ context: row.context,
639
+ decisionBy: row.decisionBy,
640
+ decisionRole: row.decisionRole,
641
+ onBehalfOf: row.onBehalfOf,
642
+ createdAt: row.createdAt,
643
+ revokedAt: row.revokedAt,
644
+ };
645
+ }
646
+ function normalizeApprovalInsert(row) {
647
+ return {
648
+ id: row.id,
649
+ session: row.session,
650
+ toolCallId: row.toolCallId,
651
+ toolName: row.toolName,
652
+ target: row.target,
653
+ action: row.action,
654
+ risk: row.risk,
655
+ status: row.status,
656
+ ...(row.assignedTo ? { assignedTo: row.assignedTo } : {}),
657
+ ...(row.decisionBy ? { decisionBy: row.decisionBy } : {}),
658
+ ...(row.decisionRole ? { decisionRole: row.decisionRole } : {}),
659
+ ...(row.onBehalfOf ? { onBehalfOf: row.onBehalfOf } : {}),
660
+ ...(row.reason ? { reason: row.reason } : {}),
661
+ ...(row.context ? { context: row.context } : {}),
662
+ ...(row.approvalOptions ? { approvalOptions: row.approvalOptions } : {}),
663
+ ...(row.policyVersion ? { policyVersion: row.policyVersion } : {}),
664
+ createdAt: new Date(toIsoString(row.createdAt, new Date().toISOString())),
665
+ ...(row.expiresAt ? { expiresAt: new Date(toIsoString(row.expiresAt, new Date().toISOString())) } : {}),
666
+ ...(row.resolvedAt ? { resolvedAt: new Date(toIsoString(row.resolvedAt, new Date().toISOString())) } : {}),
667
+ };
668
+ }
669
+ function normalizeApprovalUpdate(patch) {
670
+ const next = {};
671
+ for (const key of [
672
+ 'session',
673
+ 'toolCallId',
674
+ 'toolName',
675
+ 'target',
676
+ 'action',
677
+ 'risk',
678
+ 'status',
679
+ 'assignedTo',
680
+ 'decisionBy',
681
+ 'decisionRole',
682
+ 'onBehalfOf',
683
+ 'reason',
684
+ 'context',
685
+ 'approvalOptions',
686
+ 'policyVersion',
687
+ ]) {
688
+ if (patch[key] !== undefined) {
689
+ ;
690
+ next[key] = patch[key];
691
+ }
756
692
  }
693
+ if (patch.createdAt !== undefined)
694
+ next.createdAt = new Date(toIsoString(patch.createdAt, new Date().toISOString()));
695
+ if (patch.expiresAt !== undefined)
696
+ next.expiresAt = new Date(toIsoString(patch.expiresAt, new Date().toISOString()));
697
+ if (patch.resolvedAt !== undefined)
698
+ next.resolvedAt = new Date(toIsoString(patch.resolvedAt, new Date().toISOString()));
699
+ return next;
700
+ }
701
+ function normalizeAuditInsert(row) {
757
702
  return {
758
- id: subjectIdFromResourceUrl(url),
759
- target,
760
- action,
761
- title: firstLiteral(predicates, GrantVocab.title),
762
- summary: firstLiteral(predicates, GrantVocab.summary),
763
- body: firstLiteral(predicates, GrantVocab.body),
764
- schema: firstIri(predicates, GrantVocab.schema),
765
- pageKind: firstLiteral(predicates, GrantVocab.pageKind),
766
- wikiStatus: firstLiteral(predicates, GrantVocab.wikiStatus),
767
- tags: firstLiteral(predicates, GrantVocab.tags),
768
- source: firstLiteral(predicates, GrantVocab.source),
769
- sourceHash: firstLiteral(predicates, GrantVocab.sourceHash),
770
- compiledAt: firstLiteral(predicates, GrantVocab.compiledAt),
771
- compiledFrom: iriValues(predicates, GrantVocab.compiledFrom),
772
- related: iriValues(predicates, GrantVocab.related),
773
- effect,
774
- riskCeiling: firstLiteral(predicates, GrantVocab.riskCeiling),
775
- policy: firstLiteral(predicates, GrantVocab.policy),
776
- context: firstLiteral(predicates, GrantVocab.context),
777
- decisionBy,
778
- decisionRole,
779
- onBehalfOf: firstIri(predicates, GrantVocab.onBehalfOf),
780
- createdAt,
781
- revokedAt: firstLiteral(predicates, GrantVocab.revokedAt),
703
+ id: row.id,
704
+ action: row.action,
705
+ actor: row.actor,
706
+ actorRole: row.actorRole,
707
+ ...(row.onBehalfOf ? { onBehalfOf: row.onBehalfOf } : {}),
708
+ ...(row.session ? { session: row.session } : {}),
709
+ ...(row.entry ? { entry: row.entry } : {}),
710
+ ...(row.toolCallId ? { toolCallId: row.toolCallId } : {}),
711
+ ...(row.toolName ? { toolName: row.toolName } : {}),
712
+ ...(row.approval ? { approval: row.approval } : {}),
713
+ ...(row.policyVersion ? { policyVersion: row.policyVersion } : {}),
714
+ createdAt: new Date(toIsoString(row.createdAt, new Date().toISOString())),
715
+ };
716
+ }
717
+ function normalizeGrantInsert(row) {
718
+ return {
719
+ id: row.id,
720
+ target: row.target,
721
+ action: row.action,
722
+ ...(row.title ? { title: truncatePodLiteral(row.title, 160) } : {}),
723
+ ...(row.summary ? { summary: truncatePodLiteral(row.summary, 500) } : {}),
724
+ ...(row.body ? { body: truncatePodLiteral(row.body, MAX_GRANT_POLICY_LENGTH) } : {}),
725
+ ...(row.schema ? { schema: row.schema } : {}),
726
+ ...(row.pageKind ? { pageKind: row.pageKind } : {}),
727
+ ...(row.wikiStatus ? { wikiStatus: row.wikiStatus } : {}),
728
+ ...(row.tags ? { tags: truncatePodLiteral(row.tags, 500) } : {}),
729
+ ...(row.source ? { source: row.source } : {}),
730
+ ...(row.sourceHash ? { sourceHash: row.sourceHash } : {}),
731
+ ...(row.compiledAt ? { compiledAt: new Date(toIsoString(row.compiledAt, new Date().toISOString())) } : {}),
732
+ ...(row.compiledFrom ? { compiledFrom: row.compiledFrom } : {}),
733
+ ...(row.related ? { related: row.related } : {}),
734
+ effect: row.effect,
735
+ ...(row.riskCeiling ? { riskCeiling: row.riskCeiling } : {}),
736
+ ...(row.policy ? { policy: truncatePodLiteral(row.policy, MAX_GRANT_POLICY_LENGTH) } : {}),
737
+ ...(row.context ? { context: truncatePodLiteral(row.context, MAX_APPROVAL_CONTEXT_LENGTH) } : {}),
738
+ decisionBy: row.decisionBy,
739
+ decisionRole: row.decisionRole,
740
+ ...(row.onBehalfOf ? { onBehalfOf: row.onBehalfOf } : {}),
741
+ createdAt: new Date(toIsoString(row.createdAt, new Date().toISOString())),
742
+ ...(row.revokedAt ? { revokedAt: new Date(toIsoString(row.revokedAt, new Date().toISOString())) } : {}),
743
+ };
744
+ }
745
+ function normalizeInboxNotificationInsert(row) {
746
+ return {
747
+ id: row.id,
748
+ ...(row.actor ? { actor: row.actor } : {}),
749
+ object: row.object,
750
+ createdAt: new Date(toIsoString(row.createdAt, new Date().toISOString())),
782
751
  };
783
752
  }
784
753
  function isActiveAllowGrant(grant) {
@@ -892,10 +861,12 @@ export async function createRemoteApproval(options) {
892
861
  const request = typeof options.request === 'function'
893
862
  ? options.request({ webId, stored, sessionUri: subject.sessionUri })
894
863
  : options.request;
895
- const approvalId = crypto.randomUUID();
864
+ const approvalLocalId = crypto.randomUUID();
896
865
  const now = activeRuntime.now();
866
+ const approvalReference = store.resolveApprovalReference({ id: approvalLocalId, createdAt: now });
867
+ const approvalId = approvalReference.id;
897
868
  const sessionUri = subject.sessionUri;
898
- const approvalUri = buildApprovalUriForDate(webId, approvalId, now);
869
+ const approvalUri = approvalReference.iri;
899
870
  const targetUri = subject.targetUri ?? sessionUri;
900
871
  const assignedTo = subject.assignedTo ?? webId;
901
872
  const onBehalfOf = subject.onBehalfOf ?? webId;
@@ -906,6 +877,7 @@ export async function createRemoteApproval(options) {
906
877
  const context = compactApprovalContext(request);
907
878
  await store.insertApproval({
908
879
  id: approvalId,
880
+ approvalUri,
909
881
  session: sessionUri,
910
882
  toolCallId: request.toolCallId,
911
883
  toolName: request.toolName,
@@ -1096,13 +1068,14 @@ export async function resolveRemoteWatchApproval(options) {
1096
1068
  return normalizeApprovalSummary(row);
1097
1069
  }
1098
1070
  const now = activeRuntime.now();
1099
- const approvalCreatedAt = new Date(toIsoString(row.createdAt, now.toISOString()));
1100
- const approvalUri = buildApprovalUriForDate(row.session, row.id, approvalCreatedAt);
1071
+ const approvalUri = normalizeString(row.approvalUri)
1072
+ ?? store.resolveApprovalReference({ id: row.id, createdAt: row.createdAt }).iri;
1101
1073
  const nextStatus = options.decision === 'accept' || options.decision === 'accept_for_session'
1102
1074
  ? 'approved'
1103
1075
  : 'rejected';
1104
1076
  const decisionRole = options.decisionRole ?? 'human';
1105
1077
  await store.updateApproval(row.id, {
1078
+ approvalUri,
1106
1079
  status: nextStatus,
1107
1080
  decisionBy: webId,
1108
1081
  decisionRole,
@@ -1155,7 +1128,7 @@ export async function resolveRemoteWatchApproval(options) {
1155
1128
  await warnOnly(activeRuntime, () => store.insertInboxNotification({
1156
1129
  id: crypto.randomUUID(),
1157
1130
  actor: webId,
1158
- object: buildGrantUri(row.session, grantId),
1131
+ object: store.resolveGrantReference({ id: grantId }).iri,
1159
1132
  createdAt: now,
1160
1133
  }));
1161
1134
  }
@@ -1167,6 +1140,7 @@ export async function resolveRemoteWatchApproval(options) {
1167
1140
  }));
1168
1141
  const nextRow = {
1169
1142
  ...row,
1143
+ approvalUri,
1170
1144
  status: nextStatus,
1171
1145
  decisionBy: webId,
1172
1146
  decisionRole,
@@ -1180,11 +1154,9 @@ export async function resolveRemoteWatchApproval(options) {
1180
1154
  async function readRemoteApprovalRow(store, options) {
1181
1155
  if (store.findApproval) {
1182
1156
  const row = await store.findApproval(options.approvalId, {
1183
- resourceUri: options.approvalUri,
1157
+ approvalUri: options.approvalUri,
1184
1158
  });
1185
- if (row || options.approvalUri) {
1186
- return row;
1187
- }
1159
+ return row;
1188
1160
  }
1189
1161
  const approvals = await store.listApprovals();
1190
1162
  return approvals.find((entry) => entry.id === options.approvalId) ?? null;