@elpapi42/pi-fleet 0.1.0-beta.0 → 0.1.0-beta.2
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/CHANGELOG.md +36 -0
- package/README.md +6 -3
- package/dist/cli-meta.json +8 -8
- package/dist/cli.mjs +137 -13
- package/dist/cli.mjs.map +3 -3
- package/dist/installer-meta.json +4 -4
- package/dist/installer.mjs +116 -11
- package/dist/installer.mjs.map +3 -3
- package/dist/runtime-manifest.json +27 -27
- package/dist/runtime-meta.json +12 -12
- package/dist/runtime.mjs +149 -46
- package/dist/runtime.mjs.map +2 -2
- package/dist/sqlite-worker-meta.json +4 -4
- package/dist/sqlite-worker.mjs +45 -38
- package/dist/sqlite-worker.mjs.map +2 -2
- package/package.json +4 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
### Reliability
|
|
6
|
+
|
|
7
|
+
- Added deterministic fault-injection coverage for receive timeout/cancellation, delivery recovery, runtime crashes, Pi RPC failures, SQLite worker/storage failures, protocol framing, command races, raw session boundaries, and public-error redaction.
|
|
8
|
+
- Serialized create, send, and destroy transitions per agent so cross-command races cannot orphan or resurrect an agent generation.
|
|
9
|
+
- Made SQLite-worker failures terminal for current and future calls and made send recovery safe when an operation exists but no send record was committed.
|
|
10
|
+
- Redacted Pi stderr and unexpected internal exceptions from public errors while retaining stable typed error codes.
|
|
11
|
+
- Contained asynchronous coordinator event/store failures by stopping the affected Pi process and rejecting waiters instead of producing unhandled promise rejections.
|
|
12
|
+
- Added a nightly reliability workflow and an isolated soak suite covering 500 concurrent ordered sends and 100 name lifecycle cycles.
|
|
13
|
+
- Reject invalid UTF-8 in stdin and Pi RPC stdout instead of silently replacing bytes at either protocol boundary.
|
|
14
|
+
|
|
15
|
+
## 0.1.0-beta.2 — 2026-07-20
|
|
16
|
+
|
|
17
|
+
Reliability-focused beta maintenance release. No intentional product-contract changes.
|
|
18
|
+
|
|
19
|
+
### Reliability
|
|
20
|
+
|
|
21
|
+
- Added deterministic fault coverage and reproducible randomized lifecycle-race soak testing.
|
|
22
|
+
- Fixed timeout/cancellation classification, ambiguous-delivery recovery, no-replay behavior, and coordinator/store failure containment.
|
|
23
|
+
- Validated inbound protocol majors, strict and concurrent runtime materialization, and released beta.0/beta.1 CLI-runtime compatibility.
|
|
24
|
+
- Added unclean SQLite `quick_check` validation, redacted error handling, fatal UTF-8 checks, process cleanup coverage, and nightly resource-stability checks.
|
|
25
|
+
|
|
26
|
+
### Validation limits
|
|
27
|
+
|
|
28
|
+
- Real disk exhaustion, host logout/reboot, macOS launchd/containment, and multi-hour platform resource soak remain unvalidated.
|
|
29
|
+
|
|
30
|
+
## 0.1.0-beta.1 — 2026-07-20
|
|
31
|
+
|
|
32
|
+
Release-pipeline validation beta. No product-contract changes from `0.1.0-beta.0`.
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- Publishes through the tag-driven GitHub Actions workflow using npm trusted publishing and provenance.
|
|
37
|
+
- Retains the same Linux x64 validation scope, beta limitations, and user-owned Pi session guarantees as `0.1.0-beta.0`.
|
|
38
|
+
|
|
3
39
|
## 0.1.0-beta.0 — 2026-07-20
|
|
4
40
|
|
|
5
41
|
First public beta of Pi Fleet.
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ create · send · receive · status · list · watch · destroy
|
|
|
8
8
|
|
|
9
9
|
Fleet keeps a Pi process resident when possible, restores it from the same native Pi session when absent, accepts repeated steering input, and returns the latest assistant message after Pi becomes idle. Pi sessions remain under your control: Fleet references them but never copies or deletes them.
|
|
10
10
|
|
|
11
|
-
> **Beta:** `0.1.0-beta.
|
|
11
|
+
> **Beta:** `0.1.0-beta.2` is validated on Linux x64 with Pi `0.80.10`, including deterministic fault, recovery, package-compatibility, and resource-stability testing. Actual logout/reboot recovery, macOS launchd/containment, automatic upgrades, and public service-management UX are not yet release-validated.
|
|
12
12
|
|
|
13
13
|
## Install
|
|
14
14
|
|
|
@@ -173,9 +173,10 @@ Do not include API keys, message contents, session contents, or private paths un
|
|
|
173
173
|
|
|
174
174
|
## Beta limitations
|
|
175
175
|
|
|
176
|
-
-
|
|
176
|
+
- Current deterministic reliability and package validation covers Linux x64 only.
|
|
177
177
|
- Actual logout and host reboot recovery remain unvalidated.
|
|
178
178
|
- launchd and macOS descendant containment remain unvalidated.
|
|
179
|
+
- Real disk-exhaustion behavior and multi-hour resource growth remain unvalidated; bounded SQLite and in-process resource tests are covered.
|
|
179
180
|
- Native service install/repair/uninstall exists internally but has no supported public UX yet.
|
|
180
181
|
- Runtime upgrades are not automatic; active runtimes are not silently replaced.
|
|
181
182
|
- Session tails cannot promise exactly-once behavior under arbitrary external mutation.
|
|
@@ -189,12 +190,14 @@ npm run typecheck
|
|
|
189
190
|
npm run lint
|
|
190
191
|
npm run format:check
|
|
191
192
|
npm test
|
|
193
|
+
npm run test:faults
|
|
192
194
|
npm run build
|
|
193
195
|
npm run test:package
|
|
194
196
|
npm run test:platform
|
|
197
|
+
npm run test:soak
|
|
195
198
|
```
|
|
196
199
|
|
|
197
|
-
See [`IMPLEMENTATION_PLAN.md`](./IMPLEMENTATION_PLAN.md) for architecture,
|
|
200
|
+
See [`IMPLEMENTATION_PLAN.md`](./IMPLEMENTATION_PLAN.md) for architecture and release gates, and [`TESTING_PLAN.md`](./TESTING_PLAN.md) for failure injection, crash recovery, concurrency, privacy, and soak testing. Deterministic fault tests run in the full suite; heavier reliability checks run nightly.
|
|
198
201
|
|
|
199
202
|
## License
|
|
200
203
|
|
package/dist/cli-meta.json
CHANGED
|
@@ -156,7 +156,7 @@
|
|
|
156
156
|
"format": "esm"
|
|
157
157
|
},
|
|
158
158
|
"src/cli/input.ts": {
|
|
159
|
-
"bytes":
|
|
159
|
+
"bytes": 977,
|
|
160
160
|
"imports": [],
|
|
161
161
|
"format": "esm"
|
|
162
162
|
},
|
|
@@ -392,7 +392,7 @@
|
|
|
392
392
|
"format": "esm"
|
|
393
393
|
},
|
|
394
394
|
"src/client/socket-fleet-client.ts": {
|
|
395
|
-
"bytes":
|
|
395
|
+
"bytes": 9121,
|
|
396
396
|
"imports": [
|
|
397
397
|
{
|
|
398
398
|
"path": "node:crypto",
|
|
@@ -444,7 +444,7 @@
|
|
|
444
444
|
"format": "esm"
|
|
445
445
|
},
|
|
446
446
|
"src/platform/install/runtime-release.ts": {
|
|
447
|
-
"bytes":
|
|
447
|
+
"bytes": 9583,
|
|
448
448
|
"imports": [
|
|
449
449
|
{
|
|
450
450
|
"path": "node:crypto",
|
|
@@ -593,7 +593,7 @@
|
|
|
593
593
|
"imports": [],
|
|
594
594
|
"exports": [],
|
|
595
595
|
"inputs": {},
|
|
596
|
-
"bytes":
|
|
596
|
+
"bytes": 260315
|
|
597
597
|
},
|
|
598
598
|
"dist/cli.mjs": {
|
|
599
599
|
"imports": [
|
|
@@ -780,7 +780,7 @@
|
|
|
780
780
|
"bytesInOutput": 140
|
|
781
781
|
},
|
|
782
782
|
"src/cli/input.ts": {
|
|
783
|
-
"bytesInOutput":
|
|
783
|
+
"bytesInOutput": 841
|
|
784
784
|
},
|
|
785
785
|
"src/cli/output.ts": {
|
|
786
786
|
"bytesInOutput": 1016
|
|
@@ -816,7 +816,7 @@
|
|
|
816
816
|
"bytesInOutput": 2440
|
|
817
817
|
},
|
|
818
818
|
"src/client/socket-fleet-client.ts": {
|
|
819
|
-
"bytesInOutput":
|
|
819
|
+
"bytesInOutput": 7258
|
|
820
820
|
},
|
|
821
821
|
"src/protocol/version.ts": {
|
|
822
822
|
"bytesInOutput": 70
|
|
@@ -828,7 +828,7 @@
|
|
|
828
828
|
"bytesInOutput": 4658
|
|
829
829
|
},
|
|
830
830
|
"src/platform/install/runtime-release.ts": {
|
|
831
|
-
"bytesInOutput":
|
|
831
|
+
"bytesInOutput": 8505
|
|
832
832
|
},
|
|
833
833
|
"src/platform/install/tree-integrity.ts": {
|
|
834
834
|
"bytesInOutput": 1879
|
|
@@ -837,7 +837,7 @@
|
|
|
837
837
|
"bytesInOutput": 1191
|
|
838
838
|
}
|
|
839
839
|
},
|
|
840
|
-
"bytes":
|
|
840
|
+
"bytes": 144920
|
|
841
841
|
}
|
|
842
842
|
}
|
|
843
843
|
}
|
package/dist/cli.mjs
CHANGED
|
@@ -3385,7 +3385,7 @@ function splitArgv(argv) {
|
|
|
3385
3385
|
|
|
3386
3386
|
// src/shared/product-identity.ts
|
|
3387
3387
|
var PRODUCT_BINARY = "pifleet";
|
|
3388
|
-
var PRODUCT_VERSION = "0.1.0-beta.
|
|
3388
|
+
var PRODUCT_VERSION = "0.1.0-beta.2";
|
|
3389
3389
|
|
|
3390
3390
|
// src/cli/commands/create.ts
|
|
3391
3391
|
import { resolve } from "node:path";
|
|
@@ -3408,7 +3408,13 @@ async function resolveMessageInput(value, stdin, maxBytes = DEFAULT_MAX_INPUT_BY
|
|
|
3408
3408
|
if (bytes > maxBytes) throw new Error(`stdin exceeds the ${maxBytes}-byte limit`);
|
|
3409
3409
|
chunks.push(buffer);
|
|
3410
3410
|
}
|
|
3411
|
-
|
|
3411
|
+
let decoded;
|
|
3412
|
+
try {
|
|
3413
|
+
decoded = new TextDecoder("utf-8", { fatal: true }).decode(Buffer.concat(chunks));
|
|
3414
|
+
} catch {
|
|
3415
|
+
throw new Error("stdin must be valid UTF-8");
|
|
3416
|
+
}
|
|
3417
|
+
return requireContent(decoded);
|
|
3412
3418
|
}
|
|
3413
3419
|
function requireContent(value) {
|
|
3414
3420
|
if (value.trim().length === 0) throw new Error("message must not be empty or whitespace-only");
|
|
@@ -3693,6 +3699,13 @@ var SocketFleetClient = class {
|
|
|
3693
3699
|
try {
|
|
3694
3700
|
for await (const frame of frames) {
|
|
3695
3701
|
if (!isRecord(frame) || frame.requestId !== requestId) continue;
|
|
3702
|
+
if (frame.v !== PROTOCOL_VERSION) {
|
|
3703
|
+
yield err({
|
|
3704
|
+
code: "protocol_error",
|
|
3705
|
+
message: "Runtime protocol version is incompatible with this client."
|
|
3706
|
+
});
|
|
3707
|
+
return;
|
|
3708
|
+
}
|
|
3696
3709
|
if (frame.stream === "ready") continue;
|
|
3697
3710
|
if (frame.stream === "end") {
|
|
3698
3711
|
endedExplicitly = true;
|
|
@@ -3746,6 +3759,12 @@ var SocketFleetClient = class {
|
|
|
3746
3759
|
if (!isRecord(frame) || frame.requestId !== requestId || typeof frame.ok !== "boolean") {
|
|
3747
3760
|
return err({ code: "protocol_error", message: "Invalid runtime response." });
|
|
3748
3761
|
}
|
|
3762
|
+
if (frame.v !== PROTOCOL_VERSION) {
|
|
3763
|
+
return err({
|
|
3764
|
+
code: "protocol_error",
|
|
3765
|
+
message: "Runtime protocol version is incompatible with this client."
|
|
3766
|
+
});
|
|
3767
|
+
}
|
|
3749
3768
|
if (frame.ok) return ok(frame.result);
|
|
3750
3769
|
if (isErrorRecord(frame.error)) return err(frame.error);
|
|
3751
3770
|
return err({ code: "protocol_error", message: "Runtime returned an invalid error." });
|
|
@@ -3888,8 +3907,8 @@ import { promisify } from "node:util";
|
|
|
3888
3907
|
|
|
3889
3908
|
// src/platform/install/runtime-release.ts
|
|
3890
3909
|
import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
|
|
3891
|
-
import { chmod, cp, lstat as lstat2, mkdir, readFile as readFile2, rename, rm
|
|
3892
|
-
import { join as join2, resolve as resolve3, sep as sep2 } from "node:path";
|
|
3910
|
+
import { chmod, cp, lstat as lstat2, mkdir, readFile as readFile2, rename, rm } from "node:fs/promises";
|
|
3911
|
+
import { join as join2, posix, resolve as resolve3, sep as sep2 } from "node:path";
|
|
3893
3912
|
|
|
3894
3913
|
// src/platform/install/tree-integrity.ts
|
|
3895
3914
|
import { createHash } from "node:crypto";
|
|
@@ -3943,12 +3962,21 @@ async function collectFiles(root, directory) {
|
|
|
3943
3962
|
}
|
|
3944
3963
|
|
|
3945
3964
|
// src/platform/install/runtime-release.ts
|
|
3965
|
+
var requiredRuntimeArtifacts = /* @__PURE__ */ new Set([
|
|
3966
|
+
"package.json",
|
|
3967
|
+
"bin/pifleet.mjs",
|
|
3968
|
+
"bin/pifleet-runtime.mjs",
|
|
3969
|
+
"dist/cli.mjs",
|
|
3970
|
+
"dist/runtime.mjs",
|
|
3971
|
+
"dist/sqlite-worker.mjs"
|
|
3972
|
+
]);
|
|
3946
3973
|
async function materializeRuntime(options) {
|
|
3947
3974
|
const sourceRoot = resolve3(options.sourceRoot);
|
|
3975
|
+
const manifestBytes = await readFile2(join2(sourceRoot, "dist", "runtime-manifest.json"));
|
|
3976
|
+
const manifest = await parseRuntimeManifest(manifestBytes, sourceRoot);
|
|
3977
|
+
await verifyRuntime(sourceRoot, manifest);
|
|
3948
3978
|
const applicationRoot = resolve3(options.applicationRoot);
|
|
3949
3979
|
await ensurePrivateDirectory(applicationRoot);
|
|
3950
|
-
const manifestBytes = await readFile2(join2(sourceRoot, "dist", "runtime-manifest.json"));
|
|
3951
|
-
const manifest = JSON.parse(manifestBytes.toString("utf8"));
|
|
3952
3980
|
const manifestHash = createHash2("sha256").update(manifestBytes).digest("hex").slice(0, 16);
|
|
3953
3981
|
const releasesRoot = join2(applicationRoot, "releases");
|
|
3954
3982
|
await ensurePrivateDirectory(releasesRoot);
|
|
@@ -3974,21 +4002,30 @@ async function materializeRuntime(options) {
|
|
|
3974
4002
|
}
|
|
3975
4003
|
await verifyRuntime(staging, manifest);
|
|
3976
4004
|
await chmod(staging, 448);
|
|
3977
|
-
|
|
4005
|
+
try {
|
|
4006
|
+
await rename(staging, destination);
|
|
4007
|
+
} catch (error) {
|
|
4008
|
+
if (!isDestinationRace(error) || !await pathExists(destination)) throw error;
|
|
4009
|
+
await verifyRuntime(destination, manifest);
|
|
4010
|
+
}
|
|
3978
4011
|
return destination;
|
|
3979
|
-
}
|
|
4012
|
+
} finally {
|
|
3980
4013
|
await rm(staging, { recursive: true, force: true });
|
|
3981
|
-
throw error;
|
|
3982
4014
|
}
|
|
3983
4015
|
}
|
|
3984
4016
|
async function verifyRuntime(root, manifest) {
|
|
3985
|
-
const expected = manifest ?? JSON.parse(
|
|
3986
|
-
await readFile2(join2(root, "dist", "runtime-manifest.json"), "utf8")
|
|
3987
|
-
);
|
|
3988
4017
|
const resolvedRoot = resolve3(root);
|
|
4018
|
+
const expected = manifest ?? await parseRuntimeManifest(
|
|
4019
|
+
await readFile2(join2(resolvedRoot, "dist", "runtime-manifest.json")),
|
|
4020
|
+
resolvedRoot
|
|
4021
|
+
);
|
|
4022
|
+
if (manifest !== void 0) await validateRuntimeManifest(manifest, resolvedRoot);
|
|
3989
4023
|
for (const file of expected.files) {
|
|
3990
4024
|
const path2 = resolveInside(resolvedRoot, file.path);
|
|
3991
|
-
const info = await
|
|
4025
|
+
const info = await lstat2(path2);
|
|
4026
|
+
if (info.isSymbolicLink()) {
|
|
4027
|
+
throw new Error(`Runtime artifact ${file.path} must not be a symbolic link`);
|
|
4028
|
+
}
|
|
3992
4029
|
if (!info.isFile() || info.size !== file.bytes) {
|
|
3993
4030
|
throw new Error(`Runtime artifact ${file.path} has changed`);
|
|
3994
4031
|
}
|
|
@@ -4005,13 +4042,100 @@ async function verifyRuntime(root, manifest) {
|
|
|
4005
4042
|
}
|
|
4006
4043
|
}
|
|
4007
4044
|
}
|
|
4045
|
+
async function parseRuntimeManifest(bytes, root) {
|
|
4046
|
+
let candidate;
|
|
4047
|
+
try {
|
|
4048
|
+
candidate = JSON.parse(bytes.toString("utf8"));
|
|
4049
|
+
} catch {
|
|
4050
|
+
throw new Error("Runtime manifest is not valid JSON");
|
|
4051
|
+
}
|
|
4052
|
+
await validateRuntimeManifest(candidate, root);
|
|
4053
|
+
return candidate;
|
|
4054
|
+
}
|
|
4055
|
+
async function validateRuntimeManifest(candidate, root) {
|
|
4056
|
+
if (!isRecord2(candidate) || candidate.schemaVersion !== 2) {
|
|
4057
|
+
throw new Error("Runtime manifest has an unsupported schema version");
|
|
4058
|
+
}
|
|
4059
|
+
if (!isRecord2(candidate.package) || typeof candidate.package.name !== "string" || typeof candidate.package.version !== "string") {
|
|
4060
|
+
throw new Error("Runtime manifest has an invalid package identity");
|
|
4061
|
+
}
|
|
4062
|
+
const packageIdentity = await readPackageIdentity(root);
|
|
4063
|
+
if (candidate.package.name !== packageIdentity.name || candidate.package.version !== packageIdentity.version) {
|
|
4064
|
+
throw new Error("Runtime manifest package identity does not match package.json");
|
|
4065
|
+
}
|
|
4066
|
+
if (!Array.isArray(candidate.files) || !Array.isArray(candidate.trees)) {
|
|
4067
|
+
throw new Error("Runtime manifest has invalid artifact lists");
|
|
4068
|
+
}
|
|
4069
|
+
const filePaths = /* @__PURE__ */ new Set();
|
|
4070
|
+
for (const file of candidate.files) {
|
|
4071
|
+
if (!isRecord2(file) || !isManifestPath(file.path) || !isValidSize(file.bytes) || !isSha256(file.sha256)) {
|
|
4072
|
+
throw new Error("Runtime manifest contains an invalid artifact");
|
|
4073
|
+
}
|
|
4074
|
+
if (filePaths.has(file.path))
|
|
4075
|
+
throw new Error(`Runtime manifest has duplicate path ${file.path}`);
|
|
4076
|
+
filePaths.add(file.path);
|
|
4077
|
+
}
|
|
4078
|
+
for (const required of requiredRuntimeArtifacts) {
|
|
4079
|
+
if (!filePaths.has(required)) {
|
|
4080
|
+
throw new Error(`Runtime manifest is missing required artifact ${required}`);
|
|
4081
|
+
}
|
|
4082
|
+
}
|
|
4083
|
+
const treePaths = /* @__PURE__ */ new Set();
|
|
4084
|
+
for (const tree of candidate.trees) {
|
|
4085
|
+
if (!isRecord2(tree) || !isManifestPath(tree.path) || !isValidSize(tree.files) || !isValidSize(tree.bytes) || !isSha256(tree.sha256)) {
|
|
4086
|
+
throw new Error("Runtime manifest contains an invalid dependency tree");
|
|
4087
|
+
}
|
|
4088
|
+
if (treePaths.has(tree.path))
|
|
4089
|
+
throw new Error(`Runtime manifest has duplicate path ${tree.path}`);
|
|
4090
|
+
treePaths.add(tree.path);
|
|
4091
|
+
}
|
|
4092
|
+
const allPaths = [...filePaths, ...treePaths];
|
|
4093
|
+
for (let index = 0; index < allPaths.length; index += 1) {
|
|
4094
|
+
for (let other = index + 1; other < allPaths.length; other += 1) {
|
|
4095
|
+
const left = allPaths[index];
|
|
4096
|
+
const right = allPaths[other];
|
|
4097
|
+
if (left === right || left.startsWith(`${right}/`) || right.startsWith(`${left}/`)) {
|
|
4098
|
+
throw new Error(`Runtime manifest has overlapping paths ${left} and ${right}`);
|
|
4099
|
+
}
|
|
4100
|
+
}
|
|
4101
|
+
}
|
|
4102
|
+
}
|
|
4103
|
+
async function readPackageIdentity(root) {
|
|
4104
|
+
let candidate;
|
|
4105
|
+
try {
|
|
4106
|
+
candidate = JSON.parse(await readFile2(join2(root, "package.json"), "utf8"));
|
|
4107
|
+
} catch {
|
|
4108
|
+
throw new Error("Runtime package.json is not valid JSON");
|
|
4109
|
+
}
|
|
4110
|
+
if (!isRecord2(candidate) || typeof candidate.name !== "string" || typeof candidate.version !== "string") {
|
|
4111
|
+
throw new Error("Runtime package.json has an invalid identity");
|
|
4112
|
+
}
|
|
4113
|
+
return { name: candidate.name, version: candidate.version };
|
|
4114
|
+
}
|
|
4008
4115
|
function resolveInside(root, path2) {
|
|
4116
|
+
if (!isManifestPath(path2)) throw new Error(`Runtime manifest contains an unsafe path ${path2}`);
|
|
4009
4117
|
const resolved = resolve3(root, path2);
|
|
4010
4118
|
if (!resolved.startsWith(`${root}${sep2}`)) {
|
|
4011
4119
|
throw new Error(`Runtime manifest contains an unsafe path ${path2}`);
|
|
4012
4120
|
}
|
|
4013
4121
|
return resolved;
|
|
4014
4122
|
}
|
|
4123
|
+
function isRecord2(value) {
|
|
4124
|
+
return typeof value === "object" && value !== null;
|
|
4125
|
+
}
|
|
4126
|
+
function isManifestPath(value) {
|
|
4127
|
+
return typeof value === "string" && value.length > 0 && !value.includes("\\") && !value.startsWith("/") && posix.normalize(value) === value && value.split("/").every((part) => part.length > 0 && part !== "." && part !== "..");
|
|
4128
|
+
}
|
|
4129
|
+
function isValidSize(value) {
|
|
4130
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
4131
|
+
}
|
|
4132
|
+
function isSha256(value) {
|
|
4133
|
+
return typeof value === "string" && /^[a-f0-9]{64}$/.test(value);
|
|
4134
|
+
}
|
|
4135
|
+
function isDestinationRace(error) {
|
|
4136
|
+
const code = error.code;
|
|
4137
|
+
return code === "EEXIST" || code === "ENOTEMPTY";
|
|
4138
|
+
}
|
|
4015
4139
|
async function ensurePrivateDirectory(path2) {
|
|
4016
4140
|
await mkdir(path2, { recursive: true, mode: 448 });
|
|
4017
4141
|
const info = await lstat2(path2);
|