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