@enbox/agent 0.7.7 → 0.7.8
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/browser.mjs +9 -9
- package/dist/browser.mjs.map +4 -4
- package/dist/esm/dwn-api.js +3 -2
- package/dist/esm/dwn-api.js.map +1 -1
- package/dist/esm/enbox-connect-protocol.js +5 -5
- package/dist/esm/enbox-connect-protocol.js.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/permissions-api.js +7 -34
- package/dist/esm/permissions-api.js.map +1 -1
- package/dist/esm/sync-closure-resolver.js +229 -110
- package/dist/esm/sync-closure-resolver.js.map +1 -1
- package/dist/esm/sync-closure-types.js +24 -7
- package/dist/esm/sync-closure-types.js.map +1 -1
- package/dist/esm/sync-engine-level.js +1961 -764
- package/dist/esm/sync-engine-level.js.map +1 -1
- package/dist/esm/sync-link-id.js +4 -13
- package/dist/esm/sync-link-id.js.map +1 -1
- package/dist/esm/sync-link-reconciler.js +26 -8
- package/dist/esm/sync-link-reconciler.js.map +1 -1
- package/dist/esm/sync-messages.js +218 -154
- package/dist/esm/sync-messages.js.map +1 -1
- package/dist/esm/sync-permission-grants.js +208 -0
- package/dist/esm/sync-permission-grants.js.map +1 -0
- package/dist/esm/sync-replication-ledger.js +23 -40
- package/dist/esm/sync-replication-ledger.js.map +1 -1
- package/dist/esm/sync-scope-acceptance.js +126 -0
- package/dist/esm/sync-scope-acceptance.js.map +1 -0
- package/dist/esm/sync-topological-sort.js +57 -15
- package/dist/esm/sync-topological-sort.js.map +1 -1
- package/dist/esm/types/sync.js +130 -22
- package/dist/esm/types/sync.js.map +1 -1
- package/dist/types/dwn-api.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/permissions-api.d.ts +1 -2
- package/dist/types/permissions-api.d.ts.map +1 -1
- package/dist/types/sync-closure-resolver.d.ts.map +1 -1
- package/dist/types/sync-closure-types.d.ts +14 -3
- package/dist/types/sync-closure-types.d.ts.map +1 -1
- package/dist/types/sync-engine-level.d.ts +127 -25
- package/dist/types/sync-engine-level.d.ts.map +1 -1
- package/dist/types/sync-link-id.d.ts +3 -9
- package/dist/types/sync-link-id.d.ts.map +1 -1
- package/dist/types/sync-link-reconciler.d.ts +12 -2
- package/dist/types/sync-link-reconciler.d.ts.map +1 -1
- package/dist/types/sync-messages.d.ts +16 -13
- package/dist/types/sync-messages.d.ts.map +1 -1
- package/dist/types/sync-permission-grants.d.ts +52 -0
- package/dist/types/sync-permission-grants.d.ts.map +1 -0
- package/dist/types/sync-replication-ledger.d.ts +5 -13
- package/dist/types/sync-replication-ledger.d.ts.map +1 -1
- package/dist/types/sync-scope-acceptance.d.ts +28 -0
- package/dist/types/sync-scope-acceptance.d.ts.map +1 -0
- package/dist/types/sync-topological-sort.d.ts +2 -1
- package/dist/types/sync-topological-sort.d.ts.map +1 -1
- package/dist/types/types/permissions.d.ts +2 -0
- package/dist/types/types/permissions.d.ts.map +1 -1
- package/dist/types/types/sync.d.ts +137 -75
- package/dist/types/types/sync.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/dwn-api.ts +3 -2
- package/src/enbox-connect-protocol.ts +5 -5
- package/src/index.ts +10 -1
- package/src/permissions-api.ts +11 -42
- package/src/sync-closure-resolver.ts +306 -126
- package/src/sync-closure-types.ts +38 -9
- package/src/sync-engine-level.ts +2560 -797
- package/src/sync-link-id.ts +9 -14
- package/src/sync-link-reconciler.ts +43 -10
- package/src/sync-messages.ts +263 -159
- package/src/sync-permission-grants.ts +297 -0
- package/src/sync-replication-ledger.ts +55 -50
- package/src/sync-scope-acceptance.ts +186 -0
- package/src/sync-topological-sort.ts +89 -21
- package/src/types/permissions.ts +2 -0
- package/src/types/sync.ts +235 -62
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { Jws, Message, PermissionScopeMatcher } from '@enbox/dwn-sdk-js';
|
|
11
|
+
import { lexicographicalCompare, syncScopeFromRecordsProjectionScopes } from './types/sync.js';
|
|
12
|
+
/** Returns a sorted, duplicate-free grant ID set, or `undefined` for owner requests. */
|
|
13
|
+
export function toMessagesPermissionGrantIds(permissionGrantIds) {
|
|
14
|
+
if (permissionGrantIds === undefined || permissionGrantIds.length === 0) {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
return [...new Set(permissionGrantIds)].sort(lexicographicalCompare);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Gets the active permission grant IDs that authorize a Messages operation.
|
|
21
|
+
*
|
|
22
|
+
* Owner-authored sync does not invoke grants. Delegate full sync requires at
|
|
23
|
+
* least one active unscoped Messages.Read grant. Delegate protocol-set sync
|
|
24
|
+
* requires each requested protocol to be covered by an active Messages.Read
|
|
25
|
+
* grant, then invokes every active grant that participates in the projection.
|
|
26
|
+
* This keeps the authorization epoch tied to grant churn without widening the
|
|
27
|
+
* CID projection being compared.
|
|
28
|
+
*/
|
|
29
|
+
export function getMessagesPermissionGrantsForScope(_a) {
|
|
30
|
+
return __awaiter(this, arguments, void 0, function* ({ did, delegateDid, protocols, messageType, permissionsApi, }) {
|
|
31
|
+
const requestedScope = protocols === undefined
|
|
32
|
+
? { kind: 'full' }
|
|
33
|
+
: { kind: 'protocolSet', protocols };
|
|
34
|
+
const resolutions = yield resolveMessagesSyncScopes({
|
|
35
|
+
did,
|
|
36
|
+
delegateDid,
|
|
37
|
+
requestedScope,
|
|
38
|
+
messageType,
|
|
39
|
+
permissionsApi,
|
|
40
|
+
});
|
|
41
|
+
return resolutions
|
|
42
|
+
.flatMap(resolution => resolution.permissionGrants)
|
|
43
|
+
.sort((a, b) => lexicographicalCompare(a.grant.id, b.grant.id));
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Resolves active Messages.Read grants into one or more sync targets.
|
|
48
|
+
*
|
|
49
|
+
* Broad protocol coverage remains on StateIndex full/protocol roots. Exact
|
|
50
|
+
* protocolPath and contextId grants are grouped into a Records-primary
|
|
51
|
+
* projection target so a narrow grant never authorizes a broad protocol root.
|
|
52
|
+
*/
|
|
53
|
+
export function resolveMessagesSyncScopes(_a) {
|
|
54
|
+
return __awaiter(this, arguments, void 0, function* ({ did, delegateDid, requestedScope, messageType, permissionsApi, }) {
|
|
55
|
+
if (!delegateDid) {
|
|
56
|
+
return [{ scope: requestedScope, permissionGrants: [] }];
|
|
57
|
+
}
|
|
58
|
+
const now = new Date().toISOString();
|
|
59
|
+
const permissionGrants = (yield permissionsApi.fetchGrants({
|
|
60
|
+
author: delegateDid,
|
|
61
|
+
target: delegateDid,
|
|
62
|
+
grantor: did,
|
|
63
|
+
grantee: delegateDid,
|
|
64
|
+
})).filter(entry => isActiveMessagesGrant(entry, did, delegateDid, now));
|
|
65
|
+
if (requestedScope.kind === 'full') {
|
|
66
|
+
return [resolveFullScope(permissionGrants, requestedScope, messageType)];
|
|
67
|
+
}
|
|
68
|
+
if (requestedScope.kind === 'protocolSet') {
|
|
69
|
+
return resolveProtocolSetScope(permissionGrants, requestedScope, messageType);
|
|
70
|
+
}
|
|
71
|
+
return [resolveRecordsProjectionScope(permissionGrants, requestedScope, messageType)];
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
function resolveFullScope(permissionGrants, requestedScope, messageType) {
|
|
75
|
+
const grants = permissionGrants
|
|
76
|
+
.filter(entry => grantMatchesProtocol(entry, undefined))
|
|
77
|
+
.sort((a, b) => lexicographicalCompare(a.grant.id, b.grant.id));
|
|
78
|
+
if (grants.length === 0) {
|
|
79
|
+
throw new Error(`SyncPermissions: No active Messages.Read permission found for ${messageType}: all protocols`);
|
|
80
|
+
}
|
|
81
|
+
return { scope: requestedScope, permissionGrants: grants };
|
|
82
|
+
}
|
|
83
|
+
function resolveProtocolSetScope(permissionGrants, requestedScope, messageType) {
|
|
84
|
+
const broadProtocols = [];
|
|
85
|
+
const narrowScopes = [];
|
|
86
|
+
for (const protocol of requestedScope.protocols) {
|
|
87
|
+
if (permissionGrants.some(entry => grantMatchesProtocol(entry, protocol))) {
|
|
88
|
+
broadProtocols.push(protocol);
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const protocolNarrowScopes = permissionGrants
|
|
92
|
+
.map(entry => grantProjectionScopeForProtocol(entry, protocol))
|
|
93
|
+
.filter((scope) => scope !== undefined);
|
|
94
|
+
if (protocolNarrowScopes.length === 0) {
|
|
95
|
+
throw new Error(`SyncPermissions: No active Messages.Read permission found for ${messageType}: ${protocol}`);
|
|
96
|
+
}
|
|
97
|
+
narrowScopes.push(...protocolNarrowScopes);
|
|
98
|
+
}
|
|
99
|
+
return [
|
|
100
|
+
...broadProtocolResolution(permissionGrants, broadProtocols),
|
|
101
|
+
...recordsProjectionResolution(permissionGrants, narrowScopes, messageType),
|
|
102
|
+
];
|
|
103
|
+
}
|
|
104
|
+
function resolveRecordsProjectionScope(permissionGrants, requestedScope, messageType) {
|
|
105
|
+
if (!requestedScope.scopes.every(scope => isProjectionScopeCovered(permissionGrants, scope))) {
|
|
106
|
+
throw new Error(`SyncPermissions: No active Messages.Read permission found for ${messageType}: projected Records scope`);
|
|
107
|
+
}
|
|
108
|
+
const grants = permissionGrants
|
|
109
|
+
.filter(entry => requestedScope.scopes.some(scope => PermissionScopeMatcher.matches(entry.grant.scope, scope)))
|
|
110
|
+
.sort((a, b) => lexicographicalCompare(a.grant.id, b.grant.id));
|
|
111
|
+
return { scope: requestedScope, permissionGrants: grants };
|
|
112
|
+
}
|
|
113
|
+
function isProjectionScopeCovered(permissionGrants, scope) {
|
|
114
|
+
return permissionGrants.some(entry => PermissionScopeMatcher.matches(entry.grant.scope, scope));
|
|
115
|
+
}
|
|
116
|
+
function broadProtocolResolution(permissionGrants, broadProtocols) {
|
|
117
|
+
if (broadProtocols.length === 0) {
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
const protocols = [...new Set(broadProtocols)].sort(lexicographicalCompare);
|
|
121
|
+
const grants = permissionGrants
|
|
122
|
+
.filter(entry => grantParticipatesInProtocolSet(entry, protocols))
|
|
123
|
+
.sort((a, b) => lexicographicalCompare(a.grant.id, b.grant.id));
|
|
124
|
+
return [{
|
|
125
|
+
scope: {
|
|
126
|
+
kind: 'protocolSet',
|
|
127
|
+
protocols,
|
|
128
|
+
},
|
|
129
|
+
permissionGrants: grants,
|
|
130
|
+
}];
|
|
131
|
+
}
|
|
132
|
+
function recordsProjectionResolution(permissionGrants, projectionScopes, messageType) {
|
|
133
|
+
const [firstScope, ...remainingScopes] = projectionScopes;
|
|
134
|
+
if (firstScope === undefined) {
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
const requestedScope = syncScopeFromRecordsProjectionScopes([firstScope, ...remainingScopes]);
|
|
138
|
+
return [resolveRecordsProjectionScope(permissionGrants, requestedScope, messageType)];
|
|
139
|
+
}
|
|
140
|
+
function grantProjectionScopeForProtocol(entry, protocol) {
|
|
141
|
+
const scope = entry.grant.scope;
|
|
142
|
+
if (!isMessagesReadScope(scope) || scope.protocol !== protocol) {
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
if (scope.protocolPath !== undefined) {
|
|
146
|
+
return { protocol, protocolPath: scope.protocolPath };
|
|
147
|
+
}
|
|
148
|
+
if (scope.contextId !== undefined) {
|
|
149
|
+
return { protocol, contextId: scope.contextId };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function isMessagesReadScope(scope) {
|
|
153
|
+
return scope.interface === 'Messages' &&
|
|
154
|
+
scope.method === 'Read';
|
|
155
|
+
}
|
|
156
|
+
function grantParticipatesInProtocolSet(entry, protocols) {
|
|
157
|
+
return protocols.some(protocol => grantMatchesProtocol(entry, protocol));
|
|
158
|
+
}
|
|
159
|
+
/** Converts permission grant entries into authorization epoch inputs. */
|
|
160
|
+
export function toSyncAuthorizationGrants(permissionGrants) {
|
|
161
|
+
if (permissionGrants.length === 0) {
|
|
162
|
+
throw new Error('SyncPermissions: delegate authorization requires at least one grant.');
|
|
163
|
+
}
|
|
164
|
+
return permissionGrants
|
|
165
|
+
.map(({ grant }) => ({
|
|
166
|
+
dateExpires: grant.dateExpires,
|
|
167
|
+
dateGranted: grant.dateGranted,
|
|
168
|
+
id: grant.id,
|
|
169
|
+
}))
|
|
170
|
+
.sort((a, b) => lexicographicalCompare(a.id, b.id));
|
|
171
|
+
}
|
|
172
|
+
function isActiveMessagesGrant(entry, grantor, grantee, now) {
|
|
173
|
+
const { grant } = entry;
|
|
174
|
+
if (grant.grantor !== grantor || grant.grantee !== grantee) {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
if (grant.dateGranted > now || grant.dateExpires <= now) {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
const scope = grant.scope;
|
|
181
|
+
return scope.interface === 'Messages' &&
|
|
182
|
+
scope.method === 'Read';
|
|
183
|
+
}
|
|
184
|
+
function grantMatchesProtocol(entry, protocol) {
|
|
185
|
+
return PermissionScopeMatcher.matches(entry.grant.scope, { protocol });
|
|
186
|
+
}
|
|
187
|
+
/** Returns sorted grant IDs from permission grant entries. */
|
|
188
|
+
export function permissionGrantIdsFromEntries(permissionGrants) {
|
|
189
|
+
return toMessagesPermissionGrantIds(permissionGrants.map(entry => entry.grant.id));
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Returns the permission grant IDs invoked by a message.
|
|
193
|
+
*
|
|
194
|
+
* Real DWN messages use the author signature payload as the source of truth.
|
|
195
|
+
*/
|
|
196
|
+
export function getInvokedPermissionGrantIds(message) {
|
|
197
|
+
if (message.authorization === undefined) {
|
|
198
|
+
return [];
|
|
199
|
+
}
|
|
200
|
+
try {
|
|
201
|
+
const signaturePayload = Jws.decodePlainObjectPayload(message.authorization.signature);
|
|
202
|
+
return Message.getPermissionGrantIds(signaturePayload);
|
|
203
|
+
}
|
|
204
|
+
catch (_a) {
|
|
205
|
+
return [];
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=sync-permission-grants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-permission-grants.js","sourceRoot":"","sources":["../../src/sync-permission-grants.ts"],"names":[],"mappings":"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"}
|
|
@@ -14,7 +14,7 @@ var __asyncValues = (this && this.__asyncValues) || function (o) {
|
|
|
14
14
|
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
|
15
15
|
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
|
16
16
|
};
|
|
17
|
-
import {
|
|
17
|
+
import { canonicalizeSyncScope, computeProjectionId } from './types/sync.js';
|
|
18
18
|
/** Separator used in compound LevelDB keys. */
|
|
19
19
|
const KEY_SEP = '^';
|
|
20
20
|
/**
|
|
@@ -22,7 +22,7 @@ const KEY_SEP = '^';
|
|
|
22
22
|
* sync link in a LevelDB sublevel. Provides CRUD operations and replication
|
|
23
23
|
* checkpoint helpers.
|
|
24
24
|
*
|
|
25
|
-
* Key format: `{tenantDid}^{remoteEndpoint}^{
|
|
25
|
+
* Key format: `{tenantDid}^{remoteEndpoint}^{projectionId}^{authorizationEpoch}`
|
|
26
26
|
*
|
|
27
27
|
* Each link tracks independent pull and push {@link DirectionCheckpoint}s.
|
|
28
28
|
* The ledger does not own subscriptions or timers — it is a passive state
|
|
@@ -37,12 +37,13 @@ export class ReplicationLedger {
|
|
|
37
37
|
// Key helpers
|
|
38
38
|
// ---------------------------------------------------------------------------
|
|
39
39
|
/** Build the compound key for a link. */
|
|
40
|
-
static buildKey(tenantDid, remoteEndpoint,
|
|
41
|
-
return `${tenantDid}${KEY_SEP}${remoteEndpoint}${KEY_SEP}${
|
|
40
|
+
static buildKey(tenantDid, remoteEndpoint, projectionId, authorizationEpoch) {
|
|
41
|
+
return `${tenantDid}${KEY_SEP}${remoteEndpoint}${KEY_SEP}${projectionId}${KEY_SEP}${authorizationEpoch}`;
|
|
42
42
|
}
|
|
43
43
|
// Note: compound keys use raw '^' separator. This is safe because tenantDid
|
|
44
|
-
// (DID URI), remoteEndpoint (URL),
|
|
45
|
-
// contain '^'. If future fields
|
|
44
|
+
// (DID URI), remoteEndpoint (URL), projectionId (base64url hash), and
|
|
45
|
+
// authorizationEpoch (base64url hash) cannot contain '^'. If future fields
|
|
46
|
+
// can contain '^', keys must be escaped.
|
|
46
47
|
// ---------------------------------------------------------------------------
|
|
47
48
|
// CRUD
|
|
48
49
|
// ---------------------------------------------------------------------------
|
|
@@ -52,12 +53,12 @@ export class ReplicationLedger {
|
|
|
52
53
|
*/
|
|
53
54
|
getOrCreateLink(params) {
|
|
54
55
|
return __awaiter(this, void 0, void 0, function* () {
|
|
55
|
-
const
|
|
56
|
-
const
|
|
56
|
+
const scope = canonicalizeSyncScope(params.scope);
|
|
57
|
+
const projectionId = yield computeProjectionId(params.tenantDid, scope);
|
|
58
|
+
const key = ReplicationLedger.buildKey(params.tenantDid, params.remoteEndpoint, projectionId, params.authorizationEpoch);
|
|
57
59
|
try {
|
|
58
60
|
const raw = yield this.sublevel.get(key);
|
|
59
61
|
const link = JSON.parse(raw);
|
|
60
|
-
delete link.push; // strip legacy push field from old persisted links
|
|
61
62
|
// connectivity is runtime state — always reset to 'unknown' on load
|
|
62
63
|
// so stale 'online' from a previous session doesn't give false positives.
|
|
63
64
|
link.connectivity = 'unknown';
|
|
@@ -73,14 +74,15 @@ export class ReplicationLedger {
|
|
|
73
74
|
const link = {
|
|
74
75
|
tenantDid: params.tenantDid,
|
|
75
76
|
remoteEndpoint: params.remoteEndpoint,
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
projectionId,
|
|
78
|
+
authorizationEpoch: params.authorizationEpoch,
|
|
79
|
+
scope,
|
|
80
|
+
authorization: params.authorization,
|
|
78
81
|
status: 'initializing',
|
|
79
82
|
connectivity: 'unknown',
|
|
80
83
|
pull: {},
|
|
81
84
|
needsReconcile: false,
|
|
82
85
|
delegateDid: params.delegateDid,
|
|
83
|
-
protocol: params.protocol,
|
|
84
86
|
};
|
|
85
87
|
yield this.sublevel.put(key, JSON.stringify(link));
|
|
86
88
|
return link;
|
|
@@ -89,15 +91,15 @@ export class ReplicationLedger {
|
|
|
89
91
|
/** Persist the current state of a link. */
|
|
90
92
|
saveLink(link) {
|
|
91
93
|
return __awaiter(this, void 0, void 0, function* () {
|
|
92
|
-
const key = ReplicationLedger.buildKey(link.tenantDid, link.remoteEndpoint, link.
|
|
94
|
+
const key = ReplicationLedger.buildKey(link.tenantDid, link.remoteEndpoint, link.projectionId, link.authorizationEpoch);
|
|
93
95
|
link.lastActivityAt = new Date().toISOString();
|
|
94
96
|
yield this.sublevel.put(key, JSON.stringify(link));
|
|
95
97
|
});
|
|
96
98
|
}
|
|
97
99
|
/** Delete a link. */
|
|
98
|
-
deleteLink(tenantDid, remoteEndpoint,
|
|
100
|
+
deleteLink(tenantDid, remoteEndpoint, projectionId, authorizationEpoch) {
|
|
99
101
|
return __awaiter(this, void 0, void 0, function* () {
|
|
100
|
-
const key = ReplicationLedger.buildKey(tenantDid, remoteEndpoint,
|
|
102
|
+
const key = ReplicationLedger.buildKey(tenantDid, remoteEndpoint, projectionId, authorizationEpoch);
|
|
101
103
|
yield this.sublevel.del(key);
|
|
102
104
|
});
|
|
103
105
|
}
|
|
@@ -151,31 +153,6 @@ export class ReplicationLedger {
|
|
|
151
153
|
});
|
|
152
154
|
}
|
|
153
155
|
// ---------------------------------------------------------------------------
|
|
154
|
-
// Delegate updates
|
|
155
|
-
// ---------------------------------------------------------------------------
|
|
156
|
-
/**
|
|
157
|
-
* Update the `delegateDid` on all persisted links for a tenant and persist.
|
|
158
|
-
* This ensures that repair and reconcile paths — which read `delegateDid`
|
|
159
|
-
* from the durable {@link ReplicationLinkState} — use the current delegate
|
|
160
|
-
* after a hot-swap via `updateIdentityOptions()`.
|
|
161
|
-
*
|
|
162
|
-
* @returns the links that were updated.
|
|
163
|
-
*/
|
|
164
|
-
updateDelegateDid(tenantDid, delegateDid) {
|
|
165
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
166
|
-
const links = yield this.getLinksForTenant(tenantDid);
|
|
167
|
-
const updated = [];
|
|
168
|
-
for (const link of links) {
|
|
169
|
-
if (link.delegateDid !== delegateDid) {
|
|
170
|
-
link.delegateDid = delegateDid;
|
|
171
|
-
yield this.saveLink(link);
|
|
172
|
-
updated.push(link);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
return updated;
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
// ---------------------------------------------------------------------------
|
|
179
156
|
// Status transitions
|
|
180
157
|
// ---------------------------------------------------------------------------
|
|
181
158
|
/** Transition a link to a new status and persist. */
|
|
@@ -232,6 +209,12 @@ export class ReplicationLedger {
|
|
|
232
209
|
* are durably committed before calling this.
|
|
233
210
|
*/
|
|
234
211
|
static commitContiguousToken(checkpoint, token) {
|
|
212
|
+
if (checkpoint.contiguousAppliedToken !== undefined &&
|
|
213
|
+
token.streamId === checkpoint.contiguousAppliedToken.streamId &&
|
|
214
|
+
token.epoch === checkpoint.contiguousAppliedToken.epoch &&
|
|
215
|
+
ReplicationLedger.comparePosition(token, checkpoint.contiguousAppliedToken) <= 0) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
235
218
|
checkpoint.contiguousAppliedToken = token;
|
|
236
219
|
}
|
|
237
220
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync-replication-ledger.js","sourceRoot":"","sources":["../../src/sync-replication-ledger.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAKA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"sync-replication-ledger.js","sourceRoot":"","sources":["../../src/sync-replication-ledger.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { DwnInterfaceName, DwnMethodName, PermissionScopeMatcher, PermissionsProtocol } from '@enbox/dwn-sdk-js';
|
|
2
|
+
/**
|
|
3
|
+
* Classifies whether a live event belongs to the link's current sync scope.
|
|
4
|
+
*
|
|
5
|
+
* Full links accept every message. Protocol-set links accept records for a
|
|
6
|
+
* covered protocol, ProtocolsConfigure messages that install a covered
|
|
7
|
+
* protocol, and permission records tagged for a covered protocol. RecordsDelete
|
|
8
|
+
* messages carry no protocol in their descriptor, so they must be classified
|
|
9
|
+
* from the event's initial write metadata.
|
|
10
|
+
*/
|
|
11
|
+
export function classifySyncEventScope(event, scope) {
|
|
12
|
+
return classifySyncMessageScope({
|
|
13
|
+
message: event.message,
|
|
14
|
+
initialWrite: event.initialWrite,
|
|
15
|
+
scope,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Classifies whether a DWN message belongs to a sync scope before local apply.
|
|
20
|
+
*
|
|
21
|
+
* If a RecordsDelete cannot be tied to its initial write, the result is
|
|
22
|
+
* `unknown` so callers fail closed instead of applying an unclassified delete.
|
|
23
|
+
*/
|
|
24
|
+
export function classifySyncMessageScope({ message, initialWrite, scope, }) {
|
|
25
|
+
if (scope.kind === 'full') {
|
|
26
|
+
return 'in-scope';
|
|
27
|
+
}
|
|
28
|
+
if (scope.kind === 'recordsProjection') {
|
|
29
|
+
return classifyRecordsProjectionScope(message, initialWrite, scope);
|
|
30
|
+
}
|
|
31
|
+
return classifyProtocolSetScope(message, initialWrite, scope);
|
|
32
|
+
}
|
|
33
|
+
function classifyProtocolSetScope(message, initialWrite, scope) {
|
|
34
|
+
const descriptor = message.descriptor;
|
|
35
|
+
const scopedDescriptor = getScopedMessageDescriptor(message, initialWrite);
|
|
36
|
+
if (scopedDescriptor === undefined) {
|
|
37
|
+
return 'unknown';
|
|
38
|
+
}
|
|
39
|
+
const permissionRecordClassification = classifyTaggedPermissionRecord(scopedDescriptor, scope.protocols);
|
|
40
|
+
if (permissionRecordClassification !== undefined) {
|
|
41
|
+
return permissionRecordClassification;
|
|
42
|
+
}
|
|
43
|
+
const protocolClassification = classifyProtocolField(scopedDescriptor.protocol, scope.protocols);
|
|
44
|
+
if (protocolClassification !== undefined) {
|
|
45
|
+
return protocolClassification;
|
|
46
|
+
}
|
|
47
|
+
return classifyProtocolsConfigureDescriptor(descriptor, scope.protocols);
|
|
48
|
+
}
|
|
49
|
+
function classifyTaggedPermissionRecord(descriptor, protocols) {
|
|
50
|
+
if (descriptor.protocol !== PermissionsProtocol.uri || !isRecordObject(descriptor.tags)) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
const taggedProtocol = descriptor.tags.protocol;
|
|
54
|
+
return typeof taggedProtocol === 'string' && protocols.includes(taggedProtocol)
|
|
55
|
+
? 'in-scope'
|
|
56
|
+
: 'out-of-scope';
|
|
57
|
+
}
|
|
58
|
+
function classifyProtocolField(protocol, protocols) {
|
|
59
|
+
if (typeof protocol !== 'string') {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
return protocols.includes(protocol) ? 'in-scope' : 'out-of-scope';
|
|
63
|
+
}
|
|
64
|
+
function classifyProtocolsConfigureDescriptor(descriptor, protocols) {
|
|
65
|
+
if (descriptor.interface === DwnInterfaceName.Protocols &&
|
|
66
|
+
descriptor.method === DwnMethodName.Configure &&
|
|
67
|
+
isRecordObject(descriptor.definition)) {
|
|
68
|
+
const definitionProtocol = descriptor.definition.protocol;
|
|
69
|
+
return typeof definitionProtocol === 'string' && protocols.includes(definitionProtocol)
|
|
70
|
+
? 'in-scope'
|
|
71
|
+
: 'out-of-scope';
|
|
72
|
+
}
|
|
73
|
+
return 'out-of-scope';
|
|
74
|
+
}
|
|
75
|
+
function getScopedMessageDescriptor(message, initialWrite) {
|
|
76
|
+
const descriptor = message.descriptor;
|
|
77
|
+
if (descriptor.interface === DwnInterfaceName.Records &&
|
|
78
|
+
descriptor.method === DwnMethodName.Delete) {
|
|
79
|
+
return initialWrite === null || initialWrite === void 0 ? void 0 : initialWrite.descriptor;
|
|
80
|
+
}
|
|
81
|
+
return descriptor;
|
|
82
|
+
}
|
|
83
|
+
function classifyRecordsProjectionScope(message, initialWrite, scope) {
|
|
84
|
+
const target = getRecordsProjectionTarget(message, initialWrite);
|
|
85
|
+
if (target === undefined) {
|
|
86
|
+
return isRecordsDelete(message) ? 'unknown' : 'out-of-scope';
|
|
87
|
+
}
|
|
88
|
+
return scope.scopes.some(projectionScope => PermissionScopeMatcher.matches(projectionScope, target))
|
|
89
|
+
? 'in-scope'
|
|
90
|
+
: 'out-of-scope';
|
|
91
|
+
}
|
|
92
|
+
function getRecordsProjectionTarget(message, initialWrite) {
|
|
93
|
+
const descriptor = message.descriptor;
|
|
94
|
+
if (descriptor.interface !== DwnInterfaceName.Records) {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
if (descriptor.method === DwnMethodName.Write) {
|
|
98
|
+
return {
|
|
99
|
+
protocol: stringField(descriptor.protocol),
|
|
100
|
+
protocolPath: stringField(descriptor.protocolPath),
|
|
101
|
+
contextId: message.contextId,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
if (descriptor.method === DwnMethodName.Delete) {
|
|
105
|
+
if (initialWrite === undefined) {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
protocol: initialWrite.descriptor.protocol,
|
|
110
|
+
protocolPath: initialWrite.descriptor.protocolPath,
|
|
111
|
+
contextId: initialWrite.contextId,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function isRecordsDelete(message) {
|
|
116
|
+
const descriptor = message.descriptor;
|
|
117
|
+
return descriptor.interface === DwnInterfaceName.Records &&
|
|
118
|
+
descriptor.method === DwnMethodName.Delete;
|
|
119
|
+
}
|
|
120
|
+
function isRecordObject(value) {
|
|
121
|
+
return typeof value === 'object' && value !== null;
|
|
122
|
+
}
|
|
123
|
+
function stringField(value) {
|
|
124
|
+
return typeof value === 'string' ? value : undefined;
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=sync-scope-acceptance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-scope-acceptance.js","sourceRoot":"","sources":["../../src/sync-scope-acceptance.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,11 +1,44 @@
|
|
|
1
|
+
import { getInvokedPermissionGrantIds } from './sync-permission-grants.js';
|
|
1
2
|
import { DwnInterfaceName, DwnMethodName, PermissionsProtocol } from '@enbox/dwn-sdk-js';
|
|
3
|
+
function isRecordsDescriptor(descriptor) {
|
|
4
|
+
return descriptor.interface === DwnInterfaceName.Records;
|
|
5
|
+
}
|
|
6
|
+
function isRecordsWriteDescriptor(descriptor) {
|
|
7
|
+
return descriptor.interface === DwnInterfaceName.Records && descriptor.method === DwnMethodName.Write;
|
|
8
|
+
}
|
|
9
|
+
function isRecordsDeleteDescriptor(descriptor) {
|
|
10
|
+
return descriptor.interface === DwnInterfaceName.Records && descriptor.method === DwnMethodName.Delete;
|
|
11
|
+
}
|
|
12
|
+
function isProtocolsConfigureDescriptor(descriptor) {
|
|
13
|
+
return descriptor.interface === DwnInterfaceName.Protocols && descriptor.method === DwnMethodName.Configure;
|
|
14
|
+
}
|
|
15
|
+
function getRecordId(message) {
|
|
16
|
+
return message.recordId;
|
|
17
|
+
}
|
|
18
|
+
function getProtocolDefinition(message) {
|
|
19
|
+
const { descriptor } = message;
|
|
20
|
+
return isProtocolsConfigureDescriptor(descriptor) ? descriptor.definition : undefined;
|
|
21
|
+
}
|
|
22
|
+
function getConfiguredProtocol(message) {
|
|
23
|
+
var _a;
|
|
24
|
+
const protocol = (_a = getProtocolDefinition(message)) === null || _a === void 0 ? void 0 : _a.protocol;
|
|
25
|
+
return typeof protocol === 'string' ? protocol : undefined;
|
|
26
|
+
}
|
|
27
|
+
function getComposedProtocolDependencies(message) {
|
|
28
|
+
var _a;
|
|
29
|
+
const uses = (_a = getProtocolDefinition(message)) === null || _a === void 0 ? void 0 : _a.uses;
|
|
30
|
+
if (!uses) {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
return Object.values(uses).filter((protocol) => typeof protocol === 'string');
|
|
34
|
+
}
|
|
2
35
|
/**
|
|
3
36
|
* Checks whether a message is an initial RecordsWrite (not an update).
|
|
4
37
|
* An initial write has dateCreated === messageTimestamp (first write for this recordId).
|
|
5
38
|
*/
|
|
6
39
|
function isInitialWrite(message) {
|
|
7
40
|
const desc = message.descriptor;
|
|
8
|
-
if (desc
|
|
41
|
+
if (!isRecordsWriteDescriptor(desc)) {
|
|
9
42
|
return false;
|
|
10
43
|
}
|
|
11
44
|
// A RecordsWrite is initial if dateCreated === messageTimestamp (first write for this recordId).
|
|
@@ -17,12 +50,12 @@ function isInitialWrite(message) {
|
|
|
17
50
|
*
|
|
18
51
|
* Dependencies:
|
|
19
52
|
* - ProtocolsConfigure must come before any RecordsWrite using that protocol
|
|
53
|
+
* - Composed ProtocolsConfigure must come after ProtocolsConfigure messages for protocols in `uses`
|
|
20
54
|
* - Parent record must come before child record (via parentId)
|
|
21
55
|
* - Initial write must come before update writes (same recordId, not initial)
|
|
22
|
-
* - Permission
|
|
56
|
+
* - Permission grants must come before messages that invoke them
|
|
23
57
|
*/
|
|
24
58
|
export function topologicalSort(messages) {
|
|
25
|
-
var _a;
|
|
26
59
|
if (messages.length <= 1) {
|
|
27
60
|
return messages;
|
|
28
61
|
}
|
|
@@ -36,13 +69,13 @@ export function topologicalSort(messages) {
|
|
|
36
69
|
byIndex.set(i, entry);
|
|
37
70
|
const desc = entry.message.descriptor;
|
|
38
71
|
if (desc.interface === DwnInterfaceName.Protocols && desc.method === DwnMethodName.Configure) {
|
|
39
|
-
const protocolUrl = (
|
|
72
|
+
const protocolUrl = getConfiguredProtocol(entry.message);
|
|
40
73
|
if (protocolUrl) {
|
|
41
74
|
protocolConfigureIndex.set(protocolUrl, i);
|
|
42
75
|
}
|
|
43
76
|
}
|
|
44
|
-
if (desc
|
|
45
|
-
const recordId = entry.message
|
|
77
|
+
if (isRecordsWriteDescriptor(desc)) {
|
|
78
|
+
const recordId = getRecordId(entry.message);
|
|
46
79
|
const initial = isInitialWrite(entry.message);
|
|
47
80
|
if (initial && recordId) {
|
|
48
81
|
initialWriteIndex.set(recordId, i);
|
|
@@ -73,38 +106,47 @@ export function topologicalSort(messages) {
|
|
|
73
106
|
};
|
|
74
107
|
for (let i = 0; i < messages.length; i++) {
|
|
75
108
|
const desc = messages[i].message.descriptor;
|
|
109
|
+
// Composition dependency: a composed protocol depends on its referenced protocol definitions.
|
|
110
|
+
if (isProtocolsConfigureDescriptor(desc)) {
|
|
111
|
+
for (const protocol of getComposedProtocolDependencies(messages[i].message)) {
|
|
112
|
+
if (protocolConfigureIndex.has(protocol)) {
|
|
113
|
+
addEdge(protocolConfigureIndex.get(protocol), i);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
76
117
|
// Protocol dependency: RecordsWrite depends on ProtocolsConfigure for its protocol.
|
|
77
|
-
if (desc
|
|
118
|
+
if (isRecordsDescriptor(desc)) {
|
|
78
119
|
const protocol = desc.protocol;
|
|
79
120
|
if (protocol && protocolConfigureIndex.has(protocol)) {
|
|
80
121
|
addEdge(protocolConfigureIndex.get(protocol), i);
|
|
81
122
|
}
|
|
82
123
|
}
|
|
83
124
|
// Parent dependency: child record depends on parent record.
|
|
84
|
-
if (desc
|
|
125
|
+
if (isRecordsDescriptor(desc) && desc.parentId) {
|
|
85
126
|
const parentId = desc.parentId;
|
|
86
127
|
if (initialWriteIndex.has(parentId)) {
|
|
87
128
|
addEdge(initialWriteIndex.get(parentId), i);
|
|
88
129
|
}
|
|
89
130
|
}
|
|
90
131
|
// Initial write dependency: update depends on initial write.
|
|
91
|
-
if (desc
|
|
92
|
-
const recordId = messages[i].message
|
|
132
|
+
if (isRecordsWriteDescriptor(desc)) {
|
|
133
|
+
const recordId = getRecordId(messages[i].message);
|
|
93
134
|
if (recordId && !isInitialWrite(messages[i].message) && initialWriteIndex.has(recordId)) {
|
|
94
135
|
addEdge(initialWriteIndex.get(recordId), i);
|
|
95
136
|
}
|
|
96
137
|
}
|
|
97
138
|
// Delete depends on initial write.
|
|
98
|
-
if (desc
|
|
139
|
+
if (isRecordsDeleteDescriptor(desc)) {
|
|
99
140
|
const recordId = desc.recordId;
|
|
100
141
|
if (recordId && initialWriteIndex.has(recordId)) {
|
|
101
142
|
addEdge(initialWriteIndex.get(recordId), i);
|
|
102
143
|
}
|
|
103
144
|
}
|
|
104
|
-
// Permission grant dependency: message depends on
|
|
105
|
-
const permissionGrantId
|
|
106
|
-
|
|
107
|
-
|
|
145
|
+
// Permission grant dependency: message depends on each grant it references.
|
|
146
|
+
for (const permissionGrantId of getInvokedPermissionGrantIds(messages[i].message)) {
|
|
147
|
+
if (grantIndex.has(permissionGrantId)) {
|
|
148
|
+
addEdge(grantIndex.get(permissionGrantId), i);
|
|
149
|
+
}
|
|
108
150
|
}
|
|
109
151
|
}
|
|
110
152
|
// Kahn's algorithm for topological sort.
|