@tinycloud/node-sdk 3.2.0-beta.3 → 3.2.0-beta.5

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.
@@ -34,7 +34,7 @@ var import_sdk_core2 = require("@tinycloud/sdk-core");
34
34
  var import_replica = require("@tinycloud/replica");
35
35
 
36
36
  // src/replication/config.ts
37
- var import_sdk_services = require("@tinycloud/sdk-services");
37
+ var import_prefix = require("@tinycloud/sdk-services/kv/replication/prefix");
38
38
  function assertValidReplicationConfig(replication) {
39
39
  if (replication === void 0 || replication.enabled !== true) return;
40
40
  if (replication.storage === void 0 || replication.storage === null) {
@@ -44,19 +44,21 @@ function assertValidReplicationConfig(replication) {
44
44
  throw new TypeError("replication.prefixes must be a non-empty array");
45
45
  }
46
46
  for (const prefix of replication.prefixes) {
47
- if (typeof prefix !== "string" || prefix === "") {
48
- throw new TypeError("replication.prefixes must not contain an empty prefix");
47
+ try {
48
+ (0, import_prefix.assertFolderPrefix)(prefix);
49
+ } catch (error) {
50
+ throw new TypeError(error instanceof Error ? error.message : String(error));
49
51
  }
50
52
  }
51
53
  for (const [index, prefix] of replication.prefixes.entries()) {
52
54
  for (const other of replication.prefixes.slice(index + 1)) {
53
- if ((0, import_sdk_services.kvPrefixCovers)(prefix, other) || (0, import_sdk_services.kvPrefixCovers)(other, prefix)) {
55
+ if ((0, import_prefix.kvPrefixCovers)(prefix, other) || (0, import_prefix.kvPrefixCovers)(other, prefix)) {
54
56
  throw new TypeError(
55
57
  `replication.prefixes must not overlap: ${JSON.stringify(prefix)} and ${JSON.stringify(other)}`
56
58
  );
57
59
  }
58
60
  }
59
- if (prefix.split("/", 1)[0] === "vault" && replication.allowSecrets !== true) {
61
+ if ((prefix === "/" || prefix.split("/", 1)[0] === "vault") && replication.allowSecrets !== true) {
60
62
  throw new TypeError(
61
63
  `replication prefix ${JSON.stringify(prefix)} overlaps the vault namespace; pass allowSecrets to opt in`
62
64
  );
@@ -66,14 +68,14 @@ function assertValidReplicationConfig(replication) {
66
68
 
67
69
  // src/replication/authority-sign-in.ts
68
70
  var import_sdk_core = require("@tinycloud/sdk-core");
69
- var import_sdk_services2 = require("@tinycloud/sdk-services");
71
+ var import_sdk_services = require("@tinycloud/sdk-services");
72
+ var import_prefix2 = require("@tinycloud/sdk-services/kv/replication/prefix");
70
73
  var KV_GET = import_sdk_core.KV.GET;
71
74
  var KV_SYNC = import_sdk_core.KV.SYNC;
72
75
  var KV_SERVICE = "tinycloud.kv";
73
76
  function hasUnrestrictedGetCoverage(entries, spaceId, prefix) {
74
- const requiredPaths = (0, import_sdk_core.grantPathsForSelector)(prefix);
75
77
  return (0, import_sdk_core.isCapabilitySubset)(
76
- requiredPaths.map((path) => ({ service: KV_SERVICE, space: spaceId, path, actions: [KV_GET] })),
78
+ [{ service: KV_SERVICE, space: spaceId, path: prefix, actions: [KV_GET] }],
77
79
  [...entries]
78
80
  ).subset;
79
81
  }
@@ -81,21 +83,33 @@ function augmentSignInEntriesWithReplication(input) {
81
83
  const out = [];
82
84
  const replication = input.replication;
83
85
  if (replication === void 0) return out;
86
+ const eligiblePaths = [];
84
87
  for (const prefix of replication.prefixes) {
85
- if ((0, import_sdk_services2.requiresSecretsOptIn)(input.primarySpaceId, prefix) && replication.allowSecrets !== true) {
88
+ (0, import_prefix2.assertFolderPrefix)(prefix);
89
+ const path = prefix === "/" ? "" : prefix;
90
+ if ((0, import_sdk_services.requiresSecretsOptIn)(input.primarySpaceId, prefix) && replication.allowSecrets !== true) {
86
91
  continue;
87
92
  }
88
- if (!hasUnrestrictedGetCoverage(input.entries, input.primarySpaceId, prefix)) {
93
+ if (!hasUnrestrictedGetCoverage(input.entries, input.primarySpaceId, path)) {
89
94
  continue;
90
95
  }
91
- for (const path of (0, import_sdk_core.grantPathsForSelector)(prefix)) {
92
- out.push({
93
- service: KV_SERVICE,
94
- space: input.primarySpaceId,
95
- path,
96
- actions: [KV_GET, KV_SYNC]
97
- });
98
- }
96
+ eligiblePaths.push(path);
97
+ }
98
+ if (eligiblePaths.length === replication.prefixes.length && eligiblePaths.length > 0 && hasUnrestrictedGetCoverage(input.entries, input.primarySpaceId, "")) {
99
+ return [{
100
+ service: KV_SERVICE,
101
+ space: input.primarySpaceId,
102
+ path: "",
103
+ actions: [KV_GET, KV_SYNC]
104
+ }];
105
+ }
106
+ for (const path of eligiblePaths) {
107
+ out.push({
108
+ service: KV_SERVICE,
109
+ space: input.primarySpaceId,
110
+ path,
111
+ actions: [KV_GET, KV_SYNC]
112
+ });
99
113
  }
100
114
  return out;
101
115
  }
@@ -106,12 +120,12 @@ var KV_GET2 = import_sdk_core2.KV.GET;
106
120
  var KV_SYNC2 = import_sdk_core2.KV.SYNC;
107
121
  var KV_SERVICE2 = "tinycloud.kv";
108
122
  function replicationEntries(spaceId, prefix) {
109
- return (0, import_sdk_core2.grantPathsForSelector)(prefix).map((path) => ({
123
+ return [{
110
124
  service: KV_SERVICE2,
111
125
  space: spaceId,
112
- path,
126
+ path: prefix === "/" ? "" : prefix,
113
127
  actions: [KV_GET2, KV_SYNC2]
114
- }));
128
+ }];
115
129
  }
116
130
  function caveatBranchesUnconstrained(value) {
117
131
  if (value === void 0 || value === null) return true;
@@ -126,28 +140,28 @@ function caveatBranchesUnconstrained(value) {
126
140
  }
127
141
  }
128
142
  function kvPathInResource(resource, space) {
143
+ for (const root of [`${space}/kv`, `tinycloud://${space}/kv`]) {
144
+ if (resource === root || resource === `${root}/`) return "/";
145
+ }
129
146
  for (const base of [`${space}/kv/`, `tinycloud://${space}/kv/`]) {
130
147
  if (resource.startsWith(base)) return resource.slice(base.length);
131
148
  }
132
149
  return void 0;
133
150
  }
134
151
  function ucanAttCovers(att, space, prefix, ability, options) {
135
- const requiredPaths = prefix.endsWith("/") ? [prefix] : [prefix, `${prefix}/`];
136
- return requiredPaths.every((requestedPath) => {
137
- for (const [resource, abilities] of Object.entries(att)) {
138
- const path = kvPathInResource(resource, space);
139
- if (path === void 0) continue;
140
- for (const [granted, caveats] of Object.entries(abilities)) {
141
- if (!(0, import_sdk_core2.actionContains)(granted, ability)) continue;
142
- if (!(0, import_sdk_core2.isCapabilitySubset)(
143
- [{ service: KV_SERVICE2, space, path: requestedPath, actions: [ability] }],
144
- [{ service: KV_SERVICE2, space, path, actions: [granted] }]
145
- ).subset) continue;
146
- if (options?.ignoreCaveats === true || caveatBranchesUnconstrained(caveats)) return true;
147
- }
152
+ for (const [resource, abilities] of Object.entries(att)) {
153
+ const path = kvPathInResource(resource, space);
154
+ if (path === void 0) continue;
155
+ for (const [granted, caveats] of Object.entries(abilities)) {
156
+ if (!(0, import_sdk_core2.actionContains)(granted, ability)) continue;
157
+ if (!(0, import_sdk_core2.isCapabilitySubset)(
158
+ [{ service: KV_SERVICE2, space, path: prefix, actions: [ability] }],
159
+ [{ service: KV_SERVICE2, space, path, actions: [granted] }]
160
+ ).subset) continue;
161
+ if (options?.ignoreCaveats === true || caveatBranchesUnconstrained(caveats)) return true;
148
162
  }
149
- return false;
150
- });
163
+ }
164
+ return false;
151
165
  }
152
166
  function ucanAttUnconstrainedFor(att, space, prefix) {
153
167
  return ucanAttCovers(att, space, prefix, KV_GET2) && ucanAttCovers(att, space, prefix, KV_SYNC2);
@@ -163,7 +177,7 @@ function replicationScopeFromSignedSession(input) {
163
177
  const bareDid = (value) => value.split("#", 1)[0];
164
178
  if (grant.cid !== input.delegationCid || bareDid(grant.audience) !== bareDid(input.verificationMethod)) return void 0;
165
179
  for (const resource of Object.keys(grant.att)) {
166
- const space = resource.match(/^(?:tinycloud:\/\/)?(.+?)\/kv\//)?.[1];
180
+ const space = resource.match(/^(?:tinycloud:\/\/)?(.+?)\/kv(?:\/|$)/)?.[1];
167
181
  if (space !== input.persistedSpace) continue;
168
182
  const owner = (0, import_sdk_core2.parseSpaceUri)(space);
169
183
  if (owner?.owner && owner.address && owner.chainId) return { space, principal: owner.owner };
@@ -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 grantPathsForSelector,\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 prefix of the session space. */\nfunction replicationEntries(spaceId: string, prefix: string): PermissionEntry[] {\n return grantPathsForSelector(prefix).map((path) => ({\n service: KV_SERVICE,\n space: spaceId,\n path,\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 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 selected namespace, with\n * each grant unrestricted (no caveats). A bare selector requires both its\n * exact key and its slash descendants; a trailing-slash selector is one range.\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 const requiredPaths = prefix.endsWith(\"/\") ? [prefix] : [prefix, `${prefix}/`];\n return requiredPaths.every((requestedPath) => {\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: requestedPath, 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\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 { kvPrefixCovers, type ReplicationOptions } from \"@tinycloud/sdk-services\";\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 if (typeof prefix !== \"string\" || prefix === \"\") {\n throw new TypeError(\"replication.prefixes must not contain an empty prefix\");\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.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 grantPathsForSelector,\n KV,\n isCapabilitySubset,\n type PermissionEntry,\n type ReplicationOptions,\n} from \"@tinycloud/sdk-core\";\nimport { requiresSecretsOptIn } from \"@tinycloud/sdk-services\";\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 const requiredPaths = grantPathsForSelector(prefix);\n return isCapabilitySubset(\n requiredPaths.map((path) => ({ service: KV_SERVICE, space: spaceId, path, 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 for (const prefix of replication.prefixes) {\n if (\n requiresSecretsOptIn(input.primarySpaceId, prefix) &&\n replication.allowSecrets !== true\n ) {\n continue;\n }\n if (!hasUnrestrictedGetCoverage(input.entries, input.primarySpaceId, prefix)) {\n continue;\n }\n for (const path of grantPathsForSelector(prefix)) {\n out.push({\n service: KV_SERVICE,\n space: input.primarySpaceId,\n path,\n actions: [KV_GET, KV_SYNC],\n });\n }\n }\n return out;\n}\n"],"mappings":"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","names":["import_sdk_core","import_sdk_services","replicaParseUcanGrant","KV_GET","KV_SYNC","KV_SERVICE"]}
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":"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","names":["import_sdk_core","assertPureFolderPrefix","import_prefix","replicaParseUcanGrant","KV_GET","KV_SYNC","KV_SERVICE"]}
@@ -1,3 +1,3 @@
1
1
  import '@tinycloud/sdk-core';
2
2
  import '@tinycloud/sdk-services';
3
- export { A as AUTHORITY_EXPIRY_MARGIN_MS, D as DelegationPlanResult, R as ReplicationAuthorityHost, a as ReplicationAuthoritySession, b as assertValidReplicationConfig, c as augmentSignInEntriesWithReplication, d as createReplicationAuthority, h as hasUnrestrictedGetCoverage, r as replicationScopeFromSignedSession, u as ucanAttCovers, e as ucanAttUnconstrainedFor } from '../authority-mTQEgN56.cjs';
3
+ export { A as AUTHORITY_EXPIRY_MARGIN_MS, D as DelegationPlanResult, R as ReplicationAuthorityHost, a as ReplicationAuthoritySession, b as assertValidReplicationConfig, c as augmentSignInEntriesWithReplication, d as createReplicationAuthority, h as hasUnrestrictedGetCoverage, r as replicationScopeFromSignedSession, u as ucanAttCovers, e as ucanAttUnconstrainedFor } from '../authority-DDGtccBK.cjs';
@@ -1,3 +1,3 @@
1
1
  import '@tinycloud/sdk-core';
2
2
  import '@tinycloud/sdk-services';
3
- export { A as AUTHORITY_EXPIRY_MARGIN_MS, D as DelegationPlanResult, R as ReplicationAuthorityHost, a as ReplicationAuthoritySession, b as assertValidReplicationConfig, c as augmentSignInEntriesWithReplication, d as createReplicationAuthority, h as hasUnrestrictedGetCoverage, r as replicationScopeFromSignedSession, u as ucanAttCovers, e as ucanAttUnconstrainedFor } from '../authority-mTQEgN56.js';
3
+ export { A as AUTHORITY_EXPIRY_MARGIN_MS, D as DelegationPlanResult, R as ReplicationAuthorityHost, a as ReplicationAuthoritySession, b as assertValidReplicationConfig, c as augmentSignInEntriesWithReplication, d as createReplicationAuthority, h as hasUnrestrictedGetCoverage, r as replicationScopeFromSignedSession, u as ucanAttCovers, e as ucanAttUnconstrainedFor } from '../authority-DDGtccBK.js';
@@ -1,6 +1,5 @@
1
1
  // src/replication/authority.ts
2
2
  import {
3
- grantPathsForSelector as grantPathsForSelector2,
4
3
  actionContains,
5
4
  KV as KV2,
6
5
  canonicalizeRecapCaveats,
@@ -10,7 +9,7 @@ import {
10
9
  import { parseUcanGrant as replicaParseUcanGrant } from "@tinycloud/replica";
11
10
 
12
11
  // src/replication/config.ts
13
- import { kvPrefixCovers } from "@tinycloud/sdk-services";
12
+ import { assertFolderPrefix as assertPureFolderPrefix, kvPrefixCovers } from "@tinycloud/sdk-services/kv/replication/prefix";
14
13
  function assertValidReplicationConfig(replication) {
15
14
  if (replication === void 0 || replication.enabled !== true) return;
16
15
  if (replication.storage === void 0 || replication.storage === null) {
@@ -20,8 +19,10 @@ function assertValidReplicationConfig(replication) {
20
19
  throw new TypeError("replication.prefixes must be a non-empty array");
21
20
  }
22
21
  for (const prefix of replication.prefixes) {
23
- if (typeof prefix !== "string" || prefix === "") {
24
- throw new TypeError("replication.prefixes must not contain an empty prefix");
22
+ try {
23
+ assertPureFolderPrefix(prefix);
24
+ } catch (error) {
25
+ throw new TypeError(error instanceof Error ? error.message : String(error));
25
26
  }
26
27
  }
27
28
  for (const [index, prefix] of replication.prefixes.entries()) {
@@ -32,7 +33,7 @@ function assertValidReplicationConfig(replication) {
32
33
  );
33
34
  }
34
35
  }
35
- if (prefix.split("/", 1)[0] === "vault" && replication.allowSecrets !== true) {
36
+ if ((prefix === "/" || prefix.split("/", 1)[0] === "vault") && replication.allowSecrets !== true) {
36
37
  throw new TypeError(
37
38
  `replication prefix ${JSON.stringify(prefix)} overlaps the vault namespace; pass allowSecrets to opt in`
38
39
  );
@@ -42,18 +43,17 @@ function assertValidReplicationConfig(replication) {
42
43
 
43
44
  // src/replication/authority-sign-in.ts
44
45
  import {
45
- grantPathsForSelector,
46
46
  KV,
47
47
  isCapabilitySubset
48
48
  } from "@tinycloud/sdk-core";
49
49
  import { requiresSecretsOptIn } from "@tinycloud/sdk-services";
50
+ import { assertFolderPrefix } from "@tinycloud/sdk-services/kv/replication/prefix";
50
51
  var KV_GET = KV.GET;
51
52
  var KV_SYNC = KV.SYNC;
52
53
  var KV_SERVICE = "tinycloud.kv";
53
54
  function hasUnrestrictedGetCoverage(entries, spaceId, prefix) {
54
- const requiredPaths = grantPathsForSelector(prefix);
55
55
  return isCapabilitySubset(
56
- requiredPaths.map((path) => ({ service: KV_SERVICE, space: spaceId, path, actions: [KV_GET] })),
56
+ [{ service: KV_SERVICE, space: spaceId, path: prefix, actions: [KV_GET] }],
57
57
  [...entries]
58
58
  ).subset;
59
59
  }
@@ -61,21 +61,33 @@ function augmentSignInEntriesWithReplication(input) {
61
61
  const out = [];
62
62
  const replication = input.replication;
63
63
  if (replication === void 0) return out;
64
+ const eligiblePaths = [];
64
65
  for (const prefix of replication.prefixes) {
66
+ assertFolderPrefix(prefix);
67
+ const path = prefix === "/" ? "" : prefix;
65
68
  if (requiresSecretsOptIn(input.primarySpaceId, prefix) && replication.allowSecrets !== true) {
66
69
  continue;
67
70
  }
68
- if (!hasUnrestrictedGetCoverage(input.entries, input.primarySpaceId, prefix)) {
71
+ if (!hasUnrestrictedGetCoverage(input.entries, input.primarySpaceId, path)) {
69
72
  continue;
70
73
  }
71
- for (const path of grantPathsForSelector(prefix)) {
72
- out.push({
73
- service: KV_SERVICE,
74
- space: input.primarySpaceId,
75
- path,
76
- actions: [KV_GET, KV_SYNC]
77
- });
78
- }
74
+ eligiblePaths.push(path);
75
+ }
76
+ if (eligiblePaths.length === replication.prefixes.length && eligiblePaths.length > 0 && hasUnrestrictedGetCoverage(input.entries, input.primarySpaceId, "")) {
77
+ return [{
78
+ service: KV_SERVICE,
79
+ space: input.primarySpaceId,
80
+ path: "",
81
+ actions: [KV_GET, KV_SYNC]
82
+ }];
83
+ }
84
+ for (const path of eligiblePaths) {
85
+ out.push({
86
+ service: KV_SERVICE,
87
+ space: input.primarySpaceId,
88
+ path,
89
+ actions: [KV_GET, KV_SYNC]
90
+ });
79
91
  }
80
92
  return out;
81
93
  }
@@ -86,12 +98,12 @@ var KV_GET2 = KV2.GET;
86
98
  var KV_SYNC2 = KV2.SYNC;
87
99
  var KV_SERVICE2 = "tinycloud.kv";
88
100
  function replicationEntries(spaceId, prefix) {
89
- return grantPathsForSelector2(prefix).map((path) => ({
101
+ return [{
90
102
  service: KV_SERVICE2,
91
103
  space: spaceId,
92
- path,
104
+ path: prefix === "/" ? "" : prefix,
93
105
  actions: [KV_GET2, KV_SYNC2]
94
- }));
106
+ }];
95
107
  }
96
108
  function caveatBranchesUnconstrained(value) {
97
109
  if (value === void 0 || value === null) return true;
@@ -106,28 +118,28 @@ function caveatBranchesUnconstrained(value) {
106
118
  }
107
119
  }
108
120
  function kvPathInResource(resource, space) {
121
+ for (const root of [`${space}/kv`, `tinycloud://${space}/kv`]) {
122
+ if (resource === root || resource === `${root}/`) return "/";
123
+ }
109
124
  for (const base of [`${space}/kv/`, `tinycloud://${space}/kv/`]) {
110
125
  if (resource.startsWith(base)) return resource.slice(base.length);
111
126
  }
112
127
  return void 0;
113
128
  }
114
129
  function ucanAttCovers(att, space, prefix, ability, options) {
115
- const requiredPaths = prefix.endsWith("/") ? [prefix] : [prefix, `${prefix}/`];
116
- return requiredPaths.every((requestedPath) => {
117
- for (const [resource, abilities] of Object.entries(att)) {
118
- const path = kvPathInResource(resource, space);
119
- if (path === void 0) continue;
120
- for (const [granted, caveats] of Object.entries(abilities)) {
121
- if (!actionContains(granted, ability)) continue;
122
- if (!isCapabilitySubset2(
123
- [{ service: KV_SERVICE2, space, path: requestedPath, actions: [ability] }],
124
- [{ service: KV_SERVICE2, space, path, actions: [granted] }]
125
- ).subset) continue;
126
- if (options?.ignoreCaveats === true || caveatBranchesUnconstrained(caveats)) return true;
127
- }
130
+ for (const [resource, abilities] of Object.entries(att)) {
131
+ const path = kvPathInResource(resource, space);
132
+ if (path === void 0) continue;
133
+ for (const [granted, caveats] of Object.entries(abilities)) {
134
+ if (!actionContains(granted, ability)) continue;
135
+ if (!isCapabilitySubset2(
136
+ [{ service: KV_SERVICE2, space, path: prefix, actions: [ability] }],
137
+ [{ service: KV_SERVICE2, space, path, actions: [granted] }]
138
+ ).subset) continue;
139
+ if (options?.ignoreCaveats === true || caveatBranchesUnconstrained(caveats)) return true;
128
140
  }
129
- return false;
130
- });
141
+ }
142
+ return false;
131
143
  }
132
144
  function ucanAttUnconstrainedFor(att, space, prefix) {
133
145
  return ucanAttCovers(att, space, prefix, KV_GET2) && ucanAttCovers(att, space, prefix, KV_SYNC2);
@@ -143,7 +155,7 @@ function replicationScopeFromSignedSession(input) {
143
155
  const bareDid = (value) => value.split("#", 1)[0];
144
156
  if (grant.cid !== input.delegationCid || bareDid(grant.audience) !== bareDid(input.verificationMethod)) return void 0;
145
157
  for (const resource of Object.keys(grant.att)) {
146
- const space = resource.match(/^(?:tinycloud:\/\/)?(.+?)\/kv\//)?.[1];
158
+ const space = resource.match(/^(?:tinycloud:\/\/)?(.+?)\/kv(?:\/|$)/)?.[1];
147
159
  if (space !== input.persistedSpace) continue;
148
160
  const owner = parseSpaceUri(space);
149
161
  if (owner?.owner && owner.address && owner.chainId) return { space, principal: owner.owner };
@@ -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 grantPathsForSelector,\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 prefix of the session space. */\nfunction replicationEntries(spaceId: string, prefix: string): PermissionEntry[] {\n return grantPathsForSelector(prefix).map((path) => ({\n service: KV_SERVICE,\n space: spaceId,\n path,\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 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 selected namespace, with\n * each grant unrestricted (no caveats). A bare selector requires both its\n * exact key and its slash descendants; a trailing-slash selector is one range.\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 const requiredPaths = prefix.endsWith(\"/\") ? [prefix] : [prefix, `${prefix}/`];\n return requiredPaths.every((requestedPath) => {\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: requestedPath, 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\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 { kvPrefixCovers, type ReplicationOptions } from \"@tinycloud/sdk-services\";\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 if (typeof prefix !== \"string\" || prefix === \"\") {\n throw new TypeError(\"replication.prefixes must not contain an empty prefix\");\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.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 grantPathsForSelector,\n KV,\n isCapabilitySubset,\n type PermissionEntry,\n type ReplicationOptions,\n} from \"@tinycloud/sdk-core\";\nimport { requiresSecretsOptIn } from \"@tinycloud/sdk-services\";\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 const requiredPaths = grantPathsForSelector(prefix);\n return isCapabilitySubset(\n requiredPaths.map((path) => ({ service: KV_SERVICE, space: spaceId, path, 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 for (const prefix of replication.prefixes) {\n if (\n requiresSecretsOptIn(input.primarySpaceId, prefix) &&\n replication.allowSecrets !== true\n ) {\n continue;\n }\n if (!hasUnrestrictedGetCoverage(input.entries, input.primarySpaceId, prefix)) {\n continue;\n }\n for (const path of grantPathsForSelector(prefix)) {\n out.push({\n service: KV_SERVICE,\n space: input.primarySpaceId,\n path,\n actions: [KV_GET, KV_SYNC],\n });\n }\n }\n return out;\n}\n"],"mappings":"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","names":["grantPathsForSelector","KV","isCapabilitySubset","KV_GET","KV","KV_SYNC","KV_SERVICE","grantPathsForSelector","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 */\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":"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","names":["KV","isCapabilitySubset","KV_GET","KV","KV_SYNC","KV_SERVICE","isCapabilitySubset"]}
@@ -172,6 +172,7 @@ var ReplicationRuntime = class {
172
172
  scheduler: this.clock,
173
173
  emit,
174
174
  fetch: context.fetch,
175
+ nodeFeatures: input.nodeFeatures,
175
176
  previous: previousClose
176
177
  });
177
178
  this.current = controller;