@tinycloud/operations 0.5.0-beta.0 → 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.
- package/coverage.json +11 -1
- package/dist/artifacts.cjs +235 -43
- package/dist/artifacts.cjs.map +1 -1
- package/dist/artifacts.d.cts +1 -1
- package/dist/artifacts.d.ts +1 -1
- package/dist/artifacts.js +236 -43
- package/dist/artifacts.js.map +1 -1
- package/dist/cli-runtime.cjs +1021 -223
- package/dist/cli-runtime.cjs.map +1 -1
- package/dist/cli-runtime.d.cts +1 -1
- package/dist/cli-runtime.d.ts +1 -1
- package/dist/cli-runtime.js +1020 -222
- package/dist/cli-runtime.js.map +1 -1
- package/dist/{contract-DfyrluLB.d.cts → contract-zRklonbR.d.cts} +6 -3
- package/dist/{contract-DfyrluLB.d.ts → contract-zRklonbR.d.ts} +6 -3
- package/dist/delegation-binding.cjs +242 -43
- package/dist/delegation-binding.cjs.map +1 -1
- package/dist/delegation-binding.d.cts +15 -3
- package/dist/delegation-binding.d.ts +15 -3
- package/dist/delegation-binding.js +242 -43
- package/dist/delegation-binding.js.map +1 -1
- package/dist/index.cjs +1049 -224
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1046 -221
- package/dist/index.js.map +1 -1
- package/dist/profile.cjs +13 -4
- package/dist/profile.cjs.map +1 -1
- package/dist/profile.d.cts +14 -2
- package/dist/profile.d.ts +14 -2
- package/dist/profile.js +13 -4
- package/dist/profile.js.map +1 -1
- package/dist/recovery.cjs +67 -0
- package/dist/recovery.cjs.map +1 -0
- package/dist/recovery.d.cts +38 -0
- package/dist/recovery.d.ts +38 -0
- package/dist/recovery.js +39 -0
- package/dist/recovery.js.map +1 -0
- package/dist/secret-request.cjs +271 -0
- package/dist/secret-request.cjs.map +1 -0
- package/dist/secret-request.d.cts +119 -0
- package/dist/secret-request.d.ts +119 -0
- package/dist/secret-request.js +222 -0
- package/dist/secret-request.js.map +1 -0
- package/dist/state.cjs +232 -40
- package/dist/state.cjs.map +1 -1
- package/dist/state.js +233 -40
- package/dist/state.js.map +1 -1
- package/generated/operations.json +1838 -89
- package/package.json +15 -5
package/dist/index.cjs
CHANGED
|
@@ -300,6 +300,7 @@ var init_errors2 = __esm({
|
|
|
300
300
|
"PROFILE_NOT_FOUND",
|
|
301
301
|
"PROFILE_POSTURE_NOT_ALLOWED",
|
|
302
302
|
"PROFILE_OWNER_OPT_IN_REQUIRED",
|
|
303
|
+
"PROFILE_MIGRATION_REQUIRED",
|
|
303
304
|
"AUTH_REQUIRED",
|
|
304
305
|
"PERMISSION_DENIED",
|
|
305
306
|
"SESSION_NOT_FOUND",
|
|
@@ -4108,10 +4109,10 @@ function evaluateOperationAuthority(granted, required, resolveSpace2) {
|
|
|
4108
4109
|
const canonicalRequired = canonicalizeOperationCapabilities(required, resolveSpace2);
|
|
4109
4110
|
const missing = [];
|
|
4110
4111
|
for (const requested of canonicalRequired) {
|
|
4111
|
-
const
|
|
4112
|
-
(candidate) => candidate.service === requested.service && operationSpacesEqual(candidate.space, requested.space)
|
|
4112
|
+
const sameScope = canonicalGranted.filter(
|
|
4113
|
+
(candidate) => candidate.service === requested.service && operationSpacesEqual(candidate.space, requested.space)
|
|
4113
4114
|
);
|
|
4114
|
-
if (!
|
|
4115
|
+
if (!isCapabilitySubset([requested], sameScope).subset) missing.push(requested);
|
|
4115
4116
|
}
|
|
4116
4117
|
return {
|
|
4117
4118
|
satisfied: missing.length === 0,
|
|
@@ -4234,9 +4235,161 @@ var init_authority = __esm({
|
|
|
4234
4235
|
|
|
4235
4236
|
// src/state.ts
|
|
4236
4237
|
function processWideContext(key) {
|
|
4237
|
-
var
|
|
4238
|
+
var _a4;
|
|
4238
4239
|
const registry = globalThis;
|
|
4239
|
-
return registry[
|
|
4240
|
+
return registry[_a4 = Symbol.for(key)] ?? (registry[_a4] = new import_node_async_hooks.AsyncLocalStorage());
|
|
4241
|
+
}
|
|
4242
|
+
function processOrigin() {
|
|
4243
|
+
if (cachedProcessOrigin !== void 0) return cachedProcessOrigin;
|
|
4244
|
+
if (process.platform !== "linux") return cachedProcessOrigin = null;
|
|
4245
|
+
try {
|
|
4246
|
+
const bootId = (0, import_node_fs.readFileSync)("/proc/sys/kernel/random/boot_id", "utf8").trim();
|
|
4247
|
+
const pidNamespace = (0, import_node_fs.readlinkSync)("/proc/self/ns/pid");
|
|
4248
|
+
const processStat = (0, import_node_fs.readFileSync)("/proc/self/stat", "utf8");
|
|
4249
|
+
const closingParen = processStat.lastIndexOf(") ");
|
|
4250
|
+
const processStartTime = closingParen < 0 ? void 0 : processStat.slice(closingParen + 2).trim().split(/\s+/)[19];
|
|
4251
|
+
if (!/^[0-9a-f-]{36}$/.test(bootId) || !/^pid:\[[0-9]+\]$/.test(pidNamespace) || !processStartTime || !/^[0-9]+$/.test(processStartTime)) {
|
|
4252
|
+
return cachedProcessOrigin = null;
|
|
4253
|
+
}
|
|
4254
|
+
return cachedProcessOrigin = { bootId, pidNamespace, processStartTime };
|
|
4255
|
+
} catch {
|
|
4256
|
+
return cachedProcessOrigin = null;
|
|
4257
|
+
}
|
|
4258
|
+
}
|
|
4259
|
+
function processOwner(liveness) {
|
|
4260
|
+
return { instanceId: processInstanceId, ...processOrigin(), ...liveness ? { liveness: liveness.record } : {} };
|
|
4261
|
+
}
|
|
4262
|
+
function socketDirectory(name) {
|
|
4263
|
+
const match = LIVENESS_SOCKET.exec(name);
|
|
4264
|
+
return match ? `${match[1]}-${match[2]}` : null;
|
|
4265
|
+
}
|
|
4266
|
+
async function pinDirectory(directory) {
|
|
4267
|
+
const handle = await (0, import_promises.open)(directory, "r");
|
|
4268
|
+
try {
|
|
4269
|
+
const { dev, ino } = await handle.stat({ bigint: true });
|
|
4270
|
+
return {
|
|
4271
|
+
identity: `${dev.toString(16)}-${ino.toString(16)}`,
|
|
4272
|
+
path: (name) => `/proc/self/fd/${handle.fd}/${name}`,
|
|
4273
|
+
close: () => handle.close()
|
|
4274
|
+
};
|
|
4275
|
+
} catch (error) {
|
|
4276
|
+
await handle.close();
|
|
4277
|
+
throw error;
|
|
4278
|
+
}
|
|
4279
|
+
}
|
|
4280
|
+
async function bindLiveness(directory) {
|
|
4281
|
+
if (!processOrigin()) return null;
|
|
4282
|
+
let pinned;
|
|
4283
|
+
try {
|
|
4284
|
+
pinned = await pinDirectory(directory);
|
|
4285
|
+
} catch {
|
|
4286
|
+
return null;
|
|
4287
|
+
}
|
|
4288
|
+
const socket = `.live-${pinned.identity}-${(0, import_node_crypto.randomUUID)()}`;
|
|
4289
|
+
const server = (0, import_node_net.createServer)((connection) => connection.destroy());
|
|
4290
|
+
let bound = false;
|
|
4291
|
+
try {
|
|
4292
|
+
await new Promise((resolve2, reject) => {
|
|
4293
|
+
server.once("error", reject);
|
|
4294
|
+
server.listen({ path: pinned.path(`${socket}.new`), exclusive: true }, () => {
|
|
4295
|
+
server.off("error", reject);
|
|
4296
|
+
resolve2();
|
|
4297
|
+
});
|
|
4298
|
+
});
|
|
4299
|
+
bound = true;
|
|
4300
|
+
server.on("error", () => void 0);
|
|
4301
|
+
server.unref();
|
|
4302
|
+
await (0, import_promises.rename)(pinned.path(`${socket}.new`), pinned.path(socket));
|
|
4303
|
+
const stats = await (0, import_promises.lstat)(pinned.path(socket), { bigint: true });
|
|
4304
|
+
if (!stats.isSocket()) throw new Error(`${socket} is not a socket`);
|
|
4305
|
+
await waitForTestBarrier(TEST_LOCK_LIVENESS_BARRIER_DIR, (0, import_node_path.basename)(directory));
|
|
4306
|
+
return { record: { socket, dev: String(stats.dev), ino: String(stats.ino) }, path: (0, import_node_path.join)(directory, socket), server };
|
|
4307
|
+
} catch {
|
|
4308
|
+
if (bound) {
|
|
4309
|
+
server.close();
|
|
4310
|
+
for (const name of [`${socket}.new`, socket]) await (0, import_promises.unlink)(pinned.path(name)).catch(() => void 0);
|
|
4311
|
+
}
|
|
4312
|
+
return null;
|
|
4313
|
+
} finally {
|
|
4314
|
+
await pinned.close();
|
|
4315
|
+
}
|
|
4316
|
+
}
|
|
4317
|
+
async function ensureLiveness(acquisition) {
|
|
4318
|
+
if (acquisition.held === void 0) acquisition.held = await bindLiveness(acquisition.directory);
|
|
4319
|
+
return acquisition.held;
|
|
4320
|
+
}
|
|
4321
|
+
async function closeLiveness(held) {
|
|
4322
|
+
held.server.close();
|
|
4323
|
+
await (0, import_promises.unlink)(held.path).catch(() => void 0);
|
|
4324
|
+
}
|
|
4325
|
+
function parseLiveness(value) {
|
|
4326
|
+
if (typeof value !== "object" || value === null) return null;
|
|
4327
|
+
const { socket, dev, ino } = value;
|
|
4328
|
+
const name = typeof socket === "string" ? LIVENESS_SOCKET.exec(socket) : null;
|
|
4329
|
+
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;
|
|
4330
|
+
}
|
|
4331
|
+
async function controlIn(pinned, acquisition) {
|
|
4332
|
+
const own = await ensureLiveness(acquisition);
|
|
4333
|
+
if (!own || socketDirectory(own.record.socket) !== pinned.identity) return null;
|
|
4334
|
+
const stats = await (0, import_promises.lstat)(pinned.path(own.record.socket), { bigint: true }).catch(() => null);
|
|
4335
|
+
return stats?.isSocket() && String(stats.dev) === own.record.dev && String(stats.ino) === own.record.ino ? own : null;
|
|
4336
|
+
}
|
|
4337
|
+
async function tryConnect(path) {
|
|
4338
|
+
return new Promise((resolve2) => {
|
|
4339
|
+
const probe = (0, import_node_net.connect)({ path });
|
|
4340
|
+
const settle = (outcome) => {
|
|
4341
|
+
clearTimeout(timer);
|
|
4342
|
+
probe.destroy();
|
|
4343
|
+
resolve2(outcome);
|
|
4344
|
+
};
|
|
4345
|
+
const timer = setTimeout(() => settle("other"), LIVENESS_PROBE_TIMEOUT_MS);
|
|
4346
|
+
probe.once("connect", () => settle("connected"));
|
|
4347
|
+
probe.once("error", (error) => settle(isErrno(error, "ECONNREFUSED") ? "refused" : "other"));
|
|
4348
|
+
});
|
|
4349
|
+
}
|
|
4350
|
+
async function kernelRefuses(pinned, name, control) {
|
|
4351
|
+
if (process.versions.bun !== void 0) return false;
|
|
4352
|
+
return await tryConnect(pinned.path(name)) === "refused" && await tryConnect(pinned.path(control.record.socket)) === "connected";
|
|
4353
|
+
}
|
|
4354
|
+
async function probeLiveness(liveness, acquisition) {
|
|
4355
|
+
let pinned;
|
|
4356
|
+
try {
|
|
4357
|
+
pinned = await pinDirectory(acquisition.directory);
|
|
4358
|
+
} catch {
|
|
4359
|
+
return "unknown";
|
|
4360
|
+
}
|
|
4361
|
+
try {
|
|
4362
|
+
if (socketDirectory(liveness.socket) !== pinned.identity) return "unknown";
|
|
4363
|
+
const control = await controlIn(pinned, acquisition);
|
|
4364
|
+
if (!control) return "unknown";
|
|
4365
|
+
let stats;
|
|
4366
|
+
try {
|
|
4367
|
+
stats = await (0, import_promises.lstat)(pinned.path(liveness.socket), { bigint: true });
|
|
4368
|
+
} catch (error) {
|
|
4369
|
+
return isErrno(error, "ENOENT") ? "gone" : "unknown";
|
|
4370
|
+
}
|
|
4371
|
+
if (!stats.isSocket() || String(stats.dev) !== liveness.dev || String(stats.ino) !== liveness.ino) return "unknown";
|
|
4372
|
+
return await kernelRefuses(pinned, liveness.socket, control) ? "refused" : "unknown";
|
|
4373
|
+
} finally {
|
|
4374
|
+
await pinned.close();
|
|
4375
|
+
}
|
|
4376
|
+
}
|
|
4377
|
+
async function sweepLiveness(acquisition) {
|
|
4378
|
+
if (process.versions.bun !== void 0 || !acquisition.held) return;
|
|
4379
|
+
const pinned = await pinDirectory(acquisition.directory);
|
|
4380
|
+
try {
|
|
4381
|
+
const control = await controlIn(pinned, acquisition);
|
|
4382
|
+
if (!control) return;
|
|
4383
|
+
for (const name of await (0, import_promises.readdir)(pinned.path("."))) {
|
|
4384
|
+
if (name === control.record.socket || socketDirectory(name) !== pinned.identity) continue;
|
|
4385
|
+
const stats = await (0, import_promises.lstat)(pinned.path(name)).catch(() => null);
|
|
4386
|
+
if (stats?.isSocket() && await kernelRefuses(pinned, name, control)) {
|
|
4387
|
+
await (0, import_promises.unlink)(pinned.path(name)).catch(() => void 0);
|
|
4388
|
+
}
|
|
4389
|
+
}
|
|
4390
|
+
} finally {
|
|
4391
|
+
await pinned.close();
|
|
4392
|
+
}
|
|
4240
4393
|
}
|
|
4241
4394
|
function tinycloudHomePath() {
|
|
4242
4395
|
const home = invocationStateRoot.getStore() ?? process.env.TC_HOME ?? process.env.HOME ?? process.env.USERPROFILE ?? (0, import_node_os.homedir)();
|
|
@@ -4413,7 +4566,18 @@ async function acquireProfileLock(profile, options) {
|
|
|
4413
4566
|
if (path !== directory || !isErrno(error, "ENOENT")) throw error;
|
|
4414
4567
|
});
|
|
4415
4568
|
}
|
|
4416
|
-
const
|
|
4569
|
+
const liveness = { directory: turnDirectory, held: void 0 };
|
|
4570
|
+
const closeAcquisitionLiveness = async () => {
|
|
4571
|
+
if (liveness.held) await closeLiveness(liveness.held);
|
|
4572
|
+
};
|
|
4573
|
+
let taken;
|
|
4574
|
+
try {
|
|
4575
|
+
taken = await acquireTurn(profile, turnDirectory, deadline, liveness);
|
|
4576
|
+
} catch (error) {
|
|
4577
|
+
await closeAcquisitionLiveness();
|
|
4578
|
+
throw error;
|
|
4579
|
+
}
|
|
4580
|
+
const { turn, firstWaitedTurn } = taken;
|
|
4417
4581
|
try {
|
|
4418
4582
|
if (options.requireProfile && !await exists(profileConfigPath(profile))) {
|
|
4419
4583
|
await (0, import_promises.rmdir)(directory).catch(() => void 0);
|
|
@@ -4423,7 +4587,7 @@ async function acquireProfileLock(profile, options) {
|
|
|
4423
4587
|
const deletedWhileWaiting = typeof deletion?.slot === "number" && deletion.slot >= firstWaitedTurn;
|
|
4424
4588
|
while (true) {
|
|
4425
4589
|
const token = (0, import_node_crypto.randomUUID)();
|
|
4426
|
-
if (await publishProfileLock(profile, lockPath, token, options.requireProfile === true)) {
|
|
4590
|
+
if (await publishProfileLock(profile, lockPath, token, options.requireProfile === true, liveness.held ?? null)) {
|
|
4427
4591
|
return {
|
|
4428
4592
|
deletedWhileWaiting,
|
|
4429
4593
|
turnSlot: turn.slot,
|
|
@@ -4431,17 +4595,19 @@ async function acquireProfileLock(profile, options) {
|
|
|
4431
4595
|
try {
|
|
4432
4596
|
await releaseProfileLock(profile, lockPath, token);
|
|
4433
4597
|
} finally {
|
|
4434
|
-
await releaseTurn(profile, turn);
|
|
4598
|
+
await releaseTurn(profile, turn, liveness);
|
|
4599
|
+
await closeAcquisitionLiveness();
|
|
4435
4600
|
}
|
|
4436
4601
|
}
|
|
4437
4602
|
};
|
|
4438
4603
|
}
|
|
4439
4604
|
await signalTestLockContention(profile);
|
|
4440
|
-
if (await recoverStaleLock(profile, lockPath, staleAfterMs)) continue;
|
|
4605
|
+
if (await recoverStaleLock(profile, lockPath, liveness, staleAfterMs)) continue;
|
|
4441
4606
|
await waitOrTimeOut(profile, deadline);
|
|
4442
4607
|
}
|
|
4443
4608
|
} catch (error) {
|
|
4444
|
-
await releaseTurn(profile, turn);
|
|
4609
|
+
await releaseTurn(profile, turn, liveness);
|
|
4610
|
+
await closeAcquisitionLiveness();
|
|
4445
4611
|
throw error;
|
|
4446
4612
|
}
|
|
4447
4613
|
}
|
|
@@ -4454,7 +4620,7 @@ async function waitOrTimeOut(profile, deadline, detail) {
|
|
|
4454
4620
|
checkDeadline(profile, deadline, detail);
|
|
4455
4621
|
await sleep(Math.min(deadline.retryMs, deadline.timeoutMs - (Date.now() - deadline.startedAt)));
|
|
4456
4622
|
}
|
|
4457
|
-
async function acquireTurn(profile, directory, deadline) {
|
|
4623
|
+
async function acquireTurn(profile, directory, deadline, liveness) {
|
|
4458
4624
|
let firstWaitedTurn;
|
|
4459
4625
|
let problem;
|
|
4460
4626
|
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.`;
|
|
@@ -4479,13 +4645,13 @@ async function acquireTurn(profile, directory, deadline) {
|
|
|
4479
4645
|
}
|
|
4480
4646
|
firstWaitedTurn ?? (firstWaitedTurn = latest.done ? latest.slot + 1 : latest.slot);
|
|
4481
4647
|
if (latest.held && latest.done) {
|
|
4482
|
-
const turn = await takeTurn(profile, directory, latest);
|
|
4648
|
+
const turn = await takeTurn(profile, directory, latest, await ensureLiveness(liveness));
|
|
4483
4649
|
if (turn !== null) return { turn, firstWaitedTurn };
|
|
4484
4650
|
await waitOrTimeOut(profile, deadline);
|
|
4485
4651
|
} else if (latest.done) {
|
|
4486
4652
|
problem = damaged(`ends with turn ${latest.slot}, which was never granted`);
|
|
4487
4653
|
await waitOrTimeOut(profile, deadline, problem);
|
|
4488
|
-
} else if (latest.pid === process.pid
|
|
4654
|
+
} else if (latest.pid === process.pid && relinquishedTurns.has(latest.token) || !await isRecordedProcessAlive(latest, liveness)) {
|
|
4489
4655
|
await waitForTestBarrier(TEST_LOCK_TURN_SETTLE_BARRIER_DIR, profile);
|
|
4490
4656
|
await settleAbandonedTurn(directory, latest);
|
|
4491
4657
|
relinquishedTurns.delete(latest.token);
|
|
@@ -4530,6 +4696,7 @@ async function readTurn(directory, slot) {
|
|
|
4530
4696
|
const done = await exists((0, import_node_path.join)(path, `${owner.token}.done`));
|
|
4531
4697
|
const held = await exists((0, import_node_path.join)(path, `${owner.token}.held`));
|
|
4532
4698
|
return {
|
|
4699
|
+
...owner,
|
|
4533
4700
|
slot,
|
|
4534
4701
|
token: owner.token,
|
|
4535
4702
|
pid: owner.pid,
|
|
@@ -4543,11 +4710,11 @@ async function isFinishedTurn(directory, slot, token) {
|
|
|
4543
4710
|
if (turn === "damaged") throw new Error(`Turn ${slot} of the turn lock ${directory} is unreadable.`);
|
|
4544
4711
|
return turn !== null && turn.token === token && turn.held && turn.done;
|
|
4545
4712
|
}
|
|
4546
|
-
async function takeTurn(profile, directory, latest) {
|
|
4713
|
+
async function takeTurn(profile, directory, latest, liveness) {
|
|
4547
4714
|
const token = (0, import_node_crypto.randomUUID)();
|
|
4548
4715
|
const slot = latest.slot + 1;
|
|
4549
4716
|
const path = (0, import_node_path.join)(directory, String(slot));
|
|
4550
|
-
const owner = { pid: process.pid, createdAt: (/* @__PURE__ */ new Date()).toISOString(), token, after: latest.token };
|
|
4717
|
+
const owner = { pid: process.pid, createdAt: (/* @__PURE__ */ new Date()).toISOString(), token, after: latest.token, ...processOwner(liveness) };
|
|
4551
4718
|
let published = false;
|
|
4552
4719
|
try {
|
|
4553
4720
|
published = await publishDirectory(
|
|
@@ -4601,7 +4768,7 @@ async function markTurn(path, token, kind) {
|
|
|
4601
4768
|
}
|
|
4602
4769
|
async function createTurnDirectory(directory) {
|
|
4603
4770
|
const token = (0, import_node_crypto.randomUUID)();
|
|
4604
|
-
const owner = { pid: process.pid, createdAt: (/* @__PURE__ */ new Date()).toISOString(), token, after: null };
|
|
4771
|
+
const owner = { pid: process.pid, createdAt: (/* @__PURE__ */ new Date()).toISOString(), token, after: null, ...processOwner(null) };
|
|
4605
4772
|
await publishDirectory((0, import_node_path.join)((0, import_node_path.dirname)(directory), `.stage-${(0, import_node_path.basename)(directory)}-${process.pid}-${token}`), directory, {
|
|
4606
4773
|
"0/owner.json": `${JSON.stringify(owner, null, 2)}
|
|
4607
4774
|
`,
|
|
@@ -4625,31 +4792,39 @@ async function publishDirectory(stage, target, files, beforeRename) {
|
|
|
4625
4792
|
await (0, import_promises.rm)(stage, { recursive: true, force: true }).catch(() => void 0);
|
|
4626
4793
|
}
|
|
4627
4794
|
}
|
|
4628
|
-
async function releaseTurn(profile, turn) {
|
|
4795
|
+
async function releaseTurn(profile, turn, liveness) {
|
|
4629
4796
|
try {
|
|
4630
4797
|
await markTurn((0, import_node_path.join)(turn.directory, String(turn.slot)), turn.token, "done");
|
|
4631
4798
|
} catch {
|
|
4632
4799
|
settleOwnTurn(turn.directory, turn.slot, turn.token);
|
|
4633
4800
|
return;
|
|
4634
4801
|
}
|
|
4635
|
-
await collectTurns(profile, turn).catch(() => void 0);
|
|
4802
|
+
await collectTurns(profile, turn, liveness).catch(() => void 0);
|
|
4636
4803
|
}
|
|
4637
|
-
function isAbandonedTurnEntry(name) {
|
|
4804
|
+
async function isAbandonedTurnEntry(directory, name, liveness) {
|
|
4638
4805
|
if (name.startsWith(".trash-")) return true;
|
|
4639
4806
|
const writer = /^\.(?:stage|deleted\.json)[-.]([0-9]+)[-.]/.exec(name)?.[1];
|
|
4640
|
-
|
|
4807
|
+
if (writer === void 0) return false;
|
|
4808
|
+
if (Number(writer) !== process.pid) return !await isRecordedProcessAlive({ pid: Number(writer) }, liveness);
|
|
4809
|
+
return name.startsWith(".stage-") && await isStagedOwnerGone((0, import_node_path.join)(directory, name, "owner.json"), liveness);
|
|
4641
4810
|
}
|
|
4642
|
-
function isAbandonedTurnDirectoryStage(name) {
|
|
4811
|
+
async function isAbandonedTurnDirectoryStage(directory, name, liveness) {
|
|
4643
4812
|
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];
|
|
4644
|
-
|
|
4813
|
+
if (writer === void 0) return false;
|
|
4814
|
+
if (Number(writer) !== process.pid) return !await isRecordedProcessAlive({ pid: Number(writer) }, liveness);
|
|
4815
|
+
return isStagedOwnerGone((0, import_node_path.join)(directory, name, "0", "owner.json"), liveness);
|
|
4645
4816
|
}
|
|
4646
|
-
async function
|
|
4817
|
+
async function isStagedOwnerGone(recordPath, liveness) {
|
|
4818
|
+
const owner = await readJson(recordPath).catch(() => null);
|
|
4819
|
+
return typeof owner === "object" && owner !== null && owner.pid === process.pid && !await isRecordedProcessAlive(owner, liveness);
|
|
4820
|
+
}
|
|
4821
|
+
async function collectTurns(profile, turn, liveness) {
|
|
4647
4822
|
for (const [directory, abandoned] of [
|
|
4648
4823
|
[turn.directory, isAbandonedTurnEntry],
|
|
4649
4824
|
[(0, import_node_path.dirname)(turn.directory), isAbandonedTurnDirectoryStage]
|
|
4650
4825
|
]) {
|
|
4651
|
-
for (const name of
|
|
4652
|
-
await (0, import_promises.rm)((0, import_node_path.join)(directory, name), { recursive: true, force: true }).catch(() => void 0);
|
|
4826
|
+
for (const name of await (0, import_promises.readdir)(directory)) {
|
|
4827
|
+
if (await abandoned(directory, name, liveness)) await (0, import_promises.rm)((0, import_node_path.join)(directory, name), { recursive: true, force: true }).catch(() => void 0);
|
|
4653
4828
|
}
|
|
4654
4829
|
}
|
|
4655
4830
|
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);
|
|
@@ -4667,8 +4842,9 @@ async function collectTurns(profile, turn) {
|
|
|
4667
4842
|
await (0, import_promises.rm)(trash, { recursive: true, force: true }).catch(() => void 0);
|
|
4668
4843
|
}
|
|
4669
4844
|
}
|
|
4845
|
+
await sweepLiveness(liveness);
|
|
4670
4846
|
}
|
|
4671
|
-
async function publishProfileLock(profile, lockPath, token, requireProfile) {
|
|
4847
|
+
async function publishProfileLock(profile, lockPath, token, requireProfile, liveness) {
|
|
4672
4848
|
try {
|
|
4673
4849
|
await (0, import_promises.mkdir)(lockPath, { mode: PRIVATE_DIR_MODE });
|
|
4674
4850
|
} catch (error) {
|
|
@@ -4681,7 +4857,7 @@ async function publishProfileLock(profile, lockPath, token, requireProfile) {
|
|
|
4681
4857
|
await waitForTestBarrier(TEST_LOCK_PUBLISH_BARRIER_DIR, profile);
|
|
4682
4858
|
const ownerPath = (0, import_node_path.join)(lockPath, "owner.json");
|
|
4683
4859
|
const staged = (0, import_node_path.join)((0, import_node_path.dirname)(lockPath), `.lock-owner-${token}.tmp`);
|
|
4684
|
-
const owner = `${JSON.stringify({ pid: process.pid, createdAt: (/* @__PURE__ */ new Date()).toISOString(), token }, null, 2)}
|
|
4860
|
+
const owner = `${JSON.stringify({ pid: process.pid, createdAt: (/* @__PURE__ */ new Date()).toISOString(), token, ...processOwner(liveness) }, null, 2)}
|
|
4685
4861
|
`;
|
|
4686
4862
|
try {
|
|
4687
4863
|
await (0, import_promises.writeFile)(staged, owner, { encoding: "utf8", mode: PRIVATE_FILE_MODE, flag: "wx" });
|
|
@@ -4725,7 +4901,7 @@ async function signalTestLockContention(profile) {
|
|
|
4725
4901
|
await (0, import_promises.writeFile)(signalPath, `${profile}
|
|
4726
4902
|
`, { encoding: "utf8", flag: "wx" }).catch(() => void 0);
|
|
4727
4903
|
}
|
|
4728
|
-
async function recoverOwnerlessLock(profile, lockPath, staleAfterMs) {
|
|
4904
|
+
async function recoverOwnerlessLock(profile, lockPath, liveness, staleAfterMs) {
|
|
4729
4905
|
let aged;
|
|
4730
4906
|
try {
|
|
4731
4907
|
const { mtimeMs } = await (0, import_promises.stat)(lockPath);
|
|
@@ -4742,7 +4918,7 @@ async function recoverOwnerlessLock(profile, lockPath, staleAfterMs) {
|
|
|
4742
4918
|
if (!onlyOrphanedClaims && (!aged || recoveryFenced(checkedAt, staleAfterMs))) return false;
|
|
4743
4919
|
for (const name of entries.filter((entry) => RECOVERY_CLAIM.test(entry))) {
|
|
4744
4920
|
const claimPath = (0, import_node_path.join)(lockPath, name);
|
|
4745
|
-
if (await hasLiveRecoveryOwner(claimPath)) {
|
|
4921
|
+
if (await hasLiveRecoveryOwner(claimPath, liveness)) {
|
|
4746
4922
|
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"));
|
|
4747
4923
|
return false;
|
|
4748
4924
|
}
|
|
@@ -4758,7 +4934,7 @@ async function recoverOwnerlessLock(profile, lockPath, staleAfterMs) {
|
|
|
4758
4934
|
await removeAgedOwnerFiles(lockPath, staleAfterMs);
|
|
4759
4935
|
return true;
|
|
4760
4936
|
}
|
|
4761
|
-
async function hasLiveRecoveryOwner(claimPath) {
|
|
4937
|
+
async function hasLiveRecoveryOwner(claimPath, liveness) {
|
|
4762
4938
|
let contents;
|
|
4763
4939
|
try {
|
|
4764
4940
|
contents = await (0, import_promises.readFile)(claimPath, "utf8");
|
|
@@ -4772,8 +4948,8 @@ async function hasLiveRecoveryOwner(claimPath) {
|
|
|
4772
4948
|
} catch {
|
|
4773
4949
|
return false;
|
|
4774
4950
|
}
|
|
4775
|
-
const
|
|
4776
|
-
return typeof pid === "number" && Number.isInteger(pid) && pid > 0 &&
|
|
4951
|
+
const record = typeof owner === "object" && owner !== null && "pid" in owner ? owner : null;
|
|
4952
|
+
return typeof record?.pid === "number" && Number.isInteger(record.pid) && record.pid > 0 && await isRecordedProcessAlive(record, liveness);
|
|
4777
4953
|
}
|
|
4778
4954
|
function recoveryFenced(since, staleAfterMs) {
|
|
4779
4955
|
return performance.now() - since >= staleAfterMs / 2;
|
|
@@ -4787,11 +4963,11 @@ async function removeAgedOwnerFiles(lockPath, staleAfterMs) {
|
|
|
4787
4963
|
if (aged) await (0, import_promises.rm)(path, { force: true }).catch(() => void 0);
|
|
4788
4964
|
}
|
|
4789
4965
|
}
|
|
4790
|
-
async function recoverStaleLock(profile, lockPath, staleAfterMs) {
|
|
4966
|
+
async function recoverStaleLock(profile, lockPath, liveness, staleAfterMs) {
|
|
4791
4967
|
const ownerPath = (0, import_node_path.join)(lockPath, "owner.json");
|
|
4792
4968
|
const observed = await (0, import_promises.readFile)(ownerPath, "utf8").catch(() => null);
|
|
4793
|
-
if (observed === null) return recoverOwnerlessLock(profile, lockPath, staleAfterMs);
|
|
4794
|
-
if (!await isAbandonedOwner(observed, ownerPath, staleAfterMs)) return false;
|
|
4969
|
+
if (observed === null) return recoverOwnerlessLock(profile, lockPath, liveness, staleAfterMs);
|
|
4970
|
+
if (!await isAbandonedOwner(observed, ownerPath, liveness, staleAfterMs)) return false;
|
|
4795
4971
|
await waitForTestBarrier(TEST_LOCK_RECOVERY_BARRIER_DIR, profile);
|
|
4796
4972
|
const claimPath = (0, import_node_path.join)(lockPath, `.recover-${(0, import_node_crypto.randomUUID)()}.json`);
|
|
4797
4973
|
let moved = false;
|
|
@@ -4827,7 +5003,7 @@ async function recoverStaleLock(profile, lockPath, staleAfterMs) {
|
|
|
4827
5003
|
await removeAgedOwnerFiles(lockPath, staleAfterMs);
|
|
4828
5004
|
return true;
|
|
4829
5005
|
}
|
|
4830
|
-
async function isAbandonedOwner(observed, ownerPath, staleAfterMs) {
|
|
5006
|
+
async function isAbandonedOwner(observed, ownerPath, liveness, staleAfterMs) {
|
|
4831
5007
|
let owner;
|
|
4832
5008
|
try {
|
|
4833
5009
|
owner = JSON.parse(observed);
|
|
@@ -4835,7 +5011,7 @@ async function isAbandonedOwner(observed, ownerPath, staleAfterMs) {
|
|
|
4835
5011
|
owner = null;
|
|
4836
5012
|
}
|
|
4837
5013
|
if (typeof owner === "object" && owner !== null && "pid" in owner) {
|
|
4838
|
-
return isStaleOwner(owner, staleAfterMs);
|
|
5014
|
+
return isStaleOwner(owner, liveness, staleAfterMs);
|
|
4839
5015
|
}
|
|
4840
5016
|
const modifiedAt = await (0, import_promises.stat)(ownerPath).then(({ mtimeMs }) => mtimeMs, () => Number.NaN);
|
|
4841
5017
|
return Date.now() - modifiedAt >= staleAfterMs;
|
|
@@ -4857,14 +5033,14 @@ async function waitForTestBarrier(environmentName, profile) {
|
|
|
4857
5033
|
}
|
|
4858
5034
|
}
|
|
4859
5035
|
}
|
|
4860
|
-
function isStaleOwner(owner, staleAfterMs) {
|
|
5036
|
+
async function isStaleOwner(owner, liveness, staleAfterMs) {
|
|
4861
5037
|
const now = Date.now();
|
|
4862
5038
|
const createdAt = typeof owner?.createdAt === "string" ? Date.parse(owner.createdAt) : Number.NaN;
|
|
4863
5039
|
if (!Number.isFinite(createdAt) || now - createdAt < staleAfterMs) return false;
|
|
4864
5040
|
if (typeof owner?.pid !== "number" || !Number.isInteger(owner.pid) || owner.pid <= 0) {
|
|
4865
5041
|
return false;
|
|
4866
5042
|
}
|
|
4867
|
-
return !
|
|
5043
|
+
return !await isRecordedProcessAlive(owner, liveness);
|
|
4868
5044
|
}
|
|
4869
5045
|
async function exists(path) {
|
|
4870
5046
|
try {
|
|
@@ -4875,13 +5051,22 @@ async function exists(path) {
|
|
|
4875
5051
|
throw error;
|
|
4876
5052
|
}
|
|
4877
5053
|
}
|
|
4878
|
-
function
|
|
5054
|
+
async function isRecordedProcessAlive(owner, acquisition) {
|
|
5055
|
+
const pid = owner.pid;
|
|
5056
|
+
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0) return true;
|
|
4879
5057
|
try {
|
|
4880
5058
|
process.kill(pid, 0);
|
|
4881
|
-
return true;
|
|
4882
5059
|
} catch (error) {
|
|
4883
5060
|
return !isErrno(error, "ESRCH");
|
|
4884
5061
|
}
|
|
5062
|
+
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;
|
|
5063
|
+
const origin = processOrigin();
|
|
5064
|
+
if (!origin || owner.bootId !== origin.bootId || typeof owner.pidNamespace !== "string" || !/^pid:\[[0-9]+\]$/.test(owner.pidNamespace)) return true;
|
|
5065
|
+
if (owner.pidNamespace === origin.pidNamespace) {
|
|
5066
|
+
return typeof owner.processStartTime !== "string" || !/^[0-9]+$/.test(owner.processStartTime) || owner.processStartTime === origin.processStartTime;
|
|
5067
|
+
}
|
|
5068
|
+
const liveness = parseLiveness(owner.liveness);
|
|
5069
|
+
return liveness === null || await probeLiveness(liveness, acquisition) === "unknown";
|
|
4885
5070
|
}
|
|
4886
5071
|
function validateProfileName(profile) {
|
|
4887
5072
|
if (!profile || profile === "." || profile === ".." || profile.includes("/") || profile.includes("\\") || profile.includes("\0")) {
|
|
@@ -4895,13 +5080,15 @@ function isErrno(error, code) {
|
|
|
4895
5080
|
function sleep(milliseconds) {
|
|
4896
5081
|
return new Promise((resolve2) => setTimeout(resolve2, milliseconds));
|
|
4897
5082
|
}
|
|
4898
|
-
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,
|
|
5083
|
+
var import_node_crypto, import_node_async_hooks, import_node_fs, import_promises, import_node_net, 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, 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;
|
|
4899
5084
|
var init_state = __esm({
|
|
4900
5085
|
"src/state.ts"() {
|
|
4901
5086
|
"use strict";
|
|
4902
5087
|
import_node_crypto = require("crypto");
|
|
4903
5088
|
import_node_async_hooks = require("async_hooks");
|
|
5089
|
+
import_node_fs = require("fs");
|
|
4904
5090
|
import_promises = require("fs/promises");
|
|
5091
|
+
import_node_net = require("net");
|
|
4905
5092
|
import_node_os = require("os");
|
|
4906
5093
|
import_node_path = require("path");
|
|
4907
5094
|
DEFAULT_PROFILE = "default";
|
|
@@ -4922,8 +5109,12 @@ var init_state = __esm({
|
|
|
4922
5109
|
TEST_LOCK_TURN_PUBLISH_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_TURN_PUBLISH_BARRIER_DIR";
|
|
4923
5110
|
TEST_LOCK_TURN_SETTLE_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_TURN_SETTLE_BARRIER_DIR";
|
|
4924
5111
|
TEST_LOCK_TURN_COLLECT_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_TURN_COLLECT_BARRIER_DIR";
|
|
5112
|
+
TEST_LOCK_LIVENESS_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_LIVENESS_BARRIER_DIR";
|
|
4925
5113
|
invocationStateRoot = processWideContext("tinycloud.operations.invocationStateRoot.v1");
|
|
4926
5114
|
heldProfileLocks = processWideContext("tinycloud.operations.heldProfileLocks.v2");
|
|
5115
|
+
processInstanceId = globalThis[_a2 = /* @__PURE__ */ Symbol.for("tinycloud.operations.profileLockInstance.v1")] ?? (globalThis[_a2] = (0, import_node_crypto.randomUUID)());
|
|
5116
|
+
LIVENESS_SOCKET = /^\.live-([0-9a-f]+)-([0-9a-f]+)-[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}(\.new)?$/;
|
|
5117
|
+
LIVENESS_PROBE_TIMEOUT_MS = 1e3;
|
|
4927
5118
|
ProfileLockTimeoutError = class extends Error {
|
|
4928
5119
|
/** `detail` names the cause when it is not simply another process holding the lock. */
|
|
4929
5120
|
constructor(profile, timeoutMs, detail) {
|
|
@@ -4940,7 +5131,7 @@ var init_state = __esm({
|
|
|
4940
5131
|
}
|
|
4941
5132
|
};
|
|
4942
5133
|
TURN = /^(?:0|[1-9][0-9]*)$/;
|
|
4943
|
-
relinquishedTurns = globalThis[
|
|
5134
|
+
relinquishedTurns = globalThis[_a3 = /* @__PURE__ */ Symbol.for("tinycloud.operations.relinquishedProfileTurns.v1")] ?? (globalThis[_a3] = /* @__PURE__ */ new Set());
|
|
4944
5135
|
MARKER_RETRY_DELAYS_MS = [10, 50, 250];
|
|
4945
5136
|
SETTLE_RETRY_MS = 1e3;
|
|
4946
5137
|
SETTLE_RETRIES = 300;
|
|
@@ -5232,6 +5423,45 @@ var init_artifacts = __esm({
|
|
|
5232
5423
|
}
|
|
5233
5424
|
});
|
|
5234
5425
|
|
|
5426
|
+
// src/recovery.ts
|
|
5427
|
+
function signInRecovery(profile, posture) {
|
|
5428
|
+
if (!SAFE_PROFILE_TOKEN.test(profile)) return { action: "sign_in" };
|
|
5429
|
+
const template = posture === "local-owner-key" ? SIGN_IN_TEMPLATES.local : posture === "delegate-session" ? SIGN_IN_TEMPLATES.scoped : SIGN_IN_TEMPLATES.openkey;
|
|
5430
|
+
return { action: "sign_in", command: template(profile) };
|
|
5431
|
+
}
|
|
5432
|
+
function migrationRecovery(profile) {
|
|
5433
|
+
return SAFE_PROFILE_TOKEN.test(profile) ? { action: "complete_migration", command: migrationCommand(profile), operationId: MIGRATION_OPERATION_ID } : { action: "complete_migration", operationId: MIGRATION_OPERATION_ID };
|
|
5434
|
+
}
|
|
5435
|
+
function isSafeRecovery(value, action) {
|
|
5436
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
5437
|
+
const record = value;
|
|
5438
|
+
if (record.action !== action) return false;
|
|
5439
|
+
const keys = Object.keys(record);
|
|
5440
|
+
const allowed = action === "sign_in" ? ["action", "command"] : ["action", "command", "operationId"];
|
|
5441
|
+
if (!keys.every((key) => allowed.includes(key))) return false;
|
|
5442
|
+
if (action === "complete_migration" && record.operationId !== MIGRATION_OPERATION_ID) return false;
|
|
5443
|
+
if (!("command" in record)) return true;
|
|
5444
|
+
const command = record.command;
|
|
5445
|
+
if (typeof command !== "string") return false;
|
|
5446
|
+
const profile = /^tc --profile (\S+) /.exec(command)?.[1];
|
|
5447
|
+
if (profile === void 0 || !SAFE_PROFILE_TOKEN.test(profile)) return false;
|
|
5448
|
+
return action === "sign_in" ? Object.values(SIGN_IN_TEMPLATES).some((template) => template(profile) === command) : migrationCommand(profile) === command;
|
|
5449
|
+
}
|
|
5450
|
+
var SAFE_PROFILE_TOKEN, MIGRATION_OPERATION_ID, SIGN_IN_TEMPLATES, migrationCommand;
|
|
5451
|
+
var init_recovery = __esm({
|
|
5452
|
+
"src/recovery.ts"() {
|
|
5453
|
+
"use strict";
|
|
5454
|
+
SAFE_PROFILE_TOKEN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
5455
|
+
MIGRATION_OPERATION_ID = "tinycloud.auth.capabilities";
|
|
5456
|
+
SIGN_IN_TEMPLATES = {
|
|
5457
|
+
local: (profile) => `tc --profile ${profile} auth login --method local`,
|
|
5458
|
+
openkey: (profile) => `tc --profile ${profile} auth login --method openkey`,
|
|
5459
|
+
scoped: (profile) => `tc --profile ${profile} auth login --method openkey --paste --manifest <manifest.json>`
|
|
5460
|
+
};
|
|
5461
|
+
migrationCommand = (profile) => `tc --profile ${profile} auth caps`;
|
|
5462
|
+
}
|
|
5463
|
+
});
|
|
5464
|
+
|
|
5235
5465
|
// src/secrets.ts
|
|
5236
5466
|
function normalizeSecretsGetInput(input) {
|
|
5237
5467
|
const reference = resolveSecretReference(input);
|
|
@@ -5318,15 +5548,6 @@ function canonicalizeSpaceUri(space) {
|
|
|
5318
5548
|
}
|
|
5319
5549
|
return space;
|
|
5320
5550
|
}
|
|
5321
|
-
function buildSecretSetupUrl(reference) {
|
|
5322
|
-
const parameters = [
|
|
5323
|
-
["secret", reference.name],
|
|
5324
|
-
...reference.scope === void 0 ? [] : [["scope", reference.scope]],
|
|
5325
|
-
["space", reference.space]
|
|
5326
|
-
];
|
|
5327
|
-
const query = parameters.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join("&");
|
|
5328
|
-
return `${SECRET_MANAGER_BASE_URL}/app?${query}`;
|
|
5329
|
-
}
|
|
5330
5551
|
function canonicalizeSecretScope(scope) {
|
|
5331
5552
|
if (scope === void 0 || scope === "") return void 0;
|
|
5332
5553
|
const trimmed = scope.trim();
|
|
@@ -5354,14 +5575,13 @@ function invalidSecretInput() {
|
|
|
5354
5575
|
operationError("INPUT_INVALID", "The secret reference is invalid.")
|
|
5355
5576
|
);
|
|
5356
5577
|
}
|
|
5357
|
-
var nodeSdk, DEFAULT_SECRETS_SPACE,
|
|
5578
|
+
var nodeSdk, DEFAULT_SECRETS_SPACE, SECRET_NAME_RE2, RESERVED_SECRET_SCOPES;
|
|
5358
5579
|
var init_secrets = __esm({
|
|
5359
5580
|
"src/secrets.ts"() {
|
|
5360
5581
|
"use strict";
|
|
5361
5582
|
nodeSdk = __toESM(require("@tinycloud/node-sdk"), 1);
|
|
5362
5583
|
init_errors2();
|
|
5363
5584
|
DEFAULT_SECRETS_SPACE = "secrets";
|
|
5364
|
-
SECRET_MANAGER_BASE_URL = "https://secrets.tinycloud.xyz";
|
|
5365
5585
|
SECRET_NAME_RE2 = /^[A-Z][A-Z0-9_]*$/;
|
|
5366
5586
|
RESERVED_SECRET_SCOPES = /* @__PURE__ */ new Set(["default", "global"]);
|
|
5367
5587
|
}
|
|
@@ -5516,7 +5736,10 @@ async function importRequestBoundDelegation(context, input) {
|
|
|
5516
5736
|
delegation,
|
|
5517
5737
|
{ host }
|
|
5518
5738
|
);
|
|
5519
|
-
} catch {
|
|
5739
|
+
} catch (error) {
|
|
5740
|
+
if (typeof error === "object" && error !== null && "reason" in error && error.reason === "expired") {
|
|
5741
|
+
return expiredDelegation();
|
|
5742
|
+
}
|
|
5520
5743
|
return operationFailure(
|
|
5521
5744
|
"DELEGATION_REJECTED",
|
|
5522
5745
|
"The delegation was rejected by the TinyCloud runtime."
|
|
@@ -5534,55 +5757,67 @@ async function importRequestBoundDelegation(context, input) {
|
|
|
5534
5757
|
);
|
|
5535
5758
|
}
|
|
5536
5759
|
try {
|
|
5537
|
-
return await
|
|
5538
|
-
context.summary.profile
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
return {
|
|
5545
|
-
|
|
5546
|
-
|
|
5760
|
+
return await withProfileLock(context.summary.profile, async () => {
|
|
5761
|
+
const previousRecords = await readAdditionalDelegations(context.summary.profile);
|
|
5762
|
+
const result = await updateProfileStore(
|
|
5763
|
+
context.summary.profile,
|
|
5764
|
+
"additional-delegations",
|
|
5765
|
+
async (records) => {
|
|
5766
|
+
const state = await importState(context, input.requestId);
|
|
5767
|
+
if (!state.ok) return { records, result: state.result };
|
|
5768
|
+
if (state.sessionDid !== sessionDid || normalizeHost(state.host) !== normalizeHost(host)) {
|
|
5769
|
+
return {
|
|
5770
|
+
records,
|
|
5771
|
+
result: audienceMismatch(state.sessionDid, sessionDid)
|
|
5772
|
+
};
|
|
5773
|
+
}
|
|
5774
|
+
if (state.request.profile !== context.summary.profile || !samePrincipal(state.request.sessionDid, sessionDid) || normalizeHost(state.request.host) !== normalizeHost(host) || JSON.stringify(canonicalizeOperationCapabilities(state.request.requested, resolveSpace2)) !== JSON.stringify(canonicalizeOperationCapabilities(request.requested, resolveSpace2))) {
|
|
5775
|
+
return {
|
|
5776
|
+
records,
|
|
5777
|
+
result: operationFailure(
|
|
5778
|
+
"DELEGATION_ARTIFACT_INVALID",
|
|
5779
|
+
"The delegation does not reference a current stored request."
|
|
5780
|
+
)
|
|
5781
|
+
};
|
|
5782
|
+
}
|
|
5783
|
+
const record = {
|
|
5784
|
+
delegation: activated.delegation,
|
|
5785
|
+
permissions: activated.effectivePermissions,
|
|
5786
|
+
authorityRequest: {
|
|
5787
|
+
requestId: request.requestId,
|
|
5788
|
+
requested: request.requested
|
|
5789
|
+
}
|
|
5547
5790
|
};
|
|
5548
|
-
|
|
5549
|
-
|
|
5791
|
+
const position = records.findIndex((entry) => delegationCid(entry) === activated.cid);
|
|
5792
|
+
const alreadyPresent = position !== -1;
|
|
5793
|
+
const others = records.filter((entry) => delegationCid(entry) !== activated.cid);
|
|
5550
5794
|
return {
|
|
5551
|
-
records,
|
|
5552
|
-
result:
|
|
5553
|
-
"
|
|
5554
|
-
|
|
5555
|
-
|
|
5795
|
+
records: alreadyPresent ? [...others.slice(0, position), record, ...others.slice(position)] : [...records, record],
|
|
5796
|
+
result: {
|
|
5797
|
+
status: "ok",
|
|
5798
|
+
output: {
|
|
5799
|
+
cid: activated.cid,
|
|
5800
|
+
effectivePermissions,
|
|
5801
|
+
expiry: activated.expiry.toISOString(),
|
|
5802
|
+
audience: activated.audience,
|
|
5803
|
+
host: activated.host,
|
|
5804
|
+
activated: true,
|
|
5805
|
+
alreadyPresent
|
|
5806
|
+
}
|
|
5807
|
+
}
|
|
5556
5808
|
};
|
|
5557
5809
|
}
|
|
5558
|
-
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
const position = records.findIndex((entry) => delegationCid(entry) === activated.cid);
|
|
5567
|
-
const alreadyPresent = position !== -1;
|
|
5568
|
-
const others = records.filter((entry) => delegationCid(entry) !== activated.cid);
|
|
5569
|
-
return {
|
|
5570
|
-
records: alreadyPresent ? [...others.slice(0, position), record, ...others.slice(position)] : [...records, record],
|
|
5571
|
-
result: {
|
|
5572
|
-
status: "ok",
|
|
5573
|
-
output: {
|
|
5574
|
-
cid: activated.cid,
|
|
5575
|
-
effectivePermissions,
|
|
5576
|
-
expiry: activated.expiry.toISOString(),
|
|
5577
|
-
audience: activated.audience,
|
|
5578
|
-
host: activated.host,
|
|
5579
|
-
activated: true,
|
|
5580
|
-
alreadyPresent
|
|
5581
|
-
}
|
|
5582
|
-
}
|
|
5583
|
-
};
|
|
5810
|
+
);
|
|
5811
|
+
if (activated.expiry.getTime() <= Date.now()) {
|
|
5812
|
+
await updateProfileStore(
|
|
5813
|
+
context.summary.profile,
|
|
5814
|
+
"additional-delegations",
|
|
5815
|
+
() => ({ records: previousRecords, result: void 0 })
|
|
5816
|
+
);
|
|
5817
|
+
return expiredDelegation();
|
|
5584
5818
|
}
|
|
5585
|
-
|
|
5819
|
+
return result;
|
|
5820
|
+
});
|
|
5586
5821
|
} catch {
|
|
5587
5822
|
return internalImportFailure();
|
|
5588
5823
|
}
|
|
@@ -5831,6 +6066,33 @@ var init_auth = __esm({
|
|
|
5831
6066
|
}
|
|
5832
6067
|
});
|
|
5833
6068
|
|
|
6069
|
+
// src/kv-failure.ts
|
|
6070
|
+
function kvRefusal(error) {
|
|
6071
|
+
const verdict = (0, import_sdk_services2.authorizationVerdictOf)(error);
|
|
6072
|
+
if (verdict === "unauthenticated") {
|
|
6073
|
+
return (0, import_sdk_services2.validatedCapabilityOf)(error) === void 0 ? "auth_required" : "permission_denied";
|
|
6074
|
+
}
|
|
6075
|
+
return verdict === "forbidden" ? "permission_denied" : void 0;
|
|
6076
|
+
}
|
|
6077
|
+
function authorizationFailure(error, action) {
|
|
6078
|
+
switch (kvRefusal(error)) {
|
|
6079
|
+
case "auth_required":
|
|
6080
|
+
return { status: "error", error: operationError("AUTH_REQUIRED", `Authentication is required to ${action}.`) };
|
|
6081
|
+
case "permission_denied":
|
|
6082
|
+
return { status: "error", error: operationError("PERMISSION_DENIED", `Permission is denied to ${action}.`) };
|
|
6083
|
+
case void 0:
|
|
6084
|
+
return void 0;
|
|
6085
|
+
}
|
|
6086
|
+
}
|
|
6087
|
+
var import_sdk_services2;
|
|
6088
|
+
var init_kv_failure = __esm({
|
|
6089
|
+
"src/kv-failure.ts"() {
|
|
6090
|
+
"use strict";
|
|
6091
|
+
import_sdk_services2 = require("@tinycloud/sdk-services");
|
|
6092
|
+
init_errors2();
|
|
6093
|
+
}
|
|
6094
|
+
});
|
|
6095
|
+
|
|
5834
6096
|
// src/operations/exploration.ts
|
|
5835
6097
|
async function planAccountRegistryRead(context, path) {
|
|
5836
6098
|
return [{
|
|
@@ -6198,19 +6460,6 @@ function caughtKvFailure(error, action) {
|
|
|
6198
6460
|
if (error instanceof OperationInvocationError) return { status: "error", error: error.operationError };
|
|
6199
6461
|
return authorizationFailure(error, action) ?? nodeFailure(action);
|
|
6200
6462
|
}
|
|
6201
|
-
function authorizationFailure(error, action) {
|
|
6202
|
-
const verdict = (0, import_sdk_services2.authorizationVerdictOf)(error);
|
|
6203
|
-
if (verdict === "unauthenticated") {
|
|
6204
|
-
if ((0, import_sdk_services2.validatedCapabilityOf)(error) !== void 0) {
|
|
6205
|
-
return { status: "error", error: operationError("PERMISSION_DENIED", `Permission is denied to ${action}.`) };
|
|
6206
|
-
}
|
|
6207
|
-
return { status: "error", error: operationError("AUTH_REQUIRED", `Authentication is required to ${action}.`) };
|
|
6208
|
-
}
|
|
6209
|
-
if (verdict === "forbidden") {
|
|
6210
|
-
return { status: "error", error: operationError("PERMISSION_DENIED", `Permission is denied to ${action}.`) };
|
|
6211
|
-
}
|
|
6212
|
-
return void 0;
|
|
6213
|
-
}
|
|
6214
6463
|
function accountRegistryOutput(output) {
|
|
6215
6464
|
if (Buffer.byteLength(JSON.stringify(output), "utf8") > MAX_ACCOUNT_REGISTRY_BYTES) {
|
|
6216
6465
|
return {
|
|
@@ -6259,11 +6508,11 @@ function nodeFailure(action) {
|
|
|
6259
6508
|
)
|
|
6260
6509
|
};
|
|
6261
6510
|
}
|
|
6262
|
-
var
|
|
6511
|
+
var import_zod4, MAX_KV_VALUE_BYTES, DEFAULT_KV_LIST_LIMIT, MAX_ACCOUNT_REGISTRY_RECORDS, MAX_ACCOUNT_REGISTRY_BYTES, EmptyInputSchema2, SpaceSchema, PrefixSchema, KeySchema, LimitSchema, StrongEtagSchema, ContentTypeSchema, JsonNumberSchema, JsonValueSchema, KvMetadataSchema, AccountSpaceSchema, AccountSpacesOutputSchema, AccountApplicationSchema, AccountApplicationsOutputSchema, KvListInputSchema, KvListOutputSchema, KvGetInputSchema, KvGetOutputSchema, KvPutContentSchema, KvPutCommonSchema, KvPutInputSchema, KvPutOutputSchema, KvHeadInputSchema, KvHeadOutputSchema, KvDeleteInputSchema, KvDeleteOutputSchema, AUTHENTICATED_POSTURES, EXPLORATION_EXPOSURE, ACCOUNT_SPACES_SENSITIVITY, ACCOUNT_APPLICATIONS_SENSITIVITY, KV_LIST_SENSITIVITY, KV_GET_SENSITIVITY, KV_PUT_SENSITIVITY, KV_KEY_SENSITIVITY, explorationOperationDefinitions;
|
|
6263
6512
|
var init_exploration = __esm({
|
|
6264
6513
|
"src/operations/exploration.ts"() {
|
|
6265
6514
|
"use strict";
|
|
6266
|
-
|
|
6515
|
+
init_kv_failure();
|
|
6267
6516
|
import_zod4 = require("zod");
|
|
6268
6517
|
init_errors2();
|
|
6269
6518
|
init_secrets();
|
|
@@ -6565,6 +6814,191 @@ var init_exploration = __esm({
|
|
|
6565
6814
|
}
|
|
6566
6815
|
});
|
|
6567
6816
|
|
|
6817
|
+
// src/secret-request.ts
|
|
6818
|
+
function canonicalSecretRequestOwner(did) {
|
|
6819
|
+
const match = typeof did === "string" ? OWNER_RE.exec(did) : null;
|
|
6820
|
+
if (match === null || !Number.isSafeInteger(Number(match[1]))) throw invalidOwner();
|
|
6821
|
+
const address = match[2];
|
|
6822
|
+
let checksummed;
|
|
6823
|
+
try {
|
|
6824
|
+
checksummed = nodeSdk3.canonicalizeAddress(address);
|
|
6825
|
+
} catch {
|
|
6826
|
+
throw invalidOwner();
|
|
6827
|
+
}
|
|
6828
|
+
if (/[A-F]/.test(address.slice(2)) && address !== checksummed) throw invalidOwner();
|
|
6829
|
+
return `did:pkh:eip155:${match[1]}:${checksummed}`;
|
|
6830
|
+
}
|
|
6831
|
+
function buildSecretRequestUrl(input) {
|
|
6832
|
+
const owner = canonicalSecretRequestOwner(input.owner);
|
|
6833
|
+
const names = validSecretNames(input.names);
|
|
6834
|
+
if (input.scope !== void 0) assertSecretRequestScope(input.scope);
|
|
6835
|
+
const requester = input.requester;
|
|
6836
|
+
if (requester !== void 0 && (typeof requester !== "string" || requester.length === 0 || requester.length > MAX_REQUESTER_LENGTH || CONTROL_OR_FORMAT.test(requester))) {
|
|
6837
|
+
throw new SecretRequestInputError("INVALID_REQUESTER", "The requester label must be 1-64 characters with no control or format characters.");
|
|
6838
|
+
}
|
|
6839
|
+
if (input.returnTo !== void 0 && !isAllowedReturnTo(input.returnTo)) {
|
|
6840
|
+
throw new SecretRequestInputError("INVALID_RETURN_TO", "The return address is not an allowed Secret Manager callback.");
|
|
6841
|
+
}
|
|
6842
|
+
if (input.state !== void 0 && !STATE_RE.test(input.state)) {
|
|
6843
|
+
throw new SecretRequestInputError("INVALID_STATE", "The state must be 1-128 characters of A-Z, a-z, 0-9, '.', '_', '~' or '-'.");
|
|
6844
|
+
}
|
|
6845
|
+
const parameters = [
|
|
6846
|
+
["owner", owner],
|
|
6847
|
+
...names.map((name) => ["name", name]),
|
|
6848
|
+
...input.scope === void 0 ? [] : [["scope", input.scope]],
|
|
6849
|
+
...input.requester === void 0 ? [] : [["requester", input.requester]],
|
|
6850
|
+
...input.returnTo === void 0 ? [] : [["return_to", input.returnTo]],
|
|
6851
|
+
...input.state === void 0 ? [] : [["state", input.state]],
|
|
6852
|
+
["v", "1"]
|
|
6853
|
+
];
|
|
6854
|
+
const url = `${SECRET_MANAGER_ORIGIN}/request?${parameters.map(([key, value]) => `${key}=${encodeURIComponent(value)}`).join("&")}`;
|
|
6855
|
+
if (url.length > SECRET_REQUEST_MAX_URL_LENGTH) {
|
|
6856
|
+
throw new SecretRequestInputError("REQUEST_LINK_TOO_LONG", `The request link is longer than ${SECRET_REQUEST_MAX_URL_LENGTH} characters.`);
|
|
6857
|
+
}
|
|
6858
|
+
return { url, owner, names, ...input.scope === void 0 ? {} : { scope: input.scope } };
|
|
6859
|
+
}
|
|
6860
|
+
function secretSetupFor(link2) {
|
|
6861
|
+
return {
|
|
6862
|
+
url: link2.url,
|
|
6863
|
+
names: [...link2.names],
|
|
6864
|
+
...link2.scope === void 0 ? {} : { scope: link2.scope },
|
|
6865
|
+
owner: link2.owner,
|
|
6866
|
+
action: "add_secret"
|
|
6867
|
+
};
|
|
6868
|
+
}
|
|
6869
|
+
function resolveSecretsOwner(profile, requestedOwner) {
|
|
6870
|
+
const resolved = profile.local ? ownerOrUndefined(profile.principalDid) : ownerOrUndefined(profile.ownerDid) ?? (profile.posture === "delegate-session" ? ownerOfSpace(profile.space) : void 0) ?? ownerOrUndefined(profile.principalDid);
|
|
6871
|
+
const requested = requestedOwner === void 0 ? void 0 : ownerOrUndefined(requestedOwner);
|
|
6872
|
+
if (resolved !== void 0) {
|
|
6873
|
+
if (requestedOwner !== void 0 && requested !== resolved) {
|
|
6874
|
+
return {
|
|
6875
|
+
ok: false,
|
|
6876
|
+
code: "OWNER_MISMATCH",
|
|
6877
|
+
message: "The requested owner is not the owner of this identity's secrets."
|
|
6878
|
+
};
|
|
6879
|
+
}
|
|
6880
|
+
return { ok: true, owner: resolved, local: profile.local };
|
|
6881
|
+
}
|
|
6882
|
+
if (requested !== void 0 && !profile.local) return { ok: true, owner: requested, local: false };
|
|
6883
|
+
return {
|
|
6884
|
+
ok: false,
|
|
6885
|
+
code: "OWNER_DID_UNKNOWN",
|
|
6886
|
+
message: "The owner of this identity's secrets is unknown. Pass the owner's did:pkh with --owner."
|
|
6887
|
+
};
|
|
6888
|
+
}
|
|
6889
|
+
function missingSecretRecovery(input) {
|
|
6890
|
+
if (input.local) {
|
|
6891
|
+
return { kind: "put_locally", hint: `Store it with: ${putCommands(input.names, input.scope)}` };
|
|
6892
|
+
}
|
|
6893
|
+
if (input.owner !== void 0) {
|
|
6894
|
+
try {
|
|
6895
|
+
return {
|
|
6896
|
+
kind: "add_secret",
|
|
6897
|
+
setup: secretSetupFor(buildSecretRequestUrl({
|
|
6898
|
+
owner: input.owner,
|
|
6899
|
+
names: input.names,
|
|
6900
|
+
...input.scope === void 0 ? {} : { scope: input.scope }
|
|
6901
|
+
}))
|
|
6902
|
+
};
|
|
6903
|
+
} catch {
|
|
6904
|
+
}
|
|
6905
|
+
}
|
|
6906
|
+
return {
|
|
6907
|
+
kind: "not_requestable",
|
|
6908
|
+
hint: `No Secret Manager request link can express this secret. The owner can store it with: ${putCommands(input.names, input.scope)}`
|
|
6909
|
+
};
|
|
6910
|
+
}
|
|
6911
|
+
function isValidSecretRequestScope(scope) {
|
|
6912
|
+
return scope.length <= MAX_SCOPE_LENGTH && SCOPE_RE.test(scope) && !RESERVED_SCOPES.includes(scope);
|
|
6913
|
+
}
|
|
6914
|
+
function isValidSecretName(name) {
|
|
6915
|
+
return name.length <= MAX_NAME_LENGTH && NAME_RE.test(name);
|
|
6916
|
+
}
|
|
6917
|
+
function validSecretNames(names) {
|
|
6918
|
+
for (const name of names) assertSecretName(name);
|
|
6919
|
+
const unique = dedupe(names);
|
|
6920
|
+
if (unique.length === 0) {
|
|
6921
|
+
throw new SecretRequestInputError("INVALID_SECRET_NAME", "At least one secret name is required.");
|
|
6922
|
+
}
|
|
6923
|
+
if (unique.length > SECRET_REQUEST_MAX_NAMES) {
|
|
6924
|
+
throw new SecretRequestInputError("TOO_MANY_SECRET_NAMES", `At most ${SECRET_REQUEST_MAX_NAMES} secret names can be requested at once.`);
|
|
6925
|
+
}
|
|
6926
|
+
return unique;
|
|
6927
|
+
}
|
|
6928
|
+
function assertSecretName(name) {
|
|
6929
|
+
if (typeof name !== "string" || !isValidSecretName(name)) {
|
|
6930
|
+
throw new SecretRequestInputError("INVALID_SECRET_NAME", "Secret names are 1-64 characters of A-Z, 0-9 and '_', starting with a letter.");
|
|
6931
|
+
}
|
|
6932
|
+
}
|
|
6933
|
+
function assertSecretRequestScope(scope) {
|
|
6934
|
+
if (typeof scope !== "string" || !isValidSecretRequestScope(scope)) {
|
|
6935
|
+
throw new SecretRequestInputError("INVALID_SECRET_SCOPE", "The scope must be a canonical scope of at most 64 characters, not default, global or scoped.");
|
|
6936
|
+
}
|
|
6937
|
+
}
|
|
6938
|
+
function isAllowedReturnTo(returnTo) {
|
|
6939
|
+
if (typeof returnTo !== "string" || returnTo.length > MAX_RETURN_TO_LENGTH) return false;
|
|
6940
|
+
let url;
|
|
6941
|
+
try {
|
|
6942
|
+
url = new URL(returnTo);
|
|
6943
|
+
} catch {
|
|
6944
|
+
return false;
|
|
6945
|
+
}
|
|
6946
|
+
if (url.username !== "" || url.password !== "") return false;
|
|
6947
|
+
return RETURN_TO_ORIGINS.includes(url.origin) || RETURN_TO_PREVIEW.test(url.origin);
|
|
6948
|
+
}
|
|
6949
|
+
function ownerOrUndefined(did) {
|
|
6950
|
+
if (did === void 0) return void 0;
|
|
6951
|
+
try {
|
|
6952
|
+
return canonicalSecretRequestOwner(did);
|
|
6953
|
+
} catch {
|
|
6954
|
+
return void 0;
|
|
6955
|
+
}
|
|
6956
|
+
}
|
|
6957
|
+
function ownerOfSpace(space) {
|
|
6958
|
+
const match = space === void 0 ? null : PKH_SPACE_RE.exec(space);
|
|
6959
|
+
if (match === null) return void 0;
|
|
6960
|
+
return ownerOrUndefined(`did:pkh:eip155:${match[1]}:${match[2].toLowerCase()}`);
|
|
6961
|
+
}
|
|
6962
|
+
function putCommands(names, scope) {
|
|
6963
|
+
return dedupe(names).map((name) => `tc secrets put ${name}${scope === void 0 ? "" : ` --scope ${scope}`} --stdin`).join("; ");
|
|
6964
|
+
}
|
|
6965
|
+
function dedupe(names) {
|
|
6966
|
+
return [...new Set(names)];
|
|
6967
|
+
}
|
|
6968
|
+
function invalidOwner() {
|
|
6969
|
+
return new SecretRequestInputError("INVALID_OWNER", "The owner must be a did:pkh:eip155 account with a valid EIP-55 address.");
|
|
6970
|
+
}
|
|
6971
|
+
var nodeSdk3, 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;
|
|
6972
|
+
var init_secret_request = __esm({
|
|
6973
|
+
"src/secret-request.ts"() {
|
|
6974
|
+
"use strict";
|
|
6975
|
+
nodeSdk3 = __toESM(require("@tinycloud/node-sdk"), 1);
|
|
6976
|
+
SECRET_MANAGER_ORIGIN = "https://secrets.tinycloud.xyz";
|
|
6977
|
+
SECRET_REQUEST_MAX_NAMES = 10;
|
|
6978
|
+
SECRET_REQUEST_MAX_URL_LENGTH = 8192;
|
|
6979
|
+
OWNER_RE = /^did:pkh:eip155:([1-9][0-9]*):(0x[0-9a-fA-F]{40})$/;
|
|
6980
|
+
NAME_RE = /^[A-Z][A-Z0-9_]*$/;
|
|
6981
|
+
MAX_NAME_LENGTH = 64;
|
|
6982
|
+
SCOPE_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
6983
|
+
MAX_SCOPE_LENGTH = 64;
|
|
6984
|
+
RESERVED_SCOPES = ["default", "global", "scoped"];
|
|
6985
|
+
MAX_REQUESTER_LENGTH = 64;
|
|
6986
|
+
CONTROL_OR_FORMAT = /[\p{Cc}\p{Cf}]/u;
|
|
6987
|
+
STATE_RE = /^[A-Za-z0-9._~-]{1,128}$/;
|
|
6988
|
+
MAX_RETURN_TO_LENGTH = 2048;
|
|
6989
|
+
RETURN_TO_ORIGINS = ["https://feed.tinycloud.xyz"];
|
|
6990
|
+
RETURN_TO_PREVIEW = /^https:\/\/[a-z0-9-]+\.tinyfeed\.pages\.dev$/;
|
|
6991
|
+
PKH_SPACE_RE = /^tinycloud:pkh:eip155:([1-9][0-9]*):(0x[0-9a-fA-F]{40}):[^:]+$/;
|
|
6992
|
+
SecretRequestInputError = class extends Error {
|
|
6993
|
+
constructor(code, message) {
|
|
6994
|
+
super(message);
|
|
6995
|
+
this.name = "SecretRequestInputError";
|
|
6996
|
+
this.code = code;
|
|
6997
|
+
}
|
|
6998
|
+
};
|
|
6999
|
+
}
|
|
7000
|
+
});
|
|
7001
|
+
|
|
6568
7002
|
// src/operations/secrets-get.ts
|
|
6569
7003
|
async function planSecretGet(context, input) {
|
|
6570
7004
|
const reference = resolveSecretReferenceForOperation(
|
|
@@ -6631,27 +7065,14 @@ async function executeSecretGet(context, input) {
|
|
|
6631
7065
|
)
|
|
6632
7066
|
};
|
|
6633
7067
|
}
|
|
6634
|
-
return classifySecretRead(reference, result);
|
|
7068
|
+
return classifySecretRead(reference, result, context.runtime?.localOwner === true);
|
|
6635
7069
|
}
|
|
6636
|
-
function classifySecretRead(reference, result) {
|
|
7070
|
+
function classifySecretRead(reference, result, localOwner) {
|
|
6637
7071
|
switch (result.status) {
|
|
6638
7072
|
case "ok":
|
|
6639
7073
|
return { status: "ok", output: { value: result.value } };
|
|
6640
7074
|
case "not_found":
|
|
6641
|
-
return
|
|
6642
|
-
status: "setup_required",
|
|
6643
|
-
setup: {
|
|
6644
|
-
kind: "secret_manager",
|
|
6645
|
-
secret: {
|
|
6646
|
-
name: reference.name,
|
|
6647
|
-
...reference.scope === void 0 ? {} : { scope: reference.scope },
|
|
6648
|
-
space: reference.space
|
|
6649
|
-
},
|
|
6650
|
-
url: buildSecretSetupUrl(reference),
|
|
6651
|
-
message: "Enter this secret in Secret Manager, then retry the operation."
|
|
6652
|
-
},
|
|
6653
|
-
requiresCallerInput: true
|
|
6654
|
-
};
|
|
7075
|
+
return missingSecretOutcome(reference, localOwner);
|
|
6655
7076
|
case "permission_required":
|
|
6656
7077
|
return {
|
|
6657
7078
|
status: "authority_required",
|
|
@@ -6696,6 +7117,47 @@ function classifySecretRead(reference, result) {
|
|
|
6696
7117
|
error: operationError("SECRET_READ_FAILED", "The secret read failed.")
|
|
6697
7118
|
};
|
|
6698
7119
|
}
|
|
7120
|
+
function missingSecretOutcome(reference, localOwner) {
|
|
7121
|
+
const owner = ownerOfSpace(reference.space);
|
|
7122
|
+
const recovery = missingSecretRecovery({
|
|
7123
|
+
owner,
|
|
7124
|
+
local: localOwner,
|
|
7125
|
+
names: [reference.name],
|
|
7126
|
+
...reference.scope === void 0 ? {} : { scope: reference.scope }
|
|
7127
|
+
});
|
|
7128
|
+
if (recovery.kind === "add_secret") {
|
|
7129
|
+
const { setup } = recovery;
|
|
7130
|
+
return {
|
|
7131
|
+
status: "setup_required",
|
|
7132
|
+
setup: {
|
|
7133
|
+
kind: "secret_manager",
|
|
7134
|
+
action: setup.action,
|
|
7135
|
+
url: setup.url,
|
|
7136
|
+
names: setup.names,
|
|
7137
|
+
...setup.scope === void 0 ? {} : { scope: setup.scope },
|
|
7138
|
+
owner: setup.owner,
|
|
7139
|
+
secret: {
|
|
7140
|
+
name: reference.name,
|
|
7141
|
+
...reference.scope === void 0 ? {} : { scope: reference.scope },
|
|
7142
|
+
space: reference.space
|
|
7143
|
+
},
|
|
7144
|
+
message: "Ask the owner to add this secret in Secret Manager, then retry the operation."
|
|
7145
|
+
},
|
|
7146
|
+
requiresCallerInput: true
|
|
7147
|
+
};
|
|
7148
|
+
}
|
|
7149
|
+
return {
|
|
7150
|
+
status: "error",
|
|
7151
|
+
error: operationError("KV_NOT_FOUND", recovery.hint, {
|
|
7152
|
+
details: {
|
|
7153
|
+
names: [reference.name],
|
|
7154
|
+
...reference.scope === void 0 ? {} : { scope: reference.scope },
|
|
7155
|
+
...owner === void 0 ? {} : { owner },
|
|
7156
|
+
recoveryKind: recovery.kind
|
|
7157
|
+
}
|
|
7158
|
+
})
|
|
7159
|
+
};
|
|
7160
|
+
}
|
|
6699
7161
|
function resolveEncryptionNetworkId(node, space) {
|
|
6700
7162
|
const candidate = node;
|
|
6701
7163
|
let scoped;
|
|
@@ -6723,6 +7185,7 @@ var init_secrets_get = __esm({
|
|
|
6723
7185
|
"use strict";
|
|
6724
7186
|
import_zod5 = require("zod");
|
|
6725
7187
|
init_errors2();
|
|
7188
|
+
init_secret_request();
|
|
6726
7189
|
init_secrets();
|
|
6727
7190
|
SecretsGetInputSchema = import_zod5.z.object({
|
|
6728
7191
|
name: import_zod5.z.string(),
|
|
@@ -6774,6 +7237,194 @@ var init_secrets_get = __esm({
|
|
|
6774
7237
|
}
|
|
6775
7238
|
});
|
|
6776
7239
|
|
|
7240
|
+
// src/operations/secrets-status.ts
|
|
7241
|
+
async function executeSecretsStatus(context, input) {
|
|
7242
|
+
const runtime = context.runtime;
|
|
7243
|
+
const node = runtime?.node;
|
|
7244
|
+
if (runtime === void 0 || typeof node?.kvForSpace !== "function") {
|
|
7245
|
+
return {
|
|
7246
|
+
status: "error",
|
|
7247
|
+
error: operationError("NODE_ERROR", "The TinyCloud node could not check the secrets.", { retryable: true })
|
|
7248
|
+
};
|
|
7249
|
+
}
|
|
7250
|
+
const local = runtime.localOwner === true;
|
|
7251
|
+
const resolution = resolveSecretsOwner({
|
|
7252
|
+
local,
|
|
7253
|
+
posture: context.summary.posture,
|
|
7254
|
+
...context.summary.ownerDid === void 0 ? {} : { ownerDid: context.summary.ownerDid },
|
|
7255
|
+
...context.summary.principalDid === void 0 ? {} : { principalDid: context.summary.principalDid },
|
|
7256
|
+
...context.summary.space === void 0 ? {} : { space: context.summary.space }
|
|
7257
|
+
}, input.owner);
|
|
7258
|
+
if (!resolution.ok) {
|
|
7259
|
+
return {
|
|
7260
|
+
status: "error",
|
|
7261
|
+
error: operationError("INPUT_INVALID", resolution.message, { details: { reason: resolution.code } })
|
|
7262
|
+
};
|
|
7263
|
+
}
|
|
7264
|
+
const owner = resolution.owner;
|
|
7265
|
+
const skipped2 = runtime.skippedGrantReasons ?? [];
|
|
7266
|
+
const pkh = nodeSdk4.parsePkhDid(owner);
|
|
7267
|
+
const kv = node.kvForSpace(nodeSdk4.makePkhSpaceId(pkh.address, pkh.chainId, "secrets"));
|
|
7268
|
+
const outcomes = await Promise.all(input.names.map(async (name) => {
|
|
7269
|
+
const path = input.scope === void 0 ? `vault/secrets/${name}` : `vault/secrets/scoped/${input.scope}/${name}`;
|
|
7270
|
+
try {
|
|
7271
|
+
const result = await kv.get(path, { raw: true, prefix: "", timeout: PROBE_TIMEOUT_MS });
|
|
7272
|
+
if (result.ok) return "present";
|
|
7273
|
+
const error = result.error;
|
|
7274
|
+
if (typeof error === "object" && error !== null && "code" in error && error.code === "KV_NOT_FOUND") {
|
|
7275
|
+
return "pending";
|
|
7276
|
+
}
|
|
7277
|
+
const refusal = kvRefusal(error);
|
|
7278
|
+
if (refusal === "auth_required") return "auth_required";
|
|
7279
|
+
if (refusal === "permission_denied") return "denied";
|
|
7280
|
+
return "unreachable";
|
|
7281
|
+
} catch {
|
|
7282
|
+
return "unreachable";
|
|
7283
|
+
}
|
|
7284
|
+
}));
|
|
7285
|
+
const namesWith = (outcome) => input.names.filter((_, index) => outcomes[index] === outcome);
|
|
7286
|
+
const present = namesWith("present");
|
|
7287
|
+
const pending = namesWith("pending");
|
|
7288
|
+
const denied = namesWith("denied");
|
|
7289
|
+
if (present.length + pending.length < input.names.length) {
|
|
7290
|
+
if (outcomes.includes("auth_required") || skipped2.includes("activation_unauthenticated")) {
|
|
7291
|
+
return {
|
|
7292
|
+
status: "error",
|
|
7293
|
+
error: operationError(
|
|
7294
|
+
"AUTH_REQUIRED",
|
|
7295
|
+
"The node did not accept this profile's stored session. Sign in again, then retry.",
|
|
7296
|
+
{ retryable: false, details: { recovery: signInRecovery(context.summary.profile, context.summary.posture) } }
|
|
7297
|
+
)
|
|
7298
|
+
};
|
|
7299
|
+
}
|
|
7300
|
+
if (outcomes.includes("unreachable") || skipped2.includes("activation_unavailable")) {
|
|
7301
|
+
return {
|
|
7302
|
+
status: "error",
|
|
7303
|
+
error: operationError("NODE_UNREACHABLE", "The TinyCloud node could not be reached.", { retryable: true })
|
|
7304
|
+
};
|
|
7305
|
+
}
|
|
7306
|
+
}
|
|
7307
|
+
const recovery = pending.length === 0 ? void 0 : missingSecretRecovery({
|
|
7308
|
+
owner,
|
|
7309
|
+
local,
|
|
7310
|
+
names: pending,
|
|
7311
|
+
...input.scope === void 0 ? {} : { scope: input.scope }
|
|
7312
|
+
});
|
|
7313
|
+
return {
|
|
7314
|
+
status: "ok",
|
|
7315
|
+
output: {
|
|
7316
|
+
status: denied.length > 0 ? "consent_required" : pending.length > 0 ? "pending" : "ready",
|
|
7317
|
+
owner,
|
|
7318
|
+
names: [...input.names],
|
|
7319
|
+
...input.scope === void 0 ? {} : { scope: input.scope },
|
|
7320
|
+
present,
|
|
7321
|
+
pending,
|
|
7322
|
+
denied,
|
|
7323
|
+
...recovery === void 0 ? {} : recovery.kind === "add_secret" ? { setup: recovery.setup } : { hint: recovery.hint }
|
|
7324
|
+
}
|
|
7325
|
+
};
|
|
7326
|
+
}
|
|
7327
|
+
var import_zod6, nodeSdk4, PROBE_TIMEOUT_MS, SECRETS_STATUS_DEADLINE_MS, SecretsStatusInputSchema, SecretSetupSchema, SecretsStatusOutputSchema, SECRETS_STATUS_POSTURES, SECRETS_STATUS_EXPOSURE, SECRETS_STATUS_SENSITIVITY, secretsStatusOperationDefinition, secretsStatusOperationDefinitions;
|
|
7328
|
+
var init_secrets_status = __esm({
|
|
7329
|
+
"src/operations/secrets-status.ts"() {
|
|
7330
|
+
"use strict";
|
|
7331
|
+
import_zod6 = require("zod");
|
|
7332
|
+
nodeSdk4 = __toESM(require("@tinycloud/node-sdk"), 1);
|
|
7333
|
+
init_errors2();
|
|
7334
|
+
init_kv_failure();
|
|
7335
|
+
init_recovery();
|
|
7336
|
+
init_secret_request();
|
|
7337
|
+
init_secrets();
|
|
7338
|
+
PROBE_TIMEOUT_MS = 15e3;
|
|
7339
|
+
SECRETS_STATUS_DEADLINE_MS = 18e3;
|
|
7340
|
+
SecretsStatusInputSchema = import_zod6.z.object({
|
|
7341
|
+
names: import_zod6.z.array(import_zod6.z.string()),
|
|
7342
|
+
scope: import_zod6.z.string().optional(),
|
|
7343
|
+
owner: import_zod6.z.string().optional()
|
|
7344
|
+
}).strict().superRefine((input, context) => {
|
|
7345
|
+
try {
|
|
7346
|
+
validSecretNames(input.names);
|
|
7347
|
+
} catch {
|
|
7348
|
+
context.addIssue({ code: import_zod6.z.ZodIssueCode.custom, path: ["names"], message: "Invalid secret names." });
|
|
7349
|
+
}
|
|
7350
|
+
try {
|
|
7351
|
+
const scope = canonicalizeSecretScope(input.scope);
|
|
7352
|
+
if (scope !== void 0 && !isValidSecretRequestScope(scope)) throw new Error("invalid scope");
|
|
7353
|
+
} catch {
|
|
7354
|
+
context.addIssue({ code: import_zod6.z.ZodIssueCode.custom, path: ["scope"], message: "Invalid secret scope." });
|
|
7355
|
+
}
|
|
7356
|
+
if (input.owner !== void 0) {
|
|
7357
|
+
try {
|
|
7358
|
+
canonicalSecretRequestOwner(input.owner);
|
|
7359
|
+
} catch {
|
|
7360
|
+
context.addIssue({ code: import_zod6.z.ZodIssueCode.custom, path: ["owner"], message: "Invalid owner." });
|
|
7361
|
+
}
|
|
7362
|
+
}
|
|
7363
|
+
}).transform((input) => {
|
|
7364
|
+
const scope = canonicalizeSecretScope(input.scope);
|
|
7365
|
+
return {
|
|
7366
|
+
names: validSecretNames(input.names),
|
|
7367
|
+
...scope === void 0 ? {} : { scope },
|
|
7368
|
+
...input.owner === void 0 ? {} : { owner: canonicalSecretRequestOwner(input.owner) }
|
|
7369
|
+
};
|
|
7370
|
+
});
|
|
7371
|
+
SecretSetupSchema = import_zod6.z.object({
|
|
7372
|
+
url: import_zod6.z.string().url(),
|
|
7373
|
+
names: import_zod6.z.array(import_zod6.z.string()).min(1),
|
|
7374
|
+
scope: import_zod6.z.string().optional(),
|
|
7375
|
+
owner: import_zod6.z.string(),
|
|
7376
|
+
action: import_zod6.z.literal("add_secret")
|
|
7377
|
+
}).strict();
|
|
7378
|
+
SecretsStatusOutputSchema = import_zod6.z.object({
|
|
7379
|
+
status: import_zod6.z.enum(["ready", "pending", "consent_required"]),
|
|
7380
|
+
owner: import_zod6.z.string(),
|
|
7381
|
+
names: import_zod6.z.array(import_zod6.z.string()).min(1),
|
|
7382
|
+
scope: import_zod6.z.string().optional(),
|
|
7383
|
+
present: import_zod6.z.array(import_zod6.z.string()),
|
|
7384
|
+
pending: import_zod6.z.array(import_zod6.z.string()),
|
|
7385
|
+
denied: import_zod6.z.array(import_zod6.z.string()),
|
|
7386
|
+
setup: SecretSetupSchema.optional(),
|
|
7387
|
+
hint: import_zod6.z.string().optional()
|
|
7388
|
+
}).strict();
|
|
7389
|
+
SECRETS_STATUS_POSTURES = [
|
|
7390
|
+
"owner-openkey",
|
|
7391
|
+
"delegate-session",
|
|
7392
|
+
"local-owner-key"
|
|
7393
|
+
];
|
|
7394
|
+
SECRETS_STATUS_EXPOSURE = {
|
|
7395
|
+
cli: { status: "required" },
|
|
7396
|
+
mcp: { status: "required" },
|
|
7397
|
+
skill: { status: "required" },
|
|
7398
|
+
docs: { status: "required" }
|
|
7399
|
+
};
|
|
7400
|
+
SECRETS_STATUS_SENSITIVITY = {
|
|
7401
|
+
input: [],
|
|
7402
|
+
output: []
|
|
7403
|
+
};
|
|
7404
|
+
secretsStatusOperationDefinition = {
|
|
7405
|
+
id: "tinycloud.secrets.status",
|
|
7406
|
+
version: 1,
|
|
7407
|
+
title: "Check whether TinyCloud secrets exist",
|
|
7408
|
+
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.",
|
|
7409
|
+
input: SecretsStatusInputSchema,
|
|
7410
|
+
output: SecretsStatusOutputSchema,
|
|
7411
|
+
effects: ["read"],
|
|
7412
|
+
runtime: "restored",
|
|
7413
|
+
postures: SECRETS_STATUS_POSTURES,
|
|
7414
|
+
exposure: SECRETS_STATUS_EXPOSURE,
|
|
7415
|
+
sensitivity: SECRETS_STATUS_SENSITIVITY,
|
|
7416
|
+
deadlineMs: SECRETS_STATUS_DEADLINE_MS,
|
|
7417
|
+
// Never evaluated against the session: a missing grant is reported as
|
|
7418
|
+
// `consent_required`, never saved as an authority request.
|
|
7419
|
+
authority: async () => [],
|
|
7420
|
+
execute: executeSecretsStatus
|
|
7421
|
+
};
|
|
7422
|
+
secretsStatusOperationDefinitions = [
|
|
7423
|
+
secretsStatusOperationDefinition
|
|
7424
|
+
];
|
|
7425
|
+
}
|
|
7426
|
+
});
|
|
7427
|
+
|
|
6777
7428
|
// src/operations/sql.ts
|
|
6778
7429
|
async function planSqlRead(context, input) {
|
|
6779
7430
|
const space = resolveSqlSpace(context, input.space);
|
|
@@ -7086,15 +7737,15 @@ function isByte(value) {
|
|
|
7086
7737
|
function isRecord3(value) {
|
|
7087
7738
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7088
7739
|
}
|
|
7089
|
-
var import_node_sql_parser,
|
|
7740
|
+
var import_node_sql_parser, import_zod7, DEFAULT_MAX_ROWS, MAX_MAX_ROWS, DEFAULT_MAX_BYTES, MAX_MAX_BYTES, SCHEMA_MAX_ROWS, SCHEMA_MAX_BYTES, MAX_PARAMETERS, MAX_SQL_LENGTH, SQL_WRITE_AUTHORITY_NOTICE, SCHEMA_QUERY, sqlParser, SpaceSchema2, DatabaseSchema, SafeNumberSchema, Base64Schema, SqlInputValueSchema, SqlParamsSchema, RequiredSqlParamsSchema, SqlOutputValueSchema, SqlTargetInputSchema, SqlQueryInputSchema, SqlExecuteInputSchema, SqlLimitsOutputSchema, SqlSchemaInspectOutputSchema, SqlQueryOutputSchema, SqlExecuteOutputSchema, AUTHENTICATED_POSTURES2, SQL_EXPOSURE, SQL_SCHEMA_SENSITIVITY, SQL_QUERY_SENSITIVITY, SQL_EXECUTE_SENSITIVITY, sqlOperationDefinitions;
|
|
7090
7741
|
var init_sql = __esm({
|
|
7091
7742
|
"src/operations/sql.ts"() {
|
|
7092
7743
|
"use strict";
|
|
7093
7744
|
import_node_sql_parser = __toESM(require("node-sql-parser"), 1);
|
|
7094
|
-
|
|
7745
|
+
import_zod7 = require("zod");
|
|
7095
7746
|
init_errors2();
|
|
7096
7747
|
init_secrets();
|
|
7097
|
-
|
|
7748
|
+
init_kv_failure();
|
|
7098
7749
|
DEFAULT_MAX_ROWS = 100;
|
|
7099
7750
|
MAX_MAX_ROWS = 1e3;
|
|
7100
7751
|
DEFAULT_MAX_BYTES = 1024 * 1024;
|
|
@@ -7110,29 +7761,29 @@ WHERE type IN ('table', 'view', 'index', 'trigger')
|
|
|
7110
7761
|
AND name NOT LIKE 'sqlite_%'
|
|
7111
7762
|
ORDER BY type, name`;
|
|
7112
7763
|
sqlParser = new import_node_sql_parser.default.Parser();
|
|
7113
|
-
SpaceSchema2 =
|
|
7764
|
+
SpaceSchema2 = import_zod7.z.string().min(1).refine(
|
|
7114
7765
|
(space) => (/^[A-Za-z0-9_-]+$/.test(space) || space.startsWith("tinycloud:")) && !space.includes("*"),
|
|
7115
7766
|
"Invalid TinyCloud space."
|
|
7116
7767
|
);
|
|
7117
|
-
DatabaseSchema =
|
|
7768
|
+
DatabaseSchema = import_zod7.z.string().min(1).max(256).refine(
|
|
7118
7769
|
(database) => database !== "/" && !database.endsWith("/") && !database.includes("*") && !/[\u0000-\u001f\u007f]/.test(database),
|
|
7119
7770
|
"Invalid SQLite database name."
|
|
7120
7771
|
);
|
|
7121
|
-
SafeNumberSchema =
|
|
7772
|
+
SafeNumberSchema = import_zod7.z.number().finite().refine(
|
|
7122
7773
|
(value) => !Number.isInteger(value) || Number.isSafeInteger(value),
|
|
7123
7774
|
"SQLite integer parameters must be JavaScript safe integers."
|
|
7124
7775
|
);
|
|
7125
|
-
Base64Schema =
|
|
7776
|
+
Base64Schema = import_zod7.z.string().max(Math.ceil(MAX_MAX_BYTES / 3) * 4).refine(
|
|
7126
7777
|
isCanonicalBase642,
|
|
7127
7778
|
"Invalid base64 BLOB value."
|
|
7128
7779
|
);
|
|
7129
|
-
SqlInputValueSchema =
|
|
7130
|
-
|
|
7780
|
+
SqlInputValueSchema = import_zod7.z.union([
|
|
7781
|
+
import_zod7.z.null(),
|
|
7131
7782
|
SafeNumberSchema,
|
|
7132
|
-
|
|
7133
|
-
|
|
7783
|
+
import_zod7.z.string().max(MAX_MAX_BYTES),
|
|
7784
|
+
import_zod7.z.object({ type: import_zod7.z.literal("blob"), base64: Base64Schema }).strict()
|
|
7134
7785
|
]);
|
|
7135
|
-
SqlParamsSchema =
|
|
7786
|
+
SqlParamsSchema = import_zod7.z.array(SqlInputValueSchema).max(MAX_PARAMETERS).superRefine(
|
|
7136
7787
|
(params, context) => {
|
|
7137
7788
|
const totalBytes = params.reduce(
|
|
7138
7789
|
(total, value) => total + sqlInputValueBytes(value),
|
|
@@ -7140,13 +7791,13 @@ ORDER BY type, name`;
|
|
|
7140
7791
|
);
|
|
7141
7792
|
if (totalBytes > MAX_MAX_BYTES) {
|
|
7142
7793
|
context.addIssue({
|
|
7143
|
-
code:
|
|
7794
|
+
code: import_zod7.z.ZodIssueCode.custom,
|
|
7144
7795
|
message: "SQLite query parameters exceed the 4 MiB input limit."
|
|
7145
7796
|
});
|
|
7146
7797
|
}
|
|
7147
7798
|
}
|
|
7148
7799
|
);
|
|
7149
|
-
RequiredSqlParamsSchema =
|
|
7800
|
+
RequiredSqlParamsSchema = import_zod7.z.array(SqlInputValueSchema).min(1).max(MAX_PARAMETERS).superRefine(
|
|
7150
7801
|
(params, context) => {
|
|
7151
7802
|
const totalBytes = params.reduce(
|
|
7152
7803
|
(total, value) => total + sqlInputValueBytes(value),
|
|
@@ -7154,88 +7805,88 @@ ORDER BY type, name`;
|
|
|
7154
7805
|
);
|
|
7155
7806
|
if (totalBytes > MAX_MAX_BYTES) {
|
|
7156
7807
|
context.addIssue({
|
|
7157
|
-
code:
|
|
7808
|
+
code: import_zod7.z.ZodIssueCode.custom,
|
|
7158
7809
|
message: "SQLite query parameters exceed the 4 MiB input limit."
|
|
7159
7810
|
});
|
|
7160
7811
|
}
|
|
7161
7812
|
}
|
|
7162
7813
|
);
|
|
7163
|
-
SqlOutputValueSchema =
|
|
7164
|
-
|
|
7814
|
+
SqlOutputValueSchema = import_zod7.z.union([
|
|
7815
|
+
import_zod7.z.null(),
|
|
7165
7816
|
SafeNumberSchema,
|
|
7166
|
-
|
|
7167
|
-
|
|
7168
|
-
type:
|
|
7169
|
-
base64:
|
|
7170
|
-
byteLength:
|
|
7817
|
+
import_zod7.z.string(),
|
|
7818
|
+
import_zod7.z.object({
|
|
7819
|
+
type: import_zod7.z.literal("blob"),
|
|
7820
|
+
base64: import_zod7.z.string(),
|
|
7821
|
+
byteLength: import_zod7.z.number().int().nonnegative()
|
|
7171
7822
|
}).strict()
|
|
7172
7823
|
]);
|
|
7173
|
-
SqlTargetInputSchema =
|
|
7824
|
+
SqlTargetInputSchema = import_zod7.z.object({
|
|
7174
7825
|
space: SpaceSchema2,
|
|
7175
7826
|
database: DatabaseSchema
|
|
7176
7827
|
}).strict();
|
|
7177
|
-
SqlQueryInputSchema =
|
|
7828
|
+
SqlQueryInputSchema = import_zod7.z.object({
|
|
7178
7829
|
space: SpaceSchema2,
|
|
7179
7830
|
database: DatabaseSchema,
|
|
7180
|
-
sql:
|
|
7831
|
+
sql: import_zod7.z.string().min(1).max(MAX_SQL_LENGTH).superRefine((sql, context) => {
|
|
7181
7832
|
if (!isSingleReadQuery(sql)) {
|
|
7182
7833
|
context.addIssue({
|
|
7183
|
-
code:
|
|
7834
|
+
code: import_zod7.z.ZodIssueCode.custom,
|
|
7184
7835
|
message: "SQL must parse as exactly one SQLite SELECT statement."
|
|
7185
7836
|
});
|
|
7186
7837
|
}
|
|
7187
7838
|
}),
|
|
7188
7839
|
params: SqlParamsSchema.optional(),
|
|
7189
|
-
maxRows:
|
|
7190
|
-
maxBytes:
|
|
7840
|
+
maxRows: import_zod7.z.number().int().positive().max(MAX_MAX_ROWS).optional(),
|
|
7841
|
+
maxBytes: import_zod7.z.number().int().positive().max(MAX_MAX_BYTES).optional()
|
|
7191
7842
|
}).strict();
|
|
7192
|
-
SqlExecuteInputSchema =
|
|
7843
|
+
SqlExecuteInputSchema = import_zod7.z.object({
|
|
7193
7844
|
space: SpaceSchema2,
|
|
7194
7845
|
database: DatabaseSchema,
|
|
7195
|
-
sql:
|
|
7846
|
+
sql: import_zod7.z.string().min(1).max(MAX_SQL_LENGTH),
|
|
7196
7847
|
params: RequiredSqlParamsSchema,
|
|
7197
|
-
acknowledgeDatabaseWideAuthority:
|
|
7848
|
+
acknowledgeDatabaseWideAuthority: import_zod7.z.literal(true).describe(SQL_WRITE_AUTHORITY_NOTICE)
|
|
7198
7849
|
}).strict().superRefine((input, context) => {
|
|
7199
7850
|
if (parseSingleDmlStatement(input.sql, input.params.length) === void 0) {
|
|
7200
7851
|
context.addIssue({
|
|
7201
|
-
code:
|
|
7852
|
+
code: import_zod7.z.ZodIssueCode.custom,
|
|
7202
7853
|
path: ["sql"],
|
|
7203
7854
|
message: "SQL must parse as exactly one parameterized INSERT, UPDATE, or DELETE with one value per positional placeholder."
|
|
7204
7855
|
});
|
|
7205
7856
|
}
|
|
7206
7857
|
});
|
|
7207
|
-
SqlLimitsOutputSchema =
|
|
7208
|
-
maxRows:
|
|
7209
|
-
maxBytes:
|
|
7210
|
-
enforcement:
|
|
7858
|
+
SqlLimitsOutputSchema = import_zod7.z.object({
|
|
7859
|
+
maxRows: import_zod7.z.number().int().positive(),
|
|
7860
|
+
maxBytes: import_zod7.z.number().int().positive(),
|
|
7861
|
+
enforcement: import_zod7.z.literal("node-requested-client-verified")
|
|
7211
7862
|
}).strict();
|
|
7212
|
-
SqlSchemaInspectOutputSchema =
|
|
7213
|
-
space:
|
|
7214
|
-
database:
|
|
7215
|
-
objects:
|
|
7216
|
-
type:
|
|
7217
|
-
name:
|
|
7218
|
-
tableName:
|
|
7219
|
-
sql:
|
|
7863
|
+
SqlSchemaInspectOutputSchema = import_zod7.z.object({
|
|
7864
|
+
space: import_zod7.z.string().min(1),
|
|
7865
|
+
database: import_zod7.z.string().min(1),
|
|
7866
|
+
objects: import_zod7.z.array(import_zod7.z.object({
|
|
7867
|
+
type: import_zod7.z.enum(["table", "view", "index", "trigger"]),
|
|
7868
|
+
name: import_zod7.z.string().min(1),
|
|
7869
|
+
tableName: import_zod7.z.string().min(1),
|
|
7870
|
+
sql: import_zod7.z.string().optional()
|
|
7220
7871
|
}).strict()),
|
|
7221
|
-
count:
|
|
7872
|
+
count: import_zod7.z.number().int().nonnegative(),
|
|
7222
7873
|
limits: SqlLimitsOutputSchema
|
|
7223
7874
|
}).strict();
|
|
7224
|
-
SqlQueryOutputSchema =
|
|
7225
|
-
space:
|
|
7226
|
-
database:
|
|
7227
|
-
columns:
|
|
7228
|
-
rows:
|
|
7229
|
-
rowCount:
|
|
7875
|
+
SqlQueryOutputSchema = import_zod7.z.object({
|
|
7876
|
+
space: import_zod7.z.string().min(1),
|
|
7877
|
+
database: import_zod7.z.string().min(1),
|
|
7878
|
+
columns: import_zod7.z.array(import_zod7.z.string()),
|
|
7879
|
+
rows: import_zod7.z.array(import_zod7.z.array(SqlOutputValueSchema)),
|
|
7880
|
+
rowCount: import_zod7.z.number().int().nonnegative(),
|
|
7230
7881
|
limits: SqlLimitsOutputSchema
|
|
7231
7882
|
}).strict();
|
|
7232
|
-
SqlExecuteOutputSchema =
|
|
7233
|
-
space:
|
|
7234
|
-
database:
|
|
7235
|
-
statementType:
|
|
7236
|
-
changes:
|
|
7237
|
-
lastInsertRowId:
|
|
7238
|
-
authorityNotice:
|
|
7883
|
+
SqlExecuteOutputSchema = import_zod7.z.object({
|
|
7884
|
+
space: import_zod7.z.string().min(1),
|
|
7885
|
+
database: import_zod7.z.string().min(1),
|
|
7886
|
+
statementType: import_zod7.z.enum(["insert", "update", "delete"]),
|
|
7887
|
+
changes: import_zod7.z.number().int().nonnegative(),
|
|
7888
|
+
lastInsertRowId: import_zod7.z.number().int().nullable(),
|
|
7889
|
+
authorityNotice: import_zod7.z.literal(SQL_WRITE_AUTHORITY_NOTICE)
|
|
7239
7890
|
}).strict();
|
|
7240
7891
|
AUTHENTICATED_POSTURES2 = [
|
|
7241
7892
|
"owner-openkey",
|
|
@@ -7335,6 +7986,8 @@ var init_contract = __esm({
|
|
|
7335
7986
|
"resources_mismatch",
|
|
7336
7987
|
"unsupported",
|
|
7337
7988
|
"activation_rejected",
|
|
7989
|
+
"activation_unavailable",
|
|
7990
|
+
"activation_unauthenticated",
|
|
7338
7991
|
"invalid"
|
|
7339
7992
|
];
|
|
7340
7993
|
DELEGATION_CID_PATTERN = "^bafkr4i[a-z2-7]{52}$";
|
|
@@ -7437,6 +8090,9 @@ function skipped(reason) {
|
|
|
7437
8090
|
}
|
|
7438
8091
|
async function replayProfileDelegations(profile, node, options) {
|
|
7439
8092
|
await prepareStoredDelegationReplay(profile, node, { host: options.host, migrate: options.migrate });
|
|
8093
|
+
return replayStoredDelegationRecords(profile, node, options);
|
|
8094
|
+
}
|
|
8095
|
+
async function replayStoredDelegationRecords(profile, node, options) {
|
|
7440
8096
|
const installed = [];
|
|
7441
8097
|
const warnings = [];
|
|
7442
8098
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -7477,7 +8133,7 @@ function storedDelegationForAnotherKey(entry, node) {
|
|
|
7477
8133
|
const audience = verifiedCompactAudience(node, delegation);
|
|
7478
8134
|
if (audience === void 0) return false;
|
|
7479
8135
|
try {
|
|
7480
|
-
return
|
|
8136
|
+
return nodeSdk5.principalDidEquals(delegation.delegateDID, audience) && !nodeSdk5.principalDidEquals(audience, node.sessionDid);
|
|
7481
8137
|
} catch {
|
|
7482
8138
|
return false;
|
|
7483
8139
|
}
|
|
@@ -7511,7 +8167,7 @@ async function assess(node, entry, options, mode) {
|
|
|
7511
8167
|
let outsideRequest = false;
|
|
7512
8168
|
let effectivePermissions;
|
|
7513
8169
|
try {
|
|
7514
|
-
const installed = await
|
|
8170
|
+
const installed = await nodeSdk5.activateValidatedRuntimeDelegation(node, toActivate, {
|
|
7515
8171
|
host: options.host,
|
|
7516
8172
|
authorize: (effective) => {
|
|
7517
8173
|
verifyCompactProof(toActivate);
|
|
@@ -7543,7 +8199,7 @@ function verifyCompactProof(delegation) {
|
|
|
7543
8199
|
try {
|
|
7544
8200
|
parseSignedCompactUcanAttenuation(withoutBearer(authorization), delegation.cid);
|
|
7545
8201
|
} catch {
|
|
7546
|
-
throw new
|
|
8202
|
+
throw new nodeSdk5.RuntimeDelegationRejectedError("proof_invalid", "The compact delegation proof is invalid.");
|
|
7547
8203
|
}
|
|
7548
8204
|
}
|
|
7549
8205
|
function rejectionReason(error) {
|
|
@@ -7560,7 +8216,7 @@ async function bindingMigrationRecorded(profile) {
|
|
|
7560
8216
|
async function signedCapabilities(node, delegation, host) {
|
|
7561
8217
|
let signed;
|
|
7562
8218
|
try {
|
|
7563
|
-
await
|
|
8219
|
+
await nodeSdk5.activateValidatedRuntimeDelegation(node, delegation, {
|
|
7564
8220
|
// The transport host is not signed authority; check it at replay instead.
|
|
7565
8221
|
host: delegation.host ?? host,
|
|
7566
8222
|
authorize: (effective) => {
|
|
@@ -7595,13 +8251,13 @@ function recordKey(entry) {
|
|
|
7595
8251
|
function isRecord4(value) {
|
|
7596
8252
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7597
8253
|
}
|
|
7598
|
-
var import_node_path2,
|
|
8254
|
+
var import_node_path2, nodeSdk5, REQUEST_ID_PREFIX, BINDING_NOTES, MIGRATION_LOCK_TIMEOUT_MS, NOT_REPLAYED, DELEGATION_CID;
|
|
7599
8255
|
var init_delegation_binding = __esm({
|
|
7600
8256
|
"src/delegation-binding.ts"() {
|
|
7601
8257
|
"use strict";
|
|
7602
8258
|
import_node_path2 = require("path");
|
|
7603
8259
|
init_operations_bundle();
|
|
7604
|
-
|
|
8260
|
+
nodeSdk5 = __toESM(require("@tinycloud/node-sdk"), 1);
|
|
7605
8261
|
init_artifacts();
|
|
7606
8262
|
init_authority();
|
|
7607
8263
|
init_contract();
|
|
@@ -7742,35 +8398,35 @@ function profileNotFound(profile) {
|
|
|
7742
8398
|
function isRecord5(value) {
|
|
7743
8399
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7744
8400
|
}
|
|
7745
|
-
var
|
|
8401
|
+
var import_zod8, StatusInputSchema, StatusOutputSchema, STATUS_POSTURES, STATUS_EXPOSURE, STATUS_SENSITIVITY, statusOperationDefinitions;
|
|
7746
8402
|
var init_status = __esm({
|
|
7747
8403
|
"src/operations/status.ts"() {
|
|
7748
8404
|
"use strict";
|
|
7749
|
-
|
|
8405
|
+
import_zod8 = require("zod");
|
|
7750
8406
|
init_delegation_binding();
|
|
7751
8407
|
init_errors2();
|
|
7752
8408
|
init_state();
|
|
7753
|
-
StatusInputSchema =
|
|
7754
|
-
StatusOutputSchema =
|
|
7755
|
-
profile:
|
|
7756
|
-
host:
|
|
7757
|
-
posture:
|
|
8409
|
+
StatusInputSchema = import_zod8.z.object({}).strict();
|
|
8410
|
+
StatusOutputSchema = import_zod8.z.object({
|
|
8411
|
+
profile: import_zod8.z.string().min(1),
|
|
8412
|
+
host: import_zod8.z.string().min(1),
|
|
8413
|
+
posture: import_zod8.z.enum([
|
|
7758
8414
|
"owner-openkey",
|
|
7759
8415
|
"delegate-session",
|
|
7760
8416
|
"local-owner-key",
|
|
7761
8417
|
"unauthenticated"
|
|
7762
8418
|
]),
|
|
7763
|
-
operatorType:
|
|
7764
|
-
principalDid:
|
|
7765
|
-
sessionDid:
|
|
7766
|
-
ownerDid:
|
|
7767
|
-
space:
|
|
7768
|
-
session:
|
|
7769
|
-
present:
|
|
7770
|
-
expired:
|
|
7771
|
-
expiresAt:
|
|
8419
|
+
operatorType: import_zod8.z.enum(["human", "agent"]).optional(),
|
|
8420
|
+
principalDid: import_zod8.z.string().min(1).optional(),
|
|
8421
|
+
sessionDid: import_zod8.z.string().min(1).optional(),
|
|
8422
|
+
ownerDid: import_zod8.z.string().min(1).optional(),
|
|
8423
|
+
space: import_zod8.z.string().min(1).optional(),
|
|
8424
|
+
session: import_zod8.z.object({
|
|
8425
|
+
present: import_zod8.z.boolean(),
|
|
8426
|
+
expired: import_zod8.z.boolean().nullable(),
|
|
8427
|
+
expiresAt: import_zod8.z.string().datetime({ offset: true }).nullable()
|
|
7772
8428
|
}).strict(),
|
|
7773
|
-
liveAdditionalDelegationCount:
|
|
8429
|
+
liveAdditionalDelegationCount: import_zod8.z.number().int().nonnegative().describe(
|
|
7774
8430
|
"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."
|
|
7775
8431
|
)
|
|
7776
8432
|
}).strict();
|
|
@@ -7814,7 +8470,8 @@ function registeredOperationDefinitions() {
|
|
|
7814
8470
|
...authOperationDefinitions,
|
|
7815
8471
|
...explorationOperationDefinitions,
|
|
7816
8472
|
...sqlOperationDefinitions,
|
|
7817
|
-
...secretsGetOperationDefinitions
|
|
8473
|
+
...secretsGetOperationDefinitions,
|
|
8474
|
+
...secretsStatusOperationDefinitions
|
|
7818
8475
|
]);
|
|
7819
8476
|
return operationDefinitions;
|
|
7820
8477
|
}
|
|
@@ -7846,6 +8503,7 @@ var init_registry = __esm({
|
|
|
7846
8503
|
init_auth();
|
|
7847
8504
|
init_exploration();
|
|
7848
8505
|
init_secrets_get();
|
|
8506
|
+
init_secrets_status();
|
|
7849
8507
|
init_sql();
|
|
7850
8508
|
init_status();
|
|
7851
8509
|
}
|
|
@@ -7898,6 +8556,9 @@ function resolvePosture(profile) {
|
|
|
7898
8556
|
if (profile.authMethod === "local") return "local-owner-key";
|
|
7899
8557
|
return "owner-openkey";
|
|
7900
8558
|
}
|
|
8559
|
+
function isLocalOwnerProfile(profile) {
|
|
8560
|
+
return resolvePosture(profile) === "local-owner-key" || profile.authMethod === "local" || typeof profile.privateKey === "string";
|
|
8561
|
+
}
|
|
7901
8562
|
function profileNotFound2(profile) {
|
|
7902
8563
|
return {
|
|
7903
8564
|
ok: false,
|
|
@@ -7982,11 +8643,14 @@ async function createInvocationRuntime(target, requirement = "authenticated") {
|
|
|
7982
8643
|
if (requirement === "inspection") {
|
|
7983
8644
|
return { ok: true, context: { summary } };
|
|
7984
8645
|
}
|
|
8646
|
+
if (requirement === "restored") {
|
|
8647
|
+
return createRestoredRuntime(target, resolved);
|
|
8648
|
+
}
|
|
7985
8649
|
const profileName = summary.profile;
|
|
7986
8650
|
try {
|
|
7987
8651
|
const {
|
|
7988
8652
|
TinyCloudNode: TinyCloudNodeConstructor
|
|
7989
|
-
} =
|
|
8653
|
+
} = nodeSdk6;
|
|
7990
8654
|
const explicitPrivateKeyOverride = typeof target.privateKey === "string";
|
|
7991
8655
|
const [session, key] = explicitPrivateKeyOverride ? [null, null] : await Promise.all([
|
|
7992
8656
|
readSession(profileName),
|
|
@@ -8064,7 +8728,9 @@ async function createInvocationRuntime(target, requirement = "authenticated") {
|
|
|
8064
8728
|
const runtime = {
|
|
8065
8729
|
node,
|
|
8066
8730
|
granted: canonicalizeCapabilities(livePermissions),
|
|
8067
|
-
...warnings.length === 0 ? {} : { warnings }
|
|
8731
|
+
...warnings.length === 0 ? {} : { warnings },
|
|
8732
|
+
localOwner: explicitPrivateKeyOverride || isLocalOwnerProfile(profile),
|
|
8733
|
+
skippedGrantReasons: warnings.map((warning) => warning.reason)
|
|
8068
8734
|
};
|
|
8069
8735
|
return {
|
|
8070
8736
|
ok: true,
|
|
@@ -8088,11 +8754,112 @@ async function createInvocationRuntime(target, requirement = "authenticated") {
|
|
|
8088
8754
|
));
|
|
8089
8755
|
}
|
|
8090
8756
|
}
|
|
8757
|
+
async function createRestoredRuntime(target, resolved) {
|
|
8758
|
+
const summary = resolved.context;
|
|
8759
|
+
const profileName = summary.profile;
|
|
8760
|
+
const profile = resolved.profile;
|
|
8761
|
+
if (typeof target.privateKey === "string") {
|
|
8762
|
+
return failed(summary, operationError(
|
|
8763
|
+
"AUTH_REQUIRED",
|
|
8764
|
+
"This operation never signs in; an explicit private key has no stored session.",
|
|
8765
|
+
{ details: { reason: "explicit_key" } }
|
|
8766
|
+
));
|
|
8767
|
+
}
|
|
8768
|
+
try {
|
|
8769
|
+
const actualPosture = resolvePosture(profile);
|
|
8770
|
+
const selectedPosture = profile.authMethod === "local" ? "local-owner-key" : summary.posture;
|
|
8771
|
+
if (actualPosture !== summary.posture || selectedPosture !== summary.posture) {
|
|
8772
|
+
return failed(
|
|
8773
|
+
{ ...summary, posture: actualPosture !== summary.posture ? actualPosture : selectedPosture },
|
|
8774
|
+
operationError(
|
|
8775
|
+
"PROFILE_POSTURE_NOT_ALLOWED",
|
|
8776
|
+
"The selected profile changed posture during runtime resolution."
|
|
8777
|
+
)
|
|
8778
|
+
);
|
|
8779
|
+
}
|
|
8780
|
+
const [session, key] = await Promise.all([
|
|
8781
|
+
readSession(profileName),
|
|
8782
|
+
readJson(`${profilePath(profileName)}/key.json`)
|
|
8783
|
+
]);
|
|
8784
|
+
const activeSession = session === null ? void 0 : normalizeSession(session, key);
|
|
8785
|
+
if (activeSession === void 0) {
|
|
8786
|
+
return failed(summary, operationError(
|
|
8787
|
+
"SESSION_NOT_FOUND",
|
|
8788
|
+
"The selected profile does not have an active session. This operation never signs in.",
|
|
8789
|
+
{ details: { recovery: signInRecovery(profileName, summary.posture) } }
|
|
8790
|
+
));
|
|
8791
|
+
}
|
|
8792
|
+
if (!await bindingMigrationRecorded(profileName)) {
|
|
8793
|
+
const records = await readAdditionalDelegations(profileName);
|
|
8794
|
+
if (records.some(
|
|
8795
|
+
(entry) => storedDelegationKind(entry) === "compact" && !("authorityRequest" in entry)
|
|
8796
|
+
)) {
|
|
8797
|
+
return failed(summary, operationError(
|
|
8798
|
+
"PROFILE_MIGRATION_REQUIRED",
|
|
8799
|
+
`Profile "${profileName}" has a one-time stored-grant upgrade to finish. Run one authenticated command on it (tc --profile <profile> auth caps), then retry.`,
|
|
8800
|
+
{ retryable: false, details: { recovery: migrationRecovery(profileName) } }
|
|
8801
|
+
));
|
|
8802
|
+
}
|
|
8803
|
+
}
|
|
8804
|
+
const { TinyCloudNode: TinyCloudNodeConstructor } = nodeSdk6;
|
|
8805
|
+
const node = new TinyCloudNodeConstructor({
|
|
8806
|
+
host: summary.host,
|
|
8807
|
+
...typeof profile.privateKey === "string" ? { privateKey: profile.privateKey } : {}
|
|
8808
|
+
});
|
|
8809
|
+
try {
|
|
8810
|
+
await node.restoreSession(activeSession);
|
|
8811
|
+
} catch (error) {
|
|
8812
|
+
if (isRecord7(error) && error.code === "AUTH_EXPIRED") {
|
|
8813
|
+
return failed(summary, operationError(
|
|
8814
|
+
"AUTH_REQUIRED",
|
|
8815
|
+
"The stored session has expired or is no longer valid. Sign in again.",
|
|
8816
|
+
{ retryable: false, details: { recovery: signInRecovery(profileName, summary.posture) } }
|
|
8817
|
+
));
|
|
8818
|
+
}
|
|
8819
|
+
throw error;
|
|
8820
|
+
}
|
|
8821
|
+
const activeSessionDid = normalizeDid(node.sessionDid);
|
|
8822
|
+
const livePrincipalDid = normalizeDid(node.did);
|
|
8823
|
+
const livePermissions = [...node.getVerifiedSessionCapabilities()];
|
|
8824
|
+
const replay = await replayStoredDelegationRecords(
|
|
8825
|
+
profileName,
|
|
8826
|
+
node,
|
|
8827
|
+
{ host: summary.host, resolveSpace: operationSpaceResolver(node, summary.space) }
|
|
8828
|
+
);
|
|
8829
|
+
for (const installed of replay.installed) {
|
|
8830
|
+
livePermissions.push(...installed.effectivePermissions);
|
|
8831
|
+
}
|
|
8832
|
+
const runtime = {
|
|
8833
|
+
node,
|
|
8834
|
+
granted: canonicalizeCapabilities(livePermissions),
|
|
8835
|
+
...replay.warnings.length === 0 ? {} : { warnings: replay.warnings },
|
|
8836
|
+
localOwner: isLocalOwnerProfile(profile),
|
|
8837
|
+
skippedGrantReasons: replay.warnings.map((warning) => warning.reason)
|
|
8838
|
+
};
|
|
8839
|
+
return {
|
|
8840
|
+
ok: true,
|
|
8841
|
+
context: {
|
|
8842
|
+
summary: {
|
|
8843
|
+
...summary,
|
|
8844
|
+
...livePrincipalDid === void 0 ? {} : { principalDid: livePrincipalDid },
|
|
8845
|
+
...activeSessionDid === void 0 ? {} : { sessionDid: activeSessionDid }
|
|
8846
|
+
},
|
|
8847
|
+
runtime
|
|
8848
|
+
}
|
|
8849
|
+
};
|
|
8850
|
+
} catch {
|
|
8851
|
+
return failed(summary, operationError(
|
|
8852
|
+
"NODE_ERROR",
|
|
8853
|
+
"The selected profile runtime could not be initialized.",
|
|
8854
|
+
{ retryable: true }
|
|
8855
|
+
));
|
|
8856
|
+
}
|
|
8857
|
+
}
|
|
8091
8858
|
function spaceForAuthenticatedPrincipal(principal2) {
|
|
8092
8859
|
if (principal2 === void 0) return void 0;
|
|
8093
8860
|
try {
|
|
8094
|
-
const pkh =
|
|
8095
|
-
return pkh === null ? void 0 :
|
|
8861
|
+
const pkh = nodeSdk6.parsePkhDid(principal2);
|
|
8862
|
+
return pkh === null ? void 0 : nodeSdk6.makePkhSpaceId(pkh.address, pkh.chainId, "secrets");
|
|
8096
8863
|
} catch {
|
|
8097
8864
|
return void 0;
|
|
8098
8865
|
}
|
|
@@ -8136,14 +8903,15 @@ function unresolvedSummary(target) {
|
|
|
8136
8903
|
posture: "unauthenticated"
|
|
8137
8904
|
};
|
|
8138
8905
|
}
|
|
8139
|
-
var
|
|
8906
|
+
var nodeSdk6;
|
|
8140
8907
|
var init_runtime = __esm({
|
|
8141
8908
|
"src/runtime.ts"() {
|
|
8142
8909
|
"use strict";
|
|
8143
|
-
|
|
8910
|
+
nodeSdk6 = __toESM(require("@tinycloud/node-sdk"), 1);
|
|
8144
8911
|
init_authority();
|
|
8145
8912
|
init_errors2();
|
|
8146
8913
|
init_profile();
|
|
8914
|
+
init_recovery();
|
|
8147
8915
|
init_secrets();
|
|
8148
8916
|
init_state();
|
|
8149
8917
|
init_delegation_binding();
|
|
@@ -8166,6 +8934,9 @@ init_errors2();
|
|
|
8166
8934
|
init_authority();
|
|
8167
8935
|
init_artifacts();
|
|
8168
8936
|
|
|
8937
|
+
// src/redaction.ts
|
|
8938
|
+
init_recovery();
|
|
8939
|
+
|
|
8169
8940
|
// src/safe-values.ts
|
|
8170
8941
|
function safeOriginHost(value) {
|
|
8171
8942
|
if (typeof value !== "string" || value.length === 0) return void 0;
|
|
@@ -8242,7 +9013,25 @@ function redactOperationError(_definition, error) {
|
|
|
8242
9013
|
}
|
|
8243
9014
|
var SAFE_ERROR_DETAIL_POLICIES = {
|
|
8244
9015
|
INPUT_INVALID: {
|
|
8245
|
-
issues: (value) => Array.isArray(value) ? safeIssueCodes(value) : void 0
|
|
9016
|
+
issues: (value) => Array.isArray(value) ? safeIssueCodes(value) : void 0,
|
|
9017
|
+
reason: (value) => value === "OWNER_MISMATCH" || value === "OWNER_DID_UNKNOWN" ? value : void 0
|
|
9018
|
+
},
|
|
9019
|
+
AUTH_REQUIRED: {
|
|
9020
|
+
recovery: (value) => isSafeRecovery(value, "sign_in") ? { ...value } : void 0,
|
|
9021
|
+
reason: (value) => value === "explicit_key" ? value : void 0
|
|
9022
|
+
},
|
|
9023
|
+
SESSION_NOT_FOUND: {
|
|
9024
|
+
recovery: (value) => isSafeRecovery(value, "sign_in") ? { ...value } : void 0
|
|
9025
|
+
},
|
|
9026
|
+
PROFILE_MIGRATION_REQUIRED: {
|
|
9027
|
+
recovery: (value) => isSafeRecovery(value, "complete_migration") ? { ...value } : void 0
|
|
9028
|
+
},
|
|
9029
|
+
// `secrets.get` not found for an identity no request link serves.
|
|
9030
|
+
KV_NOT_FOUND: {
|
|
9031
|
+
recoveryKind: (value) => value === "put_locally" || value === "not_requestable" ? value : void 0,
|
|
9032
|
+
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,
|
|
9033
|
+
scope: (value) => typeof value === "string" && value.length <= 256 && /^[a-z0-9]+(-[a-z0-9]+)*$/.test(value) ? value : void 0,
|
|
9034
|
+
owner: (value) => typeof value === "string" && /^did:pkh:eip155:[1-9][0-9]*:0x[0-9a-fA-F]{40}$/.test(value) ? value : void 0
|
|
8246
9035
|
},
|
|
8247
9036
|
OPERATION_VERSION_UNSUPPORTED: {
|
|
8248
9037
|
supportedVersions: (value) => Array.isArray(value) && value.every((item) => Number.isInteger(item)) ? value : void 0
|
|
@@ -8324,7 +9113,7 @@ async function invokeOperation(operationId, operationVersion, invocationTarget,
|
|
|
8324
9113
|
operationError("INPUT_INVALID", "The invocation target is invalid.")
|
|
8325
9114
|
);
|
|
8326
9115
|
}
|
|
8327
|
-
|
|
9116
|
+
const run = withTinyCloudStateRoot(target.stateRoot, async () => {
|
|
8328
9117
|
const prepared = await prepareInvocation(
|
|
8329
9118
|
operationId,
|
|
8330
9119
|
operationVersion,
|
|
@@ -8334,6 +9123,33 @@ async function invokeOperation(operationId, operationVersion, invocationTarget,
|
|
|
8334
9123
|
if (isPreparedError(prepared)) return prepared.result;
|
|
8335
9124
|
return withRuntimeWarnings(await executePreparedInvocation(prepared), prepared);
|
|
8336
9125
|
});
|
|
9126
|
+
const { lookupOperation: lookupOperation2 } = await Promise.resolve().then(() => (init_registry(), registry_exports));
|
|
9127
|
+
const lookup = lookupOperation2(operationId, operationVersion);
|
|
9128
|
+
const deadlineMs = lookup.status === "found" ? lookup.definition.deadlineMs : void 0;
|
|
9129
|
+
if (deadlineMs === void 0) return run;
|
|
9130
|
+
return withResponseDeadline(run, deadlineMs, () => errorResult(
|
|
9131
|
+
{ operationId, operationVersion },
|
|
9132
|
+
unresolvedContextSummary(target),
|
|
9133
|
+
operationError(
|
|
9134
|
+
"NODE_UNREACHABLE",
|
|
9135
|
+
`The operation did not finish within ${deadlineMs / 1e3} s.`,
|
|
9136
|
+
{ retryable: true }
|
|
9137
|
+
)
|
|
9138
|
+
));
|
|
9139
|
+
}
|
|
9140
|
+
async function withResponseDeadline(run, deadlineMs, expired) {
|
|
9141
|
+
run.catch(() => {
|
|
9142
|
+
});
|
|
9143
|
+
let timer;
|
|
9144
|
+
const deadline = new Promise((resolve2) => {
|
|
9145
|
+
timer = setTimeout(() => resolve2(expired()), deadlineMs);
|
|
9146
|
+
timer.unref();
|
|
9147
|
+
});
|
|
9148
|
+
try {
|
|
9149
|
+
return await Promise.race([run, deadline]);
|
|
9150
|
+
} finally {
|
|
9151
|
+
clearTimeout(timer);
|
|
9152
|
+
}
|
|
8337
9153
|
}
|
|
8338
9154
|
function withRuntimeWarnings(result, prepared) {
|
|
8339
9155
|
const warnings = prepared.runtimeResolution.context.runtime?.warnings;
|
|
@@ -8408,7 +9224,7 @@ async function prepareInvocation(operationId, operationVersion, invocationTarget
|
|
|
8408
9224
|
)
|
|
8409
9225
|
) };
|
|
8410
9226
|
}
|
|
8411
|
-
if (target.privateKey === void 0 && definition.runtime
|
|
9227
|
+
if (target.privateKey === void 0 && requiresRuntime(definition.runtime) && isOwnerPosture(inspectionResolution.context.summary.posture) && target.allowOwnerProfile !== true) {
|
|
8412
9228
|
return { status: "error", result: errorResult(
|
|
8413
9229
|
operation,
|
|
8414
9230
|
inspectionResolution.context.summary,
|
|
@@ -8418,15 +9234,15 @@ async function prepareInvocation(operationId, operationVersion, invocationTarget
|
|
|
8418
9234
|
)
|
|
8419
9235
|
) };
|
|
8420
9236
|
}
|
|
8421
|
-
const runtimeResolution = definition.runtime === "inspection" ? inspectionResolution : await resolveRuntime(target,
|
|
9237
|
+
const runtimeResolution = definition.runtime === "inspection" ? inspectionResolution : await resolveRuntime(target, definition.runtime);
|
|
8422
9238
|
if (!runtimeResolution.ok) {
|
|
8423
9239
|
return { status: "error", result: errorResult(
|
|
8424
9240
|
operation,
|
|
8425
9241
|
runtimeResolution.context,
|
|
8426
|
-
runtimeResolution.error
|
|
9242
|
+
redactOperationError(definition, runtimeResolution.error)
|
|
8427
9243
|
) };
|
|
8428
9244
|
}
|
|
8429
|
-
if (definition.runtime
|
|
9245
|
+
if (requiresRuntime(definition.runtime)) {
|
|
8430
9246
|
const actualPosture = runtimeResolution.context.summary.posture;
|
|
8431
9247
|
if (!definition.postures.includes(actualPosture)) {
|
|
8432
9248
|
return { status: "error", result: errorResult(
|
|
@@ -8813,6 +9629,15 @@ function exactCapabilities2(value) {
|
|
|
8813
9629
|
function isOwnerPosture(posture) {
|
|
8814
9630
|
return posture === "owner-openkey" || posture === "local-owner-key";
|
|
8815
9631
|
}
|
|
9632
|
+
function requiresRuntime(requirement) {
|
|
9633
|
+
switch (requirement) {
|
|
9634
|
+
case "authenticated":
|
|
9635
|
+
case "restored":
|
|
9636
|
+
return true;
|
|
9637
|
+
case "inspection":
|
|
9638
|
+
return false;
|
|
9639
|
+
}
|
|
9640
|
+
}
|
|
8816
9641
|
function zodIssueDetails(error) {
|
|
8817
9642
|
return {
|
|
8818
9643
|
// The canonical error channel carries only schema-owned issue codes.
|