@tinycloud/node-sdk 3.4.0 → 3.5.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -17430,7 +17430,7 @@ __export(authority_exports, {
17430
17430
  });
17431
17431
  import { SiweMessage as SiweMessage4 } from "siwe";
17432
17432
  import {
17433
- actionContains,
17433
+ actionContains as actionContains2,
17434
17434
  KV as KV4,
17435
17435
  canonicalizeRecapCaveats as canonicalizeRecapCaveats3,
17436
17436
  isCapabilitySubset as isCapabilitySubset3,
@@ -17546,7 +17546,7 @@ function ucanAttCovers(att, space, prefix, ability, options) {
17546
17546
  const path = kvPathInResource(resource, space);
17547
17547
  if (path === void 0) continue;
17548
17548
  for (const [granted, caveats] of Object.entries(abilities)) {
17549
- if (!actionContains(granted, ability)) continue;
17549
+ if (!actionContains2(granted, ability)) continue;
17550
17550
  if (!isCapabilitySubset3(
17551
17551
  [{ service: KV_SERVICE2, space, path: prefix, actions: [ability] }],
17552
17552
  [{ service: KV_SERVICE2, space, path, actions: [granted] }]
@@ -17936,6 +17936,173 @@ var PrivateKeySigner = class {
17936
17936
  }
17937
17937
  };
17938
17938
 
17939
+ // src/recordingServiceContext.ts
17940
+ import {
17941
+ ServiceContext
17942
+ } from "@tinycloud/sdk-services";
17943
+ import {
17944
+ buildRequestEvent,
17945
+ invocationMetaOf,
17946
+ requestBytesOf
17947
+ } from "@tinycloud/sdk-services/telemetry/requestLog";
17948
+
17949
+ // src/recordingRegistry.ts
17950
+ var unrecordedFetches = /* @__PURE__ */ new WeakMap();
17951
+ function registerRecording(context, unrecordedFetch) {
17952
+ unrecordedFetches.set(context, unrecordedFetch);
17953
+ }
17954
+ function unrecordedFetchOf(context) {
17955
+ const unrecordedFetch = unrecordedFetches.get(context);
17956
+ return unrecordedFetch ? unrecordedFetch() : context.fetch;
17957
+ }
17958
+
17959
+ // src/recordingServiceContext.ts
17960
+ var SYNC_ABILITY = "tinycloud.kv/sync";
17961
+ var INFO_PATH = "/info";
17962
+ function outcomeCodeOf(response) {
17963
+ return response === void 0 ? "ERROR" : response.ok ? "ok" : `HTTP_${response.status}`;
17964
+ }
17965
+ function replicationOp(url, meta) {
17966
+ if (meta !== void 0) return meta.action === SYNC_ABILITY ? `${meta.service}/sync` : void 0;
17967
+ return isInfoUrl(url) ? "sync/info" : void 0;
17968
+ }
17969
+ function isInfoUrl(url) {
17970
+ try {
17971
+ return new URL(url).pathname.replace(/\/+$/, "") === INFO_PATH;
17972
+ } catch {
17973
+ return false;
17974
+ }
17975
+ }
17976
+ function replicationEvent(sample, op) {
17977
+ return buildRequestEvent({
17978
+ service: "sync",
17979
+ op,
17980
+ url: sample.url,
17981
+ bytes: requestBytesOf(sample.init?.body, sample.response?.headers.get("content-length")),
17982
+ durationMs: performance.now() - sample.clock,
17983
+ attempts: sample.attempts ?? 1,
17984
+ ok: sample.response?.ok === true,
17985
+ code: outcomeCodeOf(sample.response),
17986
+ at: sample.at
17987
+ });
17988
+ }
17989
+ function createServiceContext(config) {
17990
+ const { requestSink, ...base2 } = config;
17991
+ return requestSink === void 0 ? new ServiceContext(base2) : new RecordingServiceContext(base2, requestSink);
17992
+ }
17993
+ var RecordingServiceContext = class extends ServiceContext {
17994
+ constructor(config, sink) {
17995
+ const pending = {};
17996
+ super({
17997
+ ...config,
17998
+ invoke: rememberInvoke(config.invoke, sink, pending),
17999
+ invokeAny: config.invokeAny ? rememberInvokeAny(config.invokeAny, sink, pending) : void 0
18000
+ });
18001
+ this.requestSink = sink;
18002
+ this.pending = pending;
18003
+ registerRecording(this, () => this.unrecordedFetch);
18004
+ }
18005
+ /** Per-call recording; `_fetch` is read at access time, so overrides apply. */
18006
+ get fetch() {
18007
+ const raw = super.fetch;
18008
+ return (url, init2) => this.recordCall(raw, url, init2);
18009
+ }
18010
+ /** Transport without per-call recording. */
18011
+ get unrecordedFetch() {
18012
+ return super.fetch;
18013
+ }
18014
+ /**
18015
+ * One event per logical request. The first resolved attempt's init is the logged request and
18016
+ * `attempts` counts resolved builds. A retired context throws before the first build and records
18017
+ * nothing, as master does.
18018
+ */
18019
+ async invokeWithRetry(url, build) {
18020
+ const at = Date.now();
18021
+ const clock = performance.now();
18022
+ let reached = false;
18023
+ let attempts = 0;
18024
+ let first;
18025
+ let firstMeta;
18026
+ let response;
18027
+ const attempt = async () => {
18028
+ reached = true;
18029
+ this.pending.meta = void 0;
18030
+ const built = build();
18031
+ const meta = this.pending.meta;
18032
+ const init2 = await built;
18033
+ attempts += 1;
18034
+ if (first === void 0) {
18035
+ first = init2;
18036
+ firstMeta = meta;
18037
+ }
18038
+ return init2;
18039
+ };
18040
+ try {
18041
+ response = await super.invokeWithRetry(url, attempt);
18042
+ return response;
18043
+ } finally {
18044
+ if (reached) {
18045
+ this.observeAttempt({ url, init: first, response, meta: firstMeta, at, clock, attempts });
18046
+ }
18047
+ }
18048
+ }
18049
+ async recordCall(fetchFn, url, init2) {
18050
+ const at = Date.now();
18051
+ const clock = performance.now();
18052
+ let response;
18053
+ try {
18054
+ response = await fetchFn(url, init2);
18055
+ return response;
18056
+ } finally {
18057
+ this.observeAttempt({ url, init: init2, response, at, clock });
18058
+ }
18059
+ }
18060
+ observeAttempt(sample) {
18061
+ try {
18062
+ const replication = replicationOp(sample.url, sample.meta ?? invocationMetaOf(sample.init?.headers));
18063
+ if (replication !== void 0) {
18064
+ this.requestSink.record(replicationEvent(sample, replication));
18065
+ return;
18066
+ }
18067
+ const headers = sample.init?.headers;
18068
+ if (sample.meta !== void 0 && typeof headers === "object" && headers !== null) {
18069
+ this.requestSink.remember(headers, sample.meta);
18070
+ }
18071
+ this.requestSink.observe({
18072
+ url: sample.url,
18073
+ requestHeaders: headers,
18074
+ requestBody: sample.init?.body,
18075
+ responseContentLength: sample.response?.headers.get("content-length"),
18076
+ ok: sample.response?.ok === true,
18077
+ code: outcomeCodeOf(sample.response),
18078
+ durationMs: performance.now() - sample.clock,
18079
+ attempts: sample.attempts ?? 1,
18080
+ at: sample.at
18081
+ });
18082
+ } catch {
18083
+ }
18084
+ }
18085
+ };
18086
+ function rememberInvoke(invoke2, sink, pending) {
18087
+ return (session, service, path, action, facts) => {
18088
+ const headers = invoke2(session, service, path, action, facts);
18089
+ const meta = { service, path, action, spaceId: session.spaceId };
18090
+ pending.meta = meta;
18091
+ sink.remember(headers, meta);
18092
+ return headers;
18093
+ };
18094
+ }
18095
+ function rememberInvokeAny(invokeAny2, sink, pending) {
18096
+ return (session, entries, facts) => {
18097
+ const headers = invokeAny2(session, entries, facts);
18098
+ const first = entries[0];
18099
+ const meta = first === void 0 ? void 0 : { service: first.service, path: first.path, action: first.action, spaceId: first.spaceId ?? session.spaceId };
18100
+ pending.meta = meta;
18101
+ if (meta !== void 0) sink.remember(headers, meta);
18102
+ return headers;
18103
+ };
18104
+ }
18105
+
17939
18106
  // src/delegation.ts
17940
18107
  import {
17941
18108
  canonicalizeRecapCaveats,
@@ -18511,7 +18678,6 @@ import {
18511
18678
  DecryptTransportResponseError,
18512
18679
  SecretsService,
18513
18680
  createVaultCrypto,
18514
- ServiceContext as ServiceContext2,
18515
18681
  SilentNotificationHandler,
18516
18682
  DelegationManager,
18517
18683
  SpaceService,
@@ -18542,7 +18708,7 @@ import {
18542
18708
  KV as KV3,
18543
18709
  SQL as SQL2,
18544
18710
  DUCKDB as DUCKDB2,
18545
- ENCRYPTION as ENCRYPTION2,
18711
+ ENCRYPTION as ENCRYPTION3,
18546
18712
  EXPIRY as EXPIRY3,
18547
18713
  canonicalHashHex,
18548
18714
  canonicalizeEncryptionJson,
@@ -18554,7 +18720,7 @@ import {
18554
18720
  createInMemoryRegistrySequenceStore as createInMemoryRegistrySequenceStore2,
18555
18721
  publishLocationRecord,
18556
18722
  principalDidEquals as principalDidEquals3,
18557
- parseNetworkId as parseNetworkId2,
18723
+ parseNetworkId as parseNetworkId3,
18558
18724
  resolveSecretPath as resolveSecretPath2,
18559
18725
  registerPolicyV3,
18560
18726
  CREDENTIAL_INVITATION_REQUEST_DOMAIN,
@@ -18575,6 +18741,7 @@ import {
18575
18741
  parsePermissionHint,
18576
18742
  tinyCloudDebugLogger
18577
18743
  } from "@tinycloud/sdk-services";
18744
+ import { RequestLog, observeReplicaReads, readOnlyRequestLog } from "@tinycloud/sdk-services/telemetry/requestLog";
18578
18745
 
18579
18746
  // src/authorization/NodeUserAuthorization.ts
18580
18747
  init_authority_sign_in();
@@ -20425,8 +20592,7 @@ import {
20425
20592
  KVService,
20426
20593
  HooksService,
20427
20594
  SQLService,
20428
- DuckDbService,
20429
- ServiceContext
20595
+ DuckDbService
20430
20596
  } from "@tinycloud/sdk-core";
20431
20597
  var DelegatedAccess = class {
20432
20598
  /**
@@ -20439,12 +20605,13 @@ var DelegatedAccess = class {
20439
20605
  this.session = session;
20440
20606
  this._delegation = delegation;
20441
20607
  this.host = host;
20442
- const context = new ServiceContext({
20608
+ const context = createServiceContext({
20443
20609
  invoke: invoke2,
20444
20610
  invokeAny: invokeAny2,
20445
20611
  fetch: lifetime?.fetch ?? globalThis.fetch.bind(globalThis),
20446
20612
  hosts: [host],
20447
- telemetry
20613
+ telemetry,
20614
+ requestSink: lifetime?.requestSink
20448
20615
  });
20449
20616
  this._serviceContext = lifetime ? lifetime.track(context) : context;
20450
20617
  const prefix = this._delegation.path.replace(/\/$/, "");
@@ -20608,7 +20775,11 @@ function createWasmKeyProvider(sessionManager) {
20608
20775
 
20609
20776
  // src/delegateToHelpers.ts
20610
20777
  import {
20778
+ actionContains,
20779
+ ENCRYPTION as ENCRYPTION2,
20611
20780
  parseExpiry,
20781
+ isNetworkId,
20782
+ parseNetworkId as parseNetworkId2,
20612
20783
  SiweMessage as SiweMessage2,
20613
20784
  EXPIRY as EXPIRY2
20614
20785
  } from "@tinycloud/sdk-core";
@@ -20643,6 +20814,16 @@ function legacyParamsToPermissionEntries(actions, path, spaceIdOverride) {
20643
20814
  return entries;
20644
20815
  }
20645
20816
  var DEFAULT_DELEGATION_EXPIRY_MS = EXPIRY2.SESSION_MS;
20817
+ var SHARE_NETWORK_NAME_RE = /^share-[a-z0-9][a-z0-9-]{15,35}$/;
20818
+ function grantsCappedDecrypt(path, actions) {
20819
+ if (!actions.some((action) => actionContains(action, ENCRYPTION2.DECRYPT))) {
20820
+ return false;
20821
+ }
20822
+ return !(isNetworkId(path) && SHARE_NETWORK_NAME_RE.test(parseNetworkId2(path).name));
20823
+ }
20824
+ function includesDecrypt(entries) {
20825
+ return entries.some((entry) => grantsCappedDecrypt(entry.path, entry.actions));
20826
+ }
20646
20827
  function resolveExpiryMs(expiry) {
20647
20828
  if (expiry === void 0) {
20648
20829
  return DEFAULT_DELEGATION_EXPIRY_MS;
@@ -20816,6 +20997,15 @@ var NodeSecretsService = class {
20816
20997
  if (this.hasPermission(permissionEntries)) {
20817
20998
  return ok();
20818
20999
  }
21000
+ let escalation;
21001
+ try {
21002
+ escalation = this.config.uncoveredEntries?.(permissionEntries) ?? permissionEntries;
21003
+ } catch (error) {
21004
+ return this.escalationError(action, target, error);
21005
+ }
21006
+ if (this.config.uncoveredEntries !== void 0 && escalation.length === 0) {
21007
+ return ok();
21008
+ }
20819
21009
  if (!this.config.canEscalate()) {
20820
21010
  return secretsError(
20821
21011
  ErrorCodes.PERMISSION_DENIED,
@@ -20823,16 +21013,20 @@ var NodeSecretsService = class {
20823
21013
  );
20824
21014
  }
20825
21015
  try {
20826
- await this.config.grantPermissions(permissionEntries);
21016
+ const grant = action === "get" && this.config.grantGetPermissions !== void 0 ? this.config.grantGetPermissions : this.config.grantPermissions;
21017
+ await grant(escalation);
20827
21018
  return this.restoreUnlockAfterEscalation();
20828
21019
  } catch (error) {
20829
- return secretsError(
20830
- ErrorCodes.PERMISSION_DENIED,
20831
- error instanceof Error ? error.message : `Autosign escalation for ${displayActionUrn(action)} on ${target} failed.`,
20832
- error instanceof Error ? error : void 0
20833
- );
21020
+ return this.escalationError(action, target, error);
20834
21021
  }
20835
21022
  }
21023
+ escalationError(action, target, error) {
21024
+ return secretsError(
21025
+ ErrorCodes.PERMISSION_DENIED,
21026
+ error instanceof Error ? error.message : `Autosign escalation for ${displayActionUrn(action)} on ${target} failed.`,
21027
+ error instanceof Error ? error : void 0
21028
+ );
21029
+ }
20836
21030
  async restoreUnlockAfterEscalation() {
20837
21031
  if (!this.shouldRestoreUnlock) {
20838
21032
  return ok();
@@ -20867,8 +21061,8 @@ var NodeSecretsService = class {
20867
21061
  // src/TinyCloudNode.ts
20868
21062
  var DEFAULT_HOST = "https://node.tinycloud.xyz";
20869
21063
  var DEFAULT_ENCRYPTION_NETWORK_NAME = "default";
20870
- var NETWORK_CREATE_ACTION2 = ENCRYPTION2.NETWORK_CREATE;
20871
- var DECRYPT_ACTION2 = ENCRYPTION2.DECRYPT;
21064
+ var NETWORK_CREATE_ACTION2 = ENCRYPTION3.NETWORK_CREATE;
21065
+ var DECRYPT_ACTION2 = ENCRYPTION3.DECRYPT;
20872
21066
  var BOOTSTRAP_COMPLETION_MARKER_KEY = "system/bootstrap/complete";
20873
21067
  var BOOTSTRAP_COMPLETION_MARKER_VERSION = 1;
20874
21068
  var ACCEPTED_MARKER_VERSIONS = [BOOTSTRAP_COMPLETION_MARKER_VERSION];
@@ -21044,11 +21238,12 @@ function createDefaultSessionKey(wasm) {
21044
21238
  return { manager, keyId, jwk: JSON.parse(jwkStr) };
21045
21239
  }
21046
21240
  var ServiceGraphLifetime = class {
21047
- constructor(invokeFn, invokeAnyFn, fetchFn, observeContext) {
21241
+ constructor(invokeFn, invokeAnyFn, fetchFn, observeContext, requestSink) {
21048
21242
  this.invokeFn = invokeFn;
21049
21243
  this.invokeAnyFn = invokeAnyFn;
21050
21244
  this.fetchFn = fetchFn;
21051
21245
  this.observeContext = observeContext;
21246
+ this.requestSink = requestSink;
21052
21247
  this.abortController = new AbortController();
21053
21248
  this.contexts = /* @__PURE__ */ new Set();
21054
21249
  this.retired = false;
@@ -21214,7 +21409,7 @@ function ownerDelegationPermissions(params) {
21214
21409
  }
21215
21410
  if (permission.service === ENCRYPTION_PERMISSION_SERVICE2) {
21216
21411
  try {
21217
- parseNetworkId2(permission.path);
21412
+ parseNetworkId3(permission.path);
21218
21413
  } catch {
21219
21414
  throw new Error("Owner delegation encryption permission requires a canonical network URN");
21220
21415
  }
@@ -21237,6 +21432,8 @@ function ownerPermissionsToAbilities(permissions) {
21237
21432
  return { abilities, rawAbilities };
21238
21433
  }
21239
21434
  var TERMINAL_DELEGATION_REQUIRES_SESSION_KEY = "disableSubDelegation requires a session-key delegation to a did:key (not did:pkh); use the session-key DID or drop the flag";
21435
+ var DECRYPT_GRANT_MAX_EXPIRY_MS = 60 * 60 * 1e3;
21436
+ var SECRET_GET_GRANT_EXPIRY_MS = 59 * 60 * 1e3;
21240
21437
  function base64UrlEncode(bytes) {
21241
21438
  const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
21242
21439
  let output = "";
@@ -21619,6 +21816,7 @@ var _TinyCloudNode = class _TinyCloudNode {
21619
21816
  this.sessionManager = sessionKey.manager;
21620
21817
  this.sessionKeyId = sessionKey.keyId;
21621
21818
  this.sessionKeyJwk = sessionKey.jwk;
21819
+ this._requestLog = config.requestLog === true ? new RequestLog() : void 0;
21622
21820
  this._serviceGraph = this.createServiceGraphLifetime();
21623
21821
  this._capabilityRegistry = new CapabilityKeyRegistry();
21624
21822
  this._keyProvider = new WasmKeyProvider({
@@ -21790,7 +21988,8 @@ var _TinyCloudNode = class _TinyCloudNode {
21790
21988
  createKVService: (config) => {
21791
21989
  const prefix = config.pathPrefix?.replace(/\/$/, "");
21792
21990
  const kvService = new KVService2({ prefix });
21793
- const kvContext = receiveOnlyGraph.track(new ServiceContext2({
21991
+ const kvContext = receiveOnlyGraph.track(createServiceContext({
21992
+ requestSink: receiveOnlyGraph.requestSink,
21794
21993
  invoke: config.invoke,
21795
21994
  fetch: config.fetch ?? globalThis.fetch.bind(globalThis),
21796
21995
  hosts: config.hosts,
@@ -21853,6 +22052,10 @@ var _TinyCloudNode = class _TinyCloudNode {
21853
22052
  shouldUseBootstrapSignInRequest(config) {
21854
22053
  return config.autoBootstrapAccount !== false && config.manifest === void 0 && config.capabilityRequest === void 0 && (config.prefix ?? "default") === "default" && isOpenKeyAutoSignStrategy(config.signStrategy);
21855
22054
  }
22055
+ /** Sanitized developer request events; undefined unless the node was built with `requestLog: true`. */
22056
+ get requests() {
22057
+ return this._requestLog === void 0 ? void 0 : readOnlyRequestLog(this._requestLog);
22058
+ }
21856
22059
  createServiceGraphLifetime() {
21857
22060
  return new ServiceGraphLifetime(
21858
22061
  this.invokeWithRuntimePermissions,
@@ -21863,7 +22066,9 @@ var _TinyCloudNode = class _TinyCloudNode {
21863
22066
  (url, init2) => globalThis.fetch(url, init2),
21864
22067
  (context) => {
21865
22068
  this.storageFull.observe(context);
21866
- }
22069
+ if (this._requestLog) observeReplicaReads(context, this._requestLog);
22070
+ },
22071
+ this._requestLog
21867
22072
  );
21868
22073
  }
21869
22074
  syncResolvedHostFromAuth() {
@@ -22186,6 +22391,7 @@ var _TinyCloudNode = class _TinyCloudNode {
22186
22391
  this.hostedOwnedSpaceIds.clear();
22187
22392
  this.cancelOwnedSpaceHosts();
22188
22393
  this.storageFull.clear();
22394
+ this._requestLog?.clear();
22189
22395
  this._bootstrapSkipped = false;
22190
22396
  this._bootstrapStatus = { skipped: false };
22191
22397
  this.replicationDelegateSession = void 0;
@@ -22277,7 +22483,10 @@ var _TinyCloudNode = class _TinyCloudNode {
22277
22483
  const denial = await this.denyReplicaGate({ reason: "account_switch", principal: priorPrincipal, next: nextPrincipal });
22278
22484
  this.replicaRestoreGate = denial?.status === "unguarded" ? "gate_unavailable" : "stamp_stale";
22279
22485
  }
22280
- if (address !== this._address || chainId !== this._chainId) this.storageFull.clear();
22486
+ if (address !== this._address || chainId !== this._chainId) {
22487
+ this.storageFull.clear();
22488
+ this._requestLog?.clear();
22489
+ }
22281
22490
  this._address = address;
22282
22491
  this._chainId = chainId;
22283
22492
  this._kv = void 0;
@@ -22856,10 +23065,11 @@ var _TinyCloudNode = class _TinyCloudNode {
22856
23065
  const contexts = [context];
22857
23066
  const accountSpaceId = this.ownedSpaceId(ACCOUNT_REGISTRY_SPACE2);
22858
23067
  const createScopedContext = (spaceId) => {
22859
- const scoped = this._serviceGraph.track(new ServiceContext2({
23068
+ const scoped = this._serviceGraph.track(createServiceContext({
23069
+ requestSink: this._serviceGraph.requestSink,
22860
23070
  invoke: context.invoke,
22861
23071
  invokeAny: context.invokeAny,
22862
- fetch: context.fetch,
23072
+ fetch: unrecordedFetchOf(context),
22863
23073
  hosts: context.hosts,
22864
23074
  telemetry: this.config.telemetry,
22865
23075
  retryPolicy: this.config.retryPolicy
@@ -22923,10 +23133,11 @@ var _TinyCloudNode = class _TinyCloudNode {
22923
23133
  const promise = this.enqueueAccountRegistryOperation(session, async () => {
22924
23134
  const sharedContext = this._serviceContext;
22925
23135
  if (!sharedContext) return;
22926
- const context = this._serviceGraph.track(new ServiceContext2({
23136
+ const context = this._serviceGraph.track(createServiceContext({
23137
+ requestSink: this._serviceGraph.requestSink,
22927
23138
  invoke: sharedContext.invoke,
22928
23139
  invokeAny: sharedContext.invokeAny,
22929
- fetch: sharedContext.fetch,
23140
+ fetch: unrecordedFetchOf(sharedContext),
22930
23141
  hosts: sharedContext.hosts,
22931
23142
  telemetry: this.config.telemetry,
22932
23143
  retryPolicy: this.config.retryPolicy
@@ -23131,7 +23342,7 @@ var _TinyCloudNode = class _TinyCloudNode {
23131
23342
  return;
23132
23343
  }
23133
23344
  for (const networkId of this.requestedEncryptionNetworkIds()) {
23134
- const parsed = parseNetworkId2(networkId);
23345
+ const parsed = parseNetworkId3(networkId);
23135
23346
  if (!didPrincipalMatches2(parsed.ownerDid, this.did)) {
23136
23347
  continue;
23137
23348
  }
@@ -23660,6 +23871,7 @@ var _TinyCloudNode = class _TinyCloudNode {
23660
23871
  if (resolvedHost) this.config.host = resolvedHost;
23661
23872
  if (spaceOwnerKey(serviceSession.spaceId) !== this.currentStorageOwner()) {
23662
23873
  this.storageFull.clear();
23874
+ this._requestLog?.clear();
23663
23875
  }
23664
23876
  this._address = stagedAddress;
23665
23877
  this._chainId = stagedChainId;
@@ -23779,7 +23991,8 @@ var _TinyCloudNode = class _TinyCloudNode {
23779
23991
  const coreContext = graph.track(core.serviceContext);
23780
23992
  coreContext.setSession(input.serviceSession);
23781
23993
  }
23782
- const serviceContext = graph.track(new ServiceContext2({
23994
+ const serviceContext = graph.track(createServiceContext({
23995
+ requestSink: graph.requestSink,
23783
23996
  invoke: graph.invoke,
23784
23997
  invokeAny: graph.invokeAny,
23785
23998
  fetch: graph.fetch,
@@ -23867,7 +24080,8 @@ var _TinyCloudNode = class _TinyCloudNode {
23867
24080
  },
23868
24081
  createKVService: (config) => {
23869
24082
  const service = new KVService2({ prefix: config.pathPrefix?.replace(/\/$/, "") });
23870
- const context = graph.track(new ServiceContext2({
24083
+ const context = graph.track(createServiceContext({
24084
+ requestSink: graph.requestSink,
23871
24085
  invoke: config.invoke,
23872
24086
  fetch: config.fetch ?? globalThis.fetch.bind(globalThis),
23873
24087
  hosts: config.hosts,
@@ -23907,7 +24121,8 @@ var _TinyCloudNode = class _TinyCloudNode {
23907
24121
  createKVService: (spaceId) => {
23908
24122
  graph.assertActive();
23909
24123
  const scopedKv = new KVService2({});
23910
- const context = graph.track(new ServiceContext2({
24124
+ const context = graph.track(createServiceContext({
24125
+ requestSink: graph.requestSink,
23911
24126
  invoke: graph.invoke,
23912
24127
  invokeAny: graph.invokeAny,
23913
24128
  fetch: graph.fetch,
@@ -23923,7 +24138,8 @@ var _TinyCloudNode = class _TinyCloudNode {
23923
24138
  createVaultService: (spaceId) => {
23924
24139
  graph.assertActive();
23925
24140
  const scopedKv = new KVService2({});
23926
- const context = graph.track(new ServiceContext2({
24141
+ const context = graph.track(createServiceContext({
24142
+ requestSink: graph.requestSink,
23927
24143
  invoke: graph.invoke,
23928
24144
  invokeAny: graph.invokeAny,
23929
24145
  fetch: graph.fetch,
@@ -24322,7 +24538,8 @@ var _TinyCloudNode = class _TinyCloudNode {
24322
24538
  this._serviceGraph.fetch
24323
24539
  );
24324
24540
  this._serviceGraph.track(this.tc.serviceContext);
24325
- this._serviceContext = this._serviceGraph.track(new ServiceContext2({
24541
+ this._serviceContext = this._serviceGraph.track(createServiceContext({
24542
+ requestSink: this._serviceGraph.requestSink,
24326
24543
  invoke: this._serviceGraph.invoke,
24327
24544
  invokeAny: this._serviceGraph.invokeAny,
24328
24545
  fetch: this._serviceGraph.fetch,
@@ -24370,10 +24587,11 @@ var _TinyCloudNode = class _TinyCloudNode {
24370
24587
  createSpaceScopedKVService(spaceId) {
24371
24588
  const kvService = new KVService2({});
24372
24589
  if (this._serviceContext) {
24373
- const spaceScopedContext = this._serviceGraph.track(new ServiceContext2({
24590
+ const spaceScopedContext = this._serviceGraph.track(createServiceContext({
24591
+ requestSink: this._serviceGraph.requestSink,
24374
24592
  invoke: this._serviceContext.invoke,
24375
24593
  invokeAny: this._serviceContext.invokeAny,
24376
- fetch: this._serviceContext.fetch,
24594
+ fetch: unrecordedFetchOf(this._serviceContext),
24377
24595
  hosts: this._serviceContext.hosts,
24378
24596
  telemetry: this.config.telemetry,
24379
24597
  retryPolicy: this.config.retryPolicy
@@ -24839,7 +25057,8 @@ var _TinyCloudNode = class _TinyCloudNode {
24839
25057
  createKVService: (config) => {
24840
25058
  graph.assertActive();
24841
25059
  const service = new KVService2({ prefix: config.pathPrefix?.replace(/\/$/, "") });
24842
- const context = graph.track(new ServiceContext2({
25060
+ const context = graph.track(createServiceContext({
25061
+ requestSink: graph.requestSink,
24843
25062
  invoke: config.invoke,
24844
25063
  fetch: config.fetch ?? graph.fetch,
24845
25064
  hosts: config.hosts,
@@ -24942,7 +25161,7 @@ var _TinyCloudNode = class _TinyCloudNode {
24942
25161
  if (!session) throw new Error("Owner session is required");
24943
25162
  const ownerDid = pkhDid2(session.address, session.chainId);
24944
25163
  for (const permission of permissions) {
24945
- if (permission.service === ENCRYPTION_PERMISSION_SERVICE2 && !principalDidEquals3(parseNetworkId2(permission.path).ownerDid, ownerDid)) {
25164
+ if (permission.service === ENCRYPTION_PERMISSION_SERVICE2 && !principalDidEquals3(parseNetworkId3(permission.path).ownerDid, ownerDid)) {
24946
25165
  throw new Error("Owner delegation cannot grant a foreign encryption network");
24947
25166
  }
24948
25167
  }
@@ -25160,10 +25379,11 @@ var _TinyCloudNode = class _TinyCloudNode {
25160
25379
  throw new Error("Not signed in. Call signIn() first.");
25161
25380
  }
25162
25381
  const sql = new SQLService2({});
25163
- const spaceScopedContext = this._serviceGraph.track(new ServiceContext2({
25382
+ const spaceScopedContext = this._serviceGraph.track(createServiceContext({
25383
+ requestSink: this._serviceGraph.requestSink,
25164
25384
  invoke: this._serviceContext.invoke,
25165
25385
  invokeAny: this._serviceContext.invokeAny,
25166
- fetch: this._serviceContext.fetch,
25386
+ fetch: unrecordedFetchOf(this._serviceContext),
25167
25387
  hosts: this._serviceContext.hosts,
25168
25388
  telemetry: this.config.telemetry,
25169
25389
  retryPolicy: this.config.retryPolicy
@@ -25191,10 +25411,11 @@ var _TinyCloudNode = class _TinyCloudNode {
25191
25411
  throw new Error("Not signed in. Call signIn() first.");
25192
25412
  }
25193
25413
  const kv = new KVService2({});
25194
- const spaceScopedContext = this._serviceGraph.track(new ServiceContext2({
25414
+ const spaceScopedContext = this._serviceGraph.track(createServiceContext({
25415
+ requestSink: this._serviceGraph.requestSink,
25195
25416
  invoke: this._serviceContext.invoke,
25196
25417
  invokeAny: this._serviceContext.invokeAny,
25197
- fetch: this._serviceContext.fetch,
25418
+ fetch: unrecordedFetchOf(this._serviceContext),
25198
25419
  hosts: this._serviceContext.hosts
25199
25420
  }));
25200
25421
  spaceScopedContext.setSession({ ...this._serviceContext.session, spaceId });
@@ -25302,7 +25523,7 @@ var _TinyCloudNode = class _TinyCloudNode {
25302
25523
  return existing;
25303
25524
  }
25304
25525
  }
25305
- const parsed = parseNetworkId2(networkId);
25526
+ const parsed = parseNetworkId3(networkId);
25306
25527
  if (!didPrincipalMatches2(parsed.ownerDid, this.did)) {
25307
25528
  throw new Error(
25308
25529
  `Cannot create encryption network ${networkId}: owner ${parsed.ownerDid} does not match signed-in DID ${this.did}`
@@ -25335,6 +25556,8 @@ var _TinyCloudNode = class _TinyCloudNode {
25335
25556
  getManifest: () => this.manifest,
25336
25557
  hasPermissions: (permissions) => this.hasRuntimePermissions(permissions),
25337
25558
  grantPermissions: (additional) => this.grantRuntimePermissions(additional),
25559
+ grantGetPermissions: (additional) => this.grantRuntimePermissions(additional, { expiry: SECRET_GET_GRANT_EXPIRY_MS }),
25560
+ uncoveredEntries: (permissions) => permissions.filter((entry) => !this.hasRuntimePermissions([entry])),
25338
25561
  canEscalate: () => this.signer !== void 0 && this.tc !== void 0,
25339
25562
  getEncryptionNetworkId: () => this.getEncryptionNetworkIdForSpace(resolvedSpace),
25340
25563
  resolveSpace: (space) => space.startsWith("tinycloud:") ? space : this.ownedSpaceId(space),
@@ -25682,6 +25905,13 @@ var _TinyCloudNode = class _TinyCloudNode {
25682
25905
  }
25683
25906
  }
25684
25907
  const expanded = this.expandPermissionEntries(permissions);
25908
+ const requestsDecrypt = includesDecrypt(expanded);
25909
+ const requestedExpiryMs = requestsDecrypt && options?.expiry === void 0 ? DECRYPT_GRANT_MAX_EXPIRY_MS : resolveExpiryMs(options?.expiry);
25910
+ if (requestsDecrypt && requestedExpiryMs > DECRYPT_GRANT_MAX_EXPIRY_MS) {
25911
+ throw Object.assign(new Error("decrypt grants last at most one hour"), {
25912
+ code: "DECRYPT_EXPIRY_TOO_LONG"
25913
+ });
25914
+ }
25685
25915
  if (this.sessionCoversPermissionEntries(session, expanded)) {
25686
25916
  return [];
25687
25917
  }
@@ -25712,7 +25942,6 @@ var _TinyCloudNode = class _TinyCloudNode {
25712
25942
  bySpace.set(session.spaceId, []);
25713
25943
  }
25714
25944
  const now = /* @__PURE__ */ new Date();
25715
- const requestedExpiryMs = resolveExpiryMs(options?.expiry);
25716
25945
  let expiresAt = new Date(now.getTime() + requestedExpiryMs);
25717
25946
  if (sessionExpiry !== void 0 && sessionExpiry < expiresAt) {
25718
25947
  expiresAt = sessionExpiry;
@@ -25980,7 +26209,8 @@ var _TinyCloudNode = class _TinyCloudNode {
25980
26209
  }
25981
26210
  if (this._serviceContext) {
25982
26211
  const publicKV = new KVService2({ prefix: "" });
25983
- const publicContext = this._serviceGraph.track(new ServiceContext2({
26212
+ const publicContext = this._serviceGraph.track(createServiceContext({
26213
+ requestSink: this._serviceGraph.requestSink,
25984
26214
  invoke: this._serviceGraph.invoke,
25985
26215
  fetch: this._serviceGraph.fetch,
25986
26216
  hosts: this._serviceContext.hosts,
@@ -27762,9 +27992,9 @@ import {
27762
27992
  import {
27763
27993
  EncryptionService as EncryptionService2,
27764
27994
  DecryptTransportResponseError as DecryptTransportResponseError2,
27765
- parseNetworkId as parseNetworkId3,
27995
+ parseNetworkId as parseNetworkId4,
27766
27996
  buildNetworkId,
27767
- isNetworkId,
27997
+ isNetworkId as isNetworkId2,
27768
27998
  networkDiscoveryKey,
27769
27999
  NetworkIdError,
27770
28000
  ENCRYPTION_NETWORK_URN_PREFIX,
@@ -27829,7 +28059,7 @@ import {
27829
28059
  UnsupportedFeatureError as UnsupportedFeatureError2,
27830
28060
  checkNodeInfo as checkNodeInfo3
27831
28061
  } from "@tinycloud/sdk-core";
27832
- import { ServiceContext as ServiceContext4 } from "@tinycloud/sdk-core";
28062
+ import { ServiceContext as ServiceContext5 } from "@tinycloud/sdk-core";
27833
28063
 
27834
28064
  // src/replication/sqlite.ts
27835
28065
  init_authority();
@@ -27838,7 +28068,7 @@ import { createHash as createHash2, randomBytes as randomBytes2 } from "crypto";
27838
28068
  import { lstatSync, readFileSync as readFileSync2, statSync } from "fs";
27839
28069
  import { chmod, lstat, mkdir, open as openFile, readFile, readdir, realpath, rename, rm, rmdir, stat } from "fs/promises";
27840
28070
  import { basename, dirname as dirname2, join as join2, parse as parse2, resolve, sep } from "path";
27841
- import { KVService as KVService3, ServiceContext as ServiceContext3, principalDid as principalDid2 } from "@tinycloud/sdk-core";
28071
+ import { KVService as KVService3, ServiceContext as ServiceContext4, principalDid as principalDid2 } from "@tinycloud/sdk-core";
27842
28072
  import {
27843
28073
  canonicalReplicationIdentity,
27844
28074
  replicationIdentityKey,
@@ -28999,7 +29229,7 @@ transports_fn = async function() {
28999
29229
  const device = __privateGet(this, _device);
29000
29230
  const fetch2 = spec.fetch ?? globalThis.fetch.bind(globalThis);
29001
29231
  return (grant) => {
29002
- const context = new ServiceContext3({
29232
+ const context = new ServiceContext4({
29003
29233
  invoke: wasm.invoke,
29004
29234
  invokeAny: wasm.invokeAny,
29005
29235
  fetch: fetch2,
@@ -30406,7 +30636,7 @@ export {
30406
30636
  STORAGE_MANAGE_URL,
30407
30637
  STORAGE_WRITE_TOO_LARGE_MESSAGE2 as STORAGE_WRITE_TOO_LARGE_MESSAGE,
30408
30638
  SecretsService2 as SecretsService,
30409
- ServiceContext4 as ServiceContext,
30639
+ ServiceContext5 as ServiceContext,
30410
30640
  SessionExpiredError2 as SessionExpiredError,
30411
30641
  SharingService2 as SharingService,
30412
30642
  SilentNotificationHandler2 as SilentNotificationHandler,
@@ -30483,7 +30713,7 @@ export {
30483
30713
  installTinyCloudDebugGlobals,
30484
30714
  isCapabilitySubset4 as isCapabilitySubset,
30485
30715
  isEvmAddress,
30486
- isNetworkId,
30716
+ isNetworkId2 as isNetworkId,
30487
30717
  isStorageFullError2 as isStorageFullError,
30488
30718
  loadManifest,
30489
30719
  logoutPrincipalDigest,
@@ -30493,7 +30723,7 @@ export {
30493
30723
  openWrappedKey,
30494
30724
  parseCanonicalNetworkId,
30495
30725
  parseExpiry2 as parseExpiry,
30496
- parseNetworkId3 as parseNetworkId,
30726
+ parseNetworkId4 as parseNetworkId,
30497
30727
  parsePkhDid,
30498
30728
  parseSecretCatalogKey,
30499
30729
  parseSpaceUri3 as parseSpaceUri,