@tinycloud/operations 0.3.4-beta.6 → 0.4.0-beta.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/artifacts.cjs +416 -50
- package/dist/artifacts.cjs.map +1 -1
- package/dist/artifacts.d.cts +115 -58
- package/dist/artifacts.d.ts +115 -58
- package/dist/artifacts.js +417 -50
- package/dist/artifacts.js.map +1 -1
- package/dist/authority-ofwbEqmj.d.cts +30 -0
- package/dist/authority-ofwbEqmj.d.ts +30 -0
- package/dist/cli-runtime.cjs +652 -120
- package/dist/cli-runtime.cjs.map +1 -1
- package/dist/cli-runtime.js +645 -116
- package/dist/cli-runtime.js.map +1 -1
- package/dist/delegation-binding.cjs +7956 -0
- package/dist/delegation-binding.cjs.map +1 -0
- package/dist/delegation-binding.d.cts +130 -0
- package/dist/delegation-binding.d.ts +130 -0
- package/dist/delegation-binding.js +7948 -0
- package/dist/delegation-binding.js.map +1 -0
- package/dist/index.cjs +649 -117
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +645 -116
- package/dist/index.js.map +1 -1
- package/dist/profile.cjs +9 -2
- package/dist/profile.cjs.map +1 -1
- package/dist/profile.js +11 -2
- package/dist/profile.js.map +1 -1
- package/dist/state.cjs +426 -50
- package/dist/state.cjs.map +1 -1
- package/dist/state.d.cts +38 -5
- package/dist/state.d.ts +38 -5
- package/dist/state.js +424 -50
- package/dist/state.js.map +1 -1
- package/generated/operations.json +8 -4
- package/package.json +8 -3
package/dist/cli-runtime.js
CHANGED
|
@@ -6965,6 +6965,9 @@ function evaluateOperationAuthority(granted, required, resolveSpace2) {
|
|
|
6965
6965
|
missing: canonicalizeCapabilities(missing)
|
|
6966
6966
|
};
|
|
6967
6967
|
}
|
|
6968
|
+
function delegationWithinRequest(requested, effective, resolveSpace2) {
|
|
6969
|
+
return requested.length > 0 && evaluateOperationAuthority(requested, canonicalizeCapabilities(effective), resolveSpace2).satisfied;
|
|
6970
|
+
}
|
|
6968
6971
|
function isExactCapabilityMemberSubset(hinted, planned, resolveSpace2) {
|
|
6969
6972
|
const canonicalHinted = canonicalizeOperationCapabilities(hinted, resolveSpace2);
|
|
6970
6973
|
const canonicalPlanned = canonicalizeOperationCapabilities(planned, resolveSpace2);
|
|
@@ -7083,16 +7086,23 @@ import {
|
|
|
7083
7086
|
chmod,
|
|
7084
7087
|
link,
|
|
7085
7088
|
mkdir,
|
|
7089
|
+
open,
|
|
7086
7090
|
readFile,
|
|
7087
7091
|
readdir,
|
|
7088
7092
|
rename,
|
|
7089
7093
|
rmdir,
|
|
7090
7094
|
rm,
|
|
7091
7095
|
stat,
|
|
7096
|
+
unlink,
|
|
7092
7097
|
writeFile
|
|
7093
7098
|
} from "fs/promises";
|
|
7094
7099
|
import { homedir } from "os";
|
|
7095
7100
|
import { basename, dirname, isAbsolute, join, relative, resolve } from "path";
|
|
7101
|
+
function processWideContext(key) {
|
|
7102
|
+
var _a2;
|
|
7103
|
+
const registry = globalThis;
|
|
7104
|
+
return registry[_a2 = Symbol.for(key)] ?? (registry[_a2] = new AsyncLocalStorage());
|
|
7105
|
+
}
|
|
7096
7106
|
function tinycloudHomePath() {
|
|
7097
7107
|
const home = invocationStateRoot.getStore() ?? process.env.TC_HOME ?? process.env.HOME ?? process.env.USERPROFILE ?? homedir();
|
|
7098
7108
|
return join(home, ".tinycloud");
|
|
@@ -7131,6 +7141,9 @@ function profileStoreMetadataPath(profile, store) {
|
|
|
7131
7141
|
function profileLockPath(profile) {
|
|
7132
7142
|
return join(profilePath(profile), ".lock");
|
|
7133
7143
|
}
|
|
7144
|
+
function profileTurnLockPath(profile) {
|
|
7145
|
+
return join(tinycloudHomePath(), "profile-locks", validateProfileName(profile));
|
|
7146
|
+
}
|
|
7134
7147
|
async function readJson(filePath) {
|
|
7135
7148
|
try {
|
|
7136
7149
|
return JSON.parse(await readFile(filePath, "utf8"));
|
|
@@ -7140,6 +7153,9 @@ async function readJson(filePath) {
|
|
|
7140
7153
|
}
|
|
7141
7154
|
}
|
|
7142
7155
|
async function writeJsonAtomic(filePath, value) {
|
|
7156
|
+
await replaceJson(filePath, value, false);
|
|
7157
|
+
}
|
|
7158
|
+
async function replaceJson(filePath, value, durable) {
|
|
7143
7159
|
const directory = dirname(filePath);
|
|
7144
7160
|
const temporaryPath = join(
|
|
7145
7161
|
directory,
|
|
@@ -7149,13 +7165,25 @@ async function writeJsonAtomic(filePath, value) {
|
|
|
7149
7165
|
`;
|
|
7150
7166
|
await mkdir(directory, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
7151
7167
|
try {
|
|
7152
|
-
await
|
|
7168
|
+
if (durable) await writeFileDurably(temporaryPath, contents);
|
|
7169
|
+
else await writeFile(temporaryPath, contents, { encoding: "utf8", mode: PRIVATE_FILE_MODE });
|
|
7153
7170
|
await rename(temporaryPath, filePath);
|
|
7154
7171
|
} catch (error) {
|
|
7155
7172
|
await rm(temporaryPath, { force: true }).catch(() => void 0);
|
|
7156
7173
|
throw error;
|
|
7157
7174
|
}
|
|
7158
7175
|
}
|
|
7176
|
+
async function writeFileDurably(path, contents) {
|
|
7177
|
+
const handle = await open(path, "wx", PRIVATE_FILE_MODE);
|
|
7178
|
+
try {
|
|
7179
|
+
if (contents) {
|
|
7180
|
+
await handle.writeFile(contents, "utf8");
|
|
7181
|
+
await handle.datasync();
|
|
7182
|
+
}
|
|
7183
|
+
} finally {
|
|
7184
|
+
await handle.close();
|
|
7185
|
+
}
|
|
7186
|
+
}
|
|
7159
7187
|
async function readProfile(profile) {
|
|
7160
7188
|
return readJson(profileConfigPath(profile));
|
|
7161
7189
|
}
|
|
@@ -7192,15 +7220,28 @@ async function withProfileLock(profile, action, options = {}) {
|
|
|
7192
7220
|
const lockPath = profileLockPath(normalizedProfile);
|
|
7193
7221
|
const held = (heldProfileLocks.getStore() ?? []).filter((ownership2) => ownership2.active);
|
|
7194
7222
|
if (held.some((ownership2) => ownership2.lockPath === lockPath)) return action();
|
|
7195
|
-
const
|
|
7196
|
-
const ownership = {
|
|
7223
|
+
const acquired = await acquireProfileLock(normalizedProfile, options);
|
|
7224
|
+
const ownership = {
|
|
7225
|
+
lockPath,
|
|
7226
|
+
turnSlot: acquired.turnSlot,
|
|
7227
|
+
deletedWhileWaiting: acquired.deletedWhileWaiting,
|
|
7228
|
+
active: true
|
|
7229
|
+
};
|
|
7197
7230
|
try {
|
|
7198
7231
|
return await heldProfileLocks.run([...held, ownership], action);
|
|
7199
7232
|
} finally {
|
|
7200
7233
|
ownership.active = false;
|
|
7201
|
-
await release();
|
|
7234
|
+
await acquired.release();
|
|
7235
|
+
if (acquired.deletedWhileWaiting) await rmdir(profilePath(normalizedProfile)).catch(() => void 0);
|
|
7202
7236
|
}
|
|
7203
7237
|
}
|
|
7238
|
+
async function refuseWriteToDeletedProfile(profile) {
|
|
7239
|
+
const lockPath = profileLockPath(profile);
|
|
7240
|
+
const ownership = (heldProfileLocks.getStore() ?? []).find((candidate) => candidate.active && candidate.lockPath === lockPath);
|
|
7241
|
+
if (!ownership?.deletedWhileWaiting) return;
|
|
7242
|
+
if (await exists(profileConfigPath(profile))) return;
|
|
7243
|
+
throw new ProfileDeletedError(profile);
|
|
7244
|
+
}
|
|
7204
7245
|
async function updateProfileStore(profile, store, update, options = {}) {
|
|
7205
7246
|
return withProfileLock(
|
|
7206
7247
|
profile,
|
|
@@ -7209,6 +7250,7 @@ async function updateProfileStore(profile, store, update, options = {}) {
|
|
|
7209
7250
|
);
|
|
7210
7251
|
}
|
|
7211
7252
|
async function updateProfileStoreWhileLocked(profile, store, update) {
|
|
7253
|
+
await refuseWriteToDeletedProfile(profile);
|
|
7212
7254
|
const current = await readProfileStore(profile, store);
|
|
7213
7255
|
const next = await update(current.records);
|
|
7214
7256
|
await writeJsonAtomic(profileStorePath(profile, store), next.records);
|
|
@@ -7219,32 +7261,271 @@ async function writeFormatOneMetadata(profile, store) {
|
|
|
7219
7261
|
await writeJsonAtomic(profileStoreMetadataPath(profile, store), { formatVersion: 1 });
|
|
7220
7262
|
}
|
|
7221
7263
|
async function acquireProfileLock(profile, options) {
|
|
7222
|
-
const
|
|
7223
|
-
|
|
7264
|
+
const deadline = {
|
|
7265
|
+
startedAt: Date.now(),
|
|
7266
|
+
timeoutMs: options.timeoutMs ?? DEFAULT_LOCK_TIMEOUT_MS,
|
|
7267
|
+
retryMs: options.retryMs ?? DEFAULT_LOCK_RETRY_MS
|
|
7268
|
+
};
|
|
7224
7269
|
const staleAfterMs = options.staleAfterMs ?? DEFAULT_STALE_LOCK_MS;
|
|
7225
|
-
const startedAt = Date.now();
|
|
7226
7270
|
const lockPath = profileLockPath(profile);
|
|
7271
|
+
const turnDirectory = profileTurnLockPath(profile);
|
|
7227
7272
|
const directory = profilePath(profile);
|
|
7228
7273
|
await mkdir(directory, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
7274
|
+
await mkdir(dirname(turnDirectory), { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
7229
7275
|
for (const path of [tinycloudHomePath(), profilesPath(), directory]) {
|
|
7230
7276
|
await chmod(path, PRIVATE_DIR_MODE).catch((error) => {
|
|
7231
7277
|
if (path !== directory || !isErrno(error, "ENOENT")) throw error;
|
|
7232
7278
|
});
|
|
7233
7279
|
}
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
|
|
7237
|
-
|
|
7238
|
-
|
|
7239
|
-
|
|
7280
|
+
const { turn, firstWaitedTurn } = await acquireTurn(profile, turnDirectory, deadline);
|
|
7281
|
+
try {
|
|
7282
|
+
const deletion = await readJson(join(turnDirectory, "deleted.json")).catch(() => null);
|
|
7283
|
+
const deletedWhileWaiting = typeof deletion?.slot === "number" && deletion.slot >= firstWaitedTurn;
|
|
7284
|
+
while (true) {
|
|
7285
|
+
const token = randomUUID();
|
|
7286
|
+
if (await publishProfileLock(profile, lockPath, token)) {
|
|
7287
|
+
return {
|
|
7288
|
+
deletedWhileWaiting,
|
|
7289
|
+
turnSlot: turn.slot,
|
|
7290
|
+
release: async () => {
|
|
7291
|
+
try {
|
|
7292
|
+
await releaseProfileLock(profile, lockPath, token);
|
|
7293
|
+
} finally {
|
|
7294
|
+
await releaseTurn(profile, turn);
|
|
7295
|
+
}
|
|
7296
|
+
}
|
|
7297
|
+
};
|
|
7298
|
+
}
|
|
7299
|
+
await signalTestLockContention(profile);
|
|
7300
|
+
if (await recoverStaleLock(profile, lockPath, staleAfterMs)) continue;
|
|
7301
|
+
await waitOrTimeOut(profile, deadline);
|
|
7302
|
+
}
|
|
7303
|
+
} catch (error) {
|
|
7304
|
+
await releaseTurn(profile, turn);
|
|
7305
|
+
throw error;
|
|
7306
|
+
}
|
|
7307
|
+
}
|
|
7308
|
+
function checkDeadline(profile, deadline, detail) {
|
|
7309
|
+
if (Date.now() - deadline.startedAt >= deadline.timeoutMs) {
|
|
7310
|
+
throw new ProfileLockTimeoutError(profile, deadline.timeoutMs, detail);
|
|
7311
|
+
}
|
|
7312
|
+
}
|
|
7313
|
+
async function waitOrTimeOut(profile, deadline, detail) {
|
|
7314
|
+
checkDeadline(profile, deadline, detail);
|
|
7315
|
+
await sleep(Math.min(deadline.retryMs, deadline.timeoutMs - (Date.now() - deadline.startedAt)));
|
|
7316
|
+
}
|
|
7317
|
+
async function acquireTurn(profile, directory, deadline) {
|
|
7318
|
+
let firstWaitedTurn;
|
|
7319
|
+
let problem;
|
|
7320
|
+
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.`;
|
|
7321
|
+
for (let first = true; ; first = false) {
|
|
7322
|
+
if (!first) checkDeadline(profile, deadline, problem);
|
|
7323
|
+
problem = void 0;
|
|
7324
|
+
const latest = await readLatestTurn(directory);
|
|
7325
|
+
if (latest === "missing") {
|
|
7326
|
+
await createTurnDirectory(directory);
|
|
7327
|
+
continue;
|
|
7328
|
+
}
|
|
7329
|
+
if (latest === "empty") {
|
|
7330
|
+
problem = damaged("has no turns");
|
|
7331
|
+
await waitOrTimeOut(profile, deadline, problem);
|
|
7332
|
+
continue;
|
|
7333
|
+
}
|
|
7334
|
+
if (latest === null) continue;
|
|
7335
|
+
if (typeof latest === "number") {
|
|
7336
|
+
problem = damaged(`has an unreadable turn ${latest}`);
|
|
7337
|
+
await waitOrTimeOut(profile, deadline, problem);
|
|
7338
|
+
continue;
|
|
7339
|
+
}
|
|
7340
|
+
firstWaitedTurn ?? (firstWaitedTurn = latest.done ? latest.slot + 1 : latest.slot);
|
|
7341
|
+
if (latest.held && latest.done) {
|
|
7342
|
+
const turn = await takeTurn(profile, directory, latest);
|
|
7343
|
+
if (turn !== null) return { turn, firstWaitedTurn };
|
|
7344
|
+
await waitOrTimeOut(profile, deadline);
|
|
7345
|
+
} else if (latest.done) {
|
|
7346
|
+
problem = damaged(`ends with turn ${latest.slot}, which was never granted`);
|
|
7347
|
+
await waitOrTimeOut(profile, deadline, problem);
|
|
7348
|
+
} else if (latest.pid === process.pid ? relinquishedTurns.has(latest.token) : !isProcessAlive(latest.pid)) {
|
|
7349
|
+
await waitForTestBarrier(TEST_LOCK_TURN_SETTLE_BARRIER_DIR, profile);
|
|
7350
|
+
await settleAbandonedTurn(directory, latest);
|
|
7351
|
+
relinquishedTurns.delete(latest.token);
|
|
7352
|
+
} else {
|
|
7353
|
+
await signalTestLockContention(profile);
|
|
7354
|
+
await waitOrTimeOut(profile, deadline);
|
|
7355
|
+
}
|
|
7356
|
+
}
|
|
7357
|
+
}
|
|
7358
|
+
async function readLatestTurn(directory) {
|
|
7359
|
+
let names;
|
|
7360
|
+
try {
|
|
7361
|
+
names = await readdir(directory);
|
|
7362
|
+
} catch (error) {
|
|
7363
|
+
if (isErrno(error, "ENOENT")) return "missing";
|
|
7364
|
+
throw error;
|
|
7365
|
+
}
|
|
7366
|
+
const slots = names.filter((name) => TURN.test(name)).map(Number);
|
|
7367
|
+
if (slots.length === 0) return "empty";
|
|
7368
|
+
const slot = Math.max(...slots);
|
|
7369
|
+
const turn = await readTurn(directory, slot);
|
|
7370
|
+
return turn === "damaged" ? slot : turn;
|
|
7371
|
+
}
|
|
7372
|
+
async function readTurn(directory, slot) {
|
|
7373
|
+
const path = join(directory, String(slot));
|
|
7374
|
+
let text;
|
|
7375
|
+
try {
|
|
7376
|
+
text = await readFile(join(path, "owner.json"), "utf8");
|
|
7377
|
+
} catch (error) {
|
|
7378
|
+
if (!isErrno(error, "ENOENT") && !isErrno(error, "ENOTDIR")) throw error;
|
|
7379
|
+
return await exists(path) ? "damaged" : null;
|
|
7380
|
+
}
|
|
7381
|
+
let owner;
|
|
7382
|
+
try {
|
|
7383
|
+
owner = JSON.parse(text);
|
|
7384
|
+
} catch {
|
|
7385
|
+
return "damaged";
|
|
7386
|
+
}
|
|
7387
|
+
if (owner === null || typeof owner !== "object" || typeof owner.token !== "string" || typeof owner.pid !== "number") {
|
|
7388
|
+
return "damaged";
|
|
7389
|
+
}
|
|
7390
|
+
const done = await exists(join(path, `${owner.token}.done`));
|
|
7391
|
+
const held = await exists(join(path, `${owner.token}.held`));
|
|
7392
|
+
return {
|
|
7393
|
+
slot,
|
|
7394
|
+
token: owner.token,
|
|
7395
|
+
pid: owner.pid,
|
|
7396
|
+
after: typeof owner.after === "string" ? owner.after : null,
|
|
7397
|
+
held,
|
|
7398
|
+
done
|
|
7399
|
+
};
|
|
7400
|
+
}
|
|
7401
|
+
async function isFinishedTurn(directory, slot, token) {
|
|
7402
|
+
const turn = await readTurn(directory, slot);
|
|
7403
|
+
if (turn === "damaged") throw new Error(`Turn ${slot} of the turn lock ${directory} is unreadable.`);
|
|
7404
|
+
return turn !== null && turn.token === token && turn.held && turn.done;
|
|
7405
|
+
}
|
|
7406
|
+
async function takeTurn(profile, directory, latest) {
|
|
7407
|
+
const token = randomUUID();
|
|
7408
|
+
const slot = latest.slot + 1;
|
|
7409
|
+
const path = join(directory, String(slot));
|
|
7410
|
+
const owner = { pid: process.pid, createdAt: (/* @__PURE__ */ new Date()).toISOString(), token, after: latest.token };
|
|
7411
|
+
let published = false;
|
|
7412
|
+
try {
|
|
7413
|
+
published = await publishDirectory(
|
|
7414
|
+
join(directory, `.stage-${process.pid}-${token}`),
|
|
7415
|
+
path,
|
|
7416
|
+
{ "owner.json": `${JSON.stringify(owner, null, 2)}
|
|
7417
|
+
` },
|
|
7418
|
+
() => waitForTestBarrier(TEST_LOCK_TURN_PUBLISH_BARRIER_DIR, profile)
|
|
7419
|
+
);
|
|
7420
|
+
if (published && await isFinishedTurn(directory, latest.slot, latest.token) && await markTurn(path, token, "held")) {
|
|
7421
|
+
return { directory, slot, token };
|
|
7422
|
+
}
|
|
7423
|
+
if (published) await markTurn(path, token, "done");
|
|
7424
|
+
return null;
|
|
7425
|
+
} catch (error) {
|
|
7426
|
+
if (published) settleOwnTurn(directory, slot, token);
|
|
7427
|
+
throw error;
|
|
7428
|
+
}
|
|
7429
|
+
}
|
|
7430
|
+
async function settleAbandonedTurn(directory, abandoned) {
|
|
7431
|
+
const path = join(directory, String(abandoned.slot));
|
|
7432
|
+
if (!abandoned.held && abandoned.after !== null && await isFinishedTurn(directory, abandoned.slot - 1, abandoned.after)) {
|
|
7433
|
+
await markTurn(path, abandoned.token, "held");
|
|
7434
|
+
}
|
|
7435
|
+
await markTurn(path, abandoned.token, "done");
|
|
7436
|
+
}
|
|
7437
|
+
function settleOwnTurn(directory, slot, token, retries = SETTLE_RETRIES) {
|
|
7438
|
+
relinquishedTurns.add(token);
|
|
7439
|
+
void (async () => {
|
|
7440
|
+
try {
|
|
7441
|
+
const turn = await readTurn(directory, slot);
|
|
7442
|
+
if (turn !== null && turn !== "damaged" && turn.token === token && !turn.done) await settleAbandonedTurn(directory, turn);
|
|
7443
|
+
relinquishedTurns.delete(token);
|
|
7444
|
+
} catch {
|
|
7445
|
+
if (retries > 0) setTimeout(() => settleOwnTurn(directory, slot, token, retries - 1), SETTLE_RETRY_MS).unref();
|
|
7446
|
+
}
|
|
7447
|
+
})();
|
|
7448
|
+
}
|
|
7449
|
+
async function markTurn(path, token, kind) {
|
|
7450
|
+
for (let attempt = 0; ; attempt++) {
|
|
7451
|
+
try {
|
|
7452
|
+
await writeFile(join(path, `${token}.${kind}`), "", { mode: PRIVATE_FILE_MODE, flag: "wx" });
|
|
7453
|
+
return true;
|
|
7454
|
+
} catch (error) {
|
|
7455
|
+
if (isErrno(error, "EEXIST")) return true;
|
|
7456
|
+
if (isErrno(error, "ENOENT")) return false;
|
|
7457
|
+
if (attempt >= MARKER_RETRY_DELAYS_MS.length) throw error;
|
|
7458
|
+
await sleep(MARKER_RETRY_DELAYS_MS[attempt]);
|
|
7459
|
+
}
|
|
7460
|
+
}
|
|
7461
|
+
}
|
|
7462
|
+
async function createTurnDirectory(directory) {
|
|
7463
|
+
const token = randomUUID();
|
|
7464
|
+
const owner = { pid: process.pid, createdAt: (/* @__PURE__ */ new Date()).toISOString(), token, after: null };
|
|
7465
|
+
await publishDirectory(join(dirname(directory), `.stage-${basename(directory)}-${process.pid}-${token}`), directory, {
|
|
7466
|
+
"0/owner.json": `${JSON.stringify(owner, null, 2)}
|
|
7467
|
+
`,
|
|
7468
|
+
[`0/${token}.held`]: "",
|
|
7469
|
+
[`0/${token}.done`]: ""
|
|
7470
|
+
});
|
|
7471
|
+
}
|
|
7472
|
+
async function publishDirectory(stage, target, files, beforeRename) {
|
|
7473
|
+
try {
|
|
7474
|
+
for (const [name, contents] of Object.entries(files)) {
|
|
7475
|
+
await mkdir(dirname(join(stage, name)), { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
7476
|
+
await writeFileDurably(join(stage, name), contents);
|
|
7477
|
+
}
|
|
7478
|
+
await beforeRename?.();
|
|
7479
|
+
await rename(stage, target);
|
|
7480
|
+
return true;
|
|
7481
|
+
} catch (error) {
|
|
7482
|
+
if (["ENOTEMPTY", "EEXIST", "ENOENT"].some((code) => isErrno(error, code)) || await exists(target)) return false;
|
|
7483
|
+
throw error;
|
|
7484
|
+
} finally {
|
|
7485
|
+
await rm(stage, { recursive: true, force: true }).catch(() => void 0);
|
|
7486
|
+
}
|
|
7487
|
+
}
|
|
7488
|
+
async function releaseTurn(profile, turn) {
|
|
7489
|
+
try {
|
|
7490
|
+
await markTurn(join(turn.directory, String(turn.slot)), turn.token, "done");
|
|
7491
|
+
} catch {
|
|
7492
|
+
settleOwnTurn(turn.directory, turn.slot, turn.token);
|
|
7493
|
+
return;
|
|
7494
|
+
}
|
|
7495
|
+
await collectTurns(profile, turn).catch(() => void 0);
|
|
7496
|
+
}
|
|
7497
|
+
function isAbandonedTurnEntry(name) {
|
|
7498
|
+
if (name.startsWith(".trash-")) return true;
|
|
7499
|
+
const writer = /^\.(?:stage|deleted\.json)[-.]([0-9]+)[-.]/.exec(name)?.[1];
|
|
7500
|
+
return writer !== void 0 && !isProcessAlive(Number(writer));
|
|
7501
|
+
}
|
|
7502
|
+
function isAbandonedTurnDirectoryStage(name) {
|
|
7503
|
+
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];
|
|
7504
|
+
return writer !== void 0 && !isProcessAlive(Number(writer));
|
|
7505
|
+
}
|
|
7506
|
+
async function collectTurns(profile, turn) {
|
|
7507
|
+
for (const [directory, abandoned] of [
|
|
7508
|
+
[turn.directory, isAbandonedTurnEntry],
|
|
7509
|
+
[dirname(turn.directory), isAbandonedTurnDirectoryStage]
|
|
7510
|
+
]) {
|
|
7511
|
+
for (const name of (await readdir(directory)).filter(abandoned)) {
|
|
7512
|
+
await rm(join(directory, name), { recursive: true, force: true }).catch(() => void 0);
|
|
7513
|
+
}
|
|
7514
|
+
}
|
|
7515
|
+
const older = (await readdir(turn.directory)).filter((name) => TURN.test(name)).map(Number).filter((slot) => slot < turn.slot).sort((left, right) => left - right);
|
|
7516
|
+
for (const slot of older) {
|
|
7517
|
+
const candidate = await readTurn(turn.directory, slot);
|
|
7518
|
+
if (candidate === null || candidate === "damaged" || !candidate.done) continue;
|
|
7519
|
+
if (candidate.held) {
|
|
7520
|
+
if (await exists(join(turn.directory, String(slot - 1)))) continue;
|
|
7521
|
+
const next = await readTurn(turn.directory, slot + 1);
|
|
7522
|
+
if (next === null || next === "damaged" || !next.held || !next.done) continue;
|
|
7240
7523
|
}
|
|
7241
|
-
await
|
|
7242
|
-
|
|
7243
|
-
|
|
7244
|
-
|
|
7245
|
-
throw new ProfileLockTimeoutError(profile, timeoutMs);
|
|
7524
|
+
await waitForTestBarrier(TEST_LOCK_TURN_COLLECT_BARRIER_DIR, profile);
|
|
7525
|
+
const trash = join(turn.directory, `.trash-${randomUUID()}`);
|
|
7526
|
+
if (await rename(join(turn.directory, String(slot)), trash).then(() => true, () => false)) {
|
|
7527
|
+
await rm(trash, { recursive: true, force: true }).catch(() => void 0);
|
|
7246
7528
|
}
|
|
7247
|
-
await sleep(Math.min(retryMs, timeoutMs - elapsedMs));
|
|
7248
7529
|
}
|
|
7249
7530
|
}
|
|
7250
7531
|
async function publishProfileLock(profile, lockPath, token) {
|
|
@@ -7257,12 +7538,18 @@ async function publishProfileLock(profile, lockPath, token) {
|
|
|
7257
7538
|
return false;
|
|
7258
7539
|
}
|
|
7259
7540
|
await waitForTestBarrier(TEST_LOCK_PUBLISH_BARRIER_DIR, profile);
|
|
7541
|
+
const ownerPath = join(lockPath, "owner.json");
|
|
7260
7542
|
const staged = join(dirname(lockPath), `.lock-owner-${token}.tmp`);
|
|
7261
|
-
const owner = { pid: process.pid, createdAt: (/* @__PURE__ */ new Date()).toISOString(), token }
|
|
7543
|
+
const owner = `${JSON.stringify({ pid: process.pid, createdAt: (/* @__PURE__ */ new Date()).toISOString(), token }, null, 2)}
|
|
7544
|
+
`;
|
|
7262
7545
|
try {
|
|
7263
|
-
await writeFile(staged,
|
|
7264
|
-
|
|
7265
|
-
|
|
7546
|
+
await writeFile(staged, owner, { encoding: "utf8", mode: PRIVATE_FILE_MODE, flag: "wx" });
|
|
7547
|
+
try {
|
|
7548
|
+
await link(staged, ownerPath);
|
|
7549
|
+
} catch (error) {
|
|
7550
|
+
if (!lacksHardLinks(error)) throw error;
|
|
7551
|
+
await writeFile(ownerPath, owner, { encoding: "utf8", mode: PRIVATE_FILE_MODE, flag: "wx" });
|
|
7552
|
+
}
|
|
7266
7553
|
return true;
|
|
7267
7554
|
} catch (error) {
|
|
7268
7555
|
if (isErrno(error, "EEXIST") || isErrno(error, "ENOENT")) return false;
|
|
@@ -7272,6 +7559,9 @@ async function publishProfileLock(profile, lockPath, token) {
|
|
|
7272
7559
|
await rm(staged, { force: true }).catch(() => void 0);
|
|
7273
7560
|
}
|
|
7274
7561
|
}
|
|
7562
|
+
function lacksHardLinks(error) {
|
|
7563
|
+
return typeof error === "object" && error !== null && "code" in error && NO_HARD_LINK_ERRORS.has(String(error.code));
|
|
7564
|
+
}
|
|
7275
7565
|
async function releaseProfileLock(profile, lockPath, token) {
|
|
7276
7566
|
const ownerPath = join(lockPath, "owner.json");
|
|
7277
7567
|
const owner = await readJson(ownerPath).catch(() => null);
|
|
@@ -7295,9 +7585,10 @@ async function signalTestLockContention(profile) {
|
|
|
7295
7585
|
`, { encoding: "utf8", flag: "wx" }).catch(() => void 0);
|
|
7296
7586
|
}
|
|
7297
7587
|
async function recoverOwnerlessLock(profile, lockPath, staleAfterMs) {
|
|
7588
|
+
let aged;
|
|
7298
7589
|
try {
|
|
7299
7590
|
const { mtimeMs } = await stat(lockPath);
|
|
7300
|
-
|
|
7591
|
+
aged = Date.now() - mtimeMs >= staleAfterMs;
|
|
7301
7592
|
} catch {
|
|
7302
7593
|
return false;
|
|
7303
7594
|
}
|
|
@@ -7306,7 +7597,16 @@ async function recoverOwnerlessLock(profile, lockPath, staleAfterMs) {
|
|
|
7306
7597
|
try {
|
|
7307
7598
|
const entries = await readdir(lockPath);
|
|
7308
7599
|
if (entries.includes("owner.json")) return false;
|
|
7309
|
-
|
|
7600
|
+
const onlyOrphanedClaims = entries.length > 0 && entries.every((entry) => RECOVERY_CLAIM.test(entry));
|
|
7601
|
+
if (!onlyOrphanedClaims && (!aged || recoveryFenced(checkedAt, staleAfterMs))) return false;
|
|
7602
|
+
for (const name of entries.filter((entry) => RECOVERY_CLAIM.test(entry))) {
|
|
7603
|
+
const claimPath = join(lockPath, name);
|
|
7604
|
+
if (await hasLiveRecoveryOwner(claimPath)) {
|
|
7605
|
+
if (!await exists(join(lockPath, "owner.json"))) await rename(claimPath, join(lockPath, "owner.json"));
|
|
7606
|
+
return false;
|
|
7607
|
+
}
|
|
7608
|
+
await rm(claimPath, { force: true });
|
|
7609
|
+
}
|
|
7310
7610
|
for (const name of entries.filter((entry) => ABANDONED_CLAIM.test(entry))) {
|
|
7311
7611
|
await rm(join(lockPath, name), { force: true });
|
|
7312
7612
|
}
|
|
@@ -7317,6 +7617,23 @@ async function recoverOwnerlessLock(profile, lockPath, staleAfterMs) {
|
|
|
7317
7617
|
await removeAgedOwnerFiles(lockPath, staleAfterMs);
|
|
7318
7618
|
return true;
|
|
7319
7619
|
}
|
|
7620
|
+
async function hasLiveRecoveryOwner(claimPath) {
|
|
7621
|
+
let contents;
|
|
7622
|
+
try {
|
|
7623
|
+
contents = await readFile(claimPath, "utf8");
|
|
7624
|
+
} catch (error) {
|
|
7625
|
+
if (isErrno(error, "ENOENT")) return false;
|
|
7626
|
+
throw error;
|
|
7627
|
+
}
|
|
7628
|
+
let owner;
|
|
7629
|
+
try {
|
|
7630
|
+
owner = JSON.parse(contents);
|
|
7631
|
+
} catch {
|
|
7632
|
+
return false;
|
|
7633
|
+
}
|
|
7634
|
+
const pid = typeof owner === "object" && owner !== null && "pid" in owner ? owner.pid : null;
|
|
7635
|
+
return typeof pid === "number" && Number.isInteger(pid) && pid > 0 && isProcessAlive(pid);
|
|
7636
|
+
}
|
|
7320
7637
|
function recoveryFenced(since, staleAfterMs) {
|
|
7321
7638
|
return performance.now() - since >= staleAfterMs / 2;
|
|
7322
7639
|
}
|
|
@@ -7331,37 +7648,57 @@ async function removeAgedOwnerFiles(lockPath, staleAfterMs) {
|
|
|
7331
7648
|
}
|
|
7332
7649
|
async function recoverStaleLock(profile, lockPath, staleAfterMs) {
|
|
7333
7650
|
const ownerPath = join(lockPath, "owner.json");
|
|
7334
|
-
const
|
|
7335
|
-
if (
|
|
7336
|
-
if (!
|
|
7337
|
-
const observedToken = typeof owner?.token === "string" && owner.token.length > 0 ? owner.token : "legacy";
|
|
7651
|
+
const observed = await readFile(ownerPath, "utf8").catch(() => null);
|
|
7652
|
+
if (observed === null) return recoverOwnerlessLock(profile, lockPath, staleAfterMs);
|
|
7653
|
+
if (!await isAbandonedOwner(observed, ownerPath, staleAfterMs)) return false;
|
|
7338
7654
|
await waitForTestBarrier(TEST_LOCK_RECOVERY_BARRIER_DIR, profile);
|
|
7339
|
-
const claimPath = join(lockPath, `.
|
|
7655
|
+
const claimPath = join(lockPath, `.recover-${randomUUID()}.json`);
|
|
7656
|
+
let moved = false;
|
|
7340
7657
|
try {
|
|
7341
7658
|
await link(ownerPath, claimPath);
|
|
7342
|
-
} catch {
|
|
7343
|
-
return false;
|
|
7659
|
+
} catch (error) {
|
|
7660
|
+
if (!lacksHardLinks(error)) return false;
|
|
7661
|
+
try {
|
|
7662
|
+
await rename(ownerPath, claimPath);
|
|
7663
|
+
moved = true;
|
|
7664
|
+
} catch {
|
|
7665
|
+
return false;
|
|
7666
|
+
}
|
|
7344
7667
|
}
|
|
7345
|
-
|
|
7346
|
-
await
|
|
7347
|
-
const claimed = await readJson(claimPath).catch(() => null);
|
|
7348
|
-
const sameInstance = claimed !== null && (observedToken === "legacy" ? claimed.token === void 0 : claimed.token === observedToken);
|
|
7668
|
+
if (moved) await waitForTestBarrier(TEST_LOCK_MOVED_BARRIER_DIR, profile);
|
|
7669
|
+
const sameInstance = await readFile(claimPath, "utf8").then((claimed) => claimed === observed, () => false);
|
|
7349
7670
|
await waitForTestBarrier(TEST_LOCK_VERIFIED_BARRIER_DIR, profile);
|
|
7350
|
-
if (sameInstance
|
|
7351
|
-
|
|
7352
|
-
|
|
7671
|
+
if (!sameInstance) {
|
|
7672
|
+
if (moved) {
|
|
7673
|
+
await rename(claimPath, ownerPath).catch(() => void 0);
|
|
7674
|
+
} else {
|
|
7675
|
+
await unlink(claimPath).catch(() => void 0);
|
|
7676
|
+
}
|
|
7353
7677
|
return false;
|
|
7354
7678
|
}
|
|
7355
|
-
if (
|
|
7356
|
-
|
|
7357
|
-
|
|
7679
|
+
if (!moved) await rm(ownerPath, { force: true });
|
|
7680
|
+
await waitForTestBarrier(TEST_LOCK_CLAIM_BARRIER_DIR, profile);
|
|
7681
|
+
await waitForTestBarrier(TEST_LOCK_CLAIMED_BARRIER_DIR, profile);
|
|
7682
|
+
for (const name of (await readdir(lockPath).catch(() => [])).filter((entry) => RECOVERY_CLAIM.test(entry))) {
|
|
7683
|
+
if (join(lockPath, name) !== claimPath) await rm(join(lockPath, name), { force: true });
|
|
7358
7684
|
}
|
|
7359
|
-
await
|
|
7360
|
-
await rmdir(lockPath).catch(() => void 0);
|
|
7361
|
-
if (!sameInstance) return false;
|
|
7685
|
+
if (await unlink(claimPath).then(() => true, () => false)) await rmdir(lockPath).catch(() => void 0);
|
|
7362
7686
|
await removeAgedOwnerFiles(lockPath, staleAfterMs);
|
|
7363
7687
|
return true;
|
|
7364
7688
|
}
|
|
7689
|
+
async function isAbandonedOwner(observed, ownerPath, staleAfterMs) {
|
|
7690
|
+
let owner;
|
|
7691
|
+
try {
|
|
7692
|
+
owner = JSON.parse(observed);
|
|
7693
|
+
} catch {
|
|
7694
|
+
owner = null;
|
|
7695
|
+
}
|
|
7696
|
+
if (typeof owner === "object" && owner !== null && "pid" in owner) {
|
|
7697
|
+
return isStaleOwner(owner, staleAfterMs);
|
|
7698
|
+
}
|
|
7699
|
+
const modifiedAt = await stat(ownerPath).then(({ mtimeMs }) => mtimeMs, () => Number.NaN);
|
|
7700
|
+
return Date.now() - modifiedAt >= staleAfterMs;
|
|
7701
|
+
}
|
|
7365
7702
|
async function waitForTestBarrier(environmentName, profile) {
|
|
7366
7703
|
if (process.env.NODE_ENV !== "test") return;
|
|
7367
7704
|
const barrierDirectory = process.env[environmentName];
|
|
@@ -7388,6 +7725,15 @@ function isStaleOwner(owner, staleAfterMs) {
|
|
|
7388
7725
|
}
|
|
7389
7726
|
return !isProcessAlive(owner.pid);
|
|
7390
7727
|
}
|
|
7728
|
+
async function exists(path) {
|
|
7729
|
+
try {
|
|
7730
|
+
await stat(path);
|
|
7731
|
+
return true;
|
|
7732
|
+
} catch (error) {
|
|
7733
|
+
if (isErrno(error, "ENOENT") || isErrno(error, "ENOTDIR")) return false;
|
|
7734
|
+
throw error;
|
|
7735
|
+
}
|
|
7736
|
+
}
|
|
7391
7737
|
function isProcessAlive(pid) {
|
|
7392
7738
|
try {
|
|
7393
7739
|
process.kill(pid, 0);
|
|
@@ -7408,7 +7754,7 @@ function isErrno(error, code) {
|
|
|
7408
7754
|
function sleep(milliseconds) {
|
|
7409
7755
|
return new Promise((resolve2) => setTimeout(resolve2, milliseconds));
|
|
7410
7756
|
}
|
|
7411
|
-
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,
|
|
7757
|
+
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, _a, relinquishedTurns, MARKER_RETRY_DELAYS_MS, SETTLE_RETRY_MS, SETTLE_RETRIES, NO_HARD_LINK_ERRORS, ABANDONED_CLAIM, RECOVERY_CLAIM, STAGED_OWNER;
|
|
7412
7758
|
var init_state = __esm({
|
|
7413
7759
|
"src/state.ts"() {
|
|
7414
7760
|
"use strict";
|
|
@@ -7425,18 +7771,36 @@ var init_state = __esm({
|
|
|
7425
7771
|
TEST_LOCK_RELEASE_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_RELEASE_BARRIER_DIR";
|
|
7426
7772
|
TEST_LOCK_CLAIM_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_CLAIM_BARRIER_DIR";
|
|
7427
7773
|
TEST_LOCK_CLAIMED_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_CLAIMED_BARRIER_DIR";
|
|
7428
|
-
TEST_LOCK_FENCED_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_FENCED_BARRIER_DIR";
|
|
7429
7774
|
TEST_LOCK_VERIFIED_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_VERIFIED_BARRIER_DIR";
|
|
7430
|
-
|
|
7431
|
-
|
|
7775
|
+
TEST_LOCK_MOVED_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_MOVED_BARRIER_DIR";
|
|
7776
|
+
TEST_LOCK_TURN_PUBLISH_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_TURN_PUBLISH_BARRIER_DIR";
|
|
7777
|
+
TEST_LOCK_TURN_SETTLE_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_TURN_SETTLE_BARRIER_DIR";
|
|
7778
|
+
TEST_LOCK_TURN_COLLECT_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_TURN_COLLECT_BARRIER_DIR";
|
|
7779
|
+
invocationStateRoot = processWideContext("tinycloud.operations.invocationStateRoot.v1");
|
|
7780
|
+
heldProfileLocks = processWideContext("tinycloud.operations.heldProfileLocks.v2");
|
|
7432
7781
|
ProfileLockTimeoutError = class extends Error {
|
|
7433
|
-
|
|
7434
|
-
|
|
7782
|
+
/** `detail` names the cause when it is not simply another process holding the lock. */
|
|
7783
|
+
constructor(profile, timeoutMs, detail) {
|
|
7784
|
+
super(`Timed out waiting for the profile lock for "${profile}" after ${timeoutMs}ms.${detail ? ` ${detail}` : ""}`);
|
|
7435
7785
|
this.code = "PROFILE_LOCK_TIMEOUT";
|
|
7436
7786
|
this.name = "ProfileLockTimeoutError";
|
|
7437
7787
|
}
|
|
7438
7788
|
};
|
|
7789
|
+
ProfileDeletedError = class extends Error {
|
|
7790
|
+
constructor(profile) {
|
|
7791
|
+
super(`Profile "${profile}" was deleted while this change waited for its lock, so the change was not written.`);
|
|
7792
|
+
this.code = "PROFILE_NOT_FOUND";
|
|
7793
|
+
this.name = "ProfileDeletedError";
|
|
7794
|
+
}
|
|
7795
|
+
};
|
|
7796
|
+
TURN = /^(?:0|[1-9][0-9]*)$/;
|
|
7797
|
+
relinquishedTurns = globalThis[_a = /* @__PURE__ */ Symbol.for("tinycloud.operations.relinquishedProfileTurns.v1")] ?? (globalThis[_a] = /* @__PURE__ */ new Set());
|
|
7798
|
+
MARKER_RETRY_DELAYS_MS = [10, 50, 250];
|
|
7799
|
+
SETTLE_RETRY_MS = 1e3;
|
|
7800
|
+
SETTLE_RETRIES = 300;
|
|
7801
|
+
NO_HARD_LINK_ERRORS = /* @__PURE__ */ new Set(["EPERM", "ENOTSUP", "EOPNOTSUPP", "ENOSYS", "EISDIR", "EMLINK"]);
|
|
7439
7802
|
ABANDONED_CLAIM = /^\.(?:release|stale)-[0-9a-f-]+\.json$/;
|
|
7803
|
+
RECOVERY_CLAIM = /^\.recover-[0-9a-f-]+\.json$/;
|
|
7440
7804
|
STAGED_OWNER = /^\.lock-owner-[0-9a-f-]+\.tmp$/;
|
|
7441
7805
|
}
|
|
7442
7806
|
});
|
|
@@ -7645,7 +8009,7 @@ function evaluateArtifactAuthority(granted, required, resolveSpace2) {
|
|
|
7645
8009
|
function defaultRequestId(now) {
|
|
7646
8010
|
return `req_${now.getTime().toString(36)}_${randomUUID2().replace(/-/g, "")}`;
|
|
7647
8011
|
}
|
|
7648
|
-
var THIRTY_DAYS_MS, PermissionEntrySchema, CommandSchema, LegacyPermissionRequestArtifactSchema, PortableDelegationSchema, PermissionRequestArtifactSchema, DelegationImportArtifactSchema;
|
|
8012
|
+
var THIRTY_DAYS_MS, PermissionEntrySchema, CommandSchema, LegacyPermissionRequestArtifactSchema, PortableDelegationSchema, PermissionRequestArtifactSchema, DelegationImportArtifactSchema, DelegationRequestBindingSchema;
|
|
7649
8013
|
var init_artifacts = __esm({
|
|
7650
8014
|
"src/artifacts.ts"() {
|
|
7651
8015
|
"use strict";
|
|
@@ -7718,16 +8082,12 @@ var init_artifacts = __esm({
|
|
|
7718
8082
|
expiry: z2.string().datetime({ offset: true }).optional(),
|
|
7719
8083
|
prompted: z2.boolean().optional()
|
|
7720
8084
|
}).strict();
|
|
8085
|
+
DelegationRequestBindingSchema = PermissionRequestArtifactSchema.pick({ requestId: true, requested: true }).strict();
|
|
7721
8086
|
}
|
|
7722
8087
|
});
|
|
7723
8088
|
|
|
7724
8089
|
// src/secrets.ts
|
|
7725
|
-
import
|
|
7726
|
-
canonicalizeAddress,
|
|
7727
|
-
makePkhSpaceId,
|
|
7728
|
-
parsePkhDid,
|
|
7729
|
-
parseSpaceUri
|
|
7730
|
-
} from "@tinycloud/node-sdk";
|
|
8090
|
+
import * as nodeSdk from "@tinycloud/node-sdk";
|
|
7731
8091
|
function normalizeSecretsGetInput(input) {
|
|
7732
8092
|
const reference = resolveSecretReference(input);
|
|
7733
8093
|
return {
|
|
@@ -7783,18 +8143,18 @@ function resolveOperationSpace(space, node, ownerSpace) {
|
|
|
7783
8143
|
if (resolved !== void 0) return canonicalizeSpaceUri(resolved);
|
|
7784
8144
|
}
|
|
7785
8145
|
const did = typeof candidate.did === "string" ? candidate.did : void 0;
|
|
7786
|
-
const pkh = did === void 0 ? null : parsePkhDid(did);
|
|
8146
|
+
const pkh = did === void 0 ? null : nodeSdk.parsePkhDid(did);
|
|
7787
8147
|
if (pkh === null) {
|
|
7788
8148
|
throw invalidSecretInput();
|
|
7789
8149
|
}
|
|
7790
|
-
return makePkhSpaceId(pkh.address, pkh.chainId, space);
|
|
8150
|
+
return nodeSdk.makePkhSpaceId(pkh.address, pkh.chainId, space);
|
|
7791
8151
|
}
|
|
7792
8152
|
function ownedSpaceForName(ownerSpace, name) {
|
|
7793
|
-
const parsed = parseSpaceUri(ownerSpace);
|
|
8153
|
+
const parsed = nodeSdk.parseSpaceUri(ownerSpace);
|
|
7794
8154
|
if (parsed === null) return void 0;
|
|
7795
8155
|
try {
|
|
7796
|
-
const pkh = parsePkhDid(parsed.owner);
|
|
7797
|
-
return pkh === null ? void 0 : makePkhSpaceId(pkh.address, pkh.chainId, name);
|
|
8156
|
+
const pkh = nodeSdk.parsePkhDid(parsed.owner);
|
|
8157
|
+
return pkh === null ? void 0 : nodeSdk.makePkhSpaceId(pkh.address, pkh.chainId, name);
|
|
7798
8158
|
} catch {
|
|
7799
8159
|
return void 0;
|
|
7800
8160
|
}
|
|
@@ -7809,7 +8169,7 @@ function canonicalizeSpaceUri(space) {
|
|
|
7809
8169
|
/^tinycloud:pkh:eip155:(\d+):(0x[0-9a-fA-F]{40}):([^:]+)$/
|
|
7810
8170
|
);
|
|
7811
8171
|
if (pkh !== null) {
|
|
7812
|
-
return `tinycloud:pkh:eip155:${pkh[1]}:${canonicalizeAddress(pkh[2]).toLowerCase()}:${pkh[3]}`;
|
|
8172
|
+
return `tinycloud:pkh:eip155:${pkh[1]}:${nodeSdk.canonicalizeAddress(pkh[2]).toLowerCase()}:${pkh[3]}`;
|
|
7813
8173
|
}
|
|
7814
8174
|
return space;
|
|
7815
8175
|
}
|
|
@@ -7862,7 +8222,7 @@ var init_secrets = __esm({
|
|
|
7862
8222
|
});
|
|
7863
8223
|
|
|
7864
8224
|
// src/operations/auth.ts
|
|
7865
|
-
import * as
|
|
8225
|
+
import * as nodeSdk2 from "@tinycloud/node-sdk";
|
|
7866
8226
|
import { z as z3 } from "zod";
|
|
7867
8227
|
async function requestExactAuthority(context, input) {
|
|
7868
8228
|
const sessionDid = context.summary.sessionDid;
|
|
@@ -8007,7 +8367,7 @@ async function importRequestBoundDelegation(context, input) {
|
|
|
8007
8367
|
};
|
|
8008
8368
|
let activated;
|
|
8009
8369
|
try {
|
|
8010
|
-
activated = await
|
|
8370
|
+
activated = await nodeSdk2.activateValidatedRuntimeDelegation(
|
|
8011
8371
|
context.runtime.node,
|
|
8012
8372
|
delegation,
|
|
8013
8373
|
{ host }
|
|
@@ -8023,11 +8383,7 @@ async function importRequestBoundDelegation(context, input) {
|
|
|
8023
8383
|
context.runtime.node,
|
|
8024
8384
|
context.summary.space
|
|
8025
8385
|
);
|
|
8026
|
-
if (request.requested
|
|
8027
|
-
request.requested,
|
|
8028
|
-
effectivePermissions,
|
|
8029
|
-
resolveSpace2
|
|
8030
|
-
).satisfied) {
|
|
8386
|
+
if (!delegationWithinRequest(request.requested, effectivePermissions, resolveSpace2)) {
|
|
8031
8387
|
return operationFailure(
|
|
8032
8388
|
"DELEGATION_REJECTED",
|
|
8033
8389
|
"The delegation exceeds the stored authority request."
|
|
@@ -8055,12 +8411,19 @@ async function importRequestBoundDelegation(context, input) {
|
|
|
8055
8411
|
)
|
|
8056
8412
|
};
|
|
8057
8413
|
}
|
|
8058
|
-
const
|
|
8414
|
+
const record = {
|
|
8415
|
+
delegation: activated.delegation,
|
|
8416
|
+
permissions: activated.effectivePermissions,
|
|
8417
|
+
authorityRequest: {
|
|
8418
|
+
requestId: request.requestId,
|
|
8419
|
+
requested: request.requested
|
|
8420
|
+
}
|
|
8421
|
+
};
|
|
8422
|
+
const position = records.findIndex((entry) => delegationCid(entry) === activated.cid);
|
|
8423
|
+
const alreadyPresent = position !== -1;
|
|
8424
|
+
const others = records.filter((entry) => delegationCid(entry) !== activated.cid);
|
|
8059
8425
|
return {
|
|
8060
|
-
records: alreadyPresent ?
|
|
8061
|
-
delegation: activated.delegation,
|
|
8062
|
-
permissions: activated.effectivePermissions
|
|
8063
|
-
}],
|
|
8426
|
+
records: alreadyPresent ? [...others.slice(0, position), record, ...others.slice(position)] : [...records, record],
|
|
8064
8427
|
result: {
|
|
8065
8428
|
status: "ok",
|
|
8066
8429
|
output: {
|
|
@@ -9788,6 +10151,173 @@ ORDER BY type, name`;
|
|
|
9788
10151
|
}
|
|
9789
10152
|
});
|
|
9790
10153
|
|
|
10154
|
+
// src/delegation-binding.ts
|
|
10155
|
+
import { join as join2 } from "path";
|
|
10156
|
+
import * as nodeSdk3 from "@tinycloud/node-sdk";
|
|
10157
|
+
function bindingMigrationPath(profile) {
|
|
10158
|
+
return join2(profilePath(profile), "delegation-binding-migration.json");
|
|
10159
|
+
}
|
|
10160
|
+
function storedDelegationKind(entry) {
|
|
10161
|
+
if (!isRecord4(entry)) return "refused";
|
|
10162
|
+
const delegation = entry.delegation;
|
|
10163
|
+
if (!isRecord4(delegation)) return "refused";
|
|
10164
|
+
const header = delegation.delegationHeader;
|
|
10165
|
+
if (!isRecord4(header) || Object.keys(header).length !== 1 || !Object.prototype.hasOwnProperty.call(header, "Authorization") || typeof header.Authorization !== "string") {
|
|
10166
|
+
return "refused";
|
|
10167
|
+
}
|
|
10168
|
+
if ("siweProof" in delegation) return "signed-login";
|
|
10169
|
+
if (header.Authorization.includes(".")) return "compact";
|
|
10170
|
+
return "authorityRequest" in entry ? "refused" : "other";
|
|
10171
|
+
}
|
|
10172
|
+
function replayLimit(entry, migrated) {
|
|
10173
|
+
const kind = storedDelegationKind(entry);
|
|
10174
|
+
if (!isRecord4(entry) || kind !== "compact" && kind !== "signed-login") return void 0;
|
|
10175
|
+
const binding = DelegationRequestBindingSchema.safeParse(entry.authorityRequest);
|
|
10176
|
+
if (binding.success) return binding.data.requested;
|
|
10177
|
+
return !migrated && kind === "compact" && !("authorityRequest" in entry) ? "signed" : void 0;
|
|
10178
|
+
}
|
|
10179
|
+
function bindToSignedCapabilities(record, signed, source, recordedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
10180
|
+
return {
|
|
10181
|
+
...record,
|
|
10182
|
+
authorityRequest: DelegationRequestBindingSchema.parse({
|
|
10183
|
+
requestId: `${REQUEST_ID_PREFIX[source]}:${record.delegation.cid}`,
|
|
10184
|
+
requested: structuredClone(signed)
|
|
10185
|
+
}),
|
|
10186
|
+
authorityRequestAudit: { source, recordedAt, note: BINDING_NOTES[source] }
|
|
10187
|
+
};
|
|
10188
|
+
}
|
|
10189
|
+
async function prepareStoredDelegationReplay(profile, node, options) {
|
|
10190
|
+
if (await bindingMigrationRecorded(profile)) return true;
|
|
10191
|
+
if (!options.migrate || !await holdsPersistedSession(profile, node)) return false;
|
|
10192
|
+
try {
|
|
10193
|
+
return await withProfileLock(profile, async () => {
|
|
10194
|
+
if (await bindingMigrationRecorded(profile)) return true;
|
|
10195
|
+
if (!await holdsPersistedSession(profile, node)) return false;
|
|
10196
|
+
const recordedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10197
|
+
const bound = /* @__PURE__ */ new Map();
|
|
10198
|
+
const unbound = [];
|
|
10199
|
+
for (const entry of await readAdditionalDelegations(profile)) {
|
|
10200
|
+
if (!isRecord4(entry) || "authorityRequest" in entry || storedDelegationKind(entry) !== "compact") continue;
|
|
10201
|
+
const migrated = await migratedRecord(node, entry, options.host, recordedAt);
|
|
10202
|
+
if (migrated === void 0) unbound.push(String(storedCid(entry)));
|
|
10203
|
+
else bound.set(recordKey(entry), migrated);
|
|
10204
|
+
}
|
|
10205
|
+
if (bound.size > 0) {
|
|
10206
|
+
await updateProfileStore(profile, "additional-delegations", (records) => ({
|
|
10207
|
+
records: records.map((entry) => {
|
|
10208
|
+
const key = !isRecord4(entry) || "authorityRequest" in entry ? void 0 : recordKey(entry);
|
|
10209
|
+
return (key === void 0 ? void 0 : bound.get(key)) ?? entry;
|
|
10210
|
+
}),
|
|
10211
|
+
result: void 0
|
|
10212
|
+
}));
|
|
10213
|
+
}
|
|
10214
|
+
await writeJsonAtomic(bindingMigrationPath(profile), {
|
|
10215
|
+
formatVersion: 1,
|
|
10216
|
+
migratedAt: recordedAt,
|
|
10217
|
+
bound: [...bound.values()].map(storedCid),
|
|
10218
|
+
unbound
|
|
10219
|
+
});
|
|
10220
|
+
return true;
|
|
10221
|
+
}, { timeoutMs: MIGRATION_LOCK_TIMEOUT_MS });
|
|
10222
|
+
} catch {
|
|
10223
|
+
return false;
|
|
10224
|
+
}
|
|
10225
|
+
}
|
|
10226
|
+
async function holdsPersistedSession(profile, node) {
|
|
10227
|
+
try {
|
|
10228
|
+
const persisted = await readSession(profile);
|
|
10229
|
+
return typeof persisted?.verificationMethod === "string" && typeof node.sessionDid === "string" && persisted.verificationMethod.split("#", 1)[0] === node.sessionDid.split("#", 1)[0];
|
|
10230
|
+
} catch {
|
|
10231
|
+
return false;
|
|
10232
|
+
}
|
|
10233
|
+
}
|
|
10234
|
+
async function migratedRecord(node, entry, host, recordedAt) {
|
|
10235
|
+
const delegation = normalizeStoredDelegation(entry);
|
|
10236
|
+
if (delegation === void 0) return void 0;
|
|
10237
|
+
const signed = await signedCapabilities(node, delegation, host);
|
|
10238
|
+
if (signed === void 0) return void 0;
|
|
10239
|
+
try {
|
|
10240
|
+
return bindToSignedCapabilities({ ...entry, delegation: { ...entry.delegation, cid: delegation.cid } }, signed, "migration", recordedAt);
|
|
10241
|
+
} catch {
|
|
10242
|
+
return void 0;
|
|
10243
|
+
}
|
|
10244
|
+
}
|
|
10245
|
+
async function replayStoredDelegation(node, entry, options) {
|
|
10246
|
+
const delegation = normalizeStoredDelegation(entry);
|
|
10247
|
+
if (delegation === void 0 || delegation.expiry.getTime() <= Date.now()) return void 0;
|
|
10248
|
+
const limit = replayLimit(entry, options.migrated);
|
|
10249
|
+
if (limit === void 0) return void 0;
|
|
10250
|
+
const authorize = limit === "signed" ? () => true : (effective) => delegationWithinRequest(limit, effective, options.resolveSpace);
|
|
10251
|
+
try {
|
|
10252
|
+
return await nodeSdk3.activateValidatedRuntimeDelegation(node, delegation, { host: options.host, authorize });
|
|
10253
|
+
} catch {
|
|
10254
|
+
return void 0;
|
|
10255
|
+
}
|
|
10256
|
+
}
|
|
10257
|
+
async function bindingMigrationRecorded(profile) {
|
|
10258
|
+
try {
|
|
10259
|
+
return await readJson(bindingMigrationPath(profile)) !== null;
|
|
10260
|
+
} catch {
|
|
10261
|
+
return true;
|
|
10262
|
+
}
|
|
10263
|
+
}
|
|
10264
|
+
async function signedCapabilities(node, delegation, host) {
|
|
10265
|
+
let signed;
|
|
10266
|
+
try {
|
|
10267
|
+
await nodeSdk3.activateValidatedRuntimeDelegation(node, delegation, {
|
|
10268
|
+
// The transport host is not signed authority; check it at replay instead.
|
|
10269
|
+
host: delegation.host ?? host,
|
|
10270
|
+
authorize: (effective) => {
|
|
10271
|
+
signed = effective;
|
|
10272
|
+
return false;
|
|
10273
|
+
}
|
|
10274
|
+
});
|
|
10275
|
+
} catch {
|
|
10276
|
+
}
|
|
10277
|
+
return signed;
|
|
10278
|
+
}
|
|
10279
|
+
function normalizeStoredDelegation(entry) {
|
|
10280
|
+
if (!isRecord4(entry)) return void 0;
|
|
10281
|
+
const raw = entry.delegation;
|
|
10282
|
+
if (!isRecord4(raw) || !isRecord4(raw.delegationHeader)) return void 0;
|
|
10283
|
+
const expiry = raw.expiry instanceof Date ? raw.expiry : typeof raw.expiry === "string" ? new Date(raw.expiry) : void 0;
|
|
10284
|
+
if (expiry === void 0 || Number.isNaN(expiry.getTime()) || typeof raw.cid !== "string" || typeof raw.spaceId !== "string" || typeof raw.path !== "string" || !Array.isArray(raw.actions) || !raw.actions.every((action) => typeof action === "string") || typeof raw.delegateDID !== "string" || typeof raw.ownerAddress !== "string" || typeof raw.chainId !== "number" || typeof raw.delegationHeader.Authorization !== "string") {
|
|
10285
|
+
return void 0;
|
|
10286
|
+
}
|
|
10287
|
+
return { ...raw, expiry };
|
|
10288
|
+
}
|
|
10289
|
+
function storedCid(entry) {
|
|
10290
|
+
const cid = isRecord4(entry) && isRecord4(entry.delegation) ? entry.delegation.cid : void 0;
|
|
10291
|
+
return typeof cid === "string" ? cid : void 0;
|
|
10292
|
+
}
|
|
10293
|
+
function recordKey(entry) {
|
|
10294
|
+
const delegation = entry.delegation;
|
|
10295
|
+
if (!isRecord4(delegation) || !isRecord4(delegation.delegationHeader)) return void 0;
|
|
10296
|
+
return JSON.stringify([delegation.cid, delegation.delegationHeader.Authorization]);
|
|
10297
|
+
}
|
|
10298
|
+
function isRecord4(value) {
|
|
10299
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10300
|
+
}
|
|
10301
|
+
var REQUEST_ID_PREFIX, BINDING_NOTES, MIGRATION_LOCK_TIMEOUT_MS;
|
|
10302
|
+
var init_delegation_binding = __esm({
|
|
10303
|
+
"src/delegation-binding.ts"() {
|
|
10304
|
+
"use strict";
|
|
10305
|
+
init_artifacts();
|
|
10306
|
+
init_authority();
|
|
10307
|
+
init_state();
|
|
10308
|
+
init_secrets();
|
|
10309
|
+
REQUEST_ID_PREFIX = {
|
|
10310
|
+
migration: "migrated",
|
|
10311
|
+
"unbound-import": "unbound-import"
|
|
10312
|
+
};
|
|
10313
|
+
BINDING_NOTES = {
|
|
10314
|
+
migration: "Stored without a request binding before bindings were required. Bound at migration to exactly its own signed capabilities, so replay refuses any delegation in this record that exceeds them.",
|
|
10315
|
+
"unbound-import": "Imported by `tc auth import` without a stored request. Bound to exactly its own signed capabilities, so replay refuses any delegation in this record that exceeds them."
|
|
10316
|
+
};
|
|
10317
|
+
MIGRATION_LOCK_TIMEOUT_MS = 250;
|
|
10318
|
+
}
|
|
10319
|
+
});
|
|
10320
|
+
|
|
9791
10321
|
// src/operations/status.ts
|
|
9792
10322
|
import { z as z7 } from "zod";
|
|
9793
10323
|
function createStatusDefinition(params) {
|
|
@@ -9844,7 +10374,7 @@ async function inspectPinnedProfileStatus(context, _input) {
|
|
|
9844
10374
|
async function pinnedProfileExists(profile) {
|
|
9845
10375
|
try {
|
|
9846
10376
|
const storedProfile = await readProfile(profile);
|
|
9847
|
-
return
|
|
10377
|
+
return isRecord5(storedProfile);
|
|
9848
10378
|
} catch {
|
|
9849
10379
|
return false;
|
|
9850
10380
|
}
|
|
@@ -9855,16 +10385,18 @@ async function countLiveAdditionalDelegations(profile) {
|
|
|
9855
10385
|
if (rawDelegations === null) return 0;
|
|
9856
10386
|
if (!Array.isArray(rawDelegations)) throw new TypeError("Invalid additional delegation store.");
|
|
9857
10387
|
const now = Date.now();
|
|
10388
|
+
const migrated = await bindingMigrationRecorded(profile);
|
|
9858
10389
|
let count = 0;
|
|
9859
10390
|
for (const entry of rawDelegations) {
|
|
10391
|
+
if (storedDelegationKind(entry) === "refused") continue;
|
|
9860
10392
|
const expiry = storedDelegationExpiry(entry);
|
|
9861
|
-
if (expiry.getTime() > now) count += 1;
|
|
10393
|
+
if (expiry.getTime() > now && replayLimit(entry, migrated) !== void 0) count += 1;
|
|
9862
10394
|
}
|
|
9863
10395
|
return count;
|
|
9864
10396
|
}
|
|
9865
10397
|
function summarizeSession(session) {
|
|
9866
10398
|
if (session === null) return { present: false, expired: null, expiresAt: null };
|
|
9867
|
-
if (!
|
|
10399
|
+
if (!isRecord5(session)) throw new TypeError("Invalid session store.");
|
|
9868
10400
|
const expiry = extractSessionExpiry(session);
|
|
9869
10401
|
if (expiry === null) return { present: true, expired: null, expiresAt: null };
|
|
9870
10402
|
return {
|
|
@@ -9874,7 +10406,7 @@ function summarizeSession(session) {
|
|
|
9874
10406
|
};
|
|
9875
10407
|
}
|
|
9876
10408
|
function storedDelegationExpiry(entry) {
|
|
9877
|
-
if (!
|
|
10409
|
+
if (!isRecord5(entry) || !isRecord5(entry.delegation) || !("expiry" in entry.delegation)) {
|
|
9878
10410
|
throw new TypeError("Invalid stored delegation.");
|
|
9879
10411
|
}
|
|
9880
10412
|
return parseExpiry(entry.delegation.expiry);
|
|
@@ -9903,13 +10435,14 @@ function profileNotFound(profile) {
|
|
|
9903
10435
|
)
|
|
9904
10436
|
};
|
|
9905
10437
|
}
|
|
9906
|
-
function
|
|
10438
|
+
function isRecord5(value) {
|
|
9907
10439
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9908
10440
|
}
|
|
9909
10441
|
var StatusInputSchema, StatusOutputSchema, STATUS_POSTURES, STATUS_EXPOSURE, STATUS_SENSITIVITY, statusOperationDefinitions;
|
|
9910
10442
|
var init_status = __esm({
|
|
9911
10443
|
"src/operations/status.ts"() {
|
|
9912
10444
|
"use strict";
|
|
10445
|
+
init_delegation_binding();
|
|
9913
10446
|
init_errors();
|
|
9914
10447
|
init_state();
|
|
9915
10448
|
StatusInputSchema = z7.object({}).strict();
|
|
@@ -9932,7 +10465,9 @@ var init_status = __esm({
|
|
|
9932
10465
|
expired: z7.boolean().nullable(),
|
|
9933
10466
|
expiresAt: z7.string().datetime({ offset: true }).nullable()
|
|
9934
10467
|
}).strict(),
|
|
9935
|
-
liveAdditionalDelegationCount: z7.number().int().nonnegative()
|
|
10468
|
+
liveAdditionalDelegationCount: z7.number().int().nonnegative().describe(
|
|
10469
|
+
"Stored, unexpired delegations the replay rule accepts: compact-UCAN or signed-login records with a request binding, and unbound compact-UCAN records before the profile's one-time binding migration. Each is still validated, and checked against its binding, when a runtime installs it."
|
|
10470
|
+
)
|
|
9936
10471
|
}).strict();
|
|
9937
10472
|
STATUS_POSTURES = [
|
|
9938
10473
|
"owner-openkey",
|
|
@@ -10086,9 +10621,9 @@ function isOperatorType(value) {
|
|
|
10086
10621
|
return value === "human" || value === "agent";
|
|
10087
10622
|
}
|
|
10088
10623
|
function isStoredProfile(value) {
|
|
10089
|
-
return
|
|
10624
|
+
return isRecord6(value) && isNonEmptyString(value.name) && isNonEmptyString(value.host) && typeof value.chainId === "number" && Number.isFinite(value.chainId) && isNonEmptyString(value.spaceName) && isNonEmptyString(value.did) && isNonEmptyString(value.createdAt) && isOptionalNonEmptyString(value.sessionDid) && isOptionalNonEmptyString(value.ownerDid) && isOptionalNonEmptyString(value.spaceId) && isOptionalPosture(value.posture) && isOptionalOperatorType(value.operatorType) && isOptionalAuthMethod(value.authMethod) && isOptionalNonEmptyString(value.defaultSpace) && isOptionalNonEmptyString(value.privateKey) && isOptionalNonEmptyString(value.address) && isOptionalNonEmptyString(value.openkeyHost);
|
|
10090
10625
|
}
|
|
10091
|
-
function
|
|
10626
|
+
function isRecord6(value) {
|
|
10092
10627
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10093
10628
|
}
|
|
10094
10629
|
function isNonEmptyString(value) {
|
|
@@ -10129,7 +10664,7 @@ var runtime_exports = {};
|
|
|
10129
10664
|
__export(runtime_exports, {
|
|
10130
10665
|
createInvocationRuntime: () => createInvocationRuntime
|
|
10131
10666
|
});
|
|
10132
|
-
import * as
|
|
10667
|
+
import * as nodeSdk4 from "@tinycloud/node-sdk";
|
|
10133
10668
|
async function createInvocationRuntime(target, requirement = "authenticated") {
|
|
10134
10669
|
const resolved = await resolveInvocationProfile(target);
|
|
10135
10670
|
if (!resolved.ok) {
|
|
@@ -10146,14 +10681,12 @@ async function createInvocationRuntime(target, requirement = "authenticated") {
|
|
|
10146
10681
|
const profileName = summary.profile;
|
|
10147
10682
|
try {
|
|
10148
10683
|
const {
|
|
10149
|
-
activateValidatedRuntimeDelegation: activateValidatedRuntimeDelegation2,
|
|
10150
10684
|
TinyCloudNode: TinyCloudNodeConstructor
|
|
10151
|
-
} =
|
|
10685
|
+
} = nodeSdk4;
|
|
10152
10686
|
const explicitPrivateKeyOverride = typeof target.privateKey === "string";
|
|
10153
|
-
const [session, key
|
|
10687
|
+
const [session, key] = explicitPrivateKeyOverride ? [null, null] : await Promise.all([
|
|
10154
10688
|
readSession(profileName),
|
|
10155
|
-
readJson(`${profilePath(profileName)}/key.json`)
|
|
10156
|
-
readAdditionalDelegations(profileName)
|
|
10689
|
+
readJson(`${profilePath(profileName)}/key.json`)
|
|
10157
10690
|
]);
|
|
10158
10691
|
const profile = resolved.profile;
|
|
10159
10692
|
const actualPosture = resolvePosture(profile);
|
|
@@ -10200,19 +10733,26 @@ async function createInvocationRuntime(target, requirement = "authenticated") {
|
|
|
10200
10733
|
const livePrincipalDid = normalizeDid(node.did);
|
|
10201
10734
|
const authenticatedSpace = explicitPrivateKeyOverride ? spaceForAuthenticatedPrincipal(livePrincipalDid) : void 0;
|
|
10202
10735
|
const livePermissions = [...node.getVerifiedSessionCapabilities()];
|
|
10736
|
+
const resolveSpace2 = operationSpaceResolver(node, authenticatedSpace ?? summary.space);
|
|
10203
10737
|
const seenCids = /* @__PURE__ */ new Set();
|
|
10204
|
-
|
|
10205
|
-
const
|
|
10206
|
-
|
|
10207
|
-
|
|
10208
|
-
|
|
10209
|
-
|
|
10738
|
+
if (!explicitPrivateKeyOverride) {
|
|
10739
|
+
const activator = node;
|
|
10740
|
+
const migrated = await prepareStoredDelegationReplay(profileName, activator, {
|
|
10741
|
+
host: summary.host,
|
|
10742
|
+
// Only the profile's own restored session; a local sign-in without
|
|
10743
|
+
// one uses a fresh session key the stored records do not address.
|
|
10744
|
+
migrate: activeSession !== void 0
|
|
10745
|
+
});
|
|
10746
|
+
for (const entry of await readAdditionalDelegations(profileName)) {
|
|
10747
|
+
const activated = await replayStoredDelegation(activator, entry, {
|
|
10748
|
+
host: summary.host,
|
|
10749
|
+
migrated,
|
|
10750
|
+
resolveSpace: resolveSpace2
|
|
10210
10751
|
});
|
|
10211
|
-
if (!seenCids.has(activated.cid)) {
|
|
10752
|
+
if (activated !== void 0 && !seenCids.has(activated.cid)) {
|
|
10212
10753
|
seenCids.add(activated.cid);
|
|
10213
10754
|
livePermissions.push(...activated.effectivePermissions);
|
|
10214
10755
|
}
|
|
10215
|
-
} catch {
|
|
10216
10756
|
}
|
|
10217
10757
|
}
|
|
10218
10758
|
const runtime = {
|
|
@@ -10244,8 +10784,8 @@ async function createInvocationRuntime(target, requirement = "authenticated") {
|
|
|
10244
10784
|
function spaceForAuthenticatedPrincipal(principal2) {
|
|
10245
10785
|
if (principal2 === void 0) return void 0;
|
|
10246
10786
|
try {
|
|
10247
|
-
const pkh =
|
|
10248
|
-
return pkh === null ? void 0 :
|
|
10787
|
+
const pkh = nodeSdk4.parsePkhDid(principal2);
|
|
10788
|
+
return pkh === null ? void 0 : nodeSdk4.makePkhSpaceId(pkh.address, pkh.chainId, "secrets");
|
|
10249
10789
|
} catch {
|
|
10250
10790
|
return void 0;
|
|
10251
10791
|
}
|
|
@@ -10253,7 +10793,7 @@ function spaceForAuthenticatedPrincipal(principal2) {
|
|
|
10253
10793
|
function normalizeSession(session, key) {
|
|
10254
10794
|
const header = session.delegationHeader;
|
|
10255
10795
|
const jwk = hasPrivateParameter(session.jwk) ? session.jwk : key;
|
|
10256
|
-
if (!
|
|
10796
|
+
if (!isRecord7(header) || typeof header.Authorization !== "string" || typeof session.delegationCid !== "string" || typeof session.spaceId !== "string" || !hasPrivateParameter(jwk) || typeof session.verificationMethod !== "string") {
|
|
10257
10797
|
return void 0;
|
|
10258
10798
|
}
|
|
10259
10799
|
return {
|
|
@@ -10269,27 +10809,14 @@ function normalizeSession(session, key) {
|
|
|
10269
10809
|
...Array.isArray(session.tinycloudHosts) && session.tinycloudHosts.every((host) => typeof host === "string") ? { tinycloudHosts: session.tinycloudHosts } : {}
|
|
10270
10810
|
};
|
|
10271
10811
|
}
|
|
10272
|
-
function normalizeStoredDelegation(entry) {
|
|
10273
|
-
const raw = entry.delegation;
|
|
10274
|
-
if (!isRecord6(raw) || !isRecord6(raw.delegationHeader)) return void 0;
|
|
10275
|
-
const expiry = parseExpiry2(raw.expiry);
|
|
10276
|
-
if (expiry === void 0 || typeof raw.cid !== "string" || typeof raw.spaceId !== "string" || typeof raw.path !== "string" || !Array.isArray(raw.actions) || !raw.actions.every((action) => typeof action === "string") || typeof raw.delegateDID !== "string" || typeof raw.ownerAddress !== "string" || typeof raw.chainId !== "number" || typeof raw.delegationHeader.Authorization !== "string") {
|
|
10277
|
-
return void 0;
|
|
10278
|
-
}
|
|
10279
|
-
return { ...raw, expiry };
|
|
10280
|
-
}
|
|
10281
|
-
function parseExpiry2(value) {
|
|
10282
|
-
const expiry = value instanceof Date ? value : typeof value === "string" ? new Date(value) : void 0;
|
|
10283
|
-
return expiry !== void 0 && !Number.isNaN(expiry.getTime()) ? expiry : void 0;
|
|
10284
|
-
}
|
|
10285
10812
|
function hasPrivateParameter(value) {
|
|
10286
|
-
return
|
|
10813
|
+
return isRecord7(value) && typeof value.d === "string" && value.d.length > 0;
|
|
10287
10814
|
}
|
|
10288
10815
|
function normalizeDid(value) {
|
|
10289
10816
|
if (typeof value !== "string" || value.length === 0) return void 0;
|
|
10290
10817
|
return value.split("#", 1)[0];
|
|
10291
10818
|
}
|
|
10292
|
-
function
|
|
10819
|
+
function isRecord7(value) {
|
|
10293
10820
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10294
10821
|
}
|
|
10295
10822
|
function failed(context, error) {
|
|
@@ -10308,7 +10835,9 @@ var init_runtime = __esm({
|
|
|
10308
10835
|
init_authority();
|
|
10309
10836
|
init_errors();
|
|
10310
10837
|
init_profile();
|
|
10838
|
+
init_secrets();
|
|
10311
10839
|
init_state();
|
|
10840
|
+
init_delegation_binding();
|
|
10312
10841
|
}
|
|
10313
10842
|
});
|
|
10314
10843
|
|