@undefineds.co/linx 0.2.14 → 0.2.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -6
- package/dist/index.js +50 -104
- package/dist/index.js.map +1 -1
- package/dist/lib/ai-command.js +38 -60
- package/dist/lib/ai-command.js.map +1 -1
- package/dist/lib/pi-adapter/branding.js +2 -2
- package/dist/lib/pi-adapter/branding.js.map +1 -1
- package/dist/lib/pi-adapter/pod-approval.js +1 -114
- package/dist/lib/pi-adapter/pod-approval.js.map +1 -1
- package/dist/lib/pi-adapter/pod-native.js +3 -2
- package/dist/lib/pi-adapter/pod-native.js.map +1 -1
- package/dist/lib/pi-adapter/runtime.js +2 -2
- package/dist/lib/pi-adapter/runtime.js.map +1 -1
- package/dist/lib/pod-data-session.js +43 -3
- package/dist/lib/pod-data-session.js.map +1 -1
- package/dist/lib/watch/hooks/claude.js +4 -0
- package/dist/lib/watch/hooks/claude.js.map +1 -1
- package/dist/lib/watch/hooks/codebuddy.js +4 -0
- package/dist/lib/watch/hooks/codebuddy.js.map +1 -1
- package/dist/lib/watch/hooks/codex.js +4 -0
- package/dist/lib/watch/hooks/codex.js.map +1 -1
- package/dist/lib/watch/pod-ai.js +22 -14
- package/dist/lib/watch/pod-ai.js.map +1 -1
- package/dist/lib/watch/pod-approval.js +444 -40
- package/dist/lib/watch/pod-approval.js.map +1 -1
- package/dist/lib/watch/pod-persistence.js +162 -43
- package/dist/lib/watch/pod-persistence.js.map +1 -1
- package/dist/lib/watch/runner.js +243 -38
- package/dist/lib/watch/runner.js.map +1 -1
- package/dist/lib/watch/secretary.js +238 -0
- package/dist/lib/watch/secretary.js.map +1 -0
- package/dist/watch-cli.js +8 -34
- package/dist/watch-cli.js.map +1 -1
- package/package.json +3 -2
- package/vendor/agent-runtime/dist/acp.d.ts +27 -0
- package/vendor/agent-runtime/dist/acp.js +86 -0
- package/vendor/agent-runtime/dist/companion-model.d.ts +7 -0
- package/vendor/agent-runtime/dist/companion-model.js +12 -0
- package/vendor/agent-runtime/dist/index.d.ts +3 -0
- package/vendor/agent-runtime/dist/index.js +3 -0
- package/vendor/agent-runtime/dist/turn-controller.d.ts +69 -0
- package/vendor/agent-runtime/dist/turn-controller.js +129 -0
- package/vendor/agent-runtime/package.json +11 -0
|
@@ -2,11 +2,18 @@ import { setTimeout as delay } from 'node:timers/promises';
|
|
|
2
2
|
import { getDefaultPodDataSession } from '../pod-data-session.js';
|
|
3
3
|
import { AS, ODRL, UDFS } from '@undefineds.co/models/namespaces';
|
|
4
4
|
import { ApprovalVocab, AuditVocab, GrantVocab, InboxNotificationVocab } from '@undefineds.co/models/vocab/sidecar';
|
|
5
|
+
import { resolveWatchGrantCoverage } from './secretary.js';
|
|
5
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';
|
|
6
|
-
const
|
|
7
|
+
const WATCH_CHAT_ID_PREFIX = 'linx-watch';
|
|
7
8
|
const WATCH_AGENT_ID = 'linx-watch-assistant';
|
|
8
9
|
const REMOTE_APPROVAL_POLICY_VERSION = 'linx-watch-remote-approval/v1';
|
|
9
10
|
const DEFAULT_REMOTE_APPROVAL_POLL_MS = 1000;
|
|
11
|
+
const DEFAULT_WARN_ONLY_TIMEOUT_MS = 5000;
|
|
12
|
+
const DEFAULT_APPROVAL_LIST_DAYS = 7;
|
|
13
|
+
const MAX_GRANT_POLICY_LENGTH = 1200;
|
|
14
|
+
const MAX_APPROVAL_CONTEXT_LENGTH = 1400;
|
|
15
|
+
const MIN_GRANT_COVERAGE_CONFIDENCE = 0.75;
|
|
16
|
+
const MAX_GRANT_COVERAGE_CANDIDATES = 5;
|
|
10
17
|
const remoteApprovalClientCache = new WeakMap();
|
|
11
18
|
function createAbortError() {
|
|
12
19
|
const error = new Error('The operation was aborted.');
|
|
@@ -38,8 +45,11 @@ function getPodBaseUrl(webIdOrUri) {
|
|
|
38
45
|
}
|
|
39
46
|
return webIdOrUri.replace(/\/$/, '');
|
|
40
47
|
}
|
|
41
|
-
function
|
|
42
|
-
return `${
|
|
48
|
+
function buildWatchChatId(record) {
|
|
49
|
+
return `${WATCH_CHAT_ID_PREFIX}-${record.backend}`;
|
|
50
|
+
}
|
|
51
|
+
function buildThreadUri(webId, record) {
|
|
52
|
+
return `${getPodBaseUrl(webId)}/.data/chat/${buildWatchChatId(record)}/index.ttl#${record.id}`;
|
|
43
53
|
}
|
|
44
54
|
function buildApprovalUri(webIdOrUri, approvalId) {
|
|
45
55
|
return buildApprovalResourceUrl(webIdOrUri, approvalId);
|
|
@@ -47,11 +57,14 @@ function buildApprovalUri(webIdOrUri, approvalId) {
|
|
|
47
57
|
function buildApprovalUriForDate(webIdOrUri, approvalId, createdAt) {
|
|
48
58
|
return buildApprovalResourceUrl(webIdOrUri, approvalId, createdAt);
|
|
49
59
|
}
|
|
60
|
+
function documentUrlFromResourceUri(resourceUri) {
|
|
61
|
+
return resourceUri.split('#', 1)[0] ?? resourceUri;
|
|
62
|
+
}
|
|
50
63
|
function buildGrantUri(webIdOrUri, grantId) {
|
|
51
64
|
return buildGrantResourceUrl(webIdOrUri, grantId);
|
|
52
65
|
}
|
|
53
|
-
function
|
|
54
|
-
return `${getPodBaseUrl(webIdOrUri)}/settings/autonomy/
|
|
66
|
+
function buildGrantSchemaUri(webIdOrUri) {
|
|
67
|
+
return `${getPodBaseUrl(webIdOrUri)}/settings/autonomy/schema/grant.ttl#GrantWikiPage`;
|
|
55
68
|
}
|
|
56
69
|
function buildAgentUri(webId) {
|
|
57
70
|
return `${getPodBaseUrl(webId)}/.data/agents/${WATCH_AGENT_ID}.ttl`;
|
|
@@ -150,7 +163,12 @@ function parseDecisionReason(value) {
|
|
|
150
163
|
}
|
|
151
164
|
async function warnOnly(runtime, task) {
|
|
152
165
|
try {
|
|
153
|
-
await
|
|
166
|
+
await Promise.race([
|
|
167
|
+
task(),
|
|
168
|
+
runtime.sleep(DEFAULT_WARN_ONLY_TIMEOUT_MS).then(() => {
|
|
169
|
+
throw new Error(`Pod side-effect sync timed out after ${DEFAULT_WARN_ONLY_TIMEOUT_MS}ms`);
|
|
170
|
+
}),
|
|
171
|
+
]);
|
|
154
172
|
}
|
|
155
173
|
catch (error) {
|
|
156
174
|
if (runtime.onWarning) {
|
|
@@ -169,6 +187,168 @@ function safeJsonStringify(value) {
|
|
|
169
187
|
return JSON.stringify({ error: 'unserializable_context' });
|
|
170
188
|
}
|
|
171
189
|
}
|
|
190
|
+
function truncatePodLiteral(value, maxLength) {
|
|
191
|
+
if (value.length <= maxLength) {
|
|
192
|
+
return value;
|
|
193
|
+
}
|
|
194
|
+
return `${value.slice(0, Math.max(0, maxLength - 15))}...[truncated]`;
|
|
195
|
+
}
|
|
196
|
+
function safeCompactJson(value, maxLength) {
|
|
197
|
+
return truncatePodLiteral(safeJsonStringify(value), maxLength);
|
|
198
|
+
}
|
|
199
|
+
function compactApprovalContext(request) {
|
|
200
|
+
return safeCompactJson({
|
|
201
|
+
kind: request.kind,
|
|
202
|
+
message: request.message,
|
|
203
|
+
toolName: request.toolName,
|
|
204
|
+
action: request.action,
|
|
205
|
+
risk: request.risk,
|
|
206
|
+
...(request.command ? { command: request.command } : {}),
|
|
207
|
+
...(request.cwd ? { cwd: request.cwd } : {}),
|
|
208
|
+
...(request.approvalOptions ? { approvalOptions: request.approvalOptions } : {}),
|
|
209
|
+
...(request.expiresAt ? { expiresAt: normalizeDateLike(request.expiresAt) } : {}),
|
|
210
|
+
...(request.context ? { sourceContext: truncatePodLiteral(request.context, 500) } : {}),
|
|
211
|
+
}, MAX_APPROVAL_CONTEXT_LENGTH);
|
|
212
|
+
}
|
|
213
|
+
function grantWikiTitleFromApproval(row, explicitTitle) {
|
|
214
|
+
const explicit = normalizeString(explicitTitle);
|
|
215
|
+
if (explicit) {
|
|
216
|
+
return truncatePodLiteral(explicit, 160);
|
|
217
|
+
}
|
|
218
|
+
return truncatePodLiteral(`${row.toolName} grant wiki for ${extractSessionId(row.session)}`, 160);
|
|
219
|
+
}
|
|
220
|
+
function grantWikiSummaryFromApproval(row, explicitSummary) {
|
|
221
|
+
const explicit = normalizeString(explicitSummary);
|
|
222
|
+
if (explicit) {
|
|
223
|
+
return truncatePodLiteral(explicit, 500);
|
|
224
|
+
}
|
|
225
|
+
return truncatePodLiteral(`Authorization wiki page for ${row.toolName}. AI Secretary must read the page body before reusing this grant.`, 500);
|
|
226
|
+
}
|
|
227
|
+
function grantWikiBodyFromApproval(row, explicitBody) {
|
|
228
|
+
const explicit = normalizeString(explicitBody);
|
|
229
|
+
if (explicit) {
|
|
230
|
+
return truncatePodLiteral(explicit, MAX_GRANT_POLICY_LENGTH);
|
|
231
|
+
}
|
|
232
|
+
return truncatePodLiteral([
|
|
233
|
+
'# Grant Semantics',
|
|
234
|
+
'',
|
|
235
|
+
'This page follows the LLM Wiki pattern: it is the maintained wiki view AI Secretary reads before reusing an authorization.',
|
|
236
|
+
'',
|
|
237
|
+
'## Covers',
|
|
238
|
+
`- Requests semantically inside target ${row.target}.`,
|
|
239
|
+
`- Action family ${row.action}.`,
|
|
240
|
+
`- Risk no higher than ${row.risk}.`,
|
|
241
|
+
'',
|
|
242
|
+
'## Does Not Cover',
|
|
243
|
+
'- Requests that are materially broader than the source approval.',
|
|
244
|
+
'- Requests that change from read-oriented to write/destructive behavior.',
|
|
245
|
+
'- Requests that touch credentials, secrets, package installation, new network side effects, or workspace boundaries unless explicitly documented here.',
|
|
246
|
+
'',
|
|
247
|
+
'## Source Context',
|
|
248
|
+
row.context ?? safeJsonStringify({ toolName: row.toolName, action: row.action, risk: row.risk }),
|
|
249
|
+
].join('\n'), MAX_GRANT_POLICY_LENGTH);
|
|
250
|
+
}
|
|
251
|
+
function grantIndexTextFromWikiBody(body) {
|
|
252
|
+
return truncatePodLiteral(body, MAX_GRANT_POLICY_LENGTH);
|
|
253
|
+
}
|
|
254
|
+
function grantWikiTagsFromApproval(row, explicitTags) {
|
|
255
|
+
const tags = [
|
|
256
|
+
'autonomy',
|
|
257
|
+
'grant',
|
|
258
|
+
row.toolName,
|
|
259
|
+
row.risk,
|
|
260
|
+
...(explicitTags ?? []),
|
|
261
|
+
]
|
|
262
|
+
.map((tag) => tag.trim())
|
|
263
|
+
.filter(Boolean);
|
|
264
|
+
return safeJsonStringify([...new Set(tags)]);
|
|
265
|
+
}
|
|
266
|
+
function grantContextFromApproval(row) {
|
|
267
|
+
return safeCompactJson({
|
|
268
|
+
sourceApproval: buildApprovalUriForDate(row.session, row.id, new Date(toIsoString(row.createdAt, new Date().toISOString()))),
|
|
269
|
+
session: row.session,
|
|
270
|
+
toolCallId: row.toolCallId,
|
|
271
|
+
toolName: row.toolName,
|
|
272
|
+
target: row.target,
|
|
273
|
+
action: row.action,
|
|
274
|
+
risk: row.risk,
|
|
275
|
+
approvalContext: row.context,
|
|
276
|
+
}, MAX_APPROVAL_CONTEXT_LENGTH);
|
|
277
|
+
}
|
|
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
|
+
function grantSourceHash(row) {
|
|
289
|
+
return `approval:${row.id}:${row.toolCallId}:${row.risk}`;
|
|
290
|
+
}
|
|
291
|
+
function encodeApprovalOptions(options) {
|
|
292
|
+
if (!options || options.length === 0) {
|
|
293
|
+
return undefined;
|
|
294
|
+
}
|
|
295
|
+
return safeJsonStringify(options);
|
|
296
|
+
}
|
|
297
|
+
function parseApprovalOptions(value) {
|
|
298
|
+
if (typeof value !== 'string' || !value.trim()) {
|
|
299
|
+
return undefined;
|
|
300
|
+
}
|
|
301
|
+
try {
|
|
302
|
+
const parsed = JSON.parse(value);
|
|
303
|
+
if (!Array.isArray(parsed)) {
|
|
304
|
+
return undefined;
|
|
305
|
+
}
|
|
306
|
+
const options = parsed
|
|
307
|
+
.map((option) => {
|
|
308
|
+
if (!isRecord(option)) {
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
const optionId = normalizeString(option.optionId);
|
|
312
|
+
const label = normalizeString(option.label);
|
|
313
|
+
if (!optionId || !label) {
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
const kind = normalizeString(option.kind);
|
|
317
|
+
const description = normalizeString(option.description);
|
|
318
|
+
return {
|
|
319
|
+
optionId,
|
|
320
|
+
label,
|
|
321
|
+
...(kind ? { kind } : {}),
|
|
322
|
+
...(description ? { description } : {}),
|
|
323
|
+
};
|
|
324
|
+
})
|
|
325
|
+
.filter((option) => option !== null);
|
|
326
|
+
return options.length > 0 ? options : undefined;
|
|
327
|
+
}
|
|
328
|
+
catch {
|
|
329
|
+
return undefined;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
function normalizeDateLike(value) {
|
|
333
|
+
if (value instanceof Date) {
|
|
334
|
+
return Number.isFinite(value.getTime()) ? value.toISOString() : undefined;
|
|
335
|
+
}
|
|
336
|
+
if (typeof value !== 'string' || !value.trim()) {
|
|
337
|
+
return undefined;
|
|
338
|
+
}
|
|
339
|
+
const parsed = new Date(value);
|
|
340
|
+
return Number.isFinite(parsed.getTime()) ? parsed.toISOString() : undefined;
|
|
341
|
+
}
|
|
342
|
+
function resolveApprovalExpiresAt(request, now) {
|
|
343
|
+
const explicit = normalizeDateLike(request.expiresAt);
|
|
344
|
+
if (explicit) {
|
|
345
|
+
return explicit;
|
|
346
|
+
}
|
|
347
|
+
if (typeof request.timeoutMs === 'number' && Number.isFinite(request.timeoutMs) && request.timeoutMs > 0) {
|
|
348
|
+
return new Date(now.getTime() + request.timeoutMs);
|
|
349
|
+
}
|
|
350
|
+
return undefined;
|
|
351
|
+
}
|
|
172
352
|
function extractSessionId(sessionUri) {
|
|
173
353
|
if (sessionUri.includes('#')) {
|
|
174
354
|
return sessionUri.split('#').pop() || sessionUri;
|
|
@@ -193,8 +373,10 @@ function normalizeApprovalSummary(row) {
|
|
|
193
373
|
const createdAt = toIsoString(row.createdAt, new Date(0).toISOString());
|
|
194
374
|
const sessionUri = row.session;
|
|
195
375
|
const decision = decisionFromApprovalRow(row);
|
|
376
|
+
const approvalOptions = parseApprovalOptions(row.approvalOptions);
|
|
196
377
|
return {
|
|
197
378
|
id: row.id,
|
|
379
|
+
...(normalizeString(row.approvalUri) ? { approvalUri: normalizeString(row.approvalUri) } : {}),
|
|
198
380
|
sessionId: extractSessionId(sessionUri),
|
|
199
381
|
sessionUri,
|
|
200
382
|
toolCallId: row.toolCallId,
|
|
@@ -205,7 +387,9 @@ function normalizeApprovalSummary(row) {
|
|
|
205
387
|
...(normalizeString(row.assignedTo) ? { assignedTo: normalizeString(row.assignedTo) } : {}),
|
|
206
388
|
...(normalizeString(row.decisionBy) ? { decisionBy: normalizeString(row.decisionBy) } : {}),
|
|
207
389
|
...(decision ? { decision } : {}),
|
|
390
|
+
...(approvalOptions ? { approvalOptions } : {}),
|
|
208
391
|
createdAt,
|
|
392
|
+
...(row.expiresAt ? { expiresAt: toIsoString(row.expiresAt, createdAt) } : {}),
|
|
209
393
|
...(row.resolvedAt ? { resolvedAt: toIsoString(row.resolvedAt, createdAt) } : {}),
|
|
210
394
|
};
|
|
211
395
|
}
|
|
@@ -251,6 +435,7 @@ async function createDefaultRuntime() {
|
|
|
251
435
|
now() {
|
|
252
436
|
return new Date();
|
|
253
437
|
},
|
|
438
|
+
resolveGrantCoverage: resolveWatchGrantCoverage,
|
|
254
439
|
};
|
|
255
440
|
}
|
|
256
441
|
async function withRemoteApprovalStore(runtime, fn) {
|
|
@@ -295,6 +480,7 @@ async function createRemoteApprovalClient(runtime) {
|
|
|
295
480
|
function createNativeRemoteApprovalStore(webId, fetcher) {
|
|
296
481
|
return {
|
|
297
482
|
listApprovals: () => listApprovalRows(webId, fetcher),
|
|
483
|
+
findApproval: (id, options) => findApprovalRow(webId, fetcher, id, options),
|
|
298
484
|
insertApproval: (row) => writeApprovalRow(webId, fetcher, row),
|
|
299
485
|
async updateApproval(id, patch) {
|
|
300
486
|
const existing = (await listApprovalRows(webId, fetcher)).find((row) => row.id === id);
|
|
@@ -310,14 +496,39 @@ function createNativeRemoteApprovalStore(webId, fetcher) {
|
|
|
310
496
|
insertInboxNotification: (row) => writeInboxNotificationRow(webId, fetcher, row),
|
|
311
497
|
};
|
|
312
498
|
}
|
|
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
|
+
}
|
|
313
525
|
async function listApprovalRows(webId, fetcher) {
|
|
314
|
-
const
|
|
315
|
-
|
|
316
|
-
listTurtleResources(fetcher, `${getPodBaseUrl(webId)}/.data/approvals/`).catch(() => []),
|
|
317
|
-
]
|
|
318
|
-
const urls = [...new Set([...currentUrls, ...legacyUrls])];
|
|
526
|
+
const urls = [
|
|
527
|
+
...recentApprovalDocumentUrls(webId),
|
|
528
|
+
...await listTurtleResources(fetcher, `${getPodBaseUrl(webId)}/.data/approvals/`).catch(() => []),
|
|
529
|
+
];
|
|
319
530
|
const rows = [];
|
|
320
|
-
for (const url of urls.filter((entry) => entry.endsWith('.ttl'))) {
|
|
531
|
+
for (const url of [...new Set(urls)].filter((entry) => entry.endsWith('.ttl'))) {
|
|
321
532
|
const turtle = await readTurtleResource(fetcher, url).catch(() => null);
|
|
322
533
|
if (!turtle)
|
|
323
534
|
continue;
|
|
@@ -329,6 +540,15 @@ async function listApprovalRows(webId, fetcher) {
|
|
|
329
540
|
}
|
|
330
541
|
return rows;
|
|
331
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));
|
|
549
|
+
}
|
|
550
|
+
return urls;
|
|
551
|
+
}
|
|
332
552
|
async function writeApprovalRow(webId, fetcher, row) {
|
|
333
553
|
const createdAt = new Date(toIsoString(row.createdAt, new Date().toISOString()));
|
|
334
554
|
const documentUrl = buildApprovalDocumentUrl(webId, createdAt);
|
|
@@ -349,8 +569,11 @@ async function writeApprovalRow(webId, fetcher, row) {
|
|
|
349
569
|
...(row.decisionRole ? [{ predicate: ApprovalVocab.decisionRole, object: literal(row.decisionRole) }] : []),
|
|
350
570
|
...(row.onBehalfOf ? [{ predicate: ApprovalVocab.onBehalfOf, object: iri(row.onBehalfOf) }] : []),
|
|
351
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) }] : []),
|
|
352
574
|
...(row.policyVersion ? [{ predicate: ApprovalVocab.policyVersion, object: literal(row.policyVersion) }] : []),
|
|
353
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())) }] : []),
|
|
354
577
|
...(row.resolvedAt ? [{ predicate: ApprovalVocab.resolvedAt, object: literal(toIsoString(row.resolvedAt, new Date().toISOString())) }] : []),
|
|
355
578
|
],
|
|
356
579
|
});
|
|
@@ -394,7 +617,6 @@ async function writeAuditRow(webId, fetcher, row) {
|
|
|
394
617
|
}
|
|
395
618
|
async function listGrantRows(webId, fetcher) {
|
|
396
619
|
const urls = [
|
|
397
|
-
`${getPodBaseUrl(webId)}/settings/autonomy/grants.ttl`,
|
|
398
620
|
...await listTurtleResources(fetcher, `${getPodBaseUrl(webId)}/settings/autonomy/grants/`).catch(() => []),
|
|
399
621
|
];
|
|
400
622
|
const rows = [];
|
|
@@ -412,8 +634,8 @@ async function listGrantRows(webId, fetcher) {
|
|
|
412
634
|
}
|
|
413
635
|
async function writeGrantRow(webId, fetcher, row) {
|
|
414
636
|
const id = normalizeString(row.id) ?? crypto.randomUUID();
|
|
415
|
-
const documentUrl = buildGrantDocumentUrl(webId);
|
|
416
637
|
const subjectUrl = buildGrantResourceUrl(webId, id);
|
|
638
|
+
const documentUrl = subjectUrl;
|
|
417
639
|
const target = normalizeString(row.target);
|
|
418
640
|
const action = normalizeString(row.action);
|
|
419
641
|
const effect = normalizeString(row.effect);
|
|
@@ -429,8 +651,22 @@ async function writeGrantRow(webId, fetcher, row) {
|
|
|
429
651
|
{ predicate: RDF_TYPE, object: iri(UDFS.AutonomyGrant) },
|
|
430
652
|
{ predicate: GrantVocab.target, object: iri(target) },
|
|
431
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) })),
|
|
432
666
|
{ predicate: GrantVocab.effect, object: literal(effect) },
|
|
433
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)) }] : []),
|
|
434
670
|
{ predicate: GrantVocab.decisionBy, object: iri(decisionBy) },
|
|
435
671
|
{ predicate: GrantVocab.decisionRole, object: literal(decisionRole) },
|
|
436
672
|
...(normalizeString(row.onBehalfOf) ? [{ predicate: GrantVocab.onBehalfOf, object: iri(normalizeString(row.onBehalfOf)) }] : []),
|
|
@@ -477,8 +713,11 @@ function approvalRowFromPredicates(url, predicates) {
|
|
|
477
713
|
decisionRole: firstLiteral(predicates, ApprovalVocab.decisionRole),
|
|
478
714
|
onBehalfOf: firstIri(predicates, ApprovalVocab.onBehalfOf),
|
|
479
715
|
reason: firstLiteral(predicates, ApprovalVocab.reason),
|
|
716
|
+
context: firstLiteral(predicates, ApprovalVocab.context),
|
|
717
|
+
approvalOptions: firstLiteral(predicates, ApprovalVocab.approvalOptions),
|
|
480
718
|
policyVersion: firstLiteral(predicates, ApprovalVocab.policyVersion),
|
|
481
719
|
createdAt,
|
|
720
|
+
expiresAt: firstLiteral(predicates, ApprovalVocab.expiresAt),
|
|
482
721
|
resolvedAt: firstLiteral(predicates, ApprovalVocab.resolvedAt),
|
|
483
722
|
};
|
|
484
723
|
}
|
|
@@ -519,8 +758,22 @@ function grantRowFromPredicates(url, predicates) {
|
|
|
519
758
|
id: subjectIdFromResourceUrl(url),
|
|
520
759
|
target,
|
|
521
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),
|
|
522
773
|
effect,
|
|
523
774
|
riskCeiling: firstLiteral(predicates, GrantVocab.riskCeiling),
|
|
775
|
+
policy: firstLiteral(predicates, GrantVocab.policy),
|
|
776
|
+
context: firstLiteral(predicates, GrantVocab.context),
|
|
524
777
|
decisionBy,
|
|
525
778
|
decisionRole,
|
|
526
779
|
onBehalfOf: firstIri(predicates, GrantVocab.onBehalfOf),
|
|
@@ -528,11 +781,88 @@ function grantRowFromPredicates(url, predicates) {
|
|
|
528
781
|
revokedAt: firstLiteral(predicates, GrantVocab.revokedAt),
|
|
529
782
|
};
|
|
530
783
|
}
|
|
784
|
+
function isActiveAllowGrant(grant) {
|
|
785
|
+
return grant.effect === 'allow' && !grant.revokedAt && !!(normalizeString(grant.body) || normalizeString(grant.policy));
|
|
786
|
+
}
|
|
787
|
+
function isGrantRiskCandidate(grant, requestRisk) {
|
|
788
|
+
const ceiling = riskScore(typeof grant.riskCeiling === 'string' ? grant.riskCeiling : undefined);
|
|
789
|
+
return ceiling === 0 || ceiling >= riskScore(requestRisk);
|
|
790
|
+
}
|
|
791
|
+
function rankGrantCandidate(grant, requestContext) {
|
|
792
|
+
let score = 0;
|
|
793
|
+
if (grant.target === requestContext.target) {
|
|
794
|
+
score += 4;
|
|
795
|
+
}
|
|
796
|
+
if (grant.action === requestContext.action) {
|
|
797
|
+
score += 3;
|
|
798
|
+
}
|
|
799
|
+
if (grant.schema) {
|
|
800
|
+
score += 2;
|
|
801
|
+
}
|
|
802
|
+
if (grant.pageKind === 'autonomy-grant') {
|
|
803
|
+
score += 1;
|
|
804
|
+
}
|
|
805
|
+
return score;
|
|
806
|
+
}
|
|
807
|
+
function selectSemanticGrantCandidates(grants, requestContext) {
|
|
808
|
+
const risk = normalizeString(requestContext.risk) ?? 'medium';
|
|
809
|
+
return grants
|
|
810
|
+
.filter((grant) => isActiveAllowGrant(grant) && isGrantRiskCandidate(grant, risk))
|
|
811
|
+
.sort((left, right) => rankGrantCandidate(right, requestContext) - rankGrantCandidate(left, requestContext))
|
|
812
|
+
.slice(0, MAX_GRANT_COVERAGE_CANDIDATES);
|
|
813
|
+
}
|
|
814
|
+
function acceptsGrantCoverage(decision) {
|
|
815
|
+
return decision?.covers === true
|
|
816
|
+
&& typeof decision.confidence === 'number'
|
|
817
|
+
&& decision.confidence >= MIN_GRANT_COVERAGE_CONFIDENCE;
|
|
818
|
+
}
|
|
819
|
+
async function resolveSemanticGrantDecision(options) {
|
|
820
|
+
const candidates = selectSemanticGrantCandidates(options.grants, options.requestContext);
|
|
821
|
+
if (candidates.length === 0) {
|
|
822
|
+
return null;
|
|
823
|
+
}
|
|
824
|
+
const resolver = options.runtime.resolveGrantCoverage ?? resolveWatchGrantCoverage;
|
|
825
|
+
for (const grant of candidates) {
|
|
826
|
+
const coverage = await resolver({
|
|
827
|
+
record: options.record,
|
|
828
|
+
request: options.request,
|
|
829
|
+
requestContext: options.requestContext,
|
|
830
|
+
grant,
|
|
831
|
+
}).catch(() => null);
|
|
832
|
+
if (acceptsGrantCoverage(coverage)) {
|
|
833
|
+
return 'accept_for_session';
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
return null;
|
|
837
|
+
}
|
|
838
|
+
function buildWatchGrantRequestContext(input) {
|
|
839
|
+
return {
|
|
840
|
+
session: buildThreadUri(input.webId, input.record),
|
|
841
|
+
target: buildThreadUri(input.webId, input.record),
|
|
842
|
+
action: buildActionUri(input.request),
|
|
843
|
+
risk: buildRisk(input.request),
|
|
844
|
+
toolName: buildToolName(input.request),
|
|
845
|
+
cwd: input.record.cwd,
|
|
846
|
+
backend: input.record.backend,
|
|
847
|
+
mode: input.record.mode,
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
function buildGenericGrantRequestContext(input) {
|
|
851
|
+
return {
|
|
852
|
+
session: input.subject.sessionUri,
|
|
853
|
+
target: input.subject.targetUri ?? input.subject.sessionUri,
|
|
854
|
+
action: input.request.action,
|
|
855
|
+
risk: input.request.risk,
|
|
856
|
+
toolName: input.request.toolName,
|
|
857
|
+
cwd: input.request.cwd,
|
|
858
|
+
kind: input.request.kind,
|
|
859
|
+
};
|
|
860
|
+
}
|
|
531
861
|
export async function createRemoteWatchApproval(options) {
|
|
532
862
|
const activeRuntime = options.runtime ?? await createDefaultRuntime();
|
|
533
863
|
return createRemoteApproval({
|
|
534
864
|
subject: ({ webId }) => ({
|
|
535
|
-
sessionUri: buildThreadUri(webId, options.record
|
|
865
|
+
sessionUri: buildThreadUri(webId, options.record),
|
|
536
866
|
actorUri: buildAgentUri(webId),
|
|
537
867
|
policyVersion: REMOTE_APPROVAL_POLICY_VERSION,
|
|
538
868
|
}),
|
|
@@ -545,6 +875,9 @@ export async function createRemoteWatchApproval(options) {
|
|
|
545
875
|
risk: buildRisk(options.request),
|
|
546
876
|
...(options.request.kind === 'command-approval' && options.request.command ? { command: options.request.command } : {}),
|
|
547
877
|
...(options.request.kind === 'command-approval' && options.request.cwd ? { cwd: options.request.cwd } : {}),
|
|
878
|
+
...(options.request.approvalOptions ? { approvalOptions: options.request.approvalOptions } : {}),
|
|
879
|
+
...(options.request.timeoutMs ? { timeoutMs: options.request.timeoutMs } : {}),
|
|
880
|
+
...(options.request.expiresAt ? { expiresAt: options.request.expiresAt } : {}),
|
|
548
881
|
entry: sessionUri,
|
|
549
882
|
}),
|
|
550
883
|
runtime: activeRuntime,
|
|
@@ -568,6 +901,9 @@ export async function createRemoteApproval(options) {
|
|
|
568
901
|
const onBehalfOf = subject.onBehalfOf ?? webId;
|
|
569
902
|
const policyVersion = subject.policyVersion ?? REMOTE_APPROVAL_POLICY_VERSION;
|
|
570
903
|
const requestEntry = request.entry ?? approvalUri;
|
|
904
|
+
const expiresAt = resolveApprovalExpiresAt(request, now);
|
|
905
|
+
const approvalOptions = encodeApprovalOptions(request.approvalOptions);
|
|
906
|
+
const context = compactApprovalContext(request);
|
|
571
907
|
await store.insertApproval({
|
|
572
908
|
id: approvalId,
|
|
573
909
|
session: sessionUri,
|
|
@@ -578,8 +914,11 @@ export async function createRemoteApproval(options) {
|
|
|
578
914
|
risk: request.risk,
|
|
579
915
|
status: 'pending',
|
|
580
916
|
assignedTo,
|
|
917
|
+
context,
|
|
918
|
+
...(approvalOptions ? { approvalOptions } : {}),
|
|
581
919
|
policyVersion,
|
|
582
920
|
createdAt: now,
|
|
921
|
+
...(expiresAt ? { expiresAt } : {}),
|
|
583
922
|
});
|
|
584
923
|
const requestAudit = {
|
|
585
924
|
id: crypto.randomUUID(),
|
|
@@ -604,6 +943,7 @@ export async function createRemoteApproval(options) {
|
|
|
604
943
|
}));
|
|
605
944
|
return normalizeApprovalSummary({
|
|
606
945
|
id: approvalId,
|
|
946
|
+
approvalUri,
|
|
607
947
|
session: sessionUri,
|
|
608
948
|
toolCallId: request.toolCallId,
|
|
609
949
|
toolName: request.toolName,
|
|
@@ -612,8 +952,11 @@ export async function createRemoteApproval(options) {
|
|
|
612
952
|
risk: request.risk,
|
|
613
953
|
status: 'pending',
|
|
614
954
|
assignedTo,
|
|
955
|
+
context,
|
|
956
|
+
...(approvalOptions ? { approvalOptions } : {}),
|
|
615
957
|
policyVersion,
|
|
616
958
|
createdAt: now,
|
|
959
|
+
...(expiresAt ? { expiresAt } : {}),
|
|
617
960
|
});
|
|
618
961
|
});
|
|
619
962
|
}
|
|
@@ -624,10 +967,13 @@ export async function waitForRemoteWatchApproval(options) {
|
|
|
624
967
|
if (options.signal?.aborted) {
|
|
625
968
|
throw createAbortError();
|
|
626
969
|
}
|
|
627
|
-
const
|
|
628
|
-
|
|
970
|
+
const row = await readRemoteApprovalRow(store, {
|
|
971
|
+
approvalId: options.approvalId,
|
|
972
|
+
approvalUri: options.approvalUri,
|
|
973
|
+
});
|
|
629
974
|
if (!row) {
|
|
630
|
-
|
|
975
|
+
await activeRuntime.sleep(options.pollMs ?? DEFAULT_REMOTE_APPROVAL_POLL_MS);
|
|
976
|
+
continue;
|
|
631
977
|
}
|
|
632
978
|
const decision = decisionFromApprovalRow(row);
|
|
633
979
|
if (decision) {
|
|
@@ -641,17 +987,20 @@ export async function requestRemoteWatchApproval(options) {
|
|
|
641
987
|
const activeRuntime = options.runtime ?? await createDefaultRuntime();
|
|
642
988
|
const delegated = await withRemoteApprovalStore(activeRuntime, async ({ store, webId }) => {
|
|
643
989
|
const grants = await store.listGrants();
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
990
|
+
return resolveSemanticGrantDecision({
|
|
991
|
+
runtime: activeRuntime,
|
|
992
|
+
grants,
|
|
993
|
+
record: options.record,
|
|
994
|
+
request: options.request,
|
|
995
|
+
requestContext: buildWatchGrantRequestContext({
|
|
996
|
+
webId,
|
|
997
|
+
record: options.record,
|
|
998
|
+
request: options.request,
|
|
999
|
+
}),
|
|
1000
|
+
});
|
|
652
1001
|
});
|
|
653
1002
|
if (delegated) {
|
|
654
|
-
return
|
|
1003
|
+
return delegated;
|
|
655
1004
|
}
|
|
656
1005
|
const summary = await createRemoteWatchApproval({
|
|
657
1006
|
record: options.record,
|
|
@@ -660,11 +1009,29 @@ export async function requestRemoteWatchApproval(options) {
|
|
|
660
1009
|
});
|
|
661
1010
|
return waitForRemoteWatchApproval({
|
|
662
1011
|
approvalId: summary.id,
|
|
1012
|
+
approvalUri: summary.approvalUri,
|
|
663
1013
|
pollMs: options.pollMs,
|
|
664
1014
|
signal: options.signal,
|
|
665
1015
|
runtime: activeRuntime,
|
|
666
1016
|
});
|
|
667
1017
|
}
|
|
1018
|
+
export async function resolveExistingRemoteWatchGrant(options) {
|
|
1019
|
+
const activeRuntime = options.runtime ?? await createDefaultRuntime();
|
|
1020
|
+
return withRemoteApprovalStore(activeRuntime, async ({ store, webId }) => {
|
|
1021
|
+
const grants = await store.listGrants();
|
|
1022
|
+
return resolveSemanticGrantDecision({
|
|
1023
|
+
runtime: activeRuntime,
|
|
1024
|
+
grants,
|
|
1025
|
+
record: options.record,
|
|
1026
|
+
request: options.request,
|
|
1027
|
+
requestContext: buildWatchGrantRequestContext({
|
|
1028
|
+
webId,
|
|
1029
|
+
record: options.record,
|
|
1030
|
+
request: options.request,
|
|
1031
|
+
}),
|
|
1032
|
+
});
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
668
1035
|
export async function requestRemoteApproval(options) {
|
|
669
1036
|
const activeRuntime = options.runtime ?? await createDefaultRuntime();
|
|
670
1037
|
const delegated = await withRemoteApprovalStore(activeRuntime, async ({ store, webId, stored }) => {
|
|
@@ -675,15 +1042,20 @@ export async function requestRemoteApproval(options) {
|
|
|
675
1042
|
? options.request({ webId, stored, sessionUri: subject.sessionUri })
|
|
676
1043
|
: options.request;
|
|
677
1044
|
const grants = await store.listGrants();
|
|
678
|
-
const
|
|
679
|
-
return
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
1045
|
+
const requestContext = buildGenericGrantRequestContext({ subject, request });
|
|
1046
|
+
return resolveSemanticGrantDecision({
|
|
1047
|
+
runtime: activeRuntime,
|
|
1048
|
+
grants,
|
|
1049
|
+
request: {
|
|
1050
|
+
...request,
|
|
1051
|
+
session: subject.sessionUri,
|
|
1052
|
+
target: requestContext.target,
|
|
1053
|
+
},
|
|
1054
|
+
requestContext,
|
|
1055
|
+
});
|
|
684
1056
|
});
|
|
685
1057
|
if (delegated) {
|
|
686
|
-
return
|
|
1058
|
+
return delegated;
|
|
687
1059
|
}
|
|
688
1060
|
const summary = await createRemoteApproval({
|
|
689
1061
|
subject: options.subject,
|
|
@@ -692,6 +1064,7 @@ export async function requestRemoteApproval(options) {
|
|
|
692
1064
|
});
|
|
693
1065
|
return waitForRemoteWatchApproval({
|
|
694
1066
|
approvalId: summary.id,
|
|
1067
|
+
approvalUri: summary.approvalUri,
|
|
695
1068
|
pollMs: options.pollMs,
|
|
696
1069
|
signal: options.signal,
|
|
697
1070
|
runtime: activeRuntime,
|
|
@@ -712,8 +1085,10 @@ export async function listRemoteWatchApprovals(options = {}) {
|
|
|
712
1085
|
export async function resolveRemoteWatchApproval(options) {
|
|
713
1086
|
const activeRuntime = options.runtime ?? await createDefaultRuntime();
|
|
714
1087
|
return withRemoteApprovalStore(activeRuntime, async ({ store, webId }) => {
|
|
715
|
-
const
|
|
716
|
-
|
|
1088
|
+
const row = await readRemoteApprovalRow(store, {
|
|
1089
|
+
approvalId: options.approvalId,
|
|
1090
|
+
approvalUri: options.approvalUri,
|
|
1091
|
+
});
|
|
717
1092
|
if (!row) {
|
|
718
1093
|
throw new Error(`Remote approval not found: ${options.approvalId}`);
|
|
719
1094
|
}
|
|
@@ -726,10 +1101,11 @@ export async function resolveRemoteWatchApproval(options) {
|
|
|
726
1101
|
const nextStatus = options.decision === 'accept' || options.decision === 'accept_for_session'
|
|
727
1102
|
? 'approved'
|
|
728
1103
|
: 'rejected';
|
|
1104
|
+
const decisionRole = options.decisionRole ?? 'human';
|
|
729
1105
|
await store.updateApproval(row.id, {
|
|
730
1106
|
status: nextStatus,
|
|
731
1107
|
decisionBy: webId,
|
|
732
|
-
decisionRole
|
|
1108
|
+
decisionRole,
|
|
733
1109
|
onBehalfOf: webId,
|
|
734
1110
|
reason: encodeDecisionReason(options.decision, options.note),
|
|
735
1111
|
resolvedAt: now,
|
|
@@ -738,7 +1114,7 @@ export async function resolveRemoteWatchApproval(options) {
|
|
|
738
1114
|
id: crypto.randomUUID(),
|
|
739
1115
|
action: nextStatus === 'approved' ? 'approval_approved' : 'approval_rejected',
|
|
740
1116
|
actor: webId,
|
|
741
|
-
actorRole:
|
|
1117
|
+
actorRole: decisionRole,
|
|
742
1118
|
onBehalfOf: webId,
|
|
743
1119
|
session: row.session,
|
|
744
1120
|
entry: approvalUri,
|
|
@@ -750,14 +1126,29 @@ export async function resolveRemoteWatchApproval(options) {
|
|
|
750
1126
|
}));
|
|
751
1127
|
if (options.decision === 'accept_for_session') {
|
|
752
1128
|
const grantId = crypto.randomUUID();
|
|
1129
|
+
const body = grantWikiBodyFromApproval(row, options.grantWikiBody);
|
|
753
1130
|
await store.insertGrant({
|
|
754
1131
|
id: grantId,
|
|
755
1132
|
target: row.target,
|
|
756
1133
|
action: row.action,
|
|
1134
|
+
title: grantWikiTitleFromApproval(row, options.grantWikiTitle),
|
|
1135
|
+
summary: grantWikiSummaryFromApproval(row, options.grantWikiSummary),
|
|
1136
|
+
body,
|
|
1137
|
+
schema: buildGrantSchemaUri(webId),
|
|
1138
|
+
pageKind: 'autonomy-grant',
|
|
1139
|
+
wikiStatus: 'active',
|
|
1140
|
+
tags: grantWikiTagsFromApproval(row, options.grantWikiTags),
|
|
1141
|
+
source: 'approval',
|
|
1142
|
+
sourceHash: grantSourceHash(row),
|
|
1143
|
+
compiledAt: now,
|
|
1144
|
+
compiledFrom: [approvalUri],
|
|
1145
|
+
related: [row.session],
|
|
757
1146
|
effect: 'allow',
|
|
758
1147
|
riskCeiling: row.risk,
|
|
1148
|
+
policy: grantIndexTextFromWikiBody(body),
|
|
1149
|
+
context: grantContextFromApproval(row),
|
|
759
1150
|
decisionBy: webId,
|
|
760
|
-
decisionRole
|
|
1151
|
+
decisionRole,
|
|
761
1152
|
onBehalfOf: webId,
|
|
762
1153
|
createdAt: now,
|
|
763
1154
|
});
|
|
@@ -778,7 +1169,7 @@ export async function resolveRemoteWatchApproval(options) {
|
|
|
778
1169
|
...row,
|
|
779
1170
|
status: nextStatus,
|
|
780
1171
|
decisionBy: webId,
|
|
781
|
-
decisionRole
|
|
1172
|
+
decisionRole,
|
|
782
1173
|
onBehalfOf: webId,
|
|
783
1174
|
reason: encodeDecisionReason(options.decision, options.note),
|
|
784
1175
|
resolvedAt: now,
|
|
@@ -786,6 +1177,18 @@ export async function resolveRemoteWatchApproval(options) {
|
|
|
786
1177
|
return normalizeApprovalSummary(nextRow);
|
|
787
1178
|
});
|
|
788
1179
|
}
|
|
1180
|
+
async function readRemoteApprovalRow(store, options) {
|
|
1181
|
+
if (store.findApproval) {
|
|
1182
|
+
const row = await store.findApproval(options.approvalId, {
|
|
1183
|
+
resourceUri: options.approvalUri,
|
|
1184
|
+
});
|
|
1185
|
+
if (row || options.approvalUri) {
|
|
1186
|
+
return row;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
const approvals = await store.listApprovals();
|
|
1190
|
+
return approvals.find((entry) => entry.id === options.approvalId) ?? null;
|
|
1191
|
+
}
|
|
789
1192
|
export const __podApprovalInternal = {
|
|
790
1193
|
createAbortError,
|
|
791
1194
|
createDefaultRuntime,
|
|
@@ -797,6 +1200,7 @@ export const __podApprovalInternal = {
|
|
|
797
1200
|
decisionFromApprovalRow,
|
|
798
1201
|
encodeDecisionReason,
|
|
799
1202
|
formatSummaryHeadline,
|
|
1203
|
+
readRemoteApprovalRow,
|
|
800
1204
|
isRemoteApprovalAbortError,
|
|
801
1205
|
normalizeApprovalSummary,
|
|
802
1206
|
parseDecisionReason,
|