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