@undefineds.co/linx 0.2.11 → 0.2.13
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/lib/pi-adapter/pod-mirror.js +18 -47
- package/dist/lib/pi-adapter/pod-mirror.js.map +1 -1
- package/dist/lib/pi-adapter/pod-native.js +61 -8
- package/dist/lib/pi-adapter/pod-native.js.map +1 -1
- package/dist/lib/pi-adapter/runtime.js +21 -2
- package/dist/lib/pi-adapter/runtime.js.map +1 -1
- package/dist/lib/pi-adapter/session.js +163 -49
- package/dist/lib/pi-adapter/session.js.map +1 -1
- package/dist/lib/pi-adapter/stream.js +57 -1
- package/dist/lib/pi-adapter/stream.js.map +1 -1
- package/dist/lib/pod-data-session.js +70 -0
- package/dist/lib/pod-data-session.js.map +1 -0
- package/dist/lib/profile-identity.js +14 -58
- package/dist/lib/profile-identity.js.map +1 -1
- package/dist/lib/watch/pod-ai.js +16 -33
- package/dist/lib/watch/pod-ai.js.map +1 -1
- package/dist/lib/watch/pod-approval.js +99 -80
- package/dist/lib/watch/pod-approval.js.map +1 -1
- package/dist/lib/watch/pod-persistence.js +22 -36
- package/dist/lib/watch/pod-persistence.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,17 +1,12 @@
|
|
|
1
|
-
import { getClientCredentialId, getClientCredentialKey, getClientCredentials, loadCredentials, } from './credentials-store.js';
|
|
2
1
|
import { extractProfileUsernameFromWebId } from '@undefineds.co/models/client';
|
|
3
2
|
import { resolveSolidProfileIdentityFromWebIdDocument } from '@undefineds.co/models/profile';
|
|
4
|
-
import {
|
|
5
|
-
import { authenticate } from './solid-auth.js';
|
|
3
|
+
import { getDefaultPodDataSession } from './pod-data-session.js';
|
|
6
4
|
const resourceCache = new Map();
|
|
7
5
|
const defaultResolveProfileIdentity = (session, webId) => {
|
|
8
6
|
return resolveSolidProfileIdentityFromWebIdDocument(session, { webId });
|
|
9
7
|
};
|
|
10
8
|
const defaultRuntime = {
|
|
11
|
-
|
|
12
|
-
getClientCredentials,
|
|
13
|
-
getOidcAccessToken,
|
|
14
|
-
authenticate,
|
|
9
|
+
getPodDataSession: getDefaultPodDataSession,
|
|
15
10
|
resolveProfileIdentity: defaultResolveProfileIdentity,
|
|
16
11
|
getCachedResource(key) {
|
|
17
12
|
return resourceCache.get(key) ?? null;
|
|
@@ -22,11 +17,11 @@ const defaultRuntime = {
|
|
|
22
17
|
};
|
|
23
18
|
export async function resolveProfileDisplayName(options = {}) {
|
|
24
19
|
const runtime = options.runtime ?? defaultRuntime;
|
|
25
|
-
const
|
|
26
|
-
if (!
|
|
20
|
+
const session = await runtime.getPodDataSession();
|
|
21
|
+
if (!session) {
|
|
27
22
|
return null;
|
|
28
23
|
}
|
|
29
|
-
return await withTimeout(readProfileDisplayName(
|
|
24
|
+
return await withTimeout(readProfileDisplayName(session, runtime).catch(() => null), options.timeoutMs ?? 5_000);
|
|
30
25
|
}
|
|
31
26
|
export function extractUsernameFromWebId(webId) {
|
|
32
27
|
return extractProfileUsernameFromWebId(webId);
|
|
@@ -34,70 +29,31 @@ export function extractUsernameFromWebId(webId) {
|
|
|
34
29
|
export function clearProfileIdentityResourceCache() {
|
|
35
30
|
resourceCache.clear();
|
|
36
31
|
}
|
|
37
|
-
async function readProfileDisplayName(
|
|
38
|
-
const resource = await getOrCreateProfileResource(
|
|
32
|
+
async function readProfileDisplayName(session, runtime) {
|
|
33
|
+
const resource = await getOrCreateProfileResource(session, runtime);
|
|
39
34
|
return resource.identity?.displayName ?? null;
|
|
40
35
|
}
|
|
41
|
-
async function getOrCreateProfileResource(
|
|
42
|
-
const cacheKey = buildProfileResourceCacheKey(
|
|
36
|
+
async function getOrCreateProfileResource(session, runtime) {
|
|
37
|
+
const cacheKey = buildProfileResourceCacheKey(session);
|
|
43
38
|
const cached = runtime.getCachedResource?.(cacheKey);
|
|
44
39
|
if (cached) {
|
|
45
40
|
return cached;
|
|
46
41
|
}
|
|
47
|
-
const session = await createProfileSession(credentials, runtime);
|
|
48
42
|
const resolveIdentity = runtime.resolveProfileIdentity ?? defaultResolveProfileIdentity;
|
|
49
43
|
const resource = {
|
|
50
44
|
session,
|
|
51
|
-
identity: await resolveIdentity(session,
|
|
45
|
+
identity: await resolveIdentity(session.solidSession ?? session, session.webId),
|
|
52
46
|
};
|
|
53
47
|
runtime.setCachedResource?.(cacheKey, resource);
|
|
54
48
|
return resource;
|
|
55
49
|
}
|
|
56
|
-
|
|
57
|
-
const clientCredentials = runtime.getClientCredentials(credentials);
|
|
58
|
-
if (clientCredentials) {
|
|
59
|
-
const { session } = await runtime.authenticate(getClientCredentialId(clientCredentials), getClientCredentialKey(clientCredentials), credentials.url);
|
|
60
|
-
return session;
|
|
61
|
-
}
|
|
62
|
-
if (credentials.authType === 'oidc_oauth') {
|
|
63
|
-
const accessToken = await runtime.getOidcAccessToken(credentials);
|
|
64
|
-
if (!accessToken) {
|
|
65
|
-
throw new Error('Failed to restore OIDC access token for profile lookup');
|
|
66
|
-
}
|
|
67
|
-
return createOidcSessionLike(credentials, accessToken);
|
|
68
|
-
}
|
|
69
|
-
throw new Error('Unsupported credential type for profile lookup');
|
|
70
|
-
}
|
|
71
|
-
function buildProfileResourceCacheKey(credentials) {
|
|
72
|
-
const clientCredentials = getClientCredentials(credentials);
|
|
73
|
-
const secretVersion = clientCredentials
|
|
74
|
-
? getClientCredentialId(clientCredentials)
|
|
75
|
-
: 'oidcRefreshToken' in credentials.secrets
|
|
76
|
-
? credentials.secrets.oidcRefreshToken
|
|
77
|
-
: '';
|
|
50
|
+
function buildProfileResourceCacheKey(session) {
|
|
78
51
|
return [
|
|
79
|
-
credentials.authType,
|
|
80
|
-
credentials.url,
|
|
81
|
-
|
|
82
|
-
secretVersion,
|
|
52
|
+
session.credentials.authType,
|
|
53
|
+
session.credentials.url,
|
|
54
|
+
session.webId,
|
|
83
55
|
].join('\n');
|
|
84
56
|
}
|
|
85
|
-
function createOidcSessionLike(credentials, accessToken) {
|
|
86
|
-
const podUrl = credentials.webId.replace('/card#me', '').replace(/\/?$/, '/');
|
|
87
|
-
return {
|
|
88
|
-
info: {
|
|
89
|
-
isLoggedIn: true,
|
|
90
|
-
webId: credentials.webId,
|
|
91
|
-
podUrl,
|
|
92
|
-
},
|
|
93
|
-
async logout() { },
|
|
94
|
-
fetch(url, init) {
|
|
95
|
-
const headers = new Headers(init?.headers);
|
|
96
|
-
headers.set('Authorization', `Bearer ${accessToken}`);
|
|
97
|
-
return fetch(url, { ...init, headers });
|
|
98
|
-
},
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
57
|
async function withTimeout(promise, timeoutMs) {
|
|
102
58
|
let timer;
|
|
103
59
|
const timeout = new Promise((resolve) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"profile-identity.js","sourceRoot":"","sources":["../../src/lib/profile-identity.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"profile-identity.js","sourceRoot":"","sources":["../../src/lib/profile-identity.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/watch/pod-ai.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getDefaultPodDataSession } from '../pod-data-session.js';
|
|
2
2
|
async function dynamicImport(specifier) {
|
|
3
3
|
const loader = new Function('modulePath', 'return import(modulePath)');
|
|
4
4
|
return loader(specifier);
|
|
@@ -97,9 +97,6 @@ function buildBackendEnv(match, backend) {
|
|
|
97
97
|
function missingPodClientCredentialsMessage() {
|
|
98
98
|
return 'LinX cloud credential source is not connected yet. Run `linx login` first.';
|
|
99
99
|
}
|
|
100
|
-
function unsupportedStoredAuthMessage() {
|
|
101
|
-
return 'LinX watch cloud credential source requires client credentials auth in `~/.linx`.';
|
|
102
|
-
}
|
|
103
100
|
export function podCredentialMissingMessage(backend) {
|
|
104
101
|
if (backend === 'claude') {
|
|
105
102
|
return 'No active Anthropic AI credential was found in LinX cloud credential config. Configure one in `/settings/credentials.ttl` and try again.';
|
|
@@ -113,17 +110,13 @@ export function podCredentialMissingMessage(backend) {
|
|
|
113
110
|
return 'No matching Pod AI credential was found.';
|
|
114
111
|
}
|
|
115
112
|
async function createDefaultRuntime() {
|
|
116
|
-
const [
|
|
117
|
-
dynamicImport('../credentials-store.js'),
|
|
118
|
-
dynamicImport('../solid-auth.js'),
|
|
113
|
+
const [models] = await Promise.all([
|
|
119
114
|
dynamicImport('../models.js'),
|
|
120
115
|
]);
|
|
121
116
|
return {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
createDb(session) {
|
|
126
|
-
return models.drizzle(session, {
|
|
117
|
+
getPodDataSession: getDefaultPodDataSession,
|
|
118
|
+
createDb(podSession) {
|
|
119
|
+
return models.drizzle(podSession.solidSession ?? podSession, {
|
|
127
120
|
logger: false,
|
|
128
121
|
disableInteropDiscovery: true,
|
|
129
122
|
schema: models.solidSchema,
|
|
@@ -135,30 +128,20 @@ async function createDefaultRuntime() {
|
|
|
135
128
|
}
|
|
136
129
|
export async function loadPodBackendCredential(backend, runtime) {
|
|
137
130
|
const activeRuntime = runtime ?? await createDefaultRuntime();
|
|
138
|
-
const
|
|
139
|
-
if (!
|
|
131
|
+
const podSession = await activeRuntime.getPodDataSession();
|
|
132
|
+
if (!podSession) {
|
|
140
133
|
throw new Error(missingPodClientCredentialsMessage());
|
|
141
134
|
}
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
db.select().from(activeRuntime.credentialTable).execute(),
|
|
151
|
-
db.select().from(activeRuntime.aiProviderTable).execute(),
|
|
152
|
-
]);
|
|
153
|
-
const match = selectPodCredentialForBackend(backend, credentials, providers);
|
|
154
|
-
if (!match) {
|
|
155
|
-
return null;
|
|
156
|
-
}
|
|
157
|
-
return buildBackendEnv(match, backend);
|
|
158
|
-
}
|
|
159
|
-
finally {
|
|
160
|
-
await session.logout().catch(() => undefined);
|
|
135
|
+
const db = activeRuntime.createDb(podSession);
|
|
136
|
+
const [credentials, providers] = await Promise.all([
|
|
137
|
+
db.select().from(activeRuntime.credentialTable).execute(),
|
|
138
|
+
db.select().from(activeRuntime.aiProviderTable).execute(),
|
|
139
|
+
]);
|
|
140
|
+
const match = selectPodCredentialForBackend(backend, credentials, providers);
|
|
141
|
+
if (!match) {
|
|
142
|
+
return null;
|
|
161
143
|
}
|
|
144
|
+
return buildBackendEnv(match, backend);
|
|
162
145
|
}
|
|
163
146
|
export const __podInternal = {
|
|
164
147
|
POD_PROVIDER_IDS,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pod-ai.js","sourceRoot":"","sources":["../../../src/lib/watch/pod-ai.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pod-ai.js","sourceRoot":"","sources":["../../../src/lib/watch/pod-ai.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
2
|
-
import {
|
|
3
|
-
import { AS_ACTOR, AS_ANNOUNCE, AS_OBJECT, DCT_CREATED, ODRL_ACTION, ODRL_POLICY, ODRL_TARGET, RDF_TYPE, UDFS_ACTION, UDFS_ACTOR, UDFS_ACTOR_ROLE, UDFS_APPROVAL_REQUEST, UDFS_ASSIGNED_TO, UDFS_AUDIT_ENTRY, UDFS_AUTONOMY_GRANT, UDFS_CONTEXT, UDFS_DECISION_BY, UDFS_DECISION_ROLE, UDFS_EFFECT, UDFS_ON_BEHALF_OF, UDFS_POLICY_VERSION, UDFS_REASON, UDFS_RESOLVED_AT, UDFS_REVOKED_AT, UDFS_RISK, UDFS_RISK_CEILING, UDFS_SESSION, UDFS_STATUS, UDFS_TOOL_CALL_ID, UDFS_TOOL_NAME, buildApprovalResourceUrl, buildAuditResourceUrl, buildGrantResourceUrl, buildInboxResourceUrl, firstIri, firstLiteral, iri, listTurtleResources, literal, parseManagedTurtleBlocks, readTurtleResource, subjectIdFromResourceUrl, upsertManagedTurtleBlock, } from '../pi-adapter/pod-native.js';
|
|
2
|
+
import { getDefaultPodDataSession } from '../pod-data-session.js';
|
|
3
|
+
import { AS_ACTOR, AS_ANNOUNCE, AS_OBJECT, buildApprovalDocumentUrl, DCT_CREATED, ODRL_ACTION, ODRL_POLICY, ODRL_TARGET, RDF_TYPE, UDFS_ACTION, UDFS_ACTOR, UDFS_ACTOR_ROLE, UDFS_APPROVAL_REQUEST, UDFS_ASSIGNED_TO, UDFS_AUDIT_ENTRY, UDFS_AUTONOMY_GRANT, UDFS_CONTEXT, UDFS_DECISION_BY, UDFS_DECISION_ROLE, UDFS_EFFECT, UDFS_ON_BEHALF_OF, UDFS_POLICY_VERSION, UDFS_REASON, UDFS_RESOLVED_AT, UDFS_REVOKED_AT, UDFS_RISK, UDFS_RISK_CEILING, UDFS_SESSION, UDFS_STATUS, UDFS_TOOL_CALL_ID, UDFS_TOOL_NAME, buildApprovalResourceUrl, buildAuditDocumentUrl, buildAuditResourceUrl, buildGrantResourceUrl, buildInboxResourceUrl, firstIri, firstLiteral, iri, listTurtleResources, listTurtleResourcesRecursive, literal, parseManagedTurtleBlocks, readTurtleResource, subjectIdFromResourceUrl, upsertManagedTurtleBlock, } from '../pi-adapter/pod-native.js';
|
|
4
4
|
const WATCH_CHAT_ID = 'linx-watch';
|
|
5
5
|
const WATCH_AGENT_ID = 'linx-watch-assistant';
|
|
6
6
|
const REMOTE_APPROVAL_POLICY_VERSION = 'linx-watch-remote-approval/v1';
|
|
7
7
|
const DEFAULT_REMOTE_APPROVAL_POLL_MS = 1000;
|
|
8
|
+
const remoteApprovalClientCache = new WeakMap();
|
|
8
9
|
function createAbortError() {
|
|
9
10
|
const error = new Error('The operation was aborted.');
|
|
10
11
|
error.name = 'AbortError';
|
|
11
12
|
return error;
|
|
12
13
|
}
|
|
13
|
-
async function dynamicImport(specifier) {
|
|
14
|
-
const loader = new Function('modulePath', 'return import(modulePath)');
|
|
15
|
-
return loader(specifier);
|
|
16
|
-
}
|
|
17
14
|
function normalizeString(value) {
|
|
18
15
|
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
19
16
|
}
|
|
@@ -43,10 +40,16 @@ function buildThreadUri(webId, threadId) {
|
|
|
43
40
|
return `${getPodBaseUrl(webId)}/.data/chat/${WATCH_CHAT_ID}/index.ttl#${threadId}`;
|
|
44
41
|
}
|
|
45
42
|
function buildApprovalUri(webIdOrUri, approvalId) {
|
|
46
|
-
return
|
|
43
|
+
return buildApprovalResourceUrl(webIdOrUri, approvalId);
|
|
44
|
+
}
|
|
45
|
+
function buildApprovalUriForDate(webIdOrUri, approvalId, createdAt) {
|
|
46
|
+
return buildApprovalResourceUrl(webIdOrUri, approvalId, createdAt);
|
|
47
47
|
}
|
|
48
48
|
function buildGrantUri(webIdOrUri, grantId) {
|
|
49
|
-
return
|
|
49
|
+
return buildGrantResourceUrl(webIdOrUri, grantId);
|
|
50
|
+
}
|
|
51
|
+
function buildGrantDocumentUrl(webIdOrUri) {
|
|
52
|
+
return `${getPodBaseUrl(webIdOrUri)}/settings/autonomy/grants.ttl`;
|
|
50
53
|
}
|
|
51
54
|
function buildAgentUri(webId) {
|
|
52
55
|
return `${getPodBaseUrl(webId)}/.data/agents/${WATCH_AGENT_ID}.ttl`;
|
|
@@ -203,16 +206,22 @@ function decisionFromApprovalRow(row) {
|
|
|
203
206
|
}
|
|
204
207
|
return 'accept';
|
|
205
208
|
}
|
|
206
|
-
function requestAuditForApproval(
|
|
207
|
-
const
|
|
209
|
+
function requestAuditForApproval(row, approvalUris, audits) {
|
|
210
|
+
const uriSet = new Set(approvalUris);
|
|
211
|
+
const matches = audits.filter((audit) => (audit.action === 'approval_requested'
|
|
212
|
+
&& ((audit.approval && uriSet.has(audit.approval))
|
|
213
|
+
|| (audit.session === row.session && audit.toolCallId === row.toolCallId))));
|
|
208
214
|
matches.sort((left, right) => toIsoString(right.createdAt, '').localeCompare(toIsoString(left.createdAt, '')));
|
|
209
215
|
return matches[0];
|
|
210
216
|
}
|
|
211
217
|
function normalizeApprovalSummary(row, audits) {
|
|
212
|
-
const approvalUri = buildApprovalUri(row.session, row.id);
|
|
213
|
-
const requestAudit = requestAuditForApproval(approvalUri, audits);
|
|
214
|
-
const requestContext = parseRequestAuditContext(requestAudit?.context);
|
|
215
218
|
const createdAt = toIsoString(row.createdAt, new Date(0).toISOString());
|
|
219
|
+
const approvalUris = [
|
|
220
|
+
buildApprovalUriForDate(row.session, row.id, new Date(createdAt)),
|
|
221
|
+
buildApprovalUri(row.session, row.id),
|
|
222
|
+
];
|
|
223
|
+
const requestAudit = requestAuditForApproval(row, approvalUris, audits);
|
|
224
|
+
const requestContext = parseRequestAuditContext(requestAudit?.context);
|
|
216
225
|
const sessionUri = row.session;
|
|
217
226
|
const decision = decisionFromApprovalRow(row);
|
|
218
227
|
return {
|
|
@@ -251,24 +260,11 @@ export function isRemoteApprovalAbortError(error) {
|
|
|
251
260
|
function missingRemoteApprovalCredentialsMessage() {
|
|
252
261
|
return 'LinX remote approval requires `linx login` first.';
|
|
253
262
|
}
|
|
254
|
-
function unsupportedRemoteApprovalAuthMessage() {
|
|
255
|
-
return 'LinX remote approval requires client credentials auth in `~/.linx`.';
|
|
256
|
-
}
|
|
257
263
|
async function createDefaultRuntime() {
|
|
258
|
-
const [credentialsStore, solidAuth] = await Promise.all([
|
|
259
|
-
dynamicImport(new URL('../credentials-store.js', import.meta.url).href),
|
|
260
|
-
dynamicImport(new URL('../solid-auth.js', import.meta.url).href),
|
|
261
|
-
]);
|
|
262
264
|
return {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
createStore(session) {
|
|
267
|
-
const webId = session.info.webId;
|
|
268
|
-
if (!webId) {
|
|
269
|
-
throw new Error('Remote approval authentication succeeded without a WebID.');
|
|
270
|
-
}
|
|
271
|
-
return createNativeRemoteApprovalStore(webId, (url, init) => session.fetch(url, init));
|
|
265
|
+
getPodDataSession: getDefaultPodDataSession,
|
|
266
|
+
createStore(webId, fetcher) {
|
|
267
|
+
return createNativeRemoteApprovalStore(webId, fetcher);
|
|
272
268
|
},
|
|
273
269
|
sleep(ms) {
|
|
274
270
|
return delay(ms);
|
|
@@ -279,30 +275,43 @@ async function createDefaultRuntime() {
|
|
|
279
275
|
};
|
|
280
276
|
}
|
|
281
277
|
async function withRemoteApprovalStore(runtime, fn) {
|
|
282
|
-
const
|
|
283
|
-
if (!
|
|
278
|
+
const client = await getRemoteApprovalClient(runtime);
|
|
279
|
+
if (!client) {
|
|
284
280
|
throw new Error(missingRemoteApprovalCredentialsMessage());
|
|
285
281
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
282
|
+
return await fn({
|
|
283
|
+
store: client.store,
|
|
284
|
+
webId: client.session.webId,
|
|
285
|
+
stored: client.session.credentials,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
async function getRemoteApprovalClient(runtime) {
|
|
289
|
+
let promise = remoteApprovalClientCache.get(runtime);
|
|
290
|
+
if (!promise) {
|
|
291
|
+
promise = createRemoteApprovalClient(runtime)
|
|
292
|
+
.then((client) => {
|
|
293
|
+
if (!client) {
|
|
294
|
+
remoteApprovalClientCache.delete(runtime);
|
|
295
|
+
}
|
|
296
|
+
return client;
|
|
297
|
+
})
|
|
298
|
+
.catch((error) => {
|
|
299
|
+
remoteApprovalClientCache.delete(runtime);
|
|
300
|
+
throw error;
|
|
301
301
|
});
|
|
302
|
+
remoteApprovalClientCache.set(runtime, promise);
|
|
302
303
|
}
|
|
303
|
-
|
|
304
|
-
|
|
304
|
+
return promise;
|
|
305
|
+
}
|
|
306
|
+
async function createRemoteApprovalClient(runtime) {
|
|
307
|
+
const session = await runtime.getPodDataSession();
|
|
308
|
+
if (!session) {
|
|
309
|
+
return null;
|
|
305
310
|
}
|
|
311
|
+
return {
|
|
312
|
+
session,
|
|
313
|
+
store: runtime.createStore(session.webId, session.fetch),
|
|
314
|
+
};
|
|
306
315
|
}
|
|
307
316
|
function createNativeRemoteApprovalStore(webId, fetcher) {
|
|
308
317
|
return {
|
|
@@ -323,25 +332,30 @@ function createNativeRemoteApprovalStore(webId, fetcher) {
|
|
|
323
332
|
};
|
|
324
333
|
}
|
|
325
334
|
async function listApprovalRows(webId, fetcher) {
|
|
326
|
-
const
|
|
335
|
+
const [currentUrls, legacyUrls] = await Promise.all([
|
|
336
|
+
listTurtleResourcesRecursive(fetcher, `${getPodBaseUrl(webId)}/.data/approvals/`).catch(() => []),
|
|
337
|
+
listTurtleResources(fetcher, `${getPodBaseUrl(webId)}/.data/approvals/`).catch(() => []),
|
|
338
|
+
]);
|
|
339
|
+
const urls = [...new Set([...currentUrls, ...legacyUrls])];
|
|
327
340
|
const rows = [];
|
|
328
341
|
for (const url of urls.filter((entry) => entry.endsWith('.ttl'))) {
|
|
329
342
|
const turtle = await readTurtleResource(fetcher, url).catch(() => null);
|
|
330
343
|
if (!turtle)
|
|
331
344
|
continue;
|
|
332
|
-
const predicates
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
rows.push(row);
|
|
345
|
+
for (const [subject, predicates] of parseManagedTurtleBlocks(turtle, url)) {
|
|
346
|
+
const row = approvalRowFromPredicates(subject, predicates);
|
|
347
|
+
if (row)
|
|
348
|
+
rows.push(row);
|
|
349
|
+
}
|
|
338
350
|
}
|
|
339
351
|
return rows;
|
|
340
352
|
}
|
|
341
353
|
async function writeApprovalRow(webId, fetcher, row) {
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
354
|
+
const createdAt = new Date(toIsoString(row.createdAt, new Date().toISOString()));
|
|
355
|
+
const documentUrl = buildApprovalDocumentUrl(webId, createdAt);
|
|
356
|
+
const subjectUrl = buildApprovalResourceUrl(webId, row.id, createdAt);
|
|
357
|
+
await upsertManagedTurtleBlock(fetcher, documentUrl, {
|
|
358
|
+
subject: subjectUrl,
|
|
345
359
|
triples: [
|
|
346
360
|
{ predicate: RDF_TYPE, object: iri(UDFS_APPROVAL_REQUEST) },
|
|
347
361
|
{ predicate: UDFS_SESSION, object: iri(row.session) },
|
|
@@ -363,25 +377,26 @@ async function writeApprovalRow(webId, fetcher, row) {
|
|
|
363
377
|
});
|
|
364
378
|
}
|
|
365
379
|
async function listAuditRows(webId, fetcher) {
|
|
366
|
-
const urls = await
|
|
380
|
+
const urls = await listTurtleResourcesRecursive(fetcher, `${getPodBaseUrl(webId)}/.data/audits/`);
|
|
367
381
|
const rows = [];
|
|
368
382
|
for (const url of urls.filter((entry) => entry.endsWith('.ttl'))) {
|
|
369
383
|
const turtle = await readTurtleResource(fetcher, url).catch(() => null);
|
|
370
384
|
if (!turtle)
|
|
371
385
|
continue;
|
|
372
|
-
const predicates
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
rows.push(row);
|
|
386
|
+
for (const [subject, predicates] of parseManagedTurtleBlocks(turtle, url)) {
|
|
387
|
+
const row = auditRowFromPredicates(subject, predicates);
|
|
388
|
+
if (row)
|
|
389
|
+
rows.push(row);
|
|
390
|
+
}
|
|
378
391
|
}
|
|
379
392
|
return rows;
|
|
380
393
|
}
|
|
381
394
|
async function writeAuditRow(webId, fetcher, row) {
|
|
382
|
-
const
|
|
383
|
-
|
|
384
|
-
|
|
395
|
+
const createdAt = new Date(toIsoString(row.createdAt, new Date().toISOString()));
|
|
396
|
+
const documentUrl = buildAuditDocumentUrl(webId, createdAt);
|
|
397
|
+
const subjectUrl = buildAuditResourceUrl(webId, row.id, createdAt);
|
|
398
|
+
await upsertManagedTurtleBlock(fetcher, documentUrl, {
|
|
399
|
+
subject: subjectUrl,
|
|
385
400
|
triples: [
|
|
386
401
|
{ predicate: RDF_TYPE, object: iri(UDFS_AUDIT_ENTRY) },
|
|
387
402
|
{ predicate: UDFS_ACTION, object: literal(row.action) },
|
|
@@ -398,24 +413,27 @@ async function writeAuditRow(webId, fetcher, row) {
|
|
|
398
413
|
});
|
|
399
414
|
}
|
|
400
415
|
async function listGrantRows(webId, fetcher) {
|
|
401
|
-
const urls =
|
|
416
|
+
const urls = [
|
|
417
|
+
`${getPodBaseUrl(webId)}/settings/autonomy/grants.ttl`,
|
|
418
|
+
...await listTurtleResources(fetcher, `${getPodBaseUrl(webId)}/settings/autonomy/grants/`).catch(() => []),
|
|
419
|
+
];
|
|
402
420
|
const rows = [];
|
|
403
421
|
for (const url of urls.filter((entry) => entry.endsWith('.ttl'))) {
|
|
404
422
|
const turtle = await readTurtleResource(fetcher, url).catch(() => null);
|
|
405
423
|
if (!turtle)
|
|
406
424
|
continue;
|
|
407
|
-
const predicates
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
rows.push(row);
|
|
425
|
+
for (const [subject, predicates] of parseManagedTurtleBlocks(turtle, url)) {
|
|
426
|
+
const row = grantRowFromPredicates(subject, predicates);
|
|
427
|
+
if (row)
|
|
428
|
+
rows.push(row);
|
|
429
|
+
}
|
|
413
430
|
}
|
|
414
431
|
return rows;
|
|
415
432
|
}
|
|
416
433
|
async function writeGrantRow(webId, fetcher, row) {
|
|
417
434
|
const id = normalizeString(row.id) ?? crypto.randomUUID();
|
|
418
|
-
const
|
|
435
|
+
const documentUrl = buildGrantDocumentUrl(webId);
|
|
436
|
+
const subjectUrl = buildGrantResourceUrl(webId, id);
|
|
419
437
|
const target = normalizeString(row.target);
|
|
420
438
|
const action = normalizeString(row.action);
|
|
421
439
|
const effect = normalizeString(row.effect);
|
|
@@ -424,8 +442,8 @@ async function writeGrantRow(webId, fetcher, row) {
|
|
|
424
442
|
if (!target || !action || !effect || !decisionBy || !decisionRole) {
|
|
425
443
|
throw new Error(`Invalid remote approval grant row: ${id}`);
|
|
426
444
|
}
|
|
427
|
-
await upsertManagedTurtleBlock(fetcher,
|
|
428
|
-
subject:
|
|
445
|
+
await upsertManagedTurtleBlock(fetcher, documentUrl, {
|
|
446
|
+
subject: subjectUrl,
|
|
429
447
|
triples: [
|
|
430
448
|
{ predicate: RDF_TYPE, object: iri(ODRL_POLICY) },
|
|
431
449
|
{ predicate: RDF_TYPE, object: iri(UDFS_AUTONOMY_GRANT) },
|
|
@@ -563,7 +581,7 @@ export async function createRemoteApproval(options) {
|
|
|
563
581
|
const approvalId = crypto.randomUUID();
|
|
564
582
|
const now = activeRuntime.now();
|
|
565
583
|
const sessionUri = subject.sessionUri;
|
|
566
|
-
const approvalUri =
|
|
584
|
+
const approvalUri = buildApprovalUriForDate(webId, approvalId, now);
|
|
567
585
|
const targetUri = subject.targetUri ?? sessionUri;
|
|
568
586
|
const assignedTo = subject.assignedTo ?? webId;
|
|
569
587
|
const onBehalfOf = subject.onBehalfOf ?? webId;
|
|
@@ -743,7 +761,8 @@ export async function resolveRemoteWatchApproval(options) {
|
|
|
743
761
|
return normalizeApprovalSummary(row, audits);
|
|
744
762
|
}
|
|
745
763
|
const now = activeRuntime.now();
|
|
746
|
-
const
|
|
764
|
+
const approvalCreatedAt = new Date(toIsoString(row.createdAt, now.toISOString()));
|
|
765
|
+
const approvalUri = buildApprovalUriForDate(row.session, row.id, approvalCreatedAt);
|
|
747
766
|
const nextStatus = options.decision === 'accept' || options.decision === 'accept_for_session'
|
|
748
767
|
? 'approved'
|
|
749
768
|
: 'rejected';
|