@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.
@@ -1,8 +1,8 @@
1
1
  import * as _tinycloud_sdk_core from '@tinycloud/sdk-core';
2
2
  import { AutoSignStrategy, AutoRejectStrategy, CallbackStrategy, CapabilityPresentationEnvelopeV1, IUserAuthorization, ISigner, ISessionStorage, ISpaceCreationHandler, RegistryPolicy, RegistrySequenceStore, OnNodeNotFound, LocalNodeIdentityStore, IWasmBindings, ISessionManager, SiweConfig, Manifest, ComposedManifestRequest, ReplicationOptions, TinyCloudSession, PersistedSessionData, ClientSession, PermissionEntry, Extension, SignRequest, SpaceHostResult, SignInOptions, Delegation, DelegatedResource, TelemetryConfig, ServiceContext, IKVService, ISQLService, IDuckDbService, IHooksService, CreateOwnerDelegationParams as CreateOwnerDelegationParams$1, OwnerDelegationReceipt as OwnerDelegationReceipt$1, StorageFullEvent, IStorageService, INotificationHandler, IENSResolver, RetryPolicy, KVReplicaStorage, AccountService, InvokeAnyFunction as InvokeAnyFunction$1, UnifiedPolicyCapability, RegisterPolicyV3Input, RegisterPolicyV3Receipt, CanonicalJson, ShareDeliveryAuthorizationV3Receipt, IDataVaultService, IEncryptionService, NetworkDescriptor, ISecretsService, ICapabilityKeyRegistry, DelegationManager, ISpaceService, ISpace, ISharingService, DelegationResult, DelegationRevocationReceipt, DelegationStatus, ServiceSession, ReplicationControl, ResolvedDelegate } from '@tinycloud/sdk-core';
3
3
  import { ReplicaSessionStamp } from '@tinycloud/sdk-services/kv/replication';
4
+ import { InvokeFunction, InvokeAnyFunction, FetchFunction, RequestSink, RequestLogReader } from '@tinycloud/sdk-services';
4
5
  import { EventEmitter } from 'events';
5
- import { InvokeFunction, InvokeAnyFunction, FetchFunction } from '@tinycloud/sdk-services';
6
6
 
7
7
  /**
8
8
  * Node.js-specific SignStrategy types for TinyCloud authorization.
@@ -1090,6 +1090,7 @@ declare class DelegatedAccess {
1090
1090
  constructor(session: TinyCloudSession, delegation: PortableDelegation, host: string, invoke: InvokeFunction, invokeAny?: InvokeAnyFunction, telemetry?: TelemetryConfig, lifetime?: {
1091
1091
  fetch: FetchFunction;
1092
1092
  track(context: ServiceContext): ServiceContext;
1093
+ requestSink?: RequestSink;
1093
1094
  });
1094
1095
  /**
1095
1096
  * Get the delegation this access was created from.
@@ -1230,6 +1231,8 @@ interface TinyCloudNodeConfig {
1230
1231
  signStrategy?: SignStrategy;
1231
1232
  /** Explicit TinyCloud server URL. When omitted, signIn resolves the user's host. */
1232
1233
  host?: string;
1234
+ /** Build a developer request log (sanitized, in-memory, bounded). Off by default. */
1235
+ requestLog?: boolean;
1233
1236
  /** TinyCloud location registry URL. Default: https://registry.tinycloud.xyz. */
1234
1237
  tinycloudRegistryUrl?: string | null;
1235
1238
  /**
@@ -1517,6 +1520,7 @@ declare class TinyCloudNode {
1517
1520
  private wasmBindings;
1518
1521
  private sessionManager;
1519
1522
  private _serviceGraph;
1523
+ private readonly _requestLog?;
1520
1524
  private _serviceContext?;
1521
1525
  private _kv?;
1522
1526
  private readonly replicationRuntime?;
@@ -1713,6 +1717,8 @@ declare class TinyCloudNode {
1713
1717
  */
1714
1718
  private setupAuth;
1715
1719
  private shouldUseBootstrapSignInRequest;
1720
+ /** Sanitized developer request events; undefined unless the node was built with `requestLog: true`. */
1721
+ get requests(): RequestLogReader | undefined;
1716
1722
  private createServiceGraphLifetime;
1717
1723
  private syncResolvedHostFromAuth;
1718
1724
  /**
@@ -1,8 +1,8 @@
1
1
  import * as _tinycloud_sdk_core from '@tinycloud/sdk-core';
2
2
  import { AutoSignStrategy, AutoRejectStrategy, CallbackStrategy, CapabilityPresentationEnvelopeV1, IUserAuthorization, ISigner, ISessionStorage, ISpaceCreationHandler, RegistryPolicy, RegistrySequenceStore, OnNodeNotFound, LocalNodeIdentityStore, IWasmBindings, ISessionManager, SiweConfig, Manifest, ComposedManifestRequest, ReplicationOptions, TinyCloudSession, PersistedSessionData, ClientSession, PermissionEntry, Extension, SignRequest, SpaceHostResult, SignInOptions, Delegation, DelegatedResource, TelemetryConfig, ServiceContext, IKVService, ISQLService, IDuckDbService, IHooksService, CreateOwnerDelegationParams as CreateOwnerDelegationParams$1, OwnerDelegationReceipt as OwnerDelegationReceipt$1, StorageFullEvent, IStorageService, INotificationHandler, IENSResolver, RetryPolicy, KVReplicaStorage, AccountService, InvokeAnyFunction as InvokeAnyFunction$1, UnifiedPolicyCapability, RegisterPolicyV3Input, RegisterPolicyV3Receipt, CanonicalJson, ShareDeliveryAuthorizationV3Receipt, IDataVaultService, IEncryptionService, NetworkDescriptor, ISecretsService, ICapabilityKeyRegistry, DelegationManager, ISpaceService, ISpace, ISharingService, DelegationResult, DelegationRevocationReceipt, DelegationStatus, ServiceSession, ReplicationControl, ResolvedDelegate } from '@tinycloud/sdk-core';
3
3
  import { ReplicaSessionStamp } from '@tinycloud/sdk-services/kv/replication';
4
+ import { InvokeFunction, InvokeAnyFunction, FetchFunction, RequestSink, RequestLogReader } from '@tinycloud/sdk-services';
4
5
  import { EventEmitter } from 'events';
5
- import { InvokeFunction, InvokeAnyFunction, FetchFunction } from '@tinycloud/sdk-services';
6
6
 
7
7
  /**
8
8
  * Node.js-specific SignStrategy types for TinyCloud authorization.
@@ -1090,6 +1090,7 @@ declare class DelegatedAccess {
1090
1090
  constructor(session: TinyCloudSession, delegation: PortableDelegation, host: string, invoke: InvokeFunction, invokeAny?: InvokeAnyFunction, telemetry?: TelemetryConfig, lifetime?: {
1091
1091
  fetch: FetchFunction;
1092
1092
  track(context: ServiceContext): ServiceContext;
1093
+ requestSink?: RequestSink;
1093
1094
  });
1094
1095
  /**
1095
1096
  * Get the delegation this access was created from.
@@ -1230,6 +1231,8 @@ interface TinyCloudNodeConfig {
1230
1231
  signStrategy?: SignStrategy;
1231
1232
  /** Explicit TinyCloud server URL. When omitted, signIn resolves the user's host. */
1232
1233
  host?: string;
1234
+ /** Build a developer request log (sanitized, in-memory, bounded). Off by default. */
1235
+ requestLog?: boolean;
1233
1236
  /** TinyCloud location registry URL. Default: https://registry.tinycloud.xyz. */
1234
1237
  tinycloudRegistryUrl?: string | null;
1235
1238
  /**
@@ -1517,6 +1520,7 @@ declare class TinyCloudNode {
1517
1520
  private wasmBindings;
1518
1521
  private sessionManager;
1519
1522
  private _serviceGraph;
1523
+ private readonly _requestLog?;
1520
1524
  private _serviceContext?;
1521
1525
  private _kv?;
1522
1526
  private readonly replicationRuntime?;
@@ -1713,6 +1717,8 @@ declare class TinyCloudNode {
1713
1717
  */
1714
1718
  private setupAuth;
1715
1719
  private shouldUseBootstrapSignInRequest;
1720
+ /** Sanitized developer request events; undefined unless the node was built with `requestLog: true`. */
1721
+ get requests(): RequestLogReader | undefined;
1716
1722
  private createServiceGraphLifetime;
1717
1723
  private syncResolvedHostFromAuth;
1718
1724
  /**
@@ -1,7 +1,7 @@
1
1
  import { ReplicationOptions, PermissionEntry, TinyCloudSession, KVReplicaStorage, ServiceContext, IWasmBindings, WasmRecapEntry } from '@tinycloud/sdk-core';
2
2
  import { ReplicaConsentAnchor, AuthorityRefusal, ReplicationAuthority } from '@tinycloud/sdk-services';
3
3
  import { ReplicaSessionStamp, ReplicaDenyInput } from '@tinycloud/sdk-services/kv/replication';
4
- import { T as TinyCloudNode } from './TinyCloudNode-DeV-L_14.cjs';
4
+ import { T as TinyCloudNode } from './TinyCloudNode-N4mfZI_k.cjs';
5
5
 
6
6
  /** Constructor-time validation; the space-dependent secrets check runs at sign-in and open. */
7
7
  declare function assertValidReplicationConfig(replication: (ReplicationOptions & {
@@ -1,7 +1,7 @@
1
1
  import { ReplicationOptions, PermissionEntry, TinyCloudSession, KVReplicaStorage, ServiceContext, IWasmBindings, WasmRecapEntry } from '@tinycloud/sdk-core';
2
2
  import { ReplicaConsentAnchor, AuthorityRefusal, ReplicationAuthority } from '@tinycloud/sdk-services';
3
3
  import { ReplicaSessionStamp, ReplicaDenyInput } from '@tinycloud/sdk-services/kv/replication';
4
- import { T as TinyCloudNode } from './TinyCloudNode-DeV-L_14.js';
4
+ import { T as TinyCloudNode } from './TinyCloudNode-N4mfZI_k.js';
5
5
 
6
6
  /** Constructor-time validation; the space-dependent secrets check runs at sign-in and open. */
7
7
  declare function assertValidReplicationConfig(replication: (ReplicationOptions & {
package/dist/core.cjs CHANGED
@@ -2121,6 +2121,167 @@ var NodeUserAuthorization = class {
2121
2121
  }
2122
2122
  };
2123
2123
 
2124
+ // src/recordingServiceContext.ts
2125
+ var import_sdk_services2 = require("@tinycloud/sdk-services");
2126
+ var import_requestLog = require("@tinycloud/sdk-services/telemetry/requestLog");
2127
+
2128
+ // src/recordingRegistry.ts
2129
+ var unrecordedFetches = /* @__PURE__ */ new WeakMap();
2130
+ function registerRecording(context, unrecordedFetch) {
2131
+ unrecordedFetches.set(context, unrecordedFetch);
2132
+ }
2133
+ function unrecordedFetchOf(context) {
2134
+ const unrecordedFetch = unrecordedFetches.get(context);
2135
+ return unrecordedFetch ? unrecordedFetch() : context.fetch;
2136
+ }
2137
+
2138
+ // src/recordingServiceContext.ts
2139
+ var SYNC_ABILITY = "tinycloud.kv/sync";
2140
+ var INFO_PATH = "/info";
2141
+ function outcomeCodeOf(response) {
2142
+ return response === void 0 ? "ERROR" : response.ok ? "ok" : `HTTP_${response.status}`;
2143
+ }
2144
+ function replicationOp(url, meta) {
2145
+ if (meta !== void 0) return meta.action === SYNC_ABILITY ? `${meta.service}/sync` : void 0;
2146
+ return isInfoUrl(url) ? "sync/info" : void 0;
2147
+ }
2148
+ function isInfoUrl(url) {
2149
+ try {
2150
+ return new URL(url).pathname.replace(/\/+$/, "") === INFO_PATH;
2151
+ } catch {
2152
+ return false;
2153
+ }
2154
+ }
2155
+ function replicationEvent(sample, op) {
2156
+ return (0, import_requestLog.buildRequestEvent)({
2157
+ service: "sync",
2158
+ op,
2159
+ url: sample.url,
2160
+ bytes: (0, import_requestLog.requestBytesOf)(sample.init?.body, sample.response?.headers.get("content-length")),
2161
+ durationMs: performance.now() - sample.clock,
2162
+ attempts: sample.attempts ?? 1,
2163
+ ok: sample.response?.ok === true,
2164
+ code: outcomeCodeOf(sample.response),
2165
+ at: sample.at
2166
+ });
2167
+ }
2168
+ function createServiceContext(config) {
2169
+ const { requestSink, ...base } = config;
2170
+ return requestSink === void 0 ? new import_sdk_services2.ServiceContext(base) : new RecordingServiceContext(base, requestSink);
2171
+ }
2172
+ var RecordingServiceContext = class extends import_sdk_services2.ServiceContext {
2173
+ constructor(config, sink) {
2174
+ const pending = {};
2175
+ super({
2176
+ ...config,
2177
+ invoke: rememberInvoke(config.invoke, sink, pending),
2178
+ invokeAny: config.invokeAny ? rememberInvokeAny(config.invokeAny, sink, pending) : void 0
2179
+ });
2180
+ this.requestSink = sink;
2181
+ this.pending = pending;
2182
+ registerRecording(this, () => this.unrecordedFetch);
2183
+ }
2184
+ /** Per-call recording; `_fetch` is read at access time, so overrides apply. */
2185
+ get fetch() {
2186
+ const raw = super.fetch;
2187
+ return (url, init) => this.recordCall(raw, url, init);
2188
+ }
2189
+ /** Transport without per-call recording. */
2190
+ get unrecordedFetch() {
2191
+ return super.fetch;
2192
+ }
2193
+ /**
2194
+ * One event per logical request. The first resolved attempt's init is the logged request and
2195
+ * `attempts` counts resolved builds. A retired context throws before the first build and records
2196
+ * nothing, as master does.
2197
+ */
2198
+ async invokeWithRetry(url, build) {
2199
+ const at = Date.now();
2200
+ const clock = performance.now();
2201
+ let reached = false;
2202
+ let attempts = 0;
2203
+ let first;
2204
+ let firstMeta;
2205
+ let response;
2206
+ const attempt = async () => {
2207
+ reached = true;
2208
+ this.pending.meta = void 0;
2209
+ const built = build();
2210
+ const meta = this.pending.meta;
2211
+ const init = await built;
2212
+ attempts += 1;
2213
+ if (first === void 0) {
2214
+ first = init;
2215
+ firstMeta = meta;
2216
+ }
2217
+ return init;
2218
+ };
2219
+ try {
2220
+ response = await super.invokeWithRetry(url, attempt);
2221
+ return response;
2222
+ } finally {
2223
+ if (reached) {
2224
+ this.observeAttempt({ url, init: first, response, meta: firstMeta, at, clock, attempts });
2225
+ }
2226
+ }
2227
+ }
2228
+ async recordCall(fetchFn, url, init) {
2229
+ const at = Date.now();
2230
+ const clock = performance.now();
2231
+ let response;
2232
+ try {
2233
+ response = await fetchFn(url, init);
2234
+ return response;
2235
+ } finally {
2236
+ this.observeAttempt({ url, init, response, at, clock });
2237
+ }
2238
+ }
2239
+ observeAttempt(sample) {
2240
+ try {
2241
+ const replication = replicationOp(sample.url, sample.meta ?? (0, import_requestLog.invocationMetaOf)(sample.init?.headers));
2242
+ if (replication !== void 0) {
2243
+ this.requestSink.record(replicationEvent(sample, replication));
2244
+ return;
2245
+ }
2246
+ const headers = sample.init?.headers;
2247
+ if (sample.meta !== void 0 && typeof headers === "object" && headers !== null) {
2248
+ this.requestSink.remember(headers, sample.meta);
2249
+ }
2250
+ this.requestSink.observe({
2251
+ url: sample.url,
2252
+ requestHeaders: headers,
2253
+ requestBody: sample.init?.body,
2254
+ responseContentLength: sample.response?.headers.get("content-length"),
2255
+ ok: sample.response?.ok === true,
2256
+ code: outcomeCodeOf(sample.response),
2257
+ durationMs: performance.now() - sample.clock,
2258
+ attempts: sample.attempts ?? 1,
2259
+ at: sample.at
2260
+ });
2261
+ } catch {
2262
+ }
2263
+ }
2264
+ };
2265
+ function rememberInvoke(invoke, sink, pending) {
2266
+ return (session, service, path, action, facts) => {
2267
+ const headers = invoke(session, service, path, action, facts);
2268
+ const meta = { service, path, action, spaceId: session.spaceId };
2269
+ pending.meta = meta;
2270
+ sink.remember(headers, meta);
2271
+ return headers;
2272
+ };
2273
+ }
2274
+ function rememberInvokeAny(invokeAny, sink, pending) {
2275
+ return (session, entries, facts) => {
2276
+ const headers = invokeAny(session, entries, facts);
2277
+ const first = entries[0];
2278
+ const meta = first === void 0 ? void 0 : { service: first.service, path: first.path, action: first.action, spaceId: first.spaceId ?? session.spaceId };
2279
+ pending.meta = meta;
2280
+ if (meta !== void 0) sink.remember(headers, meta);
2281
+ return headers;
2282
+ };
2283
+ }
2284
+
2124
2285
  // src/delegation.ts
2125
2286
  var import_sdk_core5 = require("@tinycloud/sdk-core");
2126
2287
  var RuntimeDelegationRejectedError = class extends Error {
@@ -2602,7 +2763,8 @@ function deserializeDelegation(data) {
2602
2763
  // src/TinyCloudNode.ts
2603
2764
  var import_siwe2 = require("siwe");
2604
2765
  var import_sdk_core10 = require("@tinycloud/sdk-core");
2605
- var import_sdk_services2 = require("@tinycloud/sdk-services");
2766
+ var import_sdk_services3 = require("@tinycloud/sdk-services");
2767
+ var import_requestLog2 = require("@tinycloud/sdk-services/telemetry/requestLog");
2606
2768
 
2607
2769
  // src/account/AccountService.ts
2608
2770
  var import_sdk_core6 = require("@tinycloud/sdk-core");
@@ -2620,12 +2782,13 @@ var DelegatedAccess = class {
2620
2782
  this.session = session;
2621
2783
  this._delegation = delegation;
2622
2784
  this.host = host;
2623
- const context = new import_sdk_core7.ServiceContext({
2785
+ const context = createServiceContext({
2624
2786
  invoke,
2625
2787
  invokeAny,
2626
2788
  fetch: lifetime?.fetch ?? globalThis.fetch.bind(globalThis),
2627
2789
  hosts: [host],
2628
- telemetry
2790
+ telemetry,
2791
+ requestSink: lifetime?.requestSink
2629
2792
  });
2630
2793
  this._serviceContext = lifetime ? lifetime.track(context) : context;
2631
2794
  const prefix = this._delegation.path.replace(/\/$/, "");
@@ -2820,6 +2983,16 @@ function legacyParamsToPermissionEntries(actions, path, spaceIdOverride) {
2820
2983
  return entries;
2821
2984
  }
2822
2985
  var DEFAULT_DELEGATION_EXPIRY_MS = import_sdk_core8.EXPIRY.SESSION_MS;
2986
+ var SHARE_NETWORK_NAME_RE = /^share-[a-z0-9][a-z0-9-]{15,35}$/;
2987
+ function grantsCappedDecrypt(path, actions) {
2988
+ if (!actions.some((action) => (0, import_sdk_core8.actionContains)(action, import_sdk_core8.ENCRYPTION.DECRYPT))) {
2989
+ return false;
2990
+ }
2991
+ return !((0, import_sdk_core8.isNetworkId)(path) && SHARE_NETWORK_NAME_RE.test((0, import_sdk_core8.parseNetworkId)(path).name));
2992
+ }
2993
+ function includesDecrypt(entries) {
2994
+ return entries.some((entry) => grantsCappedDecrypt(entry.path, entry.actions));
2995
+ }
2823
2996
  function resolveExpiryMs(expiry) {
2824
2997
  if (expiry === void 0) {
2825
2998
  return DEFAULT_DELEGATION_EXPIRY_MS;
@@ -2987,6 +3160,15 @@ var NodeSecretsService = class {
2987
3160
  if (this.hasPermission(permissionEntries)) {
2988
3161
  return ok();
2989
3162
  }
3163
+ let escalation;
3164
+ try {
3165
+ escalation = this.config.uncoveredEntries?.(permissionEntries) ?? permissionEntries;
3166
+ } catch (error) {
3167
+ return this.escalationError(action, target, error);
3168
+ }
3169
+ if (this.config.uncoveredEntries !== void 0 && escalation.length === 0) {
3170
+ return ok();
3171
+ }
2990
3172
  if (!this.config.canEscalate()) {
2991
3173
  return secretsError(
2992
3174
  import_sdk_core9.ErrorCodes.PERMISSION_DENIED,
@@ -2994,16 +3176,20 @@ var NodeSecretsService = class {
2994
3176
  );
2995
3177
  }
2996
3178
  try {
2997
- await this.config.grantPermissions(permissionEntries);
3179
+ const grant = action === "get" && this.config.grantGetPermissions !== void 0 ? this.config.grantGetPermissions : this.config.grantPermissions;
3180
+ await grant(escalation);
2998
3181
  return this.restoreUnlockAfterEscalation();
2999
3182
  } catch (error) {
3000
- return secretsError(
3001
- import_sdk_core9.ErrorCodes.PERMISSION_DENIED,
3002
- error instanceof Error ? error.message : `Autosign escalation for ${displayActionUrn(action)} on ${target} failed.`,
3003
- error instanceof Error ? error : void 0
3004
- );
3183
+ return this.escalationError(action, target, error);
3005
3184
  }
3006
3185
  }
3186
+ escalationError(action, target, error) {
3187
+ return secretsError(
3188
+ import_sdk_core9.ErrorCodes.PERMISSION_DENIED,
3189
+ error instanceof Error ? error.message : `Autosign escalation for ${displayActionUrn(action)} on ${target} failed.`,
3190
+ error instanceof Error ? error : void 0
3191
+ );
3192
+ }
3007
3193
  async restoreUnlockAfterEscalation() {
3008
3194
  if (!this.shouldRestoreUnlock) {
3009
3195
  return ok();
@@ -3215,11 +3401,12 @@ function createDefaultSessionKey(wasm) {
3215
3401
  return { manager, keyId, jwk: JSON.parse(jwkStr) };
3216
3402
  }
3217
3403
  var ServiceGraphLifetime = class {
3218
- constructor(invokeFn, invokeAnyFn, fetchFn, observeContext) {
3404
+ constructor(invokeFn, invokeAnyFn, fetchFn, observeContext, requestSink) {
3219
3405
  this.invokeFn = invokeFn;
3220
3406
  this.invokeAnyFn = invokeAnyFn;
3221
3407
  this.fetchFn = fetchFn;
3222
3408
  this.observeContext = observeContext;
3409
+ this.requestSink = requestSink;
3223
3410
  this.abortController = new AbortController();
3224
3411
  this.contexts = /* @__PURE__ */ new Set();
3225
3412
  this.retired = false;
@@ -3408,6 +3595,8 @@ function ownerPermissionsToAbilities(permissions) {
3408
3595
  return { abilities, rawAbilities };
3409
3596
  }
3410
3597
  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";
3598
+ var DECRYPT_GRANT_MAX_EXPIRY_MS = 60 * 60 * 1e3;
3599
+ var SECRET_GET_GRANT_EXPIRY_MS = 59 * 60 * 1e3;
3411
3600
  function base64UrlEncode(bytes) {
3412
3601
  const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
3413
3602
  let output = "";
@@ -3790,6 +3979,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3790
3979
  this.sessionManager = sessionKey.manager;
3791
3980
  this.sessionKeyId = sessionKey.keyId;
3792
3981
  this.sessionKeyJwk = sessionKey.jwk;
3982
+ this._requestLog = config.requestLog === true ? new import_requestLog2.RequestLog() : void 0;
3793
3983
  this._serviceGraph = this.createServiceGraphLifetime();
3794
3984
  this._capabilityRegistry = new import_sdk_core10.CapabilityKeyRegistry();
3795
3985
  this._keyProvider = new WasmKeyProvider({
@@ -3961,7 +4151,8 @@ var _TinyCloudNode = class _TinyCloudNode {
3961
4151
  createKVService: (config) => {
3962
4152
  const prefix = config.pathPrefix?.replace(/\/$/, "");
3963
4153
  const kvService = new import_sdk_core10.KVService({ prefix });
3964
- const kvContext = receiveOnlyGraph.track(new import_sdk_core10.ServiceContext({
4154
+ const kvContext = receiveOnlyGraph.track(createServiceContext({
4155
+ requestSink: receiveOnlyGraph.requestSink,
3965
4156
  invoke: config.invoke,
3966
4157
  fetch: config.fetch ?? globalThis.fetch.bind(globalThis),
3967
4158
  hosts: config.hosts,
@@ -4024,6 +4215,10 @@ var _TinyCloudNode = class _TinyCloudNode {
4024
4215
  shouldUseBootstrapSignInRequest(config) {
4025
4216
  return config.autoBootstrapAccount !== false && config.manifest === void 0 && config.capabilityRequest === void 0 && (config.prefix ?? "default") === "default" && isOpenKeyAutoSignStrategy(config.signStrategy);
4026
4217
  }
4218
+ /** Sanitized developer request events; undefined unless the node was built with `requestLog: true`. */
4219
+ get requests() {
4220
+ return this._requestLog === void 0 ? void 0 : (0, import_requestLog2.readOnlyRequestLog)(this._requestLog);
4221
+ }
4027
4222
  createServiceGraphLifetime() {
4028
4223
  return new ServiceGraphLifetime(
4029
4224
  this.invokeWithRuntimePermissions,
@@ -4034,7 +4229,9 @@ var _TinyCloudNode = class _TinyCloudNode {
4034
4229
  (url, init) => globalThis.fetch(url, init),
4035
4230
  (context) => {
4036
4231
  this.storageFull.observe(context);
4037
- }
4232
+ if (this._requestLog) (0, import_requestLog2.observeReplicaReads)(context, this._requestLog);
4233
+ },
4234
+ this._requestLog
4038
4235
  );
4039
4236
  }
4040
4237
  syncResolvedHostFromAuth() {
@@ -4357,6 +4554,7 @@ var _TinyCloudNode = class _TinyCloudNode {
4357
4554
  this.hostedOwnedSpaceIds.clear();
4358
4555
  this.cancelOwnedSpaceHosts();
4359
4556
  this.storageFull.clear();
4557
+ this._requestLog?.clear();
4360
4558
  this._bootstrapSkipped = false;
4361
4559
  this._bootstrapStatus = { skipped: false };
4362
4560
  this.replicationDelegateSession = void 0;
@@ -4448,7 +4646,10 @@ var _TinyCloudNode = class _TinyCloudNode {
4448
4646
  const denial = await this.denyReplicaGate({ reason: "account_switch", principal: priorPrincipal, next: nextPrincipal });
4449
4647
  this.replicaRestoreGate = denial?.status === "unguarded" ? "gate_unavailable" : "stamp_stale";
4450
4648
  }
4451
- if (address !== this._address || chainId !== this._chainId) this.storageFull.clear();
4649
+ if (address !== this._address || chainId !== this._chainId) {
4650
+ this.storageFull.clear();
4651
+ this._requestLog?.clear();
4652
+ }
4452
4653
  this._address = address;
4453
4654
  this._chainId = chainId;
4454
4655
  this._kv = void 0;
@@ -5027,10 +5228,11 @@ var _TinyCloudNode = class _TinyCloudNode {
5027
5228
  const contexts = [context];
5028
5229
  const accountSpaceId = this.ownedSpaceId(import_sdk_core10.ACCOUNT_REGISTRY_SPACE);
5029
5230
  const createScopedContext = (spaceId) => {
5030
- const scoped = this._serviceGraph.track(new import_sdk_core10.ServiceContext({
5231
+ const scoped = this._serviceGraph.track(createServiceContext({
5232
+ requestSink: this._serviceGraph.requestSink,
5031
5233
  invoke: context.invoke,
5032
5234
  invokeAny: context.invokeAny,
5033
- fetch: context.fetch,
5235
+ fetch: unrecordedFetchOf(context),
5034
5236
  hosts: context.hosts,
5035
5237
  telemetry: this.config.telemetry,
5036
5238
  retryPolicy: this.config.retryPolicy
@@ -5082,7 +5284,7 @@ var _TinyCloudNode = class _TinyCloudNode {
5082
5284
  if (!this.currentSessionCoversAccountRegistry()) {
5083
5285
  if (!_TinyCloudNode.accountRegistrySkipLogged) {
5084
5286
  _TinyCloudNode.accountRegistrySkipLogged = true;
5085
- import_sdk_services2.tinyCloudDebugLogger.log("registry.skip");
5287
+ import_sdk_services3.tinyCloudDebugLogger.log("registry.skip");
5086
5288
  }
5087
5289
  return;
5088
5290
  }
@@ -5094,10 +5296,11 @@ var _TinyCloudNode = class _TinyCloudNode {
5094
5296
  const promise = this.enqueueAccountRegistryOperation(session, async () => {
5095
5297
  const sharedContext = this._serviceContext;
5096
5298
  if (!sharedContext) return;
5097
- const context = this._serviceGraph.track(new import_sdk_core10.ServiceContext({
5299
+ const context = this._serviceGraph.track(createServiceContext({
5300
+ requestSink: this._serviceGraph.requestSink,
5098
5301
  invoke: sharedContext.invoke,
5099
5302
  invokeAny: sharedContext.invokeAny,
5100
- fetch: sharedContext.fetch,
5303
+ fetch: unrecordedFetchOf(sharedContext),
5101
5304
  hosts: sharedContext.hosts,
5102
5305
  telemetry: this.config.telemetry,
5103
5306
  retryPolicy: this.config.retryPolicy
@@ -5831,6 +6034,7 @@ var _TinyCloudNode = class _TinyCloudNode {
5831
6034
  if (resolvedHost) this.config.host = resolvedHost;
5832
6035
  if (spaceOwnerKey(serviceSession.spaceId) !== this.currentStorageOwner()) {
5833
6036
  this.storageFull.clear();
6037
+ this._requestLog?.clear();
5834
6038
  }
5835
6039
  this._address = stagedAddress;
5836
6040
  this._chainId = stagedChainId;
@@ -5950,7 +6154,8 @@ var _TinyCloudNode = class _TinyCloudNode {
5950
6154
  const coreContext = graph.track(core.serviceContext);
5951
6155
  coreContext.setSession(input.serviceSession);
5952
6156
  }
5953
- const serviceContext = graph.track(new import_sdk_core10.ServiceContext({
6157
+ const serviceContext = graph.track(createServiceContext({
6158
+ requestSink: graph.requestSink,
5954
6159
  invoke: graph.invoke,
5955
6160
  invokeAny: graph.invokeAny,
5956
6161
  fetch: graph.fetch,
@@ -6038,7 +6243,8 @@ var _TinyCloudNode = class _TinyCloudNode {
6038
6243
  },
6039
6244
  createKVService: (config) => {
6040
6245
  const service = new import_sdk_core10.KVService({ prefix: config.pathPrefix?.replace(/\/$/, "") });
6041
- const context = graph.track(new import_sdk_core10.ServiceContext({
6246
+ const context = graph.track(createServiceContext({
6247
+ requestSink: graph.requestSink,
6042
6248
  invoke: config.invoke,
6043
6249
  fetch: config.fetch ?? globalThis.fetch.bind(globalThis),
6044
6250
  hosts: config.hosts,
@@ -6078,7 +6284,8 @@ var _TinyCloudNode = class _TinyCloudNode {
6078
6284
  createKVService: (spaceId) => {
6079
6285
  graph.assertActive();
6080
6286
  const scopedKv = new import_sdk_core10.KVService({});
6081
- const context = graph.track(new import_sdk_core10.ServiceContext({
6287
+ const context = graph.track(createServiceContext({
6288
+ requestSink: graph.requestSink,
6082
6289
  invoke: graph.invoke,
6083
6290
  invokeAny: graph.invokeAny,
6084
6291
  fetch: graph.fetch,
@@ -6094,7 +6301,8 @@ var _TinyCloudNode = class _TinyCloudNode {
6094
6301
  createVaultService: (spaceId) => {
6095
6302
  graph.assertActive();
6096
6303
  const scopedKv = new import_sdk_core10.KVService({});
6097
- const context = graph.track(new import_sdk_core10.ServiceContext({
6304
+ const context = graph.track(createServiceContext({
6305
+ requestSink: graph.requestSink,
6098
6306
  invoke: graph.invoke,
6099
6307
  invokeAny: graph.invokeAny,
6100
6308
  fetch: graph.fetch,
@@ -6493,7 +6701,8 @@ var _TinyCloudNode = class _TinyCloudNode {
6493
6701
  this._serviceGraph.fetch
6494
6702
  );
6495
6703
  this._serviceGraph.track(this.tc.serviceContext);
6496
- this._serviceContext = this._serviceGraph.track(new import_sdk_core10.ServiceContext({
6704
+ this._serviceContext = this._serviceGraph.track(createServiceContext({
6705
+ requestSink: this._serviceGraph.requestSink,
6497
6706
  invoke: this._serviceGraph.invoke,
6498
6707
  invokeAny: this._serviceGraph.invokeAny,
6499
6708
  fetch: this._serviceGraph.fetch,
@@ -6541,10 +6750,11 @@ var _TinyCloudNode = class _TinyCloudNode {
6541
6750
  createSpaceScopedKVService(spaceId) {
6542
6751
  const kvService = new import_sdk_core10.KVService({});
6543
6752
  if (this._serviceContext) {
6544
- const spaceScopedContext = this._serviceGraph.track(new import_sdk_core10.ServiceContext({
6753
+ const spaceScopedContext = this._serviceGraph.track(createServiceContext({
6754
+ requestSink: this._serviceGraph.requestSink,
6545
6755
  invoke: this._serviceContext.invoke,
6546
6756
  invokeAny: this._serviceContext.invokeAny,
6547
- fetch: this._serviceContext.fetch,
6757
+ fetch: unrecordedFetchOf(this._serviceContext),
6548
6758
  hosts: this._serviceContext.hosts,
6549
6759
  telemetry: this.config.telemetry,
6550
6760
  retryPolicy: this.config.retryPolicy
@@ -6732,7 +6942,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6732
6942
  );
6733
6943
  graph.assertActive();
6734
6944
  if (!response.ok) {
6735
- const permissionHint = response.status === 401 || response.status === 403 ? (0, import_sdk_services2.parsePermissionHint)({
6945
+ const permissionHint = response.status === 401 || response.status === 403 ? (0, import_sdk_services3.parsePermissionHint)({
6736
6946
  service: "tinycloud.encryption",
6737
6947
  path: networkId,
6738
6948
  actions: ["tinycloud.encryption/decrypt"]
@@ -7010,7 +7220,8 @@ var _TinyCloudNode = class _TinyCloudNode {
7010
7220
  createKVService: (config) => {
7011
7221
  graph.assertActive();
7012
7222
  const service = new import_sdk_core10.KVService({ prefix: config.pathPrefix?.replace(/\/$/, "") });
7013
- const context = graph.track(new import_sdk_core10.ServiceContext({
7223
+ const context = graph.track(createServiceContext({
7224
+ requestSink: graph.requestSink,
7014
7225
  invoke: config.invoke,
7015
7226
  fetch: config.fetch ?? graph.fetch,
7016
7227
  hosts: config.hosts,
@@ -7331,10 +7542,11 @@ var _TinyCloudNode = class _TinyCloudNode {
7331
7542
  throw new Error("Not signed in. Call signIn() first.");
7332
7543
  }
7333
7544
  const sql = new import_sdk_core10.SQLService({});
7334
- const spaceScopedContext = this._serviceGraph.track(new import_sdk_core10.ServiceContext({
7545
+ const spaceScopedContext = this._serviceGraph.track(createServiceContext({
7546
+ requestSink: this._serviceGraph.requestSink,
7335
7547
  invoke: this._serviceContext.invoke,
7336
7548
  invokeAny: this._serviceContext.invokeAny,
7337
- fetch: this._serviceContext.fetch,
7549
+ fetch: unrecordedFetchOf(this._serviceContext),
7338
7550
  hosts: this._serviceContext.hosts,
7339
7551
  telemetry: this.config.telemetry,
7340
7552
  retryPolicy: this.config.retryPolicy
@@ -7362,10 +7574,11 @@ var _TinyCloudNode = class _TinyCloudNode {
7362
7574
  throw new Error("Not signed in. Call signIn() first.");
7363
7575
  }
7364
7576
  const kv = new import_sdk_core10.KVService({});
7365
- const spaceScopedContext = this._serviceGraph.track(new import_sdk_core10.ServiceContext({
7577
+ const spaceScopedContext = this._serviceGraph.track(createServiceContext({
7578
+ requestSink: this._serviceGraph.requestSink,
7366
7579
  invoke: this._serviceContext.invoke,
7367
7580
  invokeAny: this._serviceContext.invokeAny,
7368
- fetch: this._serviceContext.fetch,
7581
+ fetch: unrecordedFetchOf(this._serviceContext),
7369
7582
  hosts: this._serviceContext.hosts
7370
7583
  }));
7371
7584
  spaceScopedContext.setSession({ ...this._serviceContext.session, spaceId });
@@ -7506,6 +7719,8 @@ var _TinyCloudNode = class _TinyCloudNode {
7506
7719
  getManifest: () => this.manifest,
7507
7720
  hasPermissions: (permissions) => this.hasRuntimePermissions(permissions),
7508
7721
  grantPermissions: (additional) => this.grantRuntimePermissions(additional),
7722
+ grantGetPermissions: (additional) => this.grantRuntimePermissions(additional, { expiry: SECRET_GET_GRANT_EXPIRY_MS }),
7723
+ uncoveredEntries: (permissions) => permissions.filter((entry) => !this.hasRuntimePermissions([entry])),
7509
7724
  canEscalate: () => this.signer !== void 0 && this.tc !== void 0,
7510
7725
  getEncryptionNetworkId: () => this.getEncryptionNetworkIdForSpace(resolvedSpace),
7511
7726
  resolveSpace: (space) => space.startsWith("tinycloud:") ? space : this.ownedSpaceId(space),
@@ -7528,7 +7743,7 @@ var _TinyCloudNode = class _TinyCloudNode {
7528
7743
  );
7529
7744
  if (result.status !== "ok") {
7530
7745
  if (result.status === "permission_required") {
7531
- const hint = (0, import_sdk_services2.parsePermissionHint)(result.hint);
7746
+ const hint = (0, import_sdk_services3.parsePermissionHint)(result.hint);
7532
7747
  if (hint === void 0) return { status: "read_failed" };
7533
7748
  return {
7534
7749
  status: "permission_required",
@@ -7853,6 +8068,13 @@ var _TinyCloudNode = class _TinyCloudNode {
7853
8068
  }
7854
8069
  }
7855
8070
  const expanded = this.expandPermissionEntries(permissions);
8071
+ const requestsDecrypt = includesDecrypt(expanded);
8072
+ const requestedExpiryMs = requestsDecrypt && options?.expiry === void 0 ? DECRYPT_GRANT_MAX_EXPIRY_MS : resolveExpiryMs(options?.expiry);
8073
+ if (requestsDecrypt && requestedExpiryMs > DECRYPT_GRANT_MAX_EXPIRY_MS) {
8074
+ throw Object.assign(new Error("decrypt grants last at most one hour"), {
8075
+ code: "DECRYPT_EXPIRY_TOO_LONG"
8076
+ });
8077
+ }
7856
8078
  if (this.sessionCoversPermissionEntries(session, expanded)) {
7857
8079
  return [];
7858
8080
  }
@@ -7883,7 +8105,6 @@ var _TinyCloudNode = class _TinyCloudNode {
7883
8105
  bySpace.set(session.spaceId, []);
7884
8106
  }
7885
8107
  const now = /* @__PURE__ */ new Date();
7886
- const requestedExpiryMs = resolveExpiryMs(options?.expiry);
7887
8108
  let expiresAt = new Date(now.getTime() + requestedExpiryMs);
7888
8109
  if (sessionExpiry !== void 0 && sessionExpiry < expiresAt) {
7889
8110
  expiresAt = sessionExpiry;
@@ -8151,7 +8372,8 @@ var _TinyCloudNode = class _TinyCloudNode {
8151
8372
  }
8152
8373
  if (this._serviceContext) {
8153
8374
  const publicKV = new import_sdk_core10.KVService({ prefix: "" });
8154
- const publicContext = this._serviceGraph.track(new import_sdk_core10.ServiceContext({
8375
+ const publicContext = this._serviceGraph.track(createServiceContext({
8376
+ requestSink: this._serviceGraph.requestSink,
8155
8377
  invoke: this._serviceGraph.invoke,
8156
8378
  fetch: this._serviceGraph.fetch,
8157
8379
  hosts: this._serviceContext.hosts,