@tinycloud/operations 0.5.0-beta.0 → 0.5.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/coverage.json +11 -1
- package/dist/artifacts.cjs +235 -43
- package/dist/artifacts.cjs.map +1 -1
- package/dist/artifacts.d.cts +1 -1
- package/dist/artifacts.d.ts +1 -1
- package/dist/artifacts.js +236 -43
- package/dist/artifacts.js.map +1 -1
- package/dist/cli-runtime.cjs +1021 -223
- package/dist/cli-runtime.cjs.map +1 -1
- package/dist/cli-runtime.d.cts +1 -1
- package/dist/cli-runtime.d.ts +1 -1
- package/dist/cli-runtime.js +1020 -222
- package/dist/cli-runtime.js.map +1 -1
- package/dist/{contract-DfyrluLB.d.cts → contract-zRklonbR.d.cts} +6 -3
- package/dist/{contract-DfyrluLB.d.ts → contract-zRklonbR.d.ts} +6 -3
- package/dist/delegation-binding.cjs +242 -43
- package/dist/delegation-binding.cjs.map +1 -1
- package/dist/delegation-binding.d.cts +15 -3
- package/dist/delegation-binding.d.ts +15 -3
- package/dist/delegation-binding.js +242 -43
- package/dist/delegation-binding.js.map +1 -1
- package/dist/index.cjs +1049 -224
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1046 -221
- package/dist/index.js.map +1 -1
- package/dist/profile.cjs +13 -4
- package/dist/profile.cjs.map +1 -1
- package/dist/profile.d.cts +14 -2
- package/dist/profile.d.ts +14 -2
- package/dist/profile.js +13 -4
- package/dist/profile.js.map +1 -1
- package/dist/recovery.cjs +67 -0
- package/dist/recovery.cjs.map +1 -0
- package/dist/recovery.d.cts +38 -0
- package/dist/recovery.d.ts +38 -0
- package/dist/recovery.js +39 -0
- package/dist/recovery.js.map +1 -0
- package/dist/secret-request.cjs +271 -0
- package/dist/secret-request.cjs.map +1 -0
- package/dist/secret-request.d.cts +119 -0
- package/dist/secret-request.d.ts +119 -0
- package/dist/secret-request.js +222 -0
- package/dist/secret-request.js.map +1 -0
- package/dist/state.cjs +232 -40
- package/dist/state.cjs.map +1 -1
- package/dist/state.js +233 -40
- package/dist/state.js.map +1 -1
- package/generated/operations.json +1838 -89
- package/package.json +15 -5
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
declare const OPERATION_ERROR_CODES: readonly ["INPUT_INVALID", "OPERATION_NOT_FOUND", "OPERATION_VERSION_UNSUPPORTED", "PROFILE_NOT_FOUND", "PROFILE_POSTURE_NOT_ALLOWED", "PROFILE_OWNER_OPT_IN_REQUIRED", "AUTH_REQUIRED", "PERMISSION_DENIED", "SESSION_NOT_FOUND", "PERMISSION_HINT_INVALID", "DELEGATION_ARTIFACT_INVALID", "DELEGATION_EXPIRED", "DELEGATION_AUDIENCE_MISMATCH", "DELEGATION_HOST_MISMATCH", "DELEGATION_REJECTED", "ENCRYPTION_NETWORK_UNRESOLVED", "NODE_UNREACHABLE", "NODE_ERROR", "STORAGE_QUOTA_EXCEEDED", "KV_NOT_FOUND", "KV_PRECONDITION_FAILED", "KV_CONFLICT", "KV_RESPONSE_TOO_LARGE", "SQL_QUERY_INVALID", "SQL_EXECUTION_FAILED", "SQL_RESULT_LIMIT_EXCEEDED", "SQL_VALUE_UNSAFE", "SECRET_READ_FAILED", "SECRET_DECRYPT_FAILED", "OUTPUT_INVALID", "INTERNAL_ERROR"];
|
|
1
|
+
declare const OPERATION_ERROR_CODES: readonly ["INPUT_INVALID", "OPERATION_NOT_FOUND", "OPERATION_VERSION_UNSUPPORTED", "PROFILE_NOT_FOUND", "PROFILE_POSTURE_NOT_ALLOWED", "PROFILE_OWNER_OPT_IN_REQUIRED", "PROFILE_MIGRATION_REQUIRED", "AUTH_REQUIRED", "PERMISSION_DENIED", "SESSION_NOT_FOUND", "PERMISSION_HINT_INVALID", "DELEGATION_ARTIFACT_INVALID", "DELEGATION_EXPIRED", "DELEGATION_AUDIENCE_MISMATCH", "DELEGATION_HOST_MISMATCH", "DELEGATION_REJECTED", "ENCRYPTION_NETWORK_UNRESOLVED", "NODE_UNREACHABLE", "NODE_ERROR", "STORAGE_QUOTA_EXCEEDED", "KV_NOT_FOUND", "KV_PRECONDITION_FAILED", "KV_CONFLICT", "KV_RESPONSE_TOO_LARGE", "SQL_QUERY_INVALID", "SQL_EXECUTION_FAILED", "SQL_RESULT_LIMIT_EXCEEDED", "SQL_VALUE_UNSAFE", "SECRET_READ_FAILED", "SECRET_DECRYPT_FAILED", "OUTPUT_INVALID", "INTERNAL_ERROR"];
|
|
2
2
|
type OperationErrorCode = (typeof OPERATION_ERROR_CODES)[number];
|
|
3
3
|
interface OperationError {
|
|
4
4
|
readonly code: OperationErrorCode;
|
|
@@ -29,9 +29,12 @@ interface OperationContextSummary {
|
|
|
29
29
|
* Why a stored grant was not used. A fixed vocabulary: diagnostics never
|
|
30
30
|
* carry exception text, node responses, URLs or other stored transport data.
|
|
31
31
|
* `unbound`: the record has no valid request binding where one is required;
|
|
32
|
-
* `outside_request`: its signed authority exceeds its binding
|
|
32
|
+
* `outside_request`: its signed authority exceeds its binding;
|
|
33
|
+
* `activation_unavailable`: the node could not be reached, timed out, was
|
|
34
|
+
* rate limited or failed (transport, 408, 429, 5xx) while activating it;
|
|
35
|
+
* `activation_unauthenticated`: the node answered 401 while activating it.
|
|
33
36
|
*/
|
|
34
|
-
declare const STORED_GRANT_SKIP_REASONS: readonly ["malformed", "unbound", "outside_request", "host_mismatch", "expired", "cid_mismatch", "authority_invalid", "proof_missing", "proof_invalid", "owner_mismatch", "audience_mismatch", "resources_mismatch", "unsupported", "activation_rejected", "invalid"];
|
|
37
|
+
declare const STORED_GRANT_SKIP_REASONS: readonly ["malformed", "unbound", "outside_request", "host_mismatch", "expired", "cid_mismatch", "authority_invalid", "proof_missing", "proof_invalid", "owner_mismatch", "audience_mismatch", "resources_mismatch", "unsupported", "activation_rejected", "activation_unavailable", "activation_unauthenticated", "invalid"];
|
|
35
38
|
type StoredGrantSkipReason = typeof STORED_GRANT_SKIP_REASONS[number];
|
|
36
39
|
/**
|
|
37
40
|
* A non-fatal diagnostic attached to a result. `STORED_GRANT_SKIPPED`: a
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
declare const OPERATION_ERROR_CODES: readonly ["INPUT_INVALID", "OPERATION_NOT_FOUND", "OPERATION_VERSION_UNSUPPORTED", "PROFILE_NOT_FOUND", "PROFILE_POSTURE_NOT_ALLOWED", "PROFILE_OWNER_OPT_IN_REQUIRED", "AUTH_REQUIRED", "PERMISSION_DENIED", "SESSION_NOT_FOUND", "PERMISSION_HINT_INVALID", "DELEGATION_ARTIFACT_INVALID", "DELEGATION_EXPIRED", "DELEGATION_AUDIENCE_MISMATCH", "DELEGATION_HOST_MISMATCH", "DELEGATION_REJECTED", "ENCRYPTION_NETWORK_UNRESOLVED", "NODE_UNREACHABLE", "NODE_ERROR", "STORAGE_QUOTA_EXCEEDED", "KV_NOT_FOUND", "KV_PRECONDITION_FAILED", "KV_CONFLICT", "KV_RESPONSE_TOO_LARGE", "SQL_QUERY_INVALID", "SQL_EXECUTION_FAILED", "SQL_RESULT_LIMIT_EXCEEDED", "SQL_VALUE_UNSAFE", "SECRET_READ_FAILED", "SECRET_DECRYPT_FAILED", "OUTPUT_INVALID", "INTERNAL_ERROR"];
|
|
1
|
+
declare const OPERATION_ERROR_CODES: readonly ["INPUT_INVALID", "OPERATION_NOT_FOUND", "OPERATION_VERSION_UNSUPPORTED", "PROFILE_NOT_FOUND", "PROFILE_POSTURE_NOT_ALLOWED", "PROFILE_OWNER_OPT_IN_REQUIRED", "PROFILE_MIGRATION_REQUIRED", "AUTH_REQUIRED", "PERMISSION_DENIED", "SESSION_NOT_FOUND", "PERMISSION_HINT_INVALID", "DELEGATION_ARTIFACT_INVALID", "DELEGATION_EXPIRED", "DELEGATION_AUDIENCE_MISMATCH", "DELEGATION_HOST_MISMATCH", "DELEGATION_REJECTED", "ENCRYPTION_NETWORK_UNRESOLVED", "NODE_UNREACHABLE", "NODE_ERROR", "STORAGE_QUOTA_EXCEEDED", "KV_NOT_FOUND", "KV_PRECONDITION_FAILED", "KV_CONFLICT", "KV_RESPONSE_TOO_LARGE", "SQL_QUERY_INVALID", "SQL_EXECUTION_FAILED", "SQL_RESULT_LIMIT_EXCEEDED", "SQL_VALUE_UNSAFE", "SECRET_READ_FAILED", "SECRET_DECRYPT_FAILED", "OUTPUT_INVALID", "INTERNAL_ERROR"];
|
|
2
2
|
type OperationErrorCode = (typeof OPERATION_ERROR_CODES)[number];
|
|
3
3
|
interface OperationError {
|
|
4
4
|
readonly code: OperationErrorCode;
|
|
@@ -29,9 +29,12 @@ interface OperationContextSummary {
|
|
|
29
29
|
* Why a stored grant was not used. A fixed vocabulary: diagnostics never
|
|
30
30
|
* carry exception text, node responses, URLs or other stored transport data.
|
|
31
31
|
* `unbound`: the record has no valid request binding where one is required;
|
|
32
|
-
* `outside_request`: its signed authority exceeds its binding
|
|
32
|
+
* `outside_request`: its signed authority exceeds its binding;
|
|
33
|
+
* `activation_unavailable`: the node could not be reached, timed out, was
|
|
34
|
+
* rate limited or failed (transport, 408, 429, 5xx) while activating it;
|
|
35
|
+
* `activation_unauthenticated`: the node answered 401 while activating it.
|
|
33
36
|
*/
|
|
34
|
-
declare const STORED_GRANT_SKIP_REASONS: readonly ["malformed", "unbound", "outside_request", "host_mismatch", "expired", "cid_mismatch", "authority_invalid", "proof_missing", "proof_invalid", "owner_mismatch", "audience_mismatch", "resources_mismatch", "unsupported", "activation_rejected", "invalid"];
|
|
37
|
+
declare const STORED_GRANT_SKIP_REASONS: readonly ["malformed", "unbound", "outside_request", "host_mismatch", "expired", "cid_mismatch", "authority_invalid", "proof_missing", "proof_invalid", "owner_mismatch", "audience_mismatch", "resources_mismatch", "unsupported", "activation_rejected", "activation_unavailable", "activation_unauthenticated", "invalid"];
|
|
35
38
|
type StoredGrantSkipReason = typeof STORED_GRANT_SKIP_REASONS[number];
|
|
36
39
|
/**
|
|
37
40
|
* A non-fatal diagnostic attached to a result. `STORED_GRANT_SKIPPED`: a
|
|
@@ -730,6 +730,7 @@ __export(delegation_binding_exports, {
|
|
|
730
730
|
replayLimit: () => replayLimit,
|
|
731
731
|
replayProfileDelegations: () => replayProfileDelegations,
|
|
732
732
|
replayStoredDelegation: () => replayStoredDelegation,
|
|
733
|
+
replayStoredDelegationRecords: () => replayStoredDelegationRecords,
|
|
733
734
|
storedDelegationForAnotherKey: () => storedDelegationForAnotherKey,
|
|
734
735
|
storedDelegationKind: () => storedDelegationKind,
|
|
735
736
|
storedDelegationSource: () => storedDelegationSource,
|
|
@@ -3653,10 +3654,10 @@ function evaluateOperationAuthority(granted, required, resolveSpace) {
|
|
|
3653
3654
|
const canonicalRequired = canonicalizeOperationCapabilities(required, resolveSpace);
|
|
3654
3655
|
const missing = [];
|
|
3655
3656
|
for (const requested of canonicalRequired) {
|
|
3656
|
-
const
|
|
3657
|
-
(candidate) => candidate.service === requested.service && operationSpacesEqual(candidate.space, requested.space)
|
|
3657
|
+
const sameScope = canonicalGranted.filter(
|
|
3658
|
+
(candidate) => candidate.service === requested.service && operationSpacesEqual(candidate.space, requested.space)
|
|
3658
3659
|
);
|
|
3659
|
-
if (!
|
|
3660
|
+
if (!isCapabilitySubset([requested], sameScope).subset) missing.push(requested);
|
|
3660
3661
|
}
|
|
3661
3662
|
return {
|
|
3662
3663
|
satisfied: missing.length === 0,
|
|
@@ -3733,7 +3734,9 @@ function canonicalizeCaveats(caveats) {
|
|
|
3733
3734
|
// src/state.ts
|
|
3734
3735
|
var import_node_crypto = require("crypto");
|
|
3735
3736
|
var import_node_async_hooks = require("async_hooks");
|
|
3737
|
+
var import_node_fs = require("fs");
|
|
3736
3738
|
var import_promises = require("fs/promises");
|
|
3739
|
+
var import_node_net = require("net");
|
|
3737
3740
|
var import_node_os = require("os");
|
|
3738
3741
|
var import_node_path = require("path");
|
|
3739
3742
|
var DEFAULT_LOCK_TIMEOUT_MS = 2e3;
|
|
@@ -3753,13 +3756,171 @@ var TEST_LOCK_MOVED_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_MOVED_BARRIER_DIR";
|
|
|
3753
3756
|
var TEST_LOCK_TURN_PUBLISH_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_TURN_PUBLISH_BARRIER_DIR";
|
|
3754
3757
|
var TEST_LOCK_TURN_SETTLE_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_TURN_SETTLE_BARRIER_DIR";
|
|
3755
3758
|
var TEST_LOCK_TURN_COLLECT_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_TURN_COLLECT_BARRIER_DIR";
|
|
3759
|
+
var TEST_LOCK_LIVENESS_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_LIVENESS_BARRIER_DIR";
|
|
3756
3760
|
function processWideContext(key) {
|
|
3757
|
-
var
|
|
3761
|
+
var _a4;
|
|
3758
3762
|
const registry = globalThis;
|
|
3759
|
-
return registry[
|
|
3763
|
+
return registry[_a4 = Symbol.for(key)] ?? (registry[_a4] = new import_node_async_hooks.AsyncLocalStorage());
|
|
3760
3764
|
}
|
|
3761
3765
|
var invocationStateRoot = processWideContext("tinycloud.operations.invocationStateRoot.v1");
|
|
3762
3766
|
var heldProfileLocks = processWideContext("tinycloud.operations.heldProfileLocks.v2");
|
|
3767
|
+
var _a2;
|
|
3768
|
+
var processInstanceId = globalThis[_a2 = /* @__PURE__ */ Symbol.for("tinycloud.operations.profileLockInstance.v1")] ?? (globalThis[_a2] = (0, import_node_crypto.randomUUID)());
|
|
3769
|
+
var cachedProcessOrigin;
|
|
3770
|
+
function processOrigin() {
|
|
3771
|
+
if (cachedProcessOrigin !== void 0) return cachedProcessOrigin;
|
|
3772
|
+
if (process.platform !== "linux") return cachedProcessOrigin = null;
|
|
3773
|
+
try {
|
|
3774
|
+
const bootId = (0, import_node_fs.readFileSync)("/proc/sys/kernel/random/boot_id", "utf8").trim();
|
|
3775
|
+
const pidNamespace = (0, import_node_fs.readlinkSync)("/proc/self/ns/pid");
|
|
3776
|
+
const processStat = (0, import_node_fs.readFileSync)("/proc/self/stat", "utf8");
|
|
3777
|
+
const closingParen = processStat.lastIndexOf(") ");
|
|
3778
|
+
const processStartTime = closingParen < 0 ? void 0 : processStat.slice(closingParen + 2).trim().split(/\s+/)[19];
|
|
3779
|
+
if (!/^[0-9a-f-]{36}$/.test(bootId) || !/^pid:\[[0-9]+\]$/.test(pidNamespace) || !processStartTime || !/^[0-9]+$/.test(processStartTime)) {
|
|
3780
|
+
return cachedProcessOrigin = null;
|
|
3781
|
+
}
|
|
3782
|
+
return cachedProcessOrigin = { bootId, pidNamespace, processStartTime };
|
|
3783
|
+
} catch {
|
|
3784
|
+
return cachedProcessOrigin = null;
|
|
3785
|
+
}
|
|
3786
|
+
}
|
|
3787
|
+
function processOwner(liveness) {
|
|
3788
|
+
return { instanceId: processInstanceId, ...processOrigin(), ...liveness ? { liveness: liveness.record } : {} };
|
|
3789
|
+
}
|
|
3790
|
+
var LIVENESS_SOCKET = /^\.live-([0-9a-f]+)-([0-9a-f]+)-[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}(\.new)?$/;
|
|
3791
|
+
var LIVENESS_PROBE_TIMEOUT_MS = 1e3;
|
|
3792
|
+
function socketDirectory(name) {
|
|
3793
|
+
const match = LIVENESS_SOCKET.exec(name);
|
|
3794
|
+
return match ? `${match[1]}-${match[2]}` : null;
|
|
3795
|
+
}
|
|
3796
|
+
async function pinDirectory(directory) {
|
|
3797
|
+
const handle = await (0, import_promises.open)(directory, "r");
|
|
3798
|
+
try {
|
|
3799
|
+
const { dev, ino } = await handle.stat({ bigint: true });
|
|
3800
|
+
return {
|
|
3801
|
+
identity: `${dev.toString(16)}-${ino.toString(16)}`,
|
|
3802
|
+
path: (name) => `/proc/self/fd/${handle.fd}/${name}`,
|
|
3803
|
+
close: () => handle.close()
|
|
3804
|
+
};
|
|
3805
|
+
} catch (error) {
|
|
3806
|
+
await handle.close();
|
|
3807
|
+
throw error;
|
|
3808
|
+
}
|
|
3809
|
+
}
|
|
3810
|
+
async function bindLiveness(directory) {
|
|
3811
|
+
if (!processOrigin()) return null;
|
|
3812
|
+
let pinned;
|
|
3813
|
+
try {
|
|
3814
|
+
pinned = await pinDirectory(directory);
|
|
3815
|
+
} catch {
|
|
3816
|
+
return null;
|
|
3817
|
+
}
|
|
3818
|
+
const socket = `.live-${pinned.identity}-${(0, import_node_crypto.randomUUID)()}`;
|
|
3819
|
+
const server = (0, import_node_net.createServer)((connection) => connection.destroy());
|
|
3820
|
+
let bound = false;
|
|
3821
|
+
try {
|
|
3822
|
+
await new Promise((resolve2, reject) => {
|
|
3823
|
+
server.once("error", reject);
|
|
3824
|
+
server.listen({ path: pinned.path(`${socket}.new`), exclusive: true }, () => {
|
|
3825
|
+
server.off("error", reject);
|
|
3826
|
+
resolve2();
|
|
3827
|
+
});
|
|
3828
|
+
});
|
|
3829
|
+
bound = true;
|
|
3830
|
+
server.on("error", () => void 0);
|
|
3831
|
+
server.unref();
|
|
3832
|
+
await (0, import_promises.rename)(pinned.path(`${socket}.new`), pinned.path(socket));
|
|
3833
|
+
const stats = await (0, import_promises.lstat)(pinned.path(socket), { bigint: true });
|
|
3834
|
+
if (!stats.isSocket()) throw new Error(`${socket} is not a socket`);
|
|
3835
|
+
await waitForTestBarrier(TEST_LOCK_LIVENESS_BARRIER_DIR, (0, import_node_path.basename)(directory));
|
|
3836
|
+
return { record: { socket, dev: String(stats.dev), ino: String(stats.ino) }, path: (0, import_node_path.join)(directory, socket), server };
|
|
3837
|
+
} catch {
|
|
3838
|
+
if (bound) {
|
|
3839
|
+
server.close();
|
|
3840
|
+
for (const name of [`${socket}.new`, socket]) await (0, import_promises.unlink)(pinned.path(name)).catch(() => void 0);
|
|
3841
|
+
}
|
|
3842
|
+
return null;
|
|
3843
|
+
} finally {
|
|
3844
|
+
await pinned.close();
|
|
3845
|
+
}
|
|
3846
|
+
}
|
|
3847
|
+
async function ensureLiveness(acquisition) {
|
|
3848
|
+
if (acquisition.held === void 0) acquisition.held = await bindLiveness(acquisition.directory);
|
|
3849
|
+
return acquisition.held;
|
|
3850
|
+
}
|
|
3851
|
+
async function closeLiveness(held) {
|
|
3852
|
+
held.server.close();
|
|
3853
|
+
await (0, import_promises.unlink)(held.path).catch(() => void 0);
|
|
3854
|
+
}
|
|
3855
|
+
function parseLiveness(value) {
|
|
3856
|
+
if (typeof value !== "object" || value === null) return null;
|
|
3857
|
+
const { socket, dev, ino } = value;
|
|
3858
|
+
const name = typeof socket === "string" ? LIVENESS_SOCKET.exec(socket) : null;
|
|
3859
|
+
return name !== null && name[3] === void 0 && typeof dev === "string" && /^[0-9]+$/.test(dev) && typeof ino === "string" && /^[0-9]+$/.test(ino) ? { socket: name[0], dev, ino } : null;
|
|
3860
|
+
}
|
|
3861
|
+
async function controlIn(pinned, acquisition) {
|
|
3862
|
+
const own = await ensureLiveness(acquisition);
|
|
3863
|
+
if (!own || socketDirectory(own.record.socket) !== pinned.identity) return null;
|
|
3864
|
+
const stats = await (0, import_promises.lstat)(pinned.path(own.record.socket), { bigint: true }).catch(() => null);
|
|
3865
|
+
return stats?.isSocket() && String(stats.dev) === own.record.dev && String(stats.ino) === own.record.ino ? own : null;
|
|
3866
|
+
}
|
|
3867
|
+
async function tryConnect(path) {
|
|
3868
|
+
return new Promise((resolve2) => {
|
|
3869
|
+
const probe = (0, import_node_net.connect)({ path });
|
|
3870
|
+
const settle = (outcome) => {
|
|
3871
|
+
clearTimeout(timer);
|
|
3872
|
+
probe.destroy();
|
|
3873
|
+
resolve2(outcome);
|
|
3874
|
+
};
|
|
3875
|
+
const timer = setTimeout(() => settle("other"), LIVENESS_PROBE_TIMEOUT_MS);
|
|
3876
|
+
probe.once("connect", () => settle("connected"));
|
|
3877
|
+
probe.once("error", (error) => settle(isErrno(error, "ECONNREFUSED") ? "refused" : "other"));
|
|
3878
|
+
});
|
|
3879
|
+
}
|
|
3880
|
+
async function kernelRefuses(pinned, name, control) {
|
|
3881
|
+
if (process.versions.bun !== void 0) return false;
|
|
3882
|
+
return await tryConnect(pinned.path(name)) === "refused" && await tryConnect(pinned.path(control.record.socket)) === "connected";
|
|
3883
|
+
}
|
|
3884
|
+
async function probeLiveness(liveness, acquisition) {
|
|
3885
|
+
let pinned;
|
|
3886
|
+
try {
|
|
3887
|
+
pinned = await pinDirectory(acquisition.directory);
|
|
3888
|
+
} catch {
|
|
3889
|
+
return "unknown";
|
|
3890
|
+
}
|
|
3891
|
+
try {
|
|
3892
|
+
if (socketDirectory(liveness.socket) !== pinned.identity) return "unknown";
|
|
3893
|
+
const control = await controlIn(pinned, acquisition);
|
|
3894
|
+
if (!control) return "unknown";
|
|
3895
|
+
let stats;
|
|
3896
|
+
try {
|
|
3897
|
+
stats = await (0, import_promises.lstat)(pinned.path(liveness.socket), { bigint: true });
|
|
3898
|
+
} catch (error) {
|
|
3899
|
+
return isErrno(error, "ENOENT") ? "gone" : "unknown";
|
|
3900
|
+
}
|
|
3901
|
+
if (!stats.isSocket() || String(stats.dev) !== liveness.dev || String(stats.ino) !== liveness.ino) return "unknown";
|
|
3902
|
+
return await kernelRefuses(pinned, liveness.socket, control) ? "refused" : "unknown";
|
|
3903
|
+
} finally {
|
|
3904
|
+
await pinned.close();
|
|
3905
|
+
}
|
|
3906
|
+
}
|
|
3907
|
+
async function sweepLiveness(acquisition) {
|
|
3908
|
+
if (process.versions.bun !== void 0 || !acquisition.held) return;
|
|
3909
|
+
const pinned = await pinDirectory(acquisition.directory);
|
|
3910
|
+
try {
|
|
3911
|
+
const control = await controlIn(pinned, acquisition);
|
|
3912
|
+
if (!control) return;
|
|
3913
|
+
for (const name of await (0, import_promises.readdir)(pinned.path("."))) {
|
|
3914
|
+
if (name === control.record.socket || socketDirectory(name) !== pinned.identity) continue;
|
|
3915
|
+
const stats = await (0, import_promises.lstat)(pinned.path(name)).catch(() => null);
|
|
3916
|
+
if (stats?.isSocket() && await kernelRefuses(pinned, name, control)) {
|
|
3917
|
+
await (0, import_promises.unlink)(pinned.path(name)).catch(() => void 0);
|
|
3918
|
+
}
|
|
3919
|
+
}
|
|
3920
|
+
} finally {
|
|
3921
|
+
await pinned.close();
|
|
3922
|
+
}
|
|
3923
|
+
}
|
|
3763
3924
|
var ProfileLockTimeoutError = class extends Error {
|
|
3764
3925
|
/** `detail` names the cause when it is not simply another process holding the lock. */
|
|
3765
3926
|
constructor(profile, timeoutMs, detail) {
|
|
@@ -3931,7 +4092,18 @@ async function acquireProfileLock(profile, options) {
|
|
|
3931
4092
|
if (path !== directory || !isErrno(error, "ENOENT")) throw error;
|
|
3932
4093
|
});
|
|
3933
4094
|
}
|
|
3934
|
-
const
|
|
4095
|
+
const liveness = { directory: turnDirectory, held: void 0 };
|
|
4096
|
+
const closeAcquisitionLiveness = async () => {
|
|
4097
|
+
if (liveness.held) await closeLiveness(liveness.held);
|
|
4098
|
+
};
|
|
4099
|
+
let taken;
|
|
4100
|
+
try {
|
|
4101
|
+
taken = await acquireTurn(profile, turnDirectory, deadline, liveness);
|
|
4102
|
+
} catch (error) {
|
|
4103
|
+
await closeAcquisitionLiveness();
|
|
4104
|
+
throw error;
|
|
4105
|
+
}
|
|
4106
|
+
const { turn, firstWaitedTurn } = taken;
|
|
3935
4107
|
try {
|
|
3936
4108
|
if (options.requireProfile && !await exists(profileConfigPath(profile))) {
|
|
3937
4109
|
await (0, import_promises.rmdir)(directory).catch(() => void 0);
|
|
@@ -3941,7 +4113,7 @@ async function acquireProfileLock(profile, options) {
|
|
|
3941
4113
|
const deletedWhileWaiting = typeof deletion?.slot === "number" && deletion.slot >= firstWaitedTurn;
|
|
3942
4114
|
while (true) {
|
|
3943
4115
|
const token = (0, import_node_crypto.randomUUID)();
|
|
3944
|
-
if (await publishProfileLock(profile, lockPath, token, options.requireProfile === true)) {
|
|
4116
|
+
if (await publishProfileLock(profile, lockPath, token, options.requireProfile === true, liveness.held ?? null)) {
|
|
3945
4117
|
return {
|
|
3946
4118
|
deletedWhileWaiting,
|
|
3947
4119
|
turnSlot: turn.slot,
|
|
@@ -3949,17 +4121,19 @@ async function acquireProfileLock(profile, options) {
|
|
|
3949
4121
|
try {
|
|
3950
4122
|
await releaseProfileLock(profile, lockPath, token);
|
|
3951
4123
|
} finally {
|
|
3952
|
-
await releaseTurn(profile, turn);
|
|
4124
|
+
await releaseTurn(profile, turn, liveness);
|
|
4125
|
+
await closeAcquisitionLiveness();
|
|
3953
4126
|
}
|
|
3954
4127
|
}
|
|
3955
4128
|
};
|
|
3956
4129
|
}
|
|
3957
4130
|
await signalTestLockContention(profile);
|
|
3958
|
-
if (await recoverStaleLock(profile, lockPath, staleAfterMs)) continue;
|
|
4131
|
+
if (await recoverStaleLock(profile, lockPath, liveness, staleAfterMs)) continue;
|
|
3959
4132
|
await waitOrTimeOut(profile, deadline);
|
|
3960
4133
|
}
|
|
3961
4134
|
} catch (error) {
|
|
3962
|
-
await releaseTurn(profile, turn);
|
|
4135
|
+
await releaseTurn(profile, turn, liveness);
|
|
4136
|
+
await closeAcquisitionLiveness();
|
|
3963
4137
|
throw error;
|
|
3964
4138
|
}
|
|
3965
4139
|
}
|
|
@@ -3973,12 +4147,12 @@ async function waitOrTimeOut(profile, deadline, detail) {
|
|
|
3973
4147
|
await sleep(Math.min(deadline.retryMs, deadline.timeoutMs - (Date.now() - deadline.startedAt)));
|
|
3974
4148
|
}
|
|
3975
4149
|
var TURN = /^(?:0|[1-9][0-9]*)$/;
|
|
3976
|
-
var
|
|
3977
|
-
var relinquishedTurns = globalThis[
|
|
4150
|
+
var _a3;
|
|
4151
|
+
var relinquishedTurns = globalThis[_a3 = /* @__PURE__ */ Symbol.for("tinycloud.operations.relinquishedProfileTurns.v1")] ?? (globalThis[_a3] = /* @__PURE__ */ new Set());
|
|
3978
4152
|
var MARKER_RETRY_DELAYS_MS = [10, 50, 250];
|
|
3979
4153
|
var SETTLE_RETRY_MS = 1e3;
|
|
3980
4154
|
var SETTLE_RETRIES = 300;
|
|
3981
|
-
async function acquireTurn(profile, directory, deadline) {
|
|
4155
|
+
async function acquireTurn(profile, directory, deadline, liveness) {
|
|
3982
4156
|
let firstWaitedTurn;
|
|
3983
4157
|
let problem;
|
|
3984
4158
|
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.`;
|
|
@@ -4003,13 +4177,13 @@ async function acquireTurn(profile, directory, deadline) {
|
|
|
4003
4177
|
}
|
|
4004
4178
|
firstWaitedTurn ?? (firstWaitedTurn = latest.done ? latest.slot + 1 : latest.slot);
|
|
4005
4179
|
if (latest.held && latest.done) {
|
|
4006
|
-
const turn = await takeTurn(profile, directory, latest);
|
|
4180
|
+
const turn = await takeTurn(profile, directory, latest, await ensureLiveness(liveness));
|
|
4007
4181
|
if (turn !== null) return { turn, firstWaitedTurn };
|
|
4008
4182
|
await waitOrTimeOut(profile, deadline);
|
|
4009
4183
|
} else if (latest.done) {
|
|
4010
4184
|
problem = damaged(`ends with turn ${latest.slot}, which was never granted`);
|
|
4011
4185
|
await waitOrTimeOut(profile, deadline, problem);
|
|
4012
|
-
} else if (latest.pid === process.pid
|
|
4186
|
+
} else if (latest.pid === process.pid && relinquishedTurns.has(latest.token) || !await isRecordedProcessAlive(latest, liveness)) {
|
|
4013
4187
|
await waitForTestBarrier(TEST_LOCK_TURN_SETTLE_BARRIER_DIR, profile);
|
|
4014
4188
|
await settleAbandonedTurn(directory, latest);
|
|
4015
4189
|
relinquishedTurns.delete(latest.token);
|
|
@@ -4054,6 +4228,7 @@ async function readTurn(directory, slot) {
|
|
|
4054
4228
|
const done = await exists((0, import_node_path.join)(path, `${owner.token}.done`));
|
|
4055
4229
|
const held = await exists((0, import_node_path.join)(path, `${owner.token}.held`));
|
|
4056
4230
|
return {
|
|
4231
|
+
...owner,
|
|
4057
4232
|
slot,
|
|
4058
4233
|
token: owner.token,
|
|
4059
4234
|
pid: owner.pid,
|
|
@@ -4067,11 +4242,11 @@ async function isFinishedTurn(directory, slot, token) {
|
|
|
4067
4242
|
if (turn === "damaged") throw new Error(`Turn ${slot} of the turn lock ${directory} is unreadable.`);
|
|
4068
4243
|
return turn !== null && turn.token === token && turn.held && turn.done;
|
|
4069
4244
|
}
|
|
4070
|
-
async function takeTurn(profile, directory, latest) {
|
|
4245
|
+
async function takeTurn(profile, directory, latest, liveness) {
|
|
4071
4246
|
const token = (0, import_node_crypto.randomUUID)();
|
|
4072
4247
|
const slot = latest.slot + 1;
|
|
4073
4248
|
const path = (0, import_node_path.join)(directory, String(slot));
|
|
4074
|
-
const owner = { pid: process.pid, createdAt: (/* @__PURE__ */ new Date()).toISOString(), token, after: latest.token };
|
|
4249
|
+
const owner = { pid: process.pid, createdAt: (/* @__PURE__ */ new Date()).toISOString(), token, after: latest.token, ...processOwner(liveness) };
|
|
4075
4250
|
let published = false;
|
|
4076
4251
|
try {
|
|
4077
4252
|
published = await publishDirectory(
|
|
@@ -4125,7 +4300,7 @@ async function markTurn(path, token, kind) {
|
|
|
4125
4300
|
}
|
|
4126
4301
|
async function createTurnDirectory(directory) {
|
|
4127
4302
|
const token = (0, import_node_crypto.randomUUID)();
|
|
4128
|
-
const owner = { pid: process.pid, createdAt: (/* @__PURE__ */ new Date()).toISOString(), token, after: null };
|
|
4303
|
+
const owner = { pid: process.pid, createdAt: (/* @__PURE__ */ new Date()).toISOString(), token, after: null, ...processOwner(null) };
|
|
4129
4304
|
await publishDirectory((0, import_node_path.join)((0, import_node_path.dirname)(directory), `.stage-${(0, import_node_path.basename)(directory)}-${process.pid}-${token}`), directory, {
|
|
4130
4305
|
"0/owner.json": `${JSON.stringify(owner, null, 2)}
|
|
4131
4306
|
`,
|
|
@@ -4149,31 +4324,39 @@ async function publishDirectory(stage, target, files, beforeRename) {
|
|
|
4149
4324
|
await (0, import_promises.rm)(stage, { recursive: true, force: true }).catch(() => void 0);
|
|
4150
4325
|
}
|
|
4151
4326
|
}
|
|
4152
|
-
async function releaseTurn(profile, turn) {
|
|
4327
|
+
async function releaseTurn(profile, turn, liveness) {
|
|
4153
4328
|
try {
|
|
4154
4329
|
await markTurn((0, import_node_path.join)(turn.directory, String(turn.slot)), turn.token, "done");
|
|
4155
4330
|
} catch {
|
|
4156
4331
|
settleOwnTurn(turn.directory, turn.slot, turn.token);
|
|
4157
4332
|
return;
|
|
4158
4333
|
}
|
|
4159
|
-
await collectTurns(profile, turn).catch(() => void 0);
|
|
4334
|
+
await collectTurns(profile, turn, liveness).catch(() => void 0);
|
|
4160
4335
|
}
|
|
4161
|
-
function isAbandonedTurnEntry(name) {
|
|
4336
|
+
async function isAbandonedTurnEntry(directory, name, liveness) {
|
|
4162
4337
|
if (name.startsWith(".trash-")) return true;
|
|
4163
4338
|
const writer = /^\.(?:stage|deleted\.json)[-.]([0-9]+)[-.]/.exec(name)?.[1];
|
|
4164
|
-
|
|
4339
|
+
if (writer === void 0) return false;
|
|
4340
|
+
if (Number(writer) !== process.pid) return !await isRecordedProcessAlive({ pid: Number(writer) }, liveness);
|
|
4341
|
+
return name.startsWith(".stage-") && await isStagedOwnerGone((0, import_node_path.join)(directory, name, "owner.json"), liveness);
|
|
4165
4342
|
}
|
|
4166
|
-
function isAbandonedTurnDirectoryStage(name) {
|
|
4343
|
+
async function isAbandonedTurnDirectoryStage(directory, name, liveness) {
|
|
4167
4344
|
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];
|
|
4168
|
-
|
|
4345
|
+
if (writer === void 0) return false;
|
|
4346
|
+
if (Number(writer) !== process.pid) return !await isRecordedProcessAlive({ pid: Number(writer) }, liveness);
|
|
4347
|
+
return isStagedOwnerGone((0, import_node_path.join)(directory, name, "0", "owner.json"), liveness);
|
|
4348
|
+
}
|
|
4349
|
+
async function isStagedOwnerGone(recordPath, liveness) {
|
|
4350
|
+
const owner = await readJson(recordPath).catch(() => null);
|
|
4351
|
+
return typeof owner === "object" && owner !== null && owner.pid === process.pid && !await isRecordedProcessAlive(owner, liveness);
|
|
4169
4352
|
}
|
|
4170
|
-
async function collectTurns(profile, turn) {
|
|
4353
|
+
async function collectTurns(profile, turn, liveness) {
|
|
4171
4354
|
for (const [directory, abandoned] of [
|
|
4172
4355
|
[turn.directory, isAbandonedTurnEntry],
|
|
4173
4356
|
[(0, import_node_path.dirname)(turn.directory), isAbandonedTurnDirectoryStage]
|
|
4174
4357
|
]) {
|
|
4175
|
-
for (const name of
|
|
4176
|
-
await (0, import_promises.rm)((0, import_node_path.join)(directory, name), { recursive: true, force: true }).catch(() => void 0);
|
|
4358
|
+
for (const name of await (0, import_promises.readdir)(directory)) {
|
|
4359
|
+
if (await abandoned(directory, name, liveness)) await (0, import_promises.rm)((0, import_node_path.join)(directory, name), { recursive: true, force: true }).catch(() => void 0);
|
|
4177
4360
|
}
|
|
4178
4361
|
}
|
|
4179
4362
|
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);
|
|
@@ -4191,8 +4374,9 @@ async function collectTurns(profile, turn) {
|
|
|
4191
4374
|
await (0, import_promises.rm)(trash, { recursive: true, force: true }).catch(() => void 0);
|
|
4192
4375
|
}
|
|
4193
4376
|
}
|
|
4377
|
+
await sweepLiveness(liveness);
|
|
4194
4378
|
}
|
|
4195
|
-
async function publishProfileLock(profile, lockPath, token, requireProfile) {
|
|
4379
|
+
async function publishProfileLock(profile, lockPath, token, requireProfile, liveness) {
|
|
4196
4380
|
try {
|
|
4197
4381
|
await (0, import_promises.mkdir)(lockPath, { mode: PRIVATE_DIR_MODE });
|
|
4198
4382
|
} catch (error) {
|
|
@@ -4205,7 +4389,7 @@ async function publishProfileLock(profile, lockPath, token, requireProfile) {
|
|
|
4205
4389
|
await waitForTestBarrier(TEST_LOCK_PUBLISH_BARRIER_DIR, profile);
|
|
4206
4390
|
const ownerPath = (0, import_node_path.join)(lockPath, "owner.json");
|
|
4207
4391
|
const staged = (0, import_node_path.join)((0, import_node_path.dirname)(lockPath), `.lock-owner-${token}.tmp`);
|
|
4208
|
-
const owner = `${JSON.stringify({ pid: process.pid, createdAt: (/* @__PURE__ */ new Date()).toISOString(), token }, null, 2)}
|
|
4392
|
+
const owner = `${JSON.stringify({ pid: process.pid, createdAt: (/* @__PURE__ */ new Date()).toISOString(), token, ...processOwner(liveness) }, null, 2)}
|
|
4209
4393
|
`;
|
|
4210
4394
|
try {
|
|
4211
4395
|
await (0, import_promises.writeFile)(staged, owner, { encoding: "utf8", mode: PRIVATE_FILE_MODE, flag: "wx" });
|
|
@@ -4250,7 +4434,7 @@ async function signalTestLockContention(profile) {
|
|
|
4250
4434
|
await (0, import_promises.writeFile)(signalPath, `${profile}
|
|
4251
4435
|
`, { encoding: "utf8", flag: "wx" }).catch(() => void 0);
|
|
4252
4436
|
}
|
|
4253
|
-
async function recoverOwnerlessLock(profile, lockPath, staleAfterMs) {
|
|
4437
|
+
async function recoverOwnerlessLock(profile, lockPath, liveness, staleAfterMs) {
|
|
4254
4438
|
let aged;
|
|
4255
4439
|
try {
|
|
4256
4440
|
const { mtimeMs } = await (0, import_promises.stat)(lockPath);
|
|
@@ -4267,7 +4451,7 @@ async function recoverOwnerlessLock(profile, lockPath, staleAfterMs) {
|
|
|
4267
4451
|
if (!onlyOrphanedClaims && (!aged || recoveryFenced(checkedAt, staleAfterMs))) return false;
|
|
4268
4452
|
for (const name of entries.filter((entry) => RECOVERY_CLAIM.test(entry))) {
|
|
4269
4453
|
const claimPath = (0, import_node_path.join)(lockPath, name);
|
|
4270
|
-
if (await hasLiveRecoveryOwner(claimPath)) {
|
|
4454
|
+
if (await hasLiveRecoveryOwner(claimPath, liveness)) {
|
|
4271
4455
|
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"));
|
|
4272
4456
|
return false;
|
|
4273
4457
|
}
|
|
@@ -4283,7 +4467,7 @@ async function recoverOwnerlessLock(profile, lockPath, staleAfterMs) {
|
|
|
4283
4467
|
await removeAgedOwnerFiles(lockPath, staleAfterMs);
|
|
4284
4468
|
return true;
|
|
4285
4469
|
}
|
|
4286
|
-
async function hasLiveRecoveryOwner(claimPath) {
|
|
4470
|
+
async function hasLiveRecoveryOwner(claimPath, liveness) {
|
|
4287
4471
|
let contents;
|
|
4288
4472
|
try {
|
|
4289
4473
|
contents = await (0, import_promises.readFile)(claimPath, "utf8");
|
|
@@ -4297,8 +4481,8 @@ async function hasLiveRecoveryOwner(claimPath) {
|
|
|
4297
4481
|
} catch {
|
|
4298
4482
|
return false;
|
|
4299
4483
|
}
|
|
4300
|
-
const
|
|
4301
|
-
return typeof pid === "number" && Number.isInteger(pid) && pid > 0 &&
|
|
4484
|
+
const record = typeof owner === "object" && owner !== null && "pid" in owner ? owner : null;
|
|
4485
|
+
return typeof record?.pid === "number" && Number.isInteger(record.pid) && record.pid > 0 && await isRecordedProcessAlive(record, liveness);
|
|
4302
4486
|
}
|
|
4303
4487
|
function recoveryFenced(since, staleAfterMs) {
|
|
4304
4488
|
return performance.now() - since >= staleAfterMs / 2;
|
|
@@ -4315,11 +4499,11 @@ async function removeAgedOwnerFiles(lockPath, staleAfterMs) {
|
|
|
4315
4499
|
if (aged) await (0, import_promises.rm)(path, { force: true }).catch(() => void 0);
|
|
4316
4500
|
}
|
|
4317
4501
|
}
|
|
4318
|
-
async function recoverStaleLock(profile, lockPath, staleAfterMs) {
|
|
4502
|
+
async function recoverStaleLock(profile, lockPath, liveness, staleAfterMs) {
|
|
4319
4503
|
const ownerPath = (0, import_node_path.join)(lockPath, "owner.json");
|
|
4320
4504
|
const observed = await (0, import_promises.readFile)(ownerPath, "utf8").catch(() => null);
|
|
4321
|
-
if (observed === null) return recoverOwnerlessLock(profile, lockPath, staleAfterMs);
|
|
4322
|
-
if (!await isAbandonedOwner(observed, ownerPath, staleAfterMs)) return false;
|
|
4505
|
+
if (observed === null) return recoverOwnerlessLock(profile, lockPath, liveness, staleAfterMs);
|
|
4506
|
+
if (!await isAbandonedOwner(observed, ownerPath, liveness, staleAfterMs)) return false;
|
|
4323
4507
|
await waitForTestBarrier(TEST_LOCK_RECOVERY_BARRIER_DIR, profile);
|
|
4324
4508
|
const claimPath = (0, import_node_path.join)(lockPath, `.recover-${(0, import_node_crypto.randomUUID)()}.json`);
|
|
4325
4509
|
let moved = false;
|
|
@@ -4355,7 +4539,7 @@ async function recoverStaleLock(profile, lockPath, staleAfterMs) {
|
|
|
4355
4539
|
await removeAgedOwnerFiles(lockPath, staleAfterMs);
|
|
4356
4540
|
return true;
|
|
4357
4541
|
}
|
|
4358
|
-
async function isAbandonedOwner(observed, ownerPath, staleAfterMs) {
|
|
4542
|
+
async function isAbandonedOwner(observed, ownerPath, liveness, staleAfterMs) {
|
|
4359
4543
|
let owner;
|
|
4360
4544
|
try {
|
|
4361
4545
|
owner = JSON.parse(observed);
|
|
@@ -4363,7 +4547,7 @@ async function isAbandonedOwner(observed, ownerPath, staleAfterMs) {
|
|
|
4363
4547
|
owner = null;
|
|
4364
4548
|
}
|
|
4365
4549
|
if (typeof owner === "object" && owner !== null && "pid" in owner) {
|
|
4366
|
-
return isStaleOwner(owner, staleAfterMs);
|
|
4550
|
+
return isStaleOwner(owner, liveness, staleAfterMs);
|
|
4367
4551
|
}
|
|
4368
4552
|
const modifiedAt = await (0, import_promises.stat)(ownerPath).then(({ mtimeMs }) => mtimeMs, () => Number.NaN);
|
|
4369
4553
|
return Date.now() - modifiedAt >= staleAfterMs;
|
|
@@ -4385,14 +4569,14 @@ async function waitForTestBarrier(environmentName, profile) {
|
|
|
4385
4569
|
}
|
|
4386
4570
|
}
|
|
4387
4571
|
}
|
|
4388
|
-
function isStaleOwner(owner, staleAfterMs) {
|
|
4572
|
+
async function isStaleOwner(owner, liveness, staleAfterMs) {
|
|
4389
4573
|
const now = Date.now();
|
|
4390
4574
|
const createdAt = typeof owner?.createdAt === "string" ? Date.parse(owner.createdAt) : Number.NaN;
|
|
4391
4575
|
if (!Number.isFinite(createdAt) || now - createdAt < staleAfterMs) return false;
|
|
4392
4576
|
if (typeof owner?.pid !== "number" || !Number.isInteger(owner.pid) || owner.pid <= 0) {
|
|
4393
4577
|
return false;
|
|
4394
4578
|
}
|
|
4395
|
-
return !
|
|
4579
|
+
return !await isRecordedProcessAlive(owner, liveness);
|
|
4396
4580
|
}
|
|
4397
4581
|
async function exists(path) {
|
|
4398
4582
|
try {
|
|
@@ -4403,13 +4587,22 @@ async function exists(path) {
|
|
|
4403
4587
|
throw error;
|
|
4404
4588
|
}
|
|
4405
4589
|
}
|
|
4406
|
-
function
|
|
4590
|
+
async function isRecordedProcessAlive(owner, acquisition) {
|
|
4591
|
+
const pid = owner.pid;
|
|
4592
|
+
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0) return true;
|
|
4407
4593
|
try {
|
|
4408
4594
|
process.kill(pid, 0);
|
|
4409
|
-
return true;
|
|
4410
4595
|
} catch (error) {
|
|
4411
4596
|
return !isErrno(error, "ESRCH");
|
|
4412
4597
|
}
|
|
4598
|
+
if (pid !== process.pid || typeof owner.instanceId !== "string" || !/^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/.test(owner.instanceId) || owner.instanceId === processInstanceId) return true;
|
|
4599
|
+
const origin = processOrigin();
|
|
4600
|
+
if (!origin || owner.bootId !== origin.bootId || typeof owner.pidNamespace !== "string" || !/^pid:\[[0-9]+\]$/.test(owner.pidNamespace)) return true;
|
|
4601
|
+
if (owner.pidNamespace === origin.pidNamespace) {
|
|
4602
|
+
return typeof owner.processStartTime !== "string" || !/^[0-9]+$/.test(owner.processStartTime) || owner.processStartTime === origin.processStartTime;
|
|
4603
|
+
}
|
|
4604
|
+
const liveness = parseLiveness(owner.liveness);
|
|
4605
|
+
return liveness === null || await probeLiveness(liveness, acquisition) === "unknown";
|
|
4413
4606
|
}
|
|
4414
4607
|
function validateProfileName(profile) {
|
|
4415
4608
|
if (!profile || profile === "." || profile === ".." || profile.includes("/") || profile.includes("\\") || profile.includes("\0")) {
|
|
@@ -4509,6 +4702,8 @@ var STORED_GRANT_SKIP_REASONS = [
|
|
|
4509
4702
|
"resources_mismatch",
|
|
4510
4703
|
"unsupported",
|
|
4511
4704
|
"activation_rejected",
|
|
4705
|
+
"activation_unavailable",
|
|
4706
|
+
"activation_unauthenticated",
|
|
4512
4707
|
"invalid"
|
|
4513
4708
|
];
|
|
4514
4709
|
var DELEGATION_CID_PATTERN = "^bafkr4i[a-z2-7]{52}$";
|
|
@@ -4742,6 +4937,9 @@ function skipped(reason) {
|
|
|
4742
4937
|
}
|
|
4743
4938
|
async function replayProfileDelegations(profile, node, options) {
|
|
4744
4939
|
await prepareStoredDelegationReplay(profile, node, { host: options.host, migrate: options.migrate });
|
|
4940
|
+
return replayStoredDelegationRecords(profile, node, options);
|
|
4941
|
+
}
|
|
4942
|
+
async function replayStoredDelegationRecords(profile, node, options) {
|
|
4745
4943
|
const installed = [];
|
|
4746
4944
|
const warnings = [];
|
|
4747
4945
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -4933,6 +5131,7 @@ function isRecord(value) {
|
|
|
4933
5131
|
replayLimit,
|
|
4934
5132
|
replayProfileDelegations,
|
|
4935
5133
|
replayStoredDelegation,
|
|
5134
|
+
replayStoredDelegationRecords,
|
|
4936
5135
|
storedDelegationForAnotherKey,
|
|
4937
5136
|
storedDelegationKind,
|
|
4938
5137
|
storedDelegationSource,
|