@amalgm/shell 0.1.20 → 0.1.22
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 +26 -17
- package/dist/cli.js +5 -4
- package/dist/cli.js.map +1 -1
- package/dist/content-cache-host.d.ts +52 -0
- package/dist/content-cache-host.js +235 -0
- package/dist/content-cache-host.js.map +1 -0
- package/dist/files-register-host.d.ts +13 -0
- package/dist/files-register-host.js +134 -0
- package/dist/files-register-host.js.map +1 -0
- package/dist/machine-host.d.ts +4 -0
- package/dist/machine-host.js +16 -1
- package/dist/machine-host.js.map +1 -1
- package/dist/user-ground-host.js +95 -196
- package/dist/user-ground-host.js.map +1 -1
- package/package.json +2 -2
package/PURPOSE.md
CHANGED
|
@@ -85,19 +85,20 @@ shell only assembles.
|
|
|
85
85
|
tree remains intact. A failed install leaves a clean retry boundary, never
|
|
86
86
|
a partial portable tree.
|
|
87
87
|
|
|
88
|
-
16. **The
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
88
|
+
16. **The process that owns Watch owns its shutdown.** A running host flushes
|
|
89
|
+
every filesystem event Watch observed, closes coverage, checks once more
|
|
90
|
+
for queued events, and only then tears down its wire. The one-shot
|
|
91
|
+
`files register` command is different by design: it starts no Watch, SQLite,
|
|
92
|
+
or cloud work; it renders the durable reference and returns. A running host
|
|
93
|
+
observes that declaration, while a later resume covers an interval with no
|
|
94
|
+
running host. Closing an unchanged watcher does not invent a full scan. A
|
|
95
|
+
gateway response belongs to the exact wire that delivered its request; if
|
|
96
|
+
that source closes first, the late response is discarded cleanly rather
|
|
97
|
+
than sent through a replacement or closed wire. Closing is a one-way
|
|
98
|
+
lifecycle boundary: an in-flight rescan may finish its durable work, but it
|
|
99
|
+
can never recreate watcher handles afterward. Each tracked root owns one
|
|
100
|
+
recursive Node watcher, not one handle per descendant directory; the Node
|
|
101
|
+
version floor supplies this primitive on macOS, Linux, and Windows.
|
|
101
102
|
|
|
102
103
|
17. **Independent content may travel concurrently; authority remains atomic.**
|
|
103
104
|
The host bounds parallel uploads for small immutable objects and processes
|
|
@@ -117,9 +118,9 @@ shell only assembles.
|
|
|
117
118
|
decides which entity types have artifacts, which chunks are missing, when
|
|
118
119
|
an upload manifest may commit, and when a download is verified. The Shell
|
|
119
120
|
adapter supplies seekable immutable-cache reads, the authority inventory
|
|
120
|
-
probe, bounded R2 requests, and a machine-local content-addressed
|
|
121
|
-
cache; it never scans, registers,
|
|
122
|
-
operations.
|
|
121
|
+
probe, bounded R2 requests, and a machine-local content-addressed artifact
|
|
122
|
+
cache with reusable block locators; it never scans, registers,
|
|
123
|
+
materializes, or publishes inside those operations.
|
|
123
124
|
Control and integrity metadata remains JSON, while immutable block and Git
|
|
124
125
|
parcel bytes use the authenticated binary WebSocket rail without base64.
|
|
125
126
|
|
|
@@ -143,7 +144,7 @@ shell only assembles.
|
|
|
143
144
|
untracked leaves. Git ignore never becomes Amalgm enrollment policy.
|
|
144
145
|
|
|
145
146
|
23. **Cold add never rereads a repository graph.** Download verifies and
|
|
146
|
-
|
|
147
|
+
seals each content-addressed transport to a file, streams its Git pack
|
|
147
148
|
range directly into `index-pack`, and derives local SQLite rows from the
|
|
148
149
|
transported UUID/type/payload identity map plus filesystem stats. It does
|
|
149
150
|
not buffer the complete repository parcel or reclassify checked-out files.
|
|
@@ -154,6 +155,14 @@ shell only assembles.
|
|
|
154
155
|
recomputed from authoritative local ground; they never become a legacy
|
|
155
156
|
compatibility requirement and never prevent the machine from resuming.
|
|
156
157
|
|
|
158
|
+
25. **Cold transfer performs each byte operation once.** Registration stores
|
|
159
|
+
capture-derived block evidence beside the sealed source, so upload reads
|
|
160
|
+
that source once to send it. Download places verified network blocks
|
|
161
|
+
directly into one resumable artifact, derives the whole hash from those
|
|
162
|
+
in-flight buffers, and then hands the sealed file to the registered type's
|
|
163
|
+
adapter. Per-block duplicates followed by a second assembled file are not
|
|
164
|
+
a machine-cache design.
|
|
165
|
+
|
|
157
166
|
## Relationship to amalgm-engine
|
|
158
167
|
|
|
159
168
|
Stage 1 cut the bricks out of the engine. Stage 2 proves them here, composed.
|
package/dist/cli.js
CHANGED
|
@@ -64,11 +64,12 @@ export async function runCli(argv = process.argv) {
|
|
|
64
64
|
if (parsed.positionals.length !== 2) {
|
|
65
65
|
throw new Error(`Usage: amalgm files ${filesCommand} <${filesCommand === "register" ? "path" : "workspace-id"}>`);
|
|
66
66
|
}
|
|
67
|
-
await host.resume();
|
|
68
|
-
const ports = host.filesCommandPorts();
|
|
69
67
|
const result = filesCommand === "register"
|
|
70
|
-
? await registerFilesWorkspace(parsed.positionals[1],
|
|
71
|
-
: await
|
|
68
|
+
? await registerFilesWorkspace(parsed.positionals[1], await host.filesRegisterPorts())
|
|
69
|
+
: await (async () => {
|
|
70
|
+
await host.resume();
|
|
71
|
+
return addFilesWorkspace(parsed.positionals[1], process.cwd(), host.filesCommandPorts().add);
|
|
72
|
+
})();
|
|
72
73
|
process.stdout.write(`AMALGM_FILES ${JSON.stringify({ command: filesCommand, ...result })}\n`);
|
|
73
74
|
return 0;
|
|
74
75
|
}
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Machine-local immutable content cache.
|
|
3
|
+
*
|
|
4
|
+
* Network blocks land directly at their final offset in one partial artifact.
|
|
5
|
+
* Small marker files make an interrupted write resumable, and sealed-artifact
|
|
6
|
+
* block locators let another file reuse equal bytes without storing a second
|
|
7
|
+
* copy of every block. The portable transfer law remains in @amalgm/live;
|
|
8
|
+
* this module owns only Node filesystem effects.
|
|
9
|
+
*/
|
|
10
|
+
import { type ContentManifest, type TransferArtifact } from "@amalgm/live";
|
|
11
|
+
export interface SealedContentFile {
|
|
12
|
+
readonly file: string;
|
|
13
|
+
readonly bytes: number;
|
|
14
|
+
readonly contentHash: string;
|
|
15
|
+
}
|
|
16
|
+
/** One artifact download. Calls may write different block indexes in
|
|
17
|
+
* parallel; sealing is legal only after the portable download operation has
|
|
18
|
+
* awaited them all. */
|
|
19
|
+
export declare class ContentCacheDownload {
|
|
20
|
+
private readonly cacheDir;
|
|
21
|
+
readonly artifact: TransferArtifact;
|
|
22
|
+
readonly manifest: ContentManifest;
|
|
23
|
+
private readonly finalFile;
|
|
24
|
+
private readonly temporaryFile;
|
|
25
|
+
private readonly markerDir;
|
|
26
|
+
private readonly offsets;
|
|
27
|
+
private readonly complete;
|
|
28
|
+
private readonly wholeHash;
|
|
29
|
+
private readonly pendingHash;
|
|
30
|
+
private nextHashIndex;
|
|
31
|
+
private handle;
|
|
32
|
+
private sealed;
|
|
33
|
+
constructor(cacheDir: string, artifact: TransferArtifact, manifest: ContentManifest);
|
|
34
|
+
private partialHandle;
|
|
35
|
+
private acceptForWholeHash;
|
|
36
|
+
private markerFile;
|
|
37
|
+
/** Verify and stage one already-local block. A false result means the
|
|
38
|
+
* authority must provide it. */
|
|
39
|
+
hasChunk(index: number): Promise<boolean>;
|
|
40
|
+
/** Persist one block already verified by @amalgm/live. */
|
|
41
|
+
putChunk(index: number, bytes: Uint8Array): Promise<void>;
|
|
42
|
+
/** Commit the complete artifact and its tiny reusable-block index. Cold
|
|
43
|
+
* downloads verify the whole hash from the received buffers, so sealing
|
|
44
|
+
* performs no second complete disk read. */
|
|
45
|
+
seal(): Promise<SealedContentFile>;
|
|
46
|
+
/** Keep partial bytes and markers for the next attempt, but release the
|
|
47
|
+
* descriptor held by this process. */
|
|
48
|
+
close(): Promise<void>;
|
|
49
|
+
}
|
|
50
|
+
/** Hash one file without buffering it. Used only when evidence predates the
|
|
51
|
+
* sealed-manifest cache. */
|
|
52
|
+
export declare function hashContentFile(file: string): Promise<string>;
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Machine-local immutable content cache.
|
|
3
|
+
*
|
|
4
|
+
* Network blocks land directly at their final offset in one partial artifact.
|
|
5
|
+
* Small marker files make an interrupted write resumable, and sealed-artifact
|
|
6
|
+
* block locators let another file reuse equal bytes without storing a second
|
|
7
|
+
* copy of every block. The portable transfer law remains in @amalgm/live;
|
|
8
|
+
* this module owns only Node filesystem effects.
|
|
9
|
+
*/
|
|
10
|
+
import { createHash } from "node:crypto";
|
|
11
|
+
import { chmodSync, createReadStream, existsSync, readFileSync, renameSync, rmSync, statSync, } from "node:fs";
|
|
12
|
+
import { open } from "node:fs/promises";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { stableJson, } from "@amalgm/live";
|
|
15
|
+
import { atomicWrite, ensurePrivateDir } from "./filesystem.js";
|
|
16
|
+
const sha256Hex = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
17
|
+
const contentFile = (cacheDir, contentHash) => join(cacheDir, `${contentHash}.bin`);
|
|
18
|
+
const manifestFile = (cacheDir, contentHash) => join(cacheDir, "manifests", `${contentHash}.json`);
|
|
19
|
+
const partialFile = (cacheDir, contentHash) => join(cacheDir, "partials", `${contentHash}.bin`);
|
|
20
|
+
const partialMarkers = (cacheDir, contentHash) => join(cacheDir, "partials", contentHash);
|
|
21
|
+
const blockLocatorFile = (cacheDir, blockHash) => join(cacheDir, "blocks", `${blockHash}.json`);
|
|
22
|
+
function parseLocator(file) {
|
|
23
|
+
try {
|
|
24
|
+
const value = JSON.parse(readFileSync(file, "utf8"));
|
|
25
|
+
return typeof value.contentHash === "string"
|
|
26
|
+
&& Number.isSafeInteger(value.offset) && value.offset >= 0
|
|
27
|
+
&& Number.isSafeInteger(value.bytes) && value.bytes > 0
|
|
28
|
+
? value
|
|
29
|
+
: null;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async function readRange(file, offset, bytes) {
|
|
36
|
+
const handle = await open(file, "r");
|
|
37
|
+
const output = Buffer.allocUnsafe(bytes);
|
|
38
|
+
try {
|
|
39
|
+
let cursor = 0;
|
|
40
|
+
while (cursor < bytes) {
|
|
41
|
+
const result = await handle.read(output, cursor, bytes - cursor, offset + cursor);
|
|
42
|
+
if (result.bytesRead === 0)
|
|
43
|
+
throw new Error("content cache range is truncated");
|
|
44
|
+
cursor += result.bytesRead;
|
|
45
|
+
}
|
|
46
|
+
return output;
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
await handle.close();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function writeRange(handle, offset, bytes) {
|
|
53
|
+
let cursor = 0;
|
|
54
|
+
while (cursor < bytes.byteLength) {
|
|
55
|
+
const result = await handle.write(bytes, cursor, bytes.byteLength - cursor, offset + cursor);
|
|
56
|
+
if (result.bytesWritten === 0)
|
|
57
|
+
throw new Error("content cache write made no progress");
|
|
58
|
+
cursor += result.bytesWritten;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** One artifact download. Calls may write different block indexes in
|
|
62
|
+
* parallel; sealing is legal only after the portable download operation has
|
|
63
|
+
* awaited them all. */
|
|
64
|
+
export class ContentCacheDownload {
|
|
65
|
+
cacheDir;
|
|
66
|
+
artifact;
|
|
67
|
+
manifest;
|
|
68
|
+
finalFile;
|
|
69
|
+
temporaryFile;
|
|
70
|
+
markerDir;
|
|
71
|
+
offsets;
|
|
72
|
+
complete;
|
|
73
|
+
wholeHash = createHash("sha256");
|
|
74
|
+
pendingHash = new Map();
|
|
75
|
+
nextHashIndex = 0;
|
|
76
|
+
handle = null;
|
|
77
|
+
sealed = false;
|
|
78
|
+
constructor(cacheDir, artifact, manifest) {
|
|
79
|
+
this.cacheDir = cacheDir;
|
|
80
|
+
this.artifact = artifact;
|
|
81
|
+
this.manifest = manifest;
|
|
82
|
+
this.finalFile = contentFile(cacheDir, artifact.contentHash);
|
|
83
|
+
this.temporaryFile = partialFile(cacheDir, artifact.contentHash);
|
|
84
|
+
this.markerDir = partialMarkers(cacheDir, artifact.contentHash);
|
|
85
|
+
let offset = 0;
|
|
86
|
+
this.offsets = manifest.chunks.map((chunk) => {
|
|
87
|
+
const result = offset;
|
|
88
|
+
offset += chunk.bytes;
|
|
89
|
+
return result;
|
|
90
|
+
});
|
|
91
|
+
this.complete = existsSync(this.finalFile)
|
|
92
|
+
&& statSync(this.finalFile).size === manifest.bytes;
|
|
93
|
+
}
|
|
94
|
+
partialHandle() {
|
|
95
|
+
if (!this.handle) {
|
|
96
|
+
this.handle = (async () => {
|
|
97
|
+
ensurePrivateDir(this.markerDir);
|
|
98
|
+
let handle;
|
|
99
|
+
try {
|
|
100
|
+
handle = await open(this.temporaryFile, "r+");
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
if (error.code !== "ENOENT")
|
|
104
|
+
throw error;
|
|
105
|
+
handle = await open(this.temporaryFile, "wx+", 0o600);
|
|
106
|
+
}
|
|
107
|
+
await handle.truncate(this.manifest.bytes);
|
|
108
|
+
return handle;
|
|
109
|
+
})();
|
|
110
|
+
}
|
|
111
|
+
return this.handle;
|
|
112
|
+
}
|
|
113
|
+
acceptForWholeHash(index, bytes) {
|
|
114
|
+
this.pendingHash.set(index, bytes);
|
|
115
|
+
while (true) {
|
|
116
|
+
const next = this.pendingHash.get(this.nextHashIndex);
|
|
117
|
+
if (!next)
|
|
118
|
+
return;
|
|
119
|
+
this.wholeHash.update(next);
|
|
120
|
+
this.pendingHash.delete(this.nextHashIndex);
|
|
121
|
+
this.nextHashIndex += 1;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
markerFile(index) {
|
|
125
|
+
return join(this.markerDir, `${index}.json`);
|
|
126
|
+
}
|
|
127
|
+
/** Verify and stage one already-local block. A false result means the
|
|
128
|
+
* authority must provide it. */
|
|
129
|
+
async hasChunk(index) {
|
|
130
|
+
const expected = this.manifest.chunks[index];
|
|
131
|
+
const offset = this.offsets[index];
|
|
132
|
+
if (!expected || offset === undefined)
|
|
133
|
+
throw new Error(`content manifest has no chunk ${index}`);
|
|
134
|
+
if (this.complete)
|
|
135
|
+
return true;
|
|
136
|
+
try {
|
|
137
|
+
const marker = JSON.parse(readFileSync(this.markerFile(index), "utf8"));
|
|
138
|
+
if (marker.sha256 === expected.sha256 && marker.bytes === expected.bytes) {
|
|
139
|
+
const bytes = await readRange(this.temporaryFile, offset, expected.bytes);
|
|
140
|
+
if (sha256Hex(bytes) === expected.sha256) {
|
|
141
|
+
this.acceptForWholeHash(index, bytes);
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// A missing or invalid partial marker is an ordinary cache miss.
|
|
148
|
+
}
|
|
149
|
+
const locator = parseLocator(blockLocatorFile(this.cacheDir, expected.sha256));
|
|
150
|
+
if (!locator || locator.bytes !== expected.bytes)
|
|
151
|
+
return false;
|
|
152
|
+
try {
|
|
153
|
+
const source = contentFile(this.cacheDir, locator.contentHash);
|
|
154
|
+
const bytes = await readRange(source, locator.offset, locator.bytes);
|
|
155
|
+
if (sha256Hex(bytes) !== expected.sha256)
|
|
156
|
+
return false;
|
|
157
|
+
await writeRange(await this.partialHandle(), offset, bytes);
|
|
158
|
+
atomicWrite(this.markerFile(index), stableJson(expected));
|
|
159
|
+
this.acceptForWholeHash(index, bytes);
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/** Persist one block already verified by @amalgm/live. */
|
|
167
|
+
async putChunk(index, bytes) {
|
|
168
|
+
const expected = this.manifest.chunks[index];
|
|
169
|
+
const offset = this.offsets[index];
|
|
170
|
+
if (!expected || offset === undefined)
|
|
171
|
+
throw new Error(`content manifest has no chunk ${index}`);
|
|
172
|
+
await writeRange(await this.partialHandle(), offset, bytes);
|
|
173
|
+
atomicWrite(this.markerFile(index), stableJson(expected));
|
|
174
|
+
this.acceptForWholeHash(index, bytes);
|
|
175
|
+
}
|
|
176
|
+
/** Commit the complete artifact and its tiny reusable-block index. Cold
|
|
177
|
+
* downloads verify the whole hash from the received buffers, so sealing
|
|
178
|
+
* performs no second complete disk read. */
|
|
179
|
+
async seal() {
|
|
180
|
+
if (this.sealed)
|
|
181
|
+
throw new Error("content artifact is already sealed");
|
|
182
|
+
this.sealed = true;
|
|
183
|
+
if (this.complete) {
|
|
184
|
+
return {
|
|
185
|
+
file: this.finalFile,
|
|
186
|
+
bytes: this.manifest.bytes,
|
|
187
|
+
contentHash: this.manifest.contentHash,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
const handle = await this.partialHandle();
|
|
191
|
+
if (this.nextHashIndex !== this.manifest.chunks.length || this.pendingHash.size !== 0) {
|
|
192
|
+
throw new Error("content artifact is missing verified chunks");
|
|
193
|
+
}
|
|
194
|
+
if (this.wholeHash.digest("hex") !== this.manifest.contentHash) {
|
|
195
|
+
throw new Error(`cloud content ${this.manifest.contentHash} is corrupt`);
|
|
196
|
+
}
|
|
197
|
+
await handle.sync();
|
|
198
|
+
await handle.close();
|
|
199
|
+
this.handle = null;
|
|
200
|
+
renameSync(this.temporaryFile, this.finalFile);
|
|
201
|
+
chmodSync(this.finalFile, 0o600);
|
|
202
|
+
atomicWrite(manifestFile(this.cacheDir, this.artifact.contentHash), stableJson(this.manifest));
|
|
203
|
+
for (const [index, chunk] of this.manifest.chunks.entries()) {
|
|
204
|
+
atomicWrite(blockLocatorFile(this.cacheDir, chunk.sha256), stableJson({
|
|
205
|
+
contentHash: this.artifact.contentHash,
|
|
206
|
+
offset: this.offsets[index],
|
|
207
|
+
bytes: chunk.bytes,
|
|
208
|
+
}));
|
|
209
|
+
}
|
|
210
|
+
rmSync(this.markerDir, { recursive: true, force: true });
|
|
211
|
+
return {
|
|
212
|
+
file: this.finalFile,
|
|
213
|
+
bytes: this.manifest.bytes,
|
|
214
|
+
contentHash: this.manifest.contentHash,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
/** Keep partial bytes and markers for the next attempt, but release the
|
|
218
|
+
* descriptor held by this process. */
|
|
219
|
+
async close() {
|
|
220
|
+
if (!this.handle)
|
|
221
|
+
return;
|
|
222
|
+
const handle = await this.handle;
|
|
223
|
+
this.handle = null;
|
|
224
|
+
await handle.close();
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/** Hash one file without buffering it. Used only when evidence predates the
|
|
228
|
+
* sealed-manifest cache. */
|
|
229
|
+
export async function hashContentFile(file) {
|
|
230
|
+
const hash = createHash("sha256");
|
|
231
|
+
for await (const chunk of createReadStream(file))
|
|
232
|
+
hash.update(chunk);
|
|
233
|
+
return hash.digest("hex");
|
|
234
|
+
}
|
|
235
|
+
//# sourceMappingURL=content-cache-host.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-cache-host.js","sourceRoot":"","sources":["../src/content-cache-host.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { FilesRegisterPorts } from "@amalgm/live/files";
|
|
2
|
+
export declare const WORKSPACE_UUID: RegExp;
|
|
3
|
+
export declare function workspaceBindingDir(userRoot: string, deviceId: string): string;
|
|
4
|
+
/** Local effects for Live's declaration-only register operation. This host has
|
|
5
|
+
* no SQLite, Watch, content, or cloud dependency by design. */
|
|
6
|
+
export declare function createFilesRegisterPorts(input: {
|
|
7
|
+
readonly userRoot: string;
|
|
8
|
+
readonly deviceId: string;
|
|
9
|
+
readonly onReferenceDeclared?: () => void;
|
|
10
|
+
}): FilesRegisterPorts;
|
|
11
|
+
export declare function pathExists(path: string): boolean;
|
|
12
|
+
export declare function pathWithin(root: string, candidate: string): boolean;
|
|
13
|
+
export declare function referenceWorkspaceId(referencePath: string, bindingDir: string): string | null;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { existsSync, lstatSync, realpathSync, readdirSync, readlinkSync, rmSync, statSync, symlinkSync, } from "node:fs";
|
|
3
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep, } from "node:path";
|
|
4
|
+
import { liveMachineStateDir } from "@amalgm/core/identity";
|
|
5
|
+
import { ensurePrivateDir } from "./filesystem.js";
|
|
6
|
+
export const WORKSPACE_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
7
|
+
export function workspaceBindingDir(userRoot, deviceId) {
|
|
8
|
+
return join(liveMachineStateDir(userRoot, deviceId), "bindings");
|
|
9
|
+
}
|
|
10
|
+
/** Local effects for Live's declaration-only register operation. This host has
|
|
11
|
+
* no SQLite, Watch, content, or cloud dependency by design. */
|
|
12
|
+
export function createFilesRegisterPorts(input) {
|
|
13
|
+
const bindingDir = workspaceBindingDir(input.userRoot, input.deviceId);
|
|
14
|
+
return {
|
|
15
|
+
inspectExternalWorkspace: async (requestedPath) => {
|
|
16
|
+
const resolved = realpathSync(resolve(requestedPath));
|
|
17
|
+
const stats = statSync(resolved);
|
|
18
|
+
if (!stats.isDirectory())
|
|
19
|
+
throw new Error("workspace path must name a directory");
|
|
20
|
+
if (pathWithin(input.userRoot, resolved)) {
|
|
21
|
+
throw new Error("files register is only for a workspace outside the Amalgm user ground");
|
|
22
|
+
}
|
|
23
|
+
let workspaceId = null;
|
|
24
|
+
if (existsSync(bindingDir)) {
|
|
25
|
+
for (const entry of readdirSync(bindingDir, { withFileTypes: true })) {
|
|
26
|
+
if (!entry.isSymbolicLink() || !WORKSPACE_UUID.test(entry.name))
|
|
27
|
+
continue;
|
|
28
|
+
try {
|
|
29
|
+
const found = statSync(join(bindingDir, entry.name));
|
|
30
|
+
if (found.dev === stats.dev && found.ino === stats.ino) {
|
|
31
|
+
workspaceId = entry.name.toLowerCase();
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// A broken binding is not evidence about this physical root.
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const name = basename(resolved);
|
|
41
|
+
if (!name)
|
|
42
|
+
throw new Error("workspace path has no name");
|
|
43
|
+
return { absolutePath: resolved, name, workspaceId };
|
|
44
|
+
},
|
|
45
|
+
randomUuid: randomUUID,
|
|
46
|
+
declareWorkspaceReference: async ({ workspaceId, workspaceName, absolutePath }) => {
|
|
47
|
+
ensurePrivateDir(bindingDir);
|
|
48
|
+
const binding = join(bindingDir, workspaceId);
|
|
49
|
+
let createdBinding = false;
|
|
50
|
+
if (pathExists(binding)) {
|
|
51
|
+
if (realpathSync(binding) !== realpathSync(absolutePath)) {
|
|
52
|
+
throw new Error(`workspace ${workspaceId} is already bound to different ground`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
symlinkSync(absolutePath, binding, "dir");
|
|
57
|
+
createdBinding = true;
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
const workspaceDir = join(input.userRoot, "workspaces");
|
|
61
|
+
ensurePrivateDir(workspaceDir);
|
|
62
|
+
const existing = findReference(workspaceDir, bindingDir, workspaceId);
|
|
63
|
+
if (existing) {
|
|
64
|
+
if (createdBinding)
|
|
65
|
+
input.onReferenceDeclared?.();
|
|
66
|
+
return {
|
|
67
|
+
created: false,
|
|
68
|
+
referenceName: basename(existing),
|
|
69
|
+
referencePath: existing,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const referencePath = availableReferencePath(workspaceDir, workspaceName);
|
|
73
|
+
symlinkSync(relative(dirname(referencePath), binding), referencePath);
|
|
74
|
+
input.onReferenceDeclared?.();
|
|
75
|
+
return {
|
|
76
|
+
created: true,
|
|
77
|
+
referenceName: basename(referencePath),
|
|
78
|
+
referencePath,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
if (createdBinding)
|
|
83
|
+
rmSync(binding, { force: true });
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
export function pathExists(path) {
|
|
90
|
+
try {
|
|
91
|
+
lstatSync(path);
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
export function pathWithin(root, candidate) {
|
|
99
|
+
const rel = relative(resolve(root), resolve(candidate));
|
|
100
|
+
return rel === "" || (rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel));
|
|
101
|
+
}
|
|
102
|
+
export function referenceWorkspaceId(referencePath, bindingDir) {
|
|
103
|
+
try {
|
|
104
|
+
const target = resolve(dirname(referencePath), readlinkSync(referencePath));
|
|
105
|
+
const id = basename(target).toLowerCase();
|
|
106
|
+
return resolve(dirname(target)) === resolve(bindingDir) && WORKSPACE_UUID.test(id) ? id : null;
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function findReference(workspaceDir, bindingDir, workspaceId) {
|
|
113
|
+
for (const entry of readdirSync(workspaceDir, { withFileTypes: true })) {
|
|
114
|
+
if (!entry.isSymbolicLink())
|
|
115
|
+
continue;
|
|
116
|
+
const path = join(workspaceDir, entry.name);
|
|
117
|
+
if (referenceWorkspaceId(path, bindingDir) === workspaceId)
|
|
118
|
+
return path;
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
function availableReferencePath(workspaceDir, requestedName) {
|
|
123
|
+
const name = requestedName.trim();
|
|
124
|
+
if (!name || name === "." || name === ".." || name.includes("/") || name.includes("\\")) {
|
|
125
|
+
throw new Error("workspace name must be one path segment");
|
|
126
|
+
}
|
|
127
|
+
for (let suffix = 1; suffix < 10_000; suffix += 1) {
|
|
128
|
+
const candidate = join(workspaceDir, suffix === 1 ? name : `${name} (${suffix})`);
|
|
129
|
+
if (!pathExists(candidate))
|
|
130
|
+
return candidate;
|
|
131
|
+
}
|
|
132
|
+
throw new Error(`no available reference name for ${name}`);
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=files-register-host.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"files-register-host.js","sourceRoot":"","sources":["../src/files-register-host.ts"],"names":[],"mappings":"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"}
|
package/dist/machine-host.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type JoinMachineInput, type MachineSetupPorts, type ReadyMachine } from "@amalgm/core/identity";
|
|
2
|
+
import type { FilesRegisterPorts } from "@amalgm/live/files";
|
|
2
3
|
import { UserGroundHost } from "./user-ground-host.js";
|
|
3
4
|
export interface MachineHostOptions {
|
|
4
5
|
readonly homedir?: string;
|
|
@@ -45,6 +46,9 @@ export declare class MachineHost {
|
|
|
45
46
|
resume(): Promise<ReadyMachine>;
|
|
46
47
|
selectedPaths(): MachinePaths;
|
|
47
48
|
filesCommandPorts(): ReturnType<UserGroundHost["filesCommandPorts"]>;
|
|
49
|
+
/** Registration is a local declaration, so it proves the selected machine
|
|
50
|
+
* from disk without starting Watch, SQLite convergence, or a cloud wire. */
|
|
51
|
+
filesRegisterPorts(): Promise<FilesRegisterPorts>;
|
|
48
52
|
close(): Promise<void>;
|
|
49
53
|
private ensureInstallationRoot;
|
|
50
54
|
private paths;
|
package/dist/machine-host.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { randomBytes, randomUUID } from "node:crypto";
|
|
2
2
|
import { existsSync, readdirSync, rmSync } from "node:fs";
|
|
3
3
|
import { homedir as osHomedir, hostname, platform, release } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
4
|
+
import { basename, join } from "node:path";
|
|
5
5
|
import { buildRootManifest, joinMachine, mergeRecordAuth, resumeMachine, scopedAmalgmDir, userMachineDir, } from "@amalgm/core/identity";
|
|
6
6
|
import { atomicJson, atomicJsonSet, ensurePrivateDir, readJson, } from "./filesystem.js";
|
|
7
7
|
import { createGateway } from "./gateway.js";
|
|
8
|
+
import { createFilesRegisterPorts } from "./files-register-host.js";
|
|
8
9
|
import { shellReleaseCatalog, shellReleaseVerifier } from "./release-catalog.js";
|
|
9
10
|
import { UserGroundHost } from "./user-ground-host.js";
|
|
10
11
|
/** The production effects around Core's machine-setup operation. It does not
|
|
@@ -136,6 +137,20 @@ export class MachineHost {
|
|
|
136
137
|
throw new Error("Files commands require a resumed authenticated machine");
|
|
137
138
|
return this.groundHost.filesCommandPorts();
|
|
138
139
|
}
|
|
140
|
+
/** Registration is a local declaration, so it proves the selected machine
|
|
141
|
+
* from disk without starting Watch, SQLite convergence, or a cloud wire. */
|
|
142
|
+
async filesRegisterPorts() {
|
|
143
|
+
this.resolveUserForResume();
|
|
144
|
+
const machine = await this.loadMachine();
|
|
145
|
+
if (!machine?.ready)
|
|
146
|
+
throw new Error("Files commands require a ready authenticated machine");
|
|
147
|
+
const paths = this.paths();
|
|
148
|
+
const deviceId = text(machine.publicRecord.device_id);
|
|
149
|
+
if (!deviceId || deviceId !== basename(paths.machineDir)) {
|
|
150
|
+
throw new Error("stored machine identity does not match this physical device");
|
|
151
|
+
}
|
|
152
|
+
return createFilesRegisterPorts({ userRoot: paths.userRoot, deviceId });
|
|
153
|
+
}
|
|
139
154
|
async close() {
|
|
140
155
|
const runtimes = [...this.running];
|
|
141
156
|
this.running.clear();
|