@push.rocks/smartdb 5.2.0 → 5.2.1

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/.smartconfig.json CHANGED
@@ -77,7 +77,22 @@
77
77
  },
78
78
  "locked": true,
79
79
  "static": true,
80
- "remapLocalPaths": true
80
+ "remapLocalPaths": true,
81
+ "matrix": {
82
+ "builders": {
83
+ "linux_amd64": {
84
+ "transport": "local"
85
+ },
86
+ "macos_arm64": {
87
+ "transport": "ssh",
88
+ "destinationEnv": "SMARTDB_RELEASE_MACOS_SSH",
89
+ "temporaryRootEnv": "SMARTDB_RELEASE_MACOS_TEMP_ROOT"
90
+ }
91
+ },
92
+ "smokeTestArgs": [
93
+ "--version"
94
+ ]
95
+ }
81
96
  },
82
97
  "@ship.zone/szci": {
83
98
  "npmGlobalTools": []
Binary file
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "format": "tsrust.build-provenance.v2",
3
- "binarySha256": "c5c0a72cc3e9001b008c73ecfbc05dbcde559796eeb81ddd9b57a858f98d4515",
3
+ "binarySha256": "08cf9193c7f6ab682848b35f07b45a1c5894cb0d90cab207b68abe1eb87da0ef",
4
4
  "buildInfo": {
5
5
  "projectName": "@push.rocks/smartdb",
6
- "projectVersion": "5.2.0",
7
- "gitCommit": "d61180c4086a9f771765d104260f620d76b1cda0",
6
+ "projectVersion": "5.2.1",
7
+ "gitCommit": "31b448f06189fc802c6416f1ce6cd85ef275cbe1",
8
8
  "gitDirty": false,
9
- "builtAt": "2026-08-18T22:39:04.935Z",
10
- "tsrustVersion": "1.9.1",
9
+ "builtAt": "2026-08-19T22:20:32.142Z",
10
+ "tsrustVersion": "1.10.0",
11
11
  "binary": "rustdb",
12
12
  "target": "linux_amd64"
13
13
  }
Binary file
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "format": "tsrust.build-provenance.v2",
3
- "binarySha256": "5f3878be4da5dd17eb4c269fdb3f63185d496bd84c1066ce54f47ea3b8f45321",
3
+ "binarySha256": "f246ac1ea19eccabcc828bc5f2db6e2eae2ca0ba67e3fdc9f96177cafe39afe1",
4
4
  "buildInfo": {
5
5
  "projectName": "@push.rocks/smartdb",
6
- "projectVersion": "5.2.0",
7
- "gitCommit": "d61180c4086a9f771765d104260f620d76b1cda0",
6
+ "projectVersion": "5.2.1",
7
+ "gitCommit": "31b448f06189fc802c6416f1ce6cd85ef275cbe1",
8
8
  "gitDirty": false,
9
- "builtAt": "2026-08-18T22:39:24.164Z",
10
- "tsrustVersion": "1.9.1",
9
+ "builtAt": "2026-08-19T22:21:05.240Z",
10
+ "tsrustVersion": "1.10.0",
11
11
  "binary": "rustdb",
12
12
  "target": "linux_arm64"
13
13
  }
Binary file
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "format": "tsrust.build-provenance.v2",
3
- "binarySha256": "1cf98dfc94ff75ab0fad7927c5a59cec87e9623bcc8e36908e7811bd9632064c",
3
+ "binarySha256": "6def4bb635b9993b1d1d8eca70e80500083cc66384f2c99dd39451327fcd027a",
4
4
  "buildInfo": {
5
5
  "projectName": "@push.rocks/smartdb",
6
- "projectVersion": "5.2.0",
7
- "gitCommit": "d61180c4086a9f771765d104260f620d76b1cda0",
6
+ "projectVersion": "5.2.1",
7
+ "gitCommit": "31b448f06189fc802c6416f1ce6cd85ef275cbe1",
8
8
  "gitDirty": false,
9
- "builtAt": "2026-08-18T22:40:01.824Z",
10
- "tsrustVersion": "1.9.1",
9
+ "builtAt": "2026-08-19T22:22:19.797Z",
10
+ "tsrustVersion": "1.10.0",
11
11
  "binary": "rustdb",
12
12
  "target": "macos_amd64"
13
13
  }
Binary file
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "format": "tsrust.build-provenance.v2",
3
- "binarySha256": "3784a0e1d125a25a714086b098018a34cef6056677da3f087992a7e76cd9d87f",
3
+ "binarySha256": "adb7ae9f7fe8a8f791deaab94cac63a607f79890eaf3be64c273658a4c2b6584",
4
4
  "buildInfo": {
5
5
  "projectName": "@push.rocks/smartdb",
6
- "projectVersion": "5.2.0",
7
- "gitCommit": "d61180c4086a9f771765d104260f620d76b1cda0",
6
+ "projectVersion": "5.2.1",
7
+ "gitCommit": "31b448f06189fc802c6416f1ce6cd85ef275cbe1",
8
8
  "gitDirty": false,
9
- "builtAt": "2026-08-18T22:41:12.434Z",
10
- "tsrustVersion": "1.9.1",
9
+ "builtAt": "2026-08-19T22:23:06.048Z",
10
+ "tsrustVersion": "1.10.0",
11
11
  "binary": "rustdb",
12
12
  "target": "macos_arm64"
13
13
  }
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartdb',
6
- version: '5.2.0',
6
+ version: '5.2.1',
7
7
  description: 'A MongoDB-compatible embedded database server with wire protocol support, backed by a high-performance Rust engine.'
8
8
  };
9
9
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSxxQkFBcUI7SUFDM0IsT0FBTyxFQUFFLE9BQU87SUFDaEIsV0FBVyxFQUFFLHFIQUFxSDtDQUNuSSxDQUFBIn0=
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@push.rocks/smartdb",
3
- "version": "5.2.0",
3
+ "version": "5.2.1",
4
4
  "private": false,
5
5
  "description": "A MongoDB-compatible embedded database server with wire protocol support, backed by a high-performance Rust engine.",
6
6
  "exports": {
@@ -16,8 +16,7 @@
16
16
  "@design.estate/dees-element": "^2.2.4",
17
17
  "@git.zone/tsbuild": "^4.4.2",
18
18
  "@git.zone/tsbundle": "^2.11.4",
19
- "@git.zone/tsrun": "^2.0.6",
20
- "@git.zone/tsrust": "^1.9.1",
19
+ "@git.zone/tsrust": "^1.10.0",
21
20
  "@git.zone/tstest": "^4.0.0",
22
21
  "@types/node": "26.2.0",
23
22
  "mongodb": "^7.5.0"
@@ -31,7 +30,6 @@
31
30
  ],
32
31
  "files": [
33
32
  "ts/**/*",
34
- "scripts/**/*",
35
33
  "ts_web/**/*",
36
34
  "dist/**/*",
37
35
  "dist_*/**/*",
@@ -75,7 +73,7 @@
75
73
  "test:rust": "(cd rust && cargo test)",
76
74
  "build": "(tsbundle) && (tsbuild tsfolders) && (tsrust)",
77
75
  "buildDocs": "tsdoc",
78
- "release:check-builders": "tsrun scripts/release-artifacts.ts check",
79
- "release:build-artifacts": "tsrun scripts/release-artifacts.ts build"
76
+ "release:check-builders": "tsrust matrix check",
77
+ "release:build-artifacts": "(tsbundle) && (tsbuild tsfolders) && (tsrust matrix build)"
80
78
  }
81
79
  }
package/readme.hints.md CHANGED
@@ -5,7 +5,7 @@
5
5
  - `LocalSmartDb.relocateStoppedStorageRoot()` is the only supported path-changing operation. It uses a dedicated serialized mutating sidecar runner; do not route it through the disposable offline-inspection helper. A bridge whose termination is unconfirmed remains in the static cleanup gate, and no later relocation may spawn until cleanup succeeds.
6
6
  - Rust owns the descriptor-relative operation in `rust/crates/rustdb/src/storage_root_relocation.rs`. It never constructs `RustDb`. The fixed journal phases are `preparing`, `prepared`, `exchanged`, `providerPublished`, `sentinelPublished`, and `cleaned`; retries reconcile the exact source-root/destination-absent-or-receipt topology or source-receipt/destination-root topology before continuing.
7
7
  - The destination receipt is published with an exclusive rename, then exchanged with the source root using an atomic swap (`RENAME_NOREPLACE`/`RENAME_EXCHANGE` on Linux and `RENAME_EXCL`/`RENAME_SWAP` on macOS). Provider and sentinel replacement also uses checked exchanges, retaining each previous object in the workspace until the replacement is durable and verified. The original root inode ends at the destination and the original receipt inode remains at the source indefinitely. Parent, root, internal, lock, provider, and sentinel identities are revalidated around publication.
8
- - Eligibility is intentionally narrow: Linux on the existing supported local-filesystem set or macOS on qualified local APFS, exact current owner/provider/sentinel metadata, no auth path digest, and no resource receipt, allocation, retired lock, durable database/transaction workspace, database marker, symlink, unknown entry, or unsafe ownership/mode/link profile. macOS additionally rejects case, Unicode, mount, and ancestry aliases by retained physical identity. Missing metadata is rejected and never initialized by relocation.
8
+ - Eligibility is intentionally narrow: Linux on the existing supported local-filesystem set or macOS on qualified local APFS, exact current owner/provider/sentinel metadata, no auth path digest, and no active resource receipt, allocation, database-mutation workspace, unrelated marker, symlink, unknown entry, or unsafe ownership/mode/link profile. A transaction workspace is eligible only when `rustdb-storage` validates its exact `cleaned` internal journal and matching database transaction marker; workspace debris, unfinished phases, mismatches, and allocation bindings remain unsupported. Empty retired legacy locks are eligible only with the canonical resource-digest/inode name parsed by `resource_fencing`, within its existing bound, and tied to one of those terminal transaction resources. macOS additionally rejects case, Unicode, mount, and ancestry aliases by retained physical identity. Missing metadata is rejected and never initialized by relocation.
9
9
  - Both TypeScript startup paths check for `.__rustdb_internal/storage-root-relocation` before `StorageMigrator`. Rust checks immediately after securely opening an existing root and repeats the descriptor-relative check after acquiring the owner lock, before storage initialization. This closes the interval where a relocation can create its workspace and release the lock after startup's first inspection. Occupancy or inspection ambiguity is a startup error; only an exact relocation retry performs recovery.
10
10
  - Completed roots retain the SmartDB 5.0.1 provider/sentinel JSON shape, changing only their storage-root path digest. Interrupted Linux state requires 5.1.0 or newer; interrupted macOS state requires a macOS-capable release. Exact replay lasts only while the destination remains current; a later relocation supersedes the old source/destination tuple.
11
11
 
@@ -44,4 +44,5 @@
44
44
  - `pnpm run test:rust` runs the Rust unit tests; plain `cargo test` in `rust/` works as well.
45
45
  - Verify linkage manually with `ldd dist_rust/<binary>_linux_amd64` → "statically linked".
46
46
  - Do NOT verify feature presence in release binaries with `strings`/`grep`: on x86_64 release builds LLVM embeds short string literals (≤16 bytes, e.g. BSON doc keys like `volatileOnly`) as instruction immediates, so they are invisible to byte scans while the aarch64 binary shows them in `.rodata`. This produces false "stale binary" alarms. Verify behavior by running the binary (the wire tests spawn the host-arch binary from `dist_rust/`), or disassemble: `objdump -d <bin> | grep <hex-of-literal-LE>`.
47
- - Release builds use tsrust 1.9 host partitions and strict sidecar assembly. The enabled gitzone test preflight compiles and executes a static Linux arm64 probe through `binfmt` and checks macOS Rosetta before versioning. The post-version build transfers exact HEAD by Git bundle, executes all four target binaries, and assembles separate Linux and macOS inputs. Remote cleanup is limited to the unique sentinel-owned child below `SMARTDB_RELEASE_MACOS_TEMP_ROOT`.
47
+ - Release builds use the tsrust 1.10 native matrix. The enabled gitzone test preflight checks the exact project-local tool, cross-execution, worker identities, toolchains, and remote root before versioning. The post-version build bundles TypeScript, transfers exact HEAD into isolated local and SSH workers, installs the frozen lockfile, executes all four target binaries, retrieves bounded artifact sets, and transactionally publishes the complete provenance matrix. Remote cleanup is limited to the unique owner-marked child below `SMARTDB_RELEASE_MACOS_TEMP_ROOT` and gates publication.
48
+ - Matrix workers use synthetic homes and therefore do not inherit operator-global pnpm policy. Keep the workspace release-age exception pinned to the exact audited `@git.zone/tsrust` version needed by an immediate upstream-to-downstream release; do not disable the age gate or widen the exception to a scope.
package/readme.md CHANGED
@@ -586,7 +586,7 @@ interface ILocalSmartDbStoppedStorageRootRelocationReceipt {
586
586
 
587
587
  On success, the destination directory is the original storage-root inode and the source path is an exact mode-0600 regular-file receipt. SmartDB does not remove that source receipt. Data, WAL, hint, and index bytes are not copied or rewritten. `providerRootId`, root device/inode, and every provider/sentinel field are preserved; only `storageRootSha256` changes to the destination's canonical path digest. This leaves a completed destination compatible with SmartDB 5.0.1.
588
588
 
589
- Relocation deliberately supports only the plain, unbound `LocalSmartDb` topology. The existing owner lock, `provider-identity.json`, and resource-fencing sentinel must all be present; the operation never creates missing ownership metadata. A provider identity containing `authUsersPathSha256` is unsupported. Any durable fence receipt or capability, allocation state, retired fence lock, database mutation/transaction journal, allocation/publication marker, symlink, unknown entry, unsafe mode/owner/link count, exhausted scan bound, active owner, or ambiguous identity fails closed.
589
+ Relocation deliberately supports only the plain, unbound `LocalSmartDb` topology. The existing owner lock, `provider-identity.json`, and resource-fencing sentinel must all be present; the operation never creates missing ownership metadata. A provider identity containing `authUsersPathSha256` is unsupported. One narrow terminal transaction topology is also eligible: each retained internal transaction journal must be in the exact `cleaned` phase, contain no workspace debris or allocation binding, and match the exact transaction-publication marker in its database. Canonically named, empty retired legacy fence locks are accepted only when their resource digest belongs to one of those terminal transactions and their encoded inode matches the retained file. These terminal artifacts move unchanged with the root and continue the existing transaction token sequence after restart. Any unfinished or mismatched transaction, durable database-mutation journal, active fence receipt or capability, allocation state, unrelated or malformed retired lock, allocation/general publication marker, symlink, unknown entry, unsafe mode/owner/link count, exhausted scan bound, active owner, or ambiguous identity fails closed.
590
590
 
591
591
  The operation journals phases `preparing`, `prepared`, `exchanged`, `providerPublished`, `sentinelPublished`, and `cleaned`. If a call returns `RECOVERY_REQUIRED`, stop all startup attempts and retry the exact same normalized source path, destination path, and `relocationId` with the same SmartDB version that began the relocation or a newer version. Startup rejects any relocation-workspace occupancy and does not mutate or recover it. Do not delete, rename, edit, or replace either path between attempts. Linux relocation state created by SmartDB 5.1.0 remains recoverable by 5.1.0 or newer; macOS recovery requires a macOS-capable release. A completed root remains 5.0.1-compatible.
592
592
 
@@ -1015,7 +1015,7 @@ Cross-compiled for `linux_amd64`, `linux_arm64`, `macos_amd64`, and `macos_arm64
1015
1015
 
1016
1016
  ### Multi-host release artifacts
1017
1017
 
1018
- Normal `tsrust` builds select only the current host's partition from `targetsByHost`. Releases coordinate both partitions after the version commit exists, then publish only after every exact-commit binary has executed and passed strict provenance assembly.
1018
+ Normal `tsrust` builds select only the current host's partition from `targetsByHost`. Releases use `tsrust matrix` to coordinate both partitions after the version commit exists, then publish only after every exact-commit binary has executed and passed strict provenance assembly.
1019
1019
 
1020
1020
  The release coordinator must be Linux amd64 with `aarch64-linux-gnu-gcc` and a registered Linux arm64 `binfmt` interpreter. Set these environment variables to an authorized Apple Silicon builder with Rosetta and a dedicated temporary root:
1021
1021
 
@@ -1024,14 +1024,14 @@ export SMARTDB_RELEASE_MACOS_SSH="release-user@mac-builder.example"
1024
1024
  export SMARTDB_RELEASE_MACOS_TEMP_ROOT="/absolute/dedicated/smartdb-release"
1025
1025
  ```
1026
1026
 
1027
- `gitzone release` first runs `pnpm run release:check-builders`, before creating a version commit or tag. It compiles and executes a static arm64 probe and rejects missing cross-toolchain or `binfmt` execution, an unsuitable macOS host, missing Rosetta, missing tools, or an unwritable remote temporary root. The release build then:
1027
+ `gitzone release` first runs `pnpm run release:check-builders`, before creating a version commit or tag. It verifies the project-local tsrust version, compiles and executes the required architecture probes, and rejects missing cross-toolchains or emulation, an unsuitable macOS host, missing tools, or an unsafe remote temporary root. The release build then:
1028
1028
 
1029
- 1. Builds and executes the Linux amd64 and arm64 binaries.
1030
- 2. Transfers a Git bundle of the unpushed release commit into a unique sentinel-owned macOS workspace.
1031
- 3. Builds both Darwin targets from the frozen lockfile, verifies their Cargo bytes and signatures, and executes both architectures.
1032
- 4. Copies each host output into separate `.nogit` inputs and runs strict `tsrust assemble` into `dist_rust/`.
1029
+ 1. Builds the TypeScript package and debug UI bundle from the version commit.
1030
+ 2. Streams an exact Git bundle into isolated local Linux and remote macOS worker workspaces and installs each frozen lockfile.
1031
+ 3. Builds and executes the Linux amd64, Linux arm64, macOS arm64, and macOS amd64 binaries on their assigned workers.
1032
+ 4. Retrieves bounded artifact sets and transactionally publishes the complete provenance-checked matrix into `dist_rust/`.
1033
1033
 
1034
- No host address or path is stored in the repository. A failed run retains its unique diagnostic directories and prints their paths. Successful cleanup can remove only the generated child whose path and random ownership sentinel both match.
1034
+ No host address or path is stored in the repository. A failed run retains its unique local diagnostics and owner-marked remote diagnostics, then prints their paths. Remote cleanup is an owner-validated publication gate, and an incomplete matrix never replaces the prior `dist_rust/`.
1035
1035
 
1036
1036
  ### Storage Engine Reliability 🔒
1037
1037
 
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartdb',
6
- version: '5.2.0',
6
+ version: '5.2.1',
7
7
  description: 'A MongoDB-compatible embedded database server with wire protocol support, backed by a high-performance Rust engine.'
8
8
  }
@@ -1,626 +0,0 @@
1
- import * as childProcess from 'node:child_process';
2
- import * as crypto from 'node:crypto';
3
- import * as fs from 'node:fs';
4
- import * as path from 'node:path';
5
-
6
- type TReleaseArtifactMode = 'check' | 'build';
7
-
8
- interface IReleaseArtifactEnvironment {
9
- macosSsh: string;
10
- macosTempRoot: string;
11
- }
12
-
13
- const provenanceSuffix = '.tsrust-build.json';
14
- const defaultCommandTimeoutMs = 60 * 60 * 1000;
15
- const capabilityCommandTimeoutMs = 5 * 60 * 1000;
16
- const forceKillDelayMs = 10 * 1000;
17
- const maximumCapturedOutputBytes = 16 * 1024 * 1024;
18
- const sshConnectionArgs = [
19
- '-o',
20
- 'ServerAliveInterval=15',
21
- '-o',
22
- 'ServerAliveCountMax=2',
23
- ];
24
-
25
- type TForwardedSignal = 'SIGINT' | 'SIGTERM' | 'SIGHUP';
26
-
27
- const shellQuote = (valueArg: string): string => `'${valueArg.replace(/'/g, `'"'"'`)}'`;
28
-
29
- const executeCommand = (
30
- commandArg: string,
31
- argsArg: string[],
32
- optionsArg: {
33
- cwd?: string;
34
- capture?: boolean;
35
- timeoutMs?: number;
36
- heartbeatStdin?: boolean;
37
- } = {},
38
- ): Promise<string> =>
39
- new Promise((resolveArg, rejectArg) => {
40
- const capture = optionsArg.capture === true;
41
- const child = childProcess.spawn(commandArg, argsArg, {
42
- cwd: optionsArg.cwd || process.cwd(),
43
- env: process.env,
44
- stdio: capture
45
- ? ['ignore', 'pipe', 'pipe']
46
- : optionsArg.heartbeatStdin
47
- ? ['pipe', 'inherit', 'inherit']
48
- : 'inherit',
49
- detached: true,
50
- });
51
- let stdout = '';
52
- let stderr = '';
53
- let capturedOutputBytes = 0;
54
- let timedOut = false;
55
- let outputLimitExceeded = false;
56
- let forwardedSignal: TForwardedSignal | undefined;
57
- let forwardedSignalCount = 0;
58
- let settled = false;
59
- let forceKillTimer: NodeJS.Timeout | undefined;
60
- let heartbeatTimer: NodeJS.Timeout | undefined;
61
-
62
- const killProcessGroup = (signalArg: NodeJS.Signals): void => {
63
- if (!child.pid) return;
64
- try {
65
- process.kill(-child.pid, signalArg);
66
- } catch {
67
- try {
68
- child.kill(signalArg);
69
- } catch {
70
- // The process may already have exited.
71
- }
72
- }
73
- };
74
-
75
- const terminateProcessGroup = (signalArg: NodeJS.Signals): void => {
76
- killProcessGroup(signalArg);
77
- if (!forceKillTimer) {
78
- forceKillTimer = setTimeout(
79
- () => killProcessGroup('SIGKILL'),
80
- forceKillDelayMs,
81
- );
82
- }
83
- };
84
-
85
- const processGroupExists = (): boolean => {
86
- if (!child.pid) return false;
87
- try {
88
- process.kill(-child.pid, 0);
89
- return true;
90
- } catch (error) {
91
- if ((error as NodeJS.ErrnoException).code === 'ESRCH') return false;
92
- throw error;
93
- }
94
- };
95
-
96
- const waitForProcessGroupExit = async (timeoutMsArg: number): Promise<boolean> => {
97
- const deadline = Date.now() + timeoutMsArg;
98
- while (processGroupExists() && Date.now() < deadline) {
99
- await new Promise((resolveArg) => setTimeout(resolveArg, 100));
100
- }
101
- return !processGroupExists();
102
- };
103
-
104
- const ensureProcessGroupStopped = async (): Promise<void> => {
105
- if (!processGroupExists()) return;
106
- terminateProcessGroup('SIGTERM');
107
- if (!(await waitForProcessGroupExit(forceKillDelayMs + 1000))) {
108
- killProcessGroup('SIGKILL');
109
- if (!(await waitForProcessGroupExit(2000))) {
110
- throw new Error(`${commandArg} left a live descendant process group`);
111
- }
112
- }
113
- };
114
-
115
- const signalHandlers = new Map<TForwardedSignal, () => void>();
116
- for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP'] as TForwardedSignal[]) {
117
- const handler = (): void => {
118
- forwardedSignal = signal;
119
- forwardedSignalCount += 1;
120
- if (forwardedSignalCount === 1) terminateProcessGroup(signal);
121
- else killProcessGroup('SIGKILL');
122
- };
123
- signalHandlers.set(signal, handler);
124
- process.on(signal, handler);
125
- }
126
-
127
- const timeout = setTimeout(() => {
128
- timedOut = true;
129
- terminateProcessGroup('SIGTERM');
130
- }, optionsArg.timeoutMs || defaultCommandTimeoutMs);
131
-
132
- if (optionsArg.heartbeatStdin) {
133
- child.stdin?.on('error', () => {
134
- // Process close handles a lost stdin channel.
135
- });
136
- const sendHeartbeat = (): void => {
137
- if (!child.stdin?.destroyed) child.stdin?.write('heartbeat\n');
138
- };
139
- sendHeartbeat();
140
- heartbeatTimer = setInterval(sendHeartbeat, 1000);
141
- }
142
-
143
- const cleanup = (): void => {
144
- clearTimeout(timeout);
145
- if (forceKillTimer) clearTimeout(forceKillTimer);
146
- if (heartbeatTimer) clearInterval(heartbeatTimer);
147
- child.stdin?.end();
148
- for (const [signal, handler] of signalHandlers) {
149
- process.off(signal, handler);
150
- }
151
- };
152
-
153
- const captureChunk = (targetArg: 'stdout' | 'stderr', chunkArg: Buffer): void => {
154
- capturedOutputBytes += chunkArg.length;
155
- if (capturedOutputBytes > maximumCapturedOutputBytes) {
156
- outputLimitExceeded = true;
157
- terminateProcessGroup('SIGTERM');
158
- return;
159
- }
160
- if (targetArg === 'stdout') stdout += chunkArg.toString('utf8');
161
- else stderr += chunkArg.toString('utf8');
162
- };
163
- child.stdout?.on('data', (chunkArg: Buffer) => captureChunk('stdout', chunkArg));
164
- child.stderr?.on('data', (chunkArg: Buffer) => captureChunk('stderr', chunkArg));
165
-
166
- child.once('error', (errorArg) => {
167
- if (settled) return;
168
- settled = true;
169
- cleanup();
170
- rejectArg(errorArg);
171
- });
172
- child.once('close', (codeArg, signalArg) => {
173
- if (settled) return;
174
- settled = true;
175
- void (async () => {
176
- try {
177
- await ensureProcessGroupStopped();
178
- cleanup();
179
- if (codeArg !== 0 && capture) {
180
- if (stdout) process.stdout.write(stdout);
181
- if (stderr) process.stderr.write(stderr);
182
- }
183
- if (timedOut) {
184
- rejectArg(new Error(`${commandArg} exceeded its command timeout`));
185
- } else if (outputLimitExceeded) {
186
- rejectArg(new Error(`${commandArg} exceeded the captured output limit`));
187
- } else if (forwardedSignal) {
188
- rejectArg(new Error(`${commandArg} was interrupted by ${forwardedSignal}`));
189
- } else if (codeArg !== 0) {
190
- rejectArg(
191
- new Error(
192
- `${commandArg} exited with ${signalArg ? `signal ${signalArg}` : `status ${codeArg}`}`,
193
- ),
194
- );
195
- } else {
196
- resolveArg(stdout.trim());
197
- }
198
- } catch (error) {
199
- cleanup();
200
- rejectArg(error);
201
- }
202
- })();
203
- });
204
- });
205
-
206
- const runCommand = async (
207
- commandArg: string,
208
- argsArg: string[],
209
- cwdArg: string = process.cwd(),
210
- timeoutMsArg: number = defaultCommandTimeoutMs,
211
- heartbeatStdinArg: boolean = false,
212
- ): Promise<void> => {
213
- await executeCommand(commandArg, argsArg, {
214
- cwd: cwdArg,
215
- timeoutMs: timeoutMsArg,
216
- heartbeatStdin: heartbeatStdinArg,
217
- });
218
- };
219
-
220
- const captureCommand = async (
221
- commandArg: string,
222
- argsArg: string[],
223
- cwdArg: string = process.cwd(),
224
- ): Promise<string> => {
225
- return executeCommand(commandArg, argsArg, {
226
- cwd: cwdArg,
227
- capture: true,
228
- timeoutMs: capabilityCommandTimeoutMs,
229
- });
230
- };
231
-
232
- const loadEnvironment = (): IReleaseArtifactEnvironment => {
233
- const macosSsh = process.env.SMARTDB_RELEASE_MACOS_SSH?.trim() || '';
234
- const macosTempRoot = (process.env.SMARTDB_RELEASE_MACOS_TEMP_ROOT?.trim() || '').replace(
235
- /\/+$/,
236
- '',
237
- );
238
-
239
- if (!/^[A-Za-z0-9._-]+@[A-Za-z0-9.-]+$/.test(macosSsh)) {
240
- throw new Error(
241
- 'SMARTDB_RELEASE_MACOS_SSH must use the user@host form without SSH options',
242
- );
243
- }
244
- if (
245
- !/^\/[A-Za-z0-9._/-]+$/.test(macosTempRoot) ||
246
- path.posix.normalize(macosTempRoot) !== macosTempRoot ||
247
- macosTempRoot.split('/').filter(Boolean).length < 2
248
- ) {
249
- throw new Error(
250
- 'SMARTDB_RELEASE_MACOS_TEMP_ROOT must be a non-root absolute path without spaces',
251
- );
252
- }
253
- return { macosSsh, macosTempRoot };
254
- };
255
-
256
- const runRemoteLoginScript = async (
257
- environmentArg: IReleaseArtifactEnvironment,
258
- scriptArg: string,
259
- timeoutMsArg: number = defaultCommandTimeoutMs,
260
- ): Promise<void> => {
261
- const ownedRemoteScript = [
262
- 'set -u',
263
- 'exec 3<&0',
264
- 'child_pid=',
265
- 'watchdog_pid=',
266
- 'kill_tree() {',
267
- ' local parent_pid children child_pid_to_kill',
268
- ' parent_pid="$1"',
269
- ' kill -STOP "$parent_pid" 2>/dev/null || return 0',
270
- ' children="$(/usr/bin/pgrep -P "$parent_pid" 2>/dev/null || true)"',
271
- ' for child_pid_to_kill in $children; do kill_tree "$child_pid_to_kill"; done',
272
- ' kill -KILL "$parent_pid" 2>/dev/null || true',
273
- '}',
274
- 'terminate_owned() { test -z "$child_pid" || kill_tree "$child_pid"; test -z "$watchdog_pid" || kill -KILL "$watchdog_pid" 2>/dev/null || true; }',
275
- 'watch_channel() { while IFS= read -r -t 45 ignored_line; do :; done; test -z "$child_pid" || kill_tree "$child_pid"; }',
276
- 'trap terminate_owned EXIT HUP INT TERM',
277
- `/bin/zsh -lc ${shellQuote(scriptArg)} </dev/null &`,
278
- 'child_pid=$!',
279
- 'watch_channel <&3 &',
280
- 'watchdog_pid=$!',
281
- 'wait "$child_pid"',
282
- 'exit_status=$?',
283
- 'child_pid=',
284
- 'kill -KILL "$watchdog_pid" 2>/dev/null || true',
285
- 'wait "$watchdog_pid" 2>/dev/null || true',
286
- 'watchdog_pid=',
287
- 'exec 3<&-',
288
- 'trap - EXIT HUP INT TERM',
289
- 'exit "$exit_status"',
290
- ].join('\n');
291
- await runCommand(
292
- 'ssh',
293
- [
294
- ...sshConnectionArgs,
295
- environmentArg.macosSsh,
296
- `zsh -lic ${shellQuote(`/bin/sh -c ${shellQuote(ownedRemoteScript)}`)}`,
297
- ],
298
- process.cwd(),
299
- timeoutMsArg,
300
- true,
301
- );
302
- };
303
-
304
- const checkBuilders = async (
305
- environmentArg: IReleaseArtifactEnvironment,
306
- ): Promise<void> => {
307
- if (process.platform !== 'linux' || process.arch !== 'x64') {
308
- throw new Error('SmartDB release artifact assembly requires a Linux amd64 coordinator');
309
- }
310
-
311
- await runCommand('git', ['--version'], process.cwd(), capabilityCommandTimeoutMs);
312
- await runCommand('pnpm', ['--version'], process.cwd(), capabilityCommandTimeoutMs);
313
- await captureCommand('/bin/sh', ['-c', 'command -v scp >/dev/null']);
314
- const linuxProbeParent = path.join(process.cwd(), '.nogit', 'smartdb-release-probe');
315
- fs.mkdirSync(linuxProbeParent, { recursive: true, mode: 0o700 });
316
- const linuxProbeDirectory = fs.mkdtempSync(path.join(linuxProbeParent, 'arm64-'));
317
- try {
318
- const sourcePath = path.join(linuxProbeDirectory, 'probe.c');
319
- const binaryPath = path.join(linuxProbeDirectory, 'probe');
320
- fs.writeFileSync(sourcePath, 'int main(void) { return 0; }\n');
321
- await runCommand(
322
- 'aarch64-linux-gnu-gcc',
323
- ['-static', '-O2', sourcePath, '-o', binaryPath],
324
- process.cwd(),
325
- capabilityCommandTimeoutMs,
326
- );
327
- await runCommand(binaryPath, [], process.cwd(), capabilityCommandTimeoutMs);
328
- } finally {
329
- fs.rmSync(linuxProbeDirectory, { recursive: true, force: true });
330
- }
331
-
332
- const probeToken = crypto.randomUUID();
333
- const probePath = `${environmentArg.macosTempRoot}/smartdb-release-check.${probeToken}`;
334
- const remoteCheckScript = [
335
- 'set -eu',
336
- 'test "$(uname -s)" = "Darwin"',
337
- 'test "$(uname -m)" = "arm64"',
338
- 'command -v git >/dev/null',
339
- 'command -v pnpm >/dev/null',
340
- 'command -v codesign >/dev/null',
341
- 'command -v cmp >/dev/null',
342
- `root=${shellQuote(environmentArg.macosTempRoot)}`,
343
- `probe=${shellQuote(probePath)}`,
344
- `test "$probe" = "$root/smartdb-release-check.${probeToken}"`,
345
- 'mkdir -p "$root"',
346
- 'test "$(cd "$root" && pwd -P)" = "$root"',
347
- 'created=0',
348
- `cleanup_probe() { if [ "$created" = 1 ]; then test ! -f "$probe/.smartdb-release-owner" || test "$(cat "$probe/.smartdb-release-owner")" = ${shellQuote(probeToken)} || return; rm -f "$probe/.smartdb-release-owner"; rmdir "$probe" 2>/dev/null || true; fi; }`,
349
- 'trap cleanup_probe EXIT HUP INT TERM',
350
- 'mkdir -m 700 "$probe"',
351
- 'created=1',
352
- `printf '%s\\n' ${shellQuote(probeToken)} > "$probe/.smartdb-release-owner"`,
353
- 'rm -f "$probe/.smartdb-release-owner"',
354
- 'rmdir "$probe"',
355
- 'trap - EXIT HUP INT TERM',
356
- '/usr/bin/arch -x86_64 /usr/bin/true',
357
- ].join('\n');
358
- await runRemoteLoginScript(
359
- environmentArg,
360
- remoteCheckScript,
361
- capabilityCommandTimeoutMs,
362
- );
363
- console.log('All SmartDB release builders can execute their assigned architectures.');
364
- };
365
-
366
- const assertArtifactSet = (directoryArg: string, targetsArg: string[]): void => {
367
- const expectedNames = targetsArg.flatMap((targetArg) => [
368
- `rustdb_${targetArg}`,
369
- `rustdb_${targetArg}${provenanceSuffix}`,
370
- ]);
371
- const actualNames = fs.readdirSync(directoryArg).sort();
372
- if (JSON.stringify(actualNames) !== JSON.stringify(expectedNames.sort())) {
373
- throw new Error(
374
- `Unexpected release artifact set in ${directoryArg}: ${actualNames.join(', ')}`,
375
- );
376
- }
377
- for (const target of targetsArg) {
378
- const binaryPath = path.join(directoryArg, `rustdb_${target}`);
379
- const stat = fs.lstatSync(binaryPath);
380
- if (!stat.isFile() || (stat.mode & 0o111) === 0) {
381
- throw new Error(`Release artifact is not an executable regular file: ${binaryPath}`);
382
- }
383
- }
384
- };
385
-
386
- const remoteWorkspacePath = (
387
- environmentArg: IReleaseArtifactEnvironment,
388
- gitCommitArg: string,
389
- ownerTokenArg: string,
390
- ): string =>
391
- `${environmentArg.macosTempRoot}/smartdb-release.${gitCommitArg}.${ownerTokenArg}`;
392
-
393
- const assertOwnedRemoteWorkspacePath = (
394
- environmentArg: IReleaseArtifactEnvironment,
395
- remoteDirectoryArg: string,
396
- gitCommitArg: string,
397
- ownerTokenArg: string,
398
- ): void => {
399
- const expectedDirectory = remoteWorkspacePath(
400
- environmentArg,
401
- gitCommitArg,
402
- ownerTokenArg,
403
- );
404
- if (
405
- remoteDirectoryArg !== expectedDirectory ||
406
- path.posix.dirname(remoteDirectoryArg) !== environmentArg.macosTempRoot ||
407
- path.posix.normalize(remoteDirectoryArg) !== remoteDirectoryArg
408
- ) {
409
- throw new Error(`Refusing unsafe macOS release workspace path: ${remoteDirectoryArg}`);
410
- }
411
- };
412
-
413
- const createRemoteWorkspace = async (
414
- environmentArg: IReleaseArtifactEnvironment,
415
- remoteDirectoryArg: string,
416
- gitCommitArg: string,
417
- ownerTokenArg: string,
418
- ): Promise<void> => {
419
- assertOwnedRemoteWorkspacePath(
420
- environmentArg,
421
- remoteDirectoryArg,
422
- gitCommitArg,
423
- ownerTokenArg,
424
- );
425
- const createScript = [
426
- 'set -eu',
427
- `root=${shellQuote(environmentArg.macosTempRoot)}`,
428
- `directory=${shellQuote(remoteDirectoryArg)}`,
429
- `test "$directory" = "$root/smartdb-release.${gitCommitArg}.${ownerTokenArg}"`,
430
- 'mkdir -p "$root"',
431
- 'test "$(cd "$root" && pwd -P)" = "$root"',
432
- 'created=0',
433
- `cleanup_unowned() { if [ "$created" = 1 ]; then test ! -f "$directory/.smartdb-release-owner" || test "$(cat "$directory/.smartdb-release-owner")" = ${shellQuote(ownerTokenArg)} || return; rm -f "$directory/.smartdb-release-owner"; rmdir "$directory" 2>/dev/null || true; fi; }`,
434
- 'trap cleanup_unowned EXIT HUP INT TERM',
435
- 'mkdir -m 700 "$directory"',
436
- 'created=1',
437
- `printf '%s\\n' ${shellQuote(ownerTokenArg)} > "$directory/.smartdb-release-owner"`,
438
- 'trap - EXIT HUP INT TERM',
439
- ].join('\n');
440
- await runCommand(
441
- 'ssh',
442
- [...sshConnectionArgs, environmentArg.macosSsh, createScript],
443
- process.cwd(),
444
- capabilityCommandTimeoutMs,
445
- );
446
- };
447
-
448
- const cleanupRemoteWorkspace = async (
449
- environmentArg: IReleaseArtifactEnvironment,
450
- remoteDirectoryArg: string,
451
- gitCommitArg: string,
452
- ownerTokenArg: string,
453
- ): Promise<void> => {
454
- assertOwnedRemoteWorkspacePath(
455
- environmentArg,
456
- remoteDirectoryArg,
457
- gitCommitArg,
458
- ownerTokenArg,
459
- );
460
- const cleanupScript = [
461
- 'set -eu',
462
- `root=${shellQuote(environmentArg.macosTempRoot)}`,
463
- `directory=${shellQuote(remoteDirectoryArg)}`,
464
- `test "$directory" = "$root/smartdb-release.${gitCommitArg}.${ownerTokenArg}"`,
465
- 'test "$(cd "$root" && pwd -P)" = "$root"',
466
- 'test -f "$directory/.smartdb-release-owner"',
467
- `test "$(cat "$directory/.smartdb-release-owner")" = ${shellQuote(ownerTokenArg)}`,
468
- 'rm -rf "$directory"',
469
- ].join('\n');
470
- await runCommand('ssh', [...sshConnectionArgs, environmentArg.macosSsh, cleanupScript]);
471
- };
472
-
473
- const buildArtifacts = async (
474
- environmentArg: IReleaseArtifactEnvironment,
475
- ): Promise<void> => {
476
- await checkBuilders(environmentArg);
477
-
478
- const workspace = process.cwd();
479
- const branch = await captureCommand('git', ['branch', '--show-current'], workspace);
480
- const gitCommit = await captureCommand('git', ['rev-parse', 'HEAD'], workspace);
481
- const gitStatus = await captureCommand(
482
- 'git',
483
- ['status', '--porcelain=v1', '--untracked-files=all'],
484
- workspace,
485
- );
486
- const packageJson = JSON.parse(
487
- fs.readFileSync(path.join(workspace, 'package.json'), 'utf8'),
488
- ) as { version?: string };
489
- const expectedTag = `v${packageJson.version || ''}`;
490
- const tagsAtHead = (
491
- await captureCommand('git', ['tag', '--points-at', 'HEAD'], workspace)
492
- ).split(/\r?\n/);
493
- if (branch !== 'main') throw new Error('SmartDB releases must be built from main');
494
- if (gitStatus) throw new Error('SmartDB release artifact build requires a clean worktree');
495
- if (!packageJson.version || !tagsAtHead.includes(expectedTag)) {
496
- throw new Error(`SmartDB release artifact build requires ${expectedTag} at HEAD`);
497
- }
498
-
499
- const releaseParent = path.join(workspace, '.nogit', 'smartdb-release');
500
- fs.mkdirSync(releaseParent, { recursive: true, mode: 0o700 });
501
- const localDirectory = fs.mkdtempSync(
502
- path.join(releaseParent, `${gitCommit.slice(0, 12)}-`),
503
- );
504
- const linuxDirectory = path.join(localDirectory, 'linux');
505
- const macosDirectory = path.join(localDirectory, 'macos');
506
- const bundlePath = path.join(localDirectory, 'source.bundle');
507
- const ownerToken = crypto.randomUUID();
508
- let remoteDirectory: string | undefined;
509
- let completed = false;
510
-
511
- try {
512
- await runCommand('pnpm', ['exec', 'tsbundle'], workspace);
513
- await runCommand('pnpm', ['exec', 'tsbuild', 'tsfolders'], workspace);
514
- await runCommand('pnpm', ['exec', 'tsrust'], workspace);
515
-
516
- fs.cpSync(path.join(workspace, 'dist_rust'), linuxDirectory, {
517
- recursive: true,
518
- preserveTimestamps: true,
519
- });
520
- assertArtifactSet(linuxDirectory, ['linux_amd64', 'linux_arm64']);
521
- await runCommand(path.join(linuxDirectory, 'rustdb_linux_amd64'), ['--version'], workspace);
522
- await runCommand(
523
- path.join(linuxDirectory, 'rustdb_linux_arm64'),
524
- ['--version'],
525
- workspace,
526
- );
527
-
528
- await runCommand('git', ['bundle', 'create', bundlePath, 'HEAD'], workspace);
529
- remoteDirectory = remoteWorkspacePath(environmentArg, gitCommit, ownerToken);
530
- await createRemoteWorkspace(
531
- environmentArg,
532
- remoteDirectory,
533
- gitCommit,
534
- ownerToken,
535
- );
536
- await runCommand('scp', [
537
- '-p',
538
- ...sshConnectionArgs,
539
- bundlePath,
540
- `${environmentArg.macosSsh}:${remoteDirectory}/source.bundle`,
541
- ]);
542
-
543
- const remoteWorkspace = `${remoteDirectory}/workspace`;
544
- const remoteBuildScript = [
545
- 'set -eu',
546
- `bundle=${shellQuote(`${remoteDirectory}/source.bundle`)}`,
547
- `workspace=${shellQuote(remoteWorkspace)}`,
548
- `expected_commit=${shellQuote(gitCommit)}`,
549
- 'git clone --quiet "$bundle" "$workspace"',
550
- 'cd "$workspace"',
551
- 'test "$(git rev-parse HEAD)" = "$expected_commit"',
552
- 'test -z "$(git status --porcelain=v1 --untracked-files=all)"',
553
- 'pnpm install --frozen-lockfile',
554
- 'pnpm exec tsrust',
555
- 'cmp .nogit/tsrust-target/aarch64-apple-darwin/release/rustdb dist_rust/rustdb_macos_arm64',
556
- 'cmp .nogit/tsrust-target/x86_64-apple-darwin/release/rustdb dist_rust/rustdb_macos_amd64',
557
- 'codesign --verify --deep --strict dist_rust/rustdb_macos_arm64',
558
- 'codesign --verify --deep --strict dist_rust/rustdb_macos_amd64',
559
- '/usr/bin/arch -arm64 dist_rust/rustdb_macos_arm64 --version',
560
- '/usr/bin/arch -x86_64 dist_rust/rustdb_macos_amd64 --version',
561
- 'test "$(git rev-parse HEAD)" = "$expected_commit"',
562
- 'test -z "$(git status --porcelain=v1 --untracked-files=all)"',
563
- ].join('\n');
564
- await runRemoteLoginScript(environmentArg, remoteBuildScript);
565
- await runCommand('scp', [
566
- '-pr',
567
- ...sshConnectionArgs,
568
- `${environmentArg.macosSsh}:${remoteWorkspace}/dist_rust`,
569
- macosDirectory,
570
- ]);
571
- assertArtifactSet(macosDirectory, ['macos_amd64', 'macos_arm64']);
572
-
573
- await runCommand(
574
- 'pnpm',
575
- ['exec', 'tsrust', 'assemble', linuxDirectory, macosDirectory],
576
- workspace,
577
- );
578
- assertArtifactSet(path.join(workspace, 'dist_rust'), [
579
- 'linux_amd64',
580
- 'linux_arm64',
581
- 'macos_amd64',
582
- 'macos_arm64',
583
- ]);
584
- for (const target of ['linux_amd64', 'linux_arm64', 'macos_amd64', 'macos_arm64']) {
585
- await runCommand(
586
- 'pnpm',
587
- ['exec', 'tsrust', 'inspect', `dist_rust/rustdb_${target}`],
588
- workspace,
589
- );
590
- }
591
-
592
- await cleanupRemoteWorkspace(
593
- environmentArg,
594
- remoteDirectory,
595
- gitCommit,
596
- ownerToken,
597
- );
598
- remoteDirectory = undefined;
599
- fs.rmSync(localDirectory, { recursive: true });
600
- completed = true;
601
- console.log(`Assembled and qualified SmartDB release artifacts for ${expectedTag}.`);
602
- } finally {
603
- if (!completed) {
604
- console.error(`Retained local release diagnostics at ${localDirectory}`);
605
- if (remoteDirectory) {
606
- console.error(`Retained macOS release diagnostics at ${remoteDirectory}`);
607
- }
608
- }
609
- }
610
- };
611
-
612
- try {
613
- const mode = process.argv.find((argumentArg) =>
614
- ['check', 'build'].includes(argumentArg),
615
- ) as TReleaseArtifactMode | undefined;
616
- if (!mode) throw new Error('Usage: release-artifacts.ts <check|build>');
617
- const environment = loadEnvironment();
618
- if (mode === 'check') {
619
- await checkBuilders(environment);
620
- } else {
621
- await buildArtifacts(environment);
622
- }
623
- } catch (error) {
624
- console.error(error);
625
- process.exitCode = 1;
626
- }