@tinycloud/node-sdk 3.2.0-beta.1 → 3.2.0-beta.3
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/{core-C7-YdUWg.d.cts → WasmKeyProvider-DjfRu9aL.d.cts} +163 -3
- package/dist/{core-C7-YdUWg.d.ts → WasmKeyProvider-DjfRu9aL.d.ts} +163 -3
- package/dist/authority-mTQEgN56.d.cts +97 -0
- package/dist/authority-mTQEgN56.d.ts +97 -0
- package/dist/core.cjs +1076 -447
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +13 -3
- package/dist/core.d.ts +13 -3
- package/dist/core.js +753 -120
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +2382 -495
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -4
- package/dist/index.d.ts +34 -4
- package/dist/index.js +2032 -131
- package/dist/index.js.map +1 -1
- package/dist/replication/authority.cjs +278 -0
- package/dist/replication/authority.cjs.map +1 -0
- package/dist/replication/authority.d.cts +3 -0
- package/dist/replication/authority.d.ts +3 -0
- package/dist/replication/authority.js +257 -0
- package/dist/replication/authority.js.map +1 -0
- package/dist/replication/runtime.cjs +224 -0
- package/dist/replication/runtime.cjs.map +1 -0
- package/dist/replication/runtime.d.cts +3 -0
- package/dist/replication/runtime.d.ts +3 -0
- package/dist/replication/runtime.js +189 -0
- package/dist/replication/runtime.js.map +1 -0
- package/dist/runtime-DyYwHW8j.d.cts +64 -0
- package/dist/runtime-DyYwHW8j.d.ts +64 -0
- package/package.json +25 -3
package/dist/core.js
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
// src/core.ts
|
|
2
2
|
import { TinyCloud as TinyCloud2 } from "@tinycloud/sdk-core";
|
|
3
|
+
|
|
4
|
+
// src/replication/module-registry.ts
|
|
5
|
+
var nodeLoaders;
|
|
6
|
+
function registerReplicationLoaders(loaders) {
|
|
7
|
+
nodeLoaders = loaders;
|
|
8
|
+
}
|
|
9
|
+
function getNodeReplicationLoaders() {
|
|
10
|
+
if (nodeLoaders === void 0) {
|
|
11
|
+
throw new Error("Replication requires registering replication controller loaders.");
|
|
12
|
+
}
|
|
13
|
+
return nodeLoaders;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// src/core.ts
|
|
3
17
|
import {
|
|
4
18
|
SilentNotificationHandler as SilentNotificationHandler2,
|
|
5
19
|
AutoApproveSpaceCreationHandler as AutoApproveSpaceCreationHandler2,
|
|
@@ -16,7 +30,7 @@ import {
|
|
|
16
30
|
makePkhSpaceId as makePkhSpaceId2,
|
|
17
31
|
parsePkhDid,
|
|
18
32
|
pkhDid as pkhDid3,
|
|
19
|
-
principalDid,
|
|
33
|
+
principalDid as principalDid2,
|
|
20
34
|
principalDidEquals as principalDidEquals4,
|
|
21
35
|
parseCanonicalNetworkId,
|
|
22
36
|
TinyCloudDebugLogger,
|
|
@@ -247,7 +261,7 @@ import {
|
|
|
247
261
|
parseCanonicalRecapResource,
|
|
248
262
|
ACCOUNT_REGISTRY_SPACE,
|
|
249
263
|
ACCOUNT_MANIFEST_PERMISSIONS,
|
|
250
|
-
KV,
|
|
264
|
+
KV as KV2,
|
|
251
265
|
SQL,
|
|
252
266
|
DUCKDB,
|
|
253
267
|
CAPABILITIES,
|
|
@@ -255,6 +269,46 @@ import {
|
|
|
255
269
|
ENCRYPTION
|
|
256
270
|
} from "@tinycloud/sdk-core";
|
|
257
271
|
|
|
272
|
+
// src/replication/authority-sign-in.ts
|
|
273
|
+
import {
|
|
274
|
+
grantPathsForSelector,
|
|
275
|
+
KV,
|
|
276
|
+
isCapabilitySubset
|
|
277
|
+
} from "@tinycloud/sdk-core";
|
|
278
|
+
import { requiresSecretsOptIn } from "@tinycloud/sdk-services";
|
|
279
|
+
var KV_GET = KV.GET;
|
|
280
|
+
var KV_SYNC = KV.SYNC;
|
|
281
|
+
var KV_SERVICE = "tinycloud.kv";
|
|
282
|
+
function hasUnrestrictedGetCoverage(entries, spaceId, prefix) {
|
|
283
|
+
const requiredPaths = grantPathsForSelector(prefix);
|
|
284
|
+
return isCapabilitySubset(
|
|
285
|
+
requiredPaths.map((path) => ({ service: KV_SERVICE, space: spaceId, path, actions: [KV_GET] })),
|
|
286
|
+
[...entries]
|
|
287
|
+
).subset;
|
|
288
|
+
}
|
|
289
|
+
function augmentSignInEntriesWithReplication(input) {
|
|
290
|
+
const out = [];
|
|
291
|
+
const replication = input.replication;
|
|
292
|
+
if (replication === void 0) return out;
|
|
293
|
+
for (const prefix of replication.prefixes) {
|
|
294
|
+
if (requiresSecretsOptIn(input.primarySpaceId, prefix) && replication.allowSecrets !== true) {
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
if (!hasUnrestrictedGetCoverage(input.entries, input.primarySpaceId, prefix)) {
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
for (const path of grantPathsForSelector(prefix)) {
|
|
301
|
+
out.push({
|
|
302
|
+
service: KV_SERVICE,
|
|
303
|
+
space: input.primarySpaceId,
|
|
304
|
+
path,
|
|
305
|
+
actions: [KV_GET, KV_SYNC]
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return out;
|
|
310
|
+
}
|
|
311
|
+
|
|
258
312
|
// src/authorization/strategies.ts
|
|
259
313
|
import { createOpenKeyCallbackSigningStrategy } from "@tinycloud/sdk-core";
|
|
260
314
|
var defaultSignStrategy = { type: "auto-sign" };
|
|
@@ -330,7 +384,7 @@ var NodeUserAuthorization = class {
|
|
|
330
384
|
this.spacePrefix = config.spacePrefix ?? "default";
|
|
331
385
|
this.defaultActions = config.defaultActions ?? {
|
|
332
386
|
kv: {
|
|
333
|
-
"": [
|
|
387
|
+
"": [KV2.PUT, KV2.GET, KV2.DEL, KV2.LIST, KV2.METADATA]
|
|
334
388
|
},
|
|
335
389
|
sql: {
|
|
336
390
|
"": [SQL.READ, SQL.WRITE, SQL.SCHEMA, SQL.ADMIN, SQL.EXPORT]
|
|
@@ -368,6 +422,7 @@ var NodeUserAuthorization = class {
|
|
|
368
422
|
this.nonce = config.nonce;
|
|
369
423
|
this.siweConfig = config.siweConfig;
|
|
370
424
|
this.includeAccountRegistryPermissions = config.includeAccountRegistryPermissions ?? true;
|
|
425
|
+
this.replication = config.replication?.enabled === true ? config.replication : void 0;
|
|
371
426
|
this._manifest = config.manifest;
|
|
372
427
|
this._capabilityRequest = config.capabilityRequest;
|
|
373
428
|
this.sessionManager = config.sessionManager ?? this.wasm.createSessionManager();
|
|
@@ -576,6 +631,143 @@ var NodeUserAuthorization = class {
|
|
|
576
631
|
}
|
|
577
632
|
return makePkhSpaceId(address, chainId, space);
|
|
578
633
|
}
|
|
634
|
+
/**
|
|
635
|
+
* The plain-session primary abilities: `defaultActions` (the shared table)
|
|
636
|
+
* unless the primary space IS the account space, where account-registry
|
|
637
|
+
* permissions merge into a private clone so the shared table is never
|
|
638
|
+
* mutated. Shared between `resolveSignInCapabilities` and
|
|
639
|
+
* `signInPermissionInputs` so sign-in and `replicationSignInEntries`
|
|
640
|
+
* can't disagree (§4.6).
|
|
641
|
+
*/
|
|
642
|
+
primaryActionsForSignIn() {
|
|
643
|
+
const primaryActions = this.includeAccountRegistryPermissions && this.spacePrefix === ACCOUNT_REGISTRY_SPACE ? cloneAbilitiesMap(this.defaultActions) : this.defaultActions;
|
|
644
|
+
if (this.includeAccountRegistryPermissions && this.spacePrefix === ACCOUNT_REGISTRY_SPACE) {
|
|
645
|
+
const accountAbilities = resourceCapabilitiesToAbilitiesMap(
|
|
646
|
+
ACCOUNT_MANIFEST_PERMISSIONS
|
|
647
|
+
);
|
|
648
|
+
for (const [service, paths] of Object.entries(accountAbilities)) {
|
|
649
|
+
const existingPaths = primaryActions[service] ?? (primaryActions[service] = {});
|
|
650
|
+
for (const [path, actions] of Object.entries(paths)) {
|
|
651
|
+
const existingActions = existingPaths[path] ?? (existingPaths[path] = []);
|
|
652
|
+
for (const action of actions) {
|
|
653
|
+
if (!existingActions.includes(action)) existingActions.push(action);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
return primaryActions;
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* The abilities a `spacePrefix: "secrets"` plain session actually requests
|
|
662
|
+
* for its primary space: the vault-secrets subtree only — the
|
|
663
|
+
* `defaultActions` root `get` is REPLACED, not merged (sign-in installs
|
|
664
|
+
* this map under the primary space id). Shared between
|
|
665
|
+
* `resolveSignInCapabilities` and `signInPermissionInputs` so the
|
|
666
|
+
* replication augmentation measures coverage against the effective
|
|
667
|
+
* pre-replication request, never the uninstalled `primaryActions` (§4.1).
|
|
668
|
+
*/
|
|
669
|
+
secretsPrimaryAbilities() {
|
|
670
|
+
return {
|
|
671
|
+
kv: {
|
|
672
|
+
"vault/secrets/": [
|
|
673
|
+
KV2.GET,
|
|
674
|
+
KV2.PUT,
|
|
675
|
+
KV2.DEL,
|
|
676
|
+
KV2.LIST,
|
|
677
|
+
KV2.METADATA
|
|
678
|
+
]
|
|
679
|
+
}
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* The effective sign-in request as `PermissionEntry`s plus the resolved
|
|
684
|
+
* primary space id — the inputs to the replication augmentation (§4.1).
|
|
685
|
+
* `resolveSignInCapabilities` merges the augmented entries into its
|
|
686
|
+
* abilities; `replicationSignInEntries()` returns them verbatim, so the
|
|
687
|
+
* entries the SIWE recap carries and the restore check sees are the same.
|
|
688
|
+
*/
|
|
689
|
+
signInPermissionInputs(address, chainId) {
|
|
690
|
+
const request = this.getCapabilityRequest();
|
|
691
|
+
if (request === void 0) {
|
|
692
|
+
const primarySpaceId2 = makePkhSpaceId(address, chainId, this.spacePrefix);
|
|
693
|
+
const effectivePrimary = this.spacePrefix === "secrets" ? this.secretsPrimaryAbilities() : this.primaryActionsForSignIn();
|
|
694
|
+
const kvPaths = effectivePrimary["kv"] ?? {};
|
|
695
|
+
return {
|
|
696
|
+
primarySpaceId: primarySpaceId2,
|
|
697
|
+
entries: Object.entries(kvPaths).map(([path, actions]) => ({
|
|
698
|
+
service: "tinycloud.kv",
|
|
699
|
+
space: primarySpaceId2,
|
|
700
|
+
path,
|
|
701
|
+
actions: [...actions]
|
|
702
|
+
}))
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
const spaceResources = request.resources.filter(
|
|
706
|
+
(entry) => entry.service !== ENCRYPTION_PERMISSION_SERVICE
|
|
707
|
+
);
|
|
708
|
+
const primarySpaceName = spaceResources.find((entry) => entry.space !== "account")?.space ?? DEFAULT_MANIFEST_SPACE;
|
|
709
|
+
const primarySpaceId = this.resolveSpaceName(
|
|
710
|
+
primarySpaceName,
|
|
711
|
+
address,
|
|
712
|
+
chainId
|
|
713
|
+
);
|
|
714
|
+
return {
|
|
715
|
+
primarySpaceId,
|
|
716
|
+
entries: spaceResources.map((entry) => ({
|
|
717
|
+
service: entry.service,
|
|
718
|
+
space: this.resolveSpaceName(entry.space, address, chainId),
|
|
719
|
+
path: entry.path,
|
|
720
|
+
actions: [...entry.actions]
|
|
721
|
+
}))
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Merge replication augmentation entries into a space's abilities map
|
|
726
|
+
* without mutating shared tables (`defaultActions` may back the base map):
|
|
727
|
+
* the primary entry in `spaceAbilities` is replaced with a copy whose `kv`
|
|
728
|
+
* paths gain `get`/`sync`. Returns the merged map for `abilities`.
|
|
729
|
+
*/
|
|
730
|
+
mergeReplicationEntries(spaceAbilities, primarySpaceId, abilities, entries) {
|
|
731
|
+
var _a;
|
|
732
|
+
if (entries.length === 0) {
|
|
733
|
+
return abilities;
|
|
734
|
+
}
|
|
735
|
+
const base = spaceAbilities[primarySpaceId] ?? abilities;
|
|
736
|
+
const kv = {};
|
|
737
|
+
for (const [path, actions] of Object.entries(base["kv"] ?? {})) {
|
|
738
|
+
kv[path] = [...actions];
|
|
739
|
+
}
|
|
740
|
+
for (const entry of entries) {
|
|
741
|
+
const list = kv[_a = entry.path] ?? (kv[_a] = []);
|
|
742
|
+
for (const action of entry.actions) {
|
|
743
|
+
if (!list.includes(action)) list.push(action);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
const merged = { ...base, kv };
|
|
747
|
+
spaceAbilities[primarySpaceId] = merged;
|
|
748
|
+
return merged;
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* The replication entries this authorization's sign-in request carries
|
|
752
|
+
* for the signed-in or restored address (TC-858 §4.1/§4.6): the output of
|
|
753
|
+
* the same augmentation `resolveSignInCapabilities` runs — one
|
|
754
|
+
* `{tinycloud.kv, primarySpace, prefix, [get, sync]}` per configured
|
|
755
|
+
* prefix that already has an unrestricted covering `get` and passes the
|
|
756
|
+
* secrets opt-in gate. Empty when replication is off or no address is
|
|
757
|
+
* known. Pure.
|
|
758
|
+
*/
|
|
759
|
+
replicationSignInEntries() {
|
|
760
|
+
if (this.replication === void 0) return [];
|
|
761
|
+
const address = this._address ?? this._tinyCloudSession?.address;
|
|
762
|
+
const chainId = this._chainId ?? this._tinyCloudSession?.chainId;
|
|
763
|
+
if (address === void 0 || chainId === void 0) return [];
|
|
764
|
+
const { entries, primarySpaceId } = this.signInPermissionInputs(address, chainId);
|
|
765
|
+
return augmentSignInEntriesWithReplication({
|
|
766
|
+
entries,
|
|
767
|
+
primarySpaceId,
|
|
768
|
+
replication: this.replication
|
|
769
|
+
});
|
|
770
|
+
}
|
|
579
771
|
defaultEncryptionNetworkId(address, chainId) {
|
|
580
772
|
return `urn:tinycloud:encryption:${pkhDid(address, chainId)}:default`;
|
|
581
773
|
}
|
|
@@ -585,39 +777,25 @@ var NodeUserAuthorization = class {
|
|
|
585
777
|
const defaultNetworkId = this.defaultEncryptionNetworkId(address, chainId);
|
|
586
778
|
const primarySpaceId2 = makePkhSpaceId(address, chainId, this.spacePrefix);
|
|
587
779
|
const secretsSpaceId = makePkhSpaceId(address, chainId, "secrets");
|
|
588
|
-
const primaryActions = this.
|
|
780
|
+
const primaryActions = this.primaryActionsForSignIn();
|
|
589
781
|
const spaceAbilities2 = {
|
|
590
782
|
[primarySpaceId2]: primaryActions,
|
|
591
|
-
[secretsSpaceId]:
|
|
592
|
-
kv: {
|
|
593
|
-
"vault/secrets/": [
|
|
594
|
-
KV.GET,
|
|
595
|
-
KV.PUT,
|
|
596
|
-
KV.DEL,
|
|
597
|
-
KV.LIST,
|
|
598
|
-
KV.METADATA
|
|
599
|
-
]
|
|
600
|
-
}
|
|
601
|
-
}
|
|
783
|
+
[secretsSpaceId]: this.secretsPrimaryAbilities()
|
|
602
784
|
};
|
|
603
785
|
if (this.includeAccountRegistryPermissions && this.spacePrefix !== ACCOUNT_REGISTRY_SPACE) {
|
|
604
786
|
spaceAbilities2[makePkhSpaceId(address, chainId, ACCOUNT_REGISTRY_SPACE)] = resourceCapabilitiesToAbilitiesMap(ACCOUNT_MANIFEST_PERMISSIONS);
|
|
605
|
-
} else if (this.includeAccountRegistryPermissions) {
|
|
606
|
-
const accountAbilities = resourceCapabilitiesToAbilitiesMap(
|
|
607
|
-
ACCOUNT_MANIFEST_PERMISSIONS
|
|
608
|
-
);
|
|
609
|
-
for (const [service, paths] of Object.entries(accountAbilities)) {
|
|
610
|
-
const existingPaths = primaryActions[service] ?? (primaryActions[service] = {});
|
|
611
|
-
for (const [path, actions] of Object.entries(paths)) {
|
|
612
|
-
const existingActions = existingPaths[path] ?? (existingPaths[path] = []);
|
|
613
|
-
for (const action of actions) {
|
|
614
|
-
if (!existingActions.includes(action)) existingActions.push(action);
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
787
|
}
|
|
788
|
+
const mergedPrimary = this.mergeReplicationEntries(
|
|
789
|
+
spaceAbilities2,
|
|
790
|
+
primarySpaceId2,
|
|
791
|
+
primaryActions,
|
|
792
|
+
augmentSignInEntriesWithReplication({
|
|
793
|
+
...this.signInPermissionInputs(address, chainId),
|
|
794
|
+
replication: this.replication
|
|
795
|
+
})
|
|
796
|
+
);
|
|
619
797
|
return {
|
|
620
|
-
abilities:
|
|
798
|
+
abilities: mergedPrimary,
|
|
621
799
|
spaceId: primarySpaceId2,
|
|
622
800
|
spaceAbilities: spaceAbilities2,
|
|
623
801
|
rawAbilities: {
|
|
@@ -653,8 +831,17 @@ var NodeUserAuthorization = class {
|
|
|
653
831
|
for (const [space, abilities] of Object.entries(bySpace)) {
|
|
654
832
|
spaceAbilities[this.resolveSpaceName(space, address, chainId)] = abilities;
|
|
655
833
|
}
|
|
834
|
+
const mergedPrimaryAbilities = this.mergeReplicationEntries(
|
|
835
|
+
spaceAbilities,
|
|
836
|
+
primarySpaceId,
|
|
837
|
+
spaceAbilities[primarySpaceId] ?? resourceCapabilitiesToAbilitiesMap([]),
|
|
838
|
+
augmentSignInEntriesWithReplication({
|
|
839
|
+
...this.signInPermissionInputs(address, chainId),
|
|
840
|
+
replication: this.replication
|
|
841
|
+
})
|
|
842
|
+
);
|
|
656
843
|
return {
|
|
657
|
-
abilities:
|
|
844
|
+
abilities: mergedPrimaryAbilities,
|
|
658
845
|
spaceId: primarySpaceId,
|
|
659
846
|
spaceAbilities,
|
|
660
847
|
rawAbilities: Object.keys(rawAbilities).length > 0 ? rawAbilities : void 0
|
|
@@ -712,14 +899,14 @@ var NodeUserAuthorization = class {
|
|
|
712
899
|
* Create the space on the TinyCloud server (host delegation).
|
|
713
900
|
* This registers the user as the owner of the space.
|
|
714
901
|
*/
|
|
715
|
-
async hostSpace(targetSpaceId, purpose) {
|
|
902
|
+
async hostSpace(targetSpaceId, purpose, signal) {
|
|
716
903
|
if (!this._tinyCloudSession || !this._address || !this._chainId) {
|
|
717
904
|
throw new Error("Must be signed in to host space");
|
|
718
905
|
}
|
|
719
906
|
await this.ensureTinyCloudHosts();
|
|
720
907
|
const host = this.primaryTinyCloudHost;
|
|
721
908
|
const spaceId = targetSpaceId ?? this._tinyCloudSession.spaceId;
|
|
722
|
-
const peerId = await fetchPeerId(host, spaceId);
|
|
909
|
+
const peerId = await fetchPeerId(host, spaceId, signal);
|
|
723
910
|
const siwe = this.wasm.generateHostSIWEMessage({
|
|
724
911
|
address: this._address,
|
|
725
912
|
chainId: this._chainId,
|
|
@@ -728,9 +915,28 @@ var NodeUserAuthorization = class {
|
|
|
728
915
|
spaceId,
|
|
729
916
|
peerId
|
|
730
917
|
});
|
|
731
|
-
|
|
918
|
+
if (signal?.aborted) throw signal.reason;
|
|
919
|
+
const signing = this.signMessage(siwe, purpose);
|
|
920
|
+
let signature;
|
|
921
|
+
if (!signal) {
|
|
922
|
+
signature = await signing;
|
|
923
|
+
} else {
|
|
924
|
+
let rejectOnAbort;
|
|
925
|
+
const aborted = new Promise((_resolve, reject2) => {
|
|
926
|
+
rejectOnAbort = reject2;
|
|
927
|
+
});
|
|
928
|
+
const onAbort = () => rejectOnAbort(signal.reason);
|
|
929
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
930
|
+
if (signal.aborted) onAbort();
|
|
931
|
+
try {
|
|
932
|
+
signature = await Promise.race([signing, aborted]);
|
|
933
|
+
} finally {
|
|
934
|
+
signal.removeEventListener("abort", onAbort);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
if (signal?.aborted) throw signal.reason;
|
|
732
938
|
const headers = this.wasm.siweToDelegationHeaders({ siwe, signature });
|
|
733
|
-
return submitHostDelegation(host, headers);
|
|
939
|
+
return submitHostDelegation(host, headers, signal);
|
|
734
940
|
}
|
|
735
941
|
/**
|
|
736
942
|
* Create a specific space on the server via host delegation.
|
|
@@ -751,8 +957,8 @@ var NodeUserAuthorization = class {
|
|
|
751
957
|
* keeps its HTTP status (e.g. a typed 401/403) instead of collapsing to
|
|
752
958
|
* `false`.
|
|
753
959
|
*/
|
|
754
|
-
async hostOwnedSpaceResult(spaceId, purpose) {
|
|
755
|
-
return this.hostSpace(spaceId, purpose);
|
|
960
|
+
async hostOwnedSpaceResult(spaceId, purpose, signal) {
|
|
961
|
+
return this.hostSpace(spaceId, purpose, signal);
|
|
756
962
|
}
|
|
757
963
|
/**
|
|
758
964
|
* Ensure the user's space exists on the TinyCloud server.
|
|
@@ -1691,6 +1897,18 @@ var RuntimeDelegationRejectedError = class extends Error {
|
|
|
1691
1897
|
this.reason = reason;
|
|
1692
1898
|
}
|
|
1693
1899
|
};
|
|
1900
|
+
function activationRejectionReason(error) {
|
|
1901
|
+
if (typeof error !== "object" || error === null) return "activation_rejected";
|
|
1902
|
+
if ("code" in error && error.code === "ACTIVATION_TRANSPORT_FAILED") {
|
|
1903
|
+
return "activation_unavailable";
|
|
1904
|
+
}
|
|
1905
|
+
const cause = "cause" in error ? error.cause : void 0;
|
|
1906
|
+
const status = typeof cause === "object" && cause !== null && "status" in cause ? cause.status : void 0;
|
|
1907
|
+
if (typeof status !== "number") return "activation_rejected";
|
|
1908
|
+
if (status === 408 || status === 429 || status >= 500) return "activation_unavailable";
|
|
1909
|
+
if (status === 401) return "activation_unauthenticated";
|
|
1910
|
+
return "activation_rejected";
|
|
1911
|
+
}
|
|
1694
1912
|
function reject(reason, message) {
|
|
1695
1913
|
throw new RuntimeDelegationRejectedError(reason, message);
|
|
1696
1914
|
}
|
|
@@ -2075,7 +2293,10 @@ async function activateValidatedRuntimeDelegation(node, delegation, options) {
|
|
|
2075
2293
|
await node.useRuntimeDelegation(installedCandidate);
|
|
2076
2294
|
}
|
|
2077
2295
|
} catch (error) {
|
|
2078
|
-
reject(
|
|
2296
|
+
reject(
|
|
2297
|
+
activationRejectionReason(error),
|
|
2298
|
+
error instanceof Error ? error.message : String(error)
|
|
2299
|
+
);
|
|
2079
2300
|
}
|
|
2080
2301
|
const installed = node.getRuntimePermissionDelegations().find((candidate) => candidate.cid === cid);
|
|
2081
2302
|
if (!installed) {
|
|
@@ -2148,6 +2369,37 @@ function deserializeDelegation(data) {
|
|
|
2148
2369
|
};
|
|
2149
2370
|
}
|
|
2150
2371
|
|
|
2372
|
+
// src/replication/config.ts
|
|
2373
|
+
import { kvPrefixCovers } from "@tinycloud/sdk-services";
|
|
2374
|
+
function assertValidReplicationConfig(replication) {
|
|
2375
|
+
if (replication === void 0 || replication.enabled !== true) return;
|
|
2376
|
+
if (replication.storage === void 0 || replication.storage === null) {
|
|
2377
|
+
throw new TypeError("replication.enabled requires replication.storage");
|
|
2378
|
+
}
|
|
2379
|
+
if (!Array.isArray(replication.prefixes) || replication.prefixes.length === 0) {
|
|
2380
|
+
throw new TypeError("replication.prefixes must be a non-empty array");
|
|
2381
|
+
}
|
|
2382
|
+
for (const prefix of replication.prefixes) {
|
|
2383
|
+
if (typeof prefix !== "string" || prefix === "") {
|
|
2384
|
+
throw new TypeError("replication.prefixes must not contain an empty prefix");
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
for (const [index, prefix] of replication.prefixes.entries()) {
|
|
2388
|
+
for (const other of replication.prefixes.slice(index + 1)) {
|
|
2389
|
+
if (kvPrefixCovers(prefix, other) || kvPrefixCovers(other, prefix)) {
|
|
2390
|
+
throw new TypeError(
|
|
2391
|
+
`replication.prefixes must not overlap: ${JSON.stringify(prefix)} and ${JSON.stringify(other)}`
|
|
2392
|
+
);
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
if (prefix.split("/", 1)[0] === "vault" && replication.allowSecrets !== true) {
|
|
2396
|
+
throw new TypeError(
|
|
2397
|
+
`replication prefix ${JSON.stringify(prefix)} overlaps the vault namespace; pass allowSecrets to opt in`
|
|
2398
|
+
);
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2151
2403
|
// src/TinyCloudNode.ts
|
|
2152
2404
|
import {
|
|
2153
2405
|
TinyCloud,
|
|
@@ -2186,12 +2438,12 @@ import {
|
|
|
2186
2438
|
canonicalizeRecapCaveats as canonicalizeRecapCaveats2,
|
|
2187
2439
|
recapCaveatsEqual,
|
|
2188
2440
|
expandPermissionEntries as expandPermissionEntriesCore,
|
|
2189
|
-
isCapabilitySubset,
|
|
2441
|
+
isCapabilitySubset as isCapabilitySubset2,
|
|
2190
2442
|
isExplicitOnlyAction,
|
|
2191
2443
|
parseRecapCapabilities,
|
|
2192
2444
|
SERVICE_LONG_TO_SHORT,
|
|
2193
2445
|
SERVICE_SHORT_TO_LONG,
|
|
2194
|
-
KV as
|
|
2446
|
+
KV as KV3,
|
|
2195
2447
|
SQL as SQL2,
|
|
2196
2448
|
DUCKDB as DUCKDB2,
|
|
2197
2449
|
ENCRYPTION as ENCRYPTION2,
|
|
@@ -2201,6 +2453,7 @@ import {
|
|
|
2201
2453
|
verifyDidKeyEd25519Signature,
|
|
2202
2454
|
canonicalizeAddress as canonicalizeAddress2,
|
|
2203
2455
|
pkhDid as pkhDid2,
|
|
2456
|
+
principalDid,
|
|
2204
2457
|
resolveTinyCloudHosts as resolveTinyCloudHosts2,
|
|
2205
2458
|
publishLocationRecord,
|
|
2206
2459
|
principalDidEquals as principalDidEquals3,
|
|
@@ -2672,11 +2925,11 @@ function isSecretPayload(value) {
|
|
|
2672
2925
|
return typeof value === "object" && value !== null && "value" in value && typeof value.value === "string" && "createdAt" in value && typeof value.createdAt === "string" && "updatedAt" in value && typeof value.updatedAt === "string";
|
|
2673
2926
|
}
|
|
2674
2927
|
var ROOT_DELEGATION_ACTIONS = [
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2928
|
+
KV3.PUT,
|
|
2929
|
+
KV3.GET,
|
|
2930
|
+
KV3.DEL,
|
|
2931
|
+
KV3.LIST,
|
|
2932
|
+
KV3.METADATA,
|
|
2680
2933
|
SQL2.READ,
|
|
2681
2934
|
SQL2.WRITE,
|
|
2682
2935
|
SQL2.ADMIN,
|
|
@@ -3127,6 +3380,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3127
3380
|
this.confirmedHostedSpaceIds = /* @__PURE__ */ new Set();
|
|
3128
3381
|
/** Serializes account-registry writes for the active node instance. */
|
|
3129
3382
|
this.accountRegistryTail = Promise.resolve();
|
|
3383
|
+
/** Bounds best-effort registry work on the sign-in critical path. */
|
|
3384
|
+
this.accountRegistryDeadlineMs = 4e3;
|
|
3130
3385
|
/**
|
|
3131
3386
|
* True when the last signIn() detected an interactive signer and skipped
|
|
3132
3387
|
* client-side bootstrap. Apps can read this to know whether bootstrap was
|
|
@@ -3205,11 +3460,46 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3205
3460
|
});
|
|
3206
3461
|
return this.wasmBindings.invokeAny(invocationSession, caveatPreservingEntries, facts);
|
|
3207
3462
|
};
|
|
3463
|
+
assertValidReplicationConfig(config.replication);
|
|
3208
3464
|
this.explicitHost = config.host;
|
|
3209
3465
|
this.config = {
|
|
3210
3466
|
...config,
|
|
3211
3467
|
host: config.host ?? DEFAULT_HOST
|
|
3212
3468
|
};
|
|
3469
|
+
if (this.config.replication?.enabled) {
|
|
3470
|
+
const runtime = getNodeReplicationLoaders().runtime().then(({ ReplicationRuntime }) => {
|
|
3471
|
+
const instance = new ReplicationRuntime(this.config.replication);
|
|
3472
|
+
this.replicationRuntimeInstance = instance;
|
|
3473
|
+
return instance;
|
|
3474
|
+
});
|
|
3475
|
+
this.replicationRuntime = runtime;
|
|
3476
|
+
this.replicationControl = {
|
|
3477
|
+
status: async () => {
|
|
3478
|
+
const status = await runtime.then((ready) => ready.control.status());
|
|
3479
|
+
const scopeError = this.replicationScopeError;
|
|
3480
|
+
if (!scopeError) return status;
|
|
3481
|
+
return this.config.replication.prefixes.map((prefix) => ({
|
|
3482
|
+
prefix,
|
|
3483
|
+
state: "unavailable",
|
|
3484
|
+
lastError: {
|
|
3485
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3486
|
+
code: "SESSION_SCOPE_MISMATCH",
|
|
3487
|
+
message: scopeError
|
|
3488
|
+
},
|
|
3489
|
+
pending: { inFlight: 0, committed: 0, ambiguous: 0 },
|
|
3490
|
+
pinned: [],
|
|
3491
|
+
lagMs: null
|
|
3492
|
+
}));
|
|
3493
|
+
},
|
|
3494
|
+
sync: async (input) => (await runtime).control.sync(input),
|
|
3495
|
+
purge: (input) => {
|
|
3496
|
+
const instance = this.replicationRuntimeInstance;
|
|
3497
|
+
return instance ? instance.control.purge(input) : runtime.then((ready) => ready.control.purge(input));
|
|
3498
|
+
},
|
|
3499
|
+
clearPending: async () => (await runtime).control.clearPending(),
|
|
3500
|
+
close: async () => (await runtime).control.close()
|
|
3501
|
+
};
|
|
3502
|
+
}
|
|
3213
3503
|
if (config.wasmBindings) {
|
|
3214
3504
|
this.wasmBindings = config.wasmBindings;
|
|
3215
3505
|
} else if (_TinyCloudNode.nodeDefaults) {
|
|
@@ -3330,7 +3620,14 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3330
3620
|
siweConfig: config.siweConfig,
|
|
3331
3621
|
manifest: useBootstrapSignInRequest ? void 0 : config.manifest,
|
|
3332
3622
|
capabilityRequest: useBootstrapSignInRequest ? config.includeAccountRegistryPermissions === false ? BOOTSTRAP_DEFAULT_ONLY_SESSION_REQUEST : BOOTSTRAP_SESSION_REQUESTS.default : config.capabilityRequest,
|
|
3333
|
-
includeAccountRegistryPermissions: useBootstrapSignInRequest ? false : config.includeAccountRegistryPermissions
|
|
3623
|
+
includeAccountRegistryPermissions: useBootstrapSignInRequest ? false : config.includeAccountRegistryPermissions,
|
|
3624
|
+
// Sign-in needs only the entry inputs (prefixes + secrets opt-in);
|
|
3625
|
+
// storage and scheduling never leave TinyCloudNode.
|
|
3626
|
+
replication: config.replication?.enabled === true ? {
|
|
3627
|
+
enabled: true,
|
|
3628
|
+
prefixes: config.replication.prefixes,
|
|
3629
|
+
allowSecrets: config.replication.allowSecrets === true
|
|
3630
|
+
} : void 0
|
|
3334
3631
|
});
|
|
3335
3632
|
this.tc = new TinyCloud(this.auth, {
|
|
3336
3633
|
invokeAny: this.invokeAnyWithRuntimePermissions,
|
|
@@ -3581,6 +3878,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3581
3878
|
this.confirmedHostedSpaceIds.clear();
|
|
3582
3879
|
await this.tc.signIn(options);
|
|
3583
3880
|
this.syncResolvedHostFromAuth();
|
|
3881
|
+
this.replicationDelegateSession = void 0;
|
|
3882
|
+
this.replicationScopeError = void 0;
|
|
3883
|
+
if (this.replicationRuntime) await (await this.replicationRuntime).unbind();
|
|
3584
3884
|
const oldGraph = this._serviceGraph;
|
|
3585
3885
|
this._serviceGraph = this.createServiceGraphLifetime();
|
|
3586
3886
|
oldGraph.retire();
|
|
@@ -3590,7 +3890,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3590
3890
|
this.sessionKeyId = signedInSession.sessionKey;
|
|
3591
3891
|
this.sessionKeyJwk = signedInSession.jwk;
|
|
3592
3892
|
}
|
|
3593
|
-
this.initializeServices();
|
|
3893
|
+
await this.initializeServices();
|
|
3594
3894
|
const primarySession = this.currentTinyCloudSession();
|
|
3595
3895
|
if (primarySession) {
|
|
3596
3896
|
this.registerPrimarySessionGrant(primarySession);
|
|
@@ -3602,6 +3902,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
3602
3902
|
}
|
|
3603
3903
|
if (!bootstrapped) {
|
|
3604
3904
|
this.scheduleAccountRegistrySync();
|
|
3905
|
+
await this.accountRegistryTail;
|
|
3605
3906
|
}
|
|
3606
3907
|
this.notificationHandler.success("Successfully signed in");
|
|
3607
3908
|
}
|
|
@@ -4054,7 +4355,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4054
4355
|
provenance: "primary"
|
|
4055
4356
|
});
|
|
4056
4357
|
}
|
|
4057
|
-
async writeManifestRegistryRecords() {
|
|
4358
|
+
async writeManifestRegistryRecords(account, signal) {
|
|
4058
4359
|
const request = this.capabilityRequest;
|
|
4059
4360
|
if (!request || request.registryRecords.length === 0) {
|
|
4060
4361
|
return;
|
|
@@ -4063,8 +4364,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4063
4364
|
throw new Error("Manifest registry write requires wallet mode");
|
|
4064
4365
|
}
|
|
4065
4366
|
const accountSpaceId = this.ownedSpaceId(ACCOUNT_REGISTRY_SPACE2);
|
|
4066
|
-
await this.ensureOwnedSpaceHostedById(accountSpaceId);
|
|
4067
|
-
|
|
4367
|
+
await this.ensureOwnedSpaceHostedById(accountSpaceId, signal);
|
|
4368
|
+
if (signal.aborted) throw signal.reason;
|
|
4369
|
+
const result = await account.applications.register(request.manifests);
|
|
4068
4370
|
if (!result.ok) {
|
|
4069
4371
|
throw Object.assign(
|
|
4070
4372
|
new Error(`Failed to write manifest registry records: ${result.error.message}`),
|
|
@@ -4072,34 +4374,113 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4072
4374
|
);
|
|
4073
4375
|
}
|
|
4074
4376
|
}
|
|
4377
|
+
createAccountRegistryServices(context, signal) {
|
|
4378
|
+
const session = context.session;
|
|
4379
|
+
if (!session) throw new Error("Account registry requires an active service session");
|
|
4380
|
+
const contexts = [context];
|
|
4381
|
+
const accountSpaceId = this.ownedSpaceId(ACCOUNT_REGISTRY_SPACE2);
|
|
4382
|
+
const createScopedContext = (spaceId) => {
|
|
4383
|
+
const scoped = this._serviceGraph.track(new ServiceContext2({
|
|
4384
|
+
invoke: context.invoke,
|
|
4385
|
+
invokeAny: context.invokeAny,
|
|
4386
|
+
fetch: context.fetch,
|
|
4387
|
+
hosts: context.hosts,
|
|
4388
|
+
telemetry: this.config.telemetry
|
|
4389
|
+
}));
|
|
4390
|
+
scoped.setOperationAbortSignal(signal);
|
|
4391
|
+
scoped.setSession({ ...session, spaceId });
|
|
4392
|
+
contexts.push(scoped);
|
|
4393
|
+
return scoped;
|
|
4394
|
+
};
|
|
4395
|
+
const sql = new SQLService2({});
|
|
4396
|
+
const sqlContext = createScopedContext(accountSpaceId);
|
|
4397
|
+
sql.initialize(sqlContext);
|
|
4398
|
+
const spaces = new SpaceService({
|
|
4399
|
+
hosts: context.hosts,
|
|
4400
|
+
session,
|
|
4401
|
+
invoke: context.invoke,
|
|
4402
|
+
fetch: (url, init) => context.fetch(url, {
|
|
4403
|
+
...init,
|
|
4404
|
+
signal: init?.signal ? AbortSignal.any([init.signal, signal]) : signal
|
|
4405
|
+
}),
|
|
4406
|
+
userDid: this.did,
|
|
4407
|
+
createKVService: (spaceId) => {
|
|
4408
|
+
const kv = new KVService2({});
|
|
4409
|
+
kv.initialize(createScopedContext(spaceId));
|
|
4410
|
+
return kv;
|
|
4411
|
+
}
|
|
4412
|
+
});
|
|
4413
|
+
return {
|
|
4414
|
+
contexts,
|
|
4415
|
+
account: new AccountService({
|
|
4416
|
+
getDid: () => this.did,
|
|
4417
|
+
getHost: () => context.hosts[0] ?? this.config.host,
|
|
4418
|
+
getPrimarySpaceId: () => session.spaceId,
|
|
4419
|
+
getAccountSpaceId: () => accountSpaceId,
|
|
4420
|
+
getSpaces: () => spaces,
|
|
4421
|
+
getAccountDb: () => sql.db("account"),
|
|
4422
|
+
ensureAccountSpaceHosted: () => this.ensureOwnedSpaceHostedById(accountSpaceId, signal)
|
|
4423
|
+
})
|
|
4424
|
+
};
|
|
4425
|
+
}
|
|
4075
4426
|
scheduleAccountRegistrySync() {
|
|
4076
4427
|
const session = this.currentTinyCloudSession();
|
|
4077
4428
|
if (!session || this.pendingAccountRegistrySync?.session === session) {
|
|
4078
4429
|
return;
|
|
4079
4430
|
}
|
|
4431
|
+
const controller = new AbortController();
|
|
4432
|
+
const deadline = setTimeout(
|
|
4433
|
+
() => controller.abort(new DOMException("Account registry deadline exceeded", "TimeoutError")),
|
|
4434
|
+
this.accountRegistryDeadlineMs
|
|
4435
|
+
);
|
|
4080
4436
|
const promise = this.enqueueAccountRegistryOperation(session, async () => {
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4437
|
+
const sharedContext = this._serviceContext;
|
|
4438
|
+
if (!sharedContext) return;
|
|
4439
|
+
const context = this._serviceGraph.track(new ServiceContext2({
|
|
4440
|
+
invoke: sharedContext.invoke,
|
|
4441
|
+
invokeAny: sharedContext.invokeAny,
|
|
4442
|
+
fetch: sharedContext.fetch,
|
|
4443
|
+
hosts: sharedContext.hosts,
|
|
4444
|
+
telemetry: this.config.telemetry
|
|
4445
|
+
}));
|
|
4446
|
+
context.setOperationAbortSignal(controller.signal);
|
|
4447
|
+
const current = this.currentTinyCloudSession();
|
|
4448
|
+
if (!current) return;
|
|
4449
|
+
context.setSession({
|
|
4450
|
+
delegationHeader: current.delegationHeader,
|
|
4451
|
+
delegationCid: current.delegationCid,
|
|
4452
|
+
spaceId: current.spaceId,
|
|
4453
|
+
verificationMethod: current.verificationMethod,
|
|
4454
|
+
jwk: current.jwk
|
|
4455
|
+
});
|
|
4456
|
+
const registry = this.createAccountRegistryServices(context, controller.signal);
|
|
4457
|
+
try {
|
|
4458
|
+
await this.withAccountRegistryRetry(async () => {
|
|
4459
|
+
if (controller.signal.aborted) throw controller.signal.reason;
|
|
4460
|
+
if (this.currentTinyCloudSession() !== session) return;
|
|
4461
|
+
await registry.account.index.ensure();
|
|
4462
|
+
if (controller.signal.aborted) throw controller.signal.reason;
|
|
4463
|
+
if (this.currentTinyCloudSession() !== session) return;
|
|
4464
|
+
await this.writeManifestRegistryRecords(registry.account, controller.signal);
|
|
4465
|
+
if (controller.signal.aborted) throw controller.signal.reason;
|
|
4466
|
+
if (this.currentTinyCloudSession() !== session) return;
|
|
4467
|
+
if (this.currentSessionCanListSpaces()) {
|
|
4468
|
+
const result = await registry.account.spaces.syncAccessible();
|
|
4469
|
+
if (controller.signal.aborted) throw controller.signal.reason;
|
|
4470
|
+
if (!result.ok) {
|
|
4471
|
+
throw Object.assign(
|
|
4472
|
+
new Error(`Failed to sync account spaces: ${result.error.message}`),
|
|
4473
|
+
{ cause: result.error }
|
|
4474
|
+
);
|
|
4475
|
+
}
|
|
4100
4476
|
}
|
|
4477
|
+
}, controller.signal);
|
|
4478
|
+
} finally {
|
|
4479
|
+
for (const operationContext of registry.contexts) {
|
|
4480
|
+
operationContext.retire();
|
|
4101
4481
|
}
|
|
4102
|
-
|
|
4482
|
+
clearTimeout(deadline);
|
|
4483
|
+
}
|
|
4103
4484
|
});
|
|
4104
4485
|
this.pendingAccountRegistrySync = { session, promise };
|
|
4105
4486
|
const clearPendingSync = () => {
|
|
@@ -4108,6 +4489,10 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4108
4489
|
}
|
|
4109
4490
|
};
|
|
4110
4491
|
void promise.then(clearPendingSync, clearPendingSync);
|
|
4492
|
+
void promise.then(
|
|
4493
|
+
() => clearTimeout(deadline),
|
|
4494
|
+
() => clearTimeout(deadline)
|
|
4495
|
+
);
|
|
4111
4496
|
}
|
|
4112
4497
|
enqueueAccountRegistryOperation(session, task) {
|
|
4113
4498
|
const operation = this.accountRegistryTail.then(async () => {
|
|
@@ -4143,14 +4528,22 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4143
4528
|
(operation) => operation.service === "space" && this.actionContains(operation.action, "tinycloud.space/list")
|
|
4144
4529
|
);
|
|
4145
4530
|
}
|
|
4146
|
-
async withAccountRegistryRetry(task) {
|
|
4531
|
+
async withAccountRegistryRetry(task, signal) {
|
|
4147
4532
|
const delays = [250, 1e3, 3e3];
|
|
4148
4533
|
let lastError;
|
|
4149
4534
|
for (let attempt = 0; attempt < delays.length; attempt += 1) {
|
|
4535
|
+
if (signal.aborted) {
|
|
4536
|
+
console.warn("TinyCloud account registry sync stopped: deadline exceeded", signal.reason);
|
|
4537
|
+
return;
|
|
4538
|
+
}
|
|
4150
4539
|
try {
|
|
4151
4540
|
await task();
|
|
4152
4541
|
return;
|
|
4153
4542
|
} catch (error) {
|
|
4543
|
+
if (signal.aborted) {
|
|
4544
|
+
console.warn("TinyCloud account registry sync stopped: deadline exceeded", error);
|
|
4545
|
+
return;
|
|
4546
|
+
}
|
|
4154
4547
|
const verdict = authorizationVerdictOf(error);
|
|
4155
4548
|
const message = error instanceof Error ? error.message : String(error);
|
|
4156
4549
|
const isAuthorizationVerdict = verdict === void 0 ? /Unauthorized Action|\b(?:401|403)\b/.test(message) : verdict !== "other";
|
|
@@ -4162,12 +4555,28 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4162
4555
|
return;
|
|
4163
4556
|
}
|
|
4164
4557
|
if (isStorageFullError(error) || isStorageFullError(error?.cause) || message.includes(STORAGE_FULL_MESSAGE) || message.includes(STORAGE_WRITE_TOO_LARGE_MESSAGE)) {
|
|
4165
|
-
console.warn(
|
|
4558
|
+
console.warn(
|
|
4559
|
+
"TinyCloud account registry sync stopped: storage is full",
|
|
4560
|
+
error
|
|
4561
|
+
);
|
|
4166
4562
|
return;
|
|
4167
4563
|
}
|
|
4168
4564
|
lastError = error;
|
|
4169
4565
|
if (attempt < delays.length - 1) {
|
|
4170
|
-
await new Promise((resolve) =>
|
|
4566
|
+
await new Promise((resolve, reject2) => {
|
|
4567
|
+
const timer = setTimeout(() => {
|
|
4568
|
+
signal.removeEventListener("abort", onAbort);
|
|
4569
|
+
resolve();
|
|
4570
|
+
}, delays[attempt]);
|
|
4571
|
+
const onAbort = () => {
|
|
4572
|
+
clearTimeout(timer);
|
|
4573
|
+
signal.removeEventListener("abort", onAbort);
|
|
4574
|
+
reject2(signal.reason);
|
|
4575
|
+
};
|
|
4576
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
4577
|
+
}).catch((error2) => {
|
|
4578
|
+
if (!signal.aborted) throw error2;
|
|
4579
|
+
});
|
|
4171
4580
|
}
|
|
4172
4581
|
}
|
|
4173
4582
|
}
|
|
@@ -4217,7 +4626,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4217
4626
|
* self-revealing: the next write to the space fails immediately and loudly
|
|
4218
4627
|
* with `404 Space not found`.
|
|
4219
4628
|
*/
|
|
4220
|
-
async ensureOwnedSpaceHostedById(spaceId) {
|
|
4629
|
+
async ensureOwnedSpaceHostedById(spaceId, signal) {
|
|
4221
4630
|
if (!this.auth) {
|
|
4222
4631
|
throw new Error("Owned space hosting requires wallet mode");
|
|
4223
4632
|
}
|
|
@@ -4232,7 +4641,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4232
4641
|
if (!host) {
|
|
4233
4642
|
throw new Error("Owned space hosting requires a TinyCloud host");
|
|
4234
4643
|
}
|
|
4235
|
-
const activation = await activateSessionWithHost2(host, session.delegationHeader);
|
|
4644
|
+
const activation = await activateSessionWithHost2(host, session.delegationHeader, signal);
|
|
4236
4645
|
if (activation.success && !activation.skipped?.includes(spaceId)) {
|
|
4237
4646
|
this.confirmedHostedSpaceIds.add(spaceId);
|
|
4238
4647
|
return;
|
|
@@ -4243,15 +4652,16 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4243
4652
|
{ cause: activation }
|
|
4244
4653
|
);
|
|
4245
4654
|
}
|
|
4246
|
-
const
|
|
4655
|
+
const authorization = this.auth;
|
|
4656
|
+
const created = signal ? await authorization.hostOwnedSpaceResult(spaceId, void 0, signal) : await authorization.hostOwnedSpaceResult(spaceId);
|
|
4247
4657
|
if (!created.success) {
|
|
4248
4658
|
throw Object.assign(
|
|
4249
4659
|
new Error(`Failed to create owned space ${spaceId}: ${describeHostFailure2(created)}`),
|
|
4250
4660
|
{ cause: created }
|
|
4251
4661
|
);
|
|
4252
4662
|
}
|
|
4253
|
-
|
|
4254
|
-
const retry = await activateSessionWithHost2(host, session.delegationHeader);
|
|
4663
|
+
if (signal?.aborted) throw signal.reason;
|
|
4664
|
+
const retry = await activateSessionWithHost2(host, session.delegationHeader, signal);
|
|
4255
4665
|
if (!retry.success || retry.skipped?.includes(spaceId)) {
|
|
4256
4666
|
throw Object.assign(
|
|
4257
4667
|
new Error(
|
|
@@ -4529,6 +4939,34 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4529
4939
|
const stagedHost = resolvedHost ?? this.config.host;
|
|
4530
4940
|
const stagedAddress = restoredAddress;
|
|
4531
4941
|
const stagedChainId = sessionData.chainId ?? 1;
|
|
4942
|
+
let replicationPrincipal;
|
|
4943
|
+
let compactReplicationSession;
|
|
4944
|
+
let replicationScopeError;
|
|
4945
|
+
if (this.config.replication?.enabled) {
|
|
4946
|
+
if (stagedAddress !== void 0) {
|
|
4947
|
+
replicationPrincipal = pkhDid2(stagedAddress, stagedChainId);
|
|
4948
|
+
} else {
|
|
4949
|
+
const { replicationScopeFromSignedSession } = await getNodeReplicationLoaders().authority();
|
|
4950
|
+
const signedScope = replicationScopeFromSignedSession({
|
|
4951
|
+
delegationHeader: sessionData.delegationHeader,
|
|
4952
|
+
delegationCid: sessionData.delegationCid,
|
|
4953
|
+
verificationMethod: restoredVerificationMethod,
|
|
4954
|
+
persistedSpace: sessionData.spaceId
|
|
4955
|
+
});
|
|
4956
|
+
if (!signedScope) {
|
|
4957
|
+
replicationScopeError = "Persisted space does not match the locally verified delegation scope.";
|
|
4958
|
+
} else {
|
|
4959
|
+
replicationPrincipal = signedScope.principal;
|
|
4960
|
+
compactReplicationSession = {
|
|
4961
|
+
delegationHeader: sessionData.delegationHeader,
|
|
4962
|
+
delegationCid: sessionData.delegationCid,
|
|
4963
|
+
spaceId: signedScope.space,
|
|
4964
|
+
verificationMethod: restoredVerificationMethod,
|
|
4965
|
+
jwk: stagedJwk
|
|
4966
|
+
};
|
|
4967
|
+
}
|
|
4968
|
+
}
|
|
4969
|
+
}
|
|
4532
4970
|
const stagedNodeDid = stagedAddress ? pkhDid2(stagedAddress, stagedChainId) : canonicalVerificationMethod;
|
|
4533
4971
|
const serviceSession = {
|
|
4534
4972
|
delegationHeader: sessionData.delegationHeader,
|
|
@@ -4607,7 +5045,18 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4607
5045
|
} else {
|
|
4608
5046
|
this._restoredTcSession = stagedTcSession;
|
|
4609
5047
|
}
|
|
5048
|
+
this.replicationDelegateSession = compactReplicationSession;
|
|
5049
|
+
this.replicationScopeError = replicationScopeError;
|
|
5050
|
+
if (this.replicationRuntime) await (await this.replicationRuntime).unbind();
|
|
4610
5051
|
oldGraph.retire();
|
|
5052
|
+
if (replicationScopeError) {
|
|
5053
|
+
stagedGraph.serviceContext.emit("replication.state", {
|
|
5054
|
+
state: "unavailable",
|
|
5055
|
+
code: "SESSION_SCOPE_MISMATCH",
|
|
5056
|
+
detail: replicationScopeError
|
|
5057
|
+
});
|
|
5058
|
+
}
|
|
5059
|
+
await this.bindReplicationRuntime(stagedGraph.serviceContext, serviceSession, stagedGraph.kv, replicationPrincipal);
|
|
4611
5060
|
oldCore?.retireServices?.();
|
|
4612
5061
|
}
|
|
4613
5062
|
stagePrimarySessionState(session, verificationMethod, host, sessionExpiry, recap) {
|
|
@@ -4761,6 +5210,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4761
5210
|
}));
|
|
4762
5211
|
context.setSession(config.session);
|
|
4763
5212
|
service.initialize(context);
|
|
5213
|
+
this.attachReplicationKV(config.session.spaceId, service);
|
|
4764
5214
|
return service;
|
|
4765
5215
|
}
|
|
4766
5216
|
});
|
|
@@ -4801,6 +5251,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4801
5251
|
}));
|
|
4802
5252
|
context.setSession({ ...input.serviceSession, spaceId });
|
|
4803
5253
|
scopedKv.initialize(context);
|
|
5254
|
+
this.attachReplicationKV(spaceId, scopedKv);
|
|
4804
5255
|
return scopedKv;
|
|
4805
5256
|
},
|
|
4806
5257
|
createVaultService: (spaceId) => {
|
|
@@ -4815,6 +5266,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
4815
5266
|
}));
|
|
4816
5267
|
context.setSession({ ...input.serviceSession, spaceId });
|
|
4817
5268
|
scopedKv.initialize(context);
|
|
5269
|
+
this.attachReplicationKV(spaceId, scopedKv);
|
|
4818
5270
|
const scopedVault = this.createVaultService(spaceId, scopedKv, encryption, {
|
|
4819
5271
|
host: input.host,
|
|
4820
5272
|
did: input.nodeDid,
|
|
@@ -5095,7 +5547,12 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5095
5547
|
siweConfig: this.config.siweConfig,
|
|
5096
5548
|
manifest: useBootstrapSignInRequest ? void 0 : this.config.manifest,
|
|
5097
5549
|
capabilityRequest: useBootstrapSignInRequest ? this.config.includeAccountRegistryPermissions === false ? BOOTSTRAP_DEFAULT_ONLY_SESSION_REQUEST : BOOTSTRAP_SESSION_REQUESTS.default : this.config.capabilityRequest,
|
|
5098
|
-
includeAccountRegistryPermissions: useBootstrapSignInRequest ? false : this.config.includeAccountRegistryPermissions
|
|
5550
|
+
includeAccountRegistryPermissions: useBootstrapSignInRequest ? false : this.config.includeAccountRegistryPermissions,
|
|
5551
|
+
replication: this.config.replication?.enabled === true ? {
|
|
5552
|
+
enabled: true,
|
|
5553
|
+
prefixes: this.config.replication.prefixes,
|
|
5554
|
+
allowSecrets: this.config.replication.allowSecrets === true
|
|
5555
|
+
} : void 0
|
|
5099
5556
|
});
|
|
5100
5557
|
this.tc = new TinyCloud(this.auth, {
|
|
5101
5558
|
invokeAny: this.invokeAnyWithRuntimePermissions,
|
|
@@ -5148,7 +5605,12 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5148
5605
|
siweConfig: this.config.siweConfig,
|
|
5149
5606
|
manifest: useBootstrapSignInRequest ? void 0 : this.config.manifest,
|
|
5150
5607
|
capabilityRequest: useBootstrapSignInRequest ? this.config.includeAccountRegistryPermissions === false ? BOOTSTRAP_DEFAULT_ONLY_SESSION_REQUEST : BOOTSTRAP_SESSION_REQUESTS.default : this.config.capabilityRequest,
|
|
5151
|
-
includeAccountRegistryPermissions: useBootstrapSignInRequest ? false : this.config.includeAccountRegistryPermissions
|
|
5608
|
+
includeAccountRegistryPermissions: useBootstrapSignInRequest ? false : this.config.includeAccountRegistryPermissions,
|
|
5609
|
+
replication: this.config.replication?.enabled === true ? {
|
|
5610
|
+
enabled: true,
|
|
5611
|
+
prefixes: this.config.replication.prefixes,
|
|
5612
|
+
allowSecrets: this.config.replication.allowSecrets === true
|
|
5613
|
+
} : void 0
|
|
5152
5614
|
});
|
|
5153
5615
|
this.tc = new TinyCloud(this.auth, {
|
|
5154
5616
|
invokeAny: this.invokeAnyWithRuntimePermissions,
|
|
@@ -5160,7 +5622,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5160
5622
|
* Initialize the service context and KV service after sign-in.
|
|
5161
5623
|
* @internal
|
|
5162
5624
|
*/
|
|
5163
|
-
initializeServices() {
|
|
5625
|
+
async initializeServices() {
|
|
5164
5626
|
const session = this.currentTinyCloudSession();
|
|
5165
5627
|
if (!session) {
|
|
5166
5628
|
return;
|
|
@@ -5203,12 +5665,18 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5203
5665
|
jwk: session.jwk
|
|
5204
5666
|
};
|
|
5205
5667
|
this._serviceContext.setSession(serviceSession);
|
|
5668
|
+
await this.bindReplicationRuntime(this._serviceContext, serviceSession, this._kv);
|
|
5206
5669
|
this.tc.serviceContext.setSession(serviceSession);
|
|
5207
5670
|
this._vault = this.createVaultService(session.spaceId, this._kv);
|
|
5208
5671
|
this._vault.initialize(this._serviceContext);
|
|
5209
5672
|
this._serviceContext.registerService("vault", this._vault);
|
|
5210
5673
|
this.initializeV2Services(serviceSession);
|
|
5211
5674
|
}
|
|
5675
|
+
attachReplicationKV(spaceId, kvService) {
|
|
5676
|
+
if (!this.replicationRuntime) return;
|
|
5677
|
+
const attached = this.replicationRuntime.then((runtime) => runtime.attach(spaceId, kvService));
|
|
5678
|
+
kvService.setReadThroughReady(attached);
|
|
5679
|
+
}
|
|
5212
5680
|
createSpaceScopedKVService(spaceId) {
|
|
5213
5681
|
const kvService = new KVService2({});
|
|
5214
5682
|
if (this._serviceContext) {
|
|
@@ -5224,6 +5692,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5224
5692
|
spaceScopedContext.setSession({ ...session, spaceId });
|
|
5225
5693
|
}
|
|
5226
5694
|
kvService.initialize(spaceScopedContext);
|
|
5695
|
+
this.attachReplicationKV(spaceId, kvService);
|
|
5227
5696
|
}
|
|
5228
5697
|
return kvService;
|
|
5229
5698
|
}
|
|
@@ -5686,6 +6155,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
5686
6155
|
}));
|
|
5687
6156
|
context.setSession(config.session);
|
|
5688
6157
|
service.initialize(context);
|
|
6158
|
+
this.attachReplicationKV(config.session.spaceId, service);
|
|
5689
6159
|
return service;
|
|
5690
6160
|
},
|
|
5691
6161
|
onRootDelegationNeeded: this.signer ? async (params) => {
|
|
@@ -6010,6 +6480,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6010
6480
|
}));
|
|
6011
6481
|
spaceScopedContext.setSession({ ...this._serviceContext.session, spaceId });
|
|
6012
6482
|
kv.initialize(spaceScopedContext);
|
|
6483
|
+
this.attachReplicationKV(spaceId, kv);
|
|
6013
6484
|
return kv;
|
|
6014
6485
|
}
|
|
6015
6486
|
/**
|
|
@@ -6432,10 +6903,18 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6432
6903
|
);
|
|
6433
6904
|
}
|
|
6434
6905
|
const targetHost = delegation.host ?? this.config.host;
|
|
6435
|
-
|
|
6436
|
-
|
|
6437
|
-
|
|
6438
|
-
|
|
6906
|
+
let activateResult;
|
|
6907
|
+
try {
|
|
6908
|
+
activateResult = await activateSessionWithHost2(
|
|
6909
|
+
targetHost,
|
|
6910
|
+
delegation.delegationHeader
|
|
6911
|
+
);
|
|
6912
|
+
} catch (cause) {
|
|
6913
|
+
throw Object.assign(
|
|
6914
|
+
new Error("The TinyCloud host could not be reached to activate the delegation."),
|
|
6915
|
+
{ code: "ACTIVATION_TRANSPORT_FAILED", cause }
|
|
6916
|
+
);
|
|
6917
|
+
}
|
|
6439
6918
|
if (!activateResult.success) {
|
|
6440
6919
|
if (this.operationsFromDelegation(delegation).some(
|
|
6441
6920
|
(operation) => operation.resource?.startsWith("urn:cid:") === true && operation.service === "delegation" && operation.action === "tinycloud.delegation/revoke"
|
|
@@ -6723,7 +7202,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6723
7202
|
throw new Error("Public space not enabled. Set enablePublicSpace: true in config.");
|
|
6724
7203
|
}
|
|
6725
7204
|
await this.auth.hostPublicSpace(publicSpaceId);
|
|
6726
|
-
const kvActions = [
|
|
7205
|
+
const kvActions = [KV3.PUT, KV3.GET, KV3.DEL, KV3.LIST, KV3.METADATA];
|
|
6727
7206
|
const abilities = { kv: { "": kvActions } };
|
|
6728
7207
|
const now = /* @__PURE__ */ new Date();
|
|
6729
7208
|
const expiryMs = EXPIRY3.EPHEMERAL_MS;
|
|
@@ -6964,31 +7443,31 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6964
7443
|
);
|
|
6965
7444
|
return { delegation: delegation2, prompted: true };
|
|
6966
7445
|
}
|
|
6967
|
-
const
|
|
6968
|
-
|
|
6969
|
-
|
|
6970
|
-
|
|
6971
|
-
|
|
6972
|
-
|
|
6973
|
-
|
|
6974
|
-
)
|
|
6975
|
-
|
|
6976
|
-
|
|
6977
|
-
|
|
6978
|
-
|
|
6979
|
-
}
|
|
6980
|
-
const runtimeExpiration = runtimeGrant.expiresAt < effectiveExpiration ? runtimeGrant.expiresAt : effectiveExpiration;
|
|
6981
|
-
const delegation2 = await this.createDelegationViaRuntimeGrant(
|
|
6982
|
-
did,
|
|
6983
|
-
expandedEntries,
|
|
6984
|
-
runtimeExpiration,
|
|
6985
|
-
runtimeGrant,
|
|
6986
|
-
runtimeOperations,
|
|
6987
|
-
onPrepared
|
|
6988
|
-
);
|
|
6989
|
-
return { delegation: delegation2, prompted: false };
|
|
7446
|
+
const plan = this.planDerivation(
|
|
7447
|
+
expandedEntries,
|
|
7448
|
+
session,
|
|
7449
|
+
effectiveExpiration,
|
|
7450
|
+
Date.now()
|
|
7451
|
+
);
|
|
7452
|
+
if ("refused" in plan) {
|
|
7453
|
+
if (plan.refused === "SESSION_EXPIRING") {
|
|
7454
|
+
throw new SessionExpiredError(plan.expiredAt ?? sessionExpiry ?? /* @__PURE__ */ new Date(0));
|
|
7455
|
+
}
|
|
7456
|
+
if (plan.refused === "CAVEATED_AUTHORITY") {
|
|
7457
|
+
throw new CaveatedDelegationUnsupportedError(plan.caveated ?? []);
|
|
6990
7458
|
}
|
|
6991
|
-
throw new PermissionNotInManifestError(missing, granted);
|
|
7459
|
+
throw new PermissionNotInManifestError(plan.missing ?? [], plan.granted ?? []);
|
|
7460
|
+
}
|
|
7461
|
+
if (plan.path === "runtime") {
|
|
7462
|
+
const delegation2 = await this.createDelegationViaRuntimeGrant(
|
|
7463
|
+
did,
|
|
7464
|
+
expandedEntries,
|
|
7465
|
+
plan.effectiveExpiration,
|
|
7466
|
+
plan.grant,
|
|
7467
|
+
plan.operations,
|
|
7468
|
+
onPrepared
|
|
7469
|
+
);
|
|
7470
|
+
return { delegation: delegation2, prompted: false };
|
|
6992
7471
|
}
|
|
6993
7472
|
const delegation = await this.createDelegationViaWasmPath(
|
|
6994
7473
|
did,
|
|
@@ -6999,6 +7478,149 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
6999
7478
|
);
|
|
7000
7479
|
return { delegation, prompted: false };
|
|
7001
7480
|
}
|
|
7481
|
+
/**
|
|
7482
|
+
* What `delegateTo` would select for `entries`, without signing or any
|
|
7483
|
+
* network call (TC-858 plan v3 §4.3): the session path when the signed
|
|
7484
|
+
* recap covers every entry, the runtime-grant path otherwise, or a
|
|
7485
|
+
* refusal. A refusal of `CAVEATED_AUTHORITY` means coverage exists only
|
|
7486
|
+
* through caveated branches, which the action-only WASM child-delegation
|
|
7487
|
+
* boundary cannot preserve — replication never derives authority through
|
|
7488
|
+
* them. `expiredAt`/`missing`/`granted`/`caveated` carry the payload
|
|
7489
|
+
* `delegateTo` turns back into its typed errors.
|
|
7490
|
+
* @internal
|
|
7491
|
+
*/
|
|
7492
|
+
planDerivation(expandedEntries, session, effectiveExpiration, nowMs) {
|
|
7493
|
+
const caveatedRequest = expandedEntries.filter(
|
|
7494
|
+
(entry) => !recapCaveatsEqual(entry.caveats, void 0)
|
|
7495
|
+
);
|
|
7496
|
+
if (caveatedRequest.length > 0) {
|
|
7497
|
+
return { refused: "CAVEATED_AUTHORITY", caveated: caveatedRequest };
|
|
7498
|
+
}
|
|
7499
|
+
const granted = this.projectSignedRecapCapabilities(session.siwe);
|
|
7500
|
+
const { subset, missing } = this.signedCapabilitySubset(expandedEntries, granted);
|
|
7501
|
+
if (subset) {
|
|
7502
|
+
return {
|
|
7503
|
+
path: "session",
|
|
7504
|
+
parentCid: session.delegationCid,
|
|
7505
|
+
expiresAt: effectiveExpiration.getTime(),
|
|
7506
|
+
effectiveExpiration
|
|
7507
|
+
};
|
|
7508
|
+
}
|
|
7509
|
+
const runtimeOperations = this.permissionEntriesToOperations(expandedEntries, session);
|
|
7510
|
+
const runtimeGrant = this.findGrantForOperations(
|
|
7511
|
+
runtimeOperations,
|
|
7512
|
+
{ excludePrimary: true }
|
|
7513
|
+
);
|
|
7514
|
+
if (runtimeGrant) {
|
|
7515
|
+
const marginMs = _TinyCloudNode.SESSION_EXPIRY_SAFETY_MARGIN_MS;
|
|
7516
|
+
if (runtimeGrant.expiresAt.getTime() <= nowMs + marginMs) {
|
|
7517
|
+
return { refused: "SESSION_EXPIRING", expiredAt: runtimeGrant.expiresAt };
|
|
7518
|
+
}
|
|
7519
|
+
const caveated = this.caveatedRuntimeGrantEntries(
|
|
7520
|
+
expandedEntries,
|
|
7521
|
+
runtimeOperations,
|
|
7522
|
+
runtimeGrant
|
|
7523
|
+
);
|
|
7524
|
+
if (caveated.length > 0) {
|
|
7525
|
+
return { refused: "CAVEATED_AUTHORITY", caveated };
|
|
7526
|
+
}
|
|
7527
|
+
const runtimeExpiration = runtimeGrant.expiresAt < effectiveExpiration ? runtimeGrant.expiresAt : effectiveExpiration;
|
|
7528
|
+
return {
|
|
7529
|
+
path: "runtime",
|
|
7530
|
+
parentCid: runtimeGrant.session.delegationCid,
|
|
7531
|
+
expiresAt: runtimeExpiration.getTime(),
|
|
7532
|
+
effectiveExpiration: runtimeExpiration,
|
|
7533
|
+
grant: runtimeGrant,
|
|
7534
|
+
operations: runtimeOperations
|
|
7535
|
+
};
|
|
7536
|
+
}
|
|
7537
|
+
const relaxedGranted = granted.map((entry) => ({ ...entry, caveats: void 0 }));
|
|
7538
|
+
const relaxedRequested = expandedEntries.map((entry) => ({ ...entry, caveats: void 0 }));
|
|
7539
|
+
const relaxed = this.signedCapabilitySubset(relaxedRequested, relaxedGranted);
|
|
7540
|
+
if (relaxed.subset) {
|
|
7541
|
+
return {
|
|
7542
|
+
refused: "CAVEATED_AUTHORITY",
|
|
7543
|
+
caveated: missing.map((entry) => ({ ...entry, caveats: [{ via: "session-recap" }] }))
|
|
7544
|
+
};
|
|
7545
|
+
}
|
|
7546
|
+
return { refused: "NOT_COVERED", missing, granted };
|
|
7547
|
+
}
|
|
7548
|
+
/**
|
|
7549
|
+
* The plan `delegateTo(device, entries)` would follow, as a pure lookup:
|
|
7550
|
+
* no WASM sign and no `POST /delegate`. Backs
|
|
7551
|
+
* `ReplicationAuthority.plan` (and `mint` inside it) so a predicted
|
|
7552
|
+
* parent and expiry can never disagree with what a mint would use (§4.3,
|
|
7553
|
+
* §4.4). Session expiry inside the 60 s margin refuses with
|
|
7554
|
+
* `SESSION_EXPIRING`; the same check delegateTo performs.
|
|
7555
|
+
* @internal
|
|
7556
|
+
*/
|
|
7557
|
+
planDelegation(entries, options) {
|
|
7558
|
+
if (!Array.isArray(entries) || entries.length === 0) {
|
|
7559
|
+
throw new Error("planDelegation requires a non-empty permissions array");
|
|
7560
|
+
}
|
|
7561
|
+
const session = this.currentTinyCloudSession();
|
|
7562
|
+
if (!session) {
|
|
7563
|
+
return { refused: "SESSION_EXPIRING", expiredAt: /* @__PURE__ */ new Date(0) };
|
|
7564
|
+
}
|
|
7565
|
+
const nowMs = Date.now();
|
|
7566
|
+
const sessionExpiry = extractSiweExpiration(session.siwe);
|
|
7567
|
+
if (sessionExpiry !== void 0 && sessionExpiry.getTime() <= nowMs + _TinyCloudNode.SESSION_EXPIRY_SAFETY_MARGIN_MS) {
|
|
7568
|
+
return { refused: "SESSION_EXPIRING", expiredAt: sessionExpiry };
|
|
7569
|
+
}
|
|
7570
|
+
const expandedEntries = this.expandPermissionEntries(entries);
|
|
7571
|
+
const expiryMs = resolveExpiryMs(options?.expiry);
|
|
7572
|
+
const expirationTime = new Date(nowMs + expiryMs);
|
|
7573
|
+
const effectiveExpiration = sessionExpiry !== void 0 && sessionExpiry < expirationTime ? sessionExpiry : expirationTime;
|
|
7574
|
+
return this.planDerivation(expandedEntries, session, effectiveExpiration, nowMs);
|
|
7575
|
+
}
|
|
7576
|
+
/**
|
|
7577
|
+
* The replication entries this node's sign-in request carries for the
|
|
7578
|
+
* current replication config and the signed-in or restored address: the
|
|
7579
|
+
* output of the same augmentation step `resolveSignInCapabilities` runs,
|
|
7580
|
+
* with the unrestricted-get and secrets gates applied (§4.1, §4.6).
|
|
7581
|
+
* Empty when replication is off or no address is known. Pure.
|
|
7582
|
+
*/
|
|
7583
|
+
replicationSignInEntries() {
|
|
7584
|
+
if (this.config.replication?.enabled !== true) return [];
|
|
7585
|
+
const auth = this.auth;
|
|
7586
|
+
if (auth === void 0 || typeof auth.replicationSignInEntries !== "function") {
|
|
7587
|
+
return [];
|
|
7588
|
+
}
|
|
7589
|
+
return auth.replicationSignInEntries();
|
|
7590
|
+
}
|
|
7591
|
+
async replicationAuthority() {
|
|
7592
|
+
const { createReplicationAuthority } = await getNodeReplicationLoaders().authority();
|
|
7593
|
+
return createReplicationAuthority({
|
|
7594
|
+
replicationSession: () => this.currentTinyCloudSession() ?? this.replicationDelegateSession,
|
|
7595
|
+
siweExpiration: (siwe) => extractSiweExpiration(siwe),
|
|
7596
|
+
planDelegation: (entries, options) => this.planDelegation(entries, options),
|
|
7597
|
+
mintDelegation: async (deviceDid, entries) => {
|
|
7598
|
+
const result = await this.delegateTo(principalDid(deviceDid), entries);
|
|
7599
|
+
return {
|
|
7600
|
+
ucan: result.delegation.delegationHeader.Authorization,
|
|
7601
|
+
expiresAt: result.delegation.expiry.getTime()
|
|
7602
|
+
};
|
|
7603
|
+
}
|
|
7604
|
+
});
|
|
7605
|
+
}
|
|
7606
|
+
async bindReplicationRuntime(context, session, kv, principal) {
|
|
7607
|
+
if (!this.replicationRuntime) return;
|
|
7608
|
+
const identityPrincipal = principal ?? (this._address === void 0 ? void 0 : pkhDid2(this._address, this._chainId));
|
|
7609
|
+
if (identityPrincipal === void 0) return;
|
|
7610
|
+
const runtime = await this.replicationRuntime;
|
|
7611
|
+
const authority = await this.replicationAuthority();
|
|
7612
|
+
await runtime.bind({
|
|
7613
|
+
context,
|
|
7614
|
+
session,
|
|
7615
|
+
principal: identityPrincipal,
|
|
7616
|
+
authority,
|
|
7617
|
+
primaryKV: [{ space: session.spaceId, kv }]
|
|
7618
|
+
});
|
|
7619
|
+
}
|
|
7620
|
+
/** Stable control facade for this node's optional replication runtime. */
|
|
7621
|
+
get replication() {
|
|
7622
|
+
return this.replicationControl;
|
|
7623
|
+
}
|
|
7002
7624
|
/**
|
|
7003
7625
|
* Materialize one manifest-declared delegation using the current session key.
|
|
7004
7626
|
* Delivery is intentionally out of band; callers decide how to transmit the
|
|
@@ -7221,6 +7843,18 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7221
7843
|
* it only accepts action maps, so reject the selected constrained branch.
|
|
7222
7844
|
*/
|
|
7223
7845
|
assertRuntimeGrantCaveatsPreservable(entries, requestedOperations, grant) {
|
|
7846
|
+
const caveated = this.caveatedRuntimeGrantEntries(entries, requestedOperations, grant);
|
|
7847
|
+
if (caveated.length > 0) {
|
|
7848
|
+
throw new CaveatedDelegationUnsupportedError(caveated);
|
|
7849
|
+
}
|
|
7850
|
+
}
|
|
7851
|
+
/**
|
|
7852
|
+
* The entries of `grant` that cover a requested operation while carrying
|
|
7853
|
+
* caveats — the branches {@link assertRuntimeGrantCaveatsPreservable}
|
|
7854
|
+
* rejects and {@link planDerivation} classifies as `CAVEATED_AUTHORITY`.
|
|
7855
|
+
* Shared so the pure plan and the mint path can't disagree.
|
|
7856
|
+
*/
|
|
7857
|
+
caveatedRuntimeGrantEntries(entries, requestedOperations, grant) {
|
|
7224
7858
|
let operationIndex = 0;
|
|
7225
7859
|
const caveated = [];
|
|
7226
7860
|
for (const entry of entries) {
|
|
@@ -7239,9 +7873,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7239
7873
|
}
|
|
7240
7874
|
}
|
|
7241
7875
|
}
|
|
7242
|
-
|
|
7243
|
-
throw new CaveatedDelegationUnsupportedError(caveated);
|
|
7244
|
-
}
|
|
7876
|
+
return caveated;
|
|
7245
7877
|
}
|
|
7246
7878
|
/** Reject caveated parent branches before action-only child signing. */
|
|
7247
7879
|
assertPortableDelegationCaveatsPreservable(delegation) {
|
|
@@ -7346,7 +7978,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7346
7978
|
const missing = requested.filter((entry) => {
|
|
7347
7979
|
const raw = this.isEncryptionPermissionEntry(entry) || this.isRawRevocationPermissionEntry(entry);
|
|
7348
7980
|
const comparable = raw && this.isEncryptionPermissionEntry(entry) ? { ...entry, space: "encryption" } : entry;
|
|
7349
|
-
return !
|
|
7981
|
+
return !isCapabilitySubset2([comparable], raw ? rawGranted : spaceGranted).subset;
|
|
7350
7982
|
});
|
|
7351
7983
|
return { subset: missing.length === 0, missing };
|
|
7352
7984
|
}
|
|
@@ -7818,7 +8450,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7818
8450
|
session.chainId
|
|
7819
8451
|
);
|
|
7820
8452
|
const publicAbilities = {
|
|
7821
|
-
kv: { "": [
|
|
8453
|
+
kv: { "": [KV3.GET, KV3.PUT, KV3.METADATA] }
|
|
7822
8454
|
};
|
|
7823
8455
|
const publicPrepared = this.wasmBindings.prepareSession({
|
|
7824
8456
|
abilities: publicAbilities,
|
|
@@ -7846,7 +8478,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
7846
8478
|
delegationHeader: publicSession.delegationHeader,
|
|
7847
8479
|
spaceId: publicSpaceId,
|
|
7848
8480
|
path: "",
|
|
7849
|
-
actions: [
|
|
8481
|
+
actions: [KV3.GET, KV3.PUT, KV3.METADATA],
|
|
7850
8482
|
disableSubDelegation: params.disableSubDelegation ?? false,
|
|
7851
8483
|
expiry: expirationTime,
|
|
7852
8484
|
delegateDID: params.delegateDID,
|
|
@@ -8120,7 +8752,7 @@ import {
|
|
|
8120
8752
|
resolveManifest as resolveManifest2,
|
|
8121
8753
|
validateManifest,
|
|
8122
8754
|
loadManifest,
|
|
8123
|
-
isCapabilitySubset as
|
|
8755
|
+
isCapabilitySubset as isCapabilitySubset3,
|
|
8124
8756
|
expandActionShortNames,
|
|
8125
8757
|
expandPermissionEntries as expandPermissionEntries2,
|
|
8126
8758
|
expandPermissionEntry,
|
|
@@ -8165,7 +8797,7 @@ import {
|
|
|
8165
8797
|
SpaceService as SpaceService2,
|
|
8166
8798
|
SpaceErrorCodes,
|
|
8167
8799
|
createSpaceService,
|
|
8168
|
-
parseSpaceUri,
|
|
8800
|
+
parseSpaceUri as parseSpaceUri2,
|
|
8169
8801
|
buildSpaceUri,
|
|
8170
8802
|
makePublicSpaceId as makePublicSpaceId2,
|
|
8171
8803
|
Space
|
|
@@ -8256,7 +8888,7 @@ export {
|
|
|
8256
8888
|
expandPermissionEntry,
|
|
8257
8889
|
getTinyCloudDebugLogs,
|
|
8258
8890
|
installTinyCloudDebugGlobals,
|
|
8259
|
-
|
|
8891
|
+
isCapabilitySubset3 as isCapabilitySubset,
|
|
8260
8892
|
isEvmAddress,
|
|
8261
8893
|
loadManifest,
|
|
8262
8894
|
makePkhSpaceId2 as makePkhSpaceId,
|
|
@@ -8265,10 +8897,11 @@ export {
|
|
|
8265
8897
|
parseExpiry2 as parseExpiry,
|
|
8266
8898
|
parsePkhDid,
|
|
8267
8899
|
parseSecretCatalogKey,
|
|
8268
|
-
parseSpaceUri,
|
|
8900
|
+
parseSpaceUri2 as parseSpaceUri,
|
|
8269
8901
|
pkhDid3 as pkhDid,
|
|
8270
|
-
principalDid,
|
|
8902
|
+
principalDid2 as principalDid,
|
|
8271
8903
|
principalDidEquals4 as principalDidEquals,
|
|
8904
|
+
registerReplicationLoaders,
|
|
8272
8905
|
resolveManifest2 as resolveManifest,
|
|
8273
8906
|
resolveSecretListPrefix2 as resolveSecretListPrefix,
|
|
8274
8907
|
resolveSecretPath3 as resolveSecretPath,
|