@amalgm/shell 0.1.136 → 0.1.138
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/PURPOSE.md +7 -3
- package/dist/durability-barrier.d.ts +22 -0
- package/dist/durability-barrier.js +72 -0
- package/dist/durability-barrier.js.map +1 -0
- package/dist/user-ground-host.d.ts +4 -0
- package/dist/user-ground-host.js +20 -2
- package/dist/user-ground-host.js.map +1 -1
- package/dist/workspace-add-staging.d.ts +14 -9
- package/dist/workspace-add-staging.js +14 -23
- package/dist/workspace-add-staging.js.map +1 -1
- package/npm-shrinkwrap.json +6 -6
- package/package.json +2 -2
package/PURPOSE.md
CHANGED
|
@@ -291,9 +291,13 @@ shell only assembles.
|
|
|
291
291
|
destinations, and an interrupted Add may retain them only with disposable
|
|
292
292
|
chunk evidence beside the ground database. Resume rereads and re-hashes
|
|
293
293
|
every retained chunk against the pinned manifest, downloads every missing
|
|
294
|
-
or corrupt chunk, proves the ordered whole-artifact hash
|
|
295
|
-
|
|
296
|
-
|
|
294
|
+
or corrupt chunk, and proves the ordered whole-artifact hash. Durability
|
|
295
|
+
is one explicit barrier over the whole staged tree — every file,
|
|
296
|
+
duplicate, and repository — that must succeed before entity rows are
|
|
297
|
+
committed or ground is revealed; no staged file is fsynced on its own. A
|
|
298
|
+
failed barrier leaves the intent retained, no rows committed, no visible
|
|
299
|
+
destination, and staging re-proven as untrusted on retry. Links and
|
|
300
|
+
repository transports keep their type-specific cache and adapter rails.
|
|
297
301
|
One recovered Add intent that cannot currently be satisfied remains
|
|
298
302
|
durable and reports its exact failure, but it cannot hold unrelated machine
|
|
299
303
|
readiness or other Add responsibilities hostage.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The set-level durability law for Files Add.
|
|
3
|
+
*
|
|
4
|
+
* Staged content is verified while it streams and is never trusted until it
|
|
5
|
+
* is re-proven, so no staged file needs to be durable before the next one is
|
|
6
|
+
* written. What must hold is one barrier: after every file, duplicate, and
|
|
7
|
+
* repository has been materialized, the whole staged tree is durable before
|
|
8
|
+
* entity rows are committed or ground is revealed. A failed barrier fails the
|
|
9
|
+
* Add with its intent retained — nothing committed, nothing visible — and the
|
|
10
|
+
* retry re-proves the staging as untrusted substrate.
|
|
11
|
+
*
|
|
12
|
+
* Linux settles the filesystem holding the staging root in one syncfs(2) —
|
|
13
|
+
* coreutils `sync -f` — so twelve thousand small files cost one journal
|
|
14
|
+
* flush rather than one commit each serialized through the journal. Node
|
|
15
|
+
* exposes no syncfs, so that is one child process. Elsewhere every regular
|
|
16
|
+
* file and directory under the root is flushed through its own handle:
|
|
17
|
+
* libuv's fsync is F_FULLFSYNC on macOS and FlushFileBuffers on Windows,
|
|
18
|
+
* where NTFS's own log carries the directory entries with their files. A
|
|
19
|
+
* directory-only fsync never guarantees file contents and is never used.
|
|
20
|
+
*/
|
|
21
|
+
export type DurabilityBarrier = (root: string) => Promise<void>;
|
|
22
|
+
export declare const durabilityBarrier: DurabilityBarrier;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The set-level durability law for Files Add.
|
|
3
|
+
*
|
|
4
|
+
* Staged content is verified while it streams and is never trusted until it
|
|
5
|
+
* is re-proven, so no staged file needs to be durable before the next one is
|
|
6
|
+
* written. What must hold is one barrier: after every file, duplicate, and
|
|
7
|
+
* repository has been materialized, the whole staged tree is durable before
|
|
8
|
+
* entity rows are committed or ground is revealed. A failed barrier fails the
|
|
9
|
+
* Add with its intent retained — nothing committed, nothing visible — and the
|
|
10
|
+
* retry re-proves the staging as untrusted substrate.
|
|
11
|
+
*
|
|
12
|
+
* Linux settles the filesystem holding the staging root in one syncfs(2) —
|
|
13
|
+
* coreutils `sync -f` — so twelve thousand small files cost one journal
|
|
14
|
+
* flush rather than one commit each serialized through the journal. Node
|
|
15
|
+
* exposes no syncfs, so that is one child process. Elsewhere every regular
|
|
16
|
+
* file and directory under the root is flushed through its own handle:
|
|
17
|
+
* libuv's fsync is F_FULLFSYNC on macOS and FlushFileBuffers on Windows,
|
|
18
|
+
* where NTFS's own log carries the directory entries with their files. A
|
|
19
|
+
* directory-only fsync never guarantees file contents and is never used.
|
|
20
|
+
*/
|
|
21
|
+
import { execFile } from "node:child_process";
|
|
22
|
+
import { constants } from "node:fs";
|
|
23
|
+
import { lstat, open, readdir } from "node:fs/promises";
|
|
24
|
+
import { join } from "node:path";
|
|
25
|
+
import { promisify } from "node:util";
|
|
26
|
+
import { boundedForEach } from "./bounded-for-each.js";
|
|
27
|
+
const execFileAsync = promisify(execFile);
|
|
28
|
+
/** Handles in flight while a tree is flushed entry by entry. */
|
|
29
|
+
const FLUSH_CONCURRENCY = 16;
|
|
30
|
+
async function syncFilesystem(root) {
|
|
31
|
+
await execFileAsync("sync", ["-f", root]);
|
|
32
|
+
}
|
|
33
|
+
async function flushEntry(path) {
|
|
34
|
+
const handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
35
|
+
try {
|
|
36
|
+
await handle.sync();
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
await handle.close();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async function syncTree(root) {
|
|
43
|
+
const files = [];
|
|
44
|
+
const directories = [root];
|
|
45
|
+
for (let index = 0; index < directories.length; index += 1) {
|
|
46
|
+
const directory = directories[index];
|
|
47
|
+
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
|
48
|
+
const path = join(directory, entry.name);
|
|
49
|
+
if (entry.isDirectory())
|
|
50
|
+
directories.push(path);
|
|
51
|
+
else if (entry.isFile())
|
|
52
|
+
files.push(path);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const flushed = process.platform === "win32" ? files : [...files, ...directories];
|
|
56
|
+
await boundedForEach(flushed, FLUSH_CONCURRENCY, flushEntry);
|
|
57
|
+
}
|
|
58
|
+
const platformBarrier = process.platform === "linux" ? syncFilesystem : syncTree;
|
|
59
|
+
export const durabilityBarrier = async (root) => {
|
|
60
|
+
try {
|
|
61
|
+
const stats = await lstat(root);
|
|
62
|
+
if (!stats.isDirectory() || stats.isSymbolicLink()) {
|
|
63
|
+
throw new Error("staging root is not a physical directory");
|
|
64
|
+
}
|
|
65
|
+
await platformBarrier(root);
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
69
|
+
throw new Error(`durability barrier failed for ${root}: ${detail}`, { cause: error });
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
//# sourceMappingURL=durability-barrier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"durability-barrier.js","sourceRoot":"","sources":["../src/durability-barrier.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,6 +3,7 @@ import { type EntityType, type FilesAddPorts, type FilesRegisterPorts, type User
|
|
|
3
3
|
import Database from "better-sqlite3";
|
|
4
4
|
import { type FilesStageObserver } from "./files-observability.js";
|
|
5
5
|
import { type EntityApplyStageEvidence } from "./entity-apply-host.js";
|
|
6
|
+
import { type DurabilityBarrier } from "./durability-barrier.js";
|
|
6
7
|
import { type OpenNativeWatch, type WatchCoverageEvidence, type WatchHealth } from "./watching/index.js";
|
|
7
8
|
interface GroundRow {
|
|
8
9
|
readonly resourceId: string;
|
|
@@ -73,6 +74,9 @@ export interface UserGroundHostOptions {
|
|
|
73
74
|
readonly onWatchHealth?: (health: WatchHealth) => void;
|
|
74
75
|
/** Host injection used by platform certification and failure examinations. */
|
|
75
76
|
readonly openWatch?: OpenNativeWatch;
|
|
77
|
+
/** The one durability barrier Add takes over its staged tree before rows or
|
|
78
|
+
* reveal; injected only by failure examinations. */
|
|
79
|
+
readonly durabilityBarrier?: DurabilityBarrier;
|
|
76
80
|
/** Root-scoped Detect evidence for product diagnostics and examinations. */
|
|
77
81
|
readonly onDetectScan?: (evidence: DetectScanEvidence) => void;
|
|
78
82
|
/** Transient Apply progress; durable responsibility remains in SQLite. */
|
package/dist/user-ground-host.js
CHANGED
|
@@ -15,6 +15,7 @@ import { ContentCacheDownloadSet, captureContentFile, contentBlockCollector, has
|
|
|
15
15
|
import { RegisterUploadFusion } from "./register-upload-fusion.js";
|
|
16
16
|
import { StagedContentSet, addStagingEvidenceDir, clearAddStagingEvidence, } from "./workspace-add-staging.js";
|
|
17
17
|
import { boundedForEach } from "./bounded-for-each.js";
|
|
18
|
+
import { durabilityBarrier as platformDurabilityBarrier, } from "./durability-barrier.js";
|
|
18
19
|
import { DirectContentPlane } from "./content-direct.js";
|
|
19
20
|
import { encodeContentWireBytes } from "./content-wire-codec.js";
|
|
20
21
|
import { TransferLegs } from "./transfer-instrumentation.js";
|
|
@@ -44,6 +45,12 @@ const CARGO_SEGMENT_LOAD_CONCURRENCY = 8;
|
|
|
44
45
|
* few broad ranged reads. Memory is bounded by the byte laws below, never by
|
|
45
46
|
* this count. */
|
|
46
47
|
const DIRECT_OBJECT_DOWNLOAD_CONCURRENCY = CONTENT_REFS_BATCH_OBJECTS;
|
|
48
|
+
/** Independent destination writers. Pack reads are intentionally few, but
|
|
49
|
+
* one pack may contain thousands of unrelated files; those files must not
|
|
50
|
+
* inherit the pack's request count as their filesystem concurrency. Sixteen
|
|
51
|
+
* handles are bounded on a 1 GiB runtime and enough to hide metadata latency
|
|
52
|
+
* behind the volume's byte throughput. */
|
|
53
|
+
const DIRECT_CONTENT_STORE_CONCURRENCY = 16;
|
|
47
54
|
// Opening one protected object transiently retains the fetched envelope,
|
|
48
55
|
// WebCrypto's ciphertext-with-tag input, and the plaintext. Bound plaintext
|
|
49
56
|
// to two full blocks so those three representations stay below 96 MiB even
|
|
@@ -388,6 +395,7 @@ export class UserGroundHost {
|
|
|
388
395
|
cloudHead: input.cloudHead,
|
|
389
396
|
deliveryWatermark: input.deliveryWatermark,
|
|
390
397
|
readContent: (artifacts, staged) => this.downloadContentSet(privateEntityResourceId(identity.userId, sha256Hex), this.cacheDir(identity), artifacts.map((artifact) => ({ artifact })), 10, { journey: "add", workspaceId: input.workspace.uuid }, staged),
|
|
398
|
+
durabilityBarrier: this.options.durabilityBarrier ?? platformDurabilityBarrier,
|
|
391
399
|
adoptMaterialization: (rootUUID) => {
|
|
392
400
|
const settled = this.adoptCloudDeliveryBaseline(identity, input.deliveryWatermark, rootUUID);
|
|
393
401
|
this.applyRail?.recordsAvailable();
|
|
@@ -517,6 +525,7 @@ export class UserGroundHost {
|
|
|
517
525
|
cloudHead: intent.cloudHead,
|
|
518
526
|
deliveryWatermark: intent.deliveryWatermark,
|
|
519
527
|
readContent: (artifacts, staged) => this.downloadContentSet(intent.resourceId, this.cacheDir(identity), artifacts.map((artifact) => ({ artifact })), 10, { journey: "add", workspaceId: intent.workspaceId }, staged),
|
|
528
|
+
durabilityBarrier: this.options.durabilityBarrier ?? platformDurabilityBarrier,
|
|
520
529
|
adoptMaterialization: (rootUUID) => {
|
|
521
530
|
this.adoptCloudDeliveryBaseline(identity, intent.deliveryWatermark, rootUUID);
|
|
522
531
|
this.applyRail?.recordsAvailable();
|
|
@@ -1962,6 +1971,7 @@ export class UserGroundHost {
|
|
|
1962
1971
|
},
|
|
1963
1972
|
}, {
|
|
1964
1973
|
concurrency: DIRECT_OBJECT_DOWNLOAD_CONCURRENCY,
|
|
1974
|
+
storeConcurrency: DIRECT_CONTENT_STORE_CONCURRENCY,
|
|
1965
1975
|
maxInFlightBytes: DIRECT_OBJECT_DOWNLOAD_MAX_IN_FLIGHT_BYTES,
|
|
1966
1976
|
});
|
|
1967
1977
|
const content = new Map();
|
|
@@ -6073,7 +6083,7 @@ function installCloudWorkspaceReferences(input) {
|
|
|
6073
6083
|
return references;
|
|
6074
6084
|
}
|
|
6075
6085
|
async function installCloudWorkspace(input) {
|
|
6076
|
-
const { identity, userRoot, database, cacheDir, bindingDir, resourceId, workspace, reference, references, records, destinationParent, cloudHead, deliveryWatermark, readContent, onStage, } = input;
|
|
6086
|
+
const { identity, userRoot, database, cacheDir, bindingDir, resourceId, workspace, reference, references, records, destinationParent, cloudHead, deliveryWatermark, readContent, durabilityBarrier, onStage, } = input;
|
|
6077
6087
|
const existing = readGroundClosureRows(database, workspace.uuid);
|
|
6078
6088
|
const existingBoundary = existing.find((row) => row.uuid === workspace.uuid);
|
|
6079
6089
|
const pending = readWorkspaceAddIntent(database, workspace.uuid);
|
|
@@ -6229,11 +6239,19 @@ async function installCloudWorkspace(input) {
|
|
|
6229
6239
|
? join(intent.destinationPath, ...row.relativePath.split("/"))
|
|
6230
6240
|
: intent.destinationPath,
|
|
6231
6241
|
}));
|
|
6242
|
+
const graphMs = Math.round(performance.now() - graphStarted);
|
|
6243
|
+
// Every staged byte was proven as it streamed; none of it counts as durable
|
|
6244
|
+
// until the whole tree — files, duplicates, repositories — passes one
|
|
6245
|
+
// barrier. Rows and reveal follow that barrier, never precede it; a failed
|
|
6246
|
+
// barrier leaves the intent retained and the staging untrusted.
|
|
6247
|
+
const barrierStarted = performance.now();
|
|
6248
|
+
await durabilityBarrier(intent.stagingPath);
|
|
6232
6249
|
// The milestone's duration spans the whole materialization; its legs say
|
|
6233
6250
|
// where that wall time went after the content set itself.
|
|
6234
6251
|
await onStage?.("materialization-verified", {
|
|
6235
6252
|
...materialized.timing,
|
|
6236
|
-
graphMs
|
|
6253
|
+
graphMs,
|
|
6254
|
+
durabilityBarrierMs: Math.round(performance.now() - barrierStarted),
|
|
6237
6255
|
repositories: materialized.repositories.length,
|
|
6238
6256
|
});
|
|
6239
6257
|
persistRows(database, identity, intent.resourceId, intent.workspaceId, rows);
|