@tinycloud/node-sdk 3.2.6-beta.5 → 3.3.0-beta.7
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/{WasmKeyProvider-DEdSyQ0I.d.cts → TinyCloudNode-BchWB_zv.d.cts} +162 -283
- package/dist/{WasmKeyProvider-DEdSyQ0I.d.ts → TinyCloudNode-BchWB_zv.d.ts} +162 -283
- package/dist/WasmKeyProvider-5Ee7fcAw.d.cts +217 -0
- package/dist/WasmKeyProvider-5Ee7fcAw.d.ts +217 -0
- package/dist/authority-4HXYteKF.d.ts +118 -0
- package/dist/authority-lrA-698X.d.cts +118 -0
- package/dist/core.cjs +368 -147
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +37 -4
- package/dist/core.d.ts +37 -4
- package/dist/core.js +374 -155
- package/dist/core.js.map +1 -1
- package/dist/gated-network-BEF2XSTC.d.cts +16 -0
- package/dist/gated-network-BEF2XSTC.d.ts +16 -0
- package/dist/index.cjs +2702 -524
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -89
- package/dist/index.d.ts +35 -89
- package/dist/index.js +2702 -530
- package/dist/index.js.map +1 -1
- package/dist/replication/authority.cjs +153 -15
- package/dist/replication/authority.cjs.map +1 -1
- package/dist/replication/authority.d.cts +4 -1
- package/dist/replication/authority.d.ts +4 -1
- package/dist/replication/authority.js +147 -14
- package/dist/replication/authority.js.map +1 -1
- package/dist/replication/gated-network.cjs +90 -0
- package/dist/replication/gated-network.cjs.map +1 -0
- package/dist/replication/gated-network.d.cts +2 -0
- package/dist/replication/gated-network.d.ts +2 -0
- package/dist/replication/gated-network.js +65 -0
- package/dist/replication/gated-network.js.map +1 -0
- package/dist/replication/runtime.cjs +84 -6
- package/dist/replication/runtime.cjs.map +1 -1
- package/dist/replication/runtime.d.cts +2 -1
- package/dist/replication/runtime.d.ts +2 -1
- package/dist/replication/runtime.js +84 -6
- package/dist/replication/runtime.js.map +1 -1
- package/dist/{runtime-B5P5SRpC.d.ts → runtime-D1o9Eybt.d.cts} +14 -3
- package/dist/{runtime-B5P5SRpC.d.cts → runtime-v62FzQHz.d.ts} +14 -3
- package/package.json +14 -4
- package/dist/authority-DDGtccBK.d.cts +0 -96
- package/dist/authority-DDGtccBK.d.ts +0 -96
|
@@ -1,23 +1,30 @@
|
|
|
1
1
|
// src/replication/authority.ts
|
|
2
|
+
import { SiweMessage } from "siwe";
|
|
2
3
|
import {
|
|
3
4
|
actionContains,
|
|
4
5
|
KV as KV2,
|
|
5
6
|
canonicalizeRecapCaveats,
|
|
6
7
|
isCapabilitySubset as isCapabilitySubset2,
|
|
8
|
+
principalDid,
|
|
7
9
|
parseSpaceUri
|
|
8
10
|
} from "@tinycloud/sdk-core";
|
|
9
11
|
import { parseUcanGrant as replicaParseUcanGrant } from "@tinycloud/replica";
|
|
10
12
|
|
|
11
13
|
// src/replication/config.ts
|
|
14
|
+
import { MAX_GRANT_TTL_MS } from "@tinycloud/sdk-services/kv/replication/grantPolicyConstants";
|
|
12
15
|
import { assertFolderPrefix as assertPureFolderPrefix, kvPrefixCovers } from "@tinycloud/sdk-services/kv/replication/prefix";
|
|
13
16
|
function assertValidReplicationConfig(replication) {
|
|
14
|
-
if (replication === void 0 || replication.enabled
|
|
17
|
+
if (replication === void 0 || !replication.enabled) return;
|
|
15
18
|
if (replication.storage === void 0 || replication.storage === null) {
|
|
16
19
|
throw new TypeError("replication.enabled requires replication.storage");
|
|
17
20
|
}
|
|
18
21
|
if (!Array.isArray(replication.prefixes) || replication.prefixes.length === 0) {
|
|
19
22
|
throw new TypeError("replication.prefixes must be a non-empty array");
|
|
20
23
|
}
|
|
24
|
+
const grantTtlMs = replication.grantTtlMs;
|
|
25
|
+
if (grantTtlMs !== void 0 && (!Number.isFinite(grantTtlMs) || grantTtlMs <= 0 || grantTtlMs > MAX_GRANT_TTL_MS)) {
|
|
26
|
+
throw new TypeError(`replication.grantTtlMs must be a positive number of milliseconds no greater than ${MAX_GRANT_TTL_MS}`);
|
|
27
|
+
}
|
|
21
28
|
for (const prefix of replication.prefixes) {
|
|
22
29
|
try {
|
|
23
30
|
assertPureFolderPrefix(prefix);
|
|
@@ -94,9 +101,83 @@ function augmentSignInEntriesWithReplication(input) {
|
|
|
94
101
|
|
|
95
102
|
// src/replication/authority.ts
|
|
96
103
|
var parseUcanGrant = replicaParseUcanGrant;
|
|
104
|
+
function reportReplicaDenial(result, reason, onEvent, context) {
|
|
105
|
+
if (result.status !== "denied" && result.status !== "deleted" && result.status !== "marker") return;
|
|
106
|
+
const event = {
|
|
107
|
+
type: "replication.lifecycle",
|
|
108
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
109
|
+
reason,
|
|
110
|
+
action: result.status === "denied" ? "denied" : result.status === "deleted" ? "database_deleted" : "marker_set",
|
|
111
|
+
...result.status === "denied" ? { partitions: result.intents } : {},
|
|
112
|
+
...result.status === "marker" ? { code: result.code } : {}
|
|
113
|
+
};
|
|
114
|
+
try {
|
|
115
|
+
onEvent?.(event);
|
|
116
|
+
} catch {
|
|
117
|
+
}
|
|
118
|
+
const { type, ...data } = event;
|
|
119
|
+
try {
|
|
120
|
+
context?.emit(type, data);
|
|
121
|
+
} catch {
|
|
122
|
+
}
|
|
123
|
+
}
|
|
97
124
|
var KV_GET2 = KV2.GET;
|
|
98
125
|
var KV_SYNC2 = KV2.SYNC;
|
|
99
126
|
var KV_SERVICE2 = "tinycloud.kv";
|
|
127
|
+
function verifiedOwnerConsentAnchor(session, wasmBindings, exactVerifiedRecap) {
|
|
128
|
+
if (typeof session.siwe !== "string" || typeof session.signature !== "string" || typeof session.address !== "string" || typeof session.chainId !== "number") return null;
|
|
129
|
+
try {
|
|
130
|
+
const verified = wasmBindings.validatePersistedSession?.({
|
|
131
|
+
delegationHeader: session.delegationHeader,
|
|
132
|
+
delegationCid: session.delegationCid,
|
|
133
|
+
spaceId: session.spaceId,
|
|
134
|
+
jwk: session.jwk,
|
|
135
|
+
address: session.address,
|
|
136
|
+
chainId: session.chainId,
|
|
137
|
+
siwe: session.siwe,
|
|
138
|
+
signature: session.signature
|
|
139
|
+
});
|
|
140
|
+
if (!verified || !Number.isSafeInteger(Date.parse(verified.expiresAt ?? ""))) return null;
|
|
141
|
+
const signedIssuedAt = new SiweMessage(session.siwe).issuedAt;
|
|
142
|
+
if (typeof signedIssuedAt !== "string") return null;
|
|
143
|
+
const issuedAt = Date.parse(signedIssuedAt);
|
|
144
|
+
if (!Number.isSafeInteger(issuedAt) || issuedAt < 0) return null;
|
|
145
|
+
const granted = exactVerifiedRecap(verified).filter((entry) => entry.space === session.spaceId && entry.service === "kv").map((entry) => ({ ...entry, service: "tinycloud.kv" }));
|
|
146
|
+
const coversGetAndSync = granted.some((entry) => isCapabilitySubset2([{
|
|
147
|
+
service: "tinycloud.kv",
|
|
148
|
+
space: session.spaceId,
|
|
149
|
+
path: entry.path,
|
|
150
|
+
actions: [KV2.GET, KV2.SYNC]
|
|
151
|
+
}], granted).subset);
|
|
152
|
+
return coversGetAndSync ? { at: issuedAt, source: "siwe" } : null;
|
|
153
|
+
} catch {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function establishReplicaSessionStamp(storage, principal, anchor) {
|
|
158
|
+
if (storage.kind === "indexeddb" && storage.lifecycle?.kind === "indexeddb") {
|
|
159
|
+
return storage.lifecycle.establishSessionStamp(principal, { consentAnchor: anchor });
|
|
160
|
+
}
|
|
161
|
+
if (storage.kind === "sqlite" && storage.lifecycle?.kind === "file") {
|
|
162
|
+
return storage.lifecycle.establishSessionStamp(principal, { consentAnchor: anchor });
|
|
163
|
+
}
|
|
164
|
+
throw Object.assign(new Error("Replica sign-in requires its platform lifecycle gate."), {
|
|
165
|
+
code: "REPLICA_GATED",
|
|
166
|
+
gate: "gate_unavailable"
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
function restoreReplicaSessionStamp(storage, principal, persisted) {
|
|
170
|
+
if (storage.kind === "indexeddb" && storage.lifecycle?.kind === "indexeddb") {
|
|
171
|
+
return storage.lifecycle.restoreSessionStamp(principal, persisted);
|
|
172
|
+
}
|
|
173
|
+
if (storage.kind === "sqlite" && storage.lifecycle?.kind === "file") {
|
|
174
|
+
return storage.lifecycle.restoreSessionStamp(principal, persisted);
|
|
175
|
+
}
|
|
176
|
+
throw Object.assign(new Error("Replica restore requires its platform lifecycle gate."), {
|
|
177
|
+
code: "REPLICA_GATED",
|
|
178
|
+
gate: "gate_unavailable"
|
|
179
|
+
});
|
|
180
|
+
}
|
|
100
181
|
function replicationEntries(spaceId, prefix) {
|
|
101
182
|
return [{
|
|
102
183
|
service: KV_SERVICE2,
|
|
@@ -144,6 +225,20 @@ function ucanAttCovers(att, space, prefix, ability, options) {
|
|
|
144
225
|
function ucanAttUnconstrainedFor(att, space, prefix) {
|
|
145
226
|
return ucanAttCovers(att, space, prefix, KV_GET2) && ucanAttCovers(att, space, prefix, KV_SYNC2);
|
|
146
227
|
}
|
|
228
|
+
async function mintNodeReplicationGrant(deviceDid, entries, options, renewalSession, node) {
|
|
229
|
+
if (options?.renewalOnly && !renewalSession) throw new Error("replication renewal lost its owner session");
|
|
230
|
+
const delegateOptions = options?.renewalOnly ? {
|
|
231
|
+
expiry: options.expiryMs,
|
|
232
|
+
onPrepared: async (prepared) => {
|
|
233
|
+
assertRenewalParent(prepared.delegationHeader.Authorization, renewalSession);
|
|
234
|
+
}
|
|
235
|
+
} : options?.expiryMs === void 0 ? void 0 : { expiry: options.expiryMs };
|
|
236
|
+
const result = await node.delegateTo(principalDid(deviceDid), entries, delegateOptions);
|
|
237
|
+
return {
|
|
238
|
+
ucan: result.delegation.delegationHeader.Authorization,
|
|
239
|
+
expiresAt: result.delegation.expiry.getTime()
|
|
240
|
+
};
|
|
241
|
+
}
|
|
147
242
|
var AUTHORITY_EXPIRY_MARGIN_MS = 6e4;
|
|
148
243
|
function replicationScopeFromSignedSession(input) {
|
|
149
244
|
let grant;
|
|
@@ -162,9 +257,28 @@ function replicationScopeFromSignedSession(input) {
|
|
|
162
257
|
}
|
|
163
258
|
return void 0;
|
|
164
259
|
}
|
|
260
|
+
function assertRenewalParent(authorization, session) {
|
|
261
|
+
const grant = parseUcanGrant(authorization);
|
|
262
|
+
if (grant.issuer !== session.verificationMethod.split("#", 1)[0] || grant.prf.length !== 1 || grant.prf[0] !== session.delegationCid) {
|
|
263
|
+
throw Object.assign(new Error("replication renewal requires the bound owner session parent"), { refused: "NOT_COVERED" });
|
|
264
|
+
}
|
|
265
|
+
}
|
|
165
266
|
function createReplicationAuthority(host) {
|
|
166
|
-
const
|
|
167
|
-
|
|
267
|
+
const boundSession = host.replicationSession();
|
|
268
|
+
let verifiedAnchor = null;
|
|
269
|
+
try {
|
|
270
|
+
const candidate = boundSession && host.verifiedConsentAnchor?.(boundSession);
|
|
271
|
+
if (candidate && Number.isFinite(candidate.at) && candidate.source !== "first_install") {
|
|
272
|
+
verifiedAnchor = { at: candidate.at, source: candidate.source };
|
|
273
|
+
}
|
|
274
|
+
} catch {
|
|
275
|
+
}
|
|
276
|
+
const activeSession = () => {
|
|
277
|
+
const session = host.replicationSession();
|
|
278
|
+
return session !== void 0 && session === boundSession ? session : void 0;
|
|
279
|
+
};
|
|
280
|
+
const planOrRefusal = (entries, expiryMs) => {
|
|
281
|
+
const plan = host.planDelegation(entries, expiryMs === void 0 ? void 0 : { expiry: expiryMs });
|
|
168
282
|
if ("refused" in plan) return plan;
|
|
169
283
|
return { path: plan.path, parentCid: plan.parentCid, expiresAt: plan.expiresAt };
|
|
170
284
|
};
|
|
@@ -176,12 +290,20 @@ function createReplicationAuthority(host) {
|
|
|
176
290
|
};
|
|
177
291
|
const authority = {
|
|
178
292
|
get sessionOnly() {
|
|
179
|
-
const session =
|
|
180
|
-
return session
|
|
293
|
+
const session = activeSession();
|
|
294
|
+
return session === void 0 || session.siwe === void 0;
|
|
295
|
+
},
|
|
296
|
+
consentAnchor() {
|
|
297
|
+
return activeSession() ? verifiedAnchor : null;
|
|
298
|
+
},
|
|
299
|
+
get canRenew() {
|
|
300
|
+
const session = activeSession();
|
|
301
|
+
return verifiedAnchor?.source === "siwe" && session !== void 0 && session.siwe !== void 0;
|
|
181
302
|
},
|
|
182
303
|
sessionGrant(prefix) {
|
|
183
|
-
const session =
|
|
304
|
+
const session = activeSession();
|
|
184
305
|
if (session === void 0) return { refused: "SESSION_EXPIRING" };
|
|
306
|
+
if (verifiedAnchor === null && session.siwe !== void 0) return { refused: "NOT_COVERED" };
|
|
185
307
|
const siweExpiry = session.siwe ? host.siweExpiration(session.siwe)?.getTime() : void 0;
|
|
186
308
|
if (siweExpiry !== void 0 && siweExpiry <= Date.now() + AUTHORITY_EXPIRY_MARGIN_MS) {
|
|
187
309
|
return { refused: "SESSION_EXPIRING" };
|
|
@@ -209,8 +331,9 @@ function createReplicationAuthority(host) {
|
|
|
209
331
|
};
|
|
210
332
|
},
|
|
211
333
|
plan(prefix) {
|
|
212
|
-
const session =
|
|
334
|
+
const session = activeSession();
|
|
213
335
|
if (session === void 0) return { refused: "SESSION_EXPIRING" };
|
|
336
|
+
if (verifiedAnchor === null) return { refused: "NOT_COVERED" };
|
|
214
337
|
const sessionGrant = this.sessionGrant(prefix);
|
|
215
338
|
if (!("refused" in sessionGrant)) {
|
|
216
339
|
const signed = signedSessionGrant(session);
|
|
@@ -224,23 +347,27 @@ function createReplicationAuthority(host) {
|
|
|
224
347
|
if (authority.sessionOnly) return sessionGrant;
|
|
225
348
|
return planOrRefusal(replicationEntries(session.spaceId, prefix));
|
|
226
349
|
},
|
|
227
|
-
async mint(deviceDid, prefix, signal) {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
if (session === void 0) throw new Error("
|
|
350
|
+
async mint(deviceDid, prefix, signal, options) {
|
|
351
|
+
const session = activeSession();
|
|
352
|
+
if (session === void 0) throw new Error("replication mint without the bound session");
|
|
353
|
+
if (session.siwe === void 0) throw new Error("compact delegate sessions cannot mint device grants");
|
|
354
|
+
if (verifiedAnchor?.source !== "siwe") throw new Error("replication mint requires verified owner consent");
|
|
231
355
|
const entries = replicationEntries(session.spaceId, prefix);
|
|
232
|
-
const plan = planOrRefusal(entries);
|
|
356
|
+
const plan = planOrRefusal(entries, options?.expiryMs);
|
|
233
357
|
if ("refused" in plan) {
|
|
234
358
|
throw Object.assign(
|
|
235
359
|
new Error(`replication authority refused the mint: ${plan.refused}`),
|
|
236
360
|
{ refused: plan.refused }
|
|
237
361
|
);
|
|
238
362
|
}
|
|
363
|
+
if (options?.renewalOnly && plan.path !== "session") {
|
|
364
|
+
throw Object.assign(new Error("replication renewal requires a session parent"), { refused: "NOT_COVERED" });
|
|
365
|
+
}
|
|
239
366
|
signal.throwIfAborted();
|
|
240
367
|
const minted = await new Promise((resolve, reject) => {
|
|
241
368
|
const onAbort = () => reject(signal.reason ?? new Error("replication mint aborted"));
|
|
242
369
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
243
|
-
host.mintDelegation(deviceDid, entries).then(
|
|
370
|
+
host.mintDelegation(deviceDid, entries, options).then(
|
|
244
371
|
(result) => {
|
|
245
372
|
signal.removeEventListener("abort", onAbort);
|
|
246
373
|
resolve(result);
|
|
@@ -258,12 +385,18 @@ function createReplicationAuthority(host) {
|
|
|
258
385
|
}
|
|
259
386
|
export {
|
|
260
387
|
AUTHORITY_EXPIRY_MARGIN_MS,
|
|
388
|
+
assertRenewalParent,
|
|
261
389
|
assertValidReplicationConfig,
|
|
262
390
|
augmentSignInEntriesWithReplication,
|
|
263
391
|
createReplicationAuthority,
|
|
392
|
+
establishReplicaSessionStamp,
|
|
264
393
|
hasUnrestrictedGetCoverage,
|
|
394
|
+
mintNodeReplicationGrant,
|
|
265
395
|
replicationScopeFromSignedSession,
|
|
396
|
+
reportReplicaDenial,
|
|
397
|
+
restoreReplicaSessionStamp,
|
|
266
398
|
ucanAttCovers,
|
|
267
|
-
ucanAttUnconstrainedFor
|
|
399
|
+
ucanAttUnconstrainedFor,
|
|
400
|
+
verifiedOwnerConsentAnchor
|
|
268
401
|
};
|
|
269
402
|
//# sourceMappingURL=authority.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/replication/authority.ts","../../src/replication/config.ts","../../src/replication/authority-sign-in.ts"],"sourcesContent":["/**\n * Replication authority (TC-858, plan v3 §4).\n *\n * `ReplicationAuthority` answers, per configured prefix, whether this node's\n * session can back a local replica: the session UCAN itself (delegate\n * posture), a child UCAN minted through `delegateTo`, or nothing (the\n * controller then reports `grant_missing` and reads from the network).\n *\n * The unrestricted-authority rule is enforced here, before any network work:\n * a prefix covered only by a caveated `get`/`sync` refuses with\n * `CAVEATED_AUTHORITY` — on the session path, the runtime-grant path, and on\n * any reuse of an installed grant (the adapter marks every grant's\n * `unconstrained` flag; the controller enforces it on reuse).\n */\n\nimport {\n actionContains,\n KV,\n canonicalizeRecapCaveats,\n isCapabilitySubset,\n parseSpaceUri,\n type PermissionEntry,\n type TinyCloudSession,\n} from \"@tinycloud/sdk-core\";\nimport {\n type AuthorityRefusal,\n type ReplicaDevice,\n type ReplicationAuthority,\n} from \"@tinycloud/sdk-services\";\nimport type { ReplicaRuntime } from \"./replica-contract\";\n// @ts-ignore -- node-sdk builds before replica declarations; keep the literal import for bundlers.\nimport { parseUcanGrant as replicaParseUcanGrant } from \"@tinycloud/replica\";\nconst parseUcanGrant = replicaParseUcanGrant as ReplicaRuntime[\"parseUcanGrant\"];\nexport { assertValidReplicationConfig } from \"./config\";\nexport { augmentSignInEntriesWithReplication, hasUnrestrictedGetCoverage } from \"./authority-sign-in\";\n\nconst KV_GET = KV.GET;\nconst KV_SYNC = KV.SYNC;\nconst KV_SERVICE = \"tinycloud.kv\";\n\n/** The delegation the replication grant delegates: get + sync on one folder prefix. */\nfunction replicationEntries(spaceId: string, prefix: string): PermissionEntry[] {\n return [{\n service: KV_SERVICE,\n space: spaceId,\n path: prefix === \"/\" ? \"\" : prefix,\n actions: [KV_GET, KV_SYNC],\n }];\n}\n\n\n\n\n/**\n * The signed UCAN attenuation (`att`: resource → ability → caveat branches).\n * Only array branch sets are known; anything else is treated as constrained —\n * fail closed, never assume unconstrained.\n */\nfunction caveatBranchesUnconstrained(value: unknown): boolean {\n if (value === undefined || value === null) return true;\n if (typeof value === \"object\" && !Array.isArray(value) && Object.keys(value).length === 0) {\n return true;\n }\n if (!Array.isArray(value)) return false;\n try {\n return (\n canonicalizeRecapCaveats(value as Record<string, unknown>[]) ===\n canonicalizeRecapCaveats(undefined)\n );\n } catch {\n return false;\n }\n}\n\n/** The KV path a signed UCAN resource names in `space`, like replica's `kvPathIn`. */\nfunction kvPathInResource(resource: string, space: string): string | undefined {\n for (const root of [`${space}/kv`, `tinycloud://${space}/kv`]) {\n if (resource === root || resource === `${root}/`) return \"/\";\n }\n for (const base of [`${space}/kv/`, `tinycloud://${space}/kv/`]) {\n if (resource.startsWith(base)) return resource.slice(base.length);\n }\n return undefined;\n\n}\n/**\n * Whether the signed `att` grants `ability` over the folder prefix, with\n * unrestricted authority unless explicitly asked to ignore caveats.\n */\nexport function ucanAttCovers(\n att: Record<string, Record<string, unknown>>,\n space: string,\n prefix: string,\n ability: string,\n options?: { ignoreCaveats?: boolean },\n): boolean {\n for (const [resource, abilities] of Object.entries(att)) {\n const path = kvPathInResource(resource, space);\n if (path === undefined) continue;\n for (const [granted, caveats] of Object.entries(abilities)) {\n if (!actionContains(granted, ability)) continue;\n if (!isCapabilitySubset(\n [{ service: KV_SERVICE, space, path: prefix, actions: [ability] }],\n [{ service: KV_SERVICE, space, path, actions: [granted] }],\n ).subset) continue;\n if (options?.ignoreCaveats === true || caveatBranchesUnconstrained(caveats)) return true;\n }\n }\n return false;\n}\n\n/** `unconstrained` for `ReplicaGrantInfo` (§2.1): get AND sync on the prefix carry no caveats. */\nexport function ucanAttUnconstrainedFor(\n att: Record<string, Record<string, unknown>>,\n space: string,\n prefix: string,\n): boolean {\n return (\n ucanAttCovers(att, space, prefix, KV_GET) &&\n ucanAttCovers(att, space, prefix, KV_SYNC)\n );\n}\n\nexport interface DelegationPlanResult {\n path: \"session\" | \"runtime\";\n parentCid: string;\n expiresAt: number;\n}\n\n/**\n * The `TinyCloudNode` internals the authority needs. `planDelegation` is the\n * pure extraction of `delegateTo`'s parent selection (§4.3), so `plan` and\n * `mint` can never disagree about which parent or expiry a delegation gets.\n */\nexport type ReplicationAuthoritySession = Pick<\n TinyCloudSession,\n \"delegationHeader\" | \"delegationCid\" | \"spaceId\" | \"verificationMethod\" | \"jwk\"\n> & Partial<Pick<TinyCloudSession, \"siwe\">>;\n\nexport interface ReplicationAuthorityHost {\n replicationSession(): ReplicationAuthoritySession | undefined;\n /** The SIWE \"Expiration Time\" of the session — the delegateTo delegation cap. */\n siweExpiration(siwe: string): Date | undefined;\n planDelegation(\n entries: PermissionEntry[],\n options?: { expiry?: string | number },\n ): ({ grant?: unknown } & DelegationPlanResult) | AuthorityRefusal;\n mintDelegation(\n deviceDid: string,\n entries: PermissionEntry[],\n ): Promise<{ ucan: string; expiresAt: number }>;\n}\n\n/** Milliseconds of validity a session or parent must have left for authority reuse (delegateTo's margin). */\nexport const AUTHORITY_EXPIRY_MARGIN_MS = 60_000;\n\n\n/** Verify a compact session delegation before using any persisted space metadata. */\nexport function replicationScopeFromSignedSession(input: {\n delegationHeader: { Authorization: string };\n delegationCid: string;\n verificationMethod: string;\n persistedSpace: string;\n}): { space: string; principal: string } | undefined {\n let grant: ReturnType<typeof parseUcanGrant>;\n try {\n grant = parseUcanGrant(input.delegationHeader.Authorization);\n } catch {\n return undefined;\n }\n const bareDid = (value: string): string => value.split(\"#\", 1)[0]!;\n if (\n grant.cid !== input.delegationCid ||\n bareDid(grant.audience) !== bareDid(input.verificationMethod)\n ) return undefined;\n for (const resource of Object.keys(grant.att)) {\n const space = resource.match(/^(?:tinycloud:\\/\\/)?(.+?)\\/kv(?:\\/|$)/)?.[1];\n if (space !== input.persistedSpace) continue;\n const owner = parseSpaceUri(space);\n if (owner?.owner && owner.address && owner.chainId) return { space, principal: owner.owner };\n }\n return undefined;\n}\nexport function createReplicationAuthority(host: ReplicationAuthorityHost): ReplicationAuthority {\n const planOrRefusal = (entries: PermissionEntry[]): DelegationPlanResult | AuthorityRefusal => {\n const plan = host.planDelegation(entries);\n if (\"refused\" in plan) return plan;\n return { path: plan.path, parentCid: plan.parentCid, expiresAt: plan.expiresAt };\n };\n const sessionAudience = (value: string): string => value.split(\"#\", 1)[0]!;\n const signedSessionGrant = (session: ReplicationAuthoritySession) => {\n const grant = parseUcanGrant(session.delegationHeader.Authorization);\n if (\n grant.cid !== session.delegationCid ||\n sessionAudience(grant.audience) !== sessionAudience(session.verificationMethod)\n ) return undefined;\n return grant;\n };\n const authority: ReplicationAuthority = {\n get sessionOnly() {\n const session = host.replicationSession();\n return session !== undefined && session.siwe === undefined;\n },\n sessionGrant(prefix: string): { ucan: string; device: ReplicaDevice } | AuthorityRefusal {\n const session = host.replicationSession();\n if (session === undefined) return { refused: \"SESSION_EXPIRING\" };\n const siweExpiry = session.siwe ? host.siweExpiration(session.siwe)?.getTime() : undefined;\n if (siweExpiry !== undefined && siweExpiry <= Date.now() + AUTHORITY_EXPIRY_MARGIN_MS) {\n return { refused: \"SESSION_EXPIRING\" };\n }\n let grant: ReturnType<typeof parseUcanGrant>;\n try {\n const verified = signedSessionGrant(session);\n if (!verified) return { refused: \"NOT_COVERED\" };\n grant = verified;\n } catch {\n return { refused: \"NOT_COVERED\" };\n }\n const tokenExpiry = grant.expiresAt === null ? undefined : grant.expiresAt * 1000;\n if (tokenExpiry === undefined || tokenExpiry <= Date.now() + AUTHORITY_EXPIRY_MARGIN_MS) {\n return { refused: \"SESSION_EXPIRING\" };\n }\n const covered = ucanAttCovers(grant.att, session.spaceId, prefix, KV_GET, { ignoreCaveats: true }) &&\n ucanAttCovers(grant.att, session.spaceId, prefix, KV_SYNC, { ignoreCaveats: true });\n if (!covered) return { refused: \"NOT_COVERED\" };\n if (!ucanAttUnconstrainedFor(grant.att, session.spaceId, prefix)) {\n return { refused: \"CAVEATED_AUTHORITY\" };\n }\n return {\n ucan: grant.jwt,\n device: { did: session.verificationMethod, jwk: session.jwk },\n };\n },\n plan(prefix: string): DelegationPlanResult | AuthorityRefusal {\n const session = host.replicationSession();\n if (session === undefined) return { refused: \"SESSION_EXPIRING\" };\n const sessionGrant = this.sessionGrant(prefix);\n if (!(\"refused\" in sessionGrant)) {\n const signed = signedSessionGrant(session);\n if (!signed || signed.expiresAt === null) return { refused: \"NOT_COVERED\" };\n return {\n path: \"session\",\n parentCid: session.delegationCid,\n expiresAt: signed.expiresAt * 1000,\n };\n }\n if (authority.sessionOnly) return sessionGrant;\n return planOrRefusal(replicationEntries(session.spaceId, prefix));\n },\n async mint(\n deviceDid: string,\n prefix: string,\n signal: AbortSignal,\n ): Promise<{ ucan: string; parentCid: string; expiresAt: number }> {\n if (authority.sessionOnly) throw new Error(\"compact delegate sessions cannot mint device grants\");\n const session = host.replicationSession();\n if (session === undefined) throw new Error(\"replication mint without a session\");\n const entries = replicationEntries(session.spaceId, prefix);\n const plan = planOrRefusal(entries);\n if (\"refused\" in plan) {\n throw Object.assign(\n new Error(`replication authority refused the mint: ${plan.refused}`),\n { refused: plan.refused },\n );\n }\n signal.throwIfAborted();\n const minted = await new Promise<{ ucan: string; expiresAt: number }>((resolve, reject) => {\n const onAbort = (): void => reject(signal.reason ?? new Error(\"replication mint aborted\"));\n signal.addEventListener(\"abort\", onAbort, { once: true });\n host.mintDelegation(deviceDid, entries).then(\n (result) => {\n signal.removeEventListener(\"abort\", onAbort);\n resolve(result);\n },\n (error: unknown) => {\n signal.removeEventListener(\"abort\", onAbort);\n reject(error instanceof Error ? error : new Error(String(error)));\n },\n );\n });\n return { ucan: minted.ucan, parentCid: plan.parentCid, expiresAt: minted.expiresAt };\n },\n };\n return authority;\n}\n","import { assertFolderPrefix as assertPureFolderPrefix, kvPrefixCovers } from \"@tinycloud/sdk-services/kv/replication/prefix\";\nimport type { ReplicationOptions } from \"@tinycloud/sdk-core\";\n\n/** Constructor-time validation; the space-dependent secrets check runs at sign-in and open. */\nexport function assertValidReplicationConfig(\n replication: (ReplicationOptions & { storage?: unknown }) | undefined,\n): void {\n if (replication === undefined || replication.enabled !== true) return;\n if (replication.storage === undefined || replication.storage === null) {\n throw new TypeError(\"replication.enabled requires replication.storage\");\n }\n if (!Array.isArray(replication.prefixes) || replication.prefixes.length === 0) {\n throw new TypeError(\"replication.prefixes must be a non-empty array\");\n }\n for (const prefix of replication.prefixes) {\n try {\n assertPureFolderPrefix(prefix);\n } catch (error) {\n throw new TypeError(error instanceof Error ? error.message : String(error));\n }\n }\n for (const [index, prefix] of replication.prefixes.entries()) {\n for (const other of replication.prefixes.slice(index + 1)) {\n if (kvPrefixCovers(prefix, other) || kvPrefixCovers(other, prefix)) {\n throw new TypeError(\n `replication.prefixes must not overlap: ${JSON.stringify(prefix)} and ${JSON.stringify(other)}`,\n );\n }\n }\n if ((prefix === \"/\" || prefix.split(\"/\", 1)[0] === \"vault\") && replication.allowSecrets !== true) {\n throw new TypeError(\n `replication prefix ${JSON.stringify(prefix)} overlaps the vault namespace; pass allowSecrets to opt in`,\n );\n }\n }\n}\n","import {\n KV,\n isCapabilitySubset,\n type PermissionEntry,\n type ReplicationOptions,\n} from \"@tinycloud/sdk-core\";\nimport { requiresSecretsOptIn } from \"@tinycloud/sdk-services\";\nimport { assertFolderPrefix } from \"@tinycloud/sdk-services/kv/replication/prefix\";\n\nconst KV_GET = KV.GET;\nconst KV_SYNC = KV.SYNC;\nconst KV_SERVICE = \"tinycloud.kv\";\n\nexport function hasUnrestrictedGetCoverage(\n entries: readonly PermissionEntry[],\n spaceId: string,\n prefix: string,\n): boolean {\n return isCapabilitySubset(\n [{ service: KV_SERVICE, space: spaceId, path: prefix, actions: [KV_GET] }],\n [...entries],\n ).subset;\n}\n\nexport function augmentSignInEntriesWithReplication(input: {\n entries: readonly PermissionEntry[];\n primarySpaceId: string;\n replication?: Pick<ReplicationOptions, \"prefixes\" | \"allowSecrets\">;\n}): PermissionEntry[] {\n const out: PermissionEntry[] = [];\n const replication = input.replication;\n if (replication === undefined) return out;\n const eligiblePaths: string[] = [];\n for (const prefix of replication.prefixes) {\n assertFolderPrefix(prefix);\n const path = prefix === \"/\" ? \"\" : prefix;\n if (\n requiresSecretsOptIn(input.primarySpaceId, prefix) &&\n replication.allowSecrets !== true\n ) {\n continue;\n }\n if (!hasUnrestrictedGetCoverage(input.entries, input.primarySpaceId, path)) {\n continue;\n }\n eligiblePaths.push(path);\n }\n if (\n eligiblePaths.length === replication.prefixes.length &&\n eligiblePaths.length > 0 &&\n hasUnrestrictedGetCoverage(input.entries, input.primarySpaceId, \"\")\n ) {\n return [{\n service: KV_SERVICE,\n space: input.primarySpaceId,\n path: \"\",\n actions: [KV_GET, KV_SYNC],\n }];\n }\n for (const path of eligiblePaths) {\n out.push({\n service: KV_SERVICE,\n space: input.primarySpaceId,\n path,\n actions: [KV_GET, KV_SYNC],\n });\n }\n return out;\n}\n"],"mappings":";AAeA;AAAA,EACE;AAAA,EACA,MAAAA;AAAA,EACA;AAAA,EACA,sBAAAC;AAAA,EACA;AAAA,OAGK;AAQP,SAAS,kBAAkB,6BAA6B;;;AC/BxD,SAAS,sBAAsB,wBAAwB,sBAAsB;AAItE,SAAS,6BACd,aACM;AACN,MAAI,gBAAgB,UAAa,YAAY,YAAY,KAAM;AAC/D,MAAI,YAAY,YAAY,UAAa,YAAY,YAAY,MAAM;AACrE,UAAM,IAAI,UAAU,kDAAkD;AAAA,EACxE;AACA,MAAI,CAAC,MAAM,QAAQ,YAAY,QAAQ,KAAK,YAAY,SAAS,WAAW,GAAG;AAC7E,UAAM,IAAI,UAAU,gDAAgD;AAAA,EACtE;AACA,aAAW,UAAU,YAAY,UAAU;AACzC,QAAI;AACF,6BAAuB,MAAM;AAAA,IAC/B,SAAS,OAAO;AACd,YAAM,IAAI,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,IAC5E;AAAA,EACF;AACA,aAAW,CAAC,OAAO,MAAM,KAAK,YAAY,SAAS,QAAQ,GAAG;AAC5D,eAAW,SAAS,YAAY,SAAS,MAAM,QAAQ,CAAC,GAAG;AACzD,UAAI,eAAe,QAAQ,KAAK,KAAK,eAAe,OAAO,MAAM,GAAG;AAClE,cAAM,IAAI;AAAA,UACR,0CAA0C,KAAK,UAAU,MAAM,CAAC,QAAQ,KAAK,UAAU,KAAK,CAAC;AAAA,QAC/F;AAAA,MACF;AAAA,IACF;AACA,SAAK,WAAW,OAAO,OAAO,MAAM,KAAK,CAAC,EAAE,CAAC,MAAM,YAAY,YAAY,iBAAiB,MAAM;AAChG,YAAM,IAAI;AAAA,QACR,sBAAsB,KAAK,UAAU,MAAM,CAAC;AAAA,MAC9C;AAAA,IACF;AAAA,EACF;AACF;;;ACnCA;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AACP,SAAS,4BAA4B;AACrC,SAAS,0BAA0B;AAEnC,IAAM,SAAS,GAAG;AAClB,IAAM,UAAU,GAAG;AACnB,IAAM,aAAa;AAEZ,SAAS,2BACd,SACA,SACA,QACS;AACT,SAAO;AAAA,IACL,CAAC,EAAE,SAAS,YAAY,OAAO,SAAS,MAAM,QAAQ,SAAS,CAAC,MAAM,EAAE,CAAC;AAAA,IACzE,CAAC,GAAG,OAAO;AAAA,EACb,EAAE;AACJ;AAEO,SAAS,oCAAoC,OAI9B;AACpB,QAAM,MAAyB,CAAC;AAChC,QAAM,cAAc,MAAM;AAC1B,MAAI,gBAAgB,OAAW,QAAO;AACtC,QAAM,gBAA0B,CAAC;AACjC,aAAW,UAAU,YAAY,UAAU;AACzC,uBAAmB,MAAM;AACzB,UAAM,OAAO,WAAW,MAAM,KAAK;AACnC,QACE,qBAAqB,MAAM,gBAAgB,MAAM,KACjD,YAAY,iBAAiB,MAC7B;AACA;AAAA,IACF;AACA,QAAI,CAAC,2BAA2B,MAAM,SAAS,MAAM,gBAAgB,IAAI,GAAG;AAC1E;AAAA,IACF;AACA,kBAAc,KAAK,IAAI;AAAA,EACzB;AACA,MACE,cAAc,WAAW,YAAY,SAAS,UAC9C,cAAc,SAAS,KACvB,2BAA2B,MAAM,SAAS,MAAM,gBAAgB,EAAE,GAClE;AACA,WAAO,CAAC;AAAA,MACN,SAAS;AAAA,MACT,OAAO,MAAM;AAAA,MACb,MAAM;AAAA,MACN,SAAS,CAAC,QAAQ,OAAO;AAAA,IAC3B,CAAC;AAAA,EACH;AACA,aAAW,QAAQ,eAAe;AAChC,QAAI,KAAK;AAAA,MACP,SAAS;AAAA,MACT,OAAO,MAAM;AAAA,MACb;AAAA,MACA,SAAS,CAAC,QAAQ,OAAO;AAAA,IAC3B,CAAC;AAAA,EACH;AACA,SAAO;AACT;;;AFpCA,IAAM,iBAAiB;AAIvB,IAAMC,UAASC,IAAG;AAClB,IAAMC,WAAUD,IAAG;AACnB,IAAME,cAAa;AAGnB,SAAS,mBAAmB,SAAiB,QAAmC;AAC9E,SAAO,CAAC;AAAA,IACN,SAASA;AAAA,IACT,OAAO;AAAA,IACP,MAAM,WAAW,MAAM,KAAK;AAAA,IAC5B,SAAS,CAACH,SAAQE,QAAO;AAAA,EAC3B,CAAC;AACH;AAUA,SAAS,4BAA4B,OAAyB;AAC5D,MAAI,UAAU,UAAa,UAAU,KAAM,QAAO;AAClD,MAAI,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,KAAK,OAAO,KAAK,KAAK,EAAE,WAAW,GAAG;AACzF,WAAO;AAAA,EACT;AACA,MAAI,CAAC,MAAM,QAAQ,KAAK,EAAG,QAAO;AAClC,MAAI;AACF,WACE,yBAAyB,KAAkC,MAC3D,yBAAyB,MAAS;AAAA,EAEtC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,SAAS,iBAAiB,UAAkB,OAAmC;AAC7E,aAAW,QAAQ,CAAC,GAAG,KAAK,OAAO,eAAe,KAAK,KAAK,GAAG;AAC7D,QAAI,aAAa,QAAQ,aAAa,GAAG,IAAI,IAAK,QAAO;AAAA,EAC3D;AACA,aAAW,QAAQ,CAAC,GAAG,KAAK,QAAQ,eAAe,KAAK,MAAM,GAAG;AAC/D,QAAI,SAAS,WAAW,IAAI,EAAG,QAAO,SAAS,MAAM,KAAK,MAAM;AAAA,EAClE;AACA,SAAO;AAET;AAKO,SAAS,cACd,KACA,OACA,QACA,SACA,SACS;AACT,aAAW,CAAC,UAAU,SAAS,KAAK,OAAO,QAAQ,GAAG,GAAG;AACvD,UAAM,OAAO,iBAAiB,UAAU,KAAK;AAC7C,QAAI,SAAS,OAAW;AACxB,eAAW,CAAC,SAAS,OAAO,KAAK,OAAO,QAAQ,SAAS,GAAG;AAC1D,UAAI,CAAC,eAAe,SAAS,OAAO,EAAG;AACvC,UAAI,CAACE;AAAA,QACH,CAAC,EAAE,SAASD,aAAY,OAAO,MAAM,QAAQ,SAAS,CAAC,OAAO,EAAE,CAAC;AAAA,QACjE,CAAC,EAAE,SAASA,aAAY,OAAO,MAAM,SAAS,CAAC,OAAO,EAAE,CAAC;AAAA,MAC3D,EAAE,OAAQ;AACV,UAAI,SAAS,kBAAkB,QAAQ,4BAA4B,OAAO,EAAG,QAAO;AAAA,IACtF;AAAA,EACF;AACA,SAAO;AACT;AAGO,SAAS,wBACd,KACA,OACA,QACS;AACT,SACE,cAAc,KAAK,OAAO,QAAQH,OAAM,KACxC,cAAc,KAAK,OAAO,QAAQE,QAAO;AAE7C;AAiCO,IAAM,6BAA6B;AAInC,SAAS,kCAAkC,OAKG;AACnD,MAAI;AACJ,MAAI;AACF,YAAQ,eAAe,MAAM,iBAAiB,aAAa;AAAA,EAC7D,QAAQ;AACN,WAAO;AAAA,EACT;AACA,QAAM,UAAU,CAAC,UAA0B,MAAM,MAAM,KAAK,CAAC,EAAE,CAAC;AAChE,MACE,MAAM,QAAQ,MAAM,iBACpB,QAAQ,MAAM,QAAQ,MAAM,QAAQ,MAAM,kBAAkB,EAC5D,QAAO;AACT,aAAW,YAAY,OAAO,KAAK,MAAM,GAAG,GAAG;AAC7C,UAAM,QAAQ,SAAS,MAAM,uCAAuC,IAAI,CAAC;AACzE,QAAI,UAAU,MAAM,eAAgB;AACpC,UAAM,QAAQ,cAAc,KAAK;AACjC,QAAI,OAAO,SAAS,MAAM,WAAW,MAAM,QAAS,QAAO,EAAE,OAAO,WAAW,MAAM,MAAM;AAAA,EAC7F;AACA,SAAO;AACT;AACO,SAAS,2BAA2B,MAAsD;AAC/F,QAAM,gBAAgB,CAAC,YAAwE;AAC7F,UAAM,OAAO,KAAK,eAAe,OAAO;AACxC,QAAI,aAAa,KAAM,QAAO;AAC9B,WAAO,EAAE,MAAM,KAAK,MAAM,WAAW,KAAK,WAAW,WAAW,KAAK,UAAU;AAAA,EACjF;AACA,QAAM,kBAAkB,CAAC,UAA0B,MAAM,MAAM,KAAK,CAAC,EAAE,CAAC;AACxE,QAAM,qBAAqB,CAAC,YAAyC;AACnE,UAAM,QAAQ,eAAe,QAAQ,iBAAiB,aAAa;AACnE,QACE,MAAM,QAAQ,QAAQ,iBACtB,gBAAgB,MAAM,QAAQ,MAAM,gBAAgB,QAAQ,kBAAkB,EAC9E,QAAO;AACT,WAAO;AAAA,EACT;AACA,QAAM,YAAkC;AAAA,IACtC,IAAI,cAAc;AAChB,YAAM,UAAU,KAAK,mBAAmB;AACxC,aAAO,YAAY,UAAa,QAAQ,SAAS;AAAA,IACnD;AAAA,IACA,aAAa,QAA4E;AACvF,YAAM,UAAU,KAAK,mBAAmB;AACxC,UAAI,YAAY,OAAW,QAAO,EAAE,SAAS,mBAAmB;AAChE,YAAM,aAAa,QAAQ,OAAO,KAAK,eAAe,QAAQ,IAAI,GAAG,QAAQ,IAAI;AACjF,UAAI,eAAe,UAAa,cAAc,KAAK,IAAI,IAAI,4BAA4B;AACrF,eAAO,EAAE,SAAS,mBAAmB;AAAA,MACvC;AACA,UAAI;AACJ,UAAI;AACF,cAAM,WAAW,mBAAmB,OAAO;AAC3C,YAAI,CAAC,SAAU,QAAO,EAAE,SAAS,cAAc;AAC/C,gBAAQ;AAAA,MACV,QAAQ;AACN,eAAO,EAAE,SAAS,cAAc;AAAA,MAClC;AACA,YAAM,cAAc,MAAM,cAAc,OAAO,SAAY,MAAM,YAAY;AAC7E,UAAI,gBAAgB,UAAa,eAAe,KAAK,IAAI,IAAI,4BAA4B;AACvF,eAAO,EAAE,SAAS,mBAAmB;AAAA,MACvC;AACA,YAAM,UAAU,cAAc,MAAM,KAAK,QAAQ,SAAS,QAAQF,SAAQ,EAAE,eAAe,KAAK,CAAC,KAC/F,cAAc,MAAM,KAAK,QAAQ,SAAS,QAAQE,UAAS,EAAE,eAAe,KAAK,CAAC;AACpF,UAAI,CAAC,QAAS,QAAO,EAAE,SAAS,cAAc;AAC9C,UAAI,CAAC,wBAAwB,MAAM,KAAK,QAAQ,SAAS,MAAM,GAAG;AAChE,eAAO,EAAE,SAAS,qBAAqB;AAAA,MACzC;AACA,aAAO;AAAA,QACL,MAAM,MAAM;AAAA,QACZ,QAAQ,EAAE,KAAK,QAAQ,oBAAoB,KAAK,QAAQ,IAAI;AAAA,MAC9D;AAAA,IACF;AAAA,IACA,KAAK,QAAyD;AAC5D,YAAM,UAAU,KAAK,mBAAmB;AACxC,UAAI,YAAY,OAAW,QAAO,EAAE,SAAS,mBAAmB;AAChE,YAAM,eAAe,KAAK,aAAa,MAAM;AAC7C,UAAI,EAAE,aAAa,eAAe;AAChC,cAAM,SAAS,mBAAmB,OAAO;AACzC,YAAI,CAAC,UAAU,OAAO,cAAc,KAAM,QAAO,EAAE,SAAS,cAAc;AAC1E,eAAO;AAAA,UACL,MAAM;AAAA,UACN,WAAW,QAAQ;AAAA,UACnB,WAAW,OAAO,YAAY;AAAA,QAChC;AAAA,MACF;AACA,UAAI,UAAU,YAAa,QAAO;AAClC,aAAO,cAAc,mBAAmB,QAAQ,SAAS,MAAM,CAAC;AAAA,IAClE;AAAA,IACA,MAAM,KACJ,WACA,QACA,QACiE;AACjE,UAAI,UAAU,YAAa,OAAM,IAAI,MAAM,qDAAqD;AAChG,YAAM,UAAU,KAAK,mBAAmB;AACxC,UAAI,YAAY,OAAW,OAAM,IAAI,MAAM,oCAAoC;AAC/E,YAAM,UAAU,mBAAmB,QAAQ,SAAS,MAAM;AAC1D,YAAM,OAAO,cAAc,OAAO;AAClC,UAAI,aAAa,MAAM;AACrB,cAAM,OAAO;AAAA,UACX,IAAI,MAAM,2CAA2C,KAAK,OAAO,EAAE;AAAA,UACnE,EAAE,SAAS,KAAK,QAAQ;AAAA,QAC1B;AAAA,MACF;AACA,aAAO,eAAe;AACtB,YAAM,SAAS,MAAM,IAAI,QAA6C,CAAC,SAAS,WAAW;AACzF,cAAM,UAAU,MAAY,OAAO,OAAO,UAAU,IAAI,MAAM,0BAA0B,CAAC;AACzF,eAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;AACxD,aAAK,eAAe,WAAW,OAAO,EAAE;AAAA,UACtC,CAAC,WAAW;AACV,mBAAO,oBAAoB,SAAS,OAAO;AAC3C,oBAAQ,MAAM;AAAA,UAChB;AAAA,UACA,CAAC,UAAmB;AAClB,mBAAO,oBAAoB,SAAS,OAAO;AAC3C,mBAAO,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC;AAAA,UAClE;AAAA,QACF;AAAA,MACF,CAAC;AACD,aAAO,EAAE,MAAM,OAAO,MAAM,WAAW,KAAK,WAAW,WAAW,OAAO,UAAU;AAAA,IACrF;AAAA,EACF;AACA,SAAO;AACT;","names":["KV","isCapabilitySubset","KV_GET","KV","KV_SYNC","KV_SERVICE","isCapabilitySubset"]}
|
|
1
|
+
{"version":3,"sources":["../../src/replication/authority.ts","../../src/replication/config.ts","../../src/replication/authority-sign-in.ts"],"sourcesContent":["/**\n * Replication authority (TC-858, plan v3 §4).\n *\n * `ReplicationAuthority` answers, per configured prefix, whether this node's\n * session can back a local replica: the session UCAN itself (delegate\n * posture), a child UCAN minted through `delegateTo`, or nothing (the\n * controller then reports `grant_missing` and reads from the network).\n *\n * The unrestricted-authority rule is enforced here, before any network work:\n * a prefix covered only by a caveated `get`/`sync` refuses with\n * `CAVEATED_AUTHORITY` — on the session path, the runtime-grant path, and on\n * any reuse of an installed grant (the adapter marks every grant's\n * `unconstrained` flag; the controller enforces it on reuse).\n */\nimport { SiweMessage } from \"siwe\";\n\nimport {\n actionContains,\n KV,\n canonicalizeRecapCaveats,\n isCapabilitySubset,\n principalDid,\n parseSpaceUri,\n type PermissionEntry,\n type IWasmBindings,\n type WasmRecapEntry,\n type TinyCloudSession,\n type KVReplicaStorage,\n type ServiceContext,\n type ReplicationOptions,\n} from \"@tinycloud/sdk-core\";\nimport {\n type AuthorityRefusal,\n type ReplicaConsentAnchor,\n type ReplicaDevice,\n type ReplicationAuthority,\n type ReplicationEvent,\n} from \"@tinycloud/sdk-services\";\nimport type { ReplicaDenyInput, ReplicaSessionStamp } from \"@tinycloud/sdk-services/kv/replication\";\nimport type { DelegateToOptions, DelegateToResult, TinyCloudNode } from \"../TinyCloudNode\";\nimport type { ReplicaRuntime } from \"./replica-contract\";\n// @ts-ignore -- node-sdk builds before replica declarations; keep the literal import for bundlers.\nimport { parseUcanGrant as replicaParseUcanGrant } from \"@tinycloud/replica\";\nconst parseUcanGrant = replicaParseUcanGrant as ReplicaRuntime[\"parseUcanGrant\"];\nexport { assertValidReplicationConfig } from \"./config\";\nexport { augmentSignInEntriesWithReplication, hasUnrestrictedGetCoverage } from \"./authority-sign-in\";\n\n/** Report a completed deny from the already-loaded authority; delivery was revoked synchronously. */\nexport function reportReplicaDenial(\n result: { status: string; code?: string; intents?: number },\n reason: ReplicaDenyInput[\"reason\"],\n onEvent: ReplicationOptions[\"onEvent\"],\n context?: ServiceContext,\n): void {\n if (result.status !== \"denied\" && result.status !== \"deleted\" && result.status !== \"marker\") return;\n const event: Extract<ReplicationEvent, { type: \"replication.lifecycle\" }> = {\n type: \"replication.lifecycle\",\n at: new Date().toISOString(),\n reason,\n action: result.status === \"denied\" ? \"denied\" : result.status === \"deleted\" ? \"database_deleted\" : \"marker_set\",\n ...(result.status === \"denied\" ? { partitions: result.intents } : {}),\n ...(result.status === \"marker\" ? { code: result.code } : {}),\n };\n try { onEvent?.(event); } catch { /* Observability cannot undo committed denial. */ }\n const { type, ...data } = event;\n try { context?.emit(type, data); } catch { /* Observability cannot undo committed denial. */ }\n}\n\nconst KV_GET = KV.GET;\nconst KV_SYNC = KV.SYNC;\nconst KV_SERVICE = \"tinycloud.kv\";\n\n/** The verified owner proof is signed, finite, exact-space, and uncaveated on one get+sync scope. */\nexport function verifiedOwnerConsentAnchor(\n session: TinyCloudSession,\n wasmBindings: Pick<IWasmBindings, \"validatePersistedSession\">,\n exactVerifiedRecap: (verified: { verifiedRecap?: unknown }) => WasmRecapEntry[],\n): ReplicaConsentAnchor | null {\n if (typeof session.siwe !== \"string\" || typeof session.signature !== \"string\" ||\n typeof session.address !== \"string\" || typeof session.chainId !== \"number\") return null;\n try {\n const verified = wasmBindings.validatePersistedSession?.({\n delegationHeader: session.delegationHeader,\n delegationCid: session.delegationCid,\n spaceId: session.spaceId,\n jwk: session.jwk,\n address: session.address,\n chainId: session.chainId,\n siwe: session.siwe,\n signature: session.signature,\n });\n if (!verified || !Number.isSafeInteger(Date.parse(verified.expiresAt ?? \"\"))) return null;\n const signedIssuedAt = new SiweMessage(session.siwe).issuedAt;\n if (typeof signedIssuedAt !== \"string\") return null;\n const issuedAt = Date.parse(signedIssuedAt);\n if (!Number.isSafeInteger(issuedAt) || issuedAt < 0) return null;\n const granted = exactVerifiedRecap(verified)\n .filter((entry) => entry.space === session.spaceId && entry.service === \"kv\")\n .map((entry) => ({ ...entry, service: \"tinycloud.kv\" }));\n const coversGetAndSync = granted.some((entry) => isCapabilitySubset([{\n service: \"tinycloud.kv\",\n space: session.spaceId,\n path: entry.path,\n actions: [KV.GET, KV.SYNC],\n }], granted).subset);\n return coversGetAndSync ? { at: issuedAt, source: \"siwe\" } : null;\n } catch {\n return null;\n }\n}\n\n/** Fresh sign-in only: the lifecycle creates keys; restore never calls this. */\nexport function establishReplicaSessionStamp(\n storage: KVReplicaStorage,\n principal: string,\n anchor: ReplicaConsentAnchor,\n): Promise<ReplicaSessionStamp> {\n if (storage.kind === \"indexeddb\" && storage.lifecycle?.kind === \"indexeddb\") {\n return storage.lifecycle.establishSessionStamp(principal, { consentAnchor: anchor });\n }\n if (storage.kind === \"sqlite\" && storage.lifecycle?.kind === \"file\") {\n return storage.lifecycle.establishSessionStamp(principal, { consentAnchor: anchor });\n }\n throw Object.assign(new Error(\"Replica sign-in requires its platform lifecycle gate.\"), {\n code: \"REPLICA_GATED\" as const, gate: \"gate_unavailable\" as const,\n });\n}\n\n/** Existing persisted stamp only: never manufactures an authority on restore. */\nexport function restoreReplicaSessionStamp(\n storage: KVReplicaStorage,\n principal: string,\n persisted: ReplicaSessionStamp | undefined,\n): Promise<ReplicaSessionStamp> {\n if (storage.kind === \"indexeddb\" && storage.lifecycle?.kind === \"indexeddb\") {\n return storage.lifecycle.restoreSessionStamp(principal, persisted);\n }\n if (storage.kind === \"sqlite\" && storage.lifecycle?.kind === \"file\") {\n return storage.lifecycle.restoreSessionStamp(principal, persisted);\n }\n throw Object.assign(new Error(\"Replica restore requires its platform lifecycle gate.\"), {\n code: \"REPLICA_GATED\" as const, gate: \"gate_unavailable\" as const,\n });\n}\n\n/** The delegation the replication grant delegates: get + sync on one folder prefix. */\nfunction replicationEntries(spaceId: string, prefix: string): PermissionEntry[] {\n return [{\n service: KV_SERVICE,\n space: spaceId,\n path: prefix === \"/\" ? \"\" : prefix,\n actions: [KV_GET, KV_SYNC],\n }];\n}\n\n\n\n\n/**\n * The signed UCAN attenuation (`att`: resource → ability → caveat branches).\n * Only array branch sets are known; anything else is treated as constrained —\n * fail closed, never assume unconstrained.\n */\nfunction caveatBranchesUnconstrained(value: unknown): boolean {\n if (value === undefined || value === null) return true;\n if (typeof value === \"object\" && !Array.isArray(value) && Object.keys(value).length === 0) {\n return true;\n }\n if (!Array.isArray(value)) return false;\n try {\n return (\n canonicalizeRecapCaveats(value as Record<string, unknown>[]) ===\n canonicalizeRecapCaveats(undefined)\n );\n } catch {\n return false;\n }\n}\n\n/** The KV path a signed UCAN resource names in `space`, like replica's `kvPathIn`. */\nfunction kvPathInResource(resource: string, space: string): string | undefined {\n for (const root of [`${space}/kv`, `tinycloud://${space}/kv`]) {\n if (resource === root || resource === `${root}/`) return \"/\";\n }\n for (const base of [`${space}/kv/`, `tinycloud://${space}/kv/`]) {\n if (resource.startsWith(base)) return resource.slice(base.length);\n }\n return undefined;\n\n}\n/**\n * Whether the signed `att` grants `ability` over the folder prefix, with\n * unrestricted authority unless explicitly asked to ignore caveats.\n */\nexport function ucanAttCovers(\n att: Record<string, Record<string, unknown>>,\n space: string,\n prefix: string,\n ability: string,\n options?: { ignoreCaveats?: boolean },\n): boolean {\n for (const [resource, abilities] of Object.entries(att)) {\n const path = kvPathInResource(resource, space);\n if (path === undefined) continue;\n for (const [granted, caveats] of Object.entries(abilities)) {\n if (!actionContains(granted, ability)) continue;\n if (!isCapabilitySubset(\n [{ service: KV_SERVICE, space, path: prefix, actions: [ability] }],\n [{ service: KV_SERVICE, space, path, actions: [granted] }],\n ).subset) continue;\n if (options?.ignoreCaveats === true || caveatBranchesUnconstrained(caveats)) return true;\n }\n }\n return false;\n}\n\n/** `unconstrained` for `ReplicaGrantInfo` (§2.1): get AND sync on the prefix carry no caveats. */\nexport function ucanAttUnconstrainedFor(\n att: Record<string, Record<string, unknown>>,\n space: string,\n prefix: string,\n): boolean {\n return (\n ucanAttCovers(att, space, prefix, KV_GET) &&\n ucanAttCovers(att, space, prefix, KV_SYNC)\n );\n}\n\nexport interface DelegationPlanResult {\n path: \"session\" | \"runtime\";\n parentCid: string;\n expiresAt: number;\n}\n\n/**\n * The `TinyCloudNode` internals the authority needs. `planDelegation` is the\n * pure extraction of `delegateTo`'s parent selection (§4.3), so `plan` and\n * `mint` can never disagree about which parent or expiry a delegation gets.\n */\nexport type ReplicationAuthoritySession = Pick<\n TinyCloudSession,\n \"delegationHeader\" | \"delegationCid\" | \"spaceId\" | \"verificationMethod\" | \"jwk\"\n> & Partial<Pick<TinyCloudSession, \"siwe\" | \"signature\" | \"address\" | \"chainId\">>;\n\nexport interface ReplicationAuthorityHost {\n replicationSession(): ReplicationAuthoritySession | undefined;\n /** Validated owner proof only; never infer consent from a compact child UCAN. */\n verifiedConsentAnchor?(session: ReplicationAuthoritySession): ReplicaConsentAnchor | null;\n /** The SIWE \"Expiration Time\" of the session — the delegateTo delegation cap. */\n siweExpiration(siwe: string): Date | undefined;\n planDelegation(\n entries: PermissionEntry[],\n options?: { expiry?: string | number },\n ): ({ grant?: unknown } & DelegationPlanResult) | AuthorityRefusal;\n mintDelegation(\n deviceDid: string,\n entries: PermissionEntry[],\n options?: { expiryMs?: number; renewalOnly?: boolean },\n ): Promise<{ ucan: string; expiresAt: number }>;\n}\n\n/** Delegate a replica grant only through the selected owner parent for silent renewal. */\nexport async function mintNodeReplicationGrant(\n deviceDid: string,\n entries: PermissionEntry[],\n options: { expiryMs?: number; renewalOnly?: boolean } | undefined,\n renewalSession: ReplicationAuthoritySession | undefined,\n node: Pick<TinyCloudNode, \"delegateTo\">,\n): Promise<{ ucan: string; expiresAt: number }> {\n if (options?.renewalOnly && !renewalSession) throw new Error(\"replication renewal lost its owner session\");\n const delegateOptions: DelegateToOptions | undefined = options?.renewalOnly ? {\n expiry: options.expiryMs,\n onPrepared: async (prepared) => {\n assertRenewalParent(prepared.delegationHeader.Authorization, renewalSession!);\n },\n } : options?.expiryMs === undefined ? undefined : { expiry: options.expiryMs };\n const result: DelegateToResult = await node.delegateTo(principalDid(deviceDid), entries, delegateOptions);\n return {\n ucan: result.delegation.delegationHeader.Authorization,\n expiresAt: result.delegation.expiry.getTime(),\n };\n}\n\n/** Milliseconds of validity a session or parent must have left for authority reuse (delegateTo's margin). */\nexport const AUTHORITY_EXPIRY_MARGIN_MS = 60_000;\n\n\n/** Verify a compact session delegation before using any persisted space metadata. */\nexport function replicationScopeFromSignedSession(input: {\n delegationHeader: { Authorization: string };\n delegationCid: string;\n verificationMethod: string;\n persistedSpace: string;\n}): { space: string; principal: string } | undefined {\n let grant: ReturnType<typeof parseUcanGrant>;\n try {\n grant = parseUcanGrant(input.delegationHeader.Authorization);\n } catch {\n return undefined;\n }\n const bareDid = (value: string): string => value.split(\"#\", 1)[0]!;\n if (\n grant.cid !== input.delegationCid ||\n bareDid(grant.audience) !== bareDid(input.verificationMethod)\n ) return undefined;\n for (const resource of Object.keys(grant.att)) {\n const space = resource.match(/^(?:tinycloud:\\/\\/)?(.+?)\\/kv(?:\\/|$)/)?.[1];\n if (space !== input.persistedSpace) continue;\n const owner = parseSpaceUri(space);\n if (owner?.owner && owner.address && owner.chainId) return { space, principal: owner.owner };\n }\n return undefined;\n}\n/** Runs before host activation; a replan cannot silently switch a renewal to a runtime parent. */\nexport function assertRenewalParent(authorization: string, session: ReplicationAuthoritySession): void {\n const grant = parseUcanGrant(authorization);\n if (\n grant.issuer !== session.verificationMethod.split(\"#\", 1)[0] ||\n grant.prf.length !== 1 ||\n grant.prf[0] !== session.delegationCid\n ) {\n throw Object.assign(new Error(\"replication renewal requires the bound owner session parent\"), { refused: \"NOT_COVERED\" as const });\n }\n}\n\nexport function createReplicationAuthority(host: ReplicationAuthorityHost): ReplicationAuthority {\n const boundSession = host.replicationSession();\n let verifiedAnchor: ReplicaConsentAnchor | null = null;\n try {\n const candidate = boundSession && host.verifiedConsentAnchor?.(boundSession);\n if (candidate && Number.isFinite(candidate.at) && candidate.source !== \"first_install\") {\n verifiedAnchor = { at: candidate.at, source: candidate.source };\n }\n } catch {\n // Network sessions remain usable when the optional replica proof cannot be verified.\n }\n const activeSession = (): ReplicationAuthoritySession | undefined => {\n const session = host.replicationSession();\n return session !== undefined && session === boundSession ? session : undefined;\n };\n const planOrRefusal = (entries: PermissionEntry[], expiryMs?: number): DelegationPlanResult | AuthorityRefusal => {\n const plan = host.planDelegation(entries, expiryMs === undefined ? undefined : { expiry: expiryMs });\n if (\"refused\" in plan) return plan;\n return { path: plan.path, parentCid: plan.parentCid, expiresAt: plan.expiresAt };\n };\n const sessionAudience = (value: string): string => value.split(\"#\", 1)[0]!;\n const signedSessionGrant = (session: ReplicationAuthoritySession) => {\n const grant = parseUcanGrant(session.delegationHeader.Authorization);\n if (\n grant.cid !== session.delegationCid ||\n sessionAudience(grant.audience) !== sessionAudience(session.verificationMethod)\n ) return undefined;\n return grant;\n };\n const authority: ReplicationAuthority = {\n get sessionOnly() {\n const session = activeSession();\n return session === undefined || session.siwe === undefined;\n },\n consentAnchor(): ReplicaConsentAnchor | null {\n return activeSession() ? verifiedAnchor : null;\n },\n get canRenew() {\n const session = activeSession();\n return verifiedAnchor?.source === \"siwe\" && session !== undefined && session.siwe !== undefined;\n },\n sessionGrant(prefix: string): { ucan: string; device: ReplicaDevice } | AuthorityRefusal {\n const session = activeSession();\n if (session === undefined) return { refused: \"SESSION_EXPIRING\" };\n // A compact session proves current signed coverage, not consent to install or mint.\n if (verifiedAnchor === null && session.siwe !== undefined) return { refused: \"NOT_COVERED\" };\n const siweExpiry = session.siwe ? host.siweExpiration(session.siwe)?.getTime() : undefined;\n if (siweExpiry !== undefined && siweExpiry <= Date.now() + AUTHORITY_EXPIRY_MARGIN_MS) {\n return { refused: \"SESSION_EXPIRING\" };\n }\n let grant: ReturnType<typeof parseUcanGrant>;\n try {\n const verified = signedSessionGrant(session);\n if (!verified) return { refused: \"NOT_COVERED\" };\n grant = verified;\n } catch {\n return { refused: \"NOT_COVERED\" };\n }\n const tokenExpiry = grant.expiresAt === null ? undefined : grant.expiresAt * 1000;\n if (tokenExpiry === undefined || tokenExpiry <= Date.now() + AUTHORITY_EXPIRY_MARGIN_MS) {\n return { refused: \"SESSION_EXPIRING\" };\n }\n const covered = ucanAttCovers(grant.att, session.spaceId, prefix, KV_GET, { ignoreCaveats: true }) &&\n ucanAttCovers(grant.att, session.spaceId, prefix, KV_SYNC, { ignoreCaveats: true });\n if (!covered) return { refused: \"NOT_COVERED\" };\n if (!ucanAttUnconstrainedFor(grant.att, session.spaceId, prefix)) {\n return { refused: \"CAVEATED_AUTHORITY\" };\n }\n return {\n ucan: grant.jwt,\n device: { did: session.verificationMethod, jwk: session.jwk },\n };\n },\n plan(prefix: string): DelegationPlanResult | AuthorityRefusal {\n const session = activeSession();\n if (session === undefined) return { refused: \"SESSION_EXPIRING\" };\n if (verifiedAnchor === null) return { refused: \"NOT_COVERED\" };\n const sessionGrant = this.sessionGrant(prefix);\n if (!(\"refused\" in sessionGrant)) {\n const signed = signedSessionGrant(session);\n if (!signed || signed.expiresAt === null) return { refused: \"NOT_COVERED\" };\n return {\n path: \"session\",\n parentCid: session.delegationCid,\n expiresAt: signed.expiresAt * 1000,\n };\n }\n if (authority.sessionOnly) return sessionGrant;\n return planOrRefusal(replicationEntries(session.spaceId, prefix));\n },\n async mint(\n deviceDid: string,\n prefix: string,\n signal: AbortSignal,\n options?: { expiryMs?: number; renewalOnly?: boolean },\n ): Promise<{ ucan: string; parentCid: string; expiresAt: number }> {\n const session = activeSession();\n if (session === undefined) throw new Error(\"replication mint without the bound session\");\n if (session.siwe === undefined) throw new Error(\"compact delegate sessions cannot mint device grants\");\n if (verifiedAnchor?.source !== \"siwe\") throw new Error(\"replication mint requires verified owner consent\");\n const entries = replicationEntries(session.spaceId, prefix);\n const plan = planOrRefusal(entries, options?.expiryMs);\n if (\"refused\" in plan) {\n throw Object.assign(\n new Error(`replication authority refused the mint: ${plan.refused}`),\n { refused: plan.refused },\n );\n }\n if (options?.renewalOnly && plan.path !== \"session\") {\n throw Object.assign(new Error(\"replication renewal requires a session parent\"), { refused: \"NOT_COVERED\" as const });\n }\n signal.throwIfAborted();\n const minted = await new Promise<{ ucan: string; expiresAt: number }>((resolve, reject) => {\n const onAbort = (): void => reject(signal.reason ?? new Error(\"replication mint aborted\"));\n signal.addEventListener(\"abort\", onAbort, { once: true });\n host.mintDelegation(deviceDid, entries, options).then(\n (result) => {\n signal.removeEventListener(\"abort\", onAbort);\n resolve(result);\n },\n (error: unknown) => {\n signal.removeEventListener(\"abort\", onAbort);\n reject(error instanceof Error ? error : new Error(String(error)));\n },\n );\n });\n return { ucan: minted.ucan, parentCid: plan.parentCid, expiresAt: minted.expiresAt };\n },\n };\n return authority;\n}\n","import { MAX_GRANT_TTL_MS } from \"@tinycloud/sdk-services/kv/replication/grantPolicyConstants\";\nimport { assertFolderPrefix as assertPureFolderPrefix, kvPrefixCovers } from \"@tinycloud/sdk-services/kv/replication/prefix\";\nimport type { ReplicationOptions } from \"@tinycloud/sdk-core\";\n\n/** Constructor-time validation; the space-dependent secrets check runs at sign-in and open. */\nexport function assertValidReplicationConfig(\n replication: (ReplicationOptions & { storage?: unknown }) | undefined,\n): void {\n if (replication === undefined || !replication.enabled) return;\n if (replication.storage === undefined || replication.storage === null) {\n throw new TypeError(\"replication.enabled requires replication.storage\");\n }\n if (!Array.isArray(replication.prefixes) || replication.prefixes.length === 0) {\n throw new TypeError(\"replication.prefixes must be a non-empty array\");\n }\n const grantTtlMs = replication.grantTtlMs;\n if (grantTtlMs !== undefined && (!Number.isFinite(grantTtlMs) || grantTtlMs <= 0 || grantTtlMs > MAX_GRANT_TTL_MS)) {\n throw new TypeError(`replication.grantTtlMs must be a positive number of milliseconds no greater than ${MAX_GRANT_TTL_MS}`);\n }\n for (const prefix of replication.prefixes) {\n try {\n assertPureFolderPrefix(prefix);\n } catch (error) {\n throw new TypeError(error instanceof Error ? error.message : String(error));\n }\n }\n for (const [index, prefix] of replication.prefixes.entries()) {\n for (const other of replication.prefixes.slice(index + 1)) {\n if (kvPrefixCovers(prefix, other) || kvPrefixCovers(other, prefix)) {\n throw new TypeError(\n `replication.prefixes must not overlap: ${JSON.stringify(prefix)} and ${JSON.stringify(other)}`,\n );\n }\n }\n if ((prefix === \"/\" || prefix.split(\"/\", 1)[0] === \"vault\") && replication.allowSecrets !== true) {\n throw new TypeError(\n `replication prefix ${JSON.stringify(prefix)} overlaps the vault namespace; pass allowSecrets to opt in`,\n );\n }\n }\n}\n","import {\n KV,\n isCapabilitySubset,\n type PermissionEntry,\n type ReplicationOptions,\n} from \"@tinycloud/sdk-core\";\nimport { requiresSecretsOptIn } from \"@tinycloud/sdk-services\";\nimport { assertFolderPrefix } from \"@tinycloud/sdk-services/kv/replication/prefix\";\n\nconst KV_GET = KV.GET;\nconst KV_SYNC = KV.SYNC;\nconst KV_SERVICE = \"tinycloud.kv\";\n\nexport function hasUnrestrictedGetCoverage(\n entries: readonly PermissionEntry[],\n spaceId: string,\n prefix: string,\n): boolean {\n return isCapabilitySubset(\n [{ service: KV_SERVICE, space: spaceId, path: prefix, actions: [KV_GET] }],\n [...entries],\n ).subset;\n}\n\nexport function augmentSignInEntriesWithReplication(input: {\n entries: readonly PermissionEntry[];\n primarySpaceId: string;\n replication?: Pick<ReplicationOptions, \"prefixes\" | \"allowSecrets\">;\n}): PermissionEntry[] {\n const out: PermissionEntry[] = [];\n const replication = input.replication;\n if (replication === undefined) return out;\n const eligiblePaths: string[] = [];\n for (const prefix of replication.prefixes) {\n assertFolderPrefix(prefix);\n const path = prefix === \"/\" ? \"\" : prefix;\n if (\n requiresSecretsOptIn(input.primarySpaceId, prefix) &&\n replication.allowSecrets !== true\n ) {\n continue;\n }\n if (!hasUnrestrictedGetCoverage(input.entries, input.primarySpaceId, path)) {\n continue;\n }\n eligiblePaths.push(path);\n }\n if (\n eligiblePaths.length === replication.prefixes.length &&\n eligiblePaths.length > 0 &&\n hasUnrestrictedGetCoverage(input.entries, input.primarySpaceId, \"\")\n ) {\n return [{\n service: KV_SERVICE,\n space: input.primarySpaceId,\n path: \"\",\n actions: [KV_GET, KV_SYNC],\n }];\n }\n for (const path of eligiblePaths) {\n out.push({\n service: KV_SERVICE,\n space: input.primarySpaceId,\n path,\n actions: [KV_GET, KV_SYNC],\n });\n }\n return out;\n}\n"],"mappings":"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","names":["KV","isCapabilitySubset","KV_GET","KV","KV_SYNC","KV_SERVICE","isCapabilitySubset"]}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/replication/gated-network.ts
|
|
21
|
+
var gated_network_exports = {};
|
|
22
|
+
__export(gated_network_exports, {
|
|
23
|
+
createGatedNetworkReadThrough: () => createGatedNetworkReadThrough
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(gated_network_exports);
|
|
26
|
+
var import_prefix = require("@tinycloud/sdk-services/kv/replication/prefix");
|
|
27
|
+
function createGatedNetworkReadThrough(input) {
|
|
28
|
+
const report = (op, space, path, replica, reason, outcome, latencyMs) => input.emit({
|
|
29
|
+
type: "replication.read",
|
|
30
|
+
at: new Date(input.now()).toISOString(),
|
|
31
|
+
op,
|
|
32
|
+
space,
|
|
33
|
+
key: path,
|
|
34
|
+
replica,
|
|
35
|
+
source: reason === "cursor_restart" ? "none" : "network",
|
|
36
|
+
reason,
|
|
37
|
+
outcome,
|
|
38
|
+
latencyMs,
|
|
39
|
+
stalenessMs: null,
|
|
40
|
+
coverage: null,
|
|
41
|
+
authority: null,
|
|
42
|
+
...reason === "replica_gated" ? { code: "REPLICA_GATED" } : {}
|
|
43
|
+
});
|
|
44
|
+
const readNetwork = async (r, op, path, prefix, reason) => {
|
|
45
|
+
const started = input.now();
|
|
46
|
+
let outcome = "error";
|
|
47
|
+
try {
|
|
48
|
+
const result = await r.network();
|
|
49
|
+
outcome = result.ok ? "found" : op === "get" && reason === "not_covered" && "error" in result && result.error.code === "KV_NOT_FOUND" ? "not_found" : "error";
|
|
50
|
+
return result;
|
|
51
|
+
} finally {
|
|
52
|
+
report(op, r.space, path, prefix, reason, outcome, input.now() - started);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
const covered = (path) => input.prefixes.find((prefix) => (0, import_prefix.kvPrefixCovers)(prefix, path));
|
|
56
|
+
return {
|
|
57
|
+
get: (r) => {
|
|
58
|
+
if (r.space !== input.space) return r.network();
|
|
59
|
+
if (r.options?.source === "network") return readNetwork(r, "get", r.path, null, "NETWORK_REQUESTED");
|
|
60
|
+
const prefix = covered(r.path);
|
|
61
|
+
return readNetwork(r, "get", r.path, prefix ?? null, prefix === void 0 ? "not_covered" : "replica_gated");
|
|
62
|
+
},
|
|
63
|
+
list: (r) => {
|
|
64
|
+
if (r.space !== input.space) return r.network();
|
|
65
|
+
if (r.options?.source === "network") return readNetwork(r, "list", r.listPath, null, "NETWORK_REQUESTED");
|
|
66
|
+
if (r.options?.cursor?.startsWith("tcr1.")) {
|
|
67
|
+
report("list", r.space, r.listPath, null, "cursor_restart", "error", 0);
|
|
68
|
+
return Promise.resolve({ ok: false, error: {
|
|
69
|
+
code: "INVALID_INPUT",
|
|
70
|
+
message: "Local list cursor cannot be served; restart without the cursor",
|
|
71
|
+
service: "kv",
|
|
72
|
+
meta: { replication: "cursor_restart" }
|
|
73
|
+
} });
|
|
74
|
+
}
|
|
75
|
+
if (r.options?.cursor) return readNetwork(r, "list", r.listPath, null, "network_cursor");
|
|
76
|
+
if (r.options?.raw) return readNetwork(r, "list", r.listPath, null, "unsupported_option");
|
|
77
|
+
const prefix = covered(r.listPath);
|
|
78
|
+
return readNetwork(r, "list", r.listPath, prefix ?? null, prefix === void 0 ? "not_covered" : "replica_gated");
|
|
79
|
+
},
|
|
80
|
+
write: (r) => r.network(),
|
|
81
|
+
observeNetworkRequested: (r) => {
|
|
82
|
+
if (r.space === input.space) report(r.op, r.space, r.path, null, r.reason, r.outcome, r.latencyMs);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
87
|
+
0 && (module.exports = {
|
|
88
|
+
createGatedNetworkReadThrough
|
|
89
|
+
});
|
|
90
|
+
//# sourceMappingURL=gated-network.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/replication/gated-network.ts"],"sourcesContent":["import { kvPrefixCovers } from \"@tinycloud/sdk-services/kv/replication/prefix\";\nimport type { KVReadThrough, ReplicationEvent } from \"@tinycloud/sdk-services\";\n\n/** Loaded only after a refused restore. No replica storage, authority, or worker access. */\nexport function createGatedNetworkReadThrough(input: {\n space: string;\n prefixes: readonly string[];\n now: () => number;\n emit: (event: ReplicationEvent) => void;\n}): KVReadThrough {\n const report = (\n op: \"get\" | \"list\", space: string, path: string, replica: string | null,\n reason: \"replica_gated\" | \"not_covered\" | \"unsupported_option\" | \"network_cursor\" | \"NETWORK_REQUESTED\" | \"cursor_restart\",\n outcome: \"found\" | \"not_found\" | \"error\", latencyMs: number,\n ) => input.emit({\n type: \"replication.read\", at: new Date(input.now()).toISOString(), op, space, key: path,\n replica, source: reason === \"cursor_restart\" ? \"none\" : \"network\", reason, outcome, latencyMs,\n stalenessMs: null, coverage: null, authority: null,\n ...(reason === \"replica_gated\" ? { code: \"REPLICA_GATED\" } : {}),\n });\n const readNetwork = async <T extends { ok: boolean }>(\n r: { space: string; network: () => Promise<T> }, op: \"get\" | \"list\", path: string,\n prefix: string | null, reason: \"replica_gated\" | \"not_covered\" | \"unsupported_option\" | \"network_cursor\" | \"NETWORK_REQUESTED\",\n ): Promise<T> => {\n const started = input.now();\n let outcome: \"found\" | \"not_found\" | \"error\" = \"error\";\n try {\n const result = await r.network();\n outcome = result.ok ? \"found\" : op === \"get\" && reason === \"not_covered\" && \"error\" in result &&\n (result.error as { code?: string }).code === \"KV_NOT_FOUND\" ? \"not_found\" : \"error\";\n return result;\n } finally {\n report(op, r.space, path, prefix, reason, outcome, input.now() - started);\n }\n };\n const covered = (path: string) => input.prefixes.find((prefix) => kvPrefixCovers(prefix, path));\n return {\n get: (r) => {\n if (r.space !== input.space) return r.network();\n if (r.options?.source === \"network\") return readNetwork(r, \"get\", r.path, null, \"NETWORK_REQUESTED\");\n const prefix = covered(r.path);\n return readNetwork(r, \"get\", r.path, prefix ?? null, prefix === undefined ? \"not_covered\" : \"replica_gated\");\n },\n list: (r) => {\n if (r.space !== input.space) return r.network();\n if (r.options?.source === \"network\") return readNetwork(r, \"list\", r.listPath, null, \"NETWORK_REQUESTED\");\n if (r.options?.cursor?.startsWith(\"tcr1.\")) {\n report(\"list\", r.space, r.listPath, null, \"cursor_restart\", \"error\", 0);\n return Promise.resolve({ ok: false, error: {\n code: \"INVALID_INPUT\", message: \"Local list cursor cannot be served; restart without the cursor\",\n service: \"kv\", meta: { replication: \"cursor_restart\" },\n } });\n }\n if (r.options?.cursor) return readNetwork(r, \"list\", r.listPath, null, \"network_cursor\");\n if (r.options?.raw) return readNetwork(r, \"list\", r.listPath, null, \"unsupported_option\");\n const prefix = covered(r.listPath);\n return readNetwork(r, \"list\", r.listPath, prefix ?? null, prefix === undefined ? \"not_covered\" : \"replica_gated\");\n },\n write: (r) => r.network(),\n observeNetworkRequested: (r) => {\n if (r.space === input.space) report(r.op, r.space, r.path, null, r.reason, r.outcome, r.latencyMs);\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAA+B;AAIxB,SAAS,8BAA8B,OAK5B;AAChB,QAAM,SAAS,CACb,IAAoB,OAAe,MAAc,SACjD,QACA,SAA0C,cACvC,MAAM,KAAK;AAAA,IACd,MAAM;AAAA,IAAoB,IAAI,IAAI,KAAK,MAAM,IAAI,CAAC,EAAE,YAAY;AAAA,IAAG;AAAA,IAAI;AAAA,IAAO,KAAK;AAAA,IACnF;AAAA,IAAS,QAAQ,WAAW,mBAAmB,SAAS;AAAA,IAAW;AAAA,IAAQ;AAAA,IAAS;AAAA,IACpF,aAAa;AAAA,IAAM,UAAU;AAAA,IAAM,WAAW;AAAA,IAC9C,GAAI,WAAW,kBAAkB,EAAE,MAAM,gBAAgB,IAAI,CAAC;AAAA,EAChE,CAAC;AACD,QAAM,cAAc,OAClB,GAAiD,IAAoB,MACrE,QAAuB,WACR;AACf,UAAM,UAAU,MAAM,IAAI;AAC1B,QAAI,UAA2C;AAC/C,QAAI;AACF,YAAM,SAAS,MAAM,EAAE,QAAQ;AAC/B,gBAAU,OAAO,KAAK,UAAU,OAAO,SAAS,WAAW,iBAAiB,WAAW,UACpF,OAAO,MAA4B,SAAS,iBAAiB,cAAc;AAC9E,aAAO;AAAA,IACT,UAAE;AACA,aAAO,IAAI,EAAE,OAAO,MAAM,QAAQ,QAAQ,SAAS,MAAM,IAAI,IAAI,OAAO;AAAA,IAC1E;AAAA,EACF;AACA,QAAM,UAAU,CAAC,SAAiB,MAAM,SAAS,KAAK,CAAC,eAAW,8BAAe,QAAQ,IAAI,CAAC;AAC9F,SAAO;AAAA,IACL,KAAK,CAAC,MAAM;AACV,UAAI,EAAE,UAAU,MAAM,MAAO,QAAO,EAAE,QAAQ;AAC9C,UAAI,EAAE,SAAS,WAAW,UAAW,QAAO,YAAY,GAAG,OAAO,EAAE,MAAM,MAAM,mBAAmB;AACnG,YAAM,SAAS,QAAQ,EAAE,IAAI;AAC7B,aAAO,YAAY,GAAG,OAAO,EAAE,MAAM,UAAU,MAAM,WAAW,SAAY,gBAAgB,eAAe;AAAA,IAC7G;AAAA,IACA,MAAM,CAAC,MAAM;AACX,UAAI,EAAE,UAAU,MAAM,MAAO,QAAO,EAAE,QAAQ;AAC9C,UAAI,EAAE,SAAS,WAAW,UAAW,QAAO,YAAY,GAAG,QAAQ,EAAE,UAAU,MAAM,mBAAmB;AACxG,UAAI,EAAE,SAAS,QAAQ,WAAW,OAAO,GAAG;AAC1C,eAAO,QAAQ,EAAE,OAAO,EAAE,UAAU,MAAM,kBAAkB,SAAS,CAAC;AACtE,eAAO,QAAQ,QAAQ,EAAE,IAAI,OAAO,OAAO;AAAA,UACzC,MAAM;AAAA,UAAiB,SAAS;AAAA,UAChC,SAAS;AAAA,UAAM,MAAM,EAAE,aAAa,iBAAiB;AAAA,QACvD,EAAE,CAAC;AAAA,MACL;AACA,UAAI,EAAE,SAAS,OAAQ,QAAO,YAAY,GAAG,QAAQ,EAAE,UAAU,MAAM,gBAAgB;AACvF,UAAI,EAAE,SAAS,IAAK,QAAO,YAAY,GAAG,QAAQ,EAAE,UAAU,MAAM,oBAAoB;AACxF,YAAM,SAAS,QAAQ,EAAE,QAAQ;AACjC,aAAO,YAAY,GAAG,QAAQ,EAAE,UAAU,UAAU,MAAM,WAAW,SAAY,gBAAgB,eAAe;AAAA,IAClH;AAAA,IACA,OAAO,CAAC,MAAM,EAAE,QAAQ;AAAA,IACxB,yBAAyB,CAAC,MAAM;AAC9B,UAAI,EAAE,UAAU,MAAM,MAAO,QAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS;AAAA,IACnG;AAAA,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// src/replication/gated-network.ts
|
|
2
|
+
import { kvPrefixCovers } from "@tinycloud/sdk-services/kv/replication/prefix";
|
|
3
|
+
function createGatedNetworkReadThrough(input) {
|
|
4
|
+
const report = (op, space, path, replica, reason, outcome, latencyMs) => input.emit({
|
|
5
|
+
type: "replication.read",
|
|
6
|
+
at: new Date(input.now()).toISOString(),
|
|
7
|
+
op,
|
|
8
|
+
space,
|
|
9
|
+
key: path,
|
|
10
|
+
replica,
|
|
11
|
+
source: reason === "cursor_restart" ? "none" : "network",
|
|
12
|
+
reason,
|
|
13
|
+
outcome,
|
|
14
|
+
latencyMs,
|
|
15
|
+
stalenessMs: null,
|
|
16
|
+
coverage: null,
|
|
17
|
+
authority: null,
|
|
18
|
+
...reason === "replica_gated" ? { code: "REPLICA_GATED" } : {}
|
|
19
|
+
});
|
|
20
|
+
const readNetwork = async (r, op, path, prefix, reason) => {
|
|
21
|
+
const started = input.now();
|
|
22
|
+
let outcome = "error";
|
|
23
|
+
try {
|
|
24
|
+
const result = await r.network();
|
|
25
|
+
outcome = result.ok ? "found" : op === "get" && reason === "not_covered" && "error" in result && result.error.code === "KV_NOT_FOUND" ? "not_found" : "error";
|
|
26
|
+
return result;
|
|
27
|
+
} finally {
|
|
28
|
+
report(op, r.space, path, prefix, reason, outcome, input.now() - started);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
const covered = (path) => input.prefixes.find((prefix) => kvPrefixCovers(prefix, path));
|
|
32
|
+
return {
|
|
33
|
+
get: (r) => {
|
|
34
|
+
if (r.space !== input.space) return r.network();
|
|
35
|
+
if (r.options?.source === "network") return readNetwork(r, "get", r.path, null, "NETWORK_REQUESTED");
|
|
36
|
+
const prefix = covered(r.path);
|
|
37
|
+
return readNetwork(r, "get", r.path, prefix ?? null, prefix === void 0 ? "not_covered" : "replica_gated");
|
|
38
|
+
},
|
|
39
|
+
list: (r) => {
|
|
40
|
+
if (r.space !== input.space) return r.network();
|
|
41
|
+
if (r.options?.source === "network") return readNetwork(r, "list", r.listPath, null, "NETWORK_REQUESTED");
|
|
42
|
+
if (r.options?.cursor?.startsWith("tcr1.")) {
|
|
43
|
+
report("list", r.space, r.listPath, null, "cursor_restart", "error", 0);
|
|
44
|
+
return Promise.resolve({ ok: false, error: {
|
|
45
|
+
code: "INVALID_INPUT",
|
|
46
|
+
message: "Local list cursor cannot be served; restart without the cursor",
|
|
47
|
+
service: "kv",
|
|
48
|
+
meta: { replication: "cursor_restart" }
|
|
49
|
+
} });
|
|
50
|
+
}
|
|
51
|
+
if (r.options?.cursor) return readNetwork(r, "list", r.listPath, null, "network_cursor");
|
|
52
|
+
if (r.options?.raw) return readNetwork(r, "list", r.listPath, null, "unsupported_option");
|
|
53
|
+
const prefix = covered(r.listPath);
|
|
54
|
+
return readNetwork(r, "list", r.listPath, prefix ?? null, prefix === void 0 ? "not_covered" : "replica_gated");
|
|
55
|
+
},
|
|
56
|
+
write: (r) => r.network(),
|
|
57
|
+
observeNetworkRequested: (r) => {
|
|
58
|
+
if (r.space === input.space) report(r.op, r.space, r.path, null, r.reason, r.outcome, r.latencyMs);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export {
|
|
63
|
+
createGatedNetworkReadThrough
|
|
64
|
+
};
|
|
65
|
+
//# sourceMappingURL=gated-network.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/replication/gated-network.ts"],"sourcesContent":["import { kvPrefixCovers } from \"@tinycloud/sdk-services/kv/replication/prefix\";\nimport type { KVReadThrough, ReplicationEvent } from \"@tinycloud/sdk-services\";\n\n/** Loaded only after a refused restore. No replica storage, authority, or worker access. */\nexport function createGatedNetworkReadThrough(input: {\n space: string;\n prefixes: readonly string[];\n now: () => number;\n emit: (event: ReplicationEvent) => void;\n}): KVReadThrough {\n const report = (\n op: \"get\" | \"list\", space: string, path: string, replica: string | null,\n reason: \"replica_gated\" | \"not_covered\" | \"unsupported_option\" | \"network_cursor\" | \"NETWORK_REQUESTED\" | \"cursor_restart\",\n outcome: \"found\" | \"not_found\" | \"error\", latencyMs: number,\n ) => input.emit({\n type: \"replication.read\", at: new Date(input.now()).toISOString(), op, space, key: path,\n replica, source: reason === \"cursor_restart\" ? \"none\" : \"network\", reason, outcome, latencyMs,\n stalenessMs: null, coverage: null, authority: null,\n ...(reason === \"replica_gated\" ? { code: \"REPLICA_GATED\" } : {}),\n });\n const readNetwork = async <T extends { ok: boolean }>(\n r: { space: string; network: () => Promise<T> }, op: \"get\" | \"list\", path: string,\n prefix: string | null, reason: \"replica_gated\" | \"not_covered\" | \"unsupported_option\" | \"network_cursor\" | \"NETWORK_REQUESTED\",\n ): Promise<T> => {\n const started = input.now();\n let outcome: \"found\" | \"not_found\" | \"error\" = \"error\";\n try {\n const result = await r.network();\n outcome = result.ok ? \"found\" : op === \"get\" && reason === \"not_covered\" && \"error\" in result &&\n (result.error as { code?: string }).code === \"KV_NOT_FOUND\" ? \"not_found\" : \"error\";\n return result;\n } finally {\n report(op, r.space, path, prefix, reason, outcome, input.now() - started);\n }\n };\n const covered = (path: string) => input.prefixes.find((prefix) => kvPrefixCovers(prefix, path));\n return {\n get: (r) => {\n if (r.space !== input.space) return r.network();\n if (r.options?.source === \"network\") return readNetwork(r, \"get\", r.path, null, \"NETWORK_REQUESTED\");\n const prefix = covered(r.path);\n return readNetwork(r, \"get\", r.path, prefix ?? null, prefix === undefined ? \"not_covered\" : \"replica_gated\");\n },\n list: (r) => {\n if (r.space !== input.space) return r.network();\n if (r.options?.source === \"network\") return readNetwork(r, \"list\", r.listPath, null, \"NETWORK_REQUESTED\");\n if (r.options?.cursor?.startsWith(\"tcr1.\")) {\n report(\"list\", r.space, r.listPath, null, \"cursor_restart\", \"error\", 0);\n return Promise.resolve({ ok: false, error: {\n code: \"INVALID_INPUT\", message: \"Local list cursor cannot be served; restart without the cursor\",\n service: \"kv\", meta: { replication: \"cursor_restart\" },\n } });\n }\n if (r.options?.cursor) return readNetwork(r, \"list\", r.listPath, null, \"network_cursor\");\n if (r.options?.raw) return readNetwork(r, \"list\", r.listPath, null, \"unsupported_option\");\n const prefix = covered(r.listPath);\n return readNetwork(r, \"list\", r.listPath, prefix ?? null, prefix === undefined ? \"not_covered\" : \"replica_gated\");\n },\n write: (r) => r.network(),\n observeNetworkRequested: (r) => {\n if (r.space === input.space) report(r.op, r.space, r.path, null, r.reason, r.outcome, r.latencyMs);\n },\n };\n}\n"],"mappings":";AAAA,SAAS,sBAAsB;AAIxB,SAAS,8BAA8B,OAK5B;AAChB,QAAM,SAAS,CACb,IAAoB,OAAe,MAAc,SACjD,QACA,SAA0C,cACvC,MAAM,KAAK;AAAA,IACd,MAAM;AAAA,IAAoB,IAAI,IAAI,KAAK,MAAM,IAAI,CAAC,EAAE,YAAY;AAAA,IAAG;AAAA,IAAI;AAAA,IAAO,KAAK;AAAA,IACnF;AAAA,IAAS,QAAQ,WAAW,mBAAmB,SAAS;AAAA,IAAW;AAAA,IAAQ;AAAA,IAAS;AAAA,IACpF,aAAa;AAAA,IAAM,UAAU;AAAA,IAAM,WAAW;AAAA,IAC9C,GAAI,WAAW,kBAAkB,EAAE,MAAM,gBAAgB,IAAI,CAAC;AAAA,EAChE,CAAC;AACD,QAAM,cAAc,OAClB,GAAiD,IAAoB,MACrE,QAAuB,WACR;AACf,UAAM,UAAU,MAAM,IAAI;AAC1B,QAAI,UAA2C;AAC/C,QAAI;AACF,YAAM,SAAS,MAAM,EAAE,QAAQ;AAC/B,gBAAU,OAAO,KAAK,UAAU,OAAO,SAAS,WAAW,iBAAiB,WAAW,UACpF,OAAO,MAA4B,SAAS,iBAAiB,cAAc;AAC9E,aAAO;AAAA,IACT,UAAE;AACA,aAAO,IAAI,EAAE,OAAO,MAAM,QAAQ,QAAQ,SAAS,MAAM,IAAI,IAAI,OAAO;AAAA,IAC1E;AAAA,EACF;AACA,QAAM,UAAU,CAAC,SAAiB,MAAM,SAAS,KAAK,CAAC,WAAW,eAAe,QAAQ,IAAI,CAAC;AAC9F,SAAO;AAAA,IACL,KAAK,CAAC,MAAM;AACV,UAAI,EAAE,UAAU,MAAM,MAAO,QAAO,EAAE,QAAQ;AAC9C,UAAI,EAAE,SAAS,WAAW,UAAW,QAAO,YAAY,GAAG,OAAO,EAAE,MAAM,MAAM,mBAAmB;AACnG,YAAM,SAAS,QAAQ,EAAE,IAAI;AAC7B,aAAO,YAAY,GAAG,OAAO,EAAE,MAAM,UAAU,MAAM,WAAW,SAAY,gBAAgB,eAAe;AAAA,IAC7G;AAAA,IACA,MAAM,CAAC,MAAM;AACX,UAAI,EAAE,UAAU,MAAM,MAAO,QAAO,EAAE,QAAQ;AAC9C,UAAI,EAAE,SAAS,WAAW,UAAW,QAAO,YAAY,GAAG,QAAQ,EAAE,UAAU,MAAM,mBAAmB;AACxG,UAAI,EAAE,SAAS,QAAQ,WAAW,OAAO,GAAG;AAC1C,eAAO,QAAQ,EAAE,OAAO,EAAE,UAAU,MAAM,kBAAkB,SAAS,CAAC;AACtE,eAAO,QAAQ,QAAQ,EAAE,IAAI,OAAO,OAAO;AAAA,UACzC,MAAM;AAAA,UAAiB,SAAS;AAAA,UAChC,SAAS;AAAA,UAAM,MAAM,EAAE,aAAa,iBAAiB;AAAA,QACvD,EAAE,CAAC;AAAA,MACL;AACA,UAAI,EAAE,SAAS,OAAQ,QAAO,YAAY,GAAG,QAAQ,EAAE,UAAU,MAAM,gBAAgB;AACvF,UAAI,EAAE,SAAS,IAAK,QAAO,YAAY,GAAG,QAAQ,EAAE,UAAU,MAAM,oBAAoB;AACxF,YAAM,SAAS,QAAQ,EAAE,QAAQ;AACjC,aAAO,YAAY,GAAG,QAAQ,EAAE,UAAU,UAAU,MAAM,WAAW,SAAY,gBAAgB,eAAe;AAAA,IAClH;AAAA,IACA,OAAO,CAAC,MAAM,EAAE,QAAQ;AAAA,IACxB,yBAAyB,CAAC,MAAM;AAC9B,UAAI,EAAE,UAAU,MAAM,MAAO,QAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS;AAAA,IACnG;AAAA,EACF;AACF;","names":[]}
|