@amalgm/shell 0.1.133 → 0.1.135

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.
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Register's fused upload — the walk's one read is also the upload.
3
+ *
4
+ * A cold Register used to read every source byte twice: once for identity,
5
+ * once for transport. This rail rides the capture instead. Small files hand
6
+ * their already-read bytes to a bounded cargo stream; each segment asks the
7
+ * authority's seal inventory first, so a retried or crashed register ships
8
+ * only what is not yet sealed. Large files stream verified blocks during
9
+ * the single capture read and seal their manifest last, behind the same
10
+ * fingerprint law as the classic capture.
11
+ *
12
+ * Fusion is an opportunity, never a dependency. The upload stage still
13
+ * plans afterwards from the authority's seal inventory and remains the
14
+ * guarantor: anything this rail could not fuse — a churned file, a refused
15
+ * wave, a dead wire — uploads there exactly as before. Any transport
16
+ * failure disables fusion for the rest of the walk and is never a walk
17
+ * failure; ground failures keep the walk's own churn law. Git repositories
18
+ * stay on their Git-native rail and never enter this module.
19
+ */
20
+ import { lstat } from "node:fs/promises";
21
+ import { openCargoStream, } from "@amalgm/live";
22
+ import { captureContentFileStream, sameCaptureFingerprint, } from "./content-cache-host.js";
23
+ /** A wire refusal inside a fused capture. Distinguished from ground errors
24
+ * so the caller can disable fusion without disturbing the walk's churn law. */
25
+ class FusionTransportError extends Error {
26
+ constructor(cause) {
27
+ super(cause instanceof Error ? cause.message : String(cause), { cause });
28
+ }
29
+ }
30
+ export class RegisterUploadFusion {
31
+ ports;
32
+ stream;
33
+ admitted = new Set();
34
+ shipped = [];
35
+ disabledBy = null;
36
+ smallArtifacts = 0;
37
+ smallBytes = 0;
38
+ cargoSegments = 0;
39
+ sealedSkipped = 0;
40
+ largeArtifacts = 0;
41
+ largeBytes = 0;
42
+ constructor(ports) {
43
+ this.ports = ports;
44
+ this.stream = openCargoStream({
45
+ send: async (segment) => {
46
+ // Ask before shipping: a re-walked or previously crashed register
47
+ // recaptures files whose artifacts may already be sealed whole.
48
+ const sealed = await ports.sealedContentHashes(segment.items.map((item) => item.contentHash));
49
+ const fresh = segment.items.filter((item) => !sealed.has(item.contentHash));
50
+ this.sealedSkipped += segment.items.length - fresh.length;
51
+ if (fresh.length === 0)
52
+ return [...sealed];
53
+ const answered = await ports.sendCargo(fresh.map((item) => ({
54
+ content_hash: item.contentHash,
55
+ bytes: item.bytes,
56
+ chunks: item.manifest.chunks,
57
+ })), Buffer.concat(fresh.map((item) => item.payload)));
58
+ this.cargoSegments += 1;
59
+ this.smallArtifacts += fresh.length;
60
+ for (const item of fresh)
61
+ this.smallBytes += item.bytes;
62
+ return [...answered, ...sealed];
63
+ },
64
+ });
65
+ }
66
+ get enabled() {
67
+ return this.disabledBy === null;
68
+ }
69
+ /** Hand one already-captured small artifact's bytes to the cargo rail.
70
+ * Waits only for window room — backpressure, never durability — and a
71
+ * failed stream disables fusion instead of failing the capture. */
72
+ async addSmall(absolutePath, fingerprint, captured, payload) {
73
+ if (this.disabledBy !== null)
74
+ return;
75
+ if (this.admitted.has(captured.contentHash)) {
76
+ // Equal bytes share one upload identity, but every physical path still
77
+ // owns its own capture fingerprint. A duplicate path moving after the
78
+ // read must invalidate the attempt just like the path that supplied the
79
+ // shared cargo.
80
+ this.shipped.push({ absolutePath, fingerprint });
81
+ return;
82
+ }
83
+ this.admitted.add(captured.contentHash);
84
+ try {
85
+ await this.stream.add({
86
+ contentHash: captured.contentHash,
87
+ bytes: captured.bytes,
88
+ manifest: captured.manifest,
89
+ payload,
90
+ });
91
+ this.shipped.push({ absolutePath, fingerprint });
92
+ }
93
+ catch (error) {
94
+ this.disable(error);
95
+ }
96
+ }
97
+ /** Capture one large file by streaming its single read to the authority.
98
+ * Ground churn propagates unchanged into the walk's retry law; a wire
99
+ * failure disables fusion and answers null so the caller captures the
100
+ * classic way and the upload stage ships it. */
101
+ async captureLarge(sourceFile, expected) {
102
+ if (this.disabledBy !== null)
103
+ return null;
104
+ const guard = (work) => work.catch((error) => {
105
+ throw new FusionTransportError(error);
106
+ });
107
+ try {
108
+ const captured = await captureContentFileStream(sourceFile, expected, {
109
+ putBlocks: (chunks, bytes) => guard(this.ports.putBlocks(chunks, bytes)),
110
+ sealManifest: (manifest) => guard(this.ports.sealManifest(manifest)),
111
+ });
112
+ this.largeArtifacts += 1;
113
+ this.largeBytes += captured.bytes;
114
+ this.shipped.push({ absolutePath: sourceFile, fingerprint: expected });
115
+ return captured;
116
+ }
117
+ catch (error) {
118
+ if (!(error instanceof FusionTransportError))
119
+ throw error;
120
+ this.disable(error.cause);
121
+ return null;
122
+ }
123
+ }
124
+ /** Prove every fused source still stands exactly as it was captured — the
125
+ * proof the classic upload's re-read gave for free, at stat cost only.
126
+ * Answers the first moved path, or null while the fused ground stands.
127
+ * The caller turns a moved path into its ordinary stale-capture re-walk;
128
+ * sealed content stays harmlessly unreferenced until the re-walk names
129
+ * what stands there now. */
130
+ async movedGround() {
131
+ for (const { absolutePath, fingerprint } of this.shipped) {
132
+ let current;
133
+ try {
134
+ current = await lstat(absolutePath);
135
+ }
136
+ catch {
137
+ return absolutePath;
138
+ }
139
+ if (!sameCaptureFingerprint(fingerprint, current))
140
+ return absolutePath;
141
+ }
142
+ return null;
143
+ }
144
+ /** Settle the fused rail: every admitted segment ships or the failure is
145
+ * recorded as this fusion's disabling reason. Never throws — the upload
146
+ * stage re-plans from the seal inventory, so an incomplete fusion only
147
+ * means it uploads more. */
148
+ async flush() {
149
+ try {
150
+ await this.stream.flush();
151
+ }
152
+ catch (error) {
153
+ this.disable(error);
154
+ }
155
+ return {
156
+ smallArtifacts: this.smallArtifacts,
157
+ smallBytes: this.smallBytes,
158
+ cargoSegments: this.cargoSegments,
159
+ sealedSkipped: this.sealedSkipped,
160
+ largeArtifacts: this.largeArtifacts,
161
+ largeBytes: this.largeBytes,
162
+ disabledReason: this.disabledBy,
163
+ };
164
+ }
165
+ disable(error) {
166
+ this.disabledBy ??= error instanceof Error ? error.message : String(error);
167
+ }
168
+ }
169
+ //# sourceMappingURL=register-upload-fusion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register-upload-fusion.js","sourceRoot":"","sources":["../src/register-upload-fusion.ts"],"names":[],"mappings":"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"}
@@ -225,12 +225,21 @@ export declare class UserGroundHost {
225
225
  * timings — verification, storage, sealing — which fold into the ledger
226
226
  * as a decomposition of this round trip's `wireMs`. */
227
227
  private putCargo;
228
+ /** Store one wave of provisional content blocks, addressed by their own
229
+ * digests and bound to no artifact. Resolving means every block is
230
+ * durable at the authority. Answers the wire bytes actually sent. */
231
+ private putContentBlocks;
232
+ /** Seal one artifact's manifest. The authority re-proves every named
233
+ * block durable before anything becomes visible, so this frame is only
234
+ * lawful after the complete file went over the wire. */
235
+ private putContentManifest;
228
236
  private uploadSnapshotContent;
229
237
  /** Download a set of immutable artifacts straight from content storage.
230
- * The set is the primitive: every storage object the set still needs —
231
- * manifests and blocks alike — shares one wide lane bound, so no artifact
232
- * serializes behind another's plan or seal. Each object is still its own
233
- * storage request. A lone artifact is a set of one. */
238
+ * The set is the primitive and the plan is physical: the engine resolves
239
+ * the whole want-list in two planned waves — manifests grouped by the
240
+ * pack that holds them, then blocks the same way — so all members of one
241
+ * pack collapse into a few broad ranged reads while standalone objects
242
+ * stay one read each. A lone artifact is a set of one. */
234
243
  private downloadContentSet;
235
244
  private downloadContent;
236
245
  private ensureWatchers;
@@ -251,9 +260,13 @@ export declare class UserGroundHost {
251
260
  * preparation names the slow part instead of one opaque number. */
252
261
  private finishPreparation;
253
262
  /** Which of these heads does the authority hold sealed under the current
254
- * content contract? Batched so a whole workspace costs a handful of
255
- * frames; the answer is the only permission to skip an upload. */
256
- private sealedContentHashes;
263
+ * content contract — and which of the sealed ones are packed, their whole
264
+ * closure inside immutable packs? Batched so a whole workspace costs a
265
+ * handful of frames. `sealed` is the only permission to skip an upload
266
+ * for correctness; `packed` is placement knowledge only. The packed
267
+ * answer is a protocol 16 capability and is required: a missing field
268
+ * means an older gateway, and this machine refuses to guess placement. */
269
+ private sealInventory;
257
270
  private publishPendingSnapshots;
258
271
  /** Seal this publication's graph pages. Pages are immutable, content
259
272
  * addressed, and already in memory, so the authority's seal inventory alone