@tinycloud/operations 0.5.0-beta.1 → 0.5.0-beta.10

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.
Files changed (51) hide show
  1. package/coverage.json +11 -1
  2. package/dist/artifacts.cjs +235 -43
  3. package/dist/artifacts.cjs.map +1 -1
  4. package/dist/artifacts.d.cts +1 -1
  5. package/dist/artifacts.d.ts +1 -1
  6. package/dist/artifacts.js +236 -43
  7. package/dist/artifacts.js.map +1 -1
  8. package/dist/cli-runtime.cjs +961 -178
  9. package/dist/cli-runtime.cjs.map +1 -1
  10. package/dist/cli-runtime.d.cts +1 -1
  11. package/dist/cli-runtime.d.ts +1 -1
  12. package/dist/cli-runtime.js +960 -177
  13. package/dist/cli-runtime.js.map +1 -1
  14. package/dist/{contract-DfyrluLB.d.cts → contract-zRklonbR.d.cts} +6 -3
  15. package/dist/{contract-DfyrluLB.d.ts → contract-zRklonbR.d.ts} +6 -3
  16. package/dist/delegation-binding.cjs +242 -43
  17. package/dist/delegation-binding.cjs.map +1 -1
  18. package/dist/delegation-binding.d.cts +15 -3
  19. package/dist/delegation-binding.d.ts +15 -3
  20. package/dist/delegation-binding.js +242 -43
  21. package/dist/delegation-binding.js.map +1 -1
  22. package/dist/index.cjs +989 -179
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.d.cts +1 -1
  25. package/dist/index.d.ts +1 -1
  26. package/dist/index.js +986 -176
  27. package/dist/index.js.map +1 -1
  28. package/dist/profile.cjs +13 -4
  29. package/dist/profile.cjs.map +1 -1
  30. package/dist/profile.d.cts +14 -2
  31. package/dist/profile.d.ts +14 -2
  32. package/dist/profile.js +13 -4
  33. package/dist/profile.js.map +1 -1
  34. package/dist/recovery.cjs +67 -0
  35. package/dist/recovery.cjs.map +1 -0
  36. package/dist/recovery.d.cts +38 -0
  37. package/dist/recovery.d.ts +38 -0
  38. package/dist/recovery.js +39 -0
  39. package/dist/recovery.js.map +1 -0
  40. package/dist/secret-request.cjs +271 -0
  41. package/dist/secret-request.cjs.map +1 -0
  42. package/dist/secret-request.d.cts +119 -0
  43. package/dist/secret-request.d.ts +119 -0
  44. package/dist/secret-request.js +222 -0
  45. package/dist/secret-request.js.map +1 -0
  46. package/dist/state.cjs +232 -40
  47. package/dist/state.cjs.map +1 -1
  48. package/dist/state.js +233 -40
  49. package/dist/state.js.map +1 -1
  50. package/generated/operations.json +1838 -89
  51. package/package.json +15 -5
package/dist/index.js CHANGED
@@ -298,6 +298,7 @@ var init_errors2 = __esm({
298
298
  "PROFILE_NOT_FOUND",
299
299
  "PROFILE_POSTURE_NOT_ALLOWED",
300
300
  "PROFILE_OWNER_OPT_IN_REQUIRED",
301
+ "PROFILE_MIGRATION_REQUIRED",
301
302
  "AUTH_REQUIRED",
302
303
  "PERMISSION_DENIED",
303
304
  "SESSION_NOT_FOUND",
@@ -4106,10 +4107,10 @@ function evaluateOperationAuthority(granted, required, resolveSpace2) {
4106
4107
  const canonicalRequired = canonicalizeOperationCapabilities(required, resolveSpace2);
4107
4108
  const missing = [];
4108
4109
  for (const requested of canonicalRequired) {
4109
- const covered = canonicalGranted.some(
4110
- (candidate) => candidate.service === requested.service && operationSpacesEqual(candidate.space, requested.space) && isCapabilitySubset([requested], [candidate]).subset
4110
+ const sameScope = canonicalGranted.filter(
4111
+ (candidate) => candidate.service === requested.service && operationSpacesEqual(candidate.space, requested.space)
4111
4112
  );
4112
- if (!covered) missing.push(requested);
4113
+ if (!isCapabilitySubset([requested], sameScope).subset) missing.push(requested);
4113
4114
  }
4114
4115
  return {
4115
4116
  satisfied: missing.length === 0,
@@ -4233,9 +4234,11 @@ var init_authority = __esm({
4233
4234
  // src/state.ts
4234
4235
  import { randomUUID } from "crypto";
4235
4236
  import { AsyncLocalStorage } from "async_hooks";
4237
+ import { readFileSync, readlinkSync } from "fs";
4236
4238
  import {
4237
4239
  chmod,
4238
4240
  link,
4241
+ lstat,
4239
4242
  mkdir,
4240
4243
  open,
4241
4244
  readFile,
@@ -4247,12 +4250,165 @@ import {
4247
4250
  unlink,
4248
4251
  writeFile
4249
4252
  } from "fs/promises";
4253
+ import { connect, createServer } from "net";
4250
4254
  import { homedir } from "os";
4251
4255
  import { basename, dirname, isAbsolute, join, relative, resolve } from "path";
4252
4256
  function processWideContext(key) {
4253
- var _a3;
4257
+ var _a4;
4254
4258
  const registry = globalThis;
4255
- return registry[_a3 = Symbol.for(key)] ?? (registry[_a3] = new AsyncLocalStorage());
4259
+ return registry[_a4 = Symbol.for(key)] ?? (registry[_a4] = new AsyncLocalStorage());
4260
+ }
4261
+ function processOrigin() {
4262
+ if (cachedProcessOrigin !== void 0) return cachedProcessOrigin;
4263
+ if (process.platform !== "linux") return cachedProcessOrigin = null;
4264
+ try {
4265
+ const bootId = readFileSync("/proc/sys/kernel/random/boot_id", "utf8").trim();
4266
+ const pidNamespace = readlinkSync("/proc/self/ns/pid");
4267
+ const processStat = readFileSync("/proc/self/stat", "utf8");
4268
+ const closingParen = processStat.lastIndexOf(") ");
4269
+ const processStartTime = closingParen < 0 ? void 0 : processStat.slice(closingParen + 2).trim().split(/\s+/)[19];
4270
+ if (!/^[0-9a-f-]{36}$/.test(bootId) || !/^pid:\[[0-9]+\]$/.test(pidNamespace) || !processStartTime || !/^[0-9]+$/.test(processStartTime)) {
4271
+ return cachedProcessOrigin = null;
4272
+ }
4273
+ return cachedProcessOrigin = { bootId, pidNamespace, processStartTime };
4274
+ } catch {
4275
+ return cachedProcessOrigin = null;
4276
+ }
4277
+ }
4278
+ function processOwner(liveness) {
4279
+ return { instanceId: processInstanceId, ...processOrigin(), ...liveness ? { liveness: liveness.record } : {} };
4280
+ }
4281
+ function socketDirectory(name) {
4282
+ const match = LIVENESS_SOCKET.exec(name);
4283
+ return match ? `${match[1]}-${match[2]}` : null;
4284
+ }
4285
+ async function pinDirectory(directory) {
4286
+ const handle = await open(directory, "r");
4287
+ try {
4288
+ const { dev, ino } = await handle.stat({ bigint: true });
4289
+ return {
4290
+ identity: `${dev.toString(16)}-${ino.toString(16)}`,
4291
+ path: (name) => `/proc/self/fd/${handle.fd}/${name}`,
4292
+ close: () => handle.close()
4293
+ };
4294
+ } catch (error) {
4295
+ await handle.close();
4296
+ throw error;
4297
+ }
4298
+ }
4299
+ async function bindLiveness(directory) {
4300
+ if (!processOrigin()) return null;
4301
+ let pinned;
4302
+ try {
4303
+ pinned = await pinDirectory(directory);
4304
+ } catch {
4305
+ return null;
4306
+ }
4307
+ const socket = `.live-${pinned.identity}-${randomUUID()}`;
4308
+ const server = createServer((connection) => connection.destroy());
4309
+ let bound = false;
4310
+ try {
4311
+ await new Promise((resolve2, reject) => {
4312
+ server.once("error", reject);
4313
+ server.listen({ path: pinned.path(`${socket}.new`), exclusive: true }, () => {
4314
+ server.off("error", reject);
4315
+ resolve2();
4316
+ });
4317
+ });
4318
+ bound = true;
4319
+ server.on("error", () => void 0);
4320
+ server.unref();
4321
+ await rename(pinned.path(`${socket}.new`), pinned.path(socket));
4322
+ const stats = await lstat(pinned.path(socket), { bigint: true });
4323
+ if (!stats.isSocket()) throw new Error(`${socket} is not a socket`);
4324
+ await waitForTestBarrier(TEST_LOCK_LIVENESS_BARRIER_DIR, basename(directory));
4325
+ return { record: { socket, dev: String(stats.dev), ino: String(stats.ino) }, path: join(directory, socket), server };
4326
+ } catch {
4327
+ if (bound) {
4328
+ server.close();
4329
+ for (const name of [`${socket}.new`, socket]) await unlink(pinned.path(name)).catch(() => void 0);
4330
+ }
4331
+ return null;
4332
+ } finally {
4333
+ await pinned.close();
4334
+ }
4335
+ }
4336
+ async function ensureLiveness(acquisition) {
4337
+ if (acquisition.held === void 0) acquisition.held = await bindLiveness(acquisition.directory);
4338
+ return acquisition.held;
4339
+ }
4340
+ async function closeLiveness(held) {
4341
+ held.server.close();
4342
+ await unlink(held.path).catch(() => void 0);
4343
+ }
4344
+ function parseLiveness(value) {
4345
+ if (typeof value !== "object" || value === null) return null;
4346
+ const { socket, dev, ino } = value;
4347
+ const name = typeof socket === "string" ? LIVENESS_SOCKET.exec(socket) : null;
4348
+ return name !== null && name[3] === void 0 && typeof dev === "string" && /^[0-9]+$/.test(dev) && typeof ino === "string" && /^[0-9]+$/.test(ino) ? { socket: name[0], dev, ino } : null;
4349
+ }
4350
+ async function controlIn(pinned, acquisition) {
4351
+ const own = await ensureLiveness(acquisition);
4352
+ if (!own || socketDirectory(own.record.socket) !== pinned.identity) return null;
4353
+ const stats = await lstat(pinned.path(own.record.socket), { bigint: true }).catch(() => null);
4354
+ return stats?.isSocket() && String(stats.dev) === own.record.dev && String(stats.ino) === own.record.ino ? own : null;
4355
+ }
4356
+ async function tryConnect(path) {
4357
+ return new Promise((resolve2) => {
4358
+ const probe = connect({ path });
4359
+ const settle = (outcome) => {
4360
+ clearTimeout(timer);
4361
+ probe.destroy();
4362
+ resolve2(outcome);
4363
+ };
4364
+ const timer = setTimeout(() => settle("other"), LIVENESS_PROBE_TIMEOUT_MS);
4365
+ probe.once("connect", () => settle("connected"));
4366
+ probe.once("error", (error) => settle(isErrno(error, "ECONNREFUSED") ? "refused" : "other"));
4367
+ });
4368
+ }
4369
+ async function kernelRefuses(pinned, name, control) {
4370
+ if (process.versions.bun !== void 0) return false;
4371
+ return await tryConnect(pinned.path(name)) === "refused" && await tryConnect(pinned.path(control.record.socket)) === "connected";
4372
+ }
4373
+ async function probeLiveness(liveness, acquisition) {
4374
+ let pinned;
4375
+ try {
4376
+ pinned = await pinDirectory(acquisition.directory);
4377
+ } catch {
4378
+ return "unknown";
4379
+ }
4380
+ try {
4381
+ if (socketDirectory(liveness.socket) !== pinned.identity) return "unknown";
4382
+ const control = await controlIn(pinned, acquisition);
4383
+ if (!control) return "unknown";
4384
+ let stats;
4385
+ try {
4386
+ stats = await lstat(pinned.path(liveness.socket), { bigint: true });
4387
+ } catch (error) {
4388
+ return isErrno(error, "ENOENT") ? "gone" : "unknown";
4389
+ }
4390
+ if (!stats.isSocket() || String(stats.dev) !== liveness.dev || String(stats.ino) !== liveness.ino) return "unknown";
4391
+ return await kernelRefuses(pinned, liveness.socket, control) ? "refused" : "unknown";
4392
+ } finally {
4393
+ await pinned.close();
4394
+ }
4395
+ }
4396
+ async function sweepLiveness(acquisition) {
4397
+ if (process.versions.bun !== void 0 || !acquisition.held) return;
4398
+ const pinned = await pinDirectory(acquisition.directory);
4399
+ try {
4400
+ const control = await controlIn(pinned, acquisition);
4401
+ if (!control) return;
4402
+ for (const name of await readdir(pinned.path("."))) {
4403
+ if (name === control.record.socket || socketDirectory(name) !== pinned.identity) continue;
4404
+ const stats = await lstat(pinned.path(name)).catch(() => null);
4405
+ if (stats?.isSocket() && await kernelRefuses(pinned, name, control)) {
4406
+ await unlink(pinned.path(name)).catch(() => void 0);
4407
+ }
4408
+ }
4409
+ } finally {
4410
+ await pinned.close();
4411
+ }
4256
4412
  }
4257
4413
  function tinycloudHomePath() {
4258
4414
  const home = invocationStateRoot.getStore() ?? process.env.TC_HOME ?? process.env.HOME ?? process.env.USERPROFILE ?? homedir();
@@ -4429,7 +4585,18 @@ async function acquireProfileLock(profile, options) {
4429
4585
  if (path !== directory || !isErrno(error, "ENOENT")) throw error;
4430
4586
  });
4431
4587
  }
4432
- const { turn, firstWaitedTurn } = await acquireTurn(profile, turnDirectory, deadline);
4588
+ const liveness = { directory: turnDirectory, held: void 0 };
4589
+ const closeAcquisitionLiveness = async () => {
4590
+ if (liveness.held) await closeLiveness(liveness.held);
4591
+ };
4592
+ let taken;
4593
+ try {
4594
+ taken = await acquireTurn(profile, turnDirectory, deadline, liveness);
4595
+ } catch (error) {
4596
+ await closeAcquisitionLiveness();
4597
+ throw error;
4598
+ }
4599
+ const { turn, firstWaitedTurn } = taken;
4433
4600
  try {
4434
4601
  if (options.requireProfile && !await exists(profileConfigPath(profile))) {
4435
4602
  await rmdir(directory).catch(() => void 0);
@@ -4439,7 +4606,7 @@ async function acquireProfileLock(profile, options) {
4439
4606
  const deletedWhileWaiting = typeof deletion?.slot === "number" && deletion.slot >= firstWaitedTurn;
4440
4607
  while (true) {
4441
4608
  const token = randomUUID();
4442
- if (await publishProfileLock(profile, lockPath, token, options.requireProfile === true)) {
4609
+ if (await publishProfileLock(profile, lockPath, token, options.requireProfile === true, liveness.held ?? null)) {
4443
4610
  return {
4444
4611
  deletedWhileWaiting,
4445
4612
  turnSlot: turn.slot,
@@ -4447,17 +4614,19 @@ async function acquireProfileLock(profile, options) {
4447
4614
  try {
4448
4615
  await releaseProfileLock(profile, lockPath, token);
4449
4616
  } finally {
4450
- await releaseTurn(profile, turn);
4617
+ await releaseTurn(profile, turn, liveness);
4618
+ await closeAcquisitionLiveness();
4451
4619
  }
4452
4620
  }
4453
4621
  };
4454
4622
  }
4455
4623
  await signalTestLockContention(profile);
4456
- if (await recoverStaleLock(profile, lockPath, staleAfterMs)) continue;
4624
+ if (await recoverStaleLock(profile, lockPath, liveness, staleAfterMs)) continue;
4457
4625
  await waitOrTimeOut(profile, deadline);
4458
4626
  }
4459
4627
  } catch (error) {
4460
- await releaseTurn(profile, turn);
4628
+ await releaseTurn(profile, turn, liveness);
4629
+ await closeAcquisitionLiveness();
4461
4630
  throw error;
4462
4631
  }
4463
4632
  }
@@ -4470,7 +4639,7 @@ async function waitOrTimeOut(profile, deadline, detail) {
4470
4639
  checkDeadline(profile, deadline, detail);
4471
4640
  await sleep(Math.min(deadline.retryMs, deadline.timeoutMs - (Date.now() - deadline.startedAt)));
4472
4641
  }
4473
- async function acquireTurn(profile, directory, deadline) {
4642
+ async function acquireTurn(profile, directory, deadline, liveness) {
4474
4643
  let firstWaitedTurn;
4475
4644
  let problem;
4476
4645
  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.`;
@@ -4495,13 +4664,13 @@ async function acquireTurn(profile, directory, deadline) {
4495
4664
  }
4496
4665
  firstWaitedTurn ?? (firstWaitedTurn = latest.done ? latest.slot + 1 : latest.slot);
4497
4666
  if (latest.held && latest.done) {
4498
- const turn = await takeTurn(profile, directory, latest);
4667
+ const turn = await takeTurn(profile, directory, latest, await ensureLiveness(liveness));
4499
4668
  if (turn !== null) return { turn, firstWaitedTurn };
4500
4669
  await waitOrTimeOut(profile, deadline);
4501
4670
  } else if (latest.done) {
4502
4671
  problem = damaged(`ends with turn ${latest.slot}, which was never granted`);
4503
4672
  await waitOrTimeOut(profile, deadline, problem);
4504
- } else if (latest.pid === process.pid ? relinquishedTurns.has(latest.token) : !isProcessAlive(latest.pid)) {
4673
+ } else if (latest.pid === process.pid && relinquishedTurns.has(latest.token) || !await isRecordedProcessAlive(latest, liveness)) {
4505
4674
  await waitForTestBarrier(TEST_LOCK_TURN_SETTLE_BARRIER_DIR, profile);
4506
4675
  await settleAbandonedTurn(directory, latest);
4507
4676
  relinquishedTurns.delete(latest.token);
@@ -4546,6 +4715,7 @@ async function readTurn(directory, slot) {
4546
4715
  const done = await exists(join(path, `${owner.token}.done`));
4547
4716
  const held = await exists(join(path, `${owner.token}.held`));
4548
4717
  return {
4718
+ ...owner,
4549
4719
  slot,
4550
4720
  token: owner.token,
4551
4721
  pid: owner.pid,
@@ -4559,11 +4729,11 @@ async function isFinishedTurn(directory, slot, token) {
4559
4729
  if (turn === "damaged") throw new Error(`Turn ${slot} of the turn lock ${directory} is unreadable.`);
4560
4730
  return turn !== null && turn.token === token && turn.held && turn.done;
4561
4731
  }
4562
- async function takeTurn(profile, directory, latest) {
4732
+ async function takeTurn(profile, directory, latest, liveness) {
4563
4733
  const token = randomUUID();
4564
4734
  const slot = latest.slot + 1;
4565
4735
  const path = join(directory, String(slot));
4566
- const owner = { pid: process.pid, createdAt: (/* @__PURE__ */ new Date()).toISOString(), token, after: latest.token };
4736
+ const owner = { pid: process.pid, createdAt: (/* @__PURE__ */ new Date()).toISOString(), token, after: latest.token, ...processOwner(liveness) };
4567
4737
  let published = false;
4568
4738
  try {
4569
4739
  published = await publishDirectory(
@@ -4617,7 +4787,7 @@ async function markTurn(path, token, kind) {
4617
4787
  }
4618
4788
  async function createTurnDirectory(directory) {
4619
4789
  const token = randomUUID();
4620
- const owner = { pid: process.pid, createdAt: (/* @__PURE__ */ new Date()).toISOString(), token, after: null };
4790
+ const owner = { pid: process.pid, createdAt: (/* @__PURE__ */ new Date()).toISOString(), token, after: null, ...processOwner(null) };
4621
4791
  await publishDirectory(join(dirname(directory), `.stage-${basename(directory)}-${process.pid}-${token}`), directory, {
4622
4792
  "0/owner.json": `${JSON.stringify(owner, null, 2)}
4623
4793
  `,
@@ -4641,31 +4811,39 @@ async function publishDirectory(stage, target, files, beforeRename) {
4641
4811
  await rm(stage, { recursive: true, force: true }).catch(() => void 0);
4642
4812
  }
4643
4813
  }
4644
- async function releaseTurn(profile, turn) {
4814
+ async function releaseTurn(profile, turn, liveness) {
4645
4815
  try {
4646
4816
  await markTurn(join(turn.directory, String(turn.slot)), turn.token, "done");
4647
4817
  } catch {
4648
4818
  settleOwnTurn(turn.directory, turn.slot, turn.token);
4649
4819
  return;
4650
4820
  }
4651
- await collectTurns(profile, turn).catch(() => void 0);
4821
+ await collectTurns(profile, turn, liveness).catch(() => void 0);
4652
4822
  }
4653
- function isAbandonedTurnEntry(name) {
4823
+ async function isAbandonedTurnEntry(directory, name, liveness) {
4654
4824
  if (name.startsWith(".trash-")) return true;
4655
4825
  const writer = /^\.(?:stage|deleted\.json)[-.]([0-9]+)[-.]/.exec(name)?.[1];
4656
- return writer !== void 0 && !isProcessAlive(Number(writer));
4826
+ if (writer === void 0) return false;
4827
+ if (Number(writer) !== process.pid) return !await isRecordedProcessAlive({ pid: Number(writer) }, liveness);
4828
+ return name.startsWith(".stage-") && await isStagedOwnerGone(join(directory, name, "owner.json"), liveness);
4657
4829
  }
4658
- function isAbandonedTurnDirectoryStage(name) {
4830
+ async function isAbandonedTurnDirectoryStage(directory, name, liveness) {
4659
4831
  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];
4660
- return writer !== void 0 && !isProcessAlive(Number(writer));
4832
+ if (writer === void 0) return false;
4833
+ if (Number(writer) !== process.pid) return !await isRecordedProcessAlive({ pid: Number(writer) }, liveness);
4834
+ return isStagedOwnerGone(join(directory, name, "0", "owner.json"), liveness);
4835
+ }
4836
+ async function isStagedOwnerGone(recordPath, liveness) {
4837
+ const owner = await readJson(recordPath).catch(() => null);
4838
+ return typeof owner === "object" && owner !== null && owner.pid === process.pid && !await isRecordedProcessAlive(owner, liveness);
4661
4839
  }
4662
- async function collectTurns(profile, turn) {
4840
+ async function collectTurns(profile, turn, liveness) {
4663
4841
  for (const [directory, abandoned] of [
4664
4842
  [turn.directory, isAbandonedTurnEntry],
4665
4843
  [dirname(turn.directory), isAbandonedTurnDirectoryStage]
4666
4844
  ]) {
4667
- for (const name of (await readdir(directory)).filter(abandoned)) {
4668
- await rm(join(directory, name), { recursive: true, force: true }).catch(() => void 0);
4845
+ for (const name of await readdir(directory)) {
4846
+ if (await abandoned(directory, name, liveness)) await rm(join(directory, name), { recursive: true, force: true }).catch(() => void 0);
4669
4847
  }
4670
4848
  }
4671
4849
  const older = (await readdir(turn.directory)).filter((name) => TURN.test(name)).map(Number).filter((slot) => slot < turn.slot).sort((left, right) => left - right);
@@ -4683,8 +4861,9 @@ async function collectTurns(profile, turn) {
4683
4861
  await rm(trash, { recursive: true, force: true }).catch(() => void 0);
4684
4862
  }
4685
4863
  }
4864
+ await sweepLiveness(liveness);
4686
4865
  }
4687
- async function publishProfileLock(profile, lockPath, token, requireProfile) {
4866
+ async function publishProfileLock(profile, lockPath, token, requireProfile, liveness) {
4688
4867
  try {
4689
4868
  await mkdir(lockPath, { mode: PRIVATE_DIR_MODE });
4690
4869
  } catch (error) {
@@ -4697,7 +4876,7 @@ async function publishProfileLock(profile, lockPath, token, requireProfile) {
4697
4876
  await waitForTestBarrier(TEST_LOCK_PUBLISH_BARRIER_DIR, profile);
4698
4877
  const ownerPath = join(lockPath, "owner.json");
4699
4878
  const staged = join(dirname(lockPath), `.lock-owner-${token}.tmp`);
4700
- const owner = `${JSON.stringify({ pid: process.pid, createdAt: (/* @__PURE__ */ new Date()).toISOString(), token }, null, 2)}
4879
+ const owner = `${JSON.stringify({ pid: process.pid, createdAt: (/* @__PURE__ */ new Date()).toISOString(), token, ...processOwner(liveness) }, null, 2)}
4701
4880
  `;
4702
4881
  try {
4703
4882
  await writeFile(staged, owner, { encoding: "utf8", mode: PRIVATE_FILE_MODE, flag: "wx" });
@@ -4741,7 +4920,7 @@ async function signalTestLockContention(profile) {
4741
4920
  await writeFile(signalPath, `${profile}
4742
4921
  `, { encoding: "utf8", flag: "wx" }).catch(() => void 0);
4743
4922
  }
4744
- async function recoverOwnerlessLock(profile, lockPath, staleAfterMs) {
4923
+ async function recoverOwnerlessLock(profile, lockPath, liveness, staleAfterMs) {
4745
4924
  let aged;
4746
4925
  try {
4747
4926
  const { mtimeMs } = await stat(lockPath);
@@ -4758,7 +4937,7 @@ async function recoverOwnerlessLock(profile, lockPath, staleAfterMs) {
4758
4937
  if (!onlyOrphanedClaims && (!aged || recoveryFenced(checkedAt, staleAfterMs))) return false;
4759
4938
  for (const name of entries.filter((entry) => RECOVERY_CLAIM.test(entry))) {
4760
4939
  const claimPath = join(lockPath, name);
4761
- if (await hasLiveRecoveryOwner(claimPath)) {
4940
+ if (await hasLiveRecoveryOwner(claimPath, liveness)) {
4762
4941
  if (!await exists(join(lockPath, "owner.json"))) await rename(claimPath, join(lockPath, "owner.json"));
4763
4942
  return false;
4764
4943
  }
@@ -4774,7 +4953,7 @@ async function recoverOwnerlessLock(profile, lockPath, staleAfterMs) {
4774
4953
  await removeAgedOwnerFiles(lockPath, staleAfterMs);
4775
4954
  return true;
4776
4955
  }
4777
- async function hasLiveRecoveryOwner(claimPath) {
4956
+ async function hasLiveRecoveryOwner(claimPath, liveness) {
4778
4957
  let contents;
4779
4958
  try {
4780
4959
  contents = await readFile(claimPath, "utf8");
@@ -4788,8 +4967,8 @@ async function hasLiveRecoveryOwner(claimPath) {
4788
4967
  } catch {
4789
4968
  return false;
4790
4969
  }
4791
- const pid = typeof owner === "object" && owner !== null && "pid" in owner ? owner.pid : null;
4792
- return typeof pid === "number" && Number.isInteger(pid) && pid > 0 && isProcessAlive(pid);
4970
+ const record = typeof owner === "object" && owner !== null && "pid" in owner ? owner : null;
4971
+ return typeof record?.pid === "number" && Number.isInteger(record.pid) && record.pid > 0 && await isRecordedProcessAlive(record, liveness);
4793
4972
  }
4794
4973
  function recoveryFenced(since, staleAfterMs) {
4795
4974
  return performance.now() - since >= staleAfterMs / 2;
@@ -4803,11 +4982,11 @@ async function removeAgedOwnerFiles(lockPath, staleAfterMs) {
4803
4982
  if (aged) await rm(path, { force: true }).catch(() => void 0);
4804
4983
  }
4805
4984
  }
4806
- async function recoverStaleLock(profile, lockPath, staleAfterMs) {
4985
+ async function recoverStaleLock(profile, lockPath, liveness, staleAfterMs) {
4807
4986
  const ownerPath = join(lockPath, "owner.json");
4808
4987
  const observed = await readFile(ownerPath, "utf8").catch(() => null);
4809
- if (observed === null) return recoverOwnerlessLock(profile, lockPath, staleAfterMs);
4810
- if (!await isAbandonedOwner(observed, ownerPath, staleAfterMs)) return false;
4988
+ if (observed === null) return recoverOwnerlessLock(profile, lockPath, liveness, staleAfterMs);
4989
+ if (!await isAbandonedOwner(observed, ownerPath, liveness, staleAfterMs)) return false;
4811
4990
  await waitForTestBarrier(TEST_LOCK_RECOVERY_BARRIER_DIR, profile);
4812
4991
  const claimPath = join(lockPath, `.recover-${randomUUID()}.json`);
4813
4992
  let moved = false;
@@ -4843,7 +5022,7 @@ async function recoverStaleLock(profile, lockPath, staleAfterMs) {
4843
5022
  await removeAgedOwnerFiles(lockPath, staleAfterMs);
4844
5023
  return true;
4845
5024
  }
4846
- async function isAbandonedOwner(observed, ownerPath, staleAfterMs) {
5025
+ async function isAbandonedOwner(observed, ownerPath, liveness, staleAfterMs) {
4847
5026
  let owner;
4848
5027
  try {
4849
5028
  owner = JSON.parse(observed);
@@ -4851,7 +5030,7 @@ async function isAbandonedOwner(observed, ownerPath, staleAfterMs) {
4851
5030
  owner = null;
4852
5031
  }
4853
5032
  if (typeof owner === "object" && owner !== null && "pid" in owner) {
4854
- return isStaleOwner(owner, staleAfterMs);
5033
+ return isStaleOwner(owner, liveness, staleAfterMs);
4855
5034
  }
4856
5035
  const modifiedAt = await stat(ownerPath).then(({ mtimeMs }) => mtimeMs, () => Number.NaN);
4857
5036
  return Date.now() - modifiedAt >= staleAfterMs;
@@ -4873,14 +5052,14 @@ async function waitForTestBarrier(environmentName, profile) {
4873
5052
  }
4874
5053
  }
4875
5054
  }
4876
- function isStaleOwner(owner, staleAfterMs) {
5055
+ async function isStaleOwner(owner, liveness, staleAfterMs) {
4877
5056
  const now = Date.now();
4878
5057
  const createdAt = typeof owner?.createdAt === "string" ? Date.parse(owner.createdAt) : Number.NaN;
4879
5058
  if (!Number.isFinite(createdAt) || now - createdAt < staleAfterMs) return false;
4880
5059
  if (typeof owner?.pid !== "number" || !Number.isInteger(owner.pid) || owner.pid <= 0) {
4881
5060
  return false;
4882
5061
  }
4883
- return !isProcessAlive(owner.pid);
5062
+ return !await isRecordedProcessAlive(owner, liveness);
4884
5063
  }
4885
5064
  async function exists(path) {
4886
5065
  try {
@@ -4891,13 +5070,22 @@ async function exists(path) {
4891
5070
  throw error;
4892
5071
  }
4893
5072
  }
4894
- function isProcessAlive(pid) {
5073
+ async function isRecordedProcessAlive(owner, acquisition) {
5074
+ const pid = owner.pid;
5075
+ if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0) return true;
4895
5076
  try {
4896
5077
  process.kill(pid, 0);
4897
- return true;
4898
5078
  } catch (error) {
4899
5079
  return !isErrno(error, "ESRCH");
4900
5080
  }
5081
+ if (pid !== process.pid || typeof owner.instanceId !== "string" || !/^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/.test(owner.instanceId) || owner.instanceId === processInstanceId) return true;
5082
+ const origin = processOrigin();
5083
+ if (!origin || owner.bootId !== origin.bootId || typeof owner.pidNamespace !== "string" || !/^pid:\[[0-9]+\]$/.test(owner.pidNamespace)) return true;
5084
+ if (owner.pidNamespace === origin.pidNamespace) {
5085
+ return typeof owner.processStartTime !== "string" || !/^[0-9]+$/.test(owner.processStartTime) || owner.processStartTime === origin.processStartTime;
5086
+ }
5087
+ const liveness = parseLiveness(owner.liveness);
5088
+ return liveness === null || await probeLiveness(liveness, acquisition) === "unknown";
4901
5089
  }
4902
5090
  function validateProfileName(profile) {
4903
5091
  if (!profile || profile === "." || profile === ".." || profile.includes("/") || profile.includes("\\") || profile.includes("\0")) {
@@ -4911,7 +5099,7 @@ function isErrno(error, code) {
4911
5099
  function sleep(milliseconds) {
4912
5100
  return new Promise((resolve2) => setTimeout(resolve2, milliseconds));
4913
5101
  }
4914
- var 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, _a2, relinquishedTurns, MARKER_RETRY_DELAYS_MS, SETTLE_RETRY_MS, SETTLE_RETRIES, NO_HARD_LINK_ERRORS, ABANDONED_CLAIM, RECOVERY_CLAIM, STAGED_OWNER;
5102
+ var 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, TEST_LOCK_LIVENESS_BARRIER_DIR, invocationStateRoot, heldProfileLocks, _a2, processInstanceId, cachedProcessOrigin, LIVENESS_SOCKET, LIVENESS_PROBE_TIMEOUT_MS, ProfileLockTimeoutError, ProfileDeletedError, TURN, _a3, relinquishedTurns, MARKER_RETRY_DELAYS_MS, SETTLE_RETRY_MS, SETTLE_RETRIES, NO_HARD_LINK_ERRORS, ABANDONED_CLAIM, RECOVERY_CLAIM, STAGED_OWNER;
4915
5103
  var init_state = __esm({
4916
5104
  "src/state.ts"() {
4917
5105
  "use strict";
@@ -4933,8 +5121,12 @@ var init_state = __esm({
4933
5121
  TEST_LOCK_TURN_PUBLISH_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_TURN_PUBLISH_BARRIER_DIR";
4934
5122
  TEST_LOCK_TURN_SETTLE_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_TURN_SETTLE_BARRIER_DIR";
4935
5123
  TEST_LOCK_TURN_COLLECT_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_TURN_COLLECT_BARRIER_DIR";
5124
+ TEST_LOCK_LIVENESS_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_LIVENESS_BARRIER_DIR";
4936
5125
  invocationStateRoot = processWideContext("tinycloud.operations.invocationStateRoot.v1");
4937
5126
  heldProfileLocks = processWideContext("tinycloud.operations.heldProfileLocks.v2");
5127
+ processInstanceId = globalThis[_a2 = /* @__PURE__ */ Symbol.for("tinycloud.operations.profileLockInstance.v1")] ?? (globalThis[_a2] = randomUUID());
5128
+ LIVENESS_SOCKET = /^\.live-([0-9a-f]+)-([0-9a-f]+)-[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}(\.new)?$/;
5129
+ LIVENESS_PROBE_TIMEOUT_MS = 1e3;
4938
5130
  ProfileLockTimeoutError = class extends Error {
4939
5131
  /** `detail` names the cause when it is not simply another process holding the lock. */
4940
5132
  constructor(profile, timeoutMs, detail) {
@@ -4951,7 +5143,7 @@ var init_state = __esm({
4951
5143
  }
4952
5144
  };
4953
5145
  TURN = /^(?:0|[1-9][0-9]*)$/;
4954
- relinquishedTurns = globalThis[_a2 = /* @__PURE__ */ Symbol.for("tinycloud.operations.relinquishedProfileTurns.v1")] ?? (globalThis[_a2] = /* @__PURE__ */ new Set());
5146
+ relinquishedTurns = globalThis[_a3 = /* @__PURE__ */ Symbol.for("tinycloud.operations.relinquishedProfileTurns.v1")] ?? (globalThis[_a3] = /* @__PURE__ */ new Set());
4955
5147
  MARKER_RETRY_DELAYS_MS = [10, 50, 250];
4956
5148
  SETTLE_RETRY_MS = 1e3;
4957
5149
  SETTLE_RETRIES = 300;
@@ -5243,6 +5435,45 @@ var init_artifacts = __esm({
5243
5435
  }
5244
5436
  });
5245
5437
 
5438
+ // src/recovery.ts
5439
+ function signInRecovery(profile, posture) {
5440
+ if (!SAFE_PROFILE_TOKEN.test(profile)) return { action: "sign_in" };
5441
+ const template = posture === "local-owner-key" ? SIGN_IN_TEMPLATES.local : posture === "delegate-session" ? SIGN_IN_TEMPLATES.scoped : SIGN_IN_TEMPLATES.openkey;
5442
+ return { action: "sign_in", command: template(profile) };
5443
+ }
5444
+ function migrationRecovery(profile) {
5445
+ return SAFE_PROFILE_TOKEN.test(profile) ? { action: "complete_migration", command: migrationCommand(profile), operationId: MIGRATION_OPERATION_ID } : { action: "complete_migration", operationId: MIGRATION_OPERATION_ID };
5446
+ }
5447
+ function isSafeRecovery(value, action) {
5448
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
5449
+ const record = value;
5450
+ if (record.action !== action) return false;
5451
+ const keys = Object.keys(record);
5452
+ const allowed = action === "sign_in" ? ["action", "command"] : ["action", "command", "operationId"];
5453
+ if (!keys.every((key) => allowed.includes(key))) return false;
5454
+ if (action === "complete_migration" && record.operationId !== MIGRATION_OPERATION_ID) return false;
5455
+ if (!("command" in record)) return true;
5456
+ const command = record.command;
5457
+ if (typeof command !== "string") return false;
5458
+ const profile = /^tc --profile (\S+) /.exec(command)?.[1];
5459
+ if (profile === void 0 || !SAFE_PROFILE_TOKEN.test(profile)) return false;
5460
+ return action === "sign_in" ? Object.values(SIGN_IN_TEMPLATES).some((template) => template(profile) === command) : migrationCommand(profile) === command;
5461
+ }
5462
+ var SAFE_PROFILE_TOKEN, MIGRATION_OPERATION_ID, SIGN_IN_TEMPLATES, migrationCommand;
5463
+ var init_recovery = __esm({
5464
+ "src/recovery.ts"() {
5465
+ "use strict";
5466
+ SAFE_PROFILE_TOKEN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
5467
+ MIGRATION_OPERATION_ID = "tinycloud.auth.capabilities";
5468
+ SIGN_IN_TEMPLATES = {
5469
+ local: (profile) => `tc --profile ${profile} auth login --method local`,
5470
+ openkey: (profile) => `tc --profile ${profile} auth login --method openkey`,
5471
+ scoped: (profile) => `tc --profile ${profile} auth login --method openkey --paste --manifest <manifest.json>`
5472
+ };
5473
+ migrationCommand = (profile) => `tc --profile ${profile} auth caps`;
5474
+ }
5475
+ });
5476
+
5246
5477
  // src/secrets.ts
5247
5478
  import * as nodeSdk from "@tinycloud/node-sdk";
5248
5479
  function normalizeSecretsGetInput(input) {
@@ -5330,15 +5561,6 @@ function canonicalizeSpaceUri(space) {
5330
5561
  }
5331
5562
  return space;
5332
5563
  }
5333
- function buildSecretSetupUrl(reference) {
5334
- const parameters = [
5335
- ["secret", reference.name],
5336
- ...reference.scope === void 0 ? [] : [["scope", reference.scope]],
5337
- ["space", reference.space]
5338
- ];
5339
- const query = parameters.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join("&");
5340
- return `${SECRET_MANAGER_BASE_URL}/app?${query}`;
5341
- }
5342
5564
  function canonicalizeSecretScope(scope) {
5343
5565
  if (scope === void 0 || scope === "") return void 0;
5344
5566
  const trimmed = scope.trim();
@@ -5366,13 +5588,12 @@ function invalidSecretInput() {
5366
5588
  operationError("INPUT_INVALID", "The secret reference is invalid.")
5367
5589
  );
5368
5590
  }
5369
- var DEFAULT_SECRETS_SPACE, SECRET_MANAGER_BASE_URL, SECRET_NAME_RE2, RESERVED_SECRET_SCOPES;
5591
+ var DEFAULT_SECRETS_SPACE, SECRET_NAME_RE2, RESERVED_SECRET_SCOPES;
5370
5592
  var init_secrets = __esm({
5371
5593
  "src/secrets.ts"() {
5372
5594
  "use strict";
5373
5595
  init_errors2();
5374
5596
  DEFAULT_SECRETS_SPACE = "secrets";
5375
- SECRET_MANAGER_BASE_URL = "https://secrets.tinycloud.xyz";
5376
5597
  SECRET_NAME_RE2 = /^[A-Z][A-Z0-9_]*$/;
5377
5598
  RESERVED_SECRET_SCOPES = /* @__PURE__ */ new Set(["default", "global"]);
5378
5599
  }
@@ -5857,8 +6078,33 @@ var init_auth = __esm({
5857
6078
  }
5858
6079
  });
5859
6080
 
5860
- // src/operations/exploration.ts
6081
+ // src/kv-failure.ts
5861
6082
  import { authorizationVerdictOf, validatedCapabilityOf } from "@tinycloud/sdk-services";
6083
+ function kvRefusal(error) {
6084
+ const verdict = authorizationVerdictOf(error);
6085
+ if (verdict === "unauthenticated") {
6086
+ return validatedCapabilityOf(error) === void 0 ? "auth_required" : "permission_denied";
6087
+ }
6088
+ return verdict === "forbidden" ? "permission_denied" : void 0;
6089
+ }
6090
+ function authorizationFailure(error, action) {
6091
+ switch (kvRefusal(error)) {
6092
+ case "auth_required":
6093
+ return { status: "error", error: operationError("AUTH_REQUIRED", `Authentication is required to ${action}.`) };
6094
+ case "permission_denied":
6095
+ return { status: "error", error: operationError("PERMISSION_DENIED", `Permission is denied to ${action}.`) };
6096
+ case void 0:
6097
+ return void 0;
6098
+ }
6099
+ }
6100
+ var init_kv_failure = __esm({
6101
+ "src/kv-failure.ts"() {
6102
+ "use strict";
6103
+ init_errors2();
6104
+ }
6105
+ });
6106
+
6107
+ // src/operations/exploration.ts
5862
6108
  import { z as z4 } from "zod";
5863
6109
  async function planAccountRegistryRead(context, path) {
5864
6110
  return [{
@@ -6226,19 +6472,6 @@ function caughtKvFailure(error, action) {
6226
6472
  if (error instanceof OperationInvocationError) return { status: "error", error: error.operationError };
6227
6473
  return authorizationFailure(error, action) ?? nodeFailure(action);
6228
6474
  }
6229
- function authorizationFailure(error, action) {
6230
- const verdict = authorizationVerdictOf(error);
6231
- if (verdict === "unauthenticated") {
6232
- if (validatedCapabilityOf(error) !== void 0) {
6233
- return { status: "error", error: operationError("PERMISSION_DENIED", `Permission is denied to ${action}.`) };
6234
- }
6235
- return { status: "error", error: operationError("AUTH_REQUIRED", `Authentication is required to ${action}.`) };
6236
- }
6237
- if (verdict === "forbidden") {
6238
- return { status: "error", error: operationError("PERMISSION_DENIED", `Permission is denied to ${action}.`) };
6239
- }
6240
- return void 0;
6241
- }
6242
6475
  function accountRegistryOutput(output) {
6243
6476
  if (Buffer.byteLength(JSON.stringify(output), "utf8") > MAX_ACCOUNT_REGISTRY_BYTES) {
6244
6477
  return {
@@ -6291,6 +6524,7 @@ var MAX_KV_VALUE_BYTES, DEFAULT_KV_LIST_LIMIT, MAX_ACCOUNT_REGISTRY_RECORDS, MAX
6291
6524
  var init_exploration = __esm({
6292
6525
  "src/operations/exploration.ts"() {
6293
6526
  "use strict";
6527
+ init_kv_failure();
6294
6528
  init_errors2();
6295
6529
  init_secrets();
6296
6530
  MAX_KV_VALUE_BYTES = 1024 * 1024;
@@ -6591,6 +6825,191 @@ var init_exploration = __esm({
6591
6825
  }
6592
6826
  });
6593
6827
 
6828
+ // src/secret-request.ts
6829
+ import * as nodeSdk3 from "@tinycloud/node-sdk";
6830
+ function canonicalSecretRequestOwner(did) {
6831
+ const match = typeof did === "string" ? OWNER_RE.exec(did) : null;
6832
+ if (match === null || !Number.isSafeInteger(Number(match[1]))) throw invalidOwner();
6833
+ const address = match[2];
6834
+ let checksummed;
6835
+ try {
6836
+ checksummed = nodeSdk3.canonicalizeAddress(address);
6837
+ } catch {
6838
+ throw invalidOwner();
6839
+ }
6840
+ if (/[A-F]/.test(address.slice(2)) && address !== checksummed) throw invalidOwner();
6841
+ return `did:pkh:eip155:${match[1]}:${checksummed}`;
6842
+ }
6843
+ function buildSecretRequestUrl(input) {
6844
+ const owner = canonicalSecretRequestOwner(input.owner);
6845
+ const names = validSecretNames(input.names);
6846
+ if (input.scope !== void 0) assertSecretRequestScope(input.scope);
6847
+ const requester = input.requester;
6848
+ if (requester !== void 0 && (typeof requester !== "string" || requester.length === 0 || requester.length > MAX_REQUESTER_LENGTH || CONTROL_OR_FORMAT.test(requester))) {
6849
+ throw new SecretRequestInputError("INVALID_REQUESTER", "The requester label must be 1-64 characters with no control or format characters.");
6850
+ }
6851
+ if (input.returnTo !== void 0 && !isAllowedReturnTo(input.returnTo)) {
6852
+ throw new SecretRequestInputError("INVALID_RETURN_TO", "The return address is not an allowed Secret Manager callback.");
6853
+ }
6854
+ if (input.state !== void 0 && !STATE_RE.test(input.state)) {
6855
+ throw new SecretRequestInputError("INVALID_STATE", "The state must be 1-128 characters of A-Z, a-z, 0-9, '.', '_', '~' or '-'.");
6856
+ }
6857
+ const parameters = [
6858
+ ["owner", owner],
6859
+ ...names.map((name) => ["name", name]),
6860
+ ...input.scope === void 0 ? [] : [["scope", input.scope]],
6861
+ ...input.requester === void 0 ? [] : [["requester", input.requester]],
6862
+ ...input.returnTo === void 0 ? [] : [["return_to", input.returnTo]],
6863
+ ...input.state === void 0 ? [] : [["state", input.state]],
6864
+ ["v", "1"]
6865
+ ];
6866
+ const url = `${SECRET_MANAGER_ORIGIN}/request?${parameters.map(([key, value]) => `${key}=${encodeURIComponent(value)}`).join("&")}`;
6867
+ if (url.length > SECRET_REQUEST_MAX_URL_LENGTH) {
6868
+ throw new SecretRequestInputError("REQUEST_LINK_TOO_LONG", `The request link is longer than ${SECRET_REQUEST_MAX_URL_LENGTH} characters.`);
6869
+ }
6870
+ return { url, owner, names, ...input.scope === void 0 ? {} : { scope: input.scope } };
6871
+ }
6872
+ function secretSetupFor(link2) {
6873
+ return {
6874
+ url: link2.url,
6875
+ names: [...link2.names],
6876
+ ...link2.scope === void 0 ? {} : { scope: link2.scope },
6877
+ owner: link2.owner,
6878
+ action: "add_secret"
6879
+ };
6880
+ }
6881
+ function resolveSecretsOwner(profile, requestedOwner) {
6882
+ const resolved = profile.local ? ownerOrUndefined(profile.principalDid) : ownerOrUndefined(profile.ownerDid) ?? (profile.posture === "delegate-session" ? ownerOfSpace(profile.space) : void 0) ?? ownerOrUndefined(profile.principalDid);
6883
+ const requested = requestedOwner === void 0 ? void 0 : ownerOrUndefined(requestedOwner);
6884
+ if (resolved !== void 0) {
6885
+ if (requestedOwner !== void 0 && requested !== resolved) {
6886
+ return {
6887
+ ok: false,
6888
+ code: "OWNER_MISMATCH",
6889
+ message: "The requested owner is not the owner of this identity's secrets."
6890
+ };
6891
+ }
6892
+ return { ok: true, owner: resolved, local: profile.local };
6893
+ }
6894
+ if (requested !== void 0 && !profile.local) return { ok: true, owner: requested, local: false };
6895
+ return {
6896
+ ok: false,
6897
+ code: "OWNER_DID_UNKNOWN",
6898
+ message: "The owner of this identity's secrets is unknown. Pass the owner's did:pkh with --owner."
6899
+ };
6900
+ }
6901
+ function missingSecretRecovery(input) {
6902
+ if (input.local) {
6903
+ return { kind: "put_locally", hint: `Store it with: ${putCommands(input.names, input.scope)}` };
6904
+ }
6905
+ if (input.owner !== void 0) {
6906
+ try {
6907
+ return {
6908
+ kind: "add_secret",
6909
+ setup: secretSetupFor(buildSecretRequestUrl({
6910
+ owner: input.owner,
6911
+ names: input.names,
6912
+ ...input.scope === void 0 ? {} : { scope: input.scope }
6913
+ }))
6914
+ };
6915
+ } catch {
6916
+ }
6917
+ }
6918
+ return {
6919
+ kind: "not_requestable",
6920
+ hint: `No Secret Manager request link can express this secret. The owner can store it with: ${putCommands(input.names, input.scope)}`
6921
+ };
6922
+ }
6923
+ function isValidSecretRequestScope(scope) {
6924
+ return scope.length <= MAX_SCOPE_LENGTH && SCOPE_RE.test(scope) && !RESERVED_SCOPES.includes(scope);
6925
+ }
6926
+ function isValidSecretName(name) {
6927
+ return name.length <= MAX_NAME_LENGTH && NAME_RE.test(name);
6928
+ }
6929
+ function validSecretNames(names) {
6930
+ for (const name of names) assertSecretName(name);
6931
+ const unique = dedupe(names);
6932
+ if (unique.length === 0) {
6933
+ throw new SecretRequestInputError("INVALID_SECRET_NAME", "At least one secret name is required.");
6934
+ }
6935
+ if (unique.length > SECRET_REQUEST_MAX_NAMES) {
6936
+ throw new SecretRequestInputError("TOO_MANY_SECRET_NAMES", `At most ${SECRET_REQUEST_MAX_NAMES} secret names can be requested at once.`);
6937
+ }
6938
+ return unique;
6939
+ }
6940
+ function assertSecretName(name) {
6941
+ if (typeof name !== "string" || !isValidSecretName(name)) {
6942
+ throw new SecretRequestInputError("INVALID_SECRET_NAME", "Secret names are 1-64 characters of A-Z, 0-9 and '_', starting with a letter.");
6943
+ }
6944
+ }
6945
+ function assertSecretRequestScope(scope) {
6946
+ if (typeof scope !== "string" || !isValidSecretRequestScope(scope)) {
6947
+ throw new SecretRequestInputError("INVALID_SECRET_SCOPE", "The scope must be a canonical scope of at most 64 characters, not default, global or scoped.");
6948
+ }
6949
+ }
6950
+ function isAllowedReturnTo(returnTo) {
6951
+ if (typeof returnTo !== "string" || returnTo.length > MAX_RETURN_TO_LENGTH) return false;
6952
+ let url;
6953
+ try {
6954
+ url = new URL(returnTo);
6955
+ } catch {
6956
+ return false;
6957
+ }
6958
+ if (url.username !== "" || url.password !== "") return false;
6959
+ return RETURN_TO_ORIGINS.includes(url.origin) || RETURN_TO_PREVIEW.test(url.origin);
6960
+ }
6961
+ function ownerOrUndefined(did) {
6962
+ if (did === void 0) return void 0;
6963
+ try {
6964
+ return canonicalSecretRequestOwner(did);
6965
+ } catch {
6966
+ return void 0;
6967
+ }
6968
+ }
6969
+ function ownerOfSpace(space) {
6970
+ const match = space === void 0 ? null : PKH_SPACE_RE.exec(space);
6971
+ if (match === null) return void 0;
6972
+ return ownerOrUndefined(`did:pkh:eip155:${match[1]}:${match[2].toLowerCase()}`);
6973
+ }
6974
+ function putCommands(names, scope) {
6975
+ return dedupe(names).map((name) => `tc secrets put ${name}${scope === void 0 ? "" : ` --scope ${scope}`} --stdin`).join("; ");
6976
+ }
6977
+ function dedupe(names) {
6978
+ return [...new Set(names)];
6979
+ }
6980
+ function invalidOwner() {
6981
+ return new SecretRequestInputError("INVALID_OWNER", "The owner must be a did:pkh:eip155 account with a valid EIP-55 address.");
6982
+ }
6983
+ var SECRET_MANAGER_ORIGIN, SECRET_REQUEST_MAX_NAMES, SECRET_REQUEST_MAX_URL_LENGTH, OWNER_RE, NAME_RE, MAX_NAME_LENGTH, SCOPE_RE, MAX_SCOPE_LENGTH, RESERVED_SCOPES, MAX_REQUESTER_LENGTH, CONTROL_OR_FORMAT, STATE_RE, MAX_RETURN_TO_LENGTH, RETURN_TO_ORIGINS, RETURN_TO_PREVIEW, PKH_SPACE_RE, SecretRequestInputError;
6984
+ var init_secret_request = __esm({
6985
+ "src/secret-request.ts"() {
6986
+ "use strict";
6987
+ SECRET_MANAGER_ORIGIN = "https://secrets.tinycloud.xyz";
6988
+ SECRET_REQUEST_MAX_NAMES = 10;
6989
+ SECRET_REQUEST_MAX_URL_LENGTH = 8192;
6990
+ OWNER_RE = /^did:pkh:eip155:([1-9][0-9]*):(0x[0-9a-fA-F]{40})$/;
6991
+ NAME_RE = /^[A-Z][A-Z0-9_]*$/;
6992
+ MAX_NAME_LENGTH = 64;
6993
+ SCOPE_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
6994
+ MAX_SCOPE_LENGTH = 64;
6995
+ RESERVED_SCOPES = ["default", "global", "scoped"];
6996
+ MAX_REQUESTER_LENGTH = 64;
6997
+ CONTROL_OR_FORMAT = /[\p{Cc}\p{Cf}]/u;
6998
+ STATE_RE = /^[A-Za-z0-9._~-]{1,128}$/;
6999
+ MAX_RETURN_TO_LENGTH = 2048;
7000
+ RETURN_TO_ORIGINS = ["https://feed.tinycloud.xyz"];
7001
+ RETURN_TO_PREVIEW = /^https:\/\/[a-z0-9-]+\.tinyfeed\.pages\.dev$/;
7002
+ PKH_SPACE_RE = /^tinycloud:pkh:eip155:([1-9][0-9]*):(0x[0-9a-fA-F]{40}):[^:]+$/;
7003
+ SecretRequestInputError = class extends Error {
7004
+ constructor(code, message) {
7005
+ super(message);
7006
+ this.name = "SecretRequestInputError";
7007
+ this.code = code;
7008
+ }
7009
+ };
7010
+ }
7011
+ });
7012
+
6594
7013
  // src/operations/secrets-get.ts
6595
7014
  import { z as z5 } from "zod";
6596
7015
  async function planSecretGet(context, input) {
@@ -6658,27 +7077,14 @@ async function executeSecretGet(context, input) {
6658
7077
  )
6659
7078
  };
6660
7079
  }
6661
- return classifySecretRead(reference, result);
7080
+ return classifySecretRead(reference, result, context.runtime?.localOwner === true);
6662
7081
  }
6663
- function classifySecretRead(reference, result) {
7082
+ function classifySecretRead(reference, result, localOwner) {
6664
7083
  switch (result.status) {
6665
7084
  case "ok":
6666
7085
  return { status: "ok", output: { value: result.value } };
6667
7086
  case "not_found":
6668
- return {
6669
- status: "setup_required",
6670
- setup: {
6671
- kind: "secret_manager",
6672
- secret: {
6673
- name: reference.name,
6674
- ...reference.scope === void 0 ? {} : { scope: reference.scope },
6675
- space: reference.space
6676
- },
6677
- url: buildSecretSetupUrl(reference),
6678
- message: "Enter this secret in Secret Manager, then retry the operation."
6679
- },
6680
- requiresCallerInput: true
6681
- };
7087
+ return missingSecretOutcome(reference, localOwner);
6682
7088
  case "permission_required":
6683
7089
  return {
6684
7090
  status: "authority_required",
@@ -6723,6 +7129,47 @@ function classifySecretRead(reference, result) {
6723
7129
  error: operationError("SECRET_READ_FAILED", "The secret read failed.")
6724
7130
  };
6725
7131
  }
7132
+ function missingSecretOutcome(reference, localOwner) {
7133
+ const owner = ownerOfSpace(reference.space);
7134
+ const recovery = missingSecretRecovery({
7135
+ owner,
7136
+ local: localOwner,
7137
+ names: [reference.name],
7138
+ ...reference.scope === void 0 ? {} : { scope: reference.scope }
7139
+ });
7140
+ if (recovery.kind === "add_secret") {
7141
+ const { setup } = recovery;
7142
+ return {
7143
+ status: "setup_required",
7144
+ setup: {
7145
+ kind: "secret_manager",
7146
+ action: setup.action,
7147
+ url: setup.url,
7148
+ names: setup.names,
7149
+ ...setup.scope === void 0 ? {} : { scope: setup.scope },
7150
+ owner: setup.owner,
7151
+ secret: {
7152
+ name: reference.name,
7153
+ ...reference.scope === void 0 ? {} : { scope: reference.scope },
7154
+ space: reference.space
7155
+ },
7156
+ message: "Ask the owner to add this secret in Secret Manager, then retry the operation."
7157
+ },
7158
+ requiresCallerInput: true
7159
+ };
7160
+ }
7161
+ return {
7162
+ status: "error",
7163
+ error: operationError("KV_NOT_FOUND", recovery.hint, {
7164
+ details: {
7165
+ names: [reference.name],
7166
+ ...reference.scope === void 0 ? {} : { scope: reference.scope },
7167
+ ...owner === void 0 ? {} : { owner },
7168
+ recoveryKind: recovery.kind
7169
+ }
7170
+ })
7171
+ };
7172
+ }
6726
7173
  function resolveEncryptionNetworkId(node, space) {
6727
7174
  const candidate = node;
6728
7175
  let scoped;
@@ -6749,6 +7196,7 @@ var init_secrets_get = __esm({
6749
7196
  "src/operations/secrets-get.ts"() {
6750
7197
  "use strict";
6751
7198
  init_errors2();
7199
+ init_secret_request();
6752
7200
  init_secrets();
6753
7201
  SecretsGetInputSchema = z5.object({
6754
7202
  name: z5.string(),
@@ -6800,9 +7248,197 @@ var init_secrets_get = __esm({
6800
7248
  }
6801
7249
  });
6802
7250
 
7251
+ // src/operations/secrets-status.ts
7252
+ import { z as z6 } from "zod";
7253
+ import * as nodeSdk4 from "@tinycloud/node-sdk";
7254
+ async function executeSecretsStatus(context, input) {
7255
+ const runtime = context.runtime;
7256
+ const node = runtime?.node;
7257
+ if (runtime === void 0 || typeof node?.kvForSpace !== "function") {
7258
+ return {
7259
+ status: "error",
7260
+ error: operationError("NODE_ERROR", "The TinyCloud node could not check the secrets.", { retryable: true })
7261
+ };
7262
+ }
7263
+ const local = runtime.localOwner === true;
7264
+ const resolution = resolveSecretsOwner({
7265
+ local,
7266
+ posture: context.summary.posture,
7267
+ ...context.summary.ownerDid === void 0 ? {} : { ownerDid: context.summary.ownerDid },
7268
+ ...context.summary.principalDid === void 0 ? {} : { principalDid: context.summary.principalDid },
7269
+ ...context.summary.space === void 0 ? {} : { space: context.summary.space }
7270
+ }, input.owner);
7271
+ if (!resolution.ok) {
7272
+ return {
7273
+ status: "error",
7274
+ error: operationError("INPUT_INVALID", resolution.message, { details: { reason: resolution.code } })
7275
+ };
7276
+ }
7277
+ const owner = resolution.owner;
7278
+ const skipped2 = runtime.skippedGrantReasons ?? [];
7279
+ const pkh = nodeSdk4.parsePkhDid(owner);
7280
+ const kv = node.kvForSpace(nodeSdk4.makePkhSpaceId(pkh.address, pkh.chainId, "secrets"));
7281
+ const outcomes = await Promise.all(input.names.map(async (name) => {
7282
+ const path = input.scope === void 0 ? `vault/secrets/${name}` : `vault/secrets/scoped/${input.scope}/${name}`;
7283
+ try {
7284
+ const result = await kv.get(path, { raw: true, prefix: "", timeout: PROBE_TIMEOUT_MS });
7285
+ if (result.ok) return "present";
7286
+ const error = result.error;
7287
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "KV_NOT_FOUND") {
7288
+ return "pending";
7289
+ }
7290
+ const refusal = kvRefusal(error);
7291
+ if (refusal === "auth_required") return "auth_required";
7292
+ if (refusal === "permission_denied") return "denied";
7293
+ return "unreachable";
7294
+ } catch {
7295
+ return "unreachable";
7296
+ }
7297
+ }));
7298
+ const namesWith = (outcome) => input.names.filter((_, index) => outcomes[index] === outcome);
7299
+ const present = namesWith("present");
7300
+ const pending = namesWith("pending");
7301
+ const denied = namesWith("denied");
7302
+ if (present.length + pending.length < input.names.length) {
7303
+ if (outcomes.includes("auth_required") || skipped2.includes("activation_unauthenticated")) {
7304
+ return {
7305
+ status: "error",
7306
+ error: operationError(
7307
+ "AUTH_REQUIRED",
7308
+ "The node did not accept this profile's stored session. Sign in again, then retry.",
7309
+ { retryable: false, details: { recovery: signInRecovery(context.summary.profile, context.summary.posture) } }
7310
+ )
7311
+ };
7312
+ }
7313
+ if (outcomes.includes("unreachable") || skipped2.includes("activation_unavailable")) {
7314
+ return {
7315
+ status: "error",
7316
+ error: operationError("NODE_UNREACHABLE", "The TinyCloud node could not be reached.", { retryable: true })
7317
+ };
7318
+ }
7319
+ }
7320
+ const recovery = pending.length === 0 ? void 0 : missingSecretRecovery({
7321
+ owner,
7322
+ local,
7323
+ names: pending,
7324
+ ...input.scope === void 0 ? {} : { scope: input.scope }
7325
+ });
7326
+ return {
7327
+ status: "ok",
7328
+ output: {
7329
+ status: denied.length > 0 ? "consent_required" : pending.length > 0 ? "pending" : "ready",
7330
+ owner,
7331
+ names: [...input.names],
7332
+ ...input.scope === void 0 ? {} : { scope: input.scope },
7333
+ present,
7334
+ pending,
7335
+ denied,
7336
+ ...recovery === void 0 ? {} : recovery.kind === "add_secret" ? { setup: recovery.setup } : { hint: recovery.hint }
7337
+ }
7338
+ };
7339
+ }
7340
+ var PROBE_TIMEOUT_MS, SECRETS_STATUS_DEADLINE_MS, SecretsStatusInputSchema, SecretSetupSchema, SecretsStatusOutputSchema, SECRETS_STATUS_POSTURES, SECRETS_STATUS_EXPOSURE, SECRETS_STATUS_SENSITIVITY, secretsStatusOperationDefinition, secretsStatusOperationDefinitions;
7341
+ var init_secrets_status = __esm({
7342
+ "src/operations/secrets-status.ts"() {
7343
+ "use strict";
7344
+ init_errors2();
7345
+ init_kv_failure();
7346
+ init_recovery();
7347
+ init_secret_request();
7348
+ init_secrets();
7349
+ PROBE_TIMEOUT_MS = 15e3;
7350
+ SECRETS_STATUS_DEADLINE_MS = 18e3;
7351
+ SecretsStatusInputSchema = z6.object({
7352
+ names: z6.array(z6.string()),
7353
+ scope: z6.string().optional(),
7354
+ owner: z6.string().optional()
7355
+ }).strict().superRefine((input, context) => {
7356
+ try {
7357
+ validSecretNames(input.names);
7358
+ } catch {
7359
+ context.addIssue({ code: z6.ZodIssueCode.custom, path: ["names"], message: "Invalid secret names." });
7360
+ }
7361
+ try {
7362
+ const scope = canonicalizeSecretScope(input.scope);
7363
+ if (scope !== void 0 && !isValidSecretRequestScope(scope)) throw new Error("invalid scope");
7364
+ } catch {
7365
+ context.addIssue({ code: z6.ZodIssueCode.custom, path: ["scope"], message: "Invalid secret scope." });
7366
+ }
7367
+ if (input.owner !== void 0) {
7368
+ try {
7369
+ canonicalSecretRequestOwner(input.owner);
7370
+ } catch {
7371
+ context.addIssue({ code: z6.ZodIssueCode.custom, path: ["owner"], message: "Invalid owner." });
7372
+ }
7373
+ }
7374
+ }).transform((input) => {
7375
+ const scope = canonicalizeSecretScope(input.scope);
7376
+ return {
7377
+ names: validSecretNames(input.names),
7378
+ ...scope === void 0 ? {} : { scope },
7379
+ ...input.owner === void 0 ? {} : { owner: canonicalSecretRequestOwner(input.owner) }
7380
+ };
7381
+ });
7382
+ SecretSetupSchema = z6.object({
7383
+ url: z6.string().url(),
7384
+ names: z6.array(z6.string()).min(1),
7385
+ scope: z6.string().optional(),
7386
+ owner: z6.string(),
7387
+ action: z6.literal("add_secret")
7388
+ }).strict();
7389
+ SecretsStatusOutputSchema = z6.object({
7390
+ status: z6.enum(["ready", "pending", "consent_required"]),
7391
+ owner: z6.string(),
7392
+ names: z6.array(z6.string()).min(1),
7393
+ scope: z6.string().optional(),
7394
+ present: z6.array(z6.string()),
7395
+ pending: z6.array(z6.string()),
7396
+ denied: z6.array(z6.string()),
7397
+ setup: SecretSetupSchema.optional(),
7398
+ hint: z6.string().optional()
7399
+ }).strict();
7400
+ SECRETS_STATUS_POSTURES = [
7401
+ "owner-openkey",
7402
+ "delegate-session",
7403
+ "local-owner-key"
7404
+ ];
7405
+ SECRETS_STATUS_EXPOSURE = {
7406
+ cli: { status: "required" },
7407
+ mcp: { status: "required" },
7408
+ skill: { status: "required" },
7409
+ docs: { status: "required" }
7410
+ };
7411
+ SECRETS_STATUS_SENSITIVITY = {
7412
+ input: [],
7413
+ output: []
7414
+ };
7415
+ secretsStatusOperationDefinition = {
7416
+ id: "tinycloud.secrets.status",
7417
+ version: 1,
7418
+ title: "Check whether TinyCloud secrets exist",
7419
+ description: "Report, without reading any value, which named secrets exist in the owner's secrets space: ready, pending (with a Secret Manager request link), or consent_required for names this identity may not read. Each name needs tinycloud.kv/get; this operation never requests it. It never signs in: it uses only the profile's stored session, and writes no profile state.",
7420
+ input: SecretsStatusInputSchema,
7421
+ output: SecretsStatusOutputSchema,
7422
+ effects: ["read"],
7423
+ runtime: "restored",
7424
+ postures: SECRETS_STATUS_POSTURES,
7425
+ exposure: SECRETS_STATUS_EXPOSURE,
7426
+ sensitivity: SECRETS_STATUS_SENSITIVITY,
7427
+ deadlineMs: SECRETS_STATUS_DEADLINE_MS,
7428
+ // Never evaluated against the session: a missing grant is reported as
7429
+ // `consent_required`, never saved as an authority request.
7430
+ authority: async () => [],
7431
+ execute: executeSecretsStatus
7432
+ };
7433
+ secretsStatusOperationDefinitions = [
7434
+ secretsStatusOperationDefinition
7435
+ ];
7436
+ }
7437
+ });
7438
+
6803
7439
  // src/operations/sql.ts
6804
7440
  import nodeSqlParser from "node-sql-parser";
6805
- import { z as z6 } from "zod";
7441
+ import { z as z7 } from "zod";
6806
7442
  async function planSqlRead(context, input) {
6807
7443
  const space = resolveSqlSpace(context, input.space);
6808
7444
  return [{
@@ -7120,7 +7756,7 @@ var init_sql = __esm({
7120
7756
  "use strict";
7121
7757
  init_errors2();
7122
7758
  init_secrets();
7123
- init_exploration();
7759
+ init_kv_failure();
7124
7760
  DEFAULT_MAX_ROWS = 100;
7125
7761
  MAX_MAX_ROWS = 1e3;
7126
7762
  DEFAULT_MAX_BYTES = 1024 * 1024;
@@ -7136,29 +7772,29 @@ WHERE type IN ('table', 'view', 'index', 'trigger')
7136
7772
  AND name NOT LIKE 'sqlite_%'
7137
7773
  ORDER BY type, name`;
7138
7774
  sqlParser = new nodeSqlParser.Parser();
7139
- SpaceSchema2 = z6.string().min(1).refine(
7775
+ SpaceSchema2 = z7.string().min(1).refine(
7140
7776
  (space) => (/^[A-Za-z0-9_-]+$/.test(space) || space.startsWith("tinycloud:")) && !space.includes("*"),
7141
7777
  "Invalid TinyCloud space."
7142
7778
  );
7143
- DatabaseSchema = z6.string().min(1).max(256).refine(
7779
+ DatabaseSchema = z7.string().min(1).max(256).refine(
7144
7780
  (database) => database !== "/" && !database.endsWith("/") && !database.includes("*") && !/[\u0000-\u001f\u007f]/.test(database),
7145
7781
  "Invalid SQLite database name."
7146
7782
  );
7147
- SafeNumberSchema = z6.number().finite().refine(
7783
+ SafeNumberSchema = z7.number().finite().refine(
7148
7784
  (value) => !Number.isInteger(value) || Number.isSafeInteger(value),
7149
7785
  "SQLite integer parameters must be JavaScript safe integers."
7150
7786
  );
7151
- Base64Schema = z6.string().max(Math.ceil(MAX_MAX_BYTES / 3) * 4).refine(
7787
+ Base64Schema = z7.string().max(Math.ceil(MAX_MAX_BYTES / 3) * 4).refine(
7152
7788
  isCanonicalBase642,
7153
7789
  "Invalid base64 BLOB value."
7154
7790
  );
7155
- SqlInputValueSchema = z6.union([
7156
- z6.null(),
7791
+ SqlInputValueSchema = z7.union([
7792
+ z7.null(),
7157
7793
  SafeNumberSchema,
7158
- z6.string().max(MAX_MAX_BYTES),
7159
- z6.object({ type: z6.literal("blob"), base64: Base64Schema }).strict()
7794
+ z7.string().max(MAX_MAX_BYTES),
7795
+ z7.object({ type: z7.literal("blob"), base64: Base64Schema }).strict()
7160
7796
  ]);
7161
- SqlParamsSchema = z6.array(SqlInputValueSchema).max(MAX_PARAMETERS).superRefine(
7797
+ SqlParamsSchema = z7.array(SqlInputValueSchema).max(MAX_PARAMETERS).superRefine(
7162
7798
  (params, context) => {
7163
7799
  const totalBytes = params.reduce(
7164
7800
  (total, value) => total + sqlInputValueBytes(value),
@@ -7166,13 +7802,13 @@ ORDER BY type, name`;
7166
7802
  );
7167
7803
  if (totalBytes > MAX_MAX_BYTES) {
7168
7804
  context.addIssue({
7169
- code: z6.ZodIssueCode.custom,
7805
+ code: z7.ZodIssueCode.custom,
7170
7806
  message: "SQLite query parameters exceed the 4 MiB input limit."
7171
7807
  });
7172
7808
  }
7173
7809
  }
7174
7810
  );
7175
- RequiredSqlParamsSchema = z6.array(SqlInputValueSchema).min(1).max(MAX_PARAMETERS).superRefine(
7811
+ RequiredSqlParamsSchema = z7.array(SqlInputValueSchema).min(1).max(MAX_PARAMETERS).superRefine(
7176
7812
  (params, context) => {
7177
7813
  const totalBytes = params.reduce(
7178
7814
  (total, value) => total + sqlInputValueBytes(value),
@@ -7180,88 +7816,88 @@ ORDER BY type, name`;
7180
7816
  );
7181
7817
  if (totalBytes > MAX_MAX_BYTES) {
7182
7818
  context.addIssue({
7183
- code: z6.ZodIssueCode.custom,
7819
+ code: z7.ZodIssueCode.custom,
7184
7820
  message: "SQLite query parameters exceed the 4 MiB input limit."
7185
7821
  });
7186
7822
  }
7187
7823
  }
7188
7824
  );
7189
- SqlOutputValueSchema = z6.union([
7190
- z6.null(),
7825
+ SqlOutputValueSchema = z7.union([
7826
+ z7.null(),
7191
7827
  SafeNumberSchema,
7192
- z6.string(),
7193
- z6.object({
7194
- type: z6.literal("blob"),
7195
- base64: z6.string(),
7196
- byteLength: z6.number().int().nonnegative()
7828
+ z7.string(),
7829
+ z7.object({
7830
+ type: z7.literal("blob"),
7831
+ base64: z7.string(),
7832
+ byteLength: z7.number().int().nonnegative()
7197
7833
  }).strict()
7198
7834
  ]);
7199
- SqlTargetInputSchema = z6.object({
7835
+ SqlTargetInputSchema = z7.object({
7200
7836
  space: SpaceSchema2,
7201
7837
  database: DatabaseSchema
7202
7838
  }).strict();
7203
- SqlQueryInputSchema = z6.object({
7839
+ SqlQueryInputSchema = z7.object({
7204
7840
  space: SpaceSchema2,
7205
7841
  database: DatabaseSchema,
7206
- sql: z6.string().min(1).max(MAX_SQL_LENGTH).superRefine((sql, context) => {
7842
+ sql: z7.string().min(1).max(MAX_SQL_LENGTH).superRefine((sql, context) => {
7207
7843
  if (!isSingleReadQuery(sql)) {
7208
7844
  context.addIssue({
7209
- code: z6.ZodIssueCode.custom,
7845
+ code: z7.ZodIssueCode.custom,
7210
7846
  message: "SQL must parse as exactly one SQLite SELECT statement."
7211
7847
  });
7212
7848
  }
7213
7849
  }),
7214
7850
  params: SqlParamsSchema.optional(),
7215
- maxRows: z6.number().int().positive().max(MAX_MAX_ROWS).optional(),
7216
- maxBytes: z6.number().int().positive().max(MAX_MAX_BYTES).optional()
7851
+ maxRows: z7.number().int().positive().max(MAX_MAX_ROWS).optional(),
7852
+ maxBytes: z7.number().int().positive().max(MAX_MAX_BYTES).optional()
7217
7853
  }).strict();
7218
- SqlExecuteInputSchema = z6.object({
7854
+ SqlExecuteInputSchema = z7.object({
7219
7855
  space: SpaceSchema2,
7220
7856
  database: DatabaseSchema,
7221
- sql: z6.string().min(1).max(MAX_SQL_LENGTH),
7857
+ sql: z7.string().min(1).max(MAX_SQL_LENGTH),
7222
7858
  params: RequiredSqlParamsSchema,
7223
- acknowledgeDatabaseWideAuthority: z6.literal(true).describe(SQL_WRITE_AUTHORITY_NOTICE)
7859
+ acknowledgeDatabaseWideAuthority: z7.literal(true).describe(SQL_WRITE_AUTHORITY_NOTICE)
7224
7860
  }).strict().superRefine((input, context) => {
7225
7861
  if (parseSingleDmlStatement(input.sql, input.params.length) === void 0) {
7226
7862
  context.addIssue({
7227
- code: z6.ZodIssueCode.custom,
7863
+ code: z7.ZodIssueCode.custom,
7228
7864
  path: ["sql"],
7229
7865
  message: "SQL must parse as exactly one parameterized INSERT, UPDATE, or DELETE with one value per positional placeholder."
7230
7866
  });
7231
7867
  }
7232
7868
  });
7233
- SqlLimitsOutputSchema = z6.object({
7234
- maxRows: z6.number().int().positive(),
7235
- maxBytes: z6.number().int().positive(),
7236
- enforcement: z6.literal("node-requested-client-verified")
7869
+ SqlLimitsOutputSchema = z7.object({
7870
+ maxRows: z7.number().int().positive(),
7871
+ maxBytes: z7.number().int().positive(),
7872
+ enforcement: z7.literal("node-requested-client-verified")
7237
7873
  }).strict();
7238
- SqlSchemaInspectOutputSchema = z6.object({
7239
- space: z6.string().min(1),
7240
- database: z6.string().min(1),
7241
- objects: z6.array(z6.object({
7242
- type: z6.enum(["table", "view", "index", "trigger"]),
7243
- name: z6.string().min(1),
7244
- tableName: z6.string().min(1),
7245
- sql: z6.string().optional()
7874
+ SqlSchemaInspectOutputSchema = z7.object({
7875
+ space: z7.string().min(1),
7876
+ database: z7.string().min(1),
7877
+ objects: z7.array(z7.object({
7878
+ type: z7.enum(["table", "view", "index", "trigger"]),
7879
+ name: z7.string().min(1),
7880
+ tableName: z7.string().min(1),
7881
+ sql: z7.string().optional()
7246
7882
  }).strict()),
7247
- count: z6.number().int().nonnegative(),
7883
+ count: z7.number().int().nonnegative(),
7248
7884
  limits: SqlLimitsOutputSchema
7249
7885
  }).strict();
7250
- SqlQueryOutputSchema = z6.object({
7251
- space: z6.string().min(1),
7252
- database: z6.string().min(1),
7253
- columns: z6.array(z6.string()),
7254
- rows: z6.array(z6.array(SqlOutputValueSchema)),
7255
- rowCount: z6.number().int().nonnegative(),
7886
+ SqlQueryOutputSchema = z7.object({
7887
+ space: z7.string().min(1),
7888
+ database: z7.string().min(1),
7889
+ columns: z7.array(z7.string()),
7890
+ rows: z7.array(z7.array(SqlOutputValueSchema)),
7891
+ rowCount: z7.number().int().nonnegative(),
7256
7892
  limits: SqlLimitsOutputSchema
7257
7893
  }).strict();
7258
- SqlExecuteOutputSchema = z6.object({
7259
- space: z6.string().min(1),
7260
- database: z6.string().min(1),
7261
- statementType: z6.enum(["insert", "update", "delete"]),
7262
- changes: z6.number().int().nonnegative(),
7263
- lastInsertRowId: z6.number().int().nullable(),
7264
- authorityNotice: z6.literal(SQL_WRITE_AUTHORITY_NOTICE)
7894
+ SqlExecuteOutputSchema = z7.object({
7895
+ space: z7.string().min(1),
7896
+ database: z7.string().min(1),
7897
+ statementType: z7.enum(["insert", "update", "delete"]),
7898
+ changes: z7.number().int().nonnegative(),
7899
+ lastInsertRowId: z7.number().int().nullable(),
7900
+ authorityNotice: z7.literal(SQL_WRITE_AUTHORITY_NOTICE)
7265
7901
  }).strict();
7266
7902
  AUTHENTICATED_POSTURES2 = [
7267
7903
  "owner-openkey",
@@ -7361,6 +7997,8 @@ var init_contract = __esm({
7361
7997
  "resources_mismatch",
7362
7998
  "unsupported",
7363
7999
  "activation_rejected",
8000
+ "activation_unavailable",
8001
+ "activation_unauthenticated",
7364
8002
  "invalid"
7365
8003
  ];
7366
8004
  DELEGATION_CID_PATTERN = "^bafkr4i[a-z2-7]{52}$";
@@ -7369,7 +8007,7 @@ var init_contract = __esm({
7369
8007
 
7370
8008
  // src/delegation-binding.ts
7371
8009
  import { join as join2 } from "path";
7372
- import * as nodeSdk3 from "@tinycloud/node-sdk";
8010
+ import * as nodeSdk5 from "@tinycloud/node-sdk";
7373
8011
  function bindingMigrationPath(profile) {
7374
8012
  return join2(profilePath(profile), "delegation-binding-migration.json");
7375
8013
  }
@@ -7465,6 +8103,9 @@ function skipped(reason) {
7465
8103
  }
7466
8104
  async function replayProfileDelegations(profile, node, options) {
7467
8105
  await prepareStoredDelegationReplay(profile, node, { host: options.host, migrate: options.migrate });
8106
+ return replayStoredDelegationRecords(profile, node, options);
8107
+ }
8108
+ async function replayStoredDelegationRecords(profile, node, options) {
7468
8109
  const installed = [];
7469
8110
  const warnings = [];
7470
8111
  const seen = /* @__PURE__ */ new Set();
@@ -7505,7 +8146,7 @@ function storedDelegationForAnotherKey(entry, node) {
7505
8146
  const audience = verifiedCompactAudience(node, delegation);
7506
8147
  if (audience === void 0) return false;
7507
8148
  try {
7508
- return nodeSdk3.principalDidEquals(delegation.delegateDID, audience) && !nodeSdk3.principalDidEquals(audience, node.sessionDid);
8149
+ return nodeSdk5.principalDidEquals(delegation.delegateDID, audience) && !nodeSdk5.principalDidEquals(audience, node.sessionDid);
7509
8150
  } catch {
7510
8151
  return false;
7511
8152
  }
@@ -7539,7 +8180,7 @@ async function assess(node, entry, options, mode) {
7539
8180
  let outsideRequest = false;
7540
8181
  let effectivePermissions;
7541
8182
  try {
7542
- const installed = await nodeSdk3.activateValidatedRuntimeDelegation(node, toActivate, {
8183
+ const installed = await nodeSdk5.activateValidatedRuntimeDelegation(node, toActivate, {
7543
8184
  host: options.host,
7544
8185
  authorize: (effective) => {
7545
8186
  verifyCompactProof(toActivate);
@@ -7571,7 +8212,7 @@ function verifyCompactProof(delegation) {
7571
8212
  try {
7572
8213
  parseSignedCompactUcanAttenuation(withoutBearer(authorization), delegation.cid);
7573
8214
  } catch {
7574
- throw new nodeSdk3.RuntimeDelegationRejectedError("proof_invalid", "The compact delegation proof is invalid.");
8215
+ throw new nodeSdk5.RuntimeDelegationRejectedError("proof_invalid", "The compact delegation proof is invalid.");
7575
8216
  }
7576
8217
  }
7577
8218
  function rejectionReason(error) {
@@ -7588,7 +8229,7 @@ async function bindingMigrationRecorded(profile) {
7588
8229
  async function signedCapabilities(node, delegation, host) {
7589
8230
  let signed;
7590
8231
  try {
7591
- await nodeSdk3.activateValidatedRuntimeDelegation(node, delegation, {
8232
+ await nodeSdk5.activateValidatedRuntimeDelegation(node, delegation, {
7592
8233
  // The transport host is not signed authority; check it at replay instead.
7593
8234
  host: delegation.host ?? host,
7594
8235
  authorize: (effective) => {
@@ -7651,7 +8292,7 @@ var init_delegation_binding = __esm({
7651
8292
  });
7652
8293
 
7653
8294
  // src/operations/status.ts
7654
- import { z as z7 } from "zod";
8295
+ import { z as z8 } from "zod";
7655
8296
  function createStatusDefinition(params) {
7656
8297
  return {
7657
8298
  id: params.id,
@@ -7776,27 +8417,27 @@ var init_status = __esm({
7776
8417
  init_delegation_binding();
7777
8418
  init_errors2();
7778
8419
  init_state();
7779
- StatusInputSchema = z7.object({}).strict();
7780
- StatusOutputSchema = z7.object({
7781
- profile: z7.string().min(1),
7782
- host: z7.string().min(1),
7783
- posture: z7.enum([
8420
+ StatusInputSchema = z8.object({}).strict();
8421
+ StatusOutputSchema = z8.object({
8422
+ profile: z8.string().min(1),
8423
+ host: z8.string().min(1),
8424
+ posture: z8.enum([
7784
8425
  "owner-openkey",
7785
8426
  "delegate-session",
7786
8427
  "local-owner-key",
7787
8428
  "unauthenticated"
7788
8429
  ]),
7789
- operatorType: z7.enum(["human", "agent"]).optional(),
7790
- principalDid: z7.string().min(1).optional(),
7791
- sessionDid: z7.string().min(1).optional(),
7792
- ownerDid: z7.string().min(1).optional(),
7793
- space: z7.string().min(1).optional(),
7794
- session: z7.object({
7795
- present: z7.boolean(),
7796
- expired: z7.boolean().nullable(),
7797
- expiresAt: z7.string().datetime({ offset: true }).nullable()
8430
+ operatorType: z8.enum(["human", "agent"]).optional(),
8431
+ principalDid: z8.string().min(1).optional(),
8432
+ sessionDid: z8.string().min(1).optional(),
8433
+ ownerDid: z8.string().min(1).optional(),
8434
+ space: z8.string().min(1).optional(),
8435
+ session: z8.object({
8436
+ present: z8.boolean(),
8437
+ expired: z8.boolean().nullable(),
8438
+ expiresAt: z8.string().datetime({ offset: true }).nullable()
7798
8439
  }).strict(),
7799
- liveAdditionalDelegationCount: z7.number().int().nonnegative().describe(
8440
+ liveAdditionalDelegationCount: z8.number().int().nonnegative().describe(
7800
8441
  "Stored, unexpired compact-UCAN or signed-login delegations with a valid request binding. Each still requires local proof validation and node activation before it grants authority."
7801
8442
  )
7802
8443
  }).strict();
@@ -7840,7 +8481,8 @@ function registeredOperationDefinitions() {
7840
8481
  ...authOperationDefinitions,
7841
8482
  ...explorationOperationDefinitions,
7842
8483
  ...sqlOperationDefinitions,
7843
- ...secretsGetOperationDefinitions
8484
+ ...secretsGetOperationDefinitions,
8485
+ ...secretsStatusOperationDefinitions
7844
8486
  ]);
7845
8487
  return operationDefinitions;
7846
8488
  }
@@ -7872,6 +8514,7 @@ var init_registry = __esm({
7872
8514
  init_auth();
7873
8515
  init_exploration();
7874
8516
  init_secrets_get();
8517
+ init_secrets_status();
7875
8518
  init_sql();
7876
8519
  init_status();
7877
8520
  }
@@ -7924,6 +8567,9 @@ function resolvePosture(profile) {
7924
8567
  if (profile.authMethod === "local") return "local-owner-key";
7925
8568
  return "owner-openkey";
7926
8569
  }
8570
+ function isLocalOwnerProfile(profile) {
8571
+ return resolvePosture(profile) === "local-owner-key" || profile.authMethod === "local" || typeof profile.privateKey === "string";
8572
+ }
7927
8573
  function profileNotFound2(profile) {
7928
8574
  return {
7929
8575
  ok: false,
@@ -7995,7 +8641,7 @@ var runtime_exports = {};
7995
8641
  __export(runtime_exports, {
7996
8642
  createInvocationRuntime: () => createInvocationRuntime
7997
8643
  });
7998
- import * as nodeSdk4 from "@tinycloud/node-sdk";
8644
+ import * as nodeSdk6 from "@tinycloud/node-sdk";
7999
8645
  async function createInvocationRuntime(target, requirement = "authenticated") {
8000
8646
  const resolved = await resolveInvocationProfile(target);
8001
8647
  if (!resolved.ok) {
@@ -8009,11 +8655,14 @@ async function createInvocationRuntime(target, requirement = "authenticated") {
8009
8655
  if (requirement === "inspection") {
8010
8656
  return { ok: true, context: { summary } };
8011
8657
  }
8658
+ if (requirement === "restored") {
8659
+ return createRestoredRuntime(target, resolved);
8660
+ }
8012
8661
  const profileName = summary.profile;
8013
8662
  try {
8014
8663
  const {
8015
8664
  TinyCloudNode: TinyCloudNodeConstructor
8016
- } = nodeSdk4;
8665
+ } = nodeSdk6;
8017
8666
  const explicitPrivateKeyOverride = typeof target.privateKey === "string";
8018
8667
  const [session, key] = explicitPrivateKeyOverride ? [null, null] : await Promise.all([
8019
8668
  readSession(profileName),
@@ -8091,7 +8740,9 @@ async function createInvocationRuntime(target, requirement = "authenticated") {
8091
8740
  const runtime = {
8092
8741
  node,
8093
8742
  granted: canonicalizeCapabilities(livePermissions),
8094
- ...warnings.length === 0 ? {} : { warnings }
8743
+ ...warnings.length === 0 ? {} : { warnings },
8744
+ localOwner: explicitPrivateKeyOverride || isLocalOwnerProfile(profile),
8745
+ skippedGrantReasons: warnings.map((warning) => warning.reason)
8095
8746
  };
8096
8747
  return {
8097
8748
  ok: true,
@@ -8115,11 +8766,112 @@ async function createInvocationRuntime(target, requirement = "authenticated") {
8115
8766
  ));
8116
8767
  }
8117
8768
  }
8769
+ async function createRestoredRuntime(target, resolved) {
8770
+ const summary = resolved.context;
8771
+ const profileName = summary.profile;
8772
+ const profile = resolved.profile;
8773
+ if (typeof target.privateKey === "string") {
8774
+ return failed(summary, operationError(
8775
+ "AUTH_REQUIRED",
8776
+ "This operation never signs in; an explicit private key has no stored session.",
8777
+ { details: { reason: "explicit_key" } }
8778
+ ));
8779
+ }
8780
+ try {
8781
+ const actualPosture = resolvePosture(profile);
8782
+ const selectedPosture = profile.authMethod === "local" ? "local-owner-key" : summary.posture;
8783
+ if (actualPosture !== summary.posture || selectedPosture !== summary.posture) {
8784
+ return failed(
8785
+ { ...summary, posture: actualPosture !== summary.posture ? actualPosture : selectedPosture },
8786
+ operationError(
8787
+ "PROFILE_POSTURE_NOT_ALLOWED",
8788
+ "The selected profile changed posture during runtime resolution."
8789
+ )
8790
+ );
8791
+ }
8792
+ const [session, key] = await Promise.all([
8793
+ readSession(profileName),
8794
+ readJson(`${profilePath(profileName)}/key.json`)
8795
+ ]);
8796
+ const activeSession = session === null ? void 0 : normalizeSession(session, key);
8797
+ if (activeSession === void 0) {
8798
+ return failed(summary, operationError(
8799
+ "SESSION_NOT_FOUND",
8800
+ "The selected profile does not have an active session. This operation never signs in.",
8801
+ { details: { recovery: signInRecovery(profileName, summary.posture) } }
8802
+ ));
8803
+ }
8804
+ if (!await bindingMigrationRecorded(profileName)) {
8805
+ const records = await readAdditionalDelegations(profileName);
8806
+ if (records.some(
8807
+ (entry) => storedDelegationKind(entry) === "compact" && !("authorityRequest" in entry)
8808
+ )) {
8809
+ return failed(summary, operationError(
8810
+ "PROFILE_MIGRATION_REQUIRED",
8811
+ `Profile "${profileName}" has a one-time stored-grant upgrade to finish. Run one authenticated command on it (tc --profile <profile> auth caps), then retry.`,
8812
+ { retryable: false, details: { recovery: migrationRecovery(profileName) } }
8813
+ ));
8814
+ }
8815
+ }
8816
+ const { TinyCloudNode: TinyCloudNodeConstructor } = nodeSdk6;
8817
+ const node = new TinyCloudNodeConstructor({
8818
+ host: summary.host,
8819
+ ...typeof profile.privateKey === "string" ? { privateKey: profile.privateKey } : {}
8820
+ });
8821
+ try {
8822
+ await node.restoreSession(activeSession);
8823
+ } catch (error) {
8824
+ if (isRecord7(error) && error.code === "AUTH_EXPIRED") {
8825
+ return failed(summary, operationError(
8826
+ "AUTH_REQUIRED",
8827
+ "The stored session has expired or is no longer valid. Sign in again.",
8828
+ { retryable: false, details: { recovery: signInRecovery(profileName, summary.posture) } }
8829
+ ));
8830
+ }
8831
+ throw error;
8832
+ }
8833
+ const activeSessionDid = normalizeDid(node.sessionDid);
8834
+ const livePrincipalDid = normalizeDid(node.did);
8835
+ const livePermissions = [...node.getVerifiedSessionCapabilities()];
8836
+ const replay = await replayStoredDelegationRecords(
8837
+ profileName,
8838
+ node,
8839
+ { host: summary.host, resolveSpace: operationSpaceResolver(node, summary.space) }
8840
+ );
8841
+ for (const installed of replay.installed) {
8842
+ livePermissions.push(...installed.effectivePermissions);
8843
+ }
8844
+ const runtime = {
8845
+ node,
8846
+ granted: canonicalizeCapabilities(livePermissions),
8847
+ ...replay.warnings.length === 0 ? {} : { warnings: replay.warnings },
8848
+ localOwner: isLocalOwnerProfile(profile),
8849
+ skippedGrantReasons: replay.warnings.map((warning) => warning.reason)
8850
+ };
8851
+ return {
8852
+ ok: true,
8853
+ context: {
8854
+ summary: {
8855
+ ...summary,
8856
+ ...livePrincipalDid === void 0 ? {} : { principalDid: livePrincipalDid },
8857
+ ...activeSessionDid === void 0 ? {} : { sessionDid: activeSessionDid }
8858
+ },
8859
+ runtime
8860
+ }
8861
+ };
8862
+ } catch {
8863
+ return failed(summary, operationError(
8864
+ "NODE_ERROR",
8865
+ "The selected profile runtime could not be initialized.",
8866
+ { retryable: true }
8867
+ ));
8868
+ }
8869
+ }
8118
8870
  function spaceForAuthenticatedPrincipal(principal2) {
8119
8871
  if (principal2 === void 0) return void 0;
8120
8872
  try {
8121
- const pkh = nodeSdk4.parsePkhDid(principal2);
8122
- return pkh === null ? void 0 : nodeSdk4.makePkhSpaceId(pkh.address, pkh.chainId, "secrets");
8873
+ const pkh = nodeSdk6.parsePkhDid(principal2);
8874
+ return pkh === null ? void 0 : nodeSdk6.makePkhSpaceId(pkh.address, pkh.chainId, "secrets");
8123
8875
  } catch {
8124
8876
  return void 0;
8125
8877
  }
@@ -8169,6 +8921,7 @@ var init_runtime = __esm({
8169
8921
  init_authority();
8170
8922
  init_errors2();
8171
8923
  init_profile();
8924
+ init_recovery();
8172
8925
  init_secrets();
8173
8926
  init_state();
8174
8927
  init_delegation_binding();
@@ -8183,11 +8936,14 @@ init_artifacts();
8183
8936
  import { createHash } from "crypto";
8184
8937
  import { isAbsolute as isAbsolute2 } from "path";
8185
8938
  import {
8186
- makePkhSpaceId as makePkhSpaceId3,
8187
- parsePkhDid as parsePkhDid3,
8939
+ makePkhSpaceId as makePkhSpaceId4,
8940
+ parsePkhDid as parsePkhDid4,
8188
8941
  parseSpaceUri as parseSpaceUri2
8189
8942
  } from "@tinycloud/node-sdk";
8190
8943
 
8944
+ // src/redaction.ts
8945
+ init_recovery();
8946
+
8191
8947
  // src/safe-values.ts
8192
8948
  function safeOriginHost(value) {
8193
8949
  if (typeof value !== "string" || value.length === 0) return void 0;
@@ -8264,7 +9020,25 @@ function redactOperationError(_definition, error) {
8264
9020
  }
8265
9021
  var SAFE_ERROR_DETAIL_POLICIES = {
8266
9022
  INPUT_INVALID: {
8267
- issues: (value) => Array.isArray(value) ? safeIssueCodes(value) : void 0
9023
+ issues: (value) => Array.isArray(value) ? safeIssueCodes(value) : void 0,
9024
+ reason: (value) => value === "OWNER_MISMATCH" || value === "OWNER_DID_UNKNOWN" ? value : void 0
9025
+ },
9026
+ AUTH_REQUIRED: {
9027
+ recovery: (value) => isSafeRecovery(value, "sign_in") ? { ...value } : void 0,
9028
+ reason: (value) => value === "explicit_key" ? value : void 0
9029
+ },
9030
+ SESSION_NOT_FOUND: {
9031
+ recovery: (value) => isSafeRecovery(value, "sign_in") ? { ...value } : void 0
9032
+ },
9033
+ PROFILE_MIGRATION_REQUIRED: {
9034
+ recovery: (value) => isSafeRecovery(value, "complete_migration") ? { ...value } : void 0
9035
+ },
9036
+ // `secrets.get` not found for an identity no request link serves.
9037
+ KV_NOT_FOUND: {
9038
+ recoveryKind: (value) => value === "put_locally" || value === "not_requestable" ? value : void 0,
9039
+ names: (value) => Array.isArray(value) && value.length >= 1 && value.length <= 10 && value.every((name) => typeof name === "string" && name.length <= 256 && /^[A-Z][A-Z0-9_]*$/.test(name)) ? [...value] : void 0,
9040
+ scope: (value) => typeof value === "string" && value.length <= 256 && /^[a-z0-9]+(-[a-z0-9]+)*$/.test(value) ? value : void 0,
9041
+ owner: (value) => typeof value === "string" && /^did:pkh:eip155:[1-9][0-9]*:0x[0-9a-fA-F]{40}$/.test(value) ? value : void 0
8268
9042
  },
8269
9043
  OPERATION_VERSION_UNSUPPORTED: {
8270
9044
  supportedVersions: (value) => Array.isArray(value) && value.every((item) => Number.isInteger(item)) ? value : void 0
@@ -8346,7 +9120,7 @@ async function invokeOperation(operationId, operationVersion, invocationTarget,
8346
9120
  operationError("INPUT_INVALID", "The invocation target is invalid.")
8347
9121
  );
8348
9122
  }
8349
- return withTinyCloudStateRoot(target.stateRoot, async () => {
9123
+ const run = withTinyCloudStateRoot(target.stateRoot, async () => {
8350
9124
  const prepared = await prepareInvocation(
8351
9125
  operationId,
8352
9126
  operationVersion,
@@ -8356,6 +9130,33 @@ async function invokeOperation(operationId, operationVersion, invocationTarget,
8356
9130
  if (isPreparedError(prepared)) return prepared.result;
8357
9131
  return withRuntimeWarnings(await executePreparedInvocation(prepared), prepared);
8358
9132
  });
9133
+ const { lookupOperation: lookupOperation2 } = await Promise.resolve().then(() => (init_registry(), registry_exports));
9134
+ const lookup = lookupOperation2(operationId, operationVersion);
9135
+ const deadlineMs = lookup.status === "found" ? lookup.definition.deadlineMs : void 0;
9136
+ if (deadlineMs === void 0) return run;
9137
+ return withResponseDeadline(run, deadlineMs, () => errorResult(
9138
+ { operationId, operationVersion },
9139
+ unresolvedContextSummary(target),
9140
+ operationError(
9141
+ "NODE_UNREACHABLE",
9142
+ `The operation did not finish within ${deadlineMs / 1e3} s.`,
9143
+ { retryable: true }
9144
+ )
9145
+ ));
9146
+ }
9147
+ async function withResponseDeadline(run, deadlineMs, expired) {
9148
+ run.catch(() => {
9149
+ });
9150
+ let timer;
9151
+ const deadline = new Promise((resolve2) => {
9152
+ timer = setTimeout(() => resolve2(expired()), deadlineMs);
9153
+ timer.unref();
9154
+ });
9155
+ try {
9156
+ return await Promise.race([run, deadline]);
9157
+ } finally {
9158
+ clearTimeout(timer);
9159
+ }
8359
9160
  }
8360
9161
  function withRuntimeWarnings(result, prepared) {
8361
9162
  const warnings = prepared.runtimeResolution.context.runtime?.warnings;
@@ -8430,7 +9231,7 @@ async function prepareInvocation(operationId, operationVersion, invocationTarget
8430
9231
  )
8431
9232
  ) };
8432
9233
  }
8433
- if (target.privateKey === void 0 && definition.runtime === "authenticated" && isOwnerPosture(inspectionResolution.context.summary.posture) && target.allowOwnerProfile !== true) {
9234
+ if (target.privateKey === void 0 && requiresRuntime(definition.runtime) && isOwnerPosture(inspectionResolution.context.summary.posture) && target.allowOwnerProfile !== true) {
8434
9235
  return { status: "error", result: errorResult(
8435
9236
  operation,
8436
9237
  inspectionResolution.context.summary,
@@ -8440,15 +9241,15 @@ async function prepareInvocation(operationId, operationVersion, invocationTarget
8440
9241
  )
8441
9242
  ) };
8442
9243
  }
8443
- const runtimeResolution = definition.runtime === "inspection" ? inspectionResolution : await resolveRuntime(target, "authenticated");
9244
+ const runtimeResolution = definition.runtime === "inspection" ? inspectionResolution : await resolveRuntime(target, definition.runtime);
8444
9245
  if (!runtimeResolution.ok) {
8445
9246
  return { status: "error", result: errorResult(
8446
9247
  operation,
8447
9248
  runtimeResolution.context,
8448
- runtimeResolution.error
9249
+ redactOperationError(definition, runtimeResolution.error)
8449
9250
  ) };
8450
9251
  }
8451
- if (definition.runtime === "authenticated") {
9252
+ if (requiresRuntime(definition.runtime)) {
8452
9253
  const actualPosture = runtimeResolution.context.summary.posture;
8453
9254
  if (!definition.postures.includes(actualPosture)) {
8454
9255
  return { status: "error", result: errorResult(
@@ -8835,6 +9636,15 @@ function exactCapabilities2(value) {
8835
9636
  function isOwnerPosture(posture) {
8836
9637
  return posture === "owner-openkey" || posture === "local-owner-key";
8837
9638
  }
9639
+ function requiresRuntime(requirement) {
9640
+ switch (requirement) {
9641
+ case "authenticated":
9642
+ case "restored":
9643
+ return true;
9644
+ case "inspection":
9645
+ return false;
9646
+ }
9647
+ }
8838
9648
  function zodIssueDetails(error) {
8839
9649
  return {
8840
9650
  // The canonical error channel carries only schema-owned issue codes.