@amalgm/shell 0.1.137 → 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 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, and fsyncs each
295
- completed file before rows or reveal are trusted. Links and repository
296
- transports keep their type-specific cache and adapter rails.
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. */
@@ -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";
@@ -394,6 +395,7 @@ export class UserGroundHost {
394
395
  cloudHead: input.cloudHead,
395
396
  deliveryWatermark: input.deliveryWatermark,
396
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,
397
399
  adoptMaterialization: (rootUUID) => {
398
400
  const settled = this.adoptCloudDeliveryBaseline(identity, input.deliveryWatermark, rootUUID);
399
401
  this.applyRail?.recordsAvailable();
@@ -523,6 +525,7 @@ export class UserGroundHost {
523
525
  cloudHead: intent.cloudHead,
524
526
  deliveryWatermark: intent.deliveryWatermark,
525
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,
526
529
  adoptMaterialization: (rootUUID) => {
527
530
  this.adoptCloudDeliveryBaseline(identity, intent.deliveryWatermark, rootUUID);
528
531
  this.applyRail?.recordsAvailable();
@@ -6080,7 +6083,7 @@ function installCloudWorkspaceReferences(input) {
6080
6083
  return references;
6081
6084
  }
6082
6085
  async function installCloudWorkspace(input) {
6083
- 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;
6084
6087
  const existing = readGroundClosureRows(database, workspace.uuid);
6085
6088
  const existingBoundary = existing.find((row) => row.uuid === workspace.uuid);
6086
6089
  const pending = readWorkspaceAddIntent(database, workspace.uuid);
@@ -6236,11 +6239,19 @@ async function installCloudWorkspace(input) {
6236
6239
  ? join(intent.destinationPath, ...row.relativePath.split("/"))
6237
6240
  : intent.destinationPath,
6238
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);
6239
6249
  // The milestone's duration spans the whole materialization; its legs say
6240
6250
  // where that wall time went after the content set itself.
6241
6251
  await onStage?.("materialization-verified", {
6242
6252
  ...materialized.timing,
6243
- graphMs: Math.round(performance.now() - graphStarted),
6253
+ graphMs,
6254
+ durabilityBarrierMs: Math.round(performance.now() - barrierStarted),
6244
6255
  repositories: materialized.repositories.length,
6245
6256
  });
6246
6257
  persistRows(database, identity, intent.resourceId, intent.workspaceId, rows);