@tinycloud/operations 0.3.4-beta.6 → 0.4.0-beta.8

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.cjs CHANGED
@@ -6967,6 +6967,9 @@ function evaluateOperationAuthority(granted, required, resolveSpace2) {
6967
6967
  missing: canonicalizeCapabilities(missing)
6968
6968
  };
6969
6969
  }
6970
+ function delegationWithinRequest(requested, effective, resolveSpace2) {
6971
+ return requested.length > 0 && evaluateOperationAuthority(requested, canonicalizeCapabilities(effective), resolveSpace2).satisfied;
6972
+ }
6970
6973
  function isExactCapabilityMemberSubset(hinted, planned, resolveSpace2) {
6971
6974
  const canonicalHinted = canonicalizeOperationCapabilities(hinted, resolveSpace2);
6972
6975
  const canonicalPlanned = canonicalizeOperationCapabilities(planned, resolveSpace2);
@@ -7079,6 +7082,11 @@ var init_authority = __esm({
7079
7082
  });
7080
7083
 
7081
7084
  // src/state.ts
7085
+ function processWideContext(key) {
7086
+ var _a2;
7087
+ const registry = globalThis;
7088
+ return registry[_a2 = Symbol.for(key)] ?? (registry[_a2] = new import_node_async_hooks.AsyncLocalStorage());
7089
+ }
7082
7090
  function tinycloudHomePath() {
7083
7091
  const home = invocationStateRoot.getStore() ?? process.env.TC_HOME ?? process.env.HOME ?? process.env.USERPROFILE ?? (0, import_node_os.homedir)();
7084
7092
  return (0, import_node_path.join)(home, ".tinycloud");
@@ -7117,6 +7125,9 @@ function profileStoreMetadataPath(profile, store) {
7117
7125
  function profileLockPath(profile) {
7118
7126
  return (0, import_node_path.join)(profilePath(profile), ".lock");
7119
7127
  }
7128
+ function profileTurnLockPath(profile) {
7129
+ return (0, import_node_path.join)(tinycloudHomePath(), "profile-locks", validateProfileName(profile));
7130
+ }
7120
7131
  async function readJson(filePath) {
7121
7132
  try {
7122
7133
  return JSON.parse(await (0, import_promises.readFile)(filePath, "utf8"));
@@ -7126,6 +7137,9 @@ async function readJson(filePath) {
7126
7137
  }
7127
7138
  }
7128
7139
  async function writeJsonAtomic(filePath, value) {
7140
+ await replaceJson(filePath, value, false);
7141
+ }
7142
+ async function replaceJson(filePath, value, durable) {
7129
7143
  const directory = (0, import_node_path.dirname)(filePath);
7130
7144
  const temporaryPath = (0, import_node_path.join)(
7131
7145
  directory,
@@ -7135,13 +7149,25 @@ async function writeJsonAtomic(filePath, value) {
7135
7149
  `;
7136
7150
  await (0, import_promises.mkdir)(directory, { recursive: true, mode: PRIVATE_DIR_MODE });
7137
7151
  try {
7138
- await (0, import_promises.writeFile)(temporaryPath, contents, { encoding: "utf8", mode: PRIVATE_FILE_MODE });
7152
+ if (durable) await writeFileDurably(temporaryPath, contents);
7153
+ else await (0, import_promises.writeFile)(temporaryPath, contents, { encoding: "utf8", mode: PRIVATE_FILE_MODE });
7139
7154
  await (0, import_promises.rename)(temporaryPath, filePath);
7140
7155
  } catch (error) {
7141
7156
  await (0, import_promises.rm)(temporaryPath, { force: true }).catch(() => void 0);
7142
7157
  throw error;
7143
7158
  }
7144
7159
  }
7160
+ async function writeFileDurably(path, contents) {
7161
+ const handle = await (0, import_promises.open)(path, "wx", PRIVATE_FILE_MODE);
7162
+ try {
7163
+ if (contents) {
7164
+ await handle.writeFile(contents, "utf8");
7165
+ await handle.datasync();
7166
+ }
7167
+ } finally {
7168
+ await handle.close();
7169
+ }
7170
+ }
7145
7171
  async function readProfile(profile) {
7146
7172
  return readJson(profileConfigPath(profile));
7147
7173
  }
@@ -7178,15 +7204,28 @@ async function withProfileLock(profile, action, options = {}) {
7178
7204
  const lockPath = profileLockPath(normalizedProfile);
7179
7205
  const held = (heldProfileLocks.getStore() ?? []).filter((ownership2) => ownership2.active);
7180
7206
  if (held.some((ownership2) => ownership2.lockPath === lockPath)) return action();
7181
- const release = await acquireProfileLock(normalizedProfile, options);
7182
- const ownership = { lockPath, active: true };
7207
+ const acquired = await acquireProfileLock(normalizedProfile, options);
7208
+ const ownership = {
7209
+ lockPath,
7210
+ turnSlot: acquired.turnSlot,
7211
+ deletedWhileWaiting: acquired.deletedWhileWaiting,
7212
+ active: true
7213
+ };
7183
7214
  try {
7184
7215
  return await heldProfileLocks.run([...held, ownership], action);
7185
7216
  } finally {
7186
7217
  ownership.active = false;
7187
- await release();
7218
+ await acquired.release();
7219
+ if (acquired.deletedWhileWaiting) await (0, import_promises.rmdir)(profilePath(normalizedProfile)).catch(() => void 0);
7188
7220
  }
7189
7221
  }
7222
+ async function refuseWriteToDeletedProfile(profile) {
7223
+ const lockPath = profileLockPath(profile);
7224
+ const ownership = (heldProfileLocks.getStore() ?? []).find((candidate) => candidate.active && candidate.lockPath === lockPath);
7225
+ if (!ownership?.deletedWhileWaiting) return;
7226
+ if (await exists(profileConfigPath(profile))) return;
7227
+ throw new ProfileDeletedError(profile);
7228
+ }
7190
7229
  async function updateProfileStore(profile, store, update, options = {}) {
7191
7230
  return withProfileLock(
7192
7231
  profile,
@@ -7195,6 +7234,7 @@ async function updateProfileStore(profile, store, update, options = {}) {
7195
7234
  );
7196
7235
  }
7197
7236
  async function updateProfileStoreWhileLocked(profile, store, update) {
7237
+ await refuseWriteToDeletedProfile(profile);
7198
7238
  const current = await readProfileStore(profile, store);
7199
7239
  const next = await update(current.records);
7200
7240
  await writeJsonAtomic(profileStorePath(profile, store), next.records);
@@ -7205,32 +7245,271 @@ async function writeFormatOneMetadata(profile, store) {
7205
7245
  await writeJsonAtomic(profileStoreMetadataPath(profile, store), { formatVersion: 1 });
7206
7246
  }
7207
7247
  async function acquireProfileLock(profile, options) {
7208
- const timeoutMs = options.timeoutMs ?? DEFAULT_LOCK_TIMEOUT_MS;
7209
- const retryMs = options.retryMs ?? DEFAULT_LOCK_RETRY_MS;
7248
+ const deadline = {
7249
+ startedAt: Date.now(),
7250
+ timeoutMs: options.timeoutMs ?? DEFAULT_LOCK_TIMEOUT_MS,
7251
+ retryMs: options.retryMs ?? DEFAULT_LOCK_RETRY_MS
7252
+ };
7210
7253
  const staleAfterMs = options.staleAfterMs ?? DEFAULT_STALE_LOCK_MS;
7211
- const startedAt = Date.now();
7212
7254
  const lockPath = profileLockPath(profile);
7255
+ const turnDirectory = profileTurnLockPath(profile);
7213
7256
  const directory = profilePath(profile);
7214
7257
  await (0, import_promises.mkdir)(directory, { recursive: true, mode: PRIVATE_DIR_MODE });
7258
+ await (0, import_promises.mkdir)((0, import_node_path.dirname)(turnDirectory), { recursive: true, mode: PRIVATE_DIR_MODE });
7215
7259
  for (const path of [tinycloudHomePath(), profilesPath(), directory]) {
7216
7260
  await (0, import_promises.chmod)(path, PRIVATE_DIR_MODE).catch((error) => {
7217
7261
  if (path !== directory || !isErrno(error, "ENOENT")) throw error;
7218
7262
  });
7219
7263
  }
7220
- while (true) {
7221
- const token = (0, import_node_crypto.randomUUID)();
7222
- if (await publishProfileLock(profile, lockPath, token)) {
7223
- return async () => {
7224
- await releaseProfileLock(profile, lockPath, token);
7225
- };
7264
+ const { turn, firstWaitedTurn } = await acquireTurn(profile, turnDirectory, deadline);
7265
+ try {
7266
+ const deletion = await readJson((0, import_node_path.join)(turnDirectory, "deleted.json")).catch(() => null);
7267
+ const deletedWhileWaiting = typeof deletion?.slot === "number" && deletion.slot >= firstWaitedTurn;
7268
+ while (true) {
7269
+ const token = (0, import_node_crypto.randomUUID)();
7270
+ if (await publishProfileLock(profile, lockPath, token)) {
7271
+ return {
7272
+ deletedWhileWaiting,
7273
+ turnSlot: turn.slot,
7274
+ release: async () => {
7275
+ try {
7276
+ await releaseProfileLock(profile, lockPath, token);
7277
+ } finally {
7278
+ await releaseTurn(profile, turn);
7279
+ }
7280
+ }
7281
+ };
7282
+ }
7283
+ await signalTestLockContention(profile);
7284
+ if (await recoverStaleLock(profile, lockPath, staleAfterMs)) continue;
7285
+ await waitOrTimeOut(profile, deadline);
7286
+ }
7287
+ } catch (error) {
7288
+ await releaseTurn(profile, turn);
7289
+ throw error;
7290
+ }
7291
+ }
7292
+ function checkDeadline(profile, deadline, detail) {
7293
+ if (Date.now() - deadline.startedAt >= deadline.timeoutMs) {
7294
+ throw new ProfileLockTimeoutError(profile, deadline.timeoutMs, detail);
7295
+ }
7296
+ }
7297
+ async function waitOrTimeOut(profile, deadline, detail) {
7298
+ checkDeadline(profile, deadline, detail);
7299
+ await sleep(Math.min(deadline.retryMs, deadline.timeoutMs - (Date.now() - deadline.startedAt)));
7300
+ }
7301
+ async function acquireTurn(profile, directory, deadline) {
7302
+ let firstWaitedTurn;
7303
+ let problem;
7304
+ const damaged = (what) => `The turn lock ${directory} ${what}, so no process can take it. If no tc or MCP process is using profile "${profile}", remove that directory.`;
7305
+ for (let first = true; ; first = false) {
7306
+ if (!first) checkDeadline(profile, deadline, problem);
7307
+ problem = void 0;
7308
+ const latest = await readLatestTurn(directory);
7309
+ if (latest === "missing") {
7310
+ await createTurnDirectory(directory);
7311
+ continue;
7312
+ }
7313
+ if (latest === "empty") {
7314
+ problem = damaged("has no turns");
7315
+ await waitOrTimeOut(profile, deadline, problem);
7316
+ continue;
7317
+ }
7318
+ if (latest === null) continue;
7319
+ if (typeof latest === "number") {
7320
+ problem = damaged(`has an unreadable turn ${latest}`);
7321
+ await waitOrTimeOut(profile, deadline, problem);
7322
+ continue;
7226
7323
  }
7227
- await signalTestLockContention(profile);
7228
- if (await recoverStaleLock(profile, lockPath, staleAfterMs)) continue;
7229
- const elapsedMs = Date.now() - startedAt;
7230
- if (elapsedMs >= timeoutMs) {
7231
- throw new ProfileLockTimeoutError(profile, timeoutMs);
7324
+ firstWaitedTurn ?? (firstWaitedTurn = latest.done ? latest.slot + 1 : latest.slot);
7325
+ if (latest.held && latest.done) {
7326
+ const turn = await takeTurn(profile, directory, latest);
7327
+ if (turn !== null) return { turn, firstWaitedTurn };
7328
+ await waitOrTimeOut(profile, deadline);
7329
+ } else if (latest.done) {
7330
+ problem = damaged(`ends with turn ${latest.slot}, which was never granted`);
7331
+ await waitOrTimeOut(profile, deadline, problem);
7332
+ } else if (latest.pid === process.pid ? relinquishedTurns.has(latest.token) : !isProcessAlive(latest.pid)) {
7333
+ await waitForTestBarrier(TEST_LOCK_TURN_SETTLE_BARRIER_DIR, profile);
7334
+ await settleAbandonedTurn(directory, latest);
7335
+ relinquishedTurns.delete(latest.token);
7336
+ } else {
7337
+ await signalTestLockContention(profile);
7338
+ await waitOrTimeOut(profile, deadline);
7339
+ }
7340
+ }
7341
+ }
7342
+ async function readLatestTurn(directory) {
7343
+ let names;
7344
+ try {
7345
+ names = await (0, import_promises.readdir)(directory);
7346
+ } catch (error) {
7347
+ if (isErrno(error, "ENOENT")) return "missing";
7348
+ throw error;
7349
+ }
7350
+ const slots = names.filter((name) => TURN.test(name)).map(Number);
7351
+ if (slots.length === 0) return "empty";
7352
+ const slot = Math.max(...slots);
7353
+ const turn = await readTurn(directory, slot);
7354
+ return turn === "damaged" ? slot : turn;
7355
+ }
7356
+ async function readTurn(directory, slot) {
7357
+ const path = (0, import_node_path.join)(directory, String(slot));
7358
+ let text;
7359
+ try {
7360
+ text = await (0, import_promises.readFile)((0, import_node_path.join)(path, "owner.json"), "utf8");
7361
+ } catch (error) {
7362
+ if (!isErrno(error, "ENOENT") && !isErrno(error, "ENOTDIR")) throw error;
7363
+ return await exists(path) ? "damaged" : null;
7364
+ }
7365
+ let owner;
7366
+ try {
7367
+ owner = JSON.parse(text);
7368
+ } catch {
7369
+ return "damaged";
7370
+ }
7371
+ if (owner === null || typeof owner !== "object" || typeof owner.token !== "string" || typeof owner.pid !== "number") {
7372
+ return "damaged";
7373
+ }
7374
+ const done = await exists((0, import_node_path.join)(path, `${owner.token}.done`));
7375
+ const held = await exists((0, import_node_path.join)(path, `${owner.token}.held`));
7376
+ return {
7377
+ slot,
7378
+ token: owner.token,
7379
+ pid: owner.pid,
7380
+ after: typeof owner.after === "string" ? owner.after : null,
7381
+ held,
7382
+ done
7383
+ };
7384
+ }
7385
+ async function isFinishedTurn(directory, slot, token) {
7386
+ const turn = await readTurn(directory, slot);
7387
+ if (turn === "damaged") throw new Error(`Turn ${slot} of the turn lock ${directory} is unreadable.`);
7388
+ return turn !== null && turn.token === token && turn.held && turn.done;
7389
+ }
7390
+ async function takeTurn(profile, directory, latest) {
7391
+ const token = (0, import_node_crypto.randomUUID)();
7392
+ const slot = latest.slot + 1;
7393
+ const path = (0, import_node_path.join)(directory, String(slot));
7394
+ const owner = { pid: process.pid, createdAt: (/* @__PURE__ */ new Date()).toISOString(), token, after: latest.token };
7395
+ let published = false;
7396
+ try {
7397
+ published = await publishDirectory(
7398
+ (0, import_node_path.join)(directory, `.stage-${process.pid}-${token}`),
7399
+ path,
7400
+ { "owner.json": `${JSON.stringify(owner, null, 2)}
7401
+ ` },
7402
+ () => waitForTestBarrier(TEST_LOCK_TURN_PUBLISH_BARRIER_DIR, profile)
7403
+ );
7404
+ if (published && await isFinishedTurn(directory, latest.slot, latest.token) && await markTurn(path, token, "held")) {
7405
+ return { directory, slot, token };
7406
+ }
7407
+ if (published) await markTurn(path, token, "done");
7408
+ return null;
7409
+ } catch (error) {
7410
+ if (published) settleOwnTurn(directory, slot, token);
7411
+ throw error;
7412
+ }
7413
+ }
7414
+ async function settleAbandonedTurn(directory, abandoned) {
7415
+ const path = (0, import_node_path.join)(directory, String(abandoned.slot));
7416
+ if (!abandoned.held && abandoned.after !== null && await isFinishedTurn(directory, abandoned.slot - 1, abandoned.after)) {
7417
+ await markTurn(path, abandoned.token, "held");
7418
+ }
7419
+ await markTurn(path, abandoned.token, "done");
7420
+ }
7421
+ function settleOwnTurn(directory, slot, token, retries = SETTLE_RETRIES) {
7422
+ relinquishedTurns.add(token);
7423
+ void (async () => {
7424
+ try {
7425
+ const turn = await readTurn(directory, slot);
7426
+ if (turn !== null && turn !== "damaged" && turn.token === token && !turn.done) await settleAbandonedTurn(directory, turn);
7427
+ relinquishedTurns.delete(token);
7428
+ } catch {
7429
+ if (retries > 0) setTimeout(() => settleOwnTurn(directory, slot, token, retries - 1), SETTLE_RETRY_MS).unref();
7430
+ }
7431
+ })();
7432
+ }
7433
+ async function markTurn(path, token, kind) {
7434
+ for (let attempt = 0; ; attempt++) {
7435
+ try {
7436
+ await (0, import_promises.writeFile)((0, import_node_path.join)(path, `${token}.${kind}`), "", { mode: PRIVATE_FILE_MODE, flag: "wx" });
7437
+ return true;
7438
+ } catch (error) {
7439
+ if (isErrno(error, "EEXIST")) return true;
7440
+ if (isErrno(error, "ENOENT")) return false;
7441
+ if (attempt >= MARKER_RETRY_DELAYS_MS.length) throw error;
7442
+ await sleep(MARKER_RETRY_DELAYS_MS[attempt]);
7443
+ }
7444
+ }
7445
+ }
7446
+ async function createTurnDirectory(directory) {
7447
+ const token = (0, import_node_crypto.randomUUID)();
7448
+ const owner = { pid: process.pid, createdAt: (/* @__PURE__ */ new Date()).toISOString(), token, after: null };
7449
+ await publishDirectory((0, import_node_path.join)((0, import_node_path.dirname)(directory), `.stage-${(0, import_node_path.basename)(directory)}-${process.pid}-${token}`), directory, {
7450
+ "0/owner.json": `${JSON.stringify(owner, null, 2)}
7451
+ `,
7452
+ [`0/${token}.held`]: "",
7453
+ [`0/${token}.done`]: ""
7454
+ });
7455
+ }
7456
+ async function publishDirectory(stage, target, files, beforeRename) {
7457
+ try {
7458
+ for (const [name, contents] of Object.entries(files)) {
7459
+ await (0, import_promises.mkdir)((0, import_node_path.dirname)((0, import_node_path.join)(stage, name)), { recursive: true, mode: PRIVATE_DIR_MODE });
7460
+ await writeFileDurably((0, import_node_path.join)(stage, name), contents);
7461
+ }
7462
+ await beforeRename?.();
7463
+ await (0, import_promises.rename)(stage, target);
7464
+ return true;
7465
+ } catch (error) {
7466
+ if (["ENOTEMPTY", "EEXIST", "ENOENT"].some((code) => isErrno(error, code)) || await exists(target)) return false;
7467
+ throw error;
7468
+ } finally {
7469
+ await (0, import_promises.rm)(stage, { recursive: true, force: true }).catch(() => void 0);
7470
+ }
7471
+ }
7472
+ async function releaseTurn(profile, turn) {
7473
+ try {
7474
+ await markTurn((0, import_node_path.join)(turn.directory, String(turn.slot)), turn.token, "done");
7475
+ } catch {
7476
+ settleOwnTurn(turn.directory, turn.slot, turn.token);
7477
+ return;
7478
+ }
7479
+ await collectTurns(profile, turn).catch(() => void 0);
7480
+ }
7481
+ function isAbandonedTurnEntry(name) {
7482
+ if (name.startsWith(".trash-")) return true;
7483
+ const writer = /^\.(?:stage|deleted\.json)[-.]([0-9]+)[-.]/.exec(name)?.[1];
7484
+ return writer !== void 0 && !isProcessAlive(Number(writer));
7485
+ }
7486
+ function isAbandonedTurnDirectoryStage(name) {
7487
+ const writer = /^\.stage-.+-([0-9]+)-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.exec(name)?.[1];
7488
+ return writer !== void 0 && !isProcessAlive(Number(writer));
7489
+ }
7490
+ async function collectTurns(profile, turn) {
7491
+ for (const [directory, abandoned] of [
7492
+ [turn.directory, isAbandonedTurnEntry],
7493
+ [(0, import_node_path.dirname)(turn.directory), isAbandonedTurnDirectoryStage]
7494
+ ]) {
7495
+ for (const name of (await (0, import_promises.readdir)(directory)).filter(abandoned)) {
7496
+ await (0, import_promises.rm)((0, import_node_path.join)(directory, name), { recursive: true, force: true }).catch(() => void 0);
7497
+ }
7498
+ }
7499
+ const older = (await (0, import_promises.readdir)(turn.directory)).filter((name) => TURN.test(name)).map(Number).filter((slot) => slot < turn.slot).sort((left, right) => left - right);
7500
+ for (const slot of older) {
7501
+ const candidate = await readTurn(turn.directory, slot);
7502
+ if (candidate === null || candidate === "damaged" || !candidate.done) continue;
7503
+ if (candidate.held) {
7504
+ if (await exists((0, import_node_path.join)(turn.directory, String(slot - 1)))) continue;
7505
+ const next = await readTurn(turn.directory, slot + 1);
7506
+ if (next === null || next === "damaged" || !next.held || !next.done) continue;
7507
+ }
7508
+ await waitForTestBarrier(TEST_LOCK_TURN_COLLECT_BARRIER_DIR, profile);
7509
+ const trash = (0, import_node_path.join)(turn.directory, `.trash-${(0, import_node_crypto.randomUUID)()}`);
7510
+ if (await (0, import_promises.rename)((0, import_node_path.join)(turn.directory, String(slot)), trash).then(() => true, () => false)) {
7511
+ await (0, import_promises.rm)(trash, { recursive: true, force: true }).catch(() => void 0);
7232
7512
  }
7233
- await sleep(Math.min(retryMs, timeoutMs - elapsedMs));
7234
7513
  }
7235
7514
  }
7236
7515
  async function publishProfileLock(profile, lockPath, token) {
@@ -7243,12 +7522,18 @@ async function publishProfileLock(profile, lockPath, token) {
7243
7522
  return false;
7244
7523
  }
7245
7524
  await waitForTestBarrier(TEST_LOCK_PUBLISH_BARRIER_DIR, profile);
7525
+ const ownerPath = (0, import_node_path.join)(lockPath, "owner.json");
7246
7526
  const staged = (0, import_node_path.join)((0, import_node_path.dirname)(lockPath), `.lock-owner-${token}.tmp`);
7247
- const owner = { pid: process.pid, createdAt: (/* @__PURE__ */ new Date()).toISOString(), token };
7527
+ const owner = `${JSON.stringify({ pid: process.pid, createdAt: (/* @__PURE__ */ new Date()).toISOString(), token }, null, 2)}
7528
+ `;
7248
7529
  try {
7249
- await (0, import_promises.writeFile)(staged, `${JSON.stringify(owner, null, 2)}
7250
- `, { encoding: "utf8", mode: PRIVATE_FILE_MODE, flag: "wx" });
7251
- await (0, import_promises.link)(staged, (0, import_node_path.join)(lockPath, "owner.json"));
7530
+ await (0, import_promises.writeFile)(staged, owner, { encoding: "utf8", mode: PRIVATE_FILE_MODE, flag: "wx" });
7531
+ try {
7532
+ await (0, import_promises.link)(staged, ownerPath);
7533
+ } catch (error) {
7534
+ if (!lacksHardLinks(error)) throw error;
7535
+ await (0, import_promises.writeFile)(ownerPath, owner, { encoding: "utf8", mode: PRIVATE_FILE_MODE, flag: "wx" });
7536
+ }
7252
7537
  return true;
7253
7538
  } catch (error) {
7254
7539
  if (isErrno(error, "EEXIST") || isErrno(error, "ENOENT")) return false;
@@ -7258,6 +7543,9 @@ async function publishProfileLock(profile, lockPath, token) {
7258
7543
  await (0, import_promises.rm)(staged, { force: true }).catch(() => void 0);
7259
7544
  }
7260
7545
  }
7546
+ function lacksHardLinks(error) {
7547
+ return typeof error === "object" && error !== null && "code" in error && NO_HARD_LINK_ERRORS.has(String(error.code));
7548
+ }
7261
7549
  async function releaseProfileLock(profile, lockPath, token) {
7262
7550
  const ownerPath = (0, import_node_path.join)(lockPath, "owner.json");
7263
7551
  const owner = await readJson(ownerPath).catch(() => null);
@@ -7281,9 +7569,10 @@ async function signalTestLockContention(profile) {
7281
7569
  `, { encoding: "utf8", flag: "wx" }).catch(() => void 0);
7282
7570
  }
7283
7571
  async function recoverOwnerlessLock(profile, lockPath, staleAfterMs) {
7572
+ let aged;
7284
7573
  try {
7285
7574
  const { mtimeMs } = await (0, import_promises.stat)(lockPath);
7286
- if (Date.now() - mtimeMs < staleAfterMs) return false;
7575
+ aged = Date.now() - mtimeMs >= staleAfterMs;
7287
7576
  } catch {
7288
7577
  return false;
7289
7578
  }
@@ -7292,7 +7581,16 @@ async function recoverOwnerlessLock(profile, lockPath, staleAfterMs) {
7292
7581
  try {
7293
7582
  const entries = await (0, import_promises.readdir)(lockPath);
7294
7583
  if (entries.includes("owner.json")) return false;
7295
- if (recoveryFenced(checkedAt, staleAfterMs)) return false;
7584
+ const onlyOrphanedClaims = entries.length > 0 && entries.every((entry) => RECOVERY_CLAIM.test(entry));
7585
+ if (!onlyOrphanedClaims && (!aged || recoveryFenced(checkedAt, staleAfterMs))) return false;
7586
+ for (const name of entries.filter((entry) => RECOVERY_CLAIM.test(entry))) {
7587
+ const claimPath = (0, import_node_path.join)(lockPath, name);
7588
+ if (await hasLiveRecoveryOwner(claimPath)) {
7589
+ if (!await exists((0, import_node_path.join)(lockPath, "owner.json"))) await (0, import_promises.rename)(claimPath, (0, import_node_path.join)(lockPath, "owner.json"));
7590
+ return false;
7591
+ }
7592
+ await (0, import_promises.rm)(claimPath, { force: true });
7593
+ }
7296
7594
  for (const name of entries.filter((entry) => ABANDONED_CLAIM.test(entry))) {
7297
7595
  await (0, import_promises.rm)((0, import_node_path.join)(lockPath, name), { force: true });
7298
7596
  }
@@ -7303,6 +7601,23 @@ async function recoverOwnerlessLock(profile, lockPath, staleAfterMs) {
7303
7601
  await removeAgedOwnerFiles(lockPath, staleAfterMs);
7304
7602
  return true;
7305
7603
  }
7604
+ async function hasLiveRecoveryOwner(claimPath) {
7605
+ let contents;
7606
+ try {
7607
+ contents = await (0, import_promises.readFile)(claimPath, "utf8");
7608
+ } catch (error) {
7609
+ if (isErrno(error, "ENOENT")) return false;
7610
+ throw error;
7611
+ }
7612
+ let owner;
7613
+ try {
7614
+ owner = JSON.parse(contents);
7615
+ } catch {
7616
+ return false;
7617
+ }
7618
+ const pid = typeof owner === "object" && owner !== null && "pid" in owner ? owner.pid : null;
7619
+ return typeof pid === "number" && Number.isInteger(pid) && pid > 0 && isProcessAlive(pid);
7620
+ }
7306
7621
  function recoveryFenced(since, staleAfterMs) {
7307
7622
  return performance.now() - since >= staleAfterMs / 2;
7308
7623
  }
@@ -7317,37 +7632,57 @@ async function removeAgedOwnerFiles(lockPath, staleAfterMs) {
7317
7632
  }
7318
7633
  async function recoverStaleLock(profile, lockPath, staleAfterMs) {
7319
7634
  const ownerPath = (0, import_node_path.join)(lockPath, "owner.json");
7320
- const owner = await readJson(ownerPath).catch(() => null);
7321
- if (owner === null) return recoverOwnerlessLock(profile, lockPath, staleAfterMs);
7322
- if (!isStaleOwner(owner, staleAfterMs)) return false;
7323
- const observedToken = typeof owner?.token === "string" && owner.token.length > 0 ? owner.token : "legacy";
7635
+ const observed = await (0, import_promises.readFile)(ownerPath, "utf8").catch(() => null);
7636
+ if (observed === null) return recoverOwnerlessLock(profile, lockPath, staleAfterMs);
7637
+ if (!await isAbandonedOwner(observed, ownerPath, staleAfterMs)) return false;
7324
7638
  await waitForTestBarrier(TEST_LOCK_RECOVERY_BARRIER_DIR, profile);
7325
- const claimPath = (0, import_node_path.join)(lockPath, `.stale-${(0, import_node_crypto.randomUUID)()}.json`);
7639
+ const claimPath = (0, import_node_path.join)(lockPath, `.recover-${(0, import_node_crypto.randomUUID)()}.json`);
7640
+ let moved = false;
7326
7641
  try {
7327
7642
  await (0, import_promises.link)(ownerPath, claimPath);
7328
- } catch {
7329
- return false;
7643
+ } catch (error) {
7644
+ if (!lacksHardLinks(error)) return false;
7645
+ try {
7646
+ await (0, import_promises.rename)(ownerPath, claimPath);
7647
+ moved = true;
7648
+ } catch {
7649
+ return false;
7650
+ }
7330
7651
  }
7331
- const claimedAt = performance.now();
7332
- await waitForTestBarrier(TEST_LOCK_CLAIM_BARRIER_DIR, profile);
7333
- const claimed = await readJson(claimPath).catch(() => null);
7334
- const sameInstance = claimed !== null && (observedToken === "legacy" ? claimed.token === void 0 : claimed.token === observedToken);
7652
+ if (moved) await waitForTestBarrier(TEST_LOCK_MOVED_BARRIER_DIR, profile);
7653
+ const sameInstance = await (0, import_promises.readFile)(claimPath, "utf8").then((claimed) => claimed === observed, () => false);
7335
7654
  await waitForTestBarrier(TEST_LOCK_VERIFIED_BARRIER_DIR, profile);
7336
- if (sameInstance && recoveryFenced(claimedAt, staleAfterMs)) {
7337
- await (0, import_promises.rm)(claimPath, { force: true });
7338
- await waitForTestBarrier(TEST_LOCK_FENCED_BARRIER_DIR, profile);
7655
+ if (!sameInstance) {
7656
+ if (moved) {
7657
+ await (0, import_promises.rename)(claimPath, ownerPath).catch(() => void 0);
7658
+ } else {
7659
+ await (0, import_promises.unlink)(claimPath).catch(() => void 0);
7660
+ }
7339
7661
  return false;
7340
7662
  }
7341
- if (sameInstance) {
7342
- await (0, import_promises.rm)(ownerPath, { force: true });
7343
- await waitForTestBarrier(TEST_LOCK_CLAIMED_BARRIER_DIR, profile);
7663
+ if (!moved) await (0, import_promises.rm)(ownerPath, { force: true });
7664
+ await waitForTestBarrier(TEST_LOCK_CLAIM_BARRIER_DIR, profile);
7665
+ await waitForTestBarrier(TEST_LOCK_CLAIMED_BARRIER_DIR, profile);
7666
+ for (const name of (await (0, import_promises.readdir)(lockPath).catch(() => [])).filter((entry) => RECOVERY_CLAIM.test(entry))) {
7667
+ if ((0, import_node_path.join)(lockPath, name) !== claimPath) await (0, import_promises.rm)((0, import_node_path.join)(lockPath, name), { force: true });
7344
7668
  }
7345
- await (0, import_promises.rm)(claimPath, { force: true });
7346
- await (0, import_promises.rmdir)(lockPath).catch(() => void 0);
7347
- if (!sameInstance) return false;
7669
+ if (await (0, import_promises.unlink)(claimPath).then(() => true, () => false)) await (0, import_promises.rmdir)(lockPath).catch(() => void 0);
7348
7670
  await removeAgedOwnerFiles(lockPath, staleAfterMs);
7349
7671
  return true;
7350
7672
  }
7673
+ async function isAbandonedOwner(observed, ownerPath, staleAfterMs) {
7674
+ let owner;
7675
+ try {
7676
+ owner = JSON.parse(observed);
7677
+ } catch {
7678
+ owner = null;
7679
+ }
7680
+ if (typeof owner === "object" && owner !== null && "pid" in owner) {
7681
+ return isStaleOwner(owner, staleAfterMs);
7682
+ }
7683
+ const modifiedAt = await (0, import_promises.stat)(ownerPath).then(({ mtimeMs }) => mtimeMs, () => Number.NaN);
7684
+ return Date.now() - modifiedAt >= staleAfterMs;
7685
+ }
7351
7686
  async function waitForTestBarrier(environmentName, profile) {
7352
7687
  if (process.env.NODE_ENV !== "test") return;
7353
7688
  const barrierDirectory = process.env[environmentName];
@@ -7374,6 +7709,15 @@ function isStaleOwner(owner, staleAfterMs) {
7374
7709
  }
7375
7710
  return !isProcessAlive(owner.pid);
7376
7711
  }
7712
+ async function exists(path) {
7713
+ try {
7714
+ await (0, import_promises.stat)(path);
7715
+ return true;
7716
+ } catch (error) {
7717
+ if (isErrno(error, "ENOENT") || isErrno(error, "ENOTDIR")) return false;
7718
+ throw error;
7719
+ }
7720
+ }
7377
7721
  function isProcessAlive(pid) {
7378
7722
  try {
7379
7723
  process.kill(pid, 0);
@@ -7394,7 +7738,7 @@ function isErrno(error, code) {
7394
7738
  function sleep(milliseconds) {
7395
7739
  return new Promise((resolve2) => setTimeout(resolve2, milliseconds));
7396
7740
  }
7397
- var import_node_crypto, import_node_async_hooks, import_promises, import_node_os, import_node_path, DEFAULT_PROFILE, DEFAULT_LOCK_TIMEOUT_MS, DEFAULT_LOCK_RETRY_MS, DEFAULT_STALE_LOCK_MS, TEST_LOCK_CONTENTION_SIGNAL_PATH, PRIVATE_FILE_MODE, PRIVATE_DIR_MODE, TEST_LOCK_RECOVERY_BARRIER_DIR, TEST_LOCK_OWNERLESS_BARRIER_DIR, TEST_LOCK_PUBLISH_BARRIER_DIR, TEST_LOCK_RELEASE_BARRIER_DIR, TEST_LOCK_CLAIM_BARRIER_DIR, TEST_LOCK_CLAIMED_BARRIER_DIR, TEST_LOCK_FENCED_BARRIER_DIR, TEST_LOCK_VERIFIED_BARRIER_DIR, invocationStateRoot, heldProfileLocks, ProfileLockTimeoutError, ABANDONED_CLAIM, STAGED_OWNER;
7741
+ var import_node_crypto, import_node_async_hooks, import_promises, import_node_os, import_node_path, DEFAULT_PROFILE, DEFAULT_LOCK_TIMEOUT_MS, DEFAULT_LOCK_RETRY_MS, DEFAULT_STALE_LOCK_MS, TEST_LOCK_CONTENTION_SIGNAL_PATH, PRIVATE_FILE_MODE, PRIVATE_DIR_MODE, TEST_LOCK_RECOVERY_BARRIER_DIR, TEST_LOCK_OWNERLESS_BARRIER_DIR, TEST_LOCK_PUBLISH_BARRIER_DIR, TEST_LOCK_RELEASE_BARRIER_DIR, TEST_LOCK_CLAIM_BARRIER_DIR, TEST_LOCK_CLAIMED_BARRIER_DIR, TEST_LOCK_VERIFIED_BARRIER_DIR, TEST_LOCK_MOVED_BARRIER_DIR, TEST_LOCK_TURN_PUBLISH_BARRIER_DIR, TEST_LOCK_TURN_SETTLE_BARRIER_DIR, TEST_LOCK_TURN_COLLECT_BARRIER_DIR, invocationStateRoot, heldProfileLocks, ProfileLockTimeoutError, ProfileDeletedError, TURN, _a, relinquishedTurns, MARKER_RETRY_DELAYS_MS, SETTLE_RETRY_MS, SETTLE_RETRIES, NO_HARD_LINK_ERRORS, ABANDONED_CLAIM, RECOVERY_CLAIM, STAGED_OWNER;
7398
7742
  var init_state = __esm({
7399
7743
  "src/state.ts"() {
7400
7744
  "use strict";
@@ -7416,18 +7760,36 @@ var init_state = __esm({
7416
7760
  TEST_LOCK_RELEASE_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_RELEASE_BARRIER_DIR";
7417
7761
  TEST_LOCK_CLAIM_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_CLAIM_BARRIER_DIR";
7418
7762
  TEST_LOCK_CLAIMED_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_CLAIMED_BARRIER_DIR";
7419
- TEST_LOCK_FENCED_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_FENCED_BARRIER_DIR";
7420
7763
  TEST_LOCK_VERIFIED_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_VERIFIED_BARRIER_DIR";
7421
- invocationStateRoot = new import_node_async_hooks.AsyncLocalStorage();
7422
- heldProfileLocks = new import_node_async_hooks.AsyncLocalStorage();
7764
+ TEST_LOCK_MOVED_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_MOVED_BARRIER_DIR";
7765
+ TEST_LOCK_TURN_PUBLISH_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_TURN_PUBLISH_BARRIER_DIR";
7766
+ TEST_LOCK_TURN_SETTLE_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_TURN_SETTLE_BARRIER_DIR";
7767
+ TEST_LOCK_TURN_COLLECT_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_TURN_COLLECT_BARRIER_DIR";
7768
+ invocationStateRoot = processWideContext("tinycloud.operations.invocationStateRoot.v1");
7769
+ heldProfileLocks = processWideContext("tinycloud.operations.heldProfileLocks.v2");
7423
7770
  ProfileLockTimeoutError = class extends Error {
7424
- constructor(profile, timeoutMs) {
7425
- super(`Timed out waiting for the profile lock for "${profile}" after ${timeoutMs}ms.`);
7771
+ /** `detail` names the cause when it is not simply another process holding the lock. */
7772
+ constructor(profile, timeoutMs, detail) {
7773
+ super(`Timed out waiting for the profile lock for "${profile}" after ${timeoutMs}ms.${detail ? ` ${detail}` : ""}`);
7426
7774
  this.code = "PROFILE_LOCK_TIMEOUT";
7427
7775
  this.name = "ProfileLockTimeoutError";
7428
7776
  }
7429
7777
  };
7778
+ ProfileDeletedError = class extends Error {
7779
+ constructor(profile) {
7780
+ super(`Profile "${profile}" was deleted while this change waited for its lock, so the change was not written.`);
7781
+ this.code = "PROFILE_NOT_FOUND";
7782
+ this.name = "ProfileDeletedError";
7783
+ }
7784
+ };
7785
+ TURN = /^(?:0|[1-9][0-9]*)$/;
7786
+ relinquishedTurns = globalThis[_a = /* @__PURE__ */ Symbol.for("tinycloud.operations.relinquishedProfileTurns.v1")] ?? (globalThis[_a] = /* @__PURE__ */ new Set());
7787
+ MARKER_RETRY_DELAYS_MS = [10, 50, 250];
7788
+ SETTLE_RETRY_MS = 1e3;
7789
+ SETTLE_RETRIES = 300;
7790
+ NO_HARD_LINK_ERRORS = /* @__PURE__ */ new Set(["EPERM", "ENOTSUP", "EOPNOTSUPP", "ENOSYS", "EISDIR", "EMLINK"]);
7430
7791
  ABANDONED_CLAIM = /^\.(?:release|stale)-[0-9a-f-]+\.json$/;
7792
+ RECOVERY_CLAIM = /^\.recover-[0-9a-f-]+\.json$/;
7431
7793
  STAGED_OWNER = /^\.lock-owner-[0-9a-f-]+\.tmp$/;
7432
7794
  }
7433
7795
  });
@@ -7634,7 +7996,7 @@ function evaluateArtifactAuthority(granted, required, resolveSpace2) {
7634
7996
  function defaultRequestId(now) {
7635
7997
  return `req_${now.getTime().toString(36)}_${(0, import_node_crypto2.randomUUID)().replace(/-/g, "")}`;
7636
7998
  }
7637
- var import_node_crypto2, import_zod2, THIRTY_DAYS_MS, PermissionEntrySchema, CommandSchema, LegacyPermissionRequestArtifactSchema, PortableDelegationSchema, PermissionRequestArtifactSchema, DelegationImportArtifactSchema;
7999
+ var import_node_crypto2, import_zod2, THIRTY_DAYS_MS, PermissionEntrySchema, CommandSchema, LegacyPermissionRequestArtifactSchema, PortableDelegationSchema, PermissionRequestArtifactSchema, DelegationImportArtifactSchema, DelegationRequestBindingSchema;
7638
8000
  var init_artifacts = __esm({
7639
8001
  "src/artifacts.ts"() {
7640
8002
  "use strict";
@@ -7709,6 +8071,7 @@ var init_artifacts = __esm({
7709
8071
  expiry: import_zod2.z.string().datetime({ offset: true }).optional(),
7710
8072
  prompted: import_zod2.z.boolean().optional()
7711
8073
  }).strict();
8074
+ DelegationRequestBindingSchema = PermissionRequestArtifactSchema.pick({ requestId: true, requested: true }).strict();
7712
8075
  }
7713
8076
  });
7714
8077
 
@@ -7768,18 +8131,18 @@ function resolveOperationSpace(space, node, ownerSpace) {
7768
8131
  if (resolved !== void 0) return canonicalizeSpaceUri(resolved);
7769
8132
  }
7770
8133
  const did = typeof candidate.did === "string" ? candidate.did : void 0;
7771
- const pkh = did === void 0 ? null : (0, import_node_sdk.parsePkhDid)(did);
8134
+ const pkh = did === void 0 ? null : nodeSdk.parsePkhDid(did);
7772
8135
  if (pkh === null) {
7773
8136
  throw invalidSecretInput();
7774
8137
  }
7775
- return (0, import_node_sdk.makePkhSpaceId)(pkh.address, pkh.chainId, space);
8138
+ return nodeSdk.makePkhSpaceId(pkh.address, pkh.chainId, space);
7776
8139
  }
7777
8140
  function ownedSpaceForName(ownerSpace, name) {
7778
- const parsed = (0, import_node_sdk.parseSpaceUri)(ownerSpace);
8141
+ const parsed = nodeSdk.parseSpaceUri(ownerSpace);
7779
8142
  if (parsed === null) return void 0;
7780
8143
  try {
7781
- const pkh = (0, import_node_sdk.parsePkhDid)(parsed.owner);
7782
- return pkh === null ? void 0 : (0, import_node_sdk.makePkhSpaceId)(pkh.address, pkh.chainId, name);
8144
+ const pkh = nodeSdk.parsePkhDid(parsed.owner);
8145
+ return pkh === null ? void 0 : nodeSdk.makePkhSpaceId(pkh.address, pkh.chainId, name);
7783
8146
  } catch {
7784
8147
  return void 0;
7785
8148
  }
@@ -7794,7 +8157,7 @@ function canonicalizeSpaceUri(space) {
7794
8157
  /^tinycloud:pkh:eip155:(\d+):(0x[0-9a-fA-F]{40}):([^:]+)$/
7795
8158
  );
7796
8159
  if (pkh !== null) {
7797
- return `tinycloud:pkh:eip155:${pkh[1]}:${(0, import_node_sdk.canonicalizeAddress)(pkh[2]).toLowerCase()}:${pkh[3]}`;
8160
+ return `tinycloud:pkh:eip155:${pkh[1]}:${nodeSdk.canonicalizeAddress(pkh[2]).toLowerCase()}:${pkh[3]}`;
7798
8161
  }
7799
8162
  return space;
7800
8163
  }
@@ -7834,11 +8197,11 @@ function invalidSecretInput() {
7834
8197
  operationError("INPUT_INVALID", "The secret reference is invalid.")
7835
8198
  );
7836
8199
  }
7837
- var import_node_sdk, DEFAULT_SECRETS_SPACE, SECRET_MANAGER_BASE_URL, SECRET_NAME_RE2, RESERVED_SECRET_SCOPES;
8200
+ var nodeSdk, DEFAULT_SECRETS_SPACE, SECRET_MANAGER_BASE_URL, SECRET_NAME_RE2, RESERVED_SECRET_SCOPES;
7838
8201
  var init_secrets = __esm({
7839
8202
  "src/secrets.ts"() {
7840
8203
  "use strict";
7841
- import_node_sdk = require("@tinycloud/node-sdk");
8204
+ nodeSdk = __toESM(require("@tinycloud/node-sdk"), 1);
7842
8205
  init_errors();
7843
8206
  DEFAULT_SECRETS_SPACE = "secrets";
7844
8207
  SECRET_MANAGER_BASE_URL = "https://secrets.tinycloud.xyz";
@@ -7991,7 +8354,7 @@ async function importRequestBoundDelegation(context, input) {
7991
8354
  };
7992
8355
  let activated;
7993
8356
  try {
7994
- activated = await nodeSdk.activateValidatedRuntimeDelegation(
8357
+ activated = await nodeSdk2.activateValidatedRuntimeDelegation(
7995
8358
  context.runtime.node,
7996
8359
  delegation,
7997
8360
  { host }
@@ -8007,11 +8370,7 @@ async function importRequestBoundDelegation(context, input) {
8007
8370
  context.runtime.node,
8008
8371
  context.summary.space
8009
8372
  );
8010
- if (request.requested.length === 0 || !evaluateOperationAuthority(
8011
- request.requested,
8012
- effectivePermissions,
8013
- resolveSpace2
8014
- ).satisfied) {
8373
+ if (!delegationWithinRequest(request.requested, effectivePermissions, resolveSpace2)) {
8015
8374
  return operationFailure(
8016
8375
  "DELEGATION_REJECTED",
8017
8376
  "The delegation exceeds the stored authority request."
@@ -8039,12 +8398,19 @@ async function importRequestBoundDelegation(context, input) {
8039
8398
  )
8040
8399
  };
8041
8400
  }
8042
- const alreadyPresent = records.some((entry) => delegationCid(entry) === activated.cid);
8401
+ const record = {
8402
+ delegation: activated.delegation,
8403
+ permissions: activated.effectivePermissions,
8404
+ authorityRequest: {
8405
+ requestId: request.requestId,
8406
+ requested: request.requested
8407
+ }
8408
+ };
8409
+ const position = records.findIndex((entry) => delegationCid(entry) === activated.cid);
8410
+ const alreadyPresent = position !== -1;
8411
+ const others = records.filter((entry) => delegationCid(entry) !== activated.cid);
8043
8412
  return {
8044
- records: alreadyPresent ? records : [...records, {
8045
- delegation: activated.delegation,
8046
- permissions: activated.effectivePermissions
8047
- }],
8413
+ records: alreadyPresent ? [...others.slice(0, position), record, ...others.slice(position)] : [...records, record],
8048
8414
  result: {
8049
8415
  status: "ok",
8050
8416
  output: {
@@ -8173,11 +8539,11 @@ function internalImportFailure() {
8173
8539
  function operationFailure(code, message, details) {
8174
8540
  return { status: "error", error: operationError(code, message, { ...details === void 0 ? {} : { details } }) };
8175
8541
  }
8176
- var nodeSdk, import_zod3, EmptyInputSchema, CapabilitiesOutputSchema, AuthRequestInputSchema, PermissionRequestOutputSchema, AuthRequestOutputSchema, AuthImportOutputSchema, AUTH_POSTURES, AUTH_EXPOSURE, AUTH_CAPABILITIES_SENSITIVITY, AUTH_REQUEST_SENSITIVITY, AUTH_IMPORT_SENSITIVITY, authOperationDefinitions;
8542
+ var nodeSdk2, import_zod3, EmptyInputSchema, CapabilitiesOutputSchema, AuthRequestInputSchema, PermissionRequestOutputSchema, AuthRequestOutputSchema, AuthImportOutputSchema, AUTH_POSTURES, AUTH_EXPOSURE, AUTH_CAPABILITIES_SENSITIVITY, AUTH_REQUEST_SENSITIVITY, AUTH_IMPORT_SENSITIVITY, authOperationDefinitions;
8177
8543
  var init_auth = __esm({
8178
8544
  "src/operations/auth.ts"() {
8179
8545
  "use strict";
8180
- nodeSdk = __toESM(require("@tinycloud/node-sdk"), 1);
8546
+ nodeSdk2 = __toESM(require("@tinycloud/node-sdk"), 1);
8181
8547
  import_zod3 = require("zod");
8182
8548
  init_authority();
8183
8549
  init_artifacts();
@@ -9774,6 +10140,173 @@ ORDER BY type, name`;
9774
10140
  }
9775
10141
  });
9776
10142
 
10143
+ // src/delegation-binding.ts
10144
+ function bindingMigrationPath(profile) {
10145
+ return (0, import_node_path2.join)(profilePath(profile), "delegation-binding-migration.json");
10146
+ }
10147
+ function storedDelegationKind(entry) {
10148
+ if (!isRecord4(entry)) return "refused";
10149
+ const delegation = entry.delegation;
10150
+ if (!isRecord4(delegation)) return "refused";
10151
+ const header = delegation.delegationHeader;
10152
+ if (!isRecord4(header) || Object.keys(header).length !== 1 || !Object.prototype.hasOwnProperty.call(header, "Authorization") || typeof header.Authorization !== "string") {
10153
+ return "refused";
10154
+ }
10155
+ if ("siweProof" in delegation) return "signed-login";
10156
+ if (header.Authorization.includes(".")) return "compact";
10157
+ return "authorityRequest" in entry ? "refused" : "other";
10158
+ }
10159
+ function replayLimit(entry, migrated) {
10160
+ const kind = storedDelegationKind(entry);
10161
+ if (!isRecord4(entry) || kind !== "compact" && kind !== "signed-login") return void 0;
10162
+ const binding = DelegationRequestBindingSchema.safeParse(entry.authorityRequest);
10163
+ if (binding.success) return binding.data.requested;
10164
+ return !migrated && kind === "compact" && !("authorityRequest" in entry) ? "signed" : void 0;
10165
+ }
10166
+ function bindToSignedCapabilities(record, signed, source, recordedAt = (/* @__PURE__ */ new Date()).toISOString()) {
10167
+ return {
10168
+ ...record,
10169
+ authorityRequest: DelegationRequestBindingSchema.parse({
10170
+ requestId: `${REQUEST_ID_PREFIX[source]}:${record.delegation.cid}`,
10171
+ requested: structuredClone(signed)
10172
+ }),
10173
+ authorityRequestAudit: { source, recordedAt, note: BINDING_NOTES[source] }
10174
+ };
10175
+ }
10176
+ async function prepareStoredDelegationReplay(profile, node, options) {
10177
+ if (await bindingMigrationRecorded(profile)) return true;
10178
+ if (!options.migrate || !await holdsPersistedSession(profile, node)) return false;
10179
+ try {
10180
+ return await withProfileLock(profile, async () => {
10181
+ if (await bindingMigrationRecorded(profile)) return true;
10182
+ if (!await holdsPersistedSession(profile, node)) return false;
10183
+ const recordedAt = (/* @__PURE__ */ new Date()).toISOString();
10184
+ const bound = /* @__PURE__ */ new Map();
10185
+ const unbound = [];
10186
+ for (const entry of await readAdditionalDelegations(profile)) {
10187
+ if (!isRecord4(entry) || "authorityRequest" in entry || storedDelegationKind(entry) !== "compact") continue;
10188
+ const migrated = await migratedRecord(node, entry, options.host, recordedAt);
10189
+ if (migrated === void 0) unbound.push(String(storedCid(entry)));
10190
+ else bound.set(recordKey(entry), migrated);
10191
+ }
10192
+ if (bound.size > 0) {
10193
+ await updateProfileStore(profile, "additional-delegations", (records) => ({
10194
+ records: records.map((entry) => {
10195
+ const key = !isRecord4(entry) || "authorityRequest" in entry ? void 0 : recordKey(entry);
10196
+ return (key === void 0 ? void 0 : bound.get(key)) ?? entry;
10197
+ }),
10198
+ result: void 0
10199
+ }));
10200
+ }
10201
+ await writeJsonAtomic(bindingMigrationPath(profile), {
10202
+ formatVersion: 1,
10203
+ migratedAt: recordedAt,
10204
+ bound: [...bound.values()].map(storedCid),
10205
+ unbound
10206
+ });
10207
+ return true;
10208
+ }, { timeoutMs: MIGRATION_LOCK_TIMEOUT_MS });
10209
+ } catch {
10210
+ return false;
10211
+ }
10212
+ }
10213
+ async function holdsPersistedSession(profile, node) {
10214
+ try {
10215
+ const persisted = await readSession(profile);
10216
+ return typeof persisted?.verificationMethod === "string" && typeof node.sessionDid === "string" && persisted.verificationMethod.split("#", 1)[0] === node.sessionDid.split("#", 1)[0];
10217
+ } catch {
10218
+ return false;
10219
+ }
10220
+ }
10221
+ async function migratedRecord(node, entry, host, recordedAt) {
10222
+ const delegation = normalizeStoredDelegation(entry);
10223
+ if (delegation === void 0) return void 0;
10224
+ const signed = await signedCapabilities(node, delegation, host);
10225
+ if (signed === void 0) return void 0;
10226
+ try {
10227
+ return bindToSignedCapabilities({ ...entry, delegation: { ...entry.delegation, cid: delegation.cid } }, signed, "migration", recordedAt);
10228
+ } catch {
10229
+ return void 0;
10230
+ }
10231
+ }
10232
+ async function replayStoredDelegation(node, entry, options) {
10233
+ const delegation = normalizeStoredDelegation(entry);
10234
+ if (delegation === void 0 || delegation.expiry.getTime() <= Date.now()) return void 0;
10235
+ const limit = replayLimit(entry, options.migrated);
10236
+ if (limit === void 0) return void 0;
10237
+ const authorize = limit === "signed" ? () => true : (effective) => delegationWithinRequest(limit, effective, options.resolveSpace);
10238
+ try {
10239
+ return await nodeSdk3.activateValidatedRuntimeDelegation(node, delegation, { host: options.host, authorize });
10240
+ } catch {
10241
+ return void 0;
10242
+ }
10243
+ }
10244
+ async function bindingMigrationRecorded(profile) {
10245
+ try {
10246
+ return await readJson(bindingMigrationPath(profile)) !== null;
10247
+ } catch {
10248
+ return true;
10249
+ }
10250
+ }
10251
+ async function signedCapabilities(node, delegation, host) {
10252
+ let signed;
10253
+ try {
10254
+ await nodeSdk3.activateValidatedRuntimeDelegation(node, delegation, {
10255
+ // The transport host is not signed authority; check it at replay instead.
10256
+ host: delegation.host ?? host,
10257
+ authorize: (effective) => {
10258
+ signed = effective;
10259
+ return false;
10260
+ }
10261
+ });
10262
+ } catch {
10263
+ }
10264
+ return signed;
10265
+ }
10266
+ function normalizeStoredDelegation(entry) {
10267
+ if (!isRecord4(entry)) return void 0;
10268
+ const raw = entry.delegation;
10269
+ if (!isRecord4(raw) || !isRecord4(raw.delegationHeader)) return void 0;
10270
+ const expiry = raw.expiry instanceof Date ? raw.expiry : typeof raw.expiry === "string" ? new Date(raw.expiry) : void 0;
10271
+ if (expiry === void 0 || Number.isNaN(expiry.getTime()) || typeof raw.cid !== "string" || typeof raw.spaceId !== "string" || typeof raw.path !== "string" || !Array.isArray(raw.actions) || !raw.actions.every((action) => typeof action === "string") || typeof raw.delegateDID !== "string" || typeof raw.ownerAddress !== "string" || typeof raw.chainId !== "number" || typeof raw.delegationHeader.Authorization !== "string") {
10272
+ return void 0;
10273
+ }
10274
+ return { ...raw, expiry };
10275
+ }
10276
+ function storedCid(entry) {
10277
+ const cid = isRecord4(entry) && isRecord4(entry.delegation) ? entry.delegation.cid : void 0;
10278
+ return typeof cid === "string" ? cid : void 0;
10279
+ }
10280
+ function recordKey(entry) {
10281
+ const delegation = entry.delegation;
10282
+ if (!isRecord4(delegation) || !isRecord4(delegation.delegationHeader)) return void 0;
10283
+ return JSON.stringify([delegation.cid, delegation.delegationHeader.Authorization]);
10284
+ }
10285
+ function isRecord4(value) {
10286
+ return typeof value === "object" && value !== null && !Array.isArray(value);
10287
+ }
10288
+ var import_node_path2, nodeSdk3, REQUEST_ID_PREFIX, BINDING_NOTES, MIGRATION_LOCK_TIMEOUT_MS;
10289
+ var init_delegation_binding = __esm({
10290
+ "src/delegation-binding.ts"() {
10291
+ "use strict";
10292
+ import_node_path2 = require("path");
10293
+ nodeSdk3 = __toESM(require("@tinycloud/node-sdk"), 1);
10294
+ init_artifacts();
10295
+ init_authority();
10296
+ init_state();
10297
+ init_secrets();
10298
+ REQUEST_ID_PREFIX = {
10299
+ migration: "migrated",
10300
+ "unbound-import": "unbound-import"
10301
+ };
10302
+ BINDING_NOTES = {
10303
+ migration: "Stored without a request binding before bindings were required. Bound at migration to exactly its own signed capabilities, so replay refuses any delegation in this record that exceeds them.",
10304
+ "unbound-import": "Imported by `tc auth import` without a stored request. Bound to exactly its own signed capabilities, so replay refuses any delegation in this record that exceeds them."
10305
+ };
10306
+ MIGRATION_LOCK_TIMEOUT_MS = 250;
10307
+ }
10308
+ });
10309
+
9777
10310
  // src/operations/status.ts
9778
10311
  function createStatusDefinition(params) {
9779
10312
  return {
@@ -9829,7 +10362,7 @@ async function inspectPinnedProfileStatus(context, _input) {
9829
10362
  async function pinnedProfileExists(profile) {
9830
10363
  try {
9831
10364
  const storedProfile = await readProfile(profile);
9832
- return isRecord4(storedProfile);
10365
+ return isRecord5(storedProfile);
9833
10366
  } catch {
9834
10367
  return false;
9835
10368
  }
@@ -9840,16 +10373,18 @@ async function countLiveAdditionalDelegations(profile) {
9840
10373
  if (rawDelegations === null) return 0;
9841
10374
  if (!Array.isArray(rawDelegations)) throw new TypeError("Invalid additional delegation store.");
9842
10375
  const now = Date.now();
10376
+ const migrated = await bindingMigrationRecorded(profile);
9843
10377
  let count = 0;
9844
10378
  for (const entry of rawDelegations) {
10379
+ if (storedDelegationKind(entry) === "refused") continue;
9845
10380
  const expiry = storedDelegationExpiry(entry);
9846
- if (expiry.getTime() > now) count += 1;
10381
+ if (expiry.getTime() > now && replayLimit(entry, migrated) !== void 0) count += 1;
9847
10382
  }
9848
10383
  return count;
9849
10384
  }
9850
10385
  function summarizeSession(session) {
9851
10386
  if (session === null) return { present: false, expired: null, expiresAt: null };
9852
- if (!isRecord4(session)) throw new TypeError("Invalid session store.");
10387
+ if (!isRecord5(session)) throw new TypeError("Invalid session store.");
9853
10388
  const expiry = extractSessionExpiry(session);
9854
10389
  if (expiry === null) return { present: true, expired: null, expiresAt: null };
9855
10390
  return {
@@ -9859,7 +10394,7 @@ function summarizeSession(session) {
9859
10394
  };
9860
10395
  }
9861
10396
  function storedDelegationExpiry(entry) {
9862
- if (!isRecord4(entry) || !isRecord4(entry.delegation) || !("expiry" in entry.delegation)) {
10397
+ if (!isRecord5(entry) || !isRecord5(entry.delegation) || !("expiry" in entry.delegation)) {
9863
10398
  throw new TypeError("Invalid stored delegation.");
9864
10399
  }
9865
10400
  return parseExpiry(entry.delegation.expiry);
@@ -9888,7 +10423,7 @@ function profileNotFound(profile) {
9888
10423
  )
9889
10424
  };
9890
10425
  }
9891
- function isRecord4(value) {
10426
+ function isRecord5(value) {
9892
10427
  return typeof value === "object" && value !== null && !Array.isArray(value);
9893
10428
  }
9894
10429
  var import_zod7, StatusInputSchema, StatusOutputSchema, STATUS_POSTURES, STATUS_EXPOSURE, STATUS_SENSITIVITY, statusOperationDefinitions;
@@ -9896,6 +10431,7 @@ var init_status = __esm({
9896
10431
  "src/operations/status.ts"() {
9897
10432
  "use strict";
9898
10433
  import_zod7 = require("zod");
10434
+ init_delegation_binding();
9899
10435
  init_errors();
9900
10436
  init_state();
9901
10437
  StatusInputSchema = import_zod7.z.object({}).strict();
@@ -9918,7 +10454,9 @@ var init_status = __esm({
9918
10454
  expired: import_zod7.z.boolean().nullable(),
9919
10455
  expiresAt: import_zod7.z.string().datetime({ offset: true }).nullable()
9920
10456
  }).strict(),
9921
- liveAdditionalDelegationCount: import_zod7.z.number().int().nonnegative()
10457
+ liveAdditionalDelegationCount: import_zod7.z.number().int().nonnegative().describe(
10458
+ "Stored, unexpired delegations the replay rule accepts: compact-UCAN or signed-login records with a request binding, and unbound compact-UCAN records before the profile's one-time binding migration. Each is still validated, and checked against its binding, when a runtime installs it."
10459
+ )
9922
10460
  }).strict();
9923
10461
  STATUS_POSTURES = [
9924
10462
  "owner-openkey",
@@ -10072,9 +10610,9 @@ function isOperatorType(value) {
10072
10610
  return value === "human" || value === "agent";
10073
10611
  }
10074
10612
  function isStoredProfile(value) {
10075
- return isRecord5(value) && isNonEmptyString(value.name) && isNonEmptyString(value.host) && typeof value.chainId === "number" && Number.isFinite(value.chainId) && isNonEmptyString(value.spaceName) && isNonEmptyString(value.did) && isNonEmptyString(value.createdAt) && isOptionalNonEmptyString(value.sessionDid) && isOptionalNonEmptyString(value.ownerDid) && isOptionalNonEmptyString(value.spaceId) && isOptionalPosture(value.posture) && isOptionalOperatorType(value.operatorType) && isOptionalAuthMethod(value.authMethod) && isOptionalNonEmptyString(value.defaultSpace) && isOptionalNonEmptyString(value.privateKey) && isOptionalNonEmptyString(value.address) && isOptionalNonEmptyString(value.openkeyHost);
10613
+ return isRecord6(value) && isNonEmptyString(value.name) && isNonEmptyString(value.host) && typeof value.chainId === "number" && Number.isFinite(value.chainId) && isNonEmptyString(value.spaceName) && isNonEmptyString(value.did) && isNonEmptyString(value.createdAt) && isOptionalNonEmptyString(value.sessionDid) && isOptionalNonEmptyString(value.ownerDid) && isOptionalNonEmptyString(value.spaceId) && isOptionalPosture(value.posture) && isOptionalOperatorType(value.operatorType) && isOptionalAuthMethod(value.authMethod) && isOptionalNonEmptyString(value.defaultSpace) && isOptionalNonEmptyString(value.privateKey) && isOptionalNonEmptyString(value.address) && isOptionalNonEmptyString(value.openkeyHost);
10076
10614
  }
10077
- function isRecord5(value) {
10615
+ function isRecord6(value) {
10078
10616
  return typeof value === "object" && value !== null && !Array.isArray(value);
10079
10617
  }
10080
10618
  function isNonEmptyString(value) {
@@ -10131,14 +10669,12 @@ async function createInvocationRuntime(target, requirement = "authenticated") {
10131
10669
  const profileName = summary.profile;
10132
10670
  try {
10133
10671
  const {
10134
- activateValidatedRuntimeDelegation: activateValidatedRuntimeDelegation2,
10135
10672
  TinyCloudNode: TinyCloudNodeConstructor
10136
- } = nodeSdk2;
10673
+ } = nodeSdk4;
10137
10674
  const explicitPrivateKeyOverride = typeof target.privateKey === "string";
10138
- const [session, key, additionalDelegations] = explicitPrivateKeyOverride ? [null, null, []] : await Promise.all([
10675
+ const [session, key] = explicitPrivateKeyOverride ? [null, null] : await Promise.all([
10139
10676
  readSession(profileName),
10140
- readJson(`${profilePath(profileName)}/key.json`),
10141
- readAdditionalDelegations(profileName)
10677
+ readJson(`${profilePath(profileName)}/key.json`)
10142
10678
  ]);
10143
10679
  const profile = resolved.profile;
10144
10680
  const actualPosture = resolvePosture(profile);
@@ -10185,19 +10721,26 @@ async function createInvocationRuntime(target, requirement = "authenticated") {
10185
10721
  const livePrincipalDid = normalizeDid(node.did);
10186
10722
  const authenticatedSpace = explicitPrivateKeyOverride ? spaceForAuthenticatedPrincipal(livePrincipalDid) : void 0;
10187
10723
  const livePermissions = [...node.getVerifiedSessionCapabilities()];
10724
+ const resolveSpace2 = operationSpaceResolver(node, authenticatedSpace ?? summary.space);
10188
10725
  const seenCids = /* @__PURE__ */ new Set();
10189
- for (const entry of additionalDelegations) {
10190
- const delegation = normalizeStoredDelegation(entry);
10191
- if (delegation === void 0 || delegation.expiry.getTime() <= Date.now()) continue;
10192
- try {
10193
- const activated = await activateValidatedRuntimeDelegation2(node, delegation, {
10194
- host: summary.host
10726
+ if (!explicitPrivateKeyOverride) {
10727
+ const activator = node;
10728
+ const migrated = await prepareStoredDelegationReplay(profileName, activator, {
10729
+ host: summary.host,
10730
+ // Only the profile's own restored session; a local sign-in without
10731
+ // one uses a fresh session key the stored records do not address.
10732
+ migrate: activeSession !== void 0
10733
+ });
10734
+ for (const entry of await readAdditionalDelegations(profileName)) {
10735
+ const activated = await replayStoredDelegation(activator, entry, {
10736
+ host: summary.host,
10737
+ migrated,
10738
+ resolveSpace: resolveSpace2
10195
10739
  });
10196
- if (!seenCids.has(activated.cid)) {
10740
+ if (activated !== void 0 && !seenCids.has(activated.cid)) {
10197
10741
  seenCids.add(activated.cid);
10198
10742
  livePermissions.push(...activated.effectivePermissions);
10199
10743
  }
10200
- } catch {
10201
10744
  }
10202
10745
  }
10203
10746
  const runtime = {
@@ -10229,8 +10772,8 @@ async function createInvocationRuntime(target, requirement = "authenticated") {
10229
10772
  function spaceForAuthenticatedPrincipal(principal2) {
10230
10773
  if (principal2 === void 0) return void 0;
10231
10774
  try {
10232
- const pkh = nodeSdk2.parsePkhDid(principal2);
10233
- return pkh === null ? void 0 : nodeSdk2.makePkhSpaceId(pkh.address, pkh.chainId, "secrets");
10775
+ const pkh = nodeSdk4.parsePkhDid(principal2);
10776
+ return pkh === null ? void 0 : nodeSdk4.makePkhSpaceId(pkh.address, pkh.chainId, "secrets");
10234
10777
  } catch {
10235
10778
  return void 0;
10236
10779
  }
@@ -10238,7 +10781,7 @@ function spaceForAuthenticatedPrincipal(principal2) {
10238
10781
  function normalizeSession(session, key) {
10239
10782
  const header = session.delegationHeader;
10240
10783
  const jwk = hasPrivateParameter(session.jwk) ? session.jwk : key;
10241
- if (!isRecord6(header) || typeof header.Authorization !== "string" || typeof session.delegationCid !== "string" || typeof session.spaceId !== "string" || !hasPrivateParameter(jwk) || typeof session.verificationMethod !== "string") {
10784
+ if (!isRecord7(header) || typeof header.Authorization !== "string" || typeof session.delegationCid !== "string" || typeof session.spaceId !== "string" || !hasPrivateParameter(jwk) || typeof session.verificationMethod !== "string") {
10242
10785
  return void 0;
10243
10786
  }
10244
10787
  return {
@@ -10254,27 +10797,14 @@ function normalizeSession(session, key) {
10254
10797
  ...Array.isArray(session.tinycloudHosts) && session.tinycloudHosts.every((host) => typeof host === "string") ? { tinycloudHosts: session.tinycloudHosts } : {}
10255
10798
  };
10256
10799
  }
10257
- function normalizeStoredDelegation(entry) {
10258
- const raw = entry.delegation;
10259
- if (!isRecord6(raw) || !isRecord6(raw.delegationHeader)) return void 0;
10260
- const expiry = parseExpiry2(raw.expiry);
10261
- if (expiry === void 0 || typeof raw.cid !== "string" || typeof raw.spaceId !== "string" || typeof raw.path !== "string" || !Array.isArray(raw.actions) || !raw.actions.every((action) => typeof action === "string") || typeof raw.delegateDID !== "string" || typeof raw.ownerAddress !== "string" || typeof raw.chainId !== "number" || typeof raw.delegationHeader.Authorization !== "string") {
10262
- return void 0;
10263
- }
10264
- return { ...raw, expiry };
10265
- }
10266
- function parseExpiry2(value) {
10267
- const expiry = value instanceof Date ? value : typeof value === "string" ? new Date(value) : void 0;
10268
- return expiry !== void 0 && !Number.isNaN(expiry.getTime()) ? expiry : void 0;
10269
- }
10270
10800
  function hasPrivateParameter(value) {
10271
- return isRecord6(value) && typeof value.d === "string" && value.d.length > 0;
10801
+ return isRecord7(value) && typeof value.d === "string" && value.d.length > 0;
10272
10802
  }
10273
10803
  function normalizeDid(value) {
10274
10804
  if (typeof value !== "string" || value.length === 0) return void 0;
10275
10805
  return value.split("#", 1)[0];
10276
10806
  }
10277
- function isRecord6(value) {
10807
+ function isRecord7(value) {
10278
10808
  return typeof value === "object" && value !== null && !Array.isArray(value);
10279
10809
  }
10280
10810
  function failed(context, error) {
@@ -10287,15 +10817,17 @@ function unresolvedSummary(target) {
10287
10817
  posture: "unauthenticated"
10288
10818
  };
10289
10819
  }
10290
- var nodeSdk2;
10820
+ var nodeSdk4;
10291
10821
  var init_runtime = __esm({
10292
10822
  "src/runtime.ts"() {
10293
10823
  "use strict";
10294
- nodeSdk2 = __toESM(require("@tinycloud/node-sdk"), 1);
10824
+ nodeSdk4 = __toESM(require("@tinycloud/node-sdk"), 1);
10295
10825
  init_authority();
10296
10826
  init_errors();
10297
10827
  init_profile();
10828
+ init_secrets();
10298
10829
  init_state();
10830
+ init_delegation_binding();
10299
10831
  }
10300
10832
  });
10301
10833
 
@@ -10308,8 +10840,8 @@ module.exports = __toCommonJS(index_exports);
10308
10840
 
10309
10841
  // src/invoke.ts
10310
10842
  var import_node_crypto3 = require("crypto");
10311
- var import_node_path2 = require("path");
10312
- var import_node_sdk2 = require("@tinycloud/node-sdk");
10843
+ var import_node_path3 = require("path");
10844
+ var import_node_sdk = require("@tinycloud/node-sdk");
10313
10845
 
10314
10846
  // ../sdk-core/src/policy/errors.ts
10315
10847
  var SignedObjectProfileError = class extends Error {
@@ -11014,7 +11546,7 @@ function normalizeInvocationTarget(target) {
11014
11546
  const stateRoot = candidate.stateRoot;
11015
11547
  const allowOwnerProfile = candidate.allowOwnerProfile;
11016
11548
  const privateKey = candidate.privateKey;
11017
- if (profile !== void 0 && typeof profile !== "string" || host !== void 0 && typeof host !== "string" || stateRoot !== void 0 && (typeof stateRoot !== "string" || !(0, import_node_path2.isAbsolute)(stateRoot)) || allowOwnerProfile !== void 0 && typeof allowOwnerProfile !== "boolean" || privateKey !== void 0 && typeof privateKey !== "string") {
11549
+ if (profile !== void 0 && typeof profile !== "string" || host !== void 0 && typeof host !== "string" || stateRoot !== void 0 && (typeof stateRoot !== "string" || !(0, import_node_path3.isAbsolute)(stateRoot)) || allowOwnerProfile !== void 0 && typeof allowOwnerProfile !== "boolean" || privateKey !== void 0 && typeof privateKey !== "string") {
11018
11550
  return void 0;
11019
11551
  }
11020
11552
  return {