@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.cjs
CHANGED
|
@@ -6967,6 +6967,9 @@ function evaluateOperationAuthority(granted, required, resolveSpace2) {
|
|
|
6967
6967
|
missing: canonicalizeCapabilities(missing)
|
|
6968
6968
|
};
|
|
6969
6969
|
}
|
|
6970
|
+
function delegationWithinRequest(requested, effective, resolveSpace2) {
|
|
6971
|
+
return requested.length > 0 && evaluateOperationAuthority(requested, canonicalizeCapabilities(effective), resolveSpace2).satisfied;
|
|
6972
|
+
}
|
|
6970
6973
|
function isExactCapabilityMemberSubset(hinted, planned, resolveSpace2) {
|
|
6971
6974
|
const canonicalHinted = canonicalizeOperationCapabilities(hinted, resolveSpace2);
|
|
6972
6975
|
const canonicalPlanned = canonicalizeOperationCapabilities(planned, resolveSpace2);
|
|
@@ -7079,6 +7082,11 @@ var init_authority = __esm({
|
|
|
7079
7082
|
});
|
|
7080
7083
|
|
|
7081
7084
|
// src/state.ts
|
|
7085
|
+
function processWideContext(key) {
|
|
7086
|
+
var _a2;
|
|
7087
|
+
const registry = globalThis;
|
|
7088
|
+
return registry[_a2 = Symbol.for(key)] ?? (registry[_a2] = new import_node_async_hooks.AsyncLocalStorage());
|
|
7089
|
+
}
|
|
7082
7090
|
function tinycloudHomePath() {
|
|
7083
7091
|
const home = invocationStateRoot.getStore() ?? process.env.TC_HOME ?? process.env.HOME ?? process.env.USERPROFILE ?? (0, import_node_os.homedir)();
|
|
7084
7092
|
return (0, import_node_path.join)(home, ".tinycloud");
|
|
@@ -7117,6 +7125,9 @@ function profileStoreMetadataPath(profile, store) {
|
|
|
7117
7125
|
function profileLockPath(profile) {
|
|
7118
7126
|
return (0, import_node_path.join)(profilePath(profile), ".lock");
|
|
7119
7127
|
}
|
|
7128
|
+
function profileTurnLockPath(profile) {
|
|
7129
|
+
return (0, import_node_path.join)(tinycloudHomePath(), "profile-locks", validateProfileName(profile));
|
|
7130
|
+
}
|
|
7120
7131
|
async function readJson(filePath) {
|
|
7121
7132
|
try {
|
|
7122
7133
|
return JSON.parse(await (0, import_promises.readFile)(filePath, "utf8"));
|
|
@@ -7126,6 +7137,9 @@ async function readJson(filePath) {
|
|
|
7126
7137
|
}
|
|
7127
7138
|
}
|
|
7128
7139
|
async function writeJsonAtomic(filePath, value) {
|
|
7140
|
+
await replaceJson(filePath, value, false);
|
|
7141
|
+
}
|
|
7142
|
+
async function replaceJson(filePath, value, durable) {
|
|
7129
7143
|
const directory = (0, import_node_path.dirname)(filePath);
|
|
7130
7144
|
const temporaryPath = (0, import_node_path.join)(
|
|
7131
7145
|
directory,
|
|
@@ -7135,13 +7149,25 @@ async function writeJsonAtomic(filePath, value) {
|
|
|
7135
7149
|
`;
|
|
7136
7150
|
await (0, import_promises.mkdir)(directory, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
7137
7151
|
try {
|
|
7138
|
-
|
|
7152
|
+
if (durable) await writeFileDurably(temporaryPath, contents);
|
|
7153
|
+
else await (0, import_promises.writeFile)(temporaryPath, contents, { encoding: "utf8", mode: PRIVATE_FILE_MODE });
|
|
7139
7154
|
await (0, import_promises.rename)(temporaryPath, filePath);
|
|
7140
7155
|
} catch (error) {
|
|
7141
7156
|
await (0, import_promises.rm)(temporaryPath, { force: true }).catch(() => void 0);
|
|
7142
7157
|
throw error;
|
|
7143
7158
|
}
|
|
7144
7159
|
}
|
|
7160
|
+
async function writeFileDurably(path, contents) {
|
|
7161
|
+
const handle = await (0, import_promises.open)(path, "wx", PRIVATE_FILE_MODE);
|
|
7162
|
+
try {
|
|
7163
|
+
if (contents) {
|
|
7164
|
+
await handle.writeFile(contents, "utf8");
|
|
7165
|
+
await handle.datasync();
|
|
7166
|
+
}
|
|
7167
|
+
} finally {
|
|
7168
|
+
await handle.close();
|
|
7169
|
+
}
|
|
7170
|
+
}
|
|
7145
7171
|
async function readProfile(profile) {
|
|
7146
7172
|
return readJson(profileConfigPath(profile));
|
|
7147
7173
|
}
|
|
@@ -7178,15 +7204,28 @@ async function withProfileLock(profile, action, options = {}) {
|
|
|
7178
7204
|
const lockPath = profileLockPath(normalizedProfile);
|
|
7179
7205
|
const held = (heldProfileLocks.getStore() ?? []).filter((ownership2) => ownership2.active);
|
|
7180
7206
|
if (held.some((ownership2) => ownership2.lockPath === lockPath)) return action();
|
|
7181
|
-
const
|
|
7182
|
-
const ownership = {
|
|
7207
|
+
const acquired = await acquireProfileLock(normalizedProfile, options);
|
|
7208
|
+
const ownership = {
|
|
7209
|
+
lockPath,
|
|
7210
|
+
turnSlot: acquired.turnSlot,
|
|
7211
|
+
deletedWhileWaiting: acquired.deletedWhileWaiting,
|
|
7212
|
+
active: true
|
|
7213
|
+
};
|
|
7183
7214
|
try {
|
|
7184
7215
|
return await heldProfileLocks.run([...held, ownership], action);
|
|
7185
7216
|
} finally {
|
|
7186
7217
|
ownership.active = false;
|
|
7187
|
-
await release();
|
|
7218
|
+
await acquired.release();
|
|
7219
|
+
if (acquired.deletedWhileWaiting) await (0, import_promises.rmdir)(profilePath(normalizedProfile)).catch(() => void 0);
|
|
7188
7220
|
}
|
|
7189
7221
|
}
|
|
7222
|
+
async function refuseWriteToDeletedProfile(profile) {
|
|
7223
|
+
const lockPath = profileLockPath(profile);
|
|
7224
|
+
const ownership = (heldProfileLocks.getStore() ?? []).find((candidate) => candidate.active && candidate.lockPath === lockPath);
|
|
7225
|
+
if (!ownership?.deletedWhileWaiting) return;
|
|
7226
|
+
if (await exists(profileConfigPath(profile))) return;
|
|
7227
|
+
throw new ProfileDeletedError(profile);
|
|
7228
|
+
}
|
|
7190
7229
|
async function updateProfileStore(profile, store, update, options = {}) {
|
|
7191
7230
|
return withProfileLock(
|
|
7192
7231
|
profile,
|
|
@@ -7195,6 +7234,7 @@ async function updateProfileStore(profile, store, update, options = {}) {
|
|
|
7195
7234
|
);
|
|
7196
7235
|
}
|
|
7197
7236
|
async function updateProfileStoreWhileLocked(profile, store, update) {
|
|
7237
|
+
await refuseWriteToDeletedProfile(profile);
|
|
7198
7238
|
const current = await readProfileStore(profile, store);
|
|
7199
7239
|
const next = await update(current.records);
|
|
7200
7240
|
await writeJsonAtomic(profileStorePath(profile, store), next.records);
|
|
@@ -7205,32 +7245,271 @@ async function writeFormatOneMetadata(profile, store) {
|
|
|
7205
7245
|
await writeJsonAtomic(profileStoreMetadataPath(profile, store), { formatVersion: 1 });
|
|
7206
7246
|
}
|
|
7207
7247
|
async function acquireProfileLock(profile, options) {
|
|
7208
|
-
const
|
|
7209
|
-
|
|
7248
|
+
const deadline = {
|
|
7249
|
+
startedAt: Date.now(),
|
|
7250
|
+
timeoutMs: options.timeoutMs ?? DEFAULT_LOCK_TIMEOUT_MS,
|
|
7251
|
+
retryMs: options.retryMs ?? DEFAULT_LOCK_RETRY_MS
|
|
7252
|
+
};
|
|
7210
7253
|
const staleAfterMs = options.staleAfterMs ?? DEFAULT_STALE_LOCK_MS;
|
|
7211
|
-
const startedAt = Date.now();
|
|
7212
7254
|
const lockPath = profileLockPath(profile);
|
|
7255
|
+
const turnDirectory = profileTurnLockPath(profile);
|
|
7213
7256
|
const directory = profilePath(profile);
|
|
7214
7257
|
await (0, import_promises.mkdir)(directory, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
7258
|
+
await (0, import_promises.mkdir)((0, import_node_path.dirname)(turnDirectory), { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
7215
7259
|
for (const path of [tinycloudHomePath(), profilesPath(), directory]) {
|
|
7216
7260
|
await (0, import_promises.chmod)(path, PRIVATE_DIR_MODE).catch((error) => {
|
|
7217
7261
|
if (path !== directory || !isErrno(error, "ENOENT")) throw error;
|
|
7218
7262
|
});
|
|
7219
7263
|
}
|
|
7220
|
-
|
|
7221
|
-
|
|
7222
|
-
|
|
7223
|
-
|
|
7224
|
-
|
|
7225
|
-
|
|
7264
|
+
const { turn, firstWaitedTurn } = await acquireTurn(profile, turnDirectory, deadline);
|
|
7265
|
+
try {
|
|
7266
|
+
const deletion = await readJson((0, import_node_path.join)(turnDirectory, "deleted.json")).catch(() => null);
|
|
7267
|
+
const deletedWhileWaiting = typeof deletion?.slot === "number" && deletion.slot >= firstWaitedTurn;
|
|
7268
|
+
while (true) {
|
|
7269
|
+
const token = (0, import_node_crypto.randomUUID)();
|
|
7270
|
+
if (await publishProfileLock(profile, lockPath, token)) {
|
|
7271
|
+
return {
|
|
7272
|
+
deletedWhileWaiting,
|
|
7273
|
+
turnSlot: turn.slot,
|
|
7274
|
+
release: async () => {
|
|
7275
|
+
try {
|
|
7276
|
+
await releaseProfileLock(profile, lockPath, token);
|
|
7277
|
+
} finally {
|
|
7278
|
+
await releaseTurn(profile, turn);
|
|
7279
|
+
}
|
|
7280
|
+
}
|
|
7281
|
+
};
|
|
7282
|
+
}
|
|
7283
|
+
await signalTestLockContention(profile);
|
|
7284
|
+
if (await recoverStaleLock(profile, lockPath, staleAfterMs)) continue;
|
|
7285
|
+
await waitOrTimeOut(profile, deadline);
|
|
7286
|
+
}
|
|
7287
|
+
} catch (error) {
|
|
7288
|
+
await releaseTurn(profile, turn);
|
|
7289
|
+
throw error;
|
|
7290
|
+
}
|
|
7291
|
+
}
|
|
7292
|
+
function checkDeadline(profile, deadline, detail) {
|
|
7293
|
+
if (Date.now() - deadline.startedAt >= deadline.timeoutMs) {
|
|
7294
|
+
throw new ProfileLockTimeoutError(profile, deadline.timeoutMs, detail);
|
|
7295
|
+
}
|
|
7296
|
+
}
|
|
7297
|
+
async function waitOrTimeOut(profile, deadline, detail) {
|
|
7298
|
+
checkDeadline(profile, deadline, detail);
|
|
7299
|
+
await sleep(Math.min(deadline.retryMs, deadline.timeoutMs - (Date.now() - deadline.startedAt)));
|
|
7300
|
+
}
|
|
7301
|
+
async function acquireTurn(profile, directory, deadline) {
|
|
7302
|
+
let firstWaitedTurn;
|
|
7303
|
+
let problem;
|
|
7304
|
+
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.`;
|
|
7305
|
+
for (let first = true; ; first = false) {
|
|
7306
|
+
if (!first) checkDeadline(profile, deadline, problem);
|
|
7307
|
+
problem = void 0;
|
|
7308
|
+
const latest = await readLatestTurn(directory);
|
|
7309
|
+
if (latest === "missing") {
|
|
7310
|
+
await createTurnDirectory(directory);
|
|
7311
|
+
continue;
|
|
7312
|
+
}
|
|
7313
|
+
if (latest === "empty") {
|
|
7314
|
+
problem = damaged("has no turns");
|
|
7315
|
+
await waitOrTimeOut(profile, deadline, problem);
|
|
7316
|
+
continue;
|
|
7317
|
+
}
|
|
7318
|
+
if (latest === null) continue;
|
|
7319
|
+
if (typeof latest === "number") {
|
|
7320
|
+
problem = damaged(`has an unreadable turn ${latest}`);
|
|
7321
|
+
await waitOrTimeOut(profile, deadline, problem);
|
|
7322
|
+
continue;
|
|
7226
7323
|
}
|
|
7227
|
-
|
|
7228
|
-
if (
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
|
|
7324
|
+
firstWaitedTurn ?? (firstWaitedTurn = latest.done ? latest.slot + 1 : latest.slot);
|
|
7325
|
+
if (latest.held && latest.done) {
|
|
7326
|
+
const turn = await takeTurn(profile, directory, latest);
|
|
7327
|
+
if (turn !== null) return { turn, firstWaitedTurn };
|
|
7328
|
+
await waitOrTimeOut(profile, deadline);
|
|
7329
|
+
} else if (latest.done) {
|
|
7330
|
+
problem = damaged(`ends with turn ${latest.slot}, which was never granted`);
|
|
7331
|
+
await waitOrTimeOut(profile, deadline, problem);
|
|
7332
|
+
} else if (latest.pid === process.pid ? relinquishedTurns.has(latest.token) : !isProcessAlive(latest.pid)) {
|
|
7333
|
+
await waitForTestBarrier(TEST_LOCK_TURN_SETTLE_BARRIER_DIR, profile);
|
|
7334
|
+
await settleAbandonedTurn(directory, latest);
|
|
7335
|
+
relinquishedTurns.delete(latest.token);
|
|
7336
|
+
} else {
|
|
7337
|
+
await signalTestLockContention(profile);
|
|
7338
|
+
await waitOrTimeOut(profile, deadline);
|
|
7339
|
+
}
|
|
7340
|
+
}
|
|
7341
|
+
}
|
|
7342
|
+
async function readLatestTurn(directory) {
|
|
7343
|
+
let names;
|
|
7344
|
+
try {
|
|
7345
|
+
names = await (0, import_promises.readdir)(directory);
|
|
7346
|
+
} catch (error) {
|
|
7347
|
+
if (isErrno(error, "ENOENT")) return "missing";
|
|
7348
|
+
throw error;
|
|
7349
|
+
}
|
|
7350
|
+
const slots = names.filter((name) => TURN.test(name)).map(Number);
|
|
7351
|
+
if (slots.length === 0) return "empty";
|
|
7352
|
+
const slot = Math.max(...slots);
|
|
7353
|
+
const turn = await readTurn(directory, slot);
|
|
7354
|
+
return turn === "damaged" ? slot : turn;
|
|
7355
|
+
}
|
|
7356
|
+
async function readTurn(directory, slot) {
|
|
7357
|
+
const path = (0, import_node_path.join)(directory, String(slot));
|
|
7358
|
+
let text;
|
|
7359
|
+
try {
|
|
7360
|
+
text = await (0, import_promises.readFile)((0, import_node_path.join)(path, "owner.json"), "utf8");
|
|
7361
|
+
} catch (error) {
|
|
7362
|
+
if (!isErrno(error, "ENOENT") && !isErrno(error, "ENOTDIR")) throw error;
|
|
7363
|
+
return await exists(path) ? "damaged" : null;
|
|
7364
|
+
}
|
|
7365
|
+
let owner;
|
|
7366
|
+
try {
|
|
7367
|
+
owner = JSON.parse(text);
|
|
7368
|
+
} catch {
|
|
7369
|
+
return "damaged";
|
|
7370
|
+
}
|
|
7371
|
+
if (owner === null || typeof owner !== "object" || typeof owner.token !== "string" || typeof owner.pid !== "number") {
|
|
7372
|
+
return "damaged";
|
|
7373
|
+
}
|
|
7374
|
+
const done = await exists((0, import_node_path.join)(path, `${owner.token}.done`));
|
|
7375
|
+
const held = await exists((0, import_node_path.join)(path, `${owner.token}.held`));
|
|
7376
|
+
return {
|
|
7377
|
+
slot,
|
|
7378
|
+
token: owner.token,
|
|
7379
|
+
pid: owner.pid,
|
|
7380
|
+
after: typeof owner.after === "string" ? owner.after : null,
|
|
7381
|
+
held,
|
|
7382
|
+
done
|
|
7383
|
+
};
|
|
7384
|
+
}
|
|
7385
|
+
async function isFinishedTurn(directory, slot, token) {
|
|
7386
|
+
const turn = await readTurn(directory, slot);
|
|
7387
|
+
if (turn === "damaged") throw new Error(`Turn ${slot} of the turn lock ${directory} is unreadable.`);
|
|
7388
|
+
return turn !== null && turn.token === token && turn.held && turn.done;
|
|
7389
|
+
}
|
|
7390
|
+
async function takeTurn(profile, directory, latest) {
|
|
7391
|
+
const token = (0, import_node_crypto.randomUUID)();
|
|
7392
|
+
const slot = latest.slot + 1;
|
|
7393
|
+
const path = (0, import_node_path.join)(directory, String(slot));
|
|
7394
|
+
const owner = { pid: process.pid, createdAt: (/* @__PURE__ */ new Date()).toISOString(), token, after: latest.token };
|
|
7395
|
+
let published = false;
|
|
7396
|
+
try {
|
|
7397
|
+
published = await publishDirectory(
|
|
7398
|
+
(0, import_node_path.join)(directory, `.stage-${process.pid}-${token}`),
|
|
7399
|
+
path,
|
|
7400
|
+
{ "owner.json": `${JSON.stringify(owner, null, 2)}
|
|
7401
|
+
` },
|
|
7402
|
+
() => waitForTestBarrier(TEST_LOCK_TURN_PUBLISH_BARRIER_DIR, profile)
|
|
7403
|
+
);
|
|
7404
|
+
if (published && await isFinishedTurn(directory, latest.slot, latest.token) && await markTurn(path, token, "held")) {
|
|
7405
|
+
return { directory, slot, token };
|
|
7406
|
+
}
|
|
7407
|
+
if (published) await markTurn(path, token, "done");
|
|
7408
|
+
return null;
|
|
7409
|
+
} catch (error) {
|
|
7410
|
+
if (published) settleOwnTurn(directory, slot, token);
|
|
7411
|
+
throw error;
|
|
7412
|
+
}
|
|
7413
|
+
}
|
|
7414
|
+
async function settleAbandonedTurn(directory, abandoned) {
|
|
7415
|
+
const path = (0, import_node_path.join)(directory, String(abandoned.slot));
|
|
7416
|
+
if (!abandoned.held && abandoned.after !== null && await isFinishedTurn(directory, abandoned.slot - 1, abandoned.after)) {
|
|
7417
|
+
await markTurn(path, abandoned.token, "held");
|
|
7418
|
+
}
|
|
7419
|
+
await markTurn(path, abandoned.token, "done");
|
|
7420
|
+
}
|
|
7421
|
+
function settleOwnTurn(directory, slot, token, retries = SETTLE_RETRIES) {
|
|
7422
|
+
relinquishedTurns.add(token);
|
|
7423
|
+
void (async () => {
|
|
7424
|
+
try {
|
|
7425
|
+
const turn = await readTurn(directory, slot);
|
|
7426
|
+
if (turn !== null && turn !== "damaged" && turn.token === token && !turn.done) await settleAbandonedTurn(directory, turn);
|
|
7427
|
+
relinquishedTurns.delete(token);
|
|
7428
|
+
} catch {
|
|
7429
|
+
if (retries > 0) setTimeout(() => settleOwnTurn(directory, slot, token, retries - 1), SETTLE_RETRY_MS).unref();
|
|
7430
|
+
}
|
|
7431
|
+
})();
|
|
7432
|
+
}
|
|
7433
|
+
async function markTurn(path, token, kind) {
|
|
7434
|
+
for (let attempt = 0; ; attempt++) {
|
|
7435
|
+
try {
|
|
7436
|
+
await (0, import_promises.writeFile)((0, import_node_path.join)(path, `${token}.${kind}`), "", { mode: PRIVATE_FILE_MODE, flag: "wx" });
|
|
7437
|
+
return true;
|
|
7438
|
+
} catch (error) {
|
|
7439
|
+
if (isErrno(error, "EEXIST")) return true;
|
|
7440
|
+
if (isErrno(error, "ENOENT")) return false;
|
|
7441
|
+
if (attempt >= MARKER_RETRY_DELAYS_MS.length) throw error;
|
|
7442
|
+
await sleep(MARKER_RETRY_DELAYS_MS[attempt]);
|
|
7443
|
+
}
|
|
7444
|
+
}
|
|
7445
|
+
}
|
|
7446
|
+
async function createTurnDirectory(directory) {
|
|
7447
|
+
const token = (0, import_node_crypto.randomUUID)();
|
|
7448
|
+
const owner = { pid: process.pid, createdAt: (/* @__PURE__ */ new Date()).toISOString(), token, after: null };
|
|
7449
|
+
await publishDirectory((0, import_node_path.join)((0, import_node_path.dirname)(directory), `.stage-${(0, import_node_path.basename)(directory)}-${process.pid}-${token}`), directory, {
|
|
7450
|
+
"0/owner.json": `${JSON.stringify(owner, null, 2)}
|
|
7451
|
+
`,
|
|
7452
|
+
[`0/${token}.held`]: "",
|
|
7453
|
+
[`0/${token}.done`]: ""
|
|
7454
|
+
});
|
|
7455
|
+
}
|
|
7456
|
+
async function publishDirectory(stage, target, files, beforeRename) {
|
|
7457
|
+
try {
|
|
7458
|
+
for (const [name, contents] of Object.entries(files)) {
|
|
7459
|
+
await (0, import_promises.mkdir)((0, import_node_path.dirname)((0, import_node_path.join)(stage, name)), { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
7460
|
+
await writeFileDurably((0, import_node_path.join)(stage, name), contents);
|
|
7461
|
+
}
|
|
7462
|
+
await beforeRename?.();
|
|
7463
|
+
await (0, import_promises.rename)(stage, target);
|
|
7464
|
+
return true;
|
|
7465
|
+
} catch (error) {
|
|
7466
|
+
if (["ENOTEMPTY", "EEXIST", "ENOENT"].some((code) => isErrno(error, code)) || await exists(target)) return false;
|
|
7467
|
+
throw error;
|
|
7468
|
+
} finally {
|
|
7469
|
+
await (0, import_promises.rm)(stage, { recursive: true, force: true }).catch(() => void 0);
|
|
7470
|
+
}
|
|
7471
|
+
}
|
|
7472
|
+
async function releaseTurn(profile, turn) {
|
|
7473
|
+
try {
|
|
7474
|
+
await markTurn((0, import_node_path.join)(turn.directory, String(turn.slot)), turn.token, "done");
|
|
7475
|
+
} catch {
|
|
7476
|
+
settleOwnTurn(turn.directory, turn.slot, turn.token);
|
|
7477
|
+
return;
|
|
7478
|
+
}
|
|
7479
|
+
await collectTurns(profile, turn).catch(() => void 0);
|
|
7480
|
+
}
|
|
7481
|
+
function isAbandonedTurnEntry(name) {
|
|
7482
|
+
if (name.startsWith(".trash-")) return true;
|
|
7483
|
+
const writer = /^\.(?:stage|deleted\.json)[-.]([0-9]+)[-.]/.exec(name)?.[1];
|
|
7484
|
+
return writer !== void 0 && !isProcessAlive(Number(writer));
|
|
7485
|
+
}
|
|
7486
|
+
function isAbandonedTurnDirectoryStage(name) {
|
|
7487
|
+
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];
|
|
7488
|
+
return writer !== void 0 && !isProcessAlive(Number(writer));
|
|
7489
|
+
}
|
|
7490
|
+
async function collectTurns(profile, turn) {
|
|
7491
|
+
for (const [directory, abandoned] of [
|
|
7492
|
+
[turn.directory, isAbandonedTurnEntry],
|
|
7493
|
+
[(0, import_node_path.dirname)(turn.directory), isAbandonedTurnDirectoryStage]
|
|
7494
|
+
]) {
|
|
7495
|
+
for (const name of (await (0, import_promises.readdir)(directory)).filter(abandoned)) {
|
|
7496
|
+
await (0, import_promises.rm)((0, import_node_path.join)(directory, name), { recursive: true, force: true }).catch(() => void 0);
|
|
7497
|
+
}
|
|
7498
|
+
}
|
|
7499
|
+
const older = (await (0, import_promises.readdir)(turn.directory)).filter((name) => TURN.test(name)).map(Number).filter((slot) => slot < turn.slot).sort((left, right) => left - right);
|
|
7500
|
+
for (const slot of older) {
|
|
7501
|
+
const candidate = await readTurn(turn.directory, slot);
|
|
7502
|
+
if (candidate === null || candidate === "damaged" || !candidate.done) continue;
|
|
7503
|
+
if (candidate.held) {
|
|
7504
|
+
if (await exists((0, import_node_path.join)(turn.directory, String(slot - 1)))) continue;
|
|
7505
|
+
const next = await readTurn(turn.directory, slot + 1);
|
|
7506
|
+
if (next === null || next === "damaged" || !next.held || !next.done) continue;
|
|
7507
|
+
}
|
|
7508
|
+
await waitForTestBarrier(TEST_LOCK_TURN_COLLECT_BARRIER_DIR, profile);
|
|
7509
|
+
const trash = (0, import_node_path.join)(turn.directory, `.trash-${(0, import_node_crypto.randomUUID)()}`);
|
|
7510
|
+
if (await (0, import_promises.rename)((0, import_node_path.join)(turn.directory, String(slot)), trash).then(() => true, () => false)) {
|
|
7511
|
+
await (0, import_promises.rm)(trash, { recursive: true, force: true }).catch(() => void 0);
|
|
7232
7512
|
}
|
|
7233
|
-
await sleep(Math.min(retryMs, timeoutMs - elapsedMs));
|
|
7234
7513
|
}
|
|
7235
7514
|
}
|
|
7236
7515
|
async function publishProfileLock(profile, lockPath, token) {
|
|
@@ -7243,12 +7522,18 @@ async function publishProfileLock(profile, lockPath, token) {
|
|
|
7243
7522
|
return false;
|
|
7244
7523
|
}
|
|
7245
7524
|
await waitForTestBarrier(TEST_LOCK_PUBLISH_BARRIER_DIR, profile);
|
|
7525
|
+
const ownerPath = (0, import_node_path.join)(lockPath, "owner.json");
|
|
7246
7526
|
const staged = (0, import_node_path.join)((0, import_node_path.dirname)(lockPath), `.lock-owner-${token}.tmp`);
|
|
7247
|
-
const owner = { pid: process.pid, createdAt: (/* @__PURE__ */ new Date()).toISOString(), token }
|
|
7527
|
+
const owner = `${JSON.stringify({ pid: process.pid, createdAt: (/* @__PURE__ */ new Date()).toISOString(), token }, null, 2)}
|
|
7528
|
+
`;
|
|
7248
7529
|
try {
|
|
7249
|
-
await (0, import_promises.writeFile)(staged,
|
|
7250
|
-
|
|
7251
|
-
|
|
7530
|
+
await (0, import_promises.writeFile)(staged, owner, { encoding: "utf8", mode: PRIVATE_FILE_MODE, flag: "wx" });
|
|
7531
|
+
try {
|
|
7532
|
+
await (0, import_promises.link)(staged, ownerPath);
|
|
7533
|
+
} catch (error) {
|
|
7534
|
+
if (!lacksHardLinks(error)) throw error;
|
|
7535
|
+
await (0, import_promises.writeFile)(ownerPath, owner, { encoding: "utf8", mode: PRIVATE_FILE_MODE, flag: "wx" });
|
|
7536
|
+
}
|
|
7252
7537
|
return true;
|
|
7253
7538
|
} catch (error) {
|
|
7254
7539
|
if (isErrno(error, "EEXIST") || isErrno(error, "ENOENT")) return false;
|
|
@@ -7258,6 +7543,9 @@ async function publishProfileLock(profile, lockPath, token) {
|
|
|
7258
7543
|
await (0, import_promises.rm)(staged, { force: true }).catch(() => void 0);
|
|
7259
7544
|
}
|
|
7260
7545
|
}
|
|
7546
|
+
function lacksHardLinks(error) {
|
|
7547
|
+
return typeof error === "object" && error !== null && "code" in error && NO_HARD_LINK_ERRORS.has(String(error.code));
|
|
7548
|
+
}
|
|
7261
7549
|
async function releaseProfileLock(profile, lockPath, token) {
|
|
7262
7550
|
const ownerPath = (0, import_node_path.join)(lockPath, "owner.json");
|
|
7263
7551
|
const owner = await readJson(ownerPath).catch(() => null);
|
|
@@ -7281,9 +7569,10 @@ async function signalTestLockContention(profile) {
|
|
|
7281
7569
|
`, { encoding: "utf8", flag: "wx" }).catch(() => void 0);
|
|
7282
7570
|
}
|
|
7283
7571
|
async function recoverOwnerlessLock(profile, lockPath, staleAfterMs) {
|
|
7572
|
+
let aged;
|
|
7284
7573
|
try {
|
|
7285
7574
|
const { mtimeMs } = await (0, import_promises.stat)(lockPath);
|
|
7286
|
-
|
|
7575
|
+
aged = Date.now() - mtimeMs >= staleAfterMs;
|
|
7287
7576
|
} catch {
|
|
7288
7577
|
return false;
|
|
7289
7578
|
}
|
|
@@ -7292,7 +7581,16 @@ async function recoverOwnerlessLock(profile, lockPath, staleAfterMs) {
|
|
|
7292
7581
|
try {
|
|
7293
7582
|
const entries = await (0, import_promises.readdir)(lockPath);
|
|
7294
7583
|
if (entries.includes("owner.json")) return false;
|
|
7295
|
-
|
|
7584
|
+
const onlyOrphanedClaims = entries.length > 0 && entries.every((entry) => RECOVERY_CLAIM.test(entry));
|
|
7585
|
+
if (!onlyOrphanedClaims && (!aged || recoveryFenced(checkedAt, staleAfterMs))) return false;
|
|
7586
|
+
for (const name of entries.filter((entry) => RECOVERY_CLAIM.test(entry))) {
|
|
7587
|
+
const claimPath = (0, import_node_path.join)(lockPath, name);
|
|
7588
|
+
if (await hasLiveRecoveryOwner(claimPath)) {
|
|
7589
|
+
if (!await exists((0, import_node_path.join)(lockPath, "owner.json"))) await (0, import_promises.rename)(claimPath, (0, import_node_path.join)(lockPath, "owner.json"));
|
|
7590
|
+
return false;
|
|
7591
|
+
}
|
|
7592
|
+
await (0, import_promises.rm)(claimPath, { force: true });
|
|
7593
|
+
}
|
|
7296
7594
|
for (const name of entries.filter((entry) => ABANDONED_CLAIM.test(entry))) {
|
|
7297
7595
|
await (0, import_promises.rm)((0, import_node_path.join)(lockPath, name), { force: true });
|
|
7298
7596
|
}
|
|
@@ -7303,6 +7601,23 @@ async function recoverOwnerlessLock(profile, lockPath, staleAfterMs) {
|
|
|
7303
7601
|
await removeAgedOwnerFiles(lockPath, staleAfterMs);
|
|
7304
7602
|
return true;
|
|
7305
7603
|
}
|
|
7604
|
+
async function hasLiveRecoveryOwner(claimPath) {
|
|
7605
|
+
let contents;
|
|
7606
|
+
try {
|
|
7607
|
+
contents = await (0, import_promises.readFile)(claimPath, "utf8");
|
|
7608
|
+
} catch (error) {
|
|
7609
|
+
if (isErrno(error, "ENOENT")) return false;
|
|
7610
|
+
throw error;
|
|
7611
|
+
}
|
|
7612
|
+
let owner;
|
|
7613
|
+
try {
|
|
7614
|
+
owner = JSON.parse(contents);
|
|
7615
|
+
} catch {
|
|
7616
|
+
return false;
|
|
7617
|
+
}
|
|
7618
|
+
const pid = typeof owner === "object" && owner !== null && "pid" in owner ? owner.pid : null;
|
|
7619
|
+
return typeof pid === "number" && Number.isInteger(pid) && pid > 0 && isProcessAlive(pid);
|
|
7620
|
+
}
|
|
7306
7621
|
function recoveryFenced(since, staleAfterMs) {
|
|
7307
7622
|
return performance.now() - since >= staleAfterMs / 2;
|
|
7308
7623
|
}
|
|
@@ -7317,37 +7632,57 @@ async function removeAgedOwnerFiles(lockPath, staleAfterMs) {
|
|
|
7317
7632
|
}
|
|
7318
7633
|
async function recoverStaleLock(profile, lockPath, staleAfterMs) {
|
|
7319
7634
|
const ownerPath = (0, import_node_path.join)(lockPath, "owner.json");
|
|
7320
|
-
const
|
|
7321
|
-
if (
|
|
7322
|
-
if (!
|
|
7323
|
-
const observedToken = typeof owner?.token === "string" && owner.token.length > 0 ? owner.token : "legacy";
|
|
7635
|
+
const observed = await (0, import_promises.readFile)(ownerPath, "utf8").catch(() => null);
|
|
7636
|
+
if (observed === null) return recoverOwnerlessLock(profile, lockPath, staleAfterMs);
|
|
7637
|
+
if (!await isAbandonedOwner(observed, ownerPath, staleAfterMs)) return false;
|
|
7324
7638
|
await waitForTestBarrier(TEST_LOCK_RECOVERY_BARRIER_DIR, profile);
|
|
7325
|
-
const claimPath = (0, import_node_path.join)(lockPath, `.
|
|
7639
|
+
const claimPath = (0, import_node_path.join)(lockPath, `.recover-${(0, import_node_crypto.randomUUID)()}.json`);
|
|
7640
|
+
let moved = false;
|
|
7326
7641
|
try {
|
|
7327
7642
|
await (0, import_promises.link)(ownerPath, claimPath);
|
|
7328
|
-
} catch {
|
|
7329
|
-
return false;
|
|
7643
|
+
} catch (error) {
|
|
7644
|
+
if (!lacksHardLinks(error)) return false;
|
|
7645
|
+
try {
|
|
7646
|
+
await (0, import_promises.rename)(ownerPath, claimPath);
|
|
7647
|
+
moved = true;
|
|
7648
|
+
} catch {
|
|
7649
|
+
return false;
|
|
7650
|
+
}
|
|
7330
7651
|
}
|
|
7331
|
-
|
|
7332
|
-
await
|
|
7333
|
-
const claimed = await readJson(claimPath).catch(() => null);
|
|
7334
|
-
const sameInstance = claimed !== null && (observedToken === "legacy" ? claimed.token === void 0 : claimed.token === observedToken);
|
|
7652
|
+
if (moved) await waitForTestBarrier(TEST_LOCK_MOVED_BARRIER_DIR, profile);
|
|
7653
|
+
const sameInstance = await (0, import_promises.readFile)(claimPath, "utf8").then((claimed) => claimed === observed, () => false);
|
|
7335
7654
|
await waitForTestBarrier(TEST_LOCK_VERIFIED_BARRIER_DIR, profile);
|
|
7336
|
-
if (sameInstance
|
|
7337
|
-
|
|
7338
|
-
|
|
7655
|
+
if (!sameInstance) {
|
|
7656
|
+
if (moved) {
|
|
7657
|
+
await (0, import_promises.rename)(claimPath, ownerPath).catch(() => void 0);
|
|
7658
|
+
} else {
|
|
7659
|
+
await (0, import_promises.unlink)(claimPath).catch(() => void 0);
|
|
7660
|
+
}
|
|
7339
7661
|
return false;
|
|
7340
7662
|
}
|
|
7341
|
-
if (
|
|
7342
|
-
|
|
7343
|
-
|
|
7663
|
+
if (!moved) await (0, import_promises.rm)(ownerPath, { force: true });
|
|
7664
|
+
await waitForTestBarrier(TEST_LOCK_CLAIM_BARRIER_DIR, profile);
|
|
7665
|
+
await waitForTestBarrier(TEST_LOCK_CLAIMED_BARRIER_DIR, profile);
|
|
7666
|
+
for (const name of (await (0, import_promises.readdir)(lockPath).catch(() => [])).filter((entry) => RECOVERY_CLAIM.test(entry))) {
|
|
7667
|
+
if ((0, import_node_path.join)(lockPath, name) !== claimPath) await (0, import_promises.rm)((0, import_node_path.join)(lockPath, name), { force: true });
|
|
7344
7668
|
}
|
|
7345
|
-
await (0, import_promises.
|
|
7346
|
-
await (0, import_promises.rmdir)(lockPath).catch(() => void 0);
|
|
7347
|
-
if (!sameInstance) return false;
|
|
7669
|
+
if (await (0, import_promises.unlink)(claimPath).then(() => true, () => false)) await (0, import_promises.rmdir)(lockPath).catch(() => void 0);
|
|
7348
7670
|
await removeAgedOwnerFiles(lockPath, staleAfterMs);
|
|
7349
7671
|
return true;
|
|
7350
7672
|
}
|
|
7673
|
+
async function isAbandonedOwner(observed, ownerPath, staleAfterMs) {
|
|
7674
|
+
let owner;
|
|
7675
|
+
try {
|
|
7676
|
+
owner = JSON.parse(observed);
|
|
7677
|
+
} catch {
|
|
7678
|
+
owner = null;
|
|
7679
|
+
}
|
|
7680
|
+
if (typeof owner === "object" && owner !== null && "pid" in owner) {
|
|
7681
|
+
return isStaleOwner(owner, staleAfterMs);
|
|
7682
|
+
}
|
|
7683
|
+
const modifiedAt = await (0, import_promises.stat)(ownerPath).then(({ mtimeMs }) => mtimeMs, () => Number.NaN);
|
|
7684
|
+
return Date.now() - modifiedAt >= staleAfterMs;
|
|
7685
|
+
}
|
|
7351
7686
|
async function waitForTestBarrier(environmentName, profile) {
|
|
7352
7687
|
if (process.env.NODE_ENV !== "test") return;
|
|
7353
7688
|
const barrierDirectory = process.env[environmentName];
|
|
@@ -7374,6 +7709,15 @@ function isStaleOwner(owner, staleAfterMs) {
|
|
|
7374
7709
|
}
|
|
7375
7710
|
return !isProcessAlive(owner.pid);
|
|
7376
7711
|
}
|
|
7712
|
+
async function exists(path) {
|
|
7713
|
+
try {
|
|
7714
|
+
await (0, import_promises.stat)(path);
|
|
7715
|
+
return true;
|
|
7716
|
+
} catch (error) {
|
|
7717
|
+
if (isErrno(error, "ENOENT") || isErrno(error, "ENOTDIR")) return false;
|
|
7718
|
+
throw error;
|
|
7719
|
+
}
|
|
7720
|
+
}
|
|
7377
7721
|
function isProcessAlive(pid) {
|
|
7378
7722
|
try {
|
|
7379
7723
|
process.kill(pid, 0);
|
|
@@ -7394,7 +7738,7 @@ function isErrno(error, code) {
|
|
|
7394
7738
|
function sleep(milliseconds) {
|
|
7395
7739
|
return new Promise((resolve2) => setTimeout(resolve2, milliseconds));
|
|
7396
7740
|
}
|
|
7397
|
-
var import_node_crypto, import_node_async_hooks, import_promises, import_node_os, import_node_path, DEFAULT_PROFILE, DEFAULT_LOCK_TIMEOUT_MS, DEFAULT_LOCK_RETRY_MS, DEFAULT_STALE_LOCK_MS, TEST_LOCK_CONTENTION_SIGNAL_PATH, PRIVATE_FILE_MODE, PRIVATE_DIR_MODE, TEST_LOCK_RECOVERY_BARRIER_DIR, TEST_LOCK_OWNERLESS_BARRIER_DIR, TEST_LOCK_PUBLISH_BARRIER_DIR, TEST_LOCK_RELEASE_BARRIER_DIR, TEST_LOCK_CLAIM_BARRIER_DIR, TEST_LOCK_CLAIMED_BARRIER_DIR,
|
|
7741
|
+
var import_node_crypto, import_node_async_hooks, import_promises, import_node_os, import_node_path, DEFAULT_PROFILE, DEFAULT_LOCK_TIMEOUT_MS, DEFAULT_LOCK_RETRY_MS, DEFAULT_STALE_LOCK_MS, TEST_LOCK_CONTENTION_SIGNAL_PATH, PRIVATE_FILE_MODE, PRIVATE_DIR_MODE, TEST_LOCK_RECOVERY_BARRIER_DIR, TEST_LOCK_OWNERLESS_BARRIER_DIR, TEST_LOCK_PUBLISH_BARRIER_DIR, TEST_LOCK_RELEASE_BARRIER_DIR, TEST_LOCK_CLAIM_BARRIER_DIR, TEST_LOCK_CLAIMED_BARRIER_DIR, TEST_LOCK_VERIFIED_BARRIER_DIR, TEST_LOCK_MOVED_BARRIER_DIR, TEST_LOCK_TURN_PUBLISH_BARRIER_DIR, TEST_LOCK_TURN_SETTLE_BARRIER_DIR, TEST_LOCK_TURN_COLLECT_BARRIER_DIR, invocationStateRoot, heldProfileLocks, ProfileLockTimeoutError, ProfileDeletedError, TURN, _a, relinquishedTurns, MARKER_RETRY_DELAYS_MS, SETTLE_RETRY_MS, SETTLE_RETRIES, NO_HARD_LINK_ERRORS, ABANDONED_CLAIM, RECOVERY_CLAIM, STAGED_OWNER;
|
|
7398
7742
|
var init_state = __esm({
|
|
7399
7743
|
"src/state.ts"() {
|
|
7400
7744
|
"use strict";
|
|
@@ -7416,18 +7760,36 @@ var init_state = __esm({
|
|
|
7416
7760
|
TEST_LOCK_RELEASE_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_RELEASE_BARRIER_DIR";
|
|
7417
7761
|
TEST_LOCK_CLAIM_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_CLAIM_BARRIER_DIR";
|
|
7418
7762
|
TEST_LOCK_CLAIMED_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_CLAIMED_BARRIER_DIR";
|
|
7419
|
-
TEST_LOCK_FENCED_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_FENCED_BARRIER_DIR";
|
|
7420
7763
|
TEST_LOCK_VERIFIED_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_VERIFIED_BARRIER_DIR";
|
|
7421
|
-
|
|
7422
|
-
|
|
7764
|
+
TEST_LOCK_MOVED_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_MOVED_BARRIER_DIR";
|
|
7765
|
+
TEST_LOCK_TURN_PUBLISH_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_TURN_PUBLISH_BARRIER_DIR";
|
|
7766
|
+
TEST_LOCK_TURN_SETTLE_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_TURN_SETTLE_BARRIER_DIR";
|
|
7767
|
+
TEST_LOCK_TURN_COLLECT_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_TURN_COLLECT_BARRIER_DIR";
|
|
7768
|
+
invocationStateRoot = processWideContext("tinycloud.operations.invocationStateRoot.v1");
|
|
7769
|
+
heldProfileLocks = processWideContext("tinycloud.operations.heldProfileLocks.v2");
|
|
7423
7770
|
ProfileLockTimeoutError = class extends Error {
|
|
7424
|
-
|
|
7425
|
-
|
|
7771
|
+
/** `detail` names the cause when it is not simply another process holding the lock. */
|
|
7772
|
+
constructor(profile, timeoutMs, detail) {
|
|
7773
|
+
super(`Timed out waiting for the profile lock for "${profile}" after ${timeoutMs}ms.${detail ? ` ${detail}` : ""}`);
|
|
7426
7774
|
this.code = "PROFILE_LOCK_TIMEOUT";
|
|
7427
7775
|
this.name = "ProfileLockTimeoutError";
|
|
7428
7776
|
}
|
|
7429
7777
|
};
|
|
7778
|
+
ProfileDeletedError = class extends Error {
|
|
7779
|
+
constructor(profile) {
|
|
7780
|
+
super(`Profile "${profile}" was deleted while this change waited for its lock, so the change was not written.`);
|
|
7781
|
+
this.code = "PROFILE_NOT_FOUND";
|
|
7782
|
+
this.name = "ProfileDeletedError";
|
|
7783
|
+
}
|
|
7784
|
+
};
|
|
7785
|
+
TURN = /^(?:0|[1-9][0-9]*)$/;
|
|
7786
|
+
relinquishedTurns = globalThis[_a = /* @__PURE__ */ Symbol.for("tinycloud.operations.relinquishedProfileTurns.v1")] ?? (globalThis[_a] = /* @__PURE__ */ new Set());
|
|
7787
|
+
MARKER_RETRY_DELAYS_MS = [10, 50, 250];
|
|
7788
|
+
SETTLE_RETRY_MS = 1e3;
|
|
7789
|
+
SETTLE_RETRIES = 300;
|
|
7790
|
+
NO_HARD_LINK_ERRORS = /* @__PURE__ */ new Set(["EPERM", "ENOTSUP", "EOPNOTSUPP", "ENOSYS", "EISDIR", "EMLINK"]);
|
|
7430
7791
|
ABANDONED_CLAIM = /^\.(?:release|stale)-[0-9a-f-]+\.json$/;
|
|
7792
|
+
RECOVERY_CLAIM = /^\.recover-[0-9a-f-]+\.json$/;
|
|
7431
7793
|
STAGED_OWNER = /^\.lock-owner-[0-9a-f-]+\.tmp$/;
|
|
7432
7794
|
}
|
|
7433
7795
|
});
|
|
@@ -7634,7 +7996,7 @@ function evaluateArtifactAuthority(granted, required, resolveSpace2) {
|
|
|
7634
7996
|
function defaultRequestId(now) {
|
|
7635
7997
|
return `req_${now.getTime().toString(36)}_${(0, import_node_crypto2.randomUUID)().replace(/-/g, "")}`;
|
|
7636
7998
|
}
|
|
7637
|
-
var import_node_crypto2, import_zod2, THIRTY_DAYS_MS, PermissionEntrySchema, CommandSchema, LegacyPermissionRequestArtifactSchema, PortableDelegationSchema, PermissionRequestArtifactSchema, DelegationImportArtifactSchema;
|
|
7999
|
+
var import_node_crypto2, import_zod2, THIRTY_DAYS_MS, PermissionEntrySchema, CommandSchema, LegacyPermissionRequestArtifactSchema, PortableDelegationSchema, PermissionRequestArtifactSchema, DelegationImportArtifactSchema, DelegationRequestBindingSchema;
|
|
7638
8000
|
var init_artifacts = __esm({
|
|
7639
8001
|
"src/artifacts.ts"() {
|
|
7640
8002
|
"use strict";
|
|
@@ -7709,6 +8071,7 @@ var init_artifacts = __esm({
|
|
|
7709
8071
|
expiry: import_zod2.z.string().datetime({ offset: true }).optional(),
|
|
7710
8072
|
prompted: import_zod2.z.boolean().optional()
|
|
7711
8073
|
}).strict();
|
|
8074
|
+
DelegationRequestBindingSchema = PermissionRequestArtifactSchema.pick({ requestId: true, requested: true }).strict();
|
|
7712
8075
|
}
|
|
7713
8076
|
});
|
|
7714
8077
|
|
|
@@ -7768,18 +8131,18 @@ function resolveOperationSpace(space, node, ownerSpace) {
|
|
|
7768
8131
|
if (resolved !== void 0) return canonicalizeSpaceUri(resolved);
|
|
7769
8132
|
}
|
|
7770
8133
|
const did = typeof candidate.did === "string" ? candidate.did : void 0;
|
|
7771
|
-
const pkh = did === void 0 ? null :
|
|
8134
|
+
const pkh = did === void 0 ? null : nodeSdk.parsePkhDid(did);
|
|
7772
8135
|
if (pkh === null) {
|
|
7773
8136
|
throw invalidSecretInput();
|
|
7774
8137
|
}
|
|
7775
|
-
return
|
|
8138
|
+
return nodeSdk.makePkhSpaceId(pkh.address, pkh.chainId, space);
|
|
7776
8139
|
}
|
|
7777
8140
|
function ownedSpaceForName(ownerSpace, name) {
|
|
7778
|
-
const parsed =
|
|
8141
|
+
const parsed = nodeSdk.parseSpaceUri(ownerSpace);
|
|
7779
8142
|
if (parsed === null) return void 0;
|
|
7780
8143
|
try {
|
|
7781
|
-
const pkh =
|
|
7782
|
-
return pkh === null ? void 0 :
|
|
8144
|
+
const pkh = nodeSdk.parsePkhDid(parsed.owner);
|
|
8145
|
+
return pkh === null ? void 0 : nodeSdk.makePkhSpaceId(pkh.address, pkh.chainId, name);
|
|
7783
8146
|
} catch {
|
|
7784
8147
|
return void 0;
|
|
7785
8148
|
}
|
|
@@ -7794,7 +8157,7 @@ function canonicalizeSpaceUri(space) {
|
|
|
7794
8157
|
/^tinycloud:pkh:eip155:(\d+):(0x[0-9a-fA-F]{40}):([^:]+)$/
|
|
7795
8158
|
);
|
|
7796
8159
|
if (pkh !== null) {
|
|
7797
|
-
return `tinycloud:pkh:eip155:${pkh[1]}:${
|
|
8160
|
+
return `tinycloud:pkh:eip155:${pkh[1]}:${nodeSdk.canonicalizeAddress(pkh[2]).toLowerCase()}:${pkh[3]}`;
|
|
7798
8161
|
}
|
|
7799
8162
|
return space;
|
|
7800
8163
|
}
|
|
@@ -7834,11 +8197,11 @@ function invalidSecretInput() {
|
|
|
7834
8197
|
operationError("INPUT_INVALID", "The secret reference is invalid.")
|
|
7835
8198
|
);
|
|
7836
8199
|
}
|
|
7837
|
-
var
|
|
8200
|
+
var nodeSdk, DEFAULT_SECRETS_SPACE, SECRET_MANAGER_BASE_URL, SECRET_NAME_RE2, RESERVED_SECRET_SCOPES;
|
|
7838
8201
|
var init_secrets = __esm({
|
|
7839
8202
|
"src/secrets.ts"() {
|
|
7840
8203
|
"use strict";
|
|
7841
|
-
|
|
8204
|
+
nodeSdk = __toESM(require("@tinycloud/node-sdk"), 1);
|
|
7842
8205
|
init_errors();
|
|
7843
8206
|
DEFAULT_SECRETS_SPACE = "secrets";
|
|
7844
8207
|
SECRET_MANAGER_BASE_URL = "https://secrets.tinycloud.xyz";
|
|
@@ -7991,7 +8354,7 @@ async function importRequestBoundDelegation(context, input) {
|
|
|
7991
8354
|
};
|
|
7992
8355
|
let activated;
|
|
7993
8356
|
try {
|
|
7994
|
-
activated = await
|
|
8357
|
+
activated = await nodeSdk2.activateValidatedRuntimeDelegation(
|
|
7995
8358
|
context.runtime.node,
|
|
7996
8359
|
delegation,
|
|
7997
8360
|
{ host }
|
|
@@ -8007,11 +8370,7 @@ async function importRequestBoundDelegation(context, input) {
|
|
|
8007
8370
|
context.runtime.node,
|
|
8008
8371
|
context.summary.space
|
|
8009
8372
|
);
|
|
8010
|
-
if (request.requested
|
|
8011
|
-
request.requested,
|
|
8012
|
-
effectivePermissions,
|
|
8013
|
-
resolveSpace2
|
|
8014
|
-
).satisfied) {
|
|
8373
|
+
if (!delegationWithinRequest(request.requested, effectivePermissions, resolveSpace2)) {
|
|
8015
8374
|
return operationFailure(
|
|
8016
8375
|
"DELEGATION_REJECTED",
|
|
8017
8376
|
"The delegation exceeds the stored authority request."
|
|
@@ -8039,12 +8398,19 @@ async function importRequestBoundDelegation(context, input) {
|
|
|
8039
8398
|
)
|
|
8040
8399
|
};
|
|
8041
8400
|
}
|
|
8042
|
-
const
|
|
8401
|
+
const record = {
|
|
8402
|
+
delegation: activated.delegation,
|
|
8403
|
+
permissions: activated.effectivePermissions,
|
|
8404
|
+
authorityRequest: {
|
|
8405
|
+
requestId: request.requestId,
|
|
8406
|
+
requested: request.requested
|
|
8407
|
+
}
|
|
8408
|
+
};
|
|
8409
|
+
const position = records.findIndex((entry) => delegationCid(entry) === activated.cid);
|
|
8410
|
+
const alreadyPresent = position !== -1;
|
|
8411
|
+
const others = records.filter((entry) => delegationCid(entry) !== activated.cid);
|
|
8043
8412
|
return {
|
|
8044
|
-
records: alreadyPresent ?
|
|
8045
|
-
delegation: activated.delegation,
|
|
8046
|
-
permissions: activated.effectivePermissions
|
|
8047
|
-
}],
|
|
8413
|
+
records: alreadyPresent ? [...others.slice(0, position), record, ...others.slice(position)] : [...records, record],
|
|
8048
8414
|
result: {
|
|
8049
8415
|
status: "ok",
|
|
8050
8416
|
output: {
|
|
@@ -8173,11 +8539,11 @@ function internalImportFailure() {
|
|
|
8173
8539
|
function operationFailure(code, message, details) {
|
|
8174
8540
|
return { status: "error", error: operationError(code, message, { ...details === void 0 ? {} : { details } }) };
|
|
8175
8541
|
}
|
|
8176
|
-
var
|
|
8542
|
+
var nodeSdk2, import_zod3, EmptyInputSchema, CapabilitiesOutputSchema, AuthRequestInputSchema, PermissionRequestOutputSchema, AuthRequestOutputSchema, AuthImportOutputSchema, AUTH_POSTURES, AUTH_EXPOSURE, AUTH_CAPABILITIES_SENSITIVITY, AUTH_REQUEST_SENSITIVITY, AUTH_IMPORT_SENSITIVITY, authOperationDefinitions;
|
|
8177
8543
|
var init_auth = __esm({
|
|
8178
8544
|
"src/operations/auth.ts"() {
|
|
8179
8545
|
"use strict";
|
|
8180
|
-
|
|
8546
|
+
nodeSdk2 = __toESM(require("@tinycloud/node-sdk"), 1);
|
|
8181
8547
|
import_zod3 = require("zod");
|
|
8182
8548
|
init_authority();
|
|
8183
8549
|
init_artifacts();
|
|
@@ -9774,6 +10140,173 @@ ORDER BY type, name`;
|
|
|
9774
10140
|
}
|
|
9775
10141
|
});
|
|
9776
10142
|
|
|
10143
|
+
// src/delegation-binding.ts
|
|
10144
|
+
function bindingMigrationPath(profile) {
|
|
10145
|
+
return (0, import_node_path2.join)(profilePath(profile), "delegation-binding-migration.json");
|
|
10146
|
+
}
|
|
10147
|
+
function storedDelegationKind(entry) {
|
|
10148
|
+
if (!isRecord4(entry)) return "refused";
|
|
10149
|
+
const delegation = entry.delegation;
|
|
10150
|
+
if (!isRecord4(delegation)) return "refused";
|
|
10151
|
+
const header = delegation.delegationHeader;
|
|
10152
|
+
if (!isRecord4(header) || Object.keys(header).length !== 1 || !Object.prototype.hasOwnProperty.call(header, "Authorization") || typeof header.Authorization !== "string") {
|
|
10153
|
+
return "refused";
|
|
10154
|
+
}
|
|
10155
|
+
if ("siweProof" in delegation) return "signed-login";
|
|
10156
|
+
if (header.Authorization.includes(".")) return "compact";
|
|
10157
|
+
return "authorityRequest" in entry ? "refused" : "other";
|
|
10158
|
+
}
|
|
10159
|
+
function replayLimit(entry, migrated) {
|
|
10160
|
+
const kind = storedDelegationKind(entry);
|
|
10161
|
+
if (!isRecord4(entry) || kind !== "compact" && kind !== "signed-login") return void 0;
|
|
10162
|
+
const binding = DelegationRequestBindingSchema.safeParse(entry.authorityRequest);
|
|
10163
|
+
if (binding.success) return binding.data.requested;
|
|
10164
|
+
return !migrated && kind === "compact" && !("authorityRequest" in entry) ? "signed" : void 0;
|
|
10165
|
+
}
|
|
10166
|
+
function bindToSignedCapabilities(record, signed, source, recordedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
10167
|
+
return {
|
|
10168
|
+
...record,
|
|
10169
|
+
authorityRequest: DelegationRequestBindingSchema.parse({
|
|
10170
|
+
requestId: `${REQUEST_ID_PREFIX[source]}:${record.delegation.cid}`,
|
|
10171
|
+
requested: structuredClone(signed)
|
|
10172
|
+
}),
|
|
10173
|
+
authorityRequestAudit: { source, recordedAt, note: BINDING_NOTES[source] }
|
|
10174
|
+
};
|
|
10175
|
+
}
|
|
10176
|
+
async function prepareStoredDelegationReplay(profile, node, options) {
|
|
10177
|
+
if (await bindingMigrationRecorded(profile)) return true;
|
|
10178
|
+
if (!options.migrate || !await holdsPersistedSession(profile, node)) return false;
|
|
10179
|
+
try {
|
|
10180
|
+
return await withProfileLock(profile, async () => {
|
|
10181
|
+
if (await bindingMigrationRecorded(profile)) return true;
|
|
10182
|
+
if (!await holdsPersistedSession(profile, node)) return false;
|
|
10183
|
+
const recordedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10184
|
+
const bound = /* @__PURE__ */ new Map();
|
|
10185
|
+
const unbound = [];
|
|
10186
|
+
for (const entry of await readAdditionalDelegations(profile)) {
|
|
10187
|
+
if (!isRecord4(entry) || "authorityRequest" in entry || storedDelegationKind(entry) !== "compact") continue;
|
|
10188
|
+
const migrated = await migratedRecord(node, entry, options.host, recordedAt);
|
|
10189
|
+
if (migrated === void 0) unbound.push(String(storedCid(entry)));
|
|
10190
|
+
else bound.set(recordKey(entry), migrated);
|
|
10191
|
+
}
|
|
10192
|
+
if (bound.size > 0) {
|
|
10193
|
+
await updateProfileStore(profile, "additional-delegations", (records) => ({
|
|
10194
|
+
records: records.map((entry) => {
|
|
10195
|
+
const key = !isRecord4(entry) || "authorityRequest" in entry ? void 0 : recordKey(entry);
|
|
10196
|
+
return (key === void 0 ? void 0 : bound.get(key)) ?? entry;
|
|
10197
|
+
}),
|
|
10198
|
+
result: void 0
|
|
10199
|
+
}));
|
|
10200
|
+
}
|
|
10201
|
+
await writeJsonAtomic(bindingMigrationPath(profile), {
|
|
10202
|
+
formatVersion: 1,
|
|
10203
|
+
migratedAt: recordedAt,
|
|
10204
|
+
bound: [...bound.values()].map(storedCid),
|
|
10205
|
+
unbound
|
|
10206
|
+
});
|
|
10207
|
+
return true;
|
|
10208
|
+
}, { timeoutMs: MIGRATION_LOCK_TIMEOUT_MS });
|
|
10209
|
+
} catch {
|
|
10210
|
+
return false;
|
|
10211
|
+
}
|
|
10212
|
+
}
|
|
10213
|
+
async function holdsPersistedSession(profile, node) {
|
|
10214
|
+
try {
|
|
10215
|
+
const persisted = await readSession(profile);
|
|
10216
|
+
return typeof persisted?.verificationMethod === "string" && typeof node.sessionDid === "string" && persisted.verificationMethod.split("#", 1)[0] === node.sessionDid.split("#", 1)[0];
|
|
10217
|
+
} catch {
|
|
10218
|
+
return false;
|
|
10219
|
+
}
|
|
10220
|
+
}
|
|
10221
|
+
async function migratedRecord(node, entry, host, recordedAt) {
|
|
10222
|
+
const delegation = normalizeStoredDelegation(entry);
|
|
10223
|
+
if (delegation === void 0) return void 0;
|
|
10224
|
+
const signed = await signedCapabilities(node, delegation, host);
|
|
10225
|
+
if (signed === void 0) return void 0;
|
|
10226
|
+
try {
|
|
10227
|
+
return bindToSignedCapabilities({ ...entry, delegation: { ...entry.delegation, cid: delegation.cid } }, signed, "migration", recordedAt);
|
|
10228
|
+
} catch {
|
|
10229
|
+
return void 0;
|
|
10230
|
+
}
|
|
10231
|
+
}
|
|
10232
|
+
async function replayStoredDelegation(node, entry, options) {
|
|
10233
|
+
const delegation = normalizeStoredDelegation(entry);
|
|
10234
|
+
if (delegation === void 0 || delegation.expiry.getTime() <= Date.now()) return void 0;
|
|
10235
|
+
const limit = replayLimit(entry, options.migrated);
|
|
10236
|
+
if (limit === void 0) return void 0;
|
|
10237
|
+
const authorize = limit === "signed" ? () => true : (effective) => delegationWithinRequest(limit, effective, options.resolveSpace);
|
|
10238
|
+
try {
|
|
10239
|
+
return await nodeSdk3.activateValidatedRuntimeDelegation(node, delegation, { host: options.host, authorize });
|
|
10240
|
+
} catch {
|
|
10241
|
+
return void 0;
|
|
10242
|
+
}
|
|
10243
|
+
}
|
|
10244
|
+
async function bindingMigrationRecorded(profile) {
|
|
10245
|
+
try {
|
|
10246
|
+
return await readJson(bindingMigrationPath(profile)) !== null;
|
|
10247
|
+
} catch {
|
|
10248
|
+
return true;
|
|
10249
|
+
}
|
|
10250
|
+
}
|
|
10251
|
+
async function signedCapabilities(node, delegation, host) {
|
|
10252
|
+
let signed;
|
|
10253
|
+
try {
|
|
10254
|
+
await nodeSdk3.activateValidatedRuntimeDelegation(node, delegation, {
|
|
10255
|
+
// The transport host is not signed authority; check it at replay instead.
|
|
10256
|
+
host: delegation.host ?? host,
|
|
10257
|
+
authorize: (effective) => {
|
|
10258
|
+
signed = effective;
|
|
10259
|
+
return false;
|
|
10260
|
+
}
|
|
10261
|
+
});
|
|
10262
|
+
} catch {
|
|
10263
|
+
}
|
|
10264
|
+
return signed;
|
|
10265
|
+
}
|
|
10266
|
+
function normalizeStoredDelegation(entry) {
|
|
10267
|
+
if (!isRecord4(entry)) return void 0;
|
|
10268
|
+
const raw = entry.delegation;
|
|
10269
|
+
if (!isRecord4(raw) || !isRecord4(raw.delegationHeader)) return void 0;
|
|
10270
|
+
const expiry = raw.expiry instanceof Date ? raw.expiry : typeof raw.expiry === "string" ? new Date(raw.expiry) : void 0;
|
|
10271
|
+
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") {
|
|
10272
|
+
return void 0;
|
|
10273
|
+
}
|
|
10274
|
+
return { ...raw, expiry };
|
|
10275
|
+
}
|
|
10276
|
+
function storedCid(entry) {
|
|
10277
|
+
const cid = isRecord4(entry) && isRecord4(entry.delegation) ? entry.delegation.cid : void 0;
|
|
10278
|
+
return typeof cid === "string" ? cid : void 0;
|
|
10279
|
+
}
|
|
10280
|
+
function recordKey(entry) {
|
|
10281
|
+
const delegation = entry.delegation;
|
|
10282
|
+
if (!isRecord4(delegation) || !isRecord4(delegation.delegationHeader)) return void 0;
|
|
10283
|
+
return JSON.stringify([delegation.cid, delegation.delegationHeader.Authorization]);
|
|
10284
|
+
}
|
|
10285
|
+
function isRecord4(value) {
|
|
10286
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10287
|
+
}
|
|
10288
|
+
var import_node_path2, nodeSdk3, REQUEST_ID_PREFIX, BINDING_NOTES, MIGRATION_LOCK_TIMEOUT_MS;
|
|
10289
|
+
var init_delegation_binding = __esm({
|
|
10290
|
+
"src/delegation-binding.ts"() {
|
|
10291
|
+
"use strict";
|
|
10292
|
+
import_node_path2 = require("path");
|
|
10293
|
+
nodeSdk3 = __toESM(require("@tinycloud/node-sdk"), 1);
|
|
10294
|
+
init_artifacts();
|
|
10295
|
+
init_authority();
|
|
10296
|
+
init_state();
|
|
10297
|
+
init_secrets();
|
|
10298
|
+
REQUEST_ID_PREFIX = {
|
|
10299
|
+
migration: "migrated",
|
|
10300
|
+
"unbound-import": "unbound-import"
|
|
10301
|
+
};
|
|
10302
|
+
BINDING_NOTES = {
|
|
10303
|
+
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.",
|
|
10304
|
+
"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."
|
|
10305
|
+
};
|
|
10306
|
+
MIGRATION_LOCK_TIMEOUT_MS = 250;
|
|
10307
|
+
}
|
|
10308
|
+
});
|
|
10309
|
+
|
|
9777
10310
|
// src/operations/status.ts
|
|
9778
10311
|
function createStatusDefinition(params) {
|
|
9779
10312
|
return {
|
|
@@ -9829,7 +10362,7 @@ async function inspectPinnedProfileStatus(context, _input) {
|
|
|
9829
10362
|
async function pinnedProfileExists(profile) {
|
|
9830
10363
|
try {
|
|
9831
10364
|
const storedProfile = await readProfile(profile);
|
|
9832
|
-
return
|
|
10365
|
+
return isRecord5(storedProfile);
|
|
9833
10366
|
} catch {
|
|
9834
10367
|
return false;
|
|
9835
10368
|
}
|
|
@@ -9840,16 +10373,18 @@ async function countLiveAdditionalDelegations(profile) {
|
|
|
9840
10373
|
if (rawDelegations === null) return 0;
|
|
9841
10374
|
if (!Array.isArray(rawDelegations)) throw new TypeError("Invalid additional delegation store.");
|
|
9842
10375
|
const now = Date.now();
|
|
10376
|
+
const migrated = await bindingMigrationRecorded(profile);
|
|
9843
10377
|
let count = 0;
|
|
9844
10378
|
for (const entry of rawDelegations) {
|
|
10379
|
+
if (storedDelegationKind(entry) === "refused") continue;
|
|
9845
10380
|
const expiry = storedDelegationExpiry(entry);
|
|
9846
|
-
if (expiry.getTime() > now) count += 1;
|
|
10381
|
+
if (expiry.getTime() > now && replayLimit(entry, migrated) !== void 0) count += 1;
|
|
9847
10382
|
}
|
|
9848
10383
|
return count;
|
|
9849
10384
|
}
|
|
9850
10385
|
function summarizeSession(session) {
|
|
9851
10386
|
if (session === null) return { present: false, expired: null, expiresAt: null };
|
|
9852
|
-
if (!
|
|
10387
|
+
if (!isRecord5(session)) throw new TypeError("Invalid session store.");
|
|
9853
10388
|
const expiry = extractSessionExpiry(session);
|
|
9854
10389
|
if (expiry === null) return { present: true, expired: null, expiresAt: null };
|
|
9855
10390
|
return {
|
|
@@ -9859,7 +10394,7 @@ function summarizeSession(session) {
|
|
|
9859
10394
|
};
|
|
9860
10395
|
}
|
|
9861
10396
|
function storedDelegationExpiry(entry) {
|
|
9862
|
-
if (!
|
|
10397
|
+
if (!isRecord5(entry) || !isRecord5(entry.delegation) || !("expiry" in entry.delegation)) {
|
|
9863
10398
|
throw new TypeError("Invalid stored delegation.");
|
|
9864
10399
|
}
|
|
9865
10400
|
return parseExpiry(entry.delegation.expiry);
|
|
@@ -9888,7 +10423,7 @@ function profileNotFound(profile) {
|
|
|
9888
10423
|
)
|
|
9889
10424
|
};
|
|
9890
10425
|
}
|
|
9891
|
-
function
|
|
10426
|
+
function isRecord5(value) {
|
|
9892
10427
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9893
10428
|
}
|
|
9894
10429
|
var import_zod7, StatusInputSchema, StatusOutputSchema, STATUS_POSTURES, STATUS_EXPOSURE, STATUS_SENSITIVITY, statusOperationDefinitions;
|
|
@@ -9896,6 +10431,7 @@ var init_status = __esm({
|
|
|
9896
10431
|
"src/operations/status.ts"() {
|
|
9897
10432
|
"use strict";
|
|
9898
10433
|
import_zod7 = require("zod");
|
|
10434
|
+
init_delegation_binding();
|
|
9899
10435
|
init_errors();
|
|
9900
10436
|
init_state();
|
|
9901
10437
|
StatusInputSchema = import_zod7.z.object({}).strict();
|
|
@@ -9918,7 +10454,9 @@ var init_status = __esm({
|
|
|
9918
10454
|
expired: import_zod7.z.boolean().nullable(),
|
|
9919
10455
|
expiresAt: import_zod7.z.string().datetime({ offset: true }).nullable()
|
|
9920
10456
|
}).strict(),
|
|
9921
|
-
liveAdditionalDelegationCount: import_zod7.z.number().int().nonnegative()
|
|
10457
|
+
liveAdditionalDelegationCount: import_zod7.z.number().int().nonnegative().describe(
|
|
10458
|
+
"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."
|
|
10459
|
+
)
|
|
9922
10460
|
}).strict();
|
|
9923
10461
|
STATUS_POSTURES = [
|
|
9924
10462
|
"owner-openkey",
|
|
@@ -10072,9 +10610,9 @@ function isOperatorType(value) {
|
|
|
10072
10610
|
return value === "human" || value === "agent";
|
|
10073
10611
|
}
|
|
10074
10612
|
function isStoredProfile(value) {
|
|
10075
|
-
return
|
|
10613
|
+
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);
|
|
10076
10614
|
}
|
|
10077
|
-
function
|
|
10615
|
+
function isRecord6(value) {
|
|
10078
10616
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10079
10617
|
}
|
|
10080
10618
|
function isNonEmptyString(value) {
|
|
@@ -10131,14 +10669,12 @@ async function createInvocationRuntime(target, requirement = "authenticated") {
|
|
|
10131
10669
|
const profileName = summary.profile;
|
|
10132
10670
|
try {
|
|
10133
10671
|
const {
|
|
10134
|
-
activateValidatedRuntimeDelegation: activateValidatedRuntimeDelegation2,
|
|
10135
10672
|
TinyCloudNode: TinyCloudNodeConstructor
|
|
10136
|
-
} =
|
|
10673
|
+
} = nodeSdk4;
|
|
10137
10674
|
const explicitPrivateKeyOverride = typeof target.privateKey === "string";
|
|
10138
|
-
const [session, key
|
|
10675
|
+
const [session, key] = explicitPrivateKeyOverride ? [null, null] : await Promise.all([
|
|
10139
10676
|
readSession(profileName),
|
|
10140
|
-
readJson(`${profilePath(profileName)}/key.json`)
|
|
10141
|
-
readAdditionalDelegations(profileName)
|
|
10677
|
+
readJson(`${profilePath(profileName)}/key.json`)
|
|
10142
10678
|
]);
|
|
10143
10679
|
const profile = resolved.profile;
|
|
10144
10680
|
const actualPosture = resolvePosture(profile);
|
|
@@ -10185,19 +10721,26 @@ async function createInvocationRuntime(target, requirement = "authenticated") {
|
|
|
10185
10721
|
const livePrincipalDid = normalizeDid(node.did);
|
|
10186
10722
|
const authenticatedSpace = explicitPrivateKeyOverride ? spaceForAuthenticatedPrincipal(livePrincipalDid) : void 0;
|
|
10187
10723
|
const livePermissions = [...node.getVerifiedSessionCapabilities()];
|
|
10724
|
+
const resolveSpace2 = operationSpaceResolver(node, authenticatedSpace ?? summary.space);
|
|
10188
10725
|
const seenCids = /* @__PURE__ */ new Set();
|
|
10189
|
-
|
|
10190
|
-
const
|
|
10191
|
-
|
|
10192
|
-
|
|
10193
|
-
|
|
10194
|
-
|
|
10726
|
+
if (!explicitPrivateKeyOverride) {
|
|
10727
|
+
const activator = node;
|
|
10728
|
+
const migrated = await prepareStoredDelegationReplay(profileName, activator, {
|
|
10729
|
+
host: summary.host,
|
|
10730
|
+
// Only the profile's own restored session; a local sign-in without
|
|
10731
|
+
// one uses a fresh session key the stored records do not address.
|
|
10732
|
+
migrate: activeSession !== void 0
|
|
10733
|
+
});
|
|
10734
|
+
for (const entry of await readAdditionalDelegations(profileName)) {
|
|
10735
|
+
const activated = await replayStoredDelegation(activator, entry, {
|
|
10736
|
+
host: summary.host,
|
|
10737
|
+
migrated,
|
|
10738
|
+
resolveSpace: resolveSpace2
|
|
10195
10739
|
});
|
|
10196
|
-
if (!seenCids.has(activated.cid)) {
|
|
10740
|
+
if (activated !== void 0 && !seenCids.has(activated.cid)) {
|
|
10197
10741
|
seenCids.add(activated.cid);
|
|
10198
10742
|
livePermissions.push(...activated.effectivePermissions);
|
|
10199
10743
|
}
|
|
10200
|
-
} catch {
|
|
10201
10744
|
}
|
|
10202
10745
|
}
|
|
10203
10746
|
const runtime = {
|
|
@@ -10229,8 +10772,8 @@ async function createInvocationRuntime(target, requirement = "authenticated") {
|
|
|
10229
10772
|
function spaceForAuthenticatedPrincipal(principal2) {
|
|
10230
10773
|
if (principal2 === void 0) return void 0;
|
|
10231
10774
|
try {
|
|
10232
|
-
const pkh =
|
|
10233
|
-
return pkh === null ? void 0 :
|
|
10775
|
+
const pkh = nodeSdk4.parsePkhDid(principal2);
|
|
10776
|
+
return pkh === null ? void 0 : nodeSdk4.makePkhSpaceId(pkh.address, pkh.chainId, "secrets");
|
|
10234
10777
|
} catch {
|
|
10235
10778
|
return void 0;
|
|
10236
10779
|
}
|
|
@@ -10238,7 +10781,7 @@ function spaceForAuthenticatedPrincipal(principal2) {
|
|
|
10238
10781
|
function normalizeSession(session, key) {
|
|
10239
10782
|
const header = session.delegationHeader;
|
|
10240
10783
|
const jwk = hasPrivateParameter(session.jwk) ? session.jwk : key;
|
|
10241
|
-
if (!
|
|
10784
|
+
if (!isRecord7(header) || typeof header.Authorization !== "string" || typeof session.delegationCid !== "string" || typeof session.spaceId !== "string" || !hasPrivateParameter(jwk) || typeof session.verificationMethod !== "string") {
|
|
10242
10785
|
return void 0;
|
|
10243
10786
|
}
|
|
10244
10787
|
return {
|
|
@@ -10254,27 +10797,14 @@ function normalizeSession(session, key) {
|
|
|
10254
10797
|
...Array.isArray(session.tinycloudHosts) && session.tinycloudHosts.every((host) => typeof host === "string") ? { tinycloudHosts: session.tinycloudHosts } : {}
|
|
10255
10798
|
};
|
|
10256
10799
|
}
|
|
10257
|
-
function normalizeStoredDelegation(entry) {
|
|
10258
|
-
const raw = entry.delegation;
|
|
10259
|
-
if (!isRecord6(raw) || !isRecord6(raw.delegationHeader)) return void 0;
|
|
10260
|
-
const expiry = parseExpiry2(raw.expiry);
|
|
10261
|
-
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") {
|
|
10262
|
-
return void 0;
|
|
10263
|
-
}
|
|
10264
|
-
return { ...raw, expiry };
|
|
10265
|
-
}
|
|
10266
|
-
function parseExpiry2(value) {
|
|
10267
|
-
const expiry = value instanceof Date ? value : typeof value === "string" ? new Date(value) : void 0;
|
|
10268
|
-
return expiry !== void 0 && !Number.isNaN(expiry.getTime()) ? expiry : void 0;
|
|
10269
|
-
}
|
|
10270
10800
|
function hasPrivateParameter(value) {
|
|
10271
|
-
return
|
|
10801
|
+
return isRecord7(value) && typeof value.d === "string" && value.d.length > 0;
|
|
10272
10802
|
}
|
|
10273
10803
|
function normalizeDid(value) {
|
|
10274
10804
|
if (typeof value !== "string" || value.length === 0) return void 0;
|
|
10275
10805
|
return value.split("#", 1)[0];
|
|
10276
10806
|
}
|
|
10277
|
-
function
|
|
10807
|
+
function isRecord7(value) {
|
|
10278
10808
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10279
10809
|
}
|
|
10280
10810
|
function failed(context, error) {
|
|
@@ -10287,15 +10817,17 @@ function unresolvedSummary(target) {
|
|
|
10287
10817
|
posture: "unauthenticated"
|
|
10288
10818
|
};
|
|
10289
10819
|
}
|
|
10290
|
-
var
|
|
10820
|
+
var nodeSdk4;
|
|
10291
10821
|
var init_runtime = __esm({
|
|
10292
10822
|
"src/runtime.ts"() {
|
|
10293
10823
|
"use strict";
|
|
10294
|
-
|
|
10824
|
+
nodeSdk4 = __toESM(require("@tinycloud/node-sdk"), 1);
|
|
10295
10825
|
init_authority();
|
|
10296
10826
|
init_errors();
|
|
10297
10827
|
init_profile();
|
|
10828
|
+
init_secrets();
|
|
10298
10829
|
init_state();
|
|
10830
|
+
init_delegation_binding();
|
|
10299
10831
|
}
|
|
10300
10832
|
});
|
|
10301
10833
|
|
|
@@ -10308,8 +10840,8 @@ module.exports = __toCommonJS(cli_runtime_exports);
|
|
|
10308
10840
|
|
|
10309
10841
|
// src/invoke.ts
|
|
10310
10842
|
var import_node_crypto3 = require("crypto");
|
|
10311
|
-
var
|
|
10312
|
-
var
|
|
10843
|
+
var import_node_path3 = require("path");
|
|
10844
|
+
var import_node_sdk = require("@tinycloud/node-sdk");
|
|
10313
10845
|
|
|
10314
10846
|
// ../sdk-core/src/policy/errors.ts
|
|
10315
10847
|
var SignedObjectProfileError = class extends Error {
|
|
@@ -11129,7 +11661,7 @@ function normalizeInvocationTarget(target) {
|
|
|
11129
11661
|
const stateRoot = candidate.stateRoot;
|
|
11130
11662
|
const allowOwnerProfile = candidate.allowOwnerProfile;
|
|
11131
11663
|
const privateKey = candidate.privateKey;
|
|
11132
|
-
if (profile !== void 0 && typeof profile !== "string" || host !== void 0 && typeof host !== "string" || stateRoot !== void 0 && (typeof stateRoot !== "string" || !(0,
|
|
11664
|
+
if (profile !== void 0 && typeof profile !== "string" || host !== void 0 && typeof host !== "string" || stateRoot !== void 0 && (typeof stateRoot !== "string" || !(0, import_node_path3.isAbsolute)(stateRoot)) || allowOwnerProfile !== void 0 && typeof allowOwnerProfile !== "boolean" || privateKey !== void 0 && typeof privateKey !== "string") {
|
|
11133
11665
|
return void 0;
|
|
11134
11666
|
}
|
|
11135
11667
|
return {
|
|
@@ -11269,11 +11801,11 @@ function requirementsBelongToAuthenticatedOwner(requirements, authenticatedSpace
|
|
|
11269
11801
|
}
|
|
11270
11802
|
function canonicalPkhOwner(space) {
|
|
11271
11803
|
if (typeof space !== "string") return void 0;
|
|
11272
|
-
const parsedSpace = (0,
|
|
11804
|
+
const parsedSpace = (0, import_node_sdk.parseSpaceUri)(space);
|
|
11273
11805
|
if (parsedSpace === null) return void 0;
|
|
11274
11806
|
try {
|
|
11275
|
-
const pkh = (0,
|
|
11276
|
-
return pkh === null ? void 0 : (0,
|
|
11807
|
+
const pkh = (0, import_node_sdk.parsePkhDid)(parsedSpace.owner);
|
|
11808
|
+
return pkh === null ? void 0 : (0, import_node_sdk.parseSpaceUri)((0, import_node_sdk.makePkhSpaceId)(pkh.address, pkh.chainId, parsedSpace.name))?.owner;
|
|
11277
11809
|
} catch {
|
|
11278
11810
|
return void 0;
|
|
11279
11811
|
}
|