@scp-dev/hub 0.5.1 → 0.6.0
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/index.d.ts +15 -0
- package/dist/index.js +69 -3
- package/package.json +2 -2
- package/src/federation.ts +82 -3
package/dist/index.d.ts
CHANGED
|
@@ -36,6 +36,8 @@ interface MirrorScope {
|
|
|
36
36
|
parent_ep?: string | null;
|
|
37
37
|
/** 0 for a root; null when not known */
|
|
38
38
|
depth?: number | null;
|
|
39
|
+
/** a parent on another machine of the account, `<machine>/<handle>`; null for none */
|
|
40
|
+
parent_addr?: string | null;
|
|
39
41
|
rules?: MirrorRules | null;
|
|
40
42
|
caps?: MirrorCaps | null;
|
|
41
43
|
/**
|
|
@@ -162,6 +164,17 @@ declare class FederatedCore extends RelayCore {
|
|
|
162
164
|
protected resolve(address: unknown): EndpointRow;
|
|
163
165
|
protected addressUsers(): string[];
|
|
164
166
|
protected localRow(row: EndpointRow): boolean;
|
|
167
|
+
/** `<machine>/<handle>` of a row of this account: a mirrored row's remote, a local one's on this machine; null for another account's or without a machine */
|
|
168
|
+
private keyOf;
|
|
169
|
+
/** the row `<machine>/<handle>` names: a local one on this machine, else a mirrored one */
|
|
170
|
+
private byKey;
|
|
171
|
+
/** whether walking up from `start` (local and mirrored parents, then a link across machines) reaches a row whose key or id is in `targets` */
|
|
172
|
+
private reachesUp;
|
|
173
|
+
/** a parent on another machine is a mirrored agent of this account that neither is nor sits under the child or one of its descendants */
|
|
174
|
+
protected linkAcross(row: EndpointRow, parent: EndpointRow): string;
|
|
175
|
+
protected renamedHandle(from: string, to: string): void;
|
|
176
|
+
protected linkedAcross(a: EndpointRow, b: EndpointRow): boolean;
|
|
177
|
+
protected linkedParent(row: EndpointRow, shown?: Set<string>): string | null;
|
|
165
178
|
/** a live mirrored row shows the presence its machine mirrored, not one its last_seen decays to here */
|
|
166
179
|
protected view(row: EndpointRow, shown?: Set<string>): EndpointView;
|
|
167
180
|
protected address(row: EndpointRow): string;
|
|
@@ -375,6 +388,8 @@ declare class FederatedCore extends RelayCore {
|
|
|
375
388
|
private placer;
|
|
376
389
|
/** an agent row's place, rules, caps, title and incarnation after a view: what it left out stays, unless the endpoint is a new incarnation */
|
|
377
390
|
private scopeColumns;
|
|
391
|
+
/** whether a link of `row` under `key` closes a cycle in what this relay mirrors */
|
|
392
|
+
private loops;
|
|
378
393
|
/** wire caps of `machine` with their `ep:<handle>` references as this core's row ids; an endpoint the machine does not mirror is `gone`, which admits nothing */
|
|
379
394
|
private localCaps;
|
|
380
395
|
/** caps with this core's row ids named as `ep:<handle>`, as they cross machines */
|
package/dist/index.js
CHANGED
|
@@ -223,6 +223,57 @@ var FederatedCore = class extends RelayCore {
|
|
|
223
223
|
localRow(row) {
|
|
224
224
|
return !row.remote;
|
|
225
225
|
}
|
|
226
|
+
/** `<machine>/<handle>` of a row of this account: a mirrored row's remote, a local one's on this machine; null for another account's or without a machine */
|
|
227
|
+
keyOf(row) {
|
|
228
|
+
const remote = row.remote;
|
|
229
|
+
if (remote) return isForeign(remote) ? null : remote;
|
|
230
|
+
return this.machine === null ? null : `${this.machine}/${row.handle}`;
|
|
231
|
+
}
|
|
232
|
+
/** the row `<machine>/<handle>` names: a local one on this machine, else a mirrored one */
|
|
233
|
+
byKey(key) {
|
|
234
|
+
if (!REMOTE.test(key)) return void 0;
|
|
235
|
+
const handle = handlePart(key);
|
|
236
|
+
if (machinePart(key) === this.machine) return validHandle(handle) ? this.byHandle(handle) : void 0;
|
|
237
|
+
return this.byRemote(key);
|
|
238
|
+
}
|
|
239
|
+
/** whether walking up from `start` (local and mirrored parents, then a link across machines) reaches a row whose key or id is in `targets` */
|
|
240
|
+
reachesUp(start, targets) {
|
|
241
|
+
const seen = /* @__PURE__ */ new Set();
|
|
242
|
+
let row = start;
|
|
243
|
+
while (row && !seen.has(row.id) && seen.size <= LIMITS.depthMax * 4) {
|
|
244
|
+
seen.add(row.id);
|
|
245
|
+
const key = this.keyOf(row);
|
|
246
|
+
if (targets.has(row.id) || key !== null && targets.has(key)) return true;
|
|
247
|
+
row = row.parent_ep ? this.byId(row.parent_ep) : row.parent_addr ? this.byKey(row.parent_addr) : void 0;
|
|
248
|
+
}
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
/** a parent on another machine is a mirrored agent of this account that neither is nor sits under the child or one of its descendants */
|
|
252
|
+
linkAcross(row, parent) {
|
|
253
|
+
const remote = parent.remote;
|
|
254
|
+
if (!remote || isForeign(remote) || parent.kind !== "agent" || handlePart(remote) === "")
|
|
255
|
+
throw new RelayError("invalid_params", "the parent is an agent endpoint of this account");
|
|
256
|
+
if (row.parent_addr === remote) return remote;
|
|
257
|
+
const below = this.sql.all(`SELECT id FROM endpoints WHERE tree = ?`, row.id).map((r) => r.id);
|
|
258
|
+
const key = this.keyOf(row);
|
|
259
|
+
if (this.reachesUp(parent, /* @__PURE__ */ new Set([row.id, ...below, ...key === null ? [] : [key]])))
|
|
260
|
+
throw new RelayError("scope_refused", `${this.address(parent)} sits under ${this.address(row)}: a session cannot go under its own descendant`);
|
|
261
|
+
return remote;
|
|
262
|
+
}
|
|
263
|
+
renamedHandle(from, to) {
|
|
264
|
+
if (this.machine !== null) this.sql.all(`UPDATE endpoints SET parent_addr = ? WHERE parent_addr = ?`, `${this.machine}/${to}`, `${this.machine}/${from}`);
|
|
265
|
+
}
|
|
266
|
+
linkedAcross(a, b) {
|
|
267
|
+
const [ka, kb] = [this.keyOf(a), this.keyOf(b)];
|
|
268
|
+
return a.parent_addr !== null && a.parent_addr === kb || b.parent_addr !== null && b.parent_addr === ka;
|
|
269
|
+
}
|
|
270
|
+
linkedParent(row, shown) {
|
|
271
|
+
const key = row.parent_addr;
|
|
272
|
+
if (!key) return null;
|
|
273
|
+
const parent = this.byKey(key);
|
|
274
|
+
if (parent) return shown && !shown.has(parent.id) ? null : this.address(parent);
|
|
275
|
+
return shown ? null : this.remoteAddress(key);
|
|
276
|
+
}
|
|
226
277
|
/** a live mirrored row shows the presence its machine mirrored, not one its last_seen decays to here */
|
|
227
278
|
view(row, shown) {
|
|
228
279
|
const view = super.view(row, shown);
|
|
@@ -376,6 +427,7 @@ var FederatedCore = class extends RelayCore {
|
|
|
376
427
|
const old = !before && view.was !== null ? this.byRemote(`${machine}/${view.was}`) : void 0;
|
|
377
428
|
if (old && old.token_hash === null) {
|
|
378
429
|
this.sql.all(`UPDATE endpoints SET remote = ? WHERE id = ?`, remote, old.id);
|
|
430
|
+
this.sql.all(`UPDATE endpoints SET parent_addr = ? WHERE parent_addr = ?`, remote, `${machine}/${view.was}`);
|
|
379
431
|
before = this.byId(old.id);
|
|
380
432
|
renamed.set(old.id, view.was);
|
|
381
433
|
}
|
|
@@ -459,6 +511,7 @@ var FederatedCore = class extends RelayCore {
|
|
|
459
511
|
tree: root?.handle ?? null,
|
|
460
512
|
parent_ep: parent?.handle ?? null,
|
|
461
513
|
depth: row.depth ?? 0,
|
|
514
|
+
parent_addr: row.parent_addr ?? null,
|
|
462
515
|
rules: Object.keys(rules).length ? rules : null,
|
|
463
516
|
caps: wire,
|
|
464
517
|
title: row.title ?? null,
|
|
@@ -1294,9 +1347,7 @@ var FederatedCore = class extends RelayCore {
|
|
|
1294
1347
|
const was = this.view(before);
|
|
1295
1348
|
const presence = was.presence !== view.presence;
|
|
1296
1349
|
const status = before.status_state !== after.status_state || before.status_note !== after.status_note;
|
|
1297
|
-
const other = ["kind", "harness", "model", "cwd", "labels", "title", "wake", "tree", "parent_ep", "depth", "rules", "caps", "incarnation"].some(
|
|
1298
|
-
(field) => before[field] !== after[field]
|
|
1299
|
-
);
|
|
1350
|
+
const other = ["kind", "harness", "model", "cwd", "labels", "title", "wake", "tree", "parent_ep", "parent_addr", "depth", "rules", "caps", "incarnation"].some((field) => before[field] !== after[field]);
|
|
1300
1351
|
return presence || status || other ? { view, added: false, presence, status } : null;
|
|
1301
1352
|
}
|
|
1302
1353
|
remoteView(row) {
|
|
@@ -1312,6 +1363,7 @@ var FederatedCore = class extends RelayCore {
|
|
|
1312
1363
|
tree: row.depth === null ? null : row.tree ? handle(row.tree) : remote ? handlePart(remote) : row.handle,
|
|
1313
1364
|
parent_ep: handle(row.parent_ep),
|
|
1314
1365
|
depth: row.depth,
|
|
1366
|
+
parent_addr: row.parent_addr ?? null,
|
|
1315
1367
|
rules: row.rules ? JSON.parse(row.rules) : null,
|
|
1316
1368
|
caps: row.caps ? this.wireCaps(JSON.parse(row.caps)) : null,
|
|
1317
1369
|
incarnation: row.incarnation ?? null
|
|
@@ -1357,8 +1409,10 @@ var FederatedCore = class extends RelayCore {
|
|
|
1357
1409
|
caps = merged.caps.length || Object.keys(merged.allow).length ? JSON.stringify(merged) : null;
|
|
1358
1410
|
}
|
|
1359
1411
|
const rules = scope.rules !== void 0 ? scope.rules : fresh ? null : void 0;
|
|
1412
|
+
const linked = scope.parentAddr !== void 0 ? scope.parentAddr : fresh ? null : row.parent_addr;
|
|
1360
1413
|
return {
|
|
1361
1414
|
...place,
|
|
1415
|
+
parent_addr: linked !== null && (place.parent_ep !== null || this.loops(row, linked)) ? null : linked,
|
|
1362
1416
|
rules: rules === void 0 ? row.rules : rules && (rules.in || rules.out) ? JSON.stringify(rules) : null,
|
|
1363
1417
|
caps,
|
|
1364
1418
|
title: scope.title !== void 0 ? scope.title : fresh ? null : row.title,
|
|
@@ -1366,6 +1420,12 @@ var FederatedCore = class extends RelayCore {
|
|
|
1366
1420
|
incarnation: scope.incarnation ?? row.incarnation
|
|
1367
1421
|
};
|
|
1368
1422
|
}
|
|
1423
|
+
/** whether a link of `row` under `key` closes a cycle in what this relay mirrors */
|
|
1424
|
+
loops(row, key) {
|
|
1425
|
+
const parent = this.byKey(key);
|
|
1426
|
+
const own = this.keyOf(row);
|
|
1427
|
+
return parent !== void 0 && this.reachesUp(parent, /* @__PURE__ */ new Set([row.id, ...own === null ? [] : [own]]));
|
|
1428
|
+
}
|
|
1369
1429
|
/** wire caps of `machine` with their `ep:<handle>` references as this core's row ids; an endpoint the machine does not mirror is `gone`, which admits nothing */
|
|
1370
1430
|
localCaps(machine, caps) {
|
|
1371
1431
|
const ref = (value) => {
|
|
@@ -1488,6 +1548,12 @@ function parseScope(view) {
|
|
|
1488
1548
|
if (view.depth !== null && (!Number.isInteger(view.depth) || view.depth < 0 || view.depth > 1e3)) throw bad("depth");
|
|
1489
1549
|
out.depth = view.depth ?? null;
|
|
1490
1550
|
}
|
|
1551
|
+
if (given("parent_addr")) {
|
|
1552
|
+
const key = view.parent_addr;
|
|
1553
|
+
if (key !== null && (typeof key !== "string" || !REMOTE.test(key) || !MACHINE_NAME.test(machinePart(key)) || !validHandle(handlePart(key))))
|
|
1554
|
+
throw bad("parent_addr");
|
|
1555
|
+
out.parentAddr = key ?? null;
|
|
1556
|
+
}
|
|
1491
1557
|
if (given("rules")) {
|
|
1492
1558
|
if (view.rules !== null && !isRecord(view.rules)) throw bad("rules");
|
|
1493
1559
|
const rules = view.rules;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scp-dev/hub",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Swarm Communication Protocol federation and account hub: mirrored endpoints across machines, bridges, direct clients and cross-account delivery.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://scp.dev",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"typecheck": "tsc -p ."
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@scp-dev/core": "0.
|
|
32
|
+
"@scp-dev/core": "0.2.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"tsup": "^8.5.1"
|
package/src/federation.ts
CHANGED
|
@@ -106,6 +106,8 @@ export interface MirrorScope {
|
|
|
106
106
|
parent_ep?: string | null
|
|
107
107
|
/** 0 for a root; null when not known */
|
|
108
108
|
depth?: number | null
|
|
109
|
+
/** a parent on another machine of the account, `<machine>/<handle>`; null for none */
|
|
110
|
+
parent_addr?: string | null
|
|
109
111
|
rules?: MirrorRules | null
|
|
110
112
|
caps?: MirrorCaps | null
|
|
111
113
|
/**
|
|
@@ -147,6 +149,7 @@ interface ParsedScope {
|
|
|
147
149
|
tree?: string | null
|
|
148
150
|
parent?: string | null
|
|
149
151
|
depth?: number | null
|
|
152
|
+
parentAddr?: string | null
|
|
150
153
|
rules?: MirrorRules | null
|
|
151
154
|
caps?: ScopeCaps | null
|
|
152
155
|
title?: string | null
|
|
@@ -449,6 +452,64 @@ export class FederatedCore extends RelayCore {
|
|
|
449
452
|
return !(row as FederatedRow).remote
|
|
450
453
|
}
|
|
451
454
|
|
|
455
|
+
/** `<machine>/<handle>` of a row of this account: a mirrored row's remote, a local one's on this machine; null for another account's or without a machine */
|
|
456
|
+
private keyOf(row: EndpointRow): string | null {
|
|
457
|
+
const remote = (row as FederatedRow).remote
|
|
458
|
+
if (remote) return isForeign(remote) ? null : remote
|
|
459
|
+
return this.machine === null ? null : `${this.machine}/${row.handle}`
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/** the row `<machine>/<handle>` names: a local one on this machine, else a mirrored one */
|
|
463
|
+
private byKey(key: string): EndpointRow | undefined {
|
|
464
|
+
if (!REMOTE.test(key)) return undefined
|
|
465
|
+
const handle = handlePart(key)
|
|
466
|
+
if (machinePart(key) === this.machine) return validHandle(handle) ? this.byHandle(handle) : undefined
|
|
467
|
+
return this.byRemote(key)
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/** whether walking up from `start` (local and mirrored parents, then a link across machines) reaches a row whose key or id is in `targets` */
|
|
471
|
+
private reachesUp(start: EndpointRow, targets: Set<string>): boolean {
|
|
472
|
+
const seen = new Set<string>()
|
|
473
|
+
let row: EndpointRow | undefined = start
|
|
474
|
+
while (row && !seen.has(row.id) && seen.size <= LIMITS.depthMax * 4) {
|
|
475
|
+
seen.add(row.id)
|
|
476
|
+
const key = this.keyOf(row)
|
|
477
|
+
if (targets.has(row.id) || (key !== null && targets.has(key))) return true
|
|
478
|
+
row = row.parent_ep ? this.byId(row.parent_ep) : row.parent_addr ? this.byKey(row.parent_addr) : undefined
|
|
479
|
+
}
|
|
480
|
+
return false
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/** a parent on another machine is a mirrored agent of this account that neither is nor sits under the child or one of its descendants */
|
|
484
|
+
protected override linkAcross(row: EndpointRow, parent: EndpointRow): string {
|
|
485
|
+
const remote = (parent as FederatedRow).remote
|
|
486
|
+
if (!remote || isForeign(remote) || parent.kind !== "agent" || handlePart(remote) === "")
|
|
487
|
+
throw new RelayError("invalid_params", "the parent is an agent endpoint of this account")
|
|
488
|
+
if (row.parent_addr === remote) return remote
|
|
489
|
+
const below = this.sql.all<{ id: string }>(`SELECT id FROM endpoints WHERE tree = ?`, row.id).map((r) => r.id)
|
|
490
|
+
const key = this.keyOf(row)
|
|
491
|
+
if (this.reachesUp(parent, new Set([row.id, ...below, ...(key === null ? [] : [key])])))
|
|
492
|
+
throw new RelayError("scope_refused", `${this.address(parent)} sits under ${this.address(row)}: a session cannot go under its own descendant`)
|
|
493
|
+
return remote
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
protected override renamedHandle(from: string, to: string): void {
|
|
497
|
+
if (this.machine !== null) this.sql.all(`UPDATE endpoints SET parent_addr = ? WHERE parent_addr = ?`, `${this.machine}/${to}`, `${this.machine}/${from}`)
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
protected override linkedAcross(a: EndpointRow, b: EndpointRow): boolean {
|
|
501
|
+
const [ka, kb] = [this.keyOf(a), this.keyOf(b)]
|
|
502
|
+
return (a.parent_addr !== null && a.parent_addr === kb) || (b.parent_addr !== null && b.parent_addr === ka)
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
protected override linkedParent(row: EndpointRow, shown?: Set<string>): string | null {
|
|
506
|
+
const key = row.parent_addr
|
|
507
|
+
if (!key) return null
|
|
508
|
+
const parent = this.byKey(key)
|
|
509
|
+
if (parent) return shown && !shown.has(parent.id) ? null : this.address(parent)
|
|
510
|
+
return shown ? null : this.remoteAddress(key)
|
|
511
|
+
}
|
|
512
|
+
|
|
452
513
|
/** a live mirrored row shows the presence its machine mirrored, not one its last_seen decays to here */
|
|
453
514
|
protected override view(row: EndpointRow, shown?: Set<string>): EndpointView {
|
|
454
515
|
const view = super.view(row, shown)
|
|
@@ -613,6 +674,7 @@ export class FederatedCore extends RelayCore {
|
|
|
613
674
|
const old = !before && view.was !== null ? this.byRemote(`${machine}/${view.was}`) : undefined
|
|
614
675
|
if (old && old.token_hash === null) {
|
|
615
676
|
this.sql.all(`UPDATE endpoints SET remote = ? WHERE id = ?`, remote, old.id)
|
|
677
|
+
this.sql.all(`UPDATE endpoints SET parent_addr = ? WHERE parent_addr = ?`, remote, `${machine}/${view.was}`)
|
|
616
678
|
before = this.byId(old.id) as FederatedRow
|
|
617
679
|
renamed.set(old.id, view.was!)
|
|
618
680
|
}
|
|
@@ -697,6 +759,7 @@ export class FederatedCore extends RelayCore {
|
|
|
697
759
|
tree: root?.handle ?? null,
|
|
698
760
|
parent_ep: parent?.handle ?? null,
|
|
699
761
|
depth: row.depth ?? 0,
|
|
762
|
+
parent_addr: row.parent_addr ?? null,
|
|
700
763
|
rules: Object.keys(rules).length ? rules : null,
|
|
701
764
|
caps: wire,
|
|
702
765
|
title: row.title ?? null,
|
|
@@ -1627,9 +1690,9 @@ export class FederatedCore extends RelayCore {
|
|
|
1627
1690
|
const was = this.view(before)
|
|
1628
1691
|
const presence = was.presence !== view.presence
|
|
1629
1692
|
const status = before.status_state !== after.status_state || before.status_note !== after.status_note
|
|
1630
|
-
const other = (
|
|
1631
|
-
|
|
1632
|
-
)
|
|
1693
|
+
const other = (
|
|
1694
|
+
["kind", "harness", "model", "cwd", "labels", "title", "wake", "tree", "parent_ep", "parent_addr", "depth", "rules", "caps", "incarnation"] as const
|
|
1695
|
+
).some((field) => before[field] !== (after as FederatedRow)[field])
|
|
1633
1696
|
return presence || status || other ? { view, added: false, presence, status } : null
|
|
1634
1697
|
}
|
|
1635
1698
|
|
|
@@ -1646,6 +1709,7 @@ export class FederatedCore extends RelayCore {
|
|
|
1646
1709
|
tree: row.depth === null ? null : row.tree ? handle(row.tree) : remote ? handlePart(remote) : row.handle,
|
|
1647
1710
|
parent_ep: handle(row.parent_ep),
|
|
1648
1711
|
depth: row.depth,
|
|
1712
|
+
parent_addr: row.parent_addr ?? null,
|
|
1649
1713
|
rules: row.rules ? (JSON.parse(row.rules) as MirrorRules) : null,
|
|
1650
1714
|
caps: row.caps ? this.wireCaps(JSON.parse(row.caps) as ScopeCaps) : null,
|
|
1651
1715
|
incarnation: (row as FederatedRow).incarnation ?? null,
|
|
@@ -1695,8 +1759,10 @@ export class FederatedCore extends RelayCore {
|
|
|
1695
1759
|
caps = merged.caps.length || Object.keys(merged.allow).length ? JSON.stringify(merged) : null
|
|
1696
1760
|
}
|
|
1697
1761
|
const rules = scope.rules !== undefined ? scope.rules : fresh ? null : undefined
|
|
1762
|
+
const linked = scope.parentAddr !== undefined ? scope.parentAddr : fresh ? null : row.parent_addr
|
|
1698
1763
|
return {
|
|
1699
1764
|
...place,
|
|
1765
|
+
parent_addr: linked !== null && (place.parent_ep !== null || this.loops(row, linked)) ? null : linked,
|
|
1700
1766
|
rules: rules === undefined ? row.rules : rules && (rules.in || rules.out) ? JSON.stringify(rules) : null,
|
|
1701
1767
|
caps,
|
|
1702
1768
|
title: scope.title !== undefined ? scope.title : fresh ? null : row.title,
|
|
@@ -1705,6 +1771,13 @@ export class FederatedCore extends RelayCore {
|
|
|
1705
1771
|
}
|
|
1706
1772
|
}
|
|
1707
1773
|
|
|
1774
|
+
/** whether a link of `row` under `key` closes a cycle in what this relay mirrors */
|
|
1775
|
+
private loops(row: EndpointRow, key: string): boolean {
|
|
1776
|
+
const parent = this.byKey(key)
|
|
1777
|
+
const own = this.keyOf(row)
|
|
1778
|
+
return parent !== undefined && this.reachesUp(parent, new Set([row.id, ...(own === null ? [] : [own])]))
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1708
1781
|
/** wire caps of `machine` with their `ep:<handle>` references as this core's row ids; an endpoint the machine does not mirror is `gone`, which admits nothing */
|
|
1709
1782
|
private localCaps(machine: string, caps: ScopeCaps): ScopeCaps {
|
|
1710
1783
|
const ref = (value: string) => {
|
|
@@ -1849,6 +1922,12 @@ function parseScope(view: Partial<MirrorView>): ParsedScope {
|
|
|
1849
1922
|
if (view.depth !== null && (!Number.isInteger(view.depth) || (view.depth as number) < 0 || (view.depth as number) > 1000)) throw bad("depth")
|
|
1850
1923
|
out.depth = view.depth ?? null
|
|
1851
1924
|
}
|
|
1925
|
+
if (given("parent_addr")) {
|
|
1926
|
+
const key = view.parent_addr
|
|
1927
|
+
if (key !== null && (typeof key !== "string" || !REMOTE.test(key) || !MACHINE_NAME.test(machinePart(key)) || !validHandle(handlePart(key))))
|
|
1928
|
+
throw bad("parent_addr")
|
|
1929
|
+
out.parentAddr = key ?? null
|
|
1930
|
+
}
|
|
1852
1931
|
if (given("rules")) {
|
|
1853
1932
|
if (view.rules !== null && !isRecord(view.rules)) throw bad("rules")
|
|
1854
1933
|
const rules = view.rules as Record<string, unknown> | null
|