@tinycloud/operations 0.5.0-beta.1 → 0.5.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +961 -178
- 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 +960 -177
- 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 +989 -179
- 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 +986 -176
- 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);
|
|
4835
|
+
}
|
|
4836
|
+
async function isStagedOwnerGone(recordPath, liveness) {
|
|
4837
|
+
const owner = await readJson(recordPath).catch(() => null);
|
|
4838
|
+
return typeof owner === "object" && owner !== null && owner.pid === process.pid && !await isRecordedProcessAlive(owner, liveness);
|
|
4661
4839
|
}
|
|
4662
|
-
async function collectTurns(profile, turn) {
|
|
4840
|
+
async function collectTurns(profile, turn, liveness) {
|
|
4663
4841
|
for (const [directory, abandoned] of [
|
|
4664
4842
|
[turn.directory, isAbandonedTurnEntry],
|
|
4665
4843
|
[dirname(turn.directory), isAbandonedTurnDirectoryStage]
|
|
4666
4844
|
]) {
|
|
4667
|
-
for (const name of
|
|
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
|
}
|
|
@@ -5857,8 +6078,33 @@ var init_auth = __esm({
|
|
|
5857
6078
|
}
|
|
5858
6079
|
});
|
|
5859
6080
|
|
|
5860
|
-
// src/
|
|
6081
|
+
// src/kv-failure.ts
|
|
5861
6082
|
import { authorizationVerdictOf, validatedCapabilityOf } from "@tinycloud/sdk-services";
|
|
6083
|
+
function kvRefusal(error) {
|
|
6084
|
+
const verdict = authorizationVerdictOf(error);
|
|
6085
|
+
if (verdict === "unauthenticated") {
|
|
6086
|
+
return validatedCapabilityOf(error) === void 0 ? "auth_required" : "permission_denied";
|
|
6087
|
+
}
|
|
6088
|
+
return verdict === "forbidden" ? "permission_denied" : void 0;
|
|
6089
|
+
}
|
|
6090
|
+
function authorizationFailure(error, action) {
|
|
6091
|
+
switch (kvRefusal(error)) {
|
|
6092
|
+
case "auth_required":
|
|
6093
|
+
return { status: "error", error: operationError("AUTH_REQUIRED", `Authentication is required to ${action}.`) };
|
|
6094
|
+
case "permission_denied":
|
|
6095
|
+
return { status: "error", error: operationError("PERMISSION_DENIED", `Permission is denied to ${action}.`) };
|
|
6096
|
+
case void 0:
|
|
6097
|
+
return void 0;
|
|
6098
|
+
}
|
|
6099
|
+
}
|
|
6100
|
+
var init_kv_failure = __esm({
|
|
6101
|
+
"src/kv-failure.ts"() {
|
|
6102
|
+
"use strict";
|
|
6103
|
+
init_errors2();
|
|
6104
|
+
}
|
|
6105
|
+
});
|
|
6106
|
+
|
|
6107
|
+
// src/operations/exploration.ts
|
|
5862
6108
|
import { z as z4 } from "zod";
|
|
5863
6109
|
async function planAccountRegistryRead(context, path) {
|
|
5864
6110
|
return [{
|
|
@@ -6226,19 +6472,6 @@ function caughtKvFailure(error, action) {
|
|
|
6226
6472
|
if (error instanceof OperationInvocationError) return { status: "error", error: error.operationError };
|
|
6227
6473
|
return authorizationFailure(error, action) ?? nodeFailure(action);
|
|
6228
6474
|
}
|
|
6229
|
-
function authorizationFailure(error, action) {
|
|
6230
|
-
const verdict = authorizationVerdictOf(error);
|
|
6231
|
-
if (verdict === "unauthenticated") {
|
|
6232
|
-
if (validatedCapabilityOf(error) !== void 0) {
|
|
6233
|
-
return { status: "error", error: operationError("PERMISSION_DENIED", `Permission is denied to ${action}.`) };
|
|
6234
|
-
}
|
|
6235
|
-
return { status: "error", error: operationError("AUTH_REQUIRED", `Authentication is required to ${action}.`) };
|
|
6236
|
-
}
|
|
6237
|
-
if (verdict === "forbidden") {
|
|
6238
|
-
return { status: "error", error: operationError("PERMISSION_DENIED", `Permission is denied to ${action}.`) };
|
|
6239
|
-
}
|
|
6240
|
-
return void 0;
|
|
6241
|
-
}
|
|
6242
6475
|
function accountRegistryOutput(output) {
|
|
6243
6476
|
if (Buffer.byteLength(JSON.stringify(output), "utf8") > MAX_ACCOUNT_REGISTRY_BYTES) {
|
|
6244
6477
|
return {
|
|
@@ -6291,6 +6524,7 @@ var MAX_KV_VALUE_BYTES, DEFAULT_KV_LIST_LIMIT, MAX_ACCOUNT_REGISTRY_RECORDS, MAX
|
|
|
6291
6524
|
var init_exploration = __esm({
|
|
6292
6525
|
"src/operations/exploration.ts"() {
|
|
6293
6526
|
"use strict";
|
|
6527
|
+
init_kv_failure();
|
|
6294
6528
|
init_errors2();
|
|
6295
6529
|
init_secrets();
|
|
6296
6530
|
MAX_KV_VALUE_BYTES = 1024 * 1024;
|
|
@@ -6591,6 +6825,191 @@ var init_exploration = __esm({
|
|
|
6591
6825
|
}
|
|
6592
6826
|
});
|
|
6593
6827
|
|
|
6828
|
+
// src/secret-request.ts
|
|
6829
|
+
import * as nodeSdk3 from "@tinycloud/node-sdk";
|
|
6830
|
+
function canonicalSecretRequestOwner(did) {
|
|
6831
|
+
const match = typeof did === "string" ? OWNER_RE.exec(did) : null;
|
|
6832
|
+
if (match === null || !Number.isSafeInteger(Number(match[1]))) throw invalidOwner();
|
|
6833
|
+
const address = match[2];
|
|
6834
|
+
let checksummed;
|
|
6835
|
+
try {
|
|
6836
|
+
checksummed = nodeSdk3.canonicalizeAddress(address);
|
|
6837
|
+
} catch {
|
|
6838
|
+
throw invalidOwner();
|
|
6839
|
+
}
|
|
6840
|
+
if (/[A-F]/.test(address.slice(2)) && address !== checksummed) throw invalidOwner();
|
|
6841
|
+
return `did:pkh:eip155:${match[1]}:${checksummed}`;
|
|
6842
|
+
}
|
|
6843
|
+
function buildSecretRequestUrl(input) {
|
|
6844
|
+
const owner = canonicalSecretRequestOwner(input.owner);
|
|
6845
|
+
const names = validSecretNames(input.names);
|
|
6846
|
+
if (input.scope !== void 0) assertSecretRequestScope(input.scope);
|
|
6847
|
+
const requester = input.requester;
|
|
6848
|
+
if (requester !== void 0 && (typeof requester !== "string" || requester.length === 0 || requester.length > MAX_REQUESTER_LENGTH || CONTROL_OR_FORMAT.test(requester))) {
|
|
6849
|
+
throw new SecretRequestInputError("INVALID_REQUESTER", "The requester label must be 1-64 characters with no control or format characters.");
|
|
6850
|
+
}
|
|
6851
|
+
if (input.returnTo !== void 0 && !isAllowedReturnTo(input.returnTo)) {
|
|
6852
|
+
throw new SecretRequestInputError("INVALID_RETURN_TO", "The return address is not an allowed Secret Manager callback.");
|
|
6853
|
+
}
|
|
6854
|
+
if (input.state !== void 0 && !STATE_RE.test(input.state)) {
|
|
6855
|
+
throw new SecretRequestInputError("INVALID_STATE", "The state must be 1-128 characters of A-Z, a-z, 0-9, '.', '_', '~' or '-'.");
|
|
6856
|
+
}
|
|
6857
|
+
const parameters = [
|
|
6858
|
+
["owner", owner],
|
|
6859
|
+
...names.map((name) => ["name", name]),
|
|
6860
|
+
...input.scope === void 0 ? [] : [["scope", input.scope]],
|
|
6861
|
+
...input.requester === void 0 ? [] : [["requester", input.requester]],
|
|
6862
|
+
...input.returnTo === void 0 ? [] : [["return_to", input.returnTo]],
|
|
6863
|
+
...input.state === void 0 ? [] : [["state", input.state]],
|
|
6864
|
+
["v", "1"]
|
|
6865
|
+
];
|
|
6866
|
+
const url = `${SECRET_MANAGER_ORIGIN}/request?${parameters.map(([key, value]) => `${key}=${encodeURIComponent(value)}`).join("&")}`;
|
|
6867
|
+
if (url.length > SECRET_REQUEST_MAX_URL_LENGTH) {
|
|
6868
|
+
throw new SecretRequestInputError("REQUEST_LINK_TOO_LONG", `The request link is longer than ${SECRET_REQUEST_MAX_URL_LENGTH} characters.`);
|
|
6869
|
+
}
|
|
6870
|
+
return { url, owner, names, ...input.scope === void 0 ? {} : { scope: input.scope } };
|
|
6871
|
+
}
|
|
6872
|
+
function secretSetupFor(link2) {
|
|
6873
|
+
return {
|
|
6874
|
+
url: link2.url,
|
|
6875
|
+
names: [...link2.names],
|
|
6876
|
+
...link2.scope === void 0 ? {} : { scope: link2.scope },
|
|
6877
|
+
owner: link2.owner,
|
|
6878
|
+
action: "add_secret"
|
|
6879
|
+
};
|
|
6880
|
+
}
|
|
6881
|
+
function resolveSecretsOwner(profile, requestedOwner) {
|
|
6882
|
+
const resolved = profile.local ? ownerOrUndefined(profile.principalDid) : ownerOrUndefined(profile.ownerDid) ?? (profile.posture === "delegate-session" ? ownerOfSpace(profile.space) : void 0) ?? ownerOrUndefined(profile.principalDid);
|
|
6883
|
+
const requested = requestedOwner === void 0 ? void 0 : ownerOrUndefined(requestedOwner);
|
|
6884
|
+
if (resolved !== void 0) {
|
|
6885
|
+
if (requestedOwner !== void 0 && requested !== resolved) {
|
|
6886
|
+
return {
|
|
6887
|
+
ok: false,
|
|
6888
|
+
code: "OWNER_MISMATCH",
|
|
6889
|
+
message: "The requested owner is not the owner of this identity's secrets."
|
|
6890
|
+
};
|
|
6891
|
+
}
|
|
6892
|
+
return { ok: true, owner: resolved, local: profile.local };
|
|
6893
|
+
}
|
|
6894
|
+
if (requested !== void 0 && !profile.local) return { ok: true, owner: requested, local: false };
|
|
6895
|
+
return {
|
|
6896
|
+
ok: false,
|
|
6897
|
+
code: "OWNER_DID_UNKNOWN",
|
|
6898
|
+
message: "The owner of this identity's secrets is unknown. Pass the owner's did:pkh with --owner."
|
|
6899
|
+
};
|
|
6900
|
+
}
|
|
6901
|
+
function missingSecretRecovery(input) {
|
|
6902
|
+
if (input.local) {
|
|
6903
|
+
return { kind: "put_locally", hint: `Store it with: ${putCommands(input.names, input.scope)}` };
|
|
6904
|
+
}
|
|
6905
|
+
if (input.owner !== void 0) {
|
|
6906
|
+
try {
|
|
6907
|
+
return {
|
|
6908
|
+
kind: "add_secret",
|
|
6909
|
+
setup: secretSetupFor(buildSecretRequestUrl({
|
|
6910
|
+
owner: input.owner,
|
|
6911
|
+
names: input.names,
|
|
6912
|
+
...input.scope === void 0 ? {} : { scope: input.scope }
|
|
6913
|
+
}))
|
|
6914
|
+
};
|
|
6915
|
+
} catch {
|
|
6916
|
+
}
|
|
6917
|
+
}
|
|
6918
|
+
return {
|
|
6919
|
+
kind: "not_requestable",
|
|
6920
|
+
hint: `No Secret Manager request link can express this secret. The owner can store it with: ${putCommands(input.names, input.scope)}`
|
|
6921
|
+
};
|
|
6922
|
+
}
|
|
6923
|
+
function isValidSecretRequestScope(scope) {
|
|
6924
|
+
return scope.length <= MAX_SCOPE_LENGTH && SCOPE_RE.test(scope) && !RESERVED_SCOPES.includes(scope);
|
|
6925
|
+
}
|
|
6926
|
+
function isValidSecretName(name) {
|
|
6927
|
+
return name.length <= MAX_NAME_LENGTH && NAME_RE.test(name);
|
|
6928
|
+
}
|
|
6929
|
+
function validSecretNames(names) {
|
|
6930
|
+
for (const name of names) assertSecretName(name);
|
|
6931
|
+
const unique = dedupe(names);
|
|
6932
|
+
if (unique.length === 0) {
|
|
6933
|
+
throw new SecretRequestInputError("INVALID_SECRET_NAME", "At least one secret name is required.");
|
|
6934
|
+
}
|
|
6935
|
+
if (unique.length > SECRET_REQUEST_MAX_NAMES) {
|
|
6936
|
+
throw new SecretRequestInputError("TOO_MANY_SECRET_NAMES", `At most ${SECRET_REQUEST_MAX_NAMES} secret names can be requested at once.`);
|
|
6937
|
+
}
|
|
6938
|
+
return unique;
|
|
6939
|
+
}
|
|
6940
|
+
function assertSecretName(name) {
|
|
6941
|
+
if (typeof name !== "string" || !isValidSecretName(name)) {
|
|
6942
|
+
throw new SecretRequestInputError("INVALID_SECRET_NAME", "Secret names are 1-64 characters of A-Z, 0-9 and '_', starting with a letter.");
|
|
6943
|
+
}
|
|
6944
|
+
}
|
|
6945
|
+
function assertSecretRequestScope(scope) {
|
|
6946
|
+
if (typeof scope !== "string" || !isValidSecretRequestScope(scope)) {
|
|
6947
|
+
throw new SecretRequestInputError("INVALID_SECRET_SCOPE", "The scope must be a canonical scope of at most 64 characters, not default, global or scoped.");
|
|
6948
|
+
}
|
|
6949
|
+
}
|
|
6950
|
+
function isAllowedReturnTo(returnTo) {
|
|
6951
|
+
if (typeof returnTo !== "string" || returnTo.length > MAX_RETURN_TO_LENGTH) return false;
|
|
6952
|
+
let url;
|
|
6953
|
+
try {
|
|
6954
|
+
url = new URL(returnTo);
|
|
6955
|
+
} catch {
|
|
6956
|
+
return false;
|
|
6957
|
+
}
|
|
6958
|
+
if (url.username !== "" || url.password !== "") return false;
|
|
6959
|
+
return RETURN_TO_ORIGINS.includes(url.origin) || RETURN_TO_PREVIEW.test(url.origin);
|
|
6960
|
+
}
|
|
6961
|
+
function ownerOrUndefined(did) {
|
|
6962
|
+
if (did === void 0) return void 0;
|
|
6963
|
+
try {
|
|
6964
|
+
return canonicalSecretRequestOwner(did);
|
|
6965
|
+
} catch {
|
|
6966
|
+
return void 0;
|
|
6967
|
+
}
|
|
6968
|
+
}
|
|
6969
|
+
function ownerOfSpace(space) {
|
|
6970
|
+
const match = space === void 0 ? null : PKH_SPACE_RE.exec(space);
|
|
6971
|
+
if (match === null) return void 0;
|
|
6972
|
+
return ownerOrUndefined(`did:pkh:eip155:${match[1]}:${match[2].toLowerCase()}`);
|
|
6973
|
+
}
|
|
6974
|
+
function putCommands(names, scope) {
|
|
6975
|
+
return dedupe(names).map((name) => `tc secrets put ${name}${scope === void 0 ? "" : ` --scope ${scope}`} --stdin`).join("; ");
|
|
6976
|
+
}
|
|
6977
|
+
function dedupe(names) {
|
|
6978
|
+
return [...new Set(names)];
|
|
6979
|
+
}
|
|
6980
|
+
function invalidOwner() {
|
|
6981
|
+
return new SecretRequestInputError("INVALID_OWNER", "The owner must be a did:pkh:eip155 account with a valid EIP-55 address.");
|
|
6982
|
+
}
|
|
6983
|
+
var SECRET_MANAGER_ORIGIN, SECRET_REQUEST_MAX_NAMES, SECRET_REQUEST_MAX_URL_LENGTH, OWNER_RE, NAME_RE, MAX_NAME_LENGTH, SCOPE_RE, MAX_SCOPE_LENGTH, RESERVED_SCOPES, MAX_REQUESTER_LENGTH, CONTROL_OR_FORMAT, STATE_RE, MAX_RETURN_TO_LENGTH, RETURN_TO_ORIGINS, RETURN_TO_PREVIEW, PKH_SPACE_RE, SecretRequestInputError;
|
|
6984
|
+
var init_secret_request = __esm({
|
|
6985
|
+
"src/secret-request.ts"() {
|
|
6986
|
+
"use strict";
|
|
6987
|
+
SECRET_MANAGER_ORIGIN = "https://secrets.tinycloud.xyz";
|
|
6988
|
+
SECRET_REQUEST_MAX_NAMES = 10;
|
|
6989
|
+
SECRET_REQUEST_MAX_URL_LENGTH = 8192;
|
|
6990
|
+
OWNER_RE = /^did:pkh:eip155:([1-9][0-9]*):(0x[0-9a-fA-F]{40})$/;
|
|
6991
|
+
NAME_RE = /^[A-Z][A-Z0-9_]*$/;
|
|
6992
|
+
MAX_NAME_LENGTH = 64;
|
|
6993
|
+
SCOPE_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
6994
|
+
MAX_SCOPE_LENGTH = 64;
|
|
6995
|
+
RESERVED_SCOPES = ["default", "global", "scoped"];
|
|
6996
|
+
MAX_REQUESTER_LENGTH = 64;
|
|
6997
|
+
CONTROL_OR_FORMAT = /[\p{Cc}\p{Cf}]/u;
|
|
6998
|
+
STATE_RE = /^[A-Za-z0-9._~-]{1,128}$/;
|
|
6999
|
+
MAX_RETURN_TO_LENGTH = 2048;
|
|
7000
|
+
RETURN_TO_ORIGINS = ["https://feed.tinycloud.xyz"];
|
|
7001
|
+
RETURN_TO_PREVIEW = /^https:\/\/[a-z0-9-]+\.tinyfeed\.pages\.dev$/;
|
|
7002
|
+
PKH_SPACE_RE = /^tinycloud:pkh:eip155:([1-9][0-9]*):(0x[0-9a-fA-F]{40}):[^:]+$/;
|
|
7003
|
+
SecretRequestInputError = class extends Error {
|
|
7004
|
+
constructor(code, message) {
|
|
7005
|
+
super(message);
|
|
7006
|
+
this.name = "SecretRequestInputError";
|
|
7007
|
+
this.code = code;
|
|
7008
|
+
}
|
|
7009
|
+
};
|
|
7010
|
+
}
|
|
7011
|
+
});
|
|
7012
|
+
|
|
6594
7013
|
// src/operations/secrets-get.ts
|
|
6595
7014
|
import { z as z5 } from "zod";
|
|
6596
7015
|
async function planSecretGet(context, input) {
|
|
@@ -6658,27 +7077,14 @@ async function executeSecretGet(context, input) {
|
|
|
6658
7077
|
)
|
|
6659
7078
|
};
|
|
6660
7079
|
}
|
|
6661
|
-
return classifySecretRead(reference, result);
|
|
7080
|
+
return classifySecretRead(reference, result, context.runtime?.localOwner === true);
|
|
6662
7081
|
}
|
|
6663
|
-
function classifySecretRead(reference, result) {
|
|
7082
|
+
function classifySecretRead(reference, result, localOwner) {
|
|
6664
7083
|
switch (result.status) {
|
|
6665
7084
|
case "ok":
|
|
6666
7085
|
return { status: "ok", output: { value: result.value } };
|
|
6667
7086
|
case "not_found":
|
|
6668
|
-
return
|
|
6669
|
-
status: "setup_required",
|
|
6670
|
-
setup: {
|
|
6671
|
-
kind: "secret_manager",
|
|
6672
|
-
secret: {
|
|
6673
|
-
name: reference.name,
|
|
6674
|
-
...reference.scope === void 0 ? {} : { scope: reference.scope },
|
|
6675
|
-
space: reference.space
|
|
6676
|
-
},
|
|
6677
|
-
url: buildSecretSetupUrl(reference),
|
|
6678
|
-
message: "Enter this secret in Secret Manager, then retry the operation."
|
|
6679
|
-
},
|
|
6680
|
-
requiresCallerInput: true
|
|
6681
|
-
};
|
|
7087
|
+
return missingSecretOutcome(reference, localOwner);
|
|
6682
7088
|
case "permission_required":
|
|
6683
7089
|
return {
|
|
6684
7090
|
status: "authority_required",
|
|
@@ -6723,6 +7129,47 @@ function classifySecretRead(reference, result) {
|
|
|
6723
7129
|
error: operationError("SECRET_READ_FAILED", "The secret read failed.")
|
|
6724
7130
|
};
|
|
6725
7131
|
}
|
|
7132
|
+
function missingSecretOutcome(reference, localOwner) {
|
|
7133
|
+
const owner = ownerOfSpace(reference.space);
|
|
7134
|
+
const recovery = missingSecretRecovery({
|
|
7135
|
+
owner,
|
|
7136
|
+
local: localOwner,
|
|
7137
|
+
names: [reference.name],
|
|
7138
|
+
...reference.scope === void 0 ? {} : { scope: reference.scope }
|
|
7139
|
+
});
|
|
7140
|
+
if (recovery.kind === "add_secret") {
|
|
7141
|
+
const { setup } = recovery;
|
|
7142
|
+
return {
|
|
7143
|
+
status: "setup_required",
|
|
7144
|
+
setup: {
|
|
7145
|
+
kind: "secret_manager",
|
|
7146
|
+
action: setup.action,
|
|
7147
|
+
url: setup.url,
|
|
7148
|
+
names: setup.names,
|
|
7149
|
+
...setup.scope === void 0 ? {} : { scope: setup.scope },
|
|
7150
|
+
owner: setup.owner,
|
|
7151
|
+
secret: {
|
|
7152
|
+
name: reference.name,
|
|
7153
|
+
...reference.scope === void 0 ? {} : { scope: reference.scope },
|
|
7154
|
+
space: reference.space
|
|
7155
|
+
},
|
|
7156
|
+
message: "Ask the owner to add this secret in Secret Manager, then retry the operation."
|
|
7157
|
+
},
|
|
7158
|
+
requiresCallerInput: true
|
|
7159
|
+
};
|
|
7160
|
+
}
|
|
7161
|
+
return {
|
|
7162
|
+
status: "error",
|
|
7163
|
+
error: operationError("KV_NOT_FOUND", recovery.hint, {
|
|
7164
|
+
details: {
|
|
7165
|
+
names: [reference.name],
|
|
7166
|
+
...reference.scope === void 0 ? {} : { scope: reference.scope },
|
|
7167
|
+
...owner === void 0 ? {} : { owner },
|
|
7168
|
+
recoveryKind: recovery.kind
|
|
7169
|
+
}
|
|
7170
|
+
})
|
|
7171
|
+
};
|
|
7172
|
+
}
|
|
6726
7173
|
function resolveEncryptionNetworkId(node, space) {
|
|
6727
7174
|
const candidate = node;
|
|
6728
7175
|
let scoped;
|
|
@@ -6749,6 +7196,7 @@ var init_secrets_get = __esm({
|
|
|
6749
7196
|
"src/operations/secrets-get.ts"() {
|
|
6750
7197
|
"use strict";
|
|
6751
7198
|
init_errors2();
|
|
7199
|
+
init_secret_request();
|
|
6752
7200
|
init_secrets();
|
|
6753
7201
|
SecretsGetInputSchema = z5.object({
|
|
6754
7202
|
name: z5.string(),
|
|
@@ -6800,9 +7248,197 @@ var init_secrets_get = __esm({
|
|
|
6800
7248
|
}
|
|
6801
7249
|
});
|
|
6802
7250
|
|
|
7251
|
+
// src/operations/secrets-status.ts
|
|
7252
|
+
import { z as z6 } from "zod";
|
|
7253
|
+
import * as nodeSdk4 from "@tinycloud/node-sdk";
|
|
7254
|
+
async function executeSecretsStatus(context, input) {
|
|
7255
|
+
const runtime = context.runtime;
|
|
7256
|
+
const node = runtime?.node;
|
|
7257
|
+
if (runtime === void 0 || typeof node?.kvForSpace !== "function") {
|
|
7258
|
+
return {
|
|
7259
|
+
status: "error",
|
|
7260
|
+
error: operationError("NODE_ERROR", "The TinyCloud node could not check the secrets.", { retryable: true })
|
|
7261
|
+
};
|
|
7262
|
+
}
|
|
7263
|
+
const local = runtime.localOwner === true;
|
|
7264
|
+
const resolution = resolveSecretsOwner({
|
|
7265
|
+
local,
|
|
7266
|
+
posture: context.summary.posture,
|
|
7267
|
+
...context.summary.ownerDid === void 0 ? {} : { ownerDid: context.summary.ownerDid },
|
|
7268
|
+
...context.summary.principalDid === void 0 ? {} : { principalDid: context.summary.principalDid },
|
|
7269
|
+
...context.summary.space === void 0 ? {} : { space: context.summary.space }
|
|
7270
|
+
}, input.owner);
|
|
7271
|
+
if (!resolution.ok) {
|
|
7272
|
+
return {
|
|
7273
|
+
status: "error",
|
|
7274
|
+
error: operationError("INPUT_INVALID", resolution.message, { details: { reason: resolution.code } })
|
|
7275
|
+
};
|
|
7276
|
+
}
|
|
7277
|
+
const owner = resolution.owner;
|
|
7278
|
+
const skipped2 = runtime.skippedGrantReasons ?? [];
|
|
7279
|
+
const pkh = nodeSdk4.parsePkhDid(owner);
|
|
7280
|
+
const kv = node.kvForSpace(nodeSdk4.makePkhSpaceId(pkh.address, pkh.chainId, "secrets"));
|
|
7281
|
+
const outcomes = await Promise.all(input.names.map(async (name) => {
|
|
7282
|
+
const path = input.scope === void 0 ? `vault/secrets/${name}` : `vault/secrets/scoped/${input.scope}/${name}`;
|
|
7283
|
+
try {
|
|
7284
|
+
const result = await kv.get(path, { raw: true, prefix: "", timeout: PROBE_TIMEOUT_MS });
|
|
7285
|
+
if (result.ok) return "present";
|
|
7286
|
+
const error = result.error;
|
|
7287
|
+
if (typeof error === "object" && error !== null && "code" in error && error.code === "KV_NOT_FOUND") {
|
|
7288
|
+
return "pending";
|
|
7289
|
+
}
|
|
7290
|
+
const refusal = kvRefusal(error);
|
|
7291
|
+
if (refusal === "auth_required") return "auth_required";
|
|
7292
|
+
if (refusal === "permission_denied") return "denied";
|
|
7293
|
+
return "unreachable";
|
|
7294
|
+
} catch {
|
|
7295
|
+
return "unreachable";
|
|
7296
|
+
}
|
|
7297
|
+
}));
|
|
7298
|
+
const namesWith = (outcome) => input.names.filter((_, index) => outcomes[index] === outcome);
|
|
7299
|
+
const present = namesWith("present");
|
|
7300
|
+
const pending = namesWith("pending");
|
|
7301
|
+
const denied = namesWith("denied");
|
|
7302
|
+
if (present.length + pending.length < input.names.length) {
|
|
7303
|
+
if (outcomes.includes("auth_required") || skipped2.includes("activation_unauthenticated")) {
|
|
7304
|
+
return {
|
|
7305
|
+
status: "error",
|
|
7306
|
+
error: operationError(
|
|
7307
|
+
"AUTH_REQUIRED",
|
|
7308
|
+
"The node did not accept this profile's stored session. Sign in again, then retry.",
|
|
7309
|
+
{ retryable: false, details: { recovery: signInRecovery(context.summary.profile, context.summary.posture) } }
|
|
7310
|
+
)
|
|
7311
|
+
};
|
|
7312
|
+
}
|
|
7313
|
+
if (outcomes.includes("unreachable") || skipped2.includes("activation_unavailable")) {
|
|
7314
|
+
return {
|
|
7315
|
+
status: "error",
|
|
7316
|
+
error: operationError("NODE_UNREACHABLE", "The TinyCloud node could not be reached.", { retryable: true })
|
|
7317
|
+
};
|
|
7318
|
+
}
|
|
7319
|
+
}
|
|
7320
|
+
const recovery = pending.length === 0 ? void 0 : missingSecretRecovery({
|
|
7321
|
+
owner,
|
|
7322
|
+
local,
|
|
7323
|
+
names: pending,
|
|
7324
|
+
...input.scope === void 0 ? {} : { scope: input.scope }
|
|
7325
|
+
});
|
|
7326
|
+
return {
|
|
7327
|
+
status: "ok",
|
|
7328
|
+
output: {
|
|
7329
|
+
status: denied.length > 0 ? "consent_required" : pending.length > 0 ? "pending" : "ready",
|
|
7330
|
+
owner,
|
|
7331
|
+
names: [...input.names],
|
|
7332
|
+
...input.scope === void 0 ? {} : { scope: input.scope },
|
|
7333
|
+
present,
|
|
7334
|
+
pending,
|
|
7335
|
+
denied,
|
|
7336
|
+
...recovery === void 0 ? {} : recovery.kind === "add_secret" ? { setup: recovery.setup } : { hint: recovery.hint }
|
|
7337
|
+
}
|
|
7338
|
+
};
|
|
7339
|
+
}
|
|
7340
|
+
var PROBE_TIMEOUT_MS, SECRETS_STATUS_DEADLINE_MS, SecretsStatusInputSchema, SecretSetupSchema, SecretsStatusOutputSchema, SECRETS_STATUS_POSTURES, SECRETS_STATUS_EXPOSURE, SECRETS_STATUS_SENSITIVITY, secretsStatusOperationDefinition, secretsStatusOperationDefinitions;
|
|
7341
|
+
var init_secrets_status = __esm({
|
|
7342
|
+
"src/operations/secrets-status.ts"() {
|
|
7343
|
+
"use strict";
|
|
7344
|
+
init_errors2();
|
|
7345
|
+
init_kv_failure();
|
|
7346
|
+
init_recovery();
|
|
7347
|
+
init_secret_request();
|
|
7348
|
+
init_secrets();
|
|
7349
|
+
PROBE_TIMEOUT_MS = 15e3;
|
|
7350
|
+
SECRETS_STATUS_DEADLINE_MS = 18e3;
|
|
7351
|
+
SecretsStatusInputSchema = z6.object({
|
|
7352
|
+
names: z6.array(z6.string()),
|
|
7353
|
+
scope: z6.string().optional(),
|
|
7354
|
+
owner: z6.string().optional()
|
|
7355
|
+
}).strict().superRefine((input, context) => {
|
|
7356
|
+
try {
|
|
7357
|
+
validSecretNames(input.names);
|
|
7358
|
+
} catch {
|
|
7359
|
+
context.addIssue({ code: z6.ZodIssueCode.custom, path: ["names"], message: "Invalid secret names." });
|
|
7360
|
+
}
|
|
7361
|
+
try {
|
|
7362
|
+
const scope = canonicalizeSecretScope(input.scope);
|
|
7363
|
+
if (scope !== void 0 && !isValidSecretRequestScope(scope)) throw new Error("invalid scope");
|
|
7364
|
+
} catch {
|
|
7365
|
+
context.addIssue({ code: z6.ZodIssueCode.custom, path: ["scope"], message: "Invalid secret scope." });
|
|
7366
|
+
}
|
|
7367
|
+
if (input.owner !== void 0) {
|
|
7368
|
+
try {
|
|
7369
|
+
canonicalSecretRequestOwner(input.owner);
|
|
7370
|
+
} catch {
|
|
7371
|
+
context.addIssue({ code: z6.ZodIssueCode.custom, path: ["owner"], message: "Invalid owner." });
|
|
7372
|
+
}
|
|
7373
|
+
}
|
|
7374
|
+
}).transform((input) => {
|
|
7375
|
+
const scope = canonicalizeSecretScope(input.scope);
|
|
7376
|
+
return {
|
|
7377
|
+
names: validSecretNames(input.names),
|
|
7378
|
+
...scope === void 0 ? {} : { scope },
|
|
7379
|
+
...input.owner === void 0 ? {} : { owner: canonicalSecretRequestOwner(input.owner) }
|
|
7380
|
+
};
|
|
7381
|
+
});
|
|
7382
|
+
SecretSetupSchema = z6.object({
|
|
7383
|
+
url: z6.string().url(),
|
|
7384
|
+
names: z6.array(z6.string()).min(1),
|
|
7385
|
+
scope: z6.string().optional(),
|
|
7386
|
+
owner: z6.string(),
|
|
7387
|
+
action: z6.literal("add_secret")
|
|
7388
|
+
}).strict();
|
|
7389
|
+
SecretsStatusOutputSchema = z6.object({
|
|
7390
|
+
status: z6.enum(["ready", "pending", "consent_required"]),
|
|
7391
|
+
owner: z6.string(),
|
|
7392
|
+
names: z6.array(z6.string()).min(1),
|
|
7393
|
+
scope: z6.string().optional(),
|
|
7394
|
+
present: z6.array(z6.string()),
|
|
7395
|
+
pending: z6.array(z6.string()),
|
|
7396
|
+
denied: z6.array(z6.string()),
|
|
7397
|
+
setup: SecretSetupSchema.optional(),
|
|
7398
|
+
hint: z6.string().optional()
|
|
7399
|
+
}).strict();
|
|
7400
|
+
SECRETS_STATUS_POSTURES = [
|
|
7401
|
+
"owner-openkey",
|
|
7402
|
+
"delegate-session",
|
|
7403
|
+
"local-owner-key"
|
|
7404
|
+
];
|
|
7405
|
+
SECRETS_STATUS_EXPOSURE = {
|
|
7406
|
+
cli: { status: "required" },
|
|
7407
|
+
mcp: { status: "required" },
|
|
7408
|
+
skill: { status: "required" },
|
|
7409
|
+
docs: { status: "required" }
|
|
7410
|
+
};
|
|
7411
|
+
SECRETS_STATUS_SENSITIVITY = {
|
|
7412
|
+
input: [],
|
|
7413
|
+
output: []
|
|
7414
|
+
};
|
|
7415
|
+
secretsStatusOperationDefinition = {
|
|
7416
|
+
id: "tinycloud.secrets.status",
|
|
7417
|
+
version: 1,
|
|
7418
|
+
title: "Check whether TinyCloud secrets exist",
|
|
7419
|
+
description: "Report, without reading any value, which named secrets exist in the owner's secrets space: ready, pending (with a Secret Manager request link), or consent_required for names this identity may not read. Each name needs tinycloud.kv/get; this operation never requests it. It never signs in: it uses only the profile's stored session, and writes no profile state.",
|
|
7420
|
+
input: SecretsStatusInputSchema,
|
|
7421
|
+
output: SecretsStatusOutputSchema,
|
|
7422
|
+
effects: ["read"],
|
|
7423
|
+
runtime: "restored",
|
|
7424
|
+
postures: SECRETS_STATUS_POSTURES,
|
|
7425
|
+
exposure: SECRETS_STATUS_EXPOSURE,
|
|
7426
|
+
sensitivity: SECRETS_STATUS_SENSITIVITY,
|
|
7427
|
+
deadlineMs: SECRETS_STATUS_DEADLINE_MS,
|
|
7428
|
+
// Never evaluated against the session: a missing grant is reported as
|
|
7429
|
+
// `consent_required`, never saved as an authority request.
|
|
7430
|
+
authority: async () => [],
|
|
7431
|
+
execute: executeSecretsStatus
|
|
7432
|
+
};
|
|
7433
|
+
secretsStatusOperationDefinitions = [
|
|
7434
|
+
secretsStatusOperationDefinition
|
|
7435
|
+
];
|
|
7436
|
+
}
|
|
7437
|
+
});
|
|
7438
|
+
|
|
6803
7439
|
// src/operations/sql.ts
|
|
6804
7440
|
import nodeSqlParser from "node-sql-parser";
|
|
6805
|
-
import { z as
|
|
7441
|
+
import { z as z7 } from "zod";
|
|
6806
7442
|
async function planSqlRead(context, input) {
|
|
6807
7443
|
const space = resolveSqlSpace(context, input.space);
|
|
6808
7444
|
return [{
|
|
@@ -7120,7 +7756,7 @@ var init_sql = __esm({
|
|
|
7120
7756
|
"use strict";
|
|
7121
7757
|
init_errors2();
|
|
7122
7758
|
init_secrets();
|
|
7123
|
-
|
|
7759
|
+
init_kv_failure();
|
|
7124
7760
|
DEFAULT_MAX_ROWS = 100;
|
|
7125
7761
|
MAX_MAX_ROWS = 1e3;
|
|
7126
7762
|
DEFAULT_MAX_BYTES = 1024 * 1024;
|
|
@@ -7136,29 +7772,29 @@ WHERE type IN ('table', 'view', 'index', 'trigger')
|
|
|
7136
7772
|
AND name NOT LIKE 'sqlite_%'
|
|
7137
7773
|
ORDER BY type, name`;
|
|
7138
7774
|
sqlParser = new nodeSqlParser.Parser();
|
|
7139
|
-
SpaceSchema2 =
|
|
7775
|
+
SpaceSchema2 = z7.string().min(1).refine(
|
|
7140
7776
|
(space) => (/^[A-Za-z0-9_-]+$/.test(space) || space.startsWith("tinycloud:")) && !space.includes("*"),
|
|
7141
7777
|
"Invalid TinyCloud space."
|
|
7142
7778
|
);
|
|
7143
|
-
DatabaseSchema =
|
|
7779
|
+
DatabaseSchema = z7.string().min(1).max(256).refine(
|
|
7144
7780
|
(database) => database !== "/" && !database.endsWith("/") && !database.includes("*") && !/[\u0000-\u001f\u007f]/.test(database),
|
|
7145
7781
|
"Invalid SQLite database name."
|
|
7146
7782
|
);
|
|
7147
|
-
SafeNumberSchema =
|
|
7783
|
+
SafeNumberSchema = z7.number().finite().refine(
|
|
7148
7784
|
(value) => !Number.isInteger(value) || Number.isSafeInteger(value),
|
|
7149
7785
|
"SQLite integer parameters must be JavaScript safe integers."
|
|
7150
7786
|
);
|
|
7151
|
-
Base64Schema =
|
|
7787
|
+
Base64Schema = z7.string().max(Math.ceil(MAX_MAX_BYTES / 3) * 4).refine(
|
|
7152
7788
|
isCanonicalBase642,
|
|
7153
7789
|
"Invalid base64 BLOB value."
|
|
7154
7790
|
);
|
|
7155
|
-
SqlInputValueSchema =
|
|
7156
|
-
|
|
7791
|
+
SqlInputValueSchema = z7.union([
|
|
7792
|
+
z7.null(),
|
|
7157
7793
|
SafeNumberSchema,
|
|
7158
|
-
|
|
7159
|
-
|
|
7794
|
+
z7.string().max(MAX_MAX_BYTES),
|
|
7795
|
+
z7.object({ type: z7.literal("blob"), base64: Base64Schema }).strict()
|
|
7160
7796
|
]);
|
|
7161
|
-
SqlParamsSchema =
|
|
7797
|
+
SqlParamsSchema = z7.array(SqlInputValueSchema).max(MAX_PARAMETERS).superRefine(
|
|
7162
7798
|
(params, context) => {
|
|
7163
7799
|
const totalBytes = params.reduce(
|
|
7164
7800
|
(total, value) => total + sqlInputValueBytes(value),
|
|
@@ -7166,13 +7802,13 @@ ORDER BY type, name`;
|
|
|
7166
7802
|
);
|
|
7167
7803
|
if (totalBytes > MAX_MAX_BYTES) {
|
|
7168
7804
|
context.addIssue({
|
|
7169
|
-
code:
|
|
7805
|
+
code: z7.ZodIssueCode.custom,
|
|
7170
7806
|
message: "SQLite query parameters exceed the 4 MiB input limit."
|
|
7171
7807
|
});
|
|
7172
7808
|
}
|
|
7173
7809
|
}
|
|
7174
7810
|
);
|
|
7175
|
-
RequiredSqlParamsSchema =
|
|
7811
|
+
RequiredSqlParamsSchema = z7.array(SqlInputValueSchema).min(1).max(MAX_PARAMETERS).superRefine(
|
|
7176
7812
|
(params, context) => {
|
|
7177
7813
|
const totalBytes = params.reduce(
|
|
7178
7814
|
(total, value) => total + sqlInputValueBytes(value),
|
|
@@ -7180,88 +7816,88 @@ ORDER BY type, name`;
|
|
|
7180
7816
|
);
|
|
7181
7817
|
if (totalBytes > MAX_MAX_BYTES) {
|
|
7182
7818
|
context.addIssue({
|
|
7183
|
-
code:
|
|
7819
|
+
code: z7.ZodIssueCode.custom,
|
|
7184
7820
|
message: "SQLite query parameters exceed the 4 MiB input limit."
|
|
7185
7821
|
});
|
|
7186
7822
|
}
|
|
7187
7823
|
}
|
|
7188
7824
|
);
|
|
7189
|
-
SqlOutputValueSchema =
|
|
7190
|
-
|
|
7825
|
+
SqlOutputValueSchema = z7.union([
|
|
7826
|
+
z7.null(),
|
|
7191
7827
|
SafeNumberSchema,
|
|
7192
|
-
|
|
7193
|
-
|
|
7194
|
-
type:
|
|
7195
|
-
base64:
|
|
7196
|
-
byteLength:
|
|
7828
|
+
z7.string(),
|
|
7829
|
+
z7.object({
|
|
7830
|
+
type: z7.literal("blob"),
|
|
7831
|
+
base64: z7.string(),
|
|
7832
|
+
byteLength: z7.number().int().nonnegative()
|
|
7197
7833
|
}).strict()
|
|
7198
7834
|
]);
|
|
7199
|
-
SqlTargetInputSchema =
|
|
7835
|
+
SqlTargetInputSchema = z7.object({
|
|
7200
7836
|
space: SpaceSchema2,
|
|
7201
7837
|
database: DatabaseSchema
|
|
7202
7838
|
}).strict();
|
|
7203
|
-
SqlQueryInputSchema =
|
|
7839
|
+
SqlQueryInputSchema = z7.object({
|
|
7204
7840
|
space: SpaceSchema2,
|
|
7205
7841
|
database: DatabaseSchema,
|
|
7206
|
-
sql:
|
|
7842
|
+
sql: z7.string().min(1).max(MAX_SQL_LENGTH).superRefine((sql, context) => {
|
|
7207
7843
|
if (!isSingleReadQuery(sql)) {
|
|
7208
7844
|
context.addIssue({
|
|
7209
|
-
code:
|
|
7845
|
+
code: z7.ZodIssueCode.custom,
|
|
7210
7846
|
message: "SQL must parse as exactly one SQLite SELECT statement."
|
|
7211
7847
|
});
|
|
7212
7848
|
}
|
|
7213
7849
|
}),
|
|
7214
7850
|
params: SqlParamsSchema.optional(),
|
|
7215
|
-
maxRows:
|
|
7216
|
-
maxBytes:
|
|
7851
|
+
maxRows: z7.number().int().positive().max(MAX_MAX_ROWS).optional(),
|
|
7852
|
+
maxBytes: z7.number().int().positive().max(MAX_MAX_BYTES).optional()
|
|
7217
7853
|
}).strict();
|
|
7218
|
-
SqlExecuteInputSchema =
|
|
7854
|
+
SqlExecuteInputSchema = z7.object({
|
|
7219
7855
|
space: SpaceSchema2,
|
|
7220
7856
|
database: DatabaseSchema,
|
|
7221
|
-
sql:
|
|
7857
|
+
sql: z7.string().min(1).max(MAX_SQL_LENGTH),
|
|
7222
7858
|
params: RequiredSqlParamsSchema,
|
|
7223
|
-
acknowledgeDatabaseWideAuthority:
|
|
7859
|
+
acknowledgeDatabaseWideAuthority: z7.literal(true).describe(SQL_WRITE_AUTHORITY_NOTICE)
|
|
7224
7860
|
}).strict().superRefine((input, context) => {
|
|
7225
7861
|
if (parseSingleDmlStatement(input.sql, input.params.length) === void 0) {
|
|
7226
7862
|
context.addIssue({
|
|
7227
|
-
code:
|
|
7863
|
+
code: z7.ZodIssueCode.custom,
|
|
7228
7864
|
path: ["sql"],
|
|
7229
7865
|
message: "SQL must parse as exactly one parameterized INSERT, UPDATE, or DELETE with one value per positional placeholder."
|
|
7230
7866
|
});
|
|
7231
7867
|
}
|
|
7232
7868
|
});
|
|
7233
|
-
SqlLimitsOutputSchema =
|
|
7234
|
-
maxRows:
|
|
7235
|
-
maxBytes:
|
|
7236
|
-
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")
|
|
7237
7873
|
}).strict();
|
|
7238
|
-
SqlSchemaInspectOutputSchema =
|
|
7239
|
-
space:
|
|
7240
|
-
database:
|
|
7241
|
-
objects:
|
|
7242
|
-
type:
|
|
7243
|
-
name:
|
|
7244
|
-
tableName:
|
|
7245
|
-
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()
|
|
7246
7882
|
}).strict()),
|
|
7247
|
-
count:
|
|
7883
|
+
count: z7.number().int().nonnegative(),
|
|
7248
7884
|
limits: SqlLimitsOutputSchema
|
|
7249
7885
|
}).strict();
|
|
7250
|
-
SqlQueryOutputSchema =
|
|
7251
|
-
space:
|
|
7252
|
-
database:
|
|
7253
|
-
columns:
|
|
7254
|
-
rows:
|
|
7255
|
-
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(),
|
|
7256
7892
|
limits: SqlLimitsOutputSchema
|
|
7257
7893
|
}).strict();
|
|
7258
|
-
SqlExecuteOutputSchema =
|
|
7259
|
-
space:
|
|
7260
|
-
database:
|
|
7261
|
-
statementType:
|
|
7262
|
-
changes:
|
|
7263
|
-
lastInsertRowId:
|
|
7264
|
-
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)
|
|
7265
7901
|
}).strict();
|
|
7266
7902
|
AUTHENTICATED_POSTURES2 = [
|
|
7267
7903
|
"owner-openkey",
|
|
@@ -7361,6 +7997,8 @@ var init_contract = __esm({
|
|
|
7361
7997
|
"resources_mismatch",
|
|
7362
7998
|
"unsupported",
|
|
7363
7999
|
"activation_rejected",
|
|
8000
|
+
"activation_unavailable",
|
|
8001
|
+
"activation_unauthenticated",
|
|
7364
8002
|
"invalid"
|
|
7365
8003
|
];
|
|
7366
8004
|
DELEGATION_CID_PATTERN = "^bafkr4i[a-z2-7]{52}$";
|
|
@@ -7369,7 +8007,7 @@ var init_contract = __esm({
|
|
|
7369
8007
|
|
|
7370
8008
|
// src/delegation-binding.ts
|
|
7371
8009
|
import { join as join2 } from "path";
|
|
7372
|
-
import * as
|
|
8010
|
+
import * as nodeSdk5 from "@tinycloud/node-sdk";
|
|
7373
8011
|
function bindingMigrationPath(profile) {
|
|
7374
8012
|
return join2(profilePath(profile), "delegation-binding-migration.json");
|
|
7375
8013
|
}
|
|
@@ -7465,6 +8103,9 @@ function skipped(reason) {
|
|
|
7465
8103
|
}
|
|
7466
8104
|
async function replayProfileDelegations(profile, node, options) {
|
|
7467
8105
|
await prepareStoredDelegationReplay(profile, node, { host: options.host, migrate: options.migrate });
|
|
8106
|
+
return replayStoredDelegationRecords(profile, node, options);
|
|
8107
|
+
}
|
|
8108
|
+
async function replayStoredDelegationRecords(profile, node, options) {
|
|
7468
8109
|
const installed = [];
|
|
7469
8110
|
const warnings = [];
|
|
7470
8111
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -7505,7 +8146,7 @@ function storedDelegationForAnotherKey(entry, node) {
|
|
|
7505
8146
|
const audience = verifiedCompactAudience(node, delegation);
|
|
7506
8147
|
if (audience === void 0) return false;
|
|
7507
8148
|
try {
|
|
7508
|
-
return
|
|
8149
|
+
return nodeSdk5.principalDidEquals(delegation.delegateDID, audience) && !nodeSdk5.principalDidEquals(audience, node.sessionDid);
|
|
7509
8150
|
} catch {
|
|
7510
8151
|
return false;
|
|
7511
8152
|
}
|
|
@@ -7539,7 +8180,7 @@ async function assess(node, entry, options, mode) {
|
|
|
7539
8180
|
let outsideRequest = false;
|
|
7540
8181
|
let effectivePermissions;
|
|
7541
8182
|
try {
|
|
7542
|
-
const installed = await
|
|
8183
|
+
const installed = await nodeSdk5.activateValidatedRuntimeDelegation(node, toActivate, {
|
|
7543
8184
|
host: options.host,
|
|
7544
8185
|
authorize: (effective) => {
|
|
7545
8186
|
verifyCompactProof(toActivate);
|
|
@@ -7571,7 +8212,7 @@ function verifyCompactProof(delegation) {
|
|
|
7571
8212
|
try {
|
|
7572
8213
|
parseSignedCompactUcanAttenuation(withoutBearer(authorization), delegation.cid);
|
|
7573
8214
|
} catch {
|
|
7574
|
-
throw new
|
|
8215
|
+
throw new nodeSdk5.RuntimeDelegationRejectedError("proof_invalid", "The compact delegation proof is invalid.");
|
|
7575
8216
|
}
|
|
7576
8217
|
}
|
|
7577
8218
|
function rejectionReason(error) {
|
|
@@ -7588,7 +8229,7 @@ async function bindingMigrationRecorded(profile) {
|
|
|
7588
8229
|
async function signedCapabilities(node, delegation, host) {
|
|
7589
8230
|
let signed;
|
|
7590
8231
|
try {
|
|
7591
|
-
await
|
|
8232
|
+
await nodeSdk5.activateValidatedRuntimeDelegation(node, delegation, {
|
|
7592
8233
|
// The transport host is not signed authority; check it at replay instead.
|
|
7593
8234
|
host: delegation.host ?? host,
|
|
7594
8235
|
authorize: (effective) => {
|
|
@@ -7651,7 +8292,7 @@ var init_delegation_binding = __esm({
|
|
|
7651
8292
|
});
|
|
7652
8293
|
|
|
7653
8294
|
// src/operations/status.ts
|
|
7654
|
-
import { z as
|
|
8295
|
+
import { z as z8 } from "zod";
|
|
7655
8296
|
function createStatusDefinition(params) {
|
|
7656
8297
|
return {
|
|
7657
8298
|
id: params.id,
|
|
@@ -7776,27 +8417,27 @@ var init_status = __esm({
|
|
|
7776
8417
|
init_delegation_binding();
|
|
7777
8418
|
init_errors2();
|
|
7778
8419
|
init_state();
|
|
7779
|
-
StatusInputSchema =
|
|
7780
|
-
StatusOutputSchema =
|
|
7781
|
-
profile:
|
|
7782
|
-
host:
|
|
7783
|
-
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([
|
|
7784
8425
|
"owner-openkey",
|
|
7785
8426
|
"delegate-session",
|
|
7786
8427
|
"local-owner-key",
|
|
7787
8428
|
"unauthenticated"
|
|
7788
8429
|
]),
|
|
7789
|
-
operatorType:
|
|
7790
|
-
principalDid:
|
|
7791
|
-
sessionDid:
|
|
7792
|
-
ownerDid:
|
|
7793
|
-
space:
|
|
7794
|
-
session:
|
|
7795
|
-
present:
|
|
7796
|
-
expired:
|
|
7797
|
-
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()
|
|
7798
8439
|
}).strict(),
|
|
7799
|
-
liveAdditionalDelegationCount:
|
|
8440
|
+
liveAdditionalDelegationCount: z8.number().int().nonnegative().describe(
|
|
7800
8441
|
"Stored, unexpired compact-UCAN or signed-login delegations with a valid request binding. Each still requires local proof validation and node activation before it grants authority."
|
|
7801
8442
|
)
|
|
7802
8443
|
}).strict();
|
|
@@ -7840,7 +8481,8 @@ function registeredOperationDefinitions() {
|
|
|
7840
8481
|
...authOperationDefinitions,
|
|
7841
8482
|
...explorationOperationDefinitions,
|
|
7842
8483
|
...sqlOperationDefinitions,
|
|
7843
|
-
...secretsGetOperationDefinitions
|
|
8484
|
+
...secretsGetOperationDefinitions,
|
|
8485
|
+
...secretsStatusOperationDefinitions
|
|
7844
8486
|
]);
|
|
7845
8487
|
return operationDefinitions;
|
|
7846
8488
|
}
|
|
@@ -7872,6 +8514,7 @@ var init_registry = __esm({
|
|
|
7872
8514
|
init_auth();
|
|
7873
8515
|
init_exploration();
|
|
7874
8516
|
init_secrets_get();
|
|
8517
|
+
init_secrets_status();
|
|
7875
8518
|
init_sql();
|
|
7876
8519
|
init_status();
|
|
7877
8520
|
}
|
|
@@ -7924,6 +8567,9 @@ function resolvePosture(profile) {
|
|
|
7924
8567
|
if (profile.authMethod === "local") return "local-owner-key";
|
|
7925
8568
|
return "owner-openkey";
|
|
7926
8569
|
}
|
|
8570
|
+
function isLocalOwnerProfile(profile) {
|
|
8571
|
+
return resolvePosture(profile) === "local-owner-key" || profile.authMethod === "local" || typeof profile.privateKey === "string";
|
|
8572
|
+
}
|
|
7927
8573
|
function profileNotFound2(profile) {
|
|
7928
8574
|
return {
|
|
7929
8575
|
ok: false,
|
|
@@ -7995,7 +8641,7 @@ var runtime_exports = {};
|
|
|
7995
8641
|
__export(runtime_exports, {
|
|
7996
8642
|
createInvocationRuntime: () => createInvocationRuntime
|
|
7997
8643
|
});
|
|
7998
|
-
import * as
|
|
8644
|
+
import * as nodeSdk6 from "@tinycloud/node-sdk";
|
|
7999
8645
|
async function createInvocationRuntime(target, requirement = "authenticated") {
|
|
8000
8646
|
const resolved = await resolveInvocationProfile(target);
|
|
8001
8647
|
if (!resolved.ok) {
|
|
@@ -8009,11 +8655,14 @@ async function createInvocationRuntime(target, requirement = "authenticated") {
|
|
|
8009
8655
|
if (requirement === "inspection") {
|
|
8010
8656
|
return { ok: true, context: { summary } };
|
|
8011
8657
|
}
|
|
8658
|
+
if (requirement === "restored") {
|
|
8659
|
+
return createRestoredRuntime(target, resolved);
|
|
8660
|
+
}
|
|
8012
8661
|
const profileName = summary.profile;
|
|
8013
8662
|
try {
|
|
8014
8663
|
const {
|
|
8015
8664
|
TinyCloudNode: TinyCloudNodeConstructor
|
|
8016
|
-
} =
|
|
8665
|
+
} = nodeSdk6;
|
|
8017
8666
|
const explicitPrivateKeyOverride = typeof target.privateKey === "string";
|
|
8018
8667
|
const [session, key] = explicitPrivateKeyOverride ? [null, null] : await Promise.all([
|
|
8019
8668
|
readSession(profileName),
|
|
@@ -8091,7 +8740,9 @@ async function createInvocationRuntime(target, requirement = "authenticated") {
|
|
|
8091
8740
|
const runtime = {
|
|
8092
8741
|
node,
|
|
8093
8742
|
granted: canonicalizeCapabilities(livePermissions),
|
|
8094
|
-
...warnings.length === 0 ? {} : { warnings }
|
|
8743
|
+
...warnings.length === 0 ? {} : { warnings },
|
|
8744
|
+
localOwner: explicitPrivateKeyOverride || isLocalOwnerProfile(profile),
|
|
8745
|
+
skippedGrantReasons: warnings.map((warning) => warning.reason)
|
|
8095
8746
|
};
|
|
8096
8747
|
return {
|
|
8097
8748
|
ok: true,
|
|
@@ -8115,11 +8766,112 @@ async function createInvocationRuntime(target, requirement = "authenticated") {
|
|
|
8115
8766
|
));
|
|
8116
8767
|
}
|
|
8117
8768
|
}
|
|
8769
|
+
async function createRestoredRuntime(target, resolved) {
|
|
8770
|
+
const summary = resolved.context;
|
|
8771
|
+
const profileName = summary.profile;
|
|
8772
|
+
const profile = resolved.profile;
|
|
8773
|
+
if (typeof target.privateKey === "string") {
|
|
8774
|
+
return failed(summary, operationError(
|
|
8775
|
+
"AUTH_REQUIRED",
|
|
8776
|
+
"This operation never signs in; an explicit private key has no stored session.",
|
|
8777
|
+
{ details: { reason: "explicit_key" } }
|
|
8778
|
+
));
|
|
8779
|
+
}
|
|
8780
|
+
try {
|
|
8781
|
+
const actualPosture = resolvePosture(profile);
|
|
8782
|
+
const selectedPosture = profile.authMethod === "local" ? "local-owner-key" : summary.posture;
|
|
8783
|
+
if (actualPosture !== summary.posture || selectedPosture !== summary.posture) {
|
|
8784
|
+
return failed(
|
|
8785
|
+
{ ...summary, posture: actualPosture !== summary.posture ? actualPosture : selectedPosture },
|
|
8786
|
+
operationError(
|
|
8787
|
+
"PROFILE_POSTURE_NOT_ALLOWED",
|
|
8788
|
+
"The selected profile changed posture during runtime resolution."
|
|
8789
|
+
)
|
|
8790
|
+
);
|
|
8791
|
+
}
|
|
8792
|
+
const [session, key] = await Promise.all([
|
|
8793
|
+
readSession(profileName),
|
|
8794
|
+
readJson(`${profilePath(profileName)}/key.json`)
|
|
8795
|
+
]);
|
|
8796
|
+
const activeSession = session === null ? void 0 : normalizeSession(session, key);
|
|
8797
|
+
if (activeSession === void 0) {
|
|
8798
|
+
return failed(summary, operationError(
|
|
8799
|
+
"SESSION_NOT_FOUND",
|
|
8800
|
+
"The selected profile does not have an active session. This operation never signs in.",
|
|
8801
|
+
{ details: { recovery: signInRecovery(profileName, summary.posture) } }
|
|
8802
|
+
));
|
|
8803
|
+
}
|
|
8804
|
+
if (!await bindingMigrationRecorded(profileName)) {
|
|
8805
|
+
const records = await readAdditionalDelegations(profileName);
|
|
8806
|
+
if (records.some(
|
|
8807
|
+
(entry) => storedDelegationKind(entry) === "compact" && !("authorityRequest" in entry)
|
|
8808
|
+
)) {
|
|
8809
|
+
return failed(summary, operationError(
|
|
8810
|
+
"PROFILE_MIGRATION_REQUIRED",
|
|
8811
|
+
`Profile "${profileName}" has a one-time stored-grant upgrade to finish. Run one authenticated command on it (tc --profile <profile> auth caps), then retry.`,
|
|
8812
|
+
{ retryable: false, details: { recovery: migrationRecovery(profileName) } }
|
|
8813
|
+
));
|
|
8814
|
+
}
|
|
8815
|
+
}
|
|
8816
|
+
const { TinyCloudNode: TinyCloudNodeConstructor } = nodeSdk6;
|
|
8817
|
+
const node = new TinyCloudNodeConstructor({
|
|
8818
|
+
host: summary.host,
|
|
8819
|
+
...typeof profile.privateKey === "string" ? { privateKey: profile.privateKey } : {}
|
|
8820
|
+
});
|
|
8821
|
+
try {
|
|
8822
|
+
await node.restoreSession(activeSession);
|
|
8823
|
+
} catch (error) {
|
|
8824
|
+
if (isRecord7(error) && error.code === "AUTH_EXPIRED") {
|
|
8825
|
+
return failed(summary, operationError(
|
|
8826
|
+
"AUTH_REQUIRED",
|
|
8827
|
+
"The stored session has expired or is no longer valid. Sign in again.",
|
|
8828
|
+
{ retryable: false, details: { recovery: signInRecovery(profileName, summary.posture) } }
|
|
8829
|
+
));
|
|
8830
|
+
}
|
|
8831
|
+
throw error;
|
|
8832
|
+
}
|
|
8833
|
+
const activeSessionDid = normalizeDid(node.sessionDid);
|
|
8834
|
+
const livePrincipalDid = normalizeDid(node.did);
|
|
8835
|
+
const livePermissions = [...node.getVerifiedSessionCapabilities()];
|
|
8836
|
+
const replay = await replayStoredDelegationRecords(
|
|
8837
|
+
profileName,
|
|
8838
|
+
node,
|
|
8839
|
+
{ host: summary.host, resolveSpace: operationSpaceResolver(node, summary.space) }
|
|
8840
|
+
);
|
|
8841
|
+
for (const installed of replay.installed) {
|
|
8842
|
+
livePermissions.push(...installed.effectivePermissions);
|
|
8843
|
+
}
|
|
8844
|
+
const runtime = {
|
|
8845
|
+
node,
|
|
8846
|
+
granted: canonicalizeCapabilities(livePermissions),
|
|
8847
|
+
...replay.warnings.length === 0 ? {} : { warnings: replay.warnings },
|
|
8848
|
+
localOwner: isLocalOwnerProfile(profile),
|
|
8849
|
+
skippedGrantReasons: replay.warnings.map((warning) => warning.reason)
|
|
8850
|
+
};
|
|
8851
|
+
return {
|
|
8852
|
+
ok: true,
|
|
8853
|
+
context: {
|
|
8854
|
+
summary: {
|
|
8855
|
+
...summary,
|
|
8856
|
+
...livePrincipalDid === void 0 ? {} : { principalDid: livePrincipalDid },
|
|
8857
|
+
...activeSessionDid === void 0 ? {} : { sessionDid: activeSessionDid }
|
|
8858
|
+
},
|
|
8859
|
+
runtime
|
|
8860
|
+
}
|
|
8861
|
+
};
|
|
8862
|
+
} catch {
|
|
8863
|
+
return failed(summary, operationError(
|
|
8864
|
+
"NODE_ERROR",
|
|
8865
|
+
"The selected profile runtime could not be initialized.",
|
|
8866
|
+
{ retryable: true }
|
|
8867
|
+
));
|
|
8868
|
+
}
|
|
8869
|
+
}
|
|
8118
8870
|
function spaceForAuthenticatedPrincipal(principal2) {
|
|
8119
8871
|
if (principal2 === void 0) return void 0;
|
|
8120
8872
|
try {
|
|
8121
|
-
const pkh =
|
|
8122
|
-
return pkh === null ? void 0 :
|
|
8873
|
+
const pkh = nodeSdk6.parsePkhDid(principal2);
|
|
8874
|
+
return pkh === null ? void 0 : nodeSdk6.makePkhSpaceId(pkh.address, pkh.chainId, "secrets");
|
|
8123
8875
|
} catch {
|
|
8124
8876
|
return void 0;
|
|
8125
8877
|
}
|
|
@@ -8169,6 +8921,7 @@ var init_runtime = __esm({
|
|
|
8169
8921
|
init_authority();
|
|
8170
8922
|
init_errors2();
|
|
8171
8923
|
init_profile();
|
|
8924
|
+
init_recovery();
|
|
8172
8925
|
init_secrets();
|
|
8173
8926
|
init_state();
|
|
8174
8927
|
init_delegation_binding();
|
|
@@ -8183,11 +8936,14 @@ init_artifacts();
|
|
|
8183
8936
|
import { createHash } from "crypto";
|
|
8184
8937
|
import { isAbsolute as isAbsolute2 } from "path";
|
|
8185
8938
|
import {
|
|
8186
|
-
makePkhSpaceId as
|
|
8187
|
-
parsePkhDid as
|
|
8939
|
+
makePkhSpaceId as makePkhSpaceId4,
|
|
8940
|
+
parsePkhDid as parsePkhDid4,
|
|
8188
8941
|
parseSpaceUri as parseSpaceUri2
|
|
8189
8942
|
} from "@tinycloud/node-sdk";
|
|
8190
8943
|
|
|
8944
|
+
// src/redaction.ts
|
|
8945
|
+
init_recovery();
|
|
8946
|
+
|
|
8191
8947
|
// src/safe-values.ts
|
|
8192
8948
|
function safeOriginHost(value) {
|
|
8193
8949
|
if (typeof value !== "string" || value.length === 0) return void 0;
|
|
@@ -8264,7 +9020,25 @@ function redactOperationError(_definition, error) {
|
|
|
8264
9020
|
}
|
|
8265
9021
|
var SAFE_ERROR_DETAIL_POLICIES = {
|
|
8266
9022
|
INPUT_INVALID: {
|
|
8267
|
-
issues: (value) => Array.isArray(value) ? safeIssueCodes(value) : void 0
|
|
9023
|
+
issues: (value) => Array.isArray(value) ? safeIssueCodes(value) : void 0,
|
|
9024
|
+
reason: (value) => value === "OWNER_MISMATCH" || value === "OWNER_DID_UNKNOWN" ? value : void 0
|
|
9025
|
+
},
|
|
9026
|
+
AUTH_REQUIRED: {
|
|
9027
|
+
recovery: (value) => isSafeRecovery(value, "sign_in") ? { ...value } : void 0,
|
|
9028
|
+
reason: (value) => value === "explicit_key" ? value : void 0
|
|
9029
|
+
},
|
|
9030
|
+
SESSION_NOT_FOUND: {
|
|
9031
|
+
recovery: (value) => isSafeRecovery(value, "sign_in") ? { ...value } : void 0
|
|
9032
|
+
},
|
|
9033
|
+
PROFILE_MIGRATION_REQUIRED: {
|
|
9034
|
+
recovery: (value) => isSafeRecovery(value, "complete_migration") ? { ...value } : void 0
|
|
9035
|
+
},
|
|
9036
|
+
// `secrets.get` not found for an identity no request link serves.
|
|
9037
|
+
KV_NOT_FOUND: {
|
|
9038
|
+
recoveryKind: (value) => value === "put_locally" || value === "not_requestable" ? value : void 0,
|
|
9039
|
+
names: (value) => Array.isArray(value) && value.length >= 1 && value.length <= 10 && value.every((name) => typeof name === "string" && name.length <= 256 && /^[A-Z][A-Z0-9_]*$/.test(name)) ? [...value] : void 0,
|
|
9040
|
+
scope: (value) => typeof value === "string" && value.length <= 256 && /^[a-z0-9]+(-[a-z0-9]+)*$/.test(value) ? value : void 0,
|
|
9041
|
+
owner: (value) => typeof value === "string" && /^did:pkh:eip155:[1-9][0-9]*:0x[0-9a-fA-F]{40}$/.test(value) ? value : void 0
|
|
8268
9042
|
},
|
|
8269
9043
|
OPERATION_VERSION_UNSUPPORTED: {
|
|
8270
9044
|
supportedVersions: (value) => Array.isArray(value) && value.every((item) => Number.isInteger(item)) ? value : void 0
|
|
@@ -8346,7 +9120,7 @@ async function invokeOperation(operationId, operationVersion, invocationTarget,
|
|
|
8346
9120
|
operationError("INPUT_INVALID", "The invocation target is invalid.")
|
|
8347
9121
|
);
|
|
8348
9122
|
}
|
|
8349
|
-
|
|
9123
|
+
const run = withTinyCloudStateRoot(target.stateRoot, async () => {
|
|
8350
9124
|
const prepared = await prepareInvocation(
|
|
8351
9125
|
operationId,
|
|
8352
9126
|
operationVersion,
|
|
@@ -8356,6 +9130,33 @@ async function invokeOperation(operationId, operationVersion, invocationTarget,
|
|
|
8356
9130
|
if (isPreparedError(prepared)) return prepared.result;
|
|
8357
9131
|
return withRuntimeWarnings(await executePreparedInvocation(prepared), prepared);
|
|
8358
9132
|
});
|
|
9133
|
+
const { lookupOperation: lookupOperation2 } = await Promise.resolve().then(() => (init_registry(), registry_exports));
|
|
9134
|
+
const lookup = lookupOperation2(operationId, operationVersion);
|
|
9135
|
+
const deadlineMs = lookup.status === "found" ? lookup.definition.deadlineMs : void 0;
|
|
9136
|
+
if (deadlineMs === void 0) return run;
|
|
9137
|
+
return withResponseDeadline(run, deadlineMs, () => errorResult(
|
|
9138
|
+
{ operationId, operationVersion },
|
|
9139
|
+
unresolvedContextSummary(target),
|
|
9140
|
+
operationError(
|
|
9141
|
+
"NODE_UNREACHABLE",
|
|
9142
|
+
`The operation did not finish within ${deadlineMs / 1e3} s.`,
|
|
9143
|
+
{ retryable: true }
|
|
9144
|
+
)
|
|
9145
|
+
));
|
|
9146
|
+
}
|
|
9147
|
+
async function withResponseDeadline(run, deadlineMs, expired) {
|
|
9148
|
+
run.catch(() => {
|
|
9149
|
+
});
|
|
9150
|
+
let timer;
|
|
9151
|
+
const deadline = new Promise((resolve2) => {
|
|
9152
|
+
timer = setTimeout(() => resolve2(expired()), deadlineMs);
|
|
9153
|
+
timer.unref();
|
|
9154
|
+
});
|
|
9155
|
+
try {
|
|
9156
|
+
return await Promise.race([run, deadline]);
|
|
9157
|
+
} finally {
|
|
9158
|
+
clearTimeout(timer);
|
|
9159
|
+
}
|
|
8359
9160
|
}
|
|
8360
9161
|
function withRuntimeWarnings(result, prepared) {
|
|
8361
9162
|
const warnings = prepared.runtimeResolution.context.runtime?.warnings;
|
|
@@ -8430,7 +9231,7 @@ async function prepareInvocation(operationId, operationVersion, invocationTarget
|
|
|
8430
9231
|
)
|
|
8431
9232
|
) };
|
|
8432
9233
|
}
|
|
8433
|
-
if (target.privateKey === void 0 && definition.runtime
|
|
9234
|
+
if (target.privateKey === void 0 && requiresRuntime(definition.runtime) && isOwnerPosture(inspectionResolution.context.summary.posture) && target.allowOwnerProfile !== true) {
|
|
8434
9235
|
return { status: "error", result: errorResult(
|
|
8435
9236
|
operation,
|
|
8436
9237
|
inspectionResolution.context.summary,
|
|
@@ -8440,15 +9241,15 @@ async function prepareInvocation(operationId, operationVersion, invocationTarget
|
|
|
8440
9241
|
)
|
|
8441
9242
|
) };
|
|
8442
9243
|
}
|
|
8443
|
-
const runtimeResolution = definition.runtime === "inspection" ? inspectionResolution : await resolveRuntime(target,
|
|
9244
|
+
const runtimeResolution = definition.runtime === "inspection" ? inspectionResolution : await resolveRuntime(target, definition.runtime);
|
|
8444
9245
|
if (!runtimeResolution.ok) {
|
|
8445
9246
|
return { status: "error", result: errorResult(
|
|
8446
9247
|
operation,
|
|
8447
9248
|
runtimeResolution.context,
|
|
8448
|
-
runtimeResolution.error
|
|
9249
|
+
redactOperationError(definition, runtimeResolution.error)
|
|
8449
9250
|
) };
|
|
8450
9251
|
}
|
|
8451
|
-
if (definition.runtime
|
|
9252
|
+
if (requiresRuntime(definition.runtime)) {
|
|
8452
9253
|
const actualPosture = runtimeResolution.context.summary.posture;
|
|
8453
9254
|
if (!definition.postures.includes(actualPosture)) {
|
|
8454
9255
|
return { status: "error", result: errorResult(
|
|
@@ -8835,6 +9636,15 @@ function exactCapabilities2(value) {
|
|
|
8835
9636
|
function isOwnerPosture(posture) {
|
|
8836
9637
|
return posture === "owner-openkey" || posture === "local-owner-key";
|
|
8837
9638
|
}
|
|
9639
|
+
function requiresRuntime(requirement) {
|
|
9640
|
+
switch (requirement) {
|
|
9641
|
+
case "authenticated":
|
|
9642
|
+
case "restored":
|
|
9643
|
+
return true;
|
|
9644
|
+
case "inspection":
|
|
9645
|
+
return false;
|
|
9646
|
+
}
|
|
9647
|
+
}
|
|
8838
9648
|
function zodIssueDetails(error) {
|
|
8839
9649
|
return {
|
|
8840
9650
|
// The canonical error channel carries only schema-owned issue codes.
|