@amalgm/shell 0.1.164-preview.34226938713 → 0.1.164-preview.34240142223
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
|
@@ -70,12 +70,17 @@ Fixture cleanup preserves the original failure and every cleanup failure; instal
|
|
|
70
70
|
state remains available when its native owner cannot be removed.
|
|
71
71
|
A fixture's process lease covers its final writes, and its native release fences
|
|
72
72
|
cleanup after either cooperative or forced exit.
|
|
73
|
+
Fixture termination is proved by releasing its owned native resources; an HTTP
|
|
74
|
+
stop acknowledgement or transport failure cannot substitute for that proof.
|
|
73
75
|
A fixture publishes file-based readiness only after its complete evidence is
|
|
74
76
|
atomically visible to the observing process.
|
|
75
77
|
Native certification runs independent fixture files serially on its shared host,
|
|
76
78
|
while each fixture retains the concurrent load required by its examination.
|
|
77
79
|
The certifying producer owns fixture orchestration and loads production adapters
|
|
78
80
|
from the exact installed archive, without modifying that archive's bytes.
|
|
81
|
+
An outer fixture owner removes a temporary package only after the process loading
|
|
82
|
+
its native libraries has closed and the package's native service owners have stopped.
|
|
83
|
+
The final examination proof includes process completion and temporary-file cleanup.
|
|
79
84
|
|
|
80
85
|
Shell owns both the internal `@amalgm/shell` package and the public `amalgm`
|
|
81
86
|
distribution. One tested build supplies both archives; only package identity,
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
/** The archive's owner never loads its native libraries. Only actual worker
|
|
9
|
+
* closure and successful native-owner cleanup permit removing its files. */
|
|
10
|
+
export async function certifyRuntimeProcess(directory, signal,
|
|
11
|
+
script = fileURLToPath(new URL('./runtime-upgrade.native-test.mjs', import.meta.url))) {
|
|
12
|
+
const temporary = await fs.mkdtemp(path.join(os.tmpdir(), 'amalgm-native-runtime-update-'));
|
|
13
|
+
const errors = [];
|
|
14
|
+
let proof = { passed: false };
|
|
15
|
+
try {
|
|
16
|
+
await new Promise((resolve, reject) => {
|
|
17
|
+
// Invoke the worker directly: its close must belong to the process that
|
|
18
|
+
// imports the installed archive, rather than another test-runner parent.
|
|
19
|
+
const env = { ...process.env };
|
|
20
|
+
delete env.NODE_TEST_CONTEXT; // The direct worker reports its own tests, not its parent's binary test protocol.
|
|
21
|
+
const child = spawn(process.execPath, [script, '--runtime-examination-worker', temporary],
|
|
22
|
+
{ env, stdio: 'inherit', windowsHide: true });
|
|
23
|
+
let launchError;
|
|
24
|
+
const cancel = () => child.kill('SIGKILL');
|
|
25
|
+
signal.addEventListener('abort', cancel, { once: true });
|
|
26
|
+
child.once('error', error => { launchError = error; });
|
|
27
|
+
child.once('close', (code, exitSignal) => {
|
|
28
|
+
signal.removeEventListener('abort', cancel);
|
|
29
|
+
if (launchError) reject(launchError);
|
|
30
|
+
else if (signal.aborted) reject(signal.reason);
|
|
31
|
+
else if (code !== 0) reject(new Error(`Native runtime examination process failed (${exitSignal ?? code}); see its output above`));
|
|
32
|
+
else resolve();
|
|
33
|
+
});
|
|
34
|
+
if (signal.aborted) cancel();
|
|
35
|
+
});
|
|
36
|
+
} catch (error) { errors.push(error); }
|
|
37
|
+
try {
|
|
38
|
+
const observed = JSON.parse(await fs.readFile(path.join(temporary, 'proof.json'), 'utf8'));
|
|
39
|
+
assert.ok(observed && typeof observed === 'object' && !Array.isArray(observed), 'The worker must leave an examination proof');
|
|
40
|
+
proof = observed;
|
|
41
|
+
assert.equal(typeof proof.passed, 'boolean');
|
|
42
|
+
assert.equal(proof.cleanupComplete, true, 'Native fixture owners did not prove cleanup; temporary package retained');
|
|
43
|
+
if (!proof.passed) errors.push(new Error('Native runtime examination did not pass'));
|
|
44
|
+
// Database close cannot unload a native DLL; worker close above supplies
|
|
45
|
+
// that lifetime fence. Retry counts do not substitute for this ordering.
|
|
46
|
+
await fs.rm(temporary, { recursive: true, force: true });
|
|
47
|
+
} catch (error) {
|
|
48
|
+
errors.push(error);
|
|
49
|
+
proof.retainedTemporary = temporary;
|
|
50
|
+
}
|
|
51
|
+
proof.passed = proof.passed === true && errors.length === 0;
|
|
52
|
+
if (errors.length) proof.processErrors = errors.map(error => String(error.message));
|
|
53
|
+
try {
|
|
54
|
+
await fs.writeFile(path.join(directory, `runtime-update-${process.platform}.json`), JSON.stringify(proof, null, 2) + '\n');
|
|
55
|
+
} catch (error) { errors.push(error); }
|
|
56
|
+
if (errors.length) throw new AggregateError(errors, 'Native runtime examination process or package cleanup failed');
|
|
57
|
+
return proof;
|
|
58
|
+
}
|
|
@@ -3,7 +3,6 @@ import assert from 'node:assert/strict';
|
|
|
3
3
|
import crypto from 'node:crypto';
|
|
4
4
|
import fs from 'node:fs/promises';
|
|
5
5
|
import { constants, createWriteStream } from 'node:fs';
|
|
6
|
-
import os from 'node:os';
|
|
7
6
|
import path from 'node:path';
|
|
8
7
|
import { createRequire } from 'node:module';
|
|
9
8
|
import { pathToFileURL } from 'node:url';
|
|
@@ -17,6 +16,7 @@ import { examineArchive, hashFile, runIsolated } from '../dist/updates/artifacts
|
|
|
17
16
|
import { runtimeHost } from '../dist/updates/store.js';
|
|
18
17
|
import { canonicalHost, startNetworkView } from './network-view.mjs';
|
|
19
18
|
import { nativeInstalledService } from './native-service.mjs';
|
|
19
|
+
import { certifyRuntimeProcess } from './runtime-examination-process.mjs';
|
|
20
20
|
|
|
21
21
|
const encode = value => Buffer.from(JSON.stringify(value) + '\n');
|
|
22
22
|
const digest = value => crypto.createHash('sha256').update(encode(value)).digest('hex');
|
|
@@ -166,13 +166,15 @@ export function runtimeExaminationView(examination, baselineReceipt) {
|
|
|
166
166
|
return { baseline, candidate, immutable, previous: view(examination.previous), candidateView: view(examination.candidate), events: `${scope}/events` };
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
const workerTemporary = process.argv[2] === '--runtime-examination-worker' ? process.argv[3] : undefined;
|
|
169
170
|
test('the real native Shell service replaces its authorized package and survives OS restart with identity and state intact', {
|
|
170
|
-
skip: process.env.AMALGM_TEST_NATIVE_RUNTIME_UPDATES !== '1', timeout: 1_800_000,
|
|
171
|
+
skip: process.env.AMALGM_TEST_NATIVE_RUNTIME_UPDATES !== '1', timeout: workerTemporary ? undefined : 1_800_000,
|
|
171
172
|
}, async t => {
|
|
172
173
|
assert.equal(process.env.CI, 'true'); assert.equal(process.env.GITHUB_ACTIONS, 'true');
|
|
173
174
|
assert.ok(['darwin', 'linux', 'win32'].includes(process.platform));
|
|
174
175
|
assert.ok(process.env.AMALGM_RUNTIME_EXAMINATION_DIR, 'The producer must supply signed examination inputs');
|
|
175
176
|
const directory = path.resolve(process.env.AMALGM_RUNTIME_EXAMINATION_DIR);
|
|
177
|
+
if (!workerTemporary) return await certifyRuntimeProcess(directory, t.signal);
|
|
176
178
|
const examination = await readJson(path.join(directory, 'examination.json'));
|
|
177
179
|
const baselineReceipt = examination.previous ? null : await readJson(path.join(directory, 'baseline.json'));
|
|
178
180
|
const view = runtimeExaminationView(examination, baselineReceipt);
|
|
@@ -183,8 +185,8 @@ test('the real native Shell service replaces its authorized package and survives
|
|
|
183
185
|
previousDigest: view.baseline.digest, candidateDigest: view.candidate.digest,
|
|
184
186
|
bootstrapProof: examination.previous === null, legacyAdoptionCertified: false, passed: false,
|
|
185
187
|
previousArtifact: artifact, candidateArtifact: nextArtifact, checkpoints: [] };
|
|
186
|
-
const temporary =
|
|
187
|
-
let network; let service;
|
|
188
|
+
const temporary = path.resolve(workerTemporary);
|
|
189
|
+
let network; let service; let initializingFixtures = false;
|
|
188
190
|
t.after(async () => {
|
|
189
191
|
const errors = [];
|
|
190
192
|
if (!proof.passed) {
|
|
@@ -197,9 +199,10 @@ test('the real native Shell service replaces its authorized package and survives
|
|
|
197
199
|
}
|
|
198
200
|
try { await service?.close(); } catch (error) { errors.push(error); }
|
|
199
201
|
try { await network?.close(); } catch (error) { errors.push(error); }
|
|
202
|
+
if (initializingFixtures) errors.push(new Error('Native fixture initialization did not return every cleanup owner'));
|
|
203
|
+
proof.cleanupComplete = errors.length === 0;
|
|
200
204
|
if (errors.length) { proof.passed = false; proof.cleanupErrors = errors.map(error => error.message); }
|
|
201
|
-
await fs.writeFile(path.join(
|
|
202
|
-
await fs.rm(temporary, { recursive: true, force: true, maxRetries: 5 });
|
|
205
|
+
await fs.writeFile(path.join(temporary, 'proof.json'), JSON.stringify(proof, null, 2) + '\n');
|
|
203
206
|
if (errors.length) throw new AggregateError(errors, 'Native runtime examination cleanup failed');
|
|
204
207
|
});
|
|
205
208
|
const archive = path.join(temporary, 'baseline.tgz');
|
|
@@ -217,9 +220,11 @@ test('the real native Shell service replaces its authorized package and survives
|
|
|
217
220
|
assert.deepEqual(shippedRoot, examination.roots[shippedRoot.signed.version - 1], 'The package must carry the actual reviewed trust root');
|
|
218
221
|
const npm = path.join(path.dirname(createRequire(import.meta.url).resolve('npm/package.json')), 'bin/npm-cli.js');
|
|
219
222
|
await runIsolated(process.execPath, [npm, 'ci', '--omit=dev', '--strict-allow-scripts', '--no-audit', '--no-fund'], packageRoot, new AbortController().signal);
|
|
223
|
+
initializingFixtures = true;
|
|
220
224
|
network = await startNetworkView({ temporary, immutable: view.immutable, previous: view.previous, candidate: view.candidateView,
|
|
221
225
|
events: view.events, hint: { id: examination.id, sequence: examination.candidate.target.sequence }, serviceEnvironment: true });
|
|
222
226
|
service = await nativeInstalledService({ packageRoot, label: examination.lane });
|
|
227
|
+
initializingFixtures = false;
|
|
223
228
|
const baseline = await until('Verified installed baseline and native service health', async () => {
|
|
224
229
|
const state = journal(service.identity); const status = await service.status();
|
|
225
230
|
return state.policy?.active?.digest === view.baseline.digest && state.policy.activeHealthy
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amalgm/shell",
|
|
3
|
-
"version": "0.1.164-preview.
|
|
3
|
+
"version": "0.1.164-preview.34240142223",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@amalgm/shell",
|
|
9
|
-
"version": "0.1.164-preview.
|
|
9
|
+
"version": "0.1.164-preview.34240142223",
|
|
10
10
|
"license": "UNLICENSED",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@amalgm/agents": "0.2.3-preview.34176069706",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amalgm/shell",
|
|
3
|
-
"version": "0.1.164-preview.
|
|
3
|
+
"version": "0.1.164-preview.34240142223",
|
|
4
4
|
"description": "The Amalgm runtime host assembled from the published @amalgm SDK capabilities.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -151,11 +151,11 @@
|
|
|
151
151
|
}
|
|
152
152
|
]
|
|
153
153
|
},
|
|
154
|
-
"gitHead": "
|
|
154
|
+
"gitHead": "898af33d9eb9c7b8cfb73cfe8fe0562e486925da",
|
|
155
155
|
"amalgmSource": {
|
|
156
156
|
"repository": "amalgm-inc/amalgm-shell",
|
|
157
157
|
"branch": "preview",
|
|
158
|
-
"commit": "
|
|
159
|
-
"occurrence": "
|
|
158
|
+
"commit": "898af33d9eb9c7b8cfb73cfe8fe0562e486925da",
|
|
159
|
+
"occurrence": "34240142223"
|
|
160
160
|
}
|
|
161
161
|
}
|