cabloy 5.1.216 → 5.1.218
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/.cabloy-version +1 -1
- package/.github/workflows/create-cabloy.yml +137 -0
- package/.github/workflows/playwright-e2e.yml +163 -13
- package/.github/workflows/vona-test-sqlite3.yml +77 -7
- package/CHANGELOG.md +22 -0
- package/CONTRIBUTING.md +4 -2
- package/package.json +1 -1
- package/repo-docs/frontend/scripts.md +2 -0
- package/repo-docs/reference/repo-scripts.md +27 -1
- package/repo-e2e/scripts/e2e.test.ts +1 -1
- package/repo-e2e/scripts/managedTest.test.ts +115 -3
- package/repo-e2e/scripts/managedTest.ts +69 -20
- package/repo-e2e/scripts/managedTestImports.test.ts +55 -5
- package/repo-e2e/scripts/runE2e.test.ts +109 -0
- package/repo-e2e/scripts/runE2e.ts +58 -39
- package/repo-e2e/scripts/runE2eArgs.test.ts +16 -1
- package/repo-e2e/scripts/runE2eArgs.ts +5 -0
- package/repo-e2e/specs/a-commerce.spec.ts +10 -2
- package/repo-e2e/specs/home-user-account.spec.ts +4 -0
- package/repo-e2e/specs/markdown.spec.ts +14 -3
- package/scripts/generateOpenapiIsolated.test.ts +5 -1
- package/scripts/runIsolatedTest.test.ts +403 -36
- package/scripts/runIsolatedTest.ts +139 -12
- package/scripts/testBrowserGuardianWindows.test.ts +327 -0
- package/scripts/testClaimCleanup.test.ts +25 -9
- package/scripts/testClaimCleanup.ts +14 -45
- package/scripts/testClaimProcesses.test.ts +81 -2
- package/scripts/testClaimProcesses.ts +46 -10
- package/scripts/testProcessGuardianWindows.test.ts +602 -0
- package/scripts/testProcessGuardianWindows.ts +155 -0
- package/vona/packages-cli/cli/package.json +1 -1
- package/vona/packages-cli/cli-set-api/package.json +1 -1
- package/vona/packages-cli/cli-set-api/src/lib/bean/cli.bin.build.ts +4 -1
- package/vona/packages-cli/cli-set-api/test/binBuildEntries.test.ts +159 -0
- package/vona/packages-vona/vona/package.json +1 -1
- package/vona/packages-vona/vona-core/package.json +1 -1
- package/vona/packages-vona/vona-core/src/lib/core/testClaim.test.ts +298 -17
- package/vona/packages-vona/vona-core/src/lib/core/testClaim.ts +205 -97
- package/vona/packages-vona/vona-core/src/lib/core/testClaimFilesystem.test.ts +247 -0
- package/vona/packages-vona/vona-core/src/lib/core/testClaimFilesystem.ts +224 -0
- package/vona/packages-vona/vona-core/src/lib/core/testClaimFilesystemWindows.cs +229 -0
- package/vona/packages-vona/vona-core/src/lib/core/testClaimFilesystemWindows.ps1 +20 -0
- package/vona/packages-vona/vona-core/src/lib/core/testClaimFilesystemWindows.ts +200 -0
- package/vona/packages-vona/vona-core/src/lib/core/testProcessWindowsNative.cs +336 -0
- package/vona/packages-vona/vona-core/src/lib/core/testProcessWindowsNative.ps1 +20 -0
- package/vona/packages-vona/vona-core/src/lib/core/testProcessWindowsNative.ts +531 -0
- package/vona/packages-vona/vona-core/src/lib/framework/bootstrap.test.ts +61 -0
- package/vona/packages-vona/vona-core/src/lib/framework/index.ts +1 -0
- package/vona/packages-vona/vona-core/src/lib/framework/processWorker.test.ts +59 -0
- package/vona/packages-vona/vona-core/src/lib/framework/processWorker.ts +8 -5
- package/vona/packages-vona/vona-core/src/lib/framework/useApp.test.ts +84 -0
- package/vona/packages-vona/vona-core/src/lib/framework/useApp.ts +2 -2
- package/vona/packages-vona/vona-core/src/lib/utils/util.ts +1 -1
- package/vona/packages-vona/vona-mock/package.json +1 -1
- package/vona/pnpm-lock.yaml +42 -181
- package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/package.json +1 -1
- package/vona/src/suite-vendor/a-cabloy/package.json +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-executor/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-executor/src/bean/bean.executor.ts +2 -1
- package/vona/src/suite-vendor/a-vona/modules/a-logger/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-logger/src/bean/middlewareSystem.httpLog.ts +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-logger/src/lib/redactIsolatedReadyHeader.test.ts +16 -0
- package/vona/src/suite-vendor/a-vona/modules/a-logger/src/lib/redactIsolatedReadyHeader.ts +6 -3
- package/vona/src/suite-vendor/a-vona/modules/a-logger/test/middlewareSystem.httpLog.test.ts +80 -0
- package/vona/src/suite-vendor/a-vona/modules/a-metrics/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-metrics/src/bean/middlewareSystem.health.ts +73 -2
- package/vona/src/suite-vendor/a-vona/modules/a-metrics/test/middlewareSystem.health.test.ts +309 -0
- package/vona/src/suite-vendor/a-vona/modules/a-orm/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/service/transactionAsyncLocalStorage_.ts +3 -3
- package/vona/src/suite-vendor/a-vona/modules/a-permission/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-static/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-static/src/bean/middlewareSystem.static.ts +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-static/test/middlewareSystem.static.test.ts +101 -0
- package/vona/src/suite-vendor/a-vona/package.json +1 -1
- package/zova/package.original.json +1 -1
- package/zova/packages-cli/cli/package.json +2 -2
- package/zova/packages-cli/cli-set-front/package.json +4 -4
- package/zova/packages-utils/zova-jsx/package.json +2 -2
- package/zova/packages-utils/zova-vite/package.json +2 -2
- package/zova/packages-utils/zova-vite/src/configUtils.ts +1 -1
- package/zova/packages-utils/zova-vite/test/configUtils.test.ts +55 -0
- package/zova/packages-zova/zova/package.json +3 -3
- package/zova/packages-zova/zova-core/package.json +2 -2
- package/zova/pnpm-lock.yaml +324 -63
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/model/cart.ts +8 -4
- package/zova/src/suite/a-commerce/modules/commerce-trade/test/lib/cartModel.test.ts +71 -0
- package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +2 -2
- package/zova/src/suite-vendor/a-zova/package.json +2 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import '@cabloy/json5';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
|
+
import { execFileSync } from 'node:child_process';
|
|
3
4
|
import {
|
|
4
5
|
chmodSync,
|
|
5
6
|
lstatSync,
|
|
@@ -14,9 +15,12 @@ import { hostname, tmpdir } from 'node:os';
|
|
|
14
15
|
import path from 'node:path';
|
|
15
16
|
import { test } from 'node:test';
|
|
16
17
|
|
|
18
|
+
import type { TestClaimEnv } from './testClaim.ts';
|
|
19
|
+
|
|
17
20
|
import {
|
|
18
21
|
assertTestClaimDatabaseClient,
|
|
19
22
|
assertTestClaimRedisClient,
|
|
23
|
+
assertTestClaimWindowsJob,
|
|
20
24
|
buildTestClaimResourceInventory,
|
|
21
25
|
createTestClaim,
|
|
22
26
|
getTestClaimCreatedAt,
|
|
@@ -28,13 +32,28 @@ import {
|
|
|
28
32
|
normalizeTestClaimRedisEndpoint,
|
|
29
33
|
registerTestClaimDatabaseCreation,
|
|
30
34
|
registerTestClaimResourceInventory,
|
|
35
|
+
registerTestClaimWindowsJobVerifier,
|
|
31
36
|
sendTestClaimEvent,
|
|
37
|
+
TEST_CLAIM_WINDOWS_GUARDIAN_ENV,
|
|
32
38
|
startTestClaimBroker,
|
|
33
39
|
validateTestClaim,
|
|
34
40
|
writeTestClaim,
|
|
35
41
|
} from './testClaim.ts';
|
|
42
|
+
import { assertTestClaimPrivateDirectory, readTestClaimFile } from './testClaimFilesystem.ts';
|
|
36
43
|
import { getTestAppName } from './testIsolation.ts';
|
|
37
44
|
|
|
45
|
+
function setUnsafePermissions(file: string, directory = false) {
|
|
46
|
+
if (process.platform === 'win32') {
|
|
47
|
+
// icacls receives exact argv: no CMD/PowerShell string interpolation.
|
|
48
|
+
execFileSync('icacls.exe', [file, '/grant', '*S-1-1-0:(R)'], {
|
|
49
|
+
windowsHide: true,
|
|
50
|
+
stdio: 'pipe',
|
|
51
|
+
});
|
|
52
|
+
} else {
|
|
53
|
+
chmodSync(file, directory ? 0o755 : 0o644);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
38
57
|
const runId = '20261013010203-a1b2c3d4';
|
|
39
58
|
const owner = { pid: process.pid, startTime: 'test-process-start', host: hostname() };
|
|
40
59
|
function fixture() {
|
|
@@ -79,7 +98,7 @@ function appFixture(projectPath: string) {
|
|
|
79
98
|
},
|
|
80
99
|
};
|
|
81
100
|
}
|
|
82
|
-
function transportEnv(env:
|
|
101
|
+
function transportEnv(env: TestClaimEnv): TestClaimEnv {
|
|
83
102
|
return { ...env, META_MODE: 'test', VONA_TEST_RUN_ID: runId };
|
|
84
103
|
}
|
|
85
104
|
|
|
@@ -90,8 +109,13 @@ test('exclusive central claim has canonical immutable UTC creation time and priv
|
|
|
90
109
|
assert.equal(claimPath, path.join(f.projectPath, '.vona-test-claim', `${runId}.json`));
|
|
91
110
|
assert.equal(manifest.createdAt, '2026-10-13T01:02:03.000Z');
|
|
92
111
|
assert.ok(JSON.parse('{"createdAt":"2026-10-13T01:02:03.000Z"}').createdAt instanceof Date);
|
|
93
|
-
|
|
94
|
-
|
|
112
|
+
if (process.platform === 'win32') {
|
|
113
|
+
assert.equal(await assertTestClaimPrivateDirectory(path.dirname(claimPath), false), true);
|
|
114
|
+
assert.equal(typeof (await readTestClaimFile(claimPath)), 'string');
|
|
115
|
+
} else {
|
|
116
|
+
assert.equal(lstatSync(claimPath).mode & 0o777, 0o600);
|
|
117
|
+
assert.equal(lstatSync(path.dirname(claimPath)).mode & 0o777, 0o700);
|
|
118
|
+
}
|
|
95
119
|
await assert.rejects(createTestClaim(f.projectPath, runId, 'unit', owner), { code: 'EEXIST' });
|
|
96
120
|
const contractClaim = await createTestClaim(
|
|
97
121
|
f.projectPath,
|
|
@@ -121,14 +145,19 @@ test('claim creation rejects symlink paths and permissive shared directories', a
|
|
|
121
145
|
try {
|
|
122
146
|
const directory = getTestClaimPaths(f.projectPath, runId).claimDirectory;
|
|
123
147
|
mkdirSync(path.join(f.root, 'outside'));
|
|
124
|
-
symlinkSync(
|
|
148
|
+
symlinkSync(
|
|
149
|
+
path.join(f.root, 'outside'),
|
|
150
|
+
directory,
|
|
151
|
+
process.platform === 'win32' ? 'junction' : 'dir',
|
|
152
|
+
);
|
|
125
153
|
await assert.rejects(createTestClaim(f.projectPath, runId, 'unit', owner), /Unsafe/);
|
|
126
154
|
rmSync(directory, { recursive: true, force: true });
|
|
127
155
|
mkdirSync(directory, { mode: 0o755 });
|
|
156
|
+
setUnsafePermissions(directory, true);
|
|
128
157
|
await assert.rejects(createTestClaim(f.projectPath, runId, 'unit', owner), /Unsafe/);
|
|
129
158
|
rmSync(directory, { recursive: true, force: true });
|
|
130
159
|
const { claimPath } = await createTestClaim(f.projectPath, runId, 'unit', owner);
|
|
131
|
-
|
|
160
|
+
setUnsafePermissions(claimPath);
|
|
132
161
|
await assert.rejects(loadTestClaim(claimPath), /Unsafe/);
|
|
133
162
|
} finally {
|
|
134
163
|
f.dispose();
|
|
@@ -211,12 +240,17 @@ test('normalization whitelists endpoint identity and exact physical Redis prefix
|
|
|
211
240
|
tls: false,
|
|
212
241
|
db: 0,
|
|
213
242
|
});
|
|
214
|
-
|
|
243
|
+
const socketPath = path.resolve('/run/redis.sock');
|
|
244
|
+
assert.deepEqual(normalizeTestClaimRedisEndpoint({ path: socketPath, db: 2 }), {
|
|
215
245
|
transport: 'socket',
|
|
216
|
-
socketPath
|
|
246
|
+
socketPath,
|
|
217
247
|
db: 2,
|
|
218
248
|
tls: false,
|
|
219
249
|
});
|
|
250
|
+
assert.throws(() => normalizeTestClaimRedisEndpoint({ path: 'relative/redis.sock' }), /socket/);
|
|
251
|
+
if (process.platform === 'win32') {
|
|
252
|
+
assert.throws(() => normalizeTestClaimRedisEndpoint({ path: '/run/redis.sock' }), /socket/);
|
|
253
|
+
}
|
|
220
254
|
});
|
|
221
255
|
|
|
222
256
|
test('normalization rejects unresolved or custom targets and captures pg socket port', () => {
|
|
@@ -229,24 +263,37 @@ test('normalization rejects unresolved or custom targets and captures pg socket
|
|
|
229
263
|
),
|
|
230
264
|
/explicit/,
|
|
231
265
|
);
|
|
266
|
+
const socketPath = path.resolve('/run/postgresql');
|
|
267
|
+
const socketEndpoint = {
|
|
268
|
+
dialect: 'pg',
|
|
269
|
+
transport: 'socket',
|
|
270
|
+
socketPath,
|
|
271
|
+
port: 5433,
|
|
272
|
+
tls: false,
|
|
273
|
+
adminDatabase: 'postgres',
|
|
274
|
+
};
|
|
232
275
|
assert.deepEqual(
|
|
233
276
|
normalizeTestClaimDatabaseEndpoint(
|
|
234
|
-
{ client: 'pg', connection: {
|
|
277
|
+
{ client: 'pg', connection: { socketPath, port: 5433 } },
|
|
235
278
|
'/vona',
|
|
236
279
|
runId,
|
|
237
280
|
),
|
|
238
|
-
|
|
239
|
-
dialect: 'pg',
|
|
240
|
-
transport: 'socket',
|
|
241
|
-
socketPath: '/run/postgresql',
|
|
242
|
-
port: 5433,
|
|
243
|
-
tls: false,
|
|
244
|
-
adminDatabase: 'postgres',
|
|
245
|
-
},
|
|
281
|
+
socketEndpoint,
|
|
246
282
|
);
|
|
283
|
+
const unixHost = { client: 'pg', connection: { host: '/run/postgresql', port: 5433 } };
|
|
284
|
+
if (process.platform === 'win32') {
|
|
285
|
+
assert.throws(() => normalizeTestClaimDatabaseEndpoint(unixHost, '/vona', runId), /socket/);
|
|
286
|
+
} else {
|
|
287
|
+
assert.deepEqual(normalizeTestClaimDatabaseEndpoint(unixHost, '/vona', runId), socketEndpoint);
|
|
288
|
+
}
|
|
289
|
+
for (const connection of [{ socketPath }, { host: '/run/postgresql' }]) {
|
|
290
|
+
assert.throws(() =>
|
|
291
|
+
normalizeTestClaimDatabaseEndpoint({ client: 'pg', connection }, '/vona', runId),
|
|
292
|
+
);
|
|
293
|
+
}
|
|
247
294
|
assert.throws(() =>
|
|
248
295
|
normalizeTestClaimDatabaseEndpoint(
|
|
249
|
-
{ client: 'pg', connection: {
|
|
296
|
+
{ client: 'pg', connection: { socketPath: 'relative/postgresql', port: 5433 } },
|
|
250
297
|
'/vona',
|
|
251
298
|
runId,
|
|
252
299
|
),
|
|
@@ -376,6 +423,240 @@ test('database confirmations and process registration cannot precede allocation
|
|
|
376
423
|
}
|
|
377
424
|
});
|
|
378
425
|
|
|
426
|
+
test('Windows process claim identity is explicit, write-ahead and immutable without fabricated pgid', async () => {
|
|
427
|
+
const f = fixture();
|
|
428
|
+
let broker: Awaited<ReturnType<typeof startTestClaimBroker>> | undefined;
|
|
429
|
+
try {
|
|
430
|
+
const { claimPath, manifest } = await createTestClaim(f.projectPath, runId, 'unit', owner);
|
|
431
|
+
const pending = {
|
|
432
|
+
id: 'windows-guardian',
|
|
433
|
+
role: 'guardian',
|
|
434
|
+
stage: 'pending' as const,
|
|
435
|
+
containment: 'windows-job' as const,
|
|
436
|
+
jobId: '12345678-1234-1234-1234-123456789abc',
|
|
437
|
+
};
|
|
438
|
+
validateTestClaim({ ...manifest, processes: [pending] });
|
|
439
|
+
for (const invalid of [
|
|
440
|
+
{ ...pending, pgid: 42 },
|
|
441
|
+
{ ...pending, containment: undefined },
|
|
442
|
+
{ ...pending, role: 'browser' },
|
|
443
|
+
{ ...pending, jobId: 'not-a-job' },
|
|
444
|
+
{ ...pending, stage: 'registered', pid: 42, startTime: 'posix-start', host: hostname() },
|
|
445
|
+
{ ...pending, stage: 'registered', pid: 42, startTime: '133000000000000000' },
|
|
446
|
+
])
|
|
447
|
+
assert.throws(() => validateTestClaim({ ...manifest, processes: [invalid] }));
|
|
448
|
+
broker = await startTestClaimBroker(claimPath);
|
|
449
|
+
await broker.update({ type: 'process', process: pending });
|
|
450
|
+
await assert.rejects(
|
|
451
|
+
broker.update({ type: 'process', process: { ...pending, stage: 'drained' } }),
|
|
452
|
+
/Pending/,
|
|
453
|
+
);
|
|
454
|
+
const registered = {
|
|
455
|
+
...pending,
|
|
456
|
+
stage: 'registered' as const,
|
|
457
|
+
pid: 42,
|
|
458
|
+
startTime: '133000000000000000',
|
|
459
|
+
host: hostname(),
|
|
460
|
+
};
|
|
461
|
+
await broker.update({ type: 'process', process: registered });
|
|
462
|
+
await assert.rejects(
|
|
463
|
+
broker.update({
|
|
464
|
+
type: 'process',
|
|
465
|
+
process: { ...registered, jobId: '12345678-1234-1234-1234-123456789abd' },
|
|
466
|
+
}),
|
|
467
|
+
/identity/,
|
|
468
|
+
);
|
|
469
|
+
await assert.rejects(
|
|
470
|
+
broker.update({
|
|
471
|
+
type: 'process',
|
|
472
|
+
process: { ...registered, containment: undefined, jobId: undefined, pgid: 42 },
|
|
473
|
+
}),
|
|
474
|
+
/identity/,
|
|
475
|
+
);
|
|
476
|
+
await broker.update({ type: 'process', process: { ...registered, stage: 'drained' } });
|
|
477
|
+
const processClaim = (await loadTestClaim(claimPath)).processes[0];
|
|
478
|
+
assert.equal(processClaim.stage, 'drained');
|
|
479
|
+
assert.equal(processClaim.pgid, undefined);
|
|
480
|
+
} finally {
|
|
481
|
+
await broker?.close();
|
|
482
|
+
f.dispose();
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
test('Windows containment query is authenticated, transient, E2E-only and requires the exact live registered verifier', async () => {
|
|
487
|
+
for (const kind of ['e2e', 'unit', 'cov', 'contract'] as const) {
|
|
488
|
+
const f = fixture();
|
|
489
|
+
let broker: Awaited<ReturnType<typeof startTestClaimBroker>> | undefined;
|
|
490
|
+
try {
|
|
491
|
+
const { claimPath } = await createTestClaim(f.projectPath, runId, kind, owner);
|
|
492
|
+
broker = await startTestClaimBroker(claimPath);
|
|
493
|
+
const pending = {
|
|
494
|
+
id: 'windows-e2e-guardian',
|
|
495
|
+
role: 'guardian',
|
|
496
|
+
stage: 'pending' as const,
|
|
497
|
+
containment: 'windows-job' as const,
|
|
498
|
+
jobId: '12345678-1234-1234-1234-123456789abc',
|
|
499
|
+
};
|
|
500
|
+
const registered = {
|
|
501
|
+
...pending,
|
|
502
|
+
stage: 'registered' as const,
|
|
503
|
+
pid: 42,
|
|
504
|
+
startTime: '133000000000000000',
|
|
505
|
+
host: hostname(),
|
|
506
|
+
};
|
|
507
|
+
const identity = { pid: 123, startTime: '133000000000000001', host: hostname() };
|
|
508
|
+
const env: TestClaimEnv = {
|
|
509
|
+
...transportEnv(broker.env),
|
|
510
|
+
[TEST_CLAIM_WINDOWS_GUARDIAN_ENV]: pending.id,
|
|
511
|
+
};
|
|
512
|
+
const denied = (environment = env, processIdentity = identity) =>
|
|
513
|
+
assert.rejects(
|
|
514
|
+
assertTestClaimWindowsJob(environment, processIdentity),
|
|
515
|
+
/containment verification failed/,
|
|
516
|
+
);
|
|
517
|
+
await denied();
|
|
518
|
+
await assert.rejects(
|
|
519
|
+
registerTestClaimWindowsJobVerifier(env, registered, async () => true),
|
|
520
|
+
/durable/,
|
|
521
|
+
);
|
|
522
|
+
await broker.update({ type: 'process', process: pending });
|
|
523
|
+
await denied();
|
|
524
|
+
await broker.update({ type: 'process', process: registered });
|
|
525
|
+
await denied(); // A durable record and copied locator are not a live capability.
|
|
526
|
+
let calls = 0;
|
|
527
|
+
const remove = await registerTestClaimWindowsJobVerifier(env, registered, async target => {
|
|
528
|
+
calls++;
|
|
529
|
+
return target.pid === identity.pid && target.startTime === identity.startTime;
|
|
530
|
+
});
|
|
531
|
+
const before = readFileSync(claimPath, 'utf8');
|
|
532
|
+
if (kind === 'e2e') {
|
|
533
|
+
await assertTestClaimWindowsJob(env, identity);
|
|
534
|
+
assert.equal(calls, 1);
|
|
535
|
+
await denied({ ...env, VONA_TEST_CLAIM_TOKEN: '0'.repeat(64) });
|
|
536
|
+
await denied({ ...env, VONA_TEST_RUN_ID: '20261013010203-deadbeef' });
|
|
537
|
+
await denied({ ...env, [TEST_CLAIM_WINDOWS_GUARDIAN_ENV]: 'copied-locator' });
|
|
538
|
+
await denied(env, { ...identity, host: 'other-host' });
|
|
539
|
+
assert.equal(calls, 1);
|
|
540
|
+
await denied(env, { ...identity, pid: 124 });
|
|
541
|
+
await denied(env, { ...identity, startTime: '133000000000000002' });
|
|
542
|
+
await denied(env, { ...identity, startTime: 'not-FILETIME' });
|
|
543
|
+
assert.equal(calls, 3);
|
|
544
|
+
} else {
|
|
545
|
+
await denied();
|
|
546
|
+
assert.equal(calls, 0);
|
|
547
|
+
}
|
|
548
|
+
assert.equal(readFileSync(claimPath, 'utf8'), before);
|
|
549
|
+
remove();
|
|
550
|
+
await denied();
|
|
551
|
+
const removeFailing = await registerTestClaimWindowsJobVerifier(env, registered, async () => {
|
|
552
|
+
throw new Error('helper lost');
|
|
553
|
+
});
|
|
554
|
+
await denied();
|
|
555
|
+
removeFailing();
|
|
556
|
+
const removeDrained = await registerTestClaimWindowsJobVerifier(
|
|
557
|
+
env,
|
|
558
|
+
registered,
|
|
559
|
+
async () => true,
|
|
560
|
+
);
|
|
561
|
+
await broker.update({ type: 'process', process: { ...registered, stage: 'drained' } });
|
|
562
|
+
await denied();
|
|
563
|
+
removeDrained();
|
|
564
|
+
const manifest = await loadTestClaim(claimPath);
|
|
565
|
+
assert.equal(manifest.version, 1);
|
|
566
|
+
assert.equal(manifest.processes.length, 1);
|
|
567
|
+
assert.equal(manifest.processes[0].role, 'guardian');
|
|
568
|
+
} finally {
|
|
569
|
+
await broker?.close();
|
|
570
|
+
f.dispose();
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
test('containment response cannot outlive verifier revocation or a durable drained transition', async () => {
|
|
576
|
+
const f = fixture();
|
|
577
|
+
let broker: Awaited<ReturnType<typeof startTestClaimBroker>> | undefined;
|
|
578
|
+
try {
|
|
579
|
+
const { claimPath } = await createTestClaim(f.projectPath, runId, 'e2e', owner);
|
|
580
|
+
broker = await startTestClaimBroker(claimPath);
|
|
581
|
+
const pending = {
|
|
582
|
+
id: 'guardian',
|
|
583
|
+
role: 'guardian',
|
|
584
|
+
stage: 'pending' as const,
|
|
585
|
+
containment: 'windows-job' as const,
|
|
586
|
+
jobId: '12345678-1234-1234-1234-123456789abc',
|
|
587
|
+
};
|
|
588
|
+
const registered = {
|
|
589
|
+
...pending,
|
|
590
|
+
stage: 'registered' as const,
|
|
591
|
+
pid: 42,
|
|
592
|
+
startTime: '133000000000000000',
|
|
593
|
+
host: hostname(),
|
|
594
|
+
};
|
|
595
|
+
const env = { ...transportEnv(broker.env), [TEST_CLAIM_WINDOWS_GUARDIAN_ENV]: pending.id };
|
|
596
|
+
await broker.update({ type: 'process', process: pending });
|
|
597
|
+
await broker.update({ type: 'process', process: registered });
|
|
598
|
+
for (const revoke of ['local', 'durable']) {
|
|
599
|
+
let entered!: () => void;
|
|
600
|
+
const entry = new Promise<void>(resolve => {
|
|
601
|
+
entered = resolve;
|
|
602
|
+
});
|
|
603
|
+
let release!: (contained: boolean) => void;
|
|
604
|
+
const remove = await registerTestClaimWindowsJobVerifier(env, registered, async () => {
|
|
605
|
+
entered();
|
|
606
|
+
return await new Promise<boolean>(resolve => {
|
|
607
|
+
release = resolve;
|
|
608
|
+
});
|
|
609
|
+
});
|
|
610
|
+
const queried = assertTestClaimWindowsJob(env, {
|
|
611
|
+
pid: 123,
|
|
612
|
+
startTime: '133000000000000001',
|
|
613
|
+
host: hostname(),
|
|
614
|
+
});
|
|
615
|
+
const rejected = assert.rejects(queried, /containment verification failed/);
|
|
616
|
+
await entry;
|
|
617
|
+
if (revoke === 'local') remove();
|
|
618
|
+
else await broker.update({ type: 'process', process: { ...registered, stage: 'drained' } });
|
|
619
|
+
release(true);
|
|
620
|
+
await rejected;
|
|
621
|
+
remove();
|
|
622
|
+
}
|
|
623
|
+
assert.equal((await loadTestClaim(claimPath)).processes.length, 1);
|
|
624
|
+
} finally {
|
|
625
|
+
await broker?.close();
|
|
626
|
+
f.dispose();
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
test('containment cannot be registered through persisted events or a copied remote broker transport', async () => {
|
|
631
|
+
await assert.rejects(assertTestClaimWindowsJob({}, owner), /containment verification failed/);
|
|
632
|
+
const f = fixture();
|
|
633
|
+
let broker: Awaited<ReturnType<typeof startTestClaimBroker>> | undefined;
|
|
634
|
+
try {
|
|
635
|
+
const { claimPath } = await createTestClaim(f.projectPath, runId, 'e2e', owner);
|
|
636
|
+
broker = await startTestClaimBroker(claimPath);
|
|
637
|
+
const env = transportEnv(broker.env);
|
|
638
|
+
const response = await fetch(env.VONA_TEST_CLAIM_URL!, {
|
|
639
|
+
method: 'POST',
|
|
640
|
+
headers: {
|
|
641
|
+
'authorization': `Bearer ${env.VONA_TEST_CLAIM_TOKEN}`,
|
|
642
|
+
'content-type': 'application/json',
|
|
643
|
+
},
|
|
644
|
+
body: JSON.stringify({ type: 'containment', guardianId: 'guardian', process: owner }),
|
|
645
|
+
});
|
|
646
|
+
assert.equal(response.status, 409);
|
|
647
|
+
const script = `import {registerTestClaimWindowsJobVerifier} from ${JSON.stringify(new URL('./testClaim.ts', import.meta.url).href)};
|
|
648
|
+
try { await registerTestClaimWindowsJobVerifier(JSON.parse(process.argv[1]),{},async()=>true); process.exit(1); }
|
|
649
|
+
catch(error) { if(!/local claim broker/.test(error.message)) throw error; }`;
|
|
650
|
+
execFileSync(process.execPath, ['--input-type=module', '-e', script, JSON.stringify(env)], {
|
|
651
|
+
stdio: 'pipe',
|
|
652
|
+
});
|
|
653
|
+
assert.equal((await loadTestClaim(claimPath)).processes.length, 0);
|
|
654
|
+
} finally {
|
|
655
|
+
await broker?.close();
|
|
656
|
+
f.dispose();
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
|
|
379
660
|
test('cleanup editor persists partial progress but may not replace claim identity', async () => {
|
|
380
661
|
const f = fixture();
|
|
381
662
|
try {
|