@undefineds.co/linx 0.2.19 → 0.2.24

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 (125) hide show
  1. package/README.md +23 -21
  2. package/dist/index.js +93 -142
  3. package/dist/index.js.map +1 -1
  4. package/dist/lib/ai-command.js +34 -30
  5. package/dist/lib/ai-command.js.map +1 -1
  6. package/dist/lib/auto-mode/archive.js +235 -0
  7. package/dist/lib/auto-mode/archive.js.map +1 -0
  8. package/dist/lib/{watch → auto-mode}/auth.js +6 -6
  9. package/dist/lib/auto-mode/auth.js.map +1 -0
  10. package/dist/lib/{watch → auto-mode}/codex-composer.js +1 -1
  11. package/dist/lib/auto-mode/codex-composer.js.map +1 -0
  12. package/dist/lib/auto-mode/codex-footer.js.map +1 -0
  13. package/dist/lib/auto-mode/codex-overlay.js.map +1 -0
  14. package/dist/lib/{watch → auto-mode}/codex-request-form.js +2 -2
  15. package/dist/lib/auto-mode/codex-request-form.js.map +1 -0
  16. package/dist/lib/auto-mode/codex-request-input.js.map +1 -0
  17. package/dist/lib/{watch → auto-mode}/display.js +63 -57
  18. package/dist/lib/auto-mode/display.js.map +1 -0
  19. package/dist/lib/{watch → auto-mode}/format.js +24 -16
  20. package/dist/lib/auto-mode/format.js.map +1 -0
  21. package/dist/lib/{watch → auto-mode}/hooks/claude.js +3 -2
  22. package/dist/lib/auto-mode/hooks/claude.js.map +1 -0
  23. package/dist/lib/{watch → auto-mode}/hooks/codebuddy.js +3 -2
  24. package/dist/lib/auto-mode/hooks/codebuddy.js.map +1 -0
  25. package/dist/lib/auto-mode/hooks/codex.js +18 -0
  26. package/dist/lib/auto-mode/hooks/codex.js.map +1 -0
  27. package/dist/lib/auto-mode/hooks/index.js +27 -0
  28. package/dist/lib/auto-mode/hooks/index.js.map +1 -0
  29. package/dist/lib/auto-mode/hooks/shared.js.map +1 -0
  30. package/dist/lib/auto-mode/index.js.map +1 -0
  31. package/dist/lib/auto-mode/pod-ai.js +116 -0
  32. package/dist/lib/auto-mode/pod-ai.js.map +1 -0
  33. package/dist/lib/{watch → auto-mode}/pod-approval.js +495 -325
  34. package/dist/lib/auto-mode/pod-approval.js.map +1 -0
  35. package/dist/lib/auto-mode/pod-persistence.js +412 -0
  36. package/dist/lib/auto-mode/pod-persistence.js.map +1 -0
  37. package/dist/lib/{watch → auto-mode}/runner.js +280 -193
  38. package/dist/lib/auto-mode/runner.js.map +1 -0
  39. package/dist/lib/{watch → auto-mode}/secretary.js +8 -8
  40. package/dist/lib/auto-mode/secretary.js.map +1 -0
  41. package/dist/lib/{watch → auto-mode}/types.js.map +1 -1
  42. package/dist/lib/auto-mode-command.js +114 -0
  43. package/dist/lib/auto-mode-command.js.map +1 -0
  44. package/dist/lib/codex-plugin/bridge.js +11 -12
  45. package/dist/lib/codex-plugin/bridge.js.map +1 -1
  46. package/dist/lib/codex-plugin/codex-native-proxy.js +11 -12
  47. package/dist/lib/codex-plugin/codex-native-proxy.js.map +1 -1
  48. package/dist/lib/linx-tui-contract.js +26 -0
  49. package/dist/lib/linx-tui-contract.js.map +1 -0
  50. package/dist/lib/login-command.js +9 -3
  51. package/dist/lib/login-command.js.map +1 -1
  52. package/dist/lib/models.js +2 -2
  53. package/dist/lib/models.js.map +1 -1
  54. package/dist/lib/oidc-auth.js +83 -104
  55. package/dist/lib/oidc-auth.js.map +1 -1
  56. package/dist/lib/oidc-session-storage.js +7 -1
  57. package/dist/lib/oidc-session-storage.js.map +1 -1
  58. package/dist/lib/pi-adapter/auth.js +9 -15
  59. package/dist/lib/pi-adapter/auth.js.map +1 -1
  60. package/dist/lib/pi-adapter/branding.js +14 -44
  61. package/dist/lib/pi-adapter/branding.js.map +1 -1
  62. package/dist/lib/pi-adapter/interactive.js +19 -23
  63. package/dist/lib/pi-adapter/interactive.js.map +1 -1
  64. package/dist/lib/pi-adapter/pod-approval.js +239 -1
  65. package/dist/lib/pi-adapter/pod-approval.js.map +1 -1
  66. package/dist/lib/pi-adapter/pod-mirror.js +64 -59
  67. package/dist/lib/pi-adapter/pod-mirror.js.map +1 -1
  68. package/dist/lib/pi-adapter/pod-native.js +479 -0
  69. package/dist/lib/pi-adapter/pod-native.js.map +1 -0
  70. package/dist/lib/pi-adapter/pod-tools.js +76 -40
  71. package/dist/lib/pi-adapter/pod-tools.js.map +1 -1
  72. package/dist/lib/pi-adapter/runtime.js +11 -66
  73. package/dist/lib/pi-adapter/runtime.js.map +1 -1
  74. package/dist/lib/pi-adapter/session.js +276 -33
  75. package/dist/lib/pi-adapter/session.js.map +1 -1
  76. package/dist/lib/pi-adapter/stream.js.map +1 -1
  77. package/dist/lib/pi-adapter/web-fetch.js +47 -30
  78. package/dist/lib/pi-adapter/web-fetch.js.map +1 -1
  79. package/dist/lib/pod-chat-store.js +36 -38
  80. package/dist/lib/pod-chat-store.js.map +1 -1
  81. package/dist/lib/pod-data-session.js +97 -30
  82. package/dist/lib/pod-data-session.js.map +1 -1
  83. package/package.json +3 -3
  84. package/vendor/agent-runtime/dist/auto-mode.d.ts +287 -0
  85. package/vendor/agent-runtime/dist/auto-mode.js +1498 -0
  86. package/vendor/agent-runtime/dist/index.d.ts +2 -0
  87. package/vendor/agent-runtime/dist/index.js +2 -0
  88. package/vendor/agent-runtime/dist/runtime.d.ts +47 -0
  89. package/vendor/agent-runtime/dist/runtime.js +36 -0
  90. package/vendor/agent-runtime/dist/turn-controller.d.ts +4 -4
  91. package/vendor/agent-runtime/dist/turn-controller.js +7 -7
  92. package/vendor/agent-runtime/package.json +2 -0
  93. package/dist/lib/watch/archive.js +0 -110
  94. package/dist/lib/watch/archive.js.map +0 -1
  95. package/dist/lib/watch/auth.js.map +0 -1
  96. package/dist/lib/watch/codex-composer.js.map +0 -1
  97. package/dist/lib/watch/codex-footer.js.map +0 -1
  98. package/dist/lib/watch/codex-overlay.js.map +0 -1
  99. package/dist/lib/watch/codex-request-form.js.map +0 -1
  100. package/dist/lib/watch/codex-request-input.js.map +0 -1
  101. package/dist/lib/watch/display.js.map +0 -1
  102. package/dist/lib/watch/format.js.map +0 -1
  103. package/dist/lib/watch/hooks/claude.js.map +0 -1
  104. package/dist/lib/watch/hooks/codebuddy.js.map +0 -1
  105. package/dist/lib/watch/hooks/codex.js +0 -17
  106. package/dist/lib/watch/hooks/codex.js.map +0 -1
  107. package/dist/lib/watch/hooks/index.js +0 -24
  108. package/dist/lib/watch/hooks/index.js.map +0 -1
  109. package/dist/lib/watch/hooks/shared.js.map +0 -1
  110. package/dist/lib/watch/index.js.map +0 -1
  111. package/dist/lib/watch/pod-ai.js +0 -160
  112. package/dist/lib/watch/pod-ai.js.map +0 -1
  113. package/dist/lib/watch/pod-approval.js.map +0 -1
  114. package/dist/lib/watch/pod-persistence.js +0 -334
  115. package/dist/lib/watch/pod-persistence.js.map +0 -1
  116. package/dist/lib/watch/runner.js.map +0 -1
  117. package/dist/lib/watch/secretary.js.map +0 -1
  118. package/dist/watch-cli.js +0 -116
  119. package/dist/watch-cli.js.map +0 -1
  120. /package/dist/lib/{watch → auto-mode}/codex-footer.js +0 -0
  121. /package/dist/lib/{watch → auto-mode}/codex-overlay.js +0 -0
  122. /package/dist/lib/{watch → auto-mode}/codex-request-input.js +0 -0
  123. /package/dist/lib/{watch → auto-mode}/hooks/shared.js +0 -0
  124. /package/dist/lib/{watch → auto-mode}/index.js +0 -0
  125. /package/dist/lib/{watch → auto-mode}/types.js +0 -0
@@ -1,12 +1,17 @@
1
1
  import { setTimeout as delay } from 'node:timers/promises';
2
2
  import { getDefaultPodDataSession } from '../pod-data-session.js';
3
- import { approvalResource, auditResource, drizzle, grantResource, inboxNotificationTable, initSolidTables, solidSchema, } from '../models.js';
4
- import { resolveWatchGrantCoverage } from './secretary.js';
5
- const WATCH_CHAT_ID_PREFIX = 'linx-watch';
6
- const WATCH_AGENT_ID = 'linx-watch-assistant';
7
- const REMOTE_APPROVAL_POLICY_VERSION = 'linx-watch-remote-approval/v1';
3
+ import { approvalResource, auditResource, buildApprovalSubjectPath, buildGrantSubjectPath, drizzle, grantResource, inboxNotificationResource, solidResources, } from '../models.js';
4
+ import { AS, ODRL, UDFS } from '@undefineds.co/models/namespaces';
5
+ import { ApprovalVocab, AuditVocab, GrantVocab, InboxNotificationVocab } from '@undefineds.co/models/vocab/sidecar';
6
+ import { autoModeApprovalActionUri, autoModeApprovalRequestMessage, autoModeApprovalRisk, autoModeApprovalToolName, } from '../../../vendor/agent-runtime/dist/auto-mode.js';
7
+ import { resolveAutoModeGrantCoverage } from './secretary.js';
8
+ 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';
9
+ const AUTO_MODE_CHAT_ID_PREFIX = 'linx-auto-mode';
10
+ const AUTO_MODE_AGENT_ID = 'linx-auto-mode-assistant';
11
+ const REMOTE_APPROVAL_POLICY_VERSION = 'linx-auto-mode-remote-approval/v1';
8
12
  const DEFAULT_REMOTE_APPROVAL_POLL_MS = 1000;
9
13
  const DEFAULT_WARN_ONLY_TIMEOUT_MS = 5000;
14
+ const DEFAULT_APPROVAL_LIST_DAYS = 7;
10
15
  const MAX_GRANT_POLICY_LENGTH = 1200;
11
16
  const MAX_APPROVAL_CONTEXT_LENGTH = 1400;
12
17
  const MIN_GRANT_COVERAGE_CONFIDENCE = 0.75;
@@ -42,53 +47,41 @@ function getPodBaseUrl(webIdOrUri) {
42
47
  }
43
48
  return webIdOrUri.replace(/\/$/, '');
44
49
  }
45
- function buildWatchChatId(record) {
46
- return `${WATCH_CHAT_ID_PREFIX}-${record.backend}`;
50
+ function buildAutoModeChatId(record) {
51
+ return `${AUTO_MODE_CHAT_ID_PREFIX}-${record.backend}`;
47
52
  }
48
53
  function buildThreadUri(webId, record) {
49
- return `${getPodBaseUrl(webId)}/.data/chat/${buildWatchChatId(record)}/index.ttl#${record.id}`;
54
+ return `${getPodBaseUrl(webId)}/.data/chat/${buildAutoModeChatId(record)}/index.ttl#${record.id}`;
50
55
  }
51
- function isAbsoluteIri(value) {
52
- return value.startsWith('http://') || value.startsWith('https://');
56
+ function buildApprovalUriForDate(webIdOrUri, approvalId, createdAt) {
57
+ return buildPodResourceIri(webIdOrUri, buildApprovalSubjectPath(approvalId, createdAt));
58
+ }
59
+ function documentUrlFromResourceUri(resourceUri) {
60
+ return resourceUri.split('#', 1)[0] ?? resourceUri;
61
+ }
62
+ function buildGrantUri(webIdOrUri, grantId) {
63
+ return buildPodResourceIri(webIdOrUri, buildGrantSubjectPath(grantId));
64
+ }
65
+ function buildPodResourceIri(webIdOrUri, relativeUri) {
66
+ if (/^https?:\/\//.test(relativeUri)) {
67
+ return relativeUri;
68
+ }
69
+ return new URL(relativeUri.replace(/^\//, ''), `${getPodBaseUrl(webIdOrUri)}/`).toString();
53
70
  }
54
71
  function buildGrantSchemaUri(webIdOrUri) {
55
72
  return `${getPodBaseUrl(webIdOrUri)}/settings/autonomy/schema/grant.ttl#GrantWikiPage`;
56
73
  }
57
74
  function buildAgentUri(webId) {
58
- return `${getPodBaseUrl(webId)}/.data/agents/${WATCH_AGENT_ID}.ttl`;
75
+ return `${getPodBaseUrl(webId)}/.data/agents/${AUTO_MODE_AGENT_ID}.ttl`;
59
76
  }
60
77
  function buildActionUri(request) {
61
- if (request.kind === 'command-approval') {
62
- return 'https://undefineds.co/ns#commandExecution';
63
- }
64
- if (request.kind === 'file-change-approval') {
65
- return 'https://undefineds.co/ns#fileChange';
66
- }
67
- if (request.kind === 'permissions-approval') {
68
- return 'https://undefineds.co/ns#permissionRequest';
69
- }
70
- return 'https://undefineds.co/ns#runtimeApproval';
78
+ return autoModeApprovalActionUri(request);
71
79
  }
72
80
  function buildToolName(request) {
73
- if (request.kind === 'command-approval') {
74
- return 'commandExecution';
75
- }
76
- if (request.kind === 'file-change-approval') {
77
- return 'fileChange';
78
- }
79
- if (request.kind === 'permissions-approval') {
80
- return 'permissionRequest';
81
- }
82
- return 'runtimeApproval';
81
+ return autoModeApprovalToolName(request);
83
82
  }
84
83
  function buildRisk(request) {
85
- if (request.kind === 'permissions-approval') {
86
- return 'high';
87
- }
88
- if (request.kind === 'file-change-approval') {
89
- return 'high';
90
- }
91
- return 'medium';
84
+ return autoModeApprovalRisk(request);
92
85
  }
93
86
  function riskScore(risk) {
94
87
  switch (risk) {
@@ -103,13 +96,7 @@ function riskScore(risk) {
103
96
  }
104
97
  }
105
98
  function buildRequestMessage(request) {
106
- if (request.kind === 'command-approval') {
107
- return request.command?.trim() || request.message;
108
- }
109
- if (request.kind === 'file-change-approval') {
110
- return request.reason?.trim() || request.message;
111
- }
112
- return request.message;
99
+ return autoModeApprovalRequestMessage(request);
113
100
  }
114
101
  function extractToolCallId(request) {
115
102
  if (!isRecord(request.raw)) {
@@ -253,7 +240,7 @@ function grantWikiTagsFromApproval(row, explicitTags) {
253
240
  }
254
241
  function grantContextFromApproval(row) {
255
242
  return safeCompactJson({
256
- sourceApproval: row.approvalUri ?? row.id,
243
+ sourceApproval: buildApprovalUriForDate(row.session, row.id, new Date(toIsoString(row.createdAt, new Date().toISOString()))),
257
244
  session: row.session,
258
245
  toolCallId: row.toolCallId,
259
246
  toolName: row.toolName,
@@ -263,6 +250,16 @@ function grantContextFromApproval(row) {
263
250
  approvalContext: row.context,
264
251
  }, MAX_APPROVAL_CONTEXT_LENGTH);
265
252
  }
253
+ function literalValues(predicates, predicate) {
254
+ return (predicates.get(predicate) ?? [])
255
+ .map((object) => isRecord(object) && object.type === 'literal' && typeof object.value === 'string' ? object.value : '')
256
+ .filter(Boolean);
257
+ }
258
+ function iriValues(predicates, predicate) {
259
+ return (predicates.get(predicate) ?? [])
260
+ .map((object) => isRecord(object) && object.type === 'iri' && typeof object.value === 'string' ? object.value : '')
261
+ .filter(Boolean);
262
+ }
266
263
  function grantSourceHash(row) {
267
264
  return `approval:${row.id}:${row.toolCallId}:${row.risk}`;
268
265
  }
@@ -386,7 +383,7 @@ function formatApprovalMessage(row) {
386
383
  function formatSummaryHeadline(summary) {
387
384
  return `${summary.id} | ${summary.status} | ${summary.risk} | session=${summary.sessionId}`;
388
385
  }
389
- export function formatRemoteWatchApprovalSummary(summary) {
386
+ export function formatRemoteAutoModeApprovalSummary(summary) {
390
387
  const detail = summary.command ?? summary.message;
391
388
  const secondary = [
392
389
  summary.toolName,
@@ -404,8 +401,11 @@ function missingRemoteApprovalCredentialsMessage() {
404
401
  async function createDefaultRuntime() {
405
402
  return {
406
403
  getPodDataSession: getDefaultPodDataSession,
407
- createStore(session, db) {
408
- return createNativeRemoteApprovalStore(session.webId, db);
404
+ createStore(webId, fetcher, session) {
405
+ if (session) {
406
+ return createOrmRemoteApprovalStore(session);
407
+ }
408
+ return createNativeRemoteApprovalStore(webId, fetcher);
409
409
  },
410
410
  sleep(ms) {
411
411
  return delay(ms);
@@ -413,7 +413,7 @@ async function createDefaultRuntime() {
413
413
  now() {
414
414
  return new Date();
415
415
  },
416
- resolveGrantCoverage: resolveWatchGrantCoverage,
416
+ resolveGrantCoverage: resolveAutoModeGrantCoverage,
417
417
  };
418
418
  }
419
419
  async function withRemoteApprovalStore(runtime, fn) {
@@ -450,112 +450,331 @@ async function createRemoteApprovalClient(runtime) {
450
450
  if (!session) {
451
451
  return null;
452
452
  }
453
- const db = createRemoteApprovalDb(session);
454
- await initSolidTables(db, [
455
- approvalResource,
456
- auditResource,
457
- grantResource,
458
- inboxNotificationTable,
459
- ]);
460
453
  return {
461
454
  session,
462
- store: runtime.createStore(session, db),
455
+ store: runtime.createStore(session.webId, session.fetch, session),
463
456
  };
464
457
  }
465
- function createRemoteApprovalDb(session) {
466
- return drizzle(session.solidSession, {
467
- logger: false,
468
- disableInteropDiscovery: true,
469
- schema: solidSchema,
470
- });
458
+ function createOrmRemoteApprovalStore(podSession) {
459
+ let dbPromise = null;
460
+ const getDb = async () => {
461
+ if (!dbPromise) {
462
+ dbPromise = Promise.resolve().then(async () => {
463
+ const db = drizzle(podSession.solidSession, {
464
+ logger: false,
465
+ disableInteropDiscovery: true,
466
+ podUrl: podSession.podUrl,
467
+ resourcePreparation: 'best-effort',
468
+ schema: solidResources,
469
+ });
470
+ return db;
471
+ });
472
+ }
473
+ return dbPromise;
474
+ };
475
+ return createSharedModelRemoteApprovalStore(podSession.webId, getDb);
471
476
  }
472
- function createNativeRemoteApprovalStore(_webId, db) {
477
+ function createSharedModelRemoteApprovalStore(webId, getDb) {
478
+ const listApprovals = async () => {
479
+ const rows = await modelList(getDb, approvalResource);
480
+ return rows.map((row) => enrichApprovalRow(webId, row));
481
+ };
473
482
  return {
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),
478
- async updateApproval(id, patch) {
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;
483
+ listApprovals,
484
+ findApproval: async (id, options = {}) => {
485
+ if (options.resourceUri) {
486
+ const row = await modelFindByIri(getDb, approvalResource, options.resourceUri);
487
+ return row ? enrichApprovalRow(webId, row, options.resourceUri) : null;
488
+ }
489
+ if (options.createdAt) {
490
+ const createdAt = new Date(toIsoString(options.createdAt, new Date().toISOString()));
491
+ const iri = buildApprovalUriForDate(webId, id, createdAt);
492
+ const row = await modelFindByIri(getDb, approvalResource, iri);
493
+ return row ? enrichApprovalRow(webId, row, iri) : null;
492
494
  }
493
- const updateByLocator = db.updateByLocator;
494
- if (typeof updateByLocator !== 'function') {
495
- throw new Error('Solid database does not support updateByLocator');
495
+ const row = await modelFindById(getDb, approvalResource, id);
496
+ return row ? enrichApprovalRow(webId, row) : null;
497
+ },
498
+ insertApproval: async (row) => {
499
+ await modelInsert(getDb, approvalResource, omitInternalFields(row));
500
+ },
501
+ updateApproval: async (id, patch, options = {}) => {
502
+ const explicitIri = options.resourceUri
503
+ ?? normalizeString(patch.approvalUri)
504
+ ?? (options.createdAt ? buildApprovalUriForDate(webId, id, new Date(toIsoString(options.createdAt, new Date().toISOString()))) : undefined);
505
+ if (explicitIri) {
506
+ await modelUpdateByIri(getDb, approvalResource, explicitIri, omitInternalFields(patch));
507
+ return;
496
508
  }
497
- const updated = await updateByLocator.call(db, approvalResource, { id }, data);
509
+ const updated = await modelUpdateById(getDb, approvalResource, id, omitInternalFields(patch));
498
510
  if (!updated) {
499
511
  throw new Error(`Remote approval not found: ${id}`);
500
512
  }
501
513
  },
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),
514
+ listAudits: () => modelList(getDb, auditResource),
515
+ insertAudit: async (row) => {
516
+ await modelInsert(getDb, auditResource, omitInternalFields(row));
517
+ },
518
+ listGrants: () => modelList(getDb, grantResource),
519
+ insertGrant: async (row) => {
520
+ await modelInsert(getDb, grantResource, omitInternalFields(row));
521
+ },
522
+ insertInboxNotification: async (row) => {
523
+ await modelInsert(getDb, inboxNotificationResource, omitInternalFields(row));
524
+ },
508
525
  };
509
526
  }
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');
516
- }
517
- const row = await findByIri.call(db, approvalResource, approvalUri);
518
- return normalizeApprovalRow(row);
527
+ async function modelList(getDb, resource) {
528
+ const db = await getDb();
529
+ return await db.select().from(resource).execute();
530
+ }
531
+ async function modelFindByIri(getDb, resource, iri) {
532
+ const db = await getDb();
533
+ if (typeof db.findByIri === 'function') {
534
+ return await db.findByIri(resource, iri);
519
535
  }
520
- const findByLocator = db.findByLocator;
521
- if (typeof findByLocator !== 'function') {
522
- throw new Error('Solid database does not support findByLocator');
536
+ const rows = await db.select().from(resource).whereByIri(iri).execute();
537
+ return rows[0] ?? null;
538
+ }
539
+ async function modelFindById(getDb, resource, id) {
540
+ const db = await getDb();
541
+ if (typeof db.findById === 'function') {
542
+ return await db.findById(resource, id);
523
543
  }
524
- const row = await findByLocator.call(db, approvalResource, {
525
- id,
526
- ...(options.createdAt ? { createdAt: options.createdAt } : {}),
527
- });
528
- return normalizeApprovalRow(row);
544
+ throw new Error('Remote approval shared model store requires findById support');
545
+ }
546
+ async function modelInsert(getDb, resource, row) {
547
+ const db = await getDb();
548
+ await db.insert(resource).values(stripUndefined(row)).execute();
549
+ }
550
+ async function modelUpdateByIri(getDb, resource, iri, patch) {
551
+ const db = await getDb();
552
+ const update = stripUndefined(patch);
553
+ delete update.id;
554
+ delete update.approvalUri;
555
+ if (typeof db.updateByIri === 'function') {
556
+ await db.updateByIri(resource, iri, update);
557
+ return;
558
+ }
559
+ const query = db.update(resource).set(update);
560
+ if (typeof query.whereByIri !== 'function') {
561
+ throw new Error('Remote approval shared model store requires updateByIri/whereByIri support');
562
+ }
563
+ await query.whereByIri(iri).execute();
564
+ }
565
+ async function modelUpdateById(getDb, resource, id, patch) {
566
+ const db = await getDb();
567
+ const update = stripUndefined(patch);
568
+ delete update.id;
569
+ delete update.approvalUri;
570
+ if (typeof db.updateById === 'function') {
571
+ return await db.updateById(resource, id, update);
572
+ }
573
+ const row = await modelFindById(getDb, resource, id);
574
+ const iri = rowSubject(row ?? {});
575
+ if (!iri) {
576
+ return null;
577
+ }
578
+ await modelUpdateByIri(getDb, resource, iri, update);
579
+ return await modelFindByIri(getDb, resource, iri);
529
580
  }
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');
581
+ function stripUndefined(row) {
582
+ const next = {};
583
+ for (const [key, value] of Object.entries(row)) {
584
+ if (value !== undefined) {
585
+ next[key] = value;
586
+ }
533
587
  }
588
+ return next;
589
+ }
590
+ function omitInternalFields(row) {
591
+ const next = stripUndefined(row);
592
+ delete next.approvalUri;
593
+ delete next['@id'];
594
+ delete next.subject;
595
+ delete next.source;
596
+ delete next.uri;
597
+ return next;
598
+ }
599
+ function rowSubject(row) {
600
+ return normalizeString(row['@id'])
601
+ ?? normalizeString(row.subject)
602
+ ?? normalizeString(row.uri);
603
+ }
604
+ function enrichApprovalRow(webId, row, explicitIri) {
605
+ const createdAt = new Date(toIsoString(row.createdAt, new Date().toISOString()));
534
606
  return {
535
- id: db.resolveLocatorId(resource, locator),
536
- iri: db.resolveLocatorIri(resource, locator),
607
+ ...row,
608
+ approvalUri: explicitIri
609
+ ?? normalizeString(row.approvalUri)
610
+ ?? rowSubject(row)
611
+ ?? buildApprovalUriForDate(webId, row.id, createdAt),
537
612
  };
538
613
  }
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);
614
+ function createNativeRemoteApprovalStore(webId, fetcher) {
615
+ return {
616
+ listApprovals: () => listApprovalRows(webId, fetcher),
617
+ findApproval: (id, options) => findApprovalRow(webId, fetcher, id, options),
618
+ insertApproval: (row) => writeApprovalRow(webId, fetcher, row),
619
+ async updateApproval(id, patch, options = {}) {
620
+ const explicitIri = options.resourceUri
621
+ ?? normalizeString(patch.approvalUri)
622
+ ?? (options.createdAt ? buildApprovalResourceUrl(webId, id, new Date(toIsoString(options.createdAt, new Date().toISOString()))) : undefined);
623
+ const existing = explicitIri
624
+ ? await readApprovalRowFromResource(fetcher, explicitIri)
625
+ : (await listApprovalRows(webId, fetcher)).find((row) => row.id === id);
626
+ if (!existing) {
627
+ throw new Error(`Remote approval not found: ${id}`);
628
+ }
629
+ await writeApprovalRow(webId, fetcher, { ...existing, ...patch });
630
+ },
631
+ listAudits: () => listAuditRows(webId, fetcher),
632
+ insertAudit: (row) => writeAuditRow(webId, fetcher, row),
633
+ listGrants: () => listGrantRows(webId, fetcher),
634
+ insertGrant: (row) => writeGrantRow(webId, fetcher, row),
635
+ insertInboxNotification: (row) => writeInboxNotificationRow(webId, fetcher, row),
636
+ };
542
637
  }
543
- async function writeApprovalRow(db, row) {
544
- await db.insert(approvalResource).values(normalizeApprovalInsert(row)).execute();
638
+ async function findApprovalRow(webId, fetcher, id, options = {}) {
639
+ if (options.resourceUri) {
640
+ return readApprovalRowFromResource(fetcher, options.resourceUri);
641
+ }
642
+ if (options.createdAt) {
643
+ const createdAt = new Date(toIsoString(options.createdAt, new Date().toISOString()));
644
+ return readApprovalRowFromResource(fetcher, buildApprovalResourceUrl(webId, id, createdAt));
645
+ }
646
+ return (await listApprovalRows(webId, fetcher)).find((row) => row.id === id) ?? null;
545
647
  }
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);
648
+ async function readApprovalRowFromResource(fetcher, resourceUri) {
649
+ const turtle = await readTurtleResource(fetcher, documentUrlFromResourceUri(resourceUri));
650
+ if (!turtle) {
651
+ return null;
652
+ }
653
+ for (const [subject, predicates] of parseManagedTurtleBlocks(turtle, documentUrlFromResourceUri(resourceUri))) {
654
+ if (subject !== resourceUri) {
655
+ continue;
656
+ }
657
+ const row = approvalRowFromPredicates(subject, predicates);
658
+ if (row) {
659
+ return row;
660
+ }
661
+ }
662
+ return null;
549
663
  }
550
- async function writeAuditRow(db, row) {
551
- await db.insert(auditResource).values(normalizeAuditInsert(row)).execute();
664
+ async function listApprovalRows(webId, fetcher) {
665
+ const urls = [
666
+ ...recentApprovalDocumentUrls(webId),
667
+ ...await listTurtleResources(fetcher, `${getPodBaseUrl(webId)}/.data/approvals/`).catch(() => []),
668
+ ];
669
+ const rows = [];
670
+ for (const url of [...new Set(urls)].filter((entry) => entry.endsWith('.ttl'))) {
671
+ const turtle = await readTurtleResource(fetcher, url).catch(() => null);
672
+ if (!turtle)
673
+ continue;
674
+ for (const [subject, predicates] of parseManagedTurtleBlocks(turtle, url)) {
675
+ const row = approvalRowFromPredicates(subject, predicates);
676
+ if (row)
677
+ rows.push(row);
678
+ }
679
+ }
680
+ return rows;
681
+ }
682
+ function recentApprovalDocumentUrls(webId, days = DEFAULT_APPROVAL_LIST_DAYS) {
683
+ const urls = [];
684
+ const base = Date.now();
685
+ for (let offset = 0; offset < days; offset += 1) {
686
+ const date = new Date(base - offset * 24 * 60 * 60 * 1000);
687
+ urls.push(buildApprovalDocumentUrl(webId, date));
688
+ }
689
+ return urls;
690
+ }
691
+ async function writeApprovalRow(webId, fetcher, row) {
692
+ const createdAt = new Date(toIsoString(row.createdAt, new Date().toISOString()));
693
+ const documentUrl = buildApprovalDocumentUrl(webId, createdAt);
694
+ const subjectUrl = buildApprovalResourceUrl(webId, row.id, createdAt);
695
+ await upsertManagedTurtleBlock(fetcher, documentUrl, {
696
+ subject: subjectUrl,
697
+ triples: [
698
+ { predicate: RDF_TYPE, object: iri(UDFS.ApprovalRequest) },
699
+ { predicate: ApprovalVocab.session, object: iri(row.session) },
700
+ { predicate: ApprovalVocab.toolCallId, object: literal(row.toolCallId) },
701
+ { predicate: ApprovalVocab.toolName, object: literal(row.toolName) },
702
+ { predicate: ApprovalVocab.target, object: iri(row.target) },
703
+ { predicate: ApprovalVocab.action, object: iri(row.action) },
704
+ { predicate: ApprovalVocab.risk, object: literal(row.risk) },
705
+ { predicate: ApprovalVocab.status, object: literal(row.status) },
706
+ ...(row.assignedTo ? [{ predicate: ApprovalVocab.assignedTo, object: iri(row.assignedTo) }] : []),
707
+ ...(row.decisionBy ? [{ predicate: ApprovalVocab.decisionBy, object: iri(row.decisionBy) }] : []),
708
+ ...(row.decisionRole ? [{ predicate: ApprovalVocab.decisionRole, object: literal(row.decisionRole) }] : []),
709
+ ...(row.onBehalfOf ? [{ predicate: ApprovalVocab.onBehalfOf, object: iri(row.onBehalfOf) }] : []),
710
+ ...(row.reason ? [{ predicate: ApprovalVocab.reason, object: literal(row.reason) }] : []),
711
+ ...(row.context ? [{ predicate: ApprovalVocab.context, object: literal(row.context) }] : []),
712
+ ...(row.approvalOptions ? [{ predicate: ApprovalVocab.approvalOptions, object: literal(row.approvalOptions) }] : []),
713
+ ...(row.policyVersion ? [{ predicate: ApprovalVocab.policyVersion, object: literal(row.policyVersion) }] : []),
714
+ { predicate: ApprovalVocab.createdAt, object: literal(toIsoString(row.createdAt, new Date().toISOString())) },
715
+ ...(row.expiresAt ? [{ predicate: ApprovalVocab.expiresAt, object: literal(toIsoString(row.expiresAt, new Date().toISOString())) }] : []),
716
+ ...(row.resolvedAt ? [{ predicate: ApprovalVocab.resolvedAt, object: literal(toIsoString(row.resolvedAt, new Date().toISOString())) }] : []),
717
+ ],
718
+ });
719
+ }
720
+ async function listAuditRows(webId, fetcher) {
721
+ const urls = await listTurtleResourcesRecursive(fetcher, `${getPodBaseUrl(webId)}/.data/audits/`);
722
+ const rows = [];
723
+ for (const url of urls.filter((entry) => entry.endsWith('.ttl'))) {
724
+ const turtle = await readTurtleResource(fetcher, url).catch(() => null);
725
+ if (!turtle)
726
+ continue;
727
+ for (const [subject, predicates] of parseManagedTurtleBlocks(turtle, url)) {
728
+ const row = auditRowFromPredicates(subject, predicates);
729
+ if (row)
730
+ rows.push(row);
731
+ }
732
+ }
733
+ return rows;
734
+ }
735
+ async function writeAuditRow(webId, fetcher, row) {
736
+ const createdAt = new Date(toIsoString(row.createdAt, new Date().toISOString()));
737
+ const documentUrl = buildAuditDocumentUrl(webId, createdAt);
738
+ const subjectUrl = buildAuditResourceUrl(webId, row.id, createdAt);
739
+ await upsertManagedTurtleBlock(fetcher, documentUrl, {
740
+ subject: subjectUrl,
741
+ triples: [
742
+ { predicate: RDF_TYPE, object: iri(UDFS.AuditEntry) },
743
+ { predicate: AuditVocab.action, object: literal(row.action) },
744
+ { predicate: AuditVocab.actor, object: iri(row.actor) },
745
+ { predicate: AuditVocab.actorRole, object: literal(row.actorRole) },
746
+ ...(row.onBehalfOf ? [{ predicate: AuditVocab.onBehalfOf, object: iri(row.onBehalfOf) }] : []),
747
+ ...(row.session ? [{ predicate: AuditVocab.session, object: iri(row.session) }] : []),
748
+ ...(row.entry ? [{ predicate: AuditVocab.entry, object: iri(row.entry) }] : []),
749
+ ...(row.toolCallId ? [{ predicate: AuditVocab.toolCallId, object: literal(row.toolCallId) }] : []),
750
+ ...(row.toolName ? [{ predicate: AuditVocab.toolName, object: literal(row.toolName) }] : []),
751
+ ...(row.approval ? [{ predicate: AuditVocab.approval, object: iri(row.approval) }] : []),
752
+ ...(row.policyVersion ? [{ predicate: AuditVocab.policyVersion, object: literal(row.policyVersion) }] : []),
753
+ { predicate: AuditVocab.createdAt, object: literal(toIsoString(row.createdAt, new Date().toISOString())) },
754
+ ],
755
+ });
552
756
  }
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);
757
+ async function listGrantRows(webId, fetcher) {
758
+ const urls = [
759
+ ...await listTurtleResources(fetcher, `${getPodBaseUrl(webId)}/settings/autonomy/grants/`).catch(() => []),
760
+ ];
761
+ const rows = [];
762
+ for (const url of urls.filter((entry) => entry.endsWith('.ttl'))) {
763
+ const turtle = await readTurtleResource(fetcher, url).catch(() => null);
764
+ if (!turtle)
765
+ continue;
766
+ for (const [subject, predicates] of parseManagedTurtleBlocks(turtle, url)) {
767
+ const row = grantRowFromPredicates(subject, predicates);
768
+ if (row)
769
+ rows.push(row);
770
+ }
771
+ }
772
+ return rows;
556
773
  }
557
- async function writeGrantRow(db, row) {
774
+ async function writeGrantRow(webId, fetcher, row) {
558
775
  const id = normalizeString(row.id) ?? crypto.randomUUID();
776
+ const subjectUrl = buildGrantResourceUrl(webId, id);
777
+ const documentUrl = subjectUrl;
559
778
  const target = normalizeString(row.target);
560
779
  const action = normalizeString(row.action);
561
780
  const effect = normalizeString(row.effect);
@@ -564,190 +783,141 @@ async function writeGrantRow(db, row) {
564
783
  if (!target || !action || !effect || !decisionBy || !decisionRole) {
565
784
  throw new Error(`Invalid remote approval grant row: ${id}`);
566
785
  }
567
- await db.insert(grantResource).values(normalizeGrantInsert({ ...row, id, target, action, effect, decisionBy, decisionRole })).execute();
786
+ await upsertManagedTurtleBlock(fetcher, documentUrl, {
787
+ subject: subjectUrl,
788
+ triples: [
789
+ { predicate: RDF_TYPE, object: iri(ODRL.Policy) },
790
+ { predicate: RDF_TYPE, object: iri(UDFS.AutonomyGrant) },
791
+ { predicate: GrantVocab.target, object: iri(target) },
792
+ { predicate: GrantVocab.action, object: iri(action) },
793
+ ...(normalizeString(row.title) ? [{ predicate: GrantVocab.title, object: literal(truncatePodLiteral(normalizeString(row.title), 160)) }] : []),
794
+ ...(normalizeString(row.summary) ? [{ predicate: GrantVocab.summary, object: literal(truncatePodLiteral(normalizeString(row.summary), 500)) }] : []),
795
+ ...(normalizeString(row.body) ? [{ predicate: GrantVocab.body, object: literal(truncatePodLiteral(normalizeString(row.body), MAX_GRANT_POLICY_LENGTH)) }] : []),
796
+ ...(normalizeString(row.schema) ? [{ predicate: GrantVocab.schema, object: iri(normalizeString(row.schema)) }] : []),
797
+ ...(normalizeString(row.pageKind) ? [{ predicate: GrantVocab.pageKind, object: literal(normalizeString(row.pageKind)) }] : []),
798
+ ...(normalizeString(row.wikiStatus) ? [{ predicate: GrantVocab.wikiStatus, object: literal(normalizeString(row.wikiStatus)) }] : []),
799
+ ...(normalizeString(row.tags) ? [{ predicate: GrantVocab.tags, object: literal(truncatePodLiteral(normalizeString(row.tags), 500)) }] : []),
800
+ ...(normalizeString(row.source) ? [{ predicate: GrantVocab.source, object: literal(normalizeString(row.source)) }] : []),
801
+ ...(normalizeString(row.sourceHash) ? [{ predicate: GrantVocab.sourceHash, object: literal(normalizeString(row.sourceHash)) }] : []),
802
+ ...(row.compiledAt ? [{ predicate: GrantVocab.compiledAt, object: literal(toIsoString(row.compiledAt, new Date().toISOString())) }] : []),
803
+ ...(row.compiledFrom ?? []).map((value) => ({ predicate: GrantVocab.compiledFrom, object: iri(value) })),
804
+ ...(row.related ?? []).map((value) => ({ predicate: GrantVocab.related, object: iri(value) })),
805
+ { predicate: GrantVocab.effect, object: literal(effect) },
806
+ ...(normalizeString(row.riskCeiling) ? [{ predicate: GrantVocab.riskCeiling, object: literal(normalizeString(row.riskCeiling)) }] : []),
807
+ ...(normalizeString(row.policy) ? [{ predicate: GrantVocab.policy, object: literal(truncatePodLiteral(normalizeString(row.policy), MAX_GRANT_POLICY_LENGTH)) }] : []),
808
+ ...(normalizeString(row.context) ? [{ predicate: GrantVocab.context, object: literal(truncatePodLiteral(normalizeString(row.context), MAX_APPROVAL_CONTEXT_LENGTH)) }] : []),
809
+ { predicate: GrantVocab.decisionBy, object: iri(decisionBy) },
810
+ { predicate: GrantVocab.decisionRole, object: literal(decisionRole) },
811
+ ...(normalizeString(row.onBehalfOf) ? [{ predicate: GrantVocab.onBehalfOf, object: iri(normalizeString(row.onBehalfOf)) }] : []),
812
+ { predicate: GrantVocab.createdAt, object: literal(toIsoString(row.createdAt, new Date().toISOString())) },
813
+ ...(normalizeString(row.revokedAt) ? [{ predicate: GrantVocab.revokedAt, object: literal(normalizeString(row.revokedAt)) }] : []),
814
+ ],
815
+ });
568
816
  }
569
- async function writeInboxNotificationRow(db, row) {
570
- await db.insert(inboxNotificationTable).values(normalizeInboxNotificationInsert(row)).execute();
817
+ async function writeInboxNotificationRow(webId, fetcher, row) {
818
+ const url = buildInboxResourceUrl(webId, row.id);
819
+ await upsertManagedTurtleBlock(fetcher, url, {
820
+ subject: url,
821
+ triples: [
822
+ { predicate: RDF_TYPE, object: iri(AS.Announce) },
823
+ ...(row.actor ? [{ predicate: InboxNotificationVocab.actor, object: iri(row.actor) }] : []),
824
+ { predicate: InboxNotificationVocab.object, object: iri(row.object) },
825
+ { predicate: InboxNotificationVocab.createdAt, object: literal(toIsoString(row.createdAt, new Date().toISOString())) },
826
+ ],
827
+ });
571
828
  }
572
- function normalizeApprovalRow(row) {
573
- if (!row)
829
+ function approvalRowFromPredicates(url, predicates) {
830
+ const session = firstIri(predicates, ApprovalVocab.session);
831
+ const toolCallId = firstLiteral(predicates, ApprovalVocab.toolCallId);
832
+ const toolName = firstLiteral(predicates, ApprovalVocab.toolName);
833
+ const target = firstIri(predicates, ApprovalVocab.target);
834
+ const action = firstIri(predicates, ApprovalVocab.action);
835
+ const risk = firstLiteral(predicates, ApprovalVocab.risk);
836
+ const status = firstLiteral(predicates, ApprovalVocab.status);
837
+ const createdAt = firstLiteral(predicates, ApprovalVocab.createdAt);
838
+ if (!session || !toolCallId || !toolName || !target || !action || !risk || !status || !createdAt) {
574
839
  return null;
840
+ }
575
841
  return {
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,
842
+ id: subjectIdFromResourceUrl(url),
843
+ session,
844
+ toolCallId,
845
+ toolName,
846
+ target,
847
+ action,
848
+ risk,
849
+ status,
850
+ assignedTo: firstIri(predicates, ApprovalVocab.assignedTo),
851
+ decisionBy: firstIri(predicates, ApprovalVocab.decisionBy),
852
+ decisionRole: firstLiteral(predicates, ApprovalVocab.decisionRole),
853
+ onBehalfOf: firstIri(predicates, ApprovalVocab.onBehalfOf),
854
+ reason: firstLiteral(predicates, ApprovalVocab.reason),
855
+ context: firstLiteral(predicates, ApprovalVocab.context),
856
+ approvalOptions: firstLiteral(predicates, ApprovalVocab.approvalOptions),
857
+ policyVersion: firstLiteral(predicates, ApprovalVocab.policyVersion),
858
+ createdAt,
859
+ expiresAt: firstLiteral(predicates, ApprovalVocab.expiresAt),
860
+ resolvedAt: firstLiteral(predicates, ApprovalVocab.resolvedAt),
596
861
  };
597
862
  }
598
- function normalizeAuditRow(row) {
599
- if (!row)
863
+ function auditRowFromPredicates(url, predicates) {
864
+ const action = firstLiteral(predicates, AuditVocab.action);
865
+ const actor = firstIri(predicates, AuditVocab.actor);
866
+ const actorRole = firstLiteral(predicates, AuditVocab.actorRole);
867
+ const createdAt = firstLiteral(predicates, AuditVocab.createdAt);
868
+ if (!action || !actor || !actorRole || !createdAt) {
600
869
  return null;
870
+ }
601
871
  return {
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,
872
+ id: subjectIdFromResourceUrl(url),
873
+ action,
874
+ actor,
875
+ actorRole,
876
+ onBehalfOf: firstIri(predicates, AuditVocab.onBehalfOf),
877
+ session: firstIri(predicates, AuditVocab.session),
878
+ entry: firstIri(predicates, AuditVocab.entry),
879
+ toolCallId: firstLiteral(predicates, AuditVocab.toolCallId),
880
+ toolName: firstLiteral(predicates, AuditVocab.toolName),
881
+ approval: firstIri(predicates, AuditVocab.approval),
882
+ policyVersion: firstLiteral(predicates, AuditVocab.policyVersion),
883
+ createdAt,
614
884
  };
615
885
  }
616
- function normalizeGrantRow(row) {
617
- if (!row)
886
+ function grantRowFromPredicates(url, predicates) {
887
+ const target = firstIri(predicates, GrantVocab.target);
888
+ const action = firstIri(predicates, GrantVocab.action);
889
+ const effect = firstLiteral(predicates, GrantVocab.effect);
890
+ const decisionBy = firstIri(predicates, GrantVocab.decisionBy);
891
+ const decisionRole = firstLiteral(predicates, GrantVocab.decisionRole);
892
+ const createdAt = firstLiteral(predicates, GrantVocab.createdAt);
893
+ if (!target || !action || !effect || !decisionBy || !decisionRole || !createdAt) {
618
894
  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
- }
692
895
  }
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) {
702
896
  return {
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())),
897
+ id: subjectIdFromResourceUrl(url),
898
+ target,
899
+ action,
900
+ title: firstLiteral(predicates, GrantVocab.title),
901
+ summary: firstLiteral(predicates, GrantVocab.summary),
902
+ body: firstLiteral(predicates, GrantVocab.body),
903
+ schema: firstIri(predicates, GrantVocab.schema),
904
+ pageKind: firstLiteral(predicates, GrantVocab.pageKind),
905
+ wikiStatus: firstLiteral(predicates, GrantVocab.wikiStatus),
906
+ tags: firstLiteral(predicates, GrantVocab.tags),
907
+ source: firstLiteral(predicates, GrantVocab.source),
908
+ sourceHash: firstLiteral(predicates, GrantVocab.sourceHash),
909
+ compiledAt: firstLiteral(predicates, GrantVocab.compiledAt),
910
+ compiledFrom: iriValues(predicates, GrantVocab.compiledFrom),
911
+ related: iriValues(predicates, GrantVocab.related),
912
+ effect,
913
+ riskCeiling: firstLiteral(predicates, GrantVocab.riskCeiling),
914
+ policy: firstLiteral(predicates, GrantVocab.policy),
915
+ context: firstLiteral(predicates, GrantVocab.context),
916
+ decisionBy,
917
+ decisionRole,
918
+ onBehalfOf: firstIri(predicates, GrantVocab.onBehalfOf),
919
+ createdAt,
920
+ revokedAt: firstLiteral(predicates, GrantVocab.revokedAt),
751
921
  };
752
922
  }
753
923
  function isActiveAllowGrant(grant) {
@@ -790,7 +960,7 @@ async function resolveSemanticGrantDecision(options) {
790
960
  if (candidates.length === 0) {
791
961
  return null;
792
962
  }
793
- const resolver = options.runtime.resolveGrantCoverage ?? resolveWatchGrantCoverage;
963
+ const resolver = options.runtime.resolveGrantCoverage ?? resolveAutoModeGrantCoverage;
794
964
  for (const grant of candidates) {
795
965
  const coverage = await resolver({
796
966
  record: options.record,
@@ -804,7 +974,7 @@ async function resolveSemanticGrantDecision(options) {
804
974
  }
805
975
  return null;
806
976
  }
807
- function buildWatchGrantRequestContext(input) {
977
+ function buildAutoModeGrantRequestContext(input) {
808
978
  return {
809
979
  session: buildThreadUri(input.webId, input.record),
810
980
  target: buildThreadUri(input.webId, input.record),
@@ -827,7 +997,7 @@ function buildGenericGrantRequestContext(input) {
827
997
  kind: input.request.kind,
828
998
  };
829
999
  }
830
- export async function createRemoteWatchApproval(options) {
1000
+ export async function createRemoteAutoModeApproval(options) {
831
1001
  const activeRuntime = options.runtime ?? await createDefaultRuntime();
832
1002
  return createRemoteApproval({
833
1003
  subject: ({ webId }) => ({
@@ -861,12 +1031,10 @@ export async function createRemoteApproval(options) {
861
1031
  const request = typeof options.request === 'function'
862
1032
  ? options.request({ webId, stored, sessionUri: subject.sessionUri })
863
1033
  : options.request;
864
- const approvalLocalId = crypto.randomUUID();
1034
+ const approvalId = crypto.randomUUID();
865
1035
  const now = activeRuntime.now();
866
- const approvalReference = store.resolveApprovalReference({ id: approvalLocalId, createdAt: now });
867
- const approvalId = approvalReference.id;
868
1036
  const sessionUri = subject.sessionUri;
869
- const approvalUri = approvalReference.iri;
1037
+ const approvalUri = buildApprovalUriForDate(webId, approvalId, now);
870
1038
  const targetUri = subject.targetUri ?? sessionUri;
871
1039
  const assignedTo = subject.assignedTo ?? webId;
872
1040
  const onBehalfOf = subject.onBehalfOf ?? webId;
@@ -877,7 +1045,6 @@ export async function createRemoteApproval(options) {
877
1045
  const context = compactApprovalContext(request);
878
1046
  await store.insertApproval({
879
1047
  id: approvalId,
880
- approvalUri,
881
1048
  session: sessionUri,
882
1049
  toolCallId: request.toolCallId,
883
1050
  toolName: request.toolName,
@@ -932,7 +1099,7 @@ export async function createRemoteApproval(options) {
932
1099
  });
933
1100
  });
934
1101
  }
935
- export async function waitForRemoteWatchApproval(options) {
1102
+ export async function waitForRemoteAutoModeApproval(options) {
936
1103
  const activeRuntime = options.runtime ?? await createDefaultRuntime();
937
1104
  return withRemoteApprovalStore(activeRuntime, async ({ store }) => {
938
1105
  while (true) {
@@ -955,7 +1122,7 @@ export async function waitForRemoteWatchApproval(options) {
955
1122
  }
956
1123
  });
957
1124
  }
958
- export async function requestRemoteWatchApproval(options) {
1125
+ export async function requestRemoteAutoModeApproval(options) {
959
1126
  const activeRuntime = options.runtime ?? await createDefaultRuntime();
960
1127
  const delegated = await withRemoteApprovalStore(activeRuntime, async ({ store, webId }) => {
961
1128
  const grants = await store.listGrants();
@@ -964,7 +1131,7 @@ export async function requestRemoteWatchApproval(options) {
964
1131
  grants,
965
1132
  record: options.record,
966
1133
  request: options.request,
967
- requestContext: buildWatchGrantRequestContext({
1134
+ requestContext: buildAutoModeGrantRequestContext({
968
1135
  webId,
969
1136
  record: options.record,
970
1137
  request: options.request,
@@ -974,12 +1141,12 @@ export async function requestRemoteWatchApproval(options) {
974
1141
  if (delegated) {
975
1142
  return delegated;
976
1143
  }
977
- const summary = await createRemoteWatchApproval({
1144
+ const summary = await createRemoteAutoModeApproval({
978
1145
  record: options.record,
979
1146
  request: options.request,
980
1147
  runtime: activeRuntime,
981
1148
  });
982
- return waitForRemoteWatchApproval({
1149
+ return waitForRemoteAutoModeApproval({
983
1150
  approvalId: summary.id,
984
1151
  approvalUri: summary.approvalUri,
985
1152
  pollMs: options.pollMs,
@@ -987,7 +1154,7 @@ export async function requestRemoteWatchApproval(options) {
987
1154
  runtime: activeRuntime,
988
1155
  });
989
1156
  }
990
- export async function resolveExistingRemoteWatchGrant(options) {
1157
+ export async function resolveExistingRemoteAutoModeGrant(options) {
991
1158
  const activeRuntime = options.runtime ?? await createDefaultRuntime();
992
1159
  return withRemoteApprovalStore(activeRuntime, async ({ store, webId }) => {
993
1160
  const grants = await store.listGrants();
@@ -996,7 +1163,7 @@ export async function resolveExistingRemoteWatchGrant(options) {
996
1163
  grants,
997
1164
  record: options.record,
998
1165
  request: options.request,
999
- requestContext: buildWatchGrantRequestContext({
1166
+ requestContext: buildAutoModeGrantRequestContext({
1000
1167
  webId,
1001
1168
  record: options.record,
1002
1169
  request: options.request,
@@ -1034,7 +1201,7 @@ export async function requestRemoteApproval(options) {
1034
1201
  request: options.request,
1035
1202
  runtime: activeRuntime,
1036
1203
  });
1037
- return waitForRemoteWatchApproval({
1204
+ return waitForRemoteAutoModeApproval({
1038
1205
  approvalId: summary.id,
1039
1206
  approvalUri: summary.approvalUri,
1040
1207
  pollMs: options.pollMs,
@@ -1042,7 +1209,7 @@ export async function requestRemoteApproval(options) {
1042
1209
  runtime: activeRuntime,
1043
1210
  });
1044
1211
  }
1045
- export async function listRemoteWatchApprovals(options = {}) {
1212
+ export async function listRemoteAutoModeApprovals(options = {}) {
1046
1213
  const activeRuntime = options.runtime ?? await createDefaultRuntime();
1047
1214
  const requestedStatus = options.status ?? 'pending';
1048
1215
  return withRemoteApprovalStore(activeRuntime, async ({ store, webId }) => {
@@ -1054,7 +1221,7 @@ export async function listRemoteWatchApprovals(options = {}) {
1054
1221
  .sort((left, right) => right.createdAt.localeCompare(left.createdAt));
1055
1222
  });
1056
1223
  }
1057
- export async function resolveRemoteWatchApproval(options) {
1224
+ export async function resolveRemoteAutoModeApproval(options) {
1058
1225
  const activeRuntime = options.runtime ?? await createDefaultRuntime();
1059
1226
  return withRemoteApprovalStore(activeRuntime, async ({ store, webId }) => {
1060
1227
  const row = await readRemoteApprovalRow(store, {
@@ -1068,20 +1235,21 @@ export async function resolveRemoteWatchApproval(options) {
1068
1235
  return normalizeApprovalSummary(row);
1069
1236
  }
1070
1237
  const now = activeRuntime.now();
1071
- const approvalUri = normalizeString(row.approvalUri)
1072
- ?? store.resolveApprovalReference({ id: row.id, createdAt: row.createdAt }).iri;
1238
+ const approvalCreatedAt = new Date(toIsoString(row.createdAt, now.toISOString()));
1239
+ const approvalUri = buildApprovalUriForDate(row.session, row.id, approvalCreatedAt);
1073
1240
  const nextStatus = options.decision === 'accept' || options.decision === 'accept_for_session'
1074
1241
  ? 'approved'
1075
1242
  : 'rejected';
1076
1243
  const decisionRole = options.decisionRole ?? 'human';
1077
1244
  await store.updateApproval(row.id, {
1078
- approvalUri,
1079
1245
  status: nextStatus,
1080
1246
  decisionBy: webId,
1081
1247
  decisionRole,
1082
1248
  onBehalfOf: webId,
1083
1249
  reason: encodeDecisionReason(options.decision, options.note),
1084
1250
  resolvedAt: now,
1251
+ }, {
1252
+ resourceUri: options.approvalUri ?? row.approvalUri ?? approvalUri,
1085
1253
  });
1086
1254
  await warnOnly(activeRuntime, () => store.insertAudit({
1087
1255
  id: crypto.randomUUID(),
@@ -1128,7 +1296,7 @@ export async function resolveRemoteWatchApproval(options) {
1128
1296
  await warnOnly(activeRuntime, () => store.insertInboxNotification({
1129
1297
  id: crypto.randomUUID(),
1130
1298
  actor: webId,
1131
- object: store.resolveGrantReference({ id: grantId }).iri,
1299
+ object: buildGrantUri(row.session, grantId),
1132
1300
  createdAt: now,
1133
1301
  }));
1134
1302
  }
@@ -1140,7 +1308,6 @@ export async function resolveRemoteWatchApproval(options) {
1140
1308
  }));
1141
1309
  const nextRow = {
1142
1310
  ...row,
1143
- approvalUri,
1144
1311
  status: nextStatus,
1145
1312
  decisionBy: webId,
1146
1313
  decisionRole,
@@ -1154,9 +1321,11 @@ export async function resolveRemoteWatchApproval(options) {
1154
1321
  async function readRemoteApprovalRow(store, options) {
1155
1322
  if (store.findApproval) {
1156
1323
  const row = await store.findApproval(options.approvalId, {
1157
- approvalUri: options.approvalUri,
1324
+ resourceUri: options.approvalUri,
1158
1325
  });
1159
- return row;
1326
+ if (row || options.approvalUri) {
1327
+ return row;
1328
+ }
1160
1329
  }
1161
1330
  const approvals = await store.listApprovals();
1162
1331
  return approvals.find((entry) => entry.id === options.approvalId) ?? null;
@@ -1167,6 +1336,7 @@ export const __podApprovalInternal = {
1167
1336
  buildActionUri,
1168
1337
  buildRisk,
1169
1338
  buildToolName,
1339
+ createSharedModelRemoteApprovalStore,
1170
1340
  createNativeRemoteApprovalStore,
1171
1341
  extractToolCallId,
1172
1342
  decisionFromApprovalRow,