Skip to content

Commit b8e9a60

Browse files
test: verify native ledger contention across processes
1 parent 48e9846 commit b8e9a60

4 files changed

Lines changed: 73 additions & 4 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ jobs:
8585
return int(match.group(1)) if match else -1
8686
print(f'installed: {tests} tests, {skipped} skipped, {failed} failed; '
8787
f'native: {count("tests")} tests, {count("skipped")} skipped, {count("fail")} failed')
88-
if tests != 27 or skipped or failed or count('tests') != 19 or count('skipped') or count('fail'):
88+
if tests != 27 or skipped or failed or count('tests') != 20 or count('skipped') or count('fail'):
8989
raise SystemExit('required installed/native Node 24 qualification incomplete')
9090
PY
9191
native-runner-qualification:

‎SHA256SUMS‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
4e32ce77e64227b8fed5cb090cc9bf3ee8e87c8d7f31ef7f6c5a162693cfdf7c .editorconfig
22
46a132840194b21580069ef028caaf88891a2d57f9831c43d2c6b49679f4a835 .gitattributes
33
1247214797e4439805a0a451bf254fb2e25e233ff7e6891489c986b6b2abd7ee .github/required-installed-journeys.txt
4-
f0f46a8b0852aa99709a6370de5fc7176e99cf1513fd0e3a391272267843f12a .github/workflows/ci.yml
4+
0fe2d33ef89ae46efb05680596f6cf20498f7a91d99055745fbd6891119f9130 .github/workflows/ci.yml
55
d4eb80256172fb98928b1c42a953059b3f934e6f2a61ee469cdd50c932f7b04c .gitignore
66
c6a2f8e9584e6d1c5bd21431a01be1fc1f96aa68749efac4293ae9c2ac8629dd AGENTS.md
77
e9f605dec3962bc7dfe2ed17e4406c780dc7a427f39ca2e5d19614d2a166929c BRANDING.md
@@ -494,7 +494,7 @@ ac71fada9c932f4926a037f78e4f0f8ca76539256c6740c901e64fbf1f2a169d references/tem
494494
bc7c8e5369387eb4ec61c8d62cc84ca8edd1a38f8db25928e9b23bbbbf560526 references/template-delivery-session-v1.md
495495
282999ed4cbba58e9bae08531165848b5b2aba05c304f88948686b84fe7aad8f references/template-installation-v1.md
496496
8255b071076e5b84308324602fb396dbaeecba918d08b46942996e91d0b3ffbc references/template-node-connected-v1.md
497-
56c05e1cd7079de3bb8ee07b61a0413bdd6c6ce0b6e2a0b9d51136deef038661 references/template-node-installation-v1.md
497+
f24fa0bade5b2d2057cf5286541a2cd9b0eb726a26b2b00f209f22fac70f49e7 references/template-node-installation-v1.md
498498
38bd959ff5ecdca22e6f10ca2fa85316f7583a3281a512c44efdb1e2aa9a54ba references/template-python-entrypoint-v1.md
499499
0cb19ac97100d8bde0f484dcb5382f51c6e47bc8b70e47e0012427d9c2e8c7ed references/use-case-template-matrix-v1.md
500500
766e3bca7ab51a8ac30bbc0a283b73ffe2354d63dca26325946bc802b30b2ddb references/windows-source-mutation-v1.md
@@ -830,7 +830,7 @@ c78deab0d89125e1debebfcd300930d00850522953ed3ef3e53bf2c2018d5067 tests/independ
830830
6551e1fa27fd8dbd093dbc4cdd839613492f866386cfa400cb27c9ab5288b8ca tests/independent_hosts/work_queue/src/work_queue/runtime.json
831831
5909e532926cde3f8dcd352b476dc3697d8e79dfd0c589001e8c32cf7df5817e tests/integration_helpers.py
832832
50c33d58c287173b673962a0c57ef350d79aaf1e74f70940b5497bfbe8b4604d tests/js_transform.test.cjs
833-
7b01af41c0af3962ba970a2a0a09c4fe4892a06d2a2206359e65a6be65dbd994 tests/native_js_runtime.test.cjs
833+
166604b958321ca7eba8eee8651ab7bb08ceb4abf4603556db29bb0c31845fb8 tests/native_js_runtime.test.cjs
834834
8d9dbc31c83b66a17cf83add6acfd7995b998daedafedd6e94f1a70bd1320a1f tests/parent_death_probe.py
835835
55375adeab12024e5e249b53c2cb0c7ff4261044fa06a64891987bf439c98821 tests/path_corpus/README.md
836836
523e0a5e55faa7704965e07a01e80e3d8b28cdfd496e6ec0042fb9b7035f9813 tests/path_corpus/deterministic_only/expected.json

‎references/template-node-installation-v1.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,3 +420,13 @@ Node session adds bounded offline process ownership and observation. Required
420420
full regressions, connected
421421
authority, async rejection/cancellation and cross-placement budget coverage
422422
remain pending before a complete issue #60 claim.
423+
424+
The native runtime test `separate native processes cannot reset a held ledger
425+
or replay its settled effects` exercises two actual Node 24.18.0 processes
426+
against one private durable ledger. The peer is refused while the first owner
427+
holds the SQLite exclusive lock. After release, the peer retains the original
428+
charge and settled invocation tombstone, consumes the last reservation, and
429+
refuses further calls; the original process then observes the exhausted budget.
430+
This is offline ledger contention evidence. It does not establish concurrent
431+
distributed execution, installed cross-package composition, provider operation,
432+
or a selected installed application effect.

‎tests/native_js_runtime.test.cjs‎

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,65 @@ test('durable native ledger retains charges and refuses unresolved or revoked re
353353
}
354354
});
355355

356+
test('separate native processes cannot reset a held ledger or replay its settled effects',
357+
{skip: process.platform !== 'linux' || process.version !== 'v24.18.0'}, () => {
358+
const {spawnSync} = require('node:child_process');
359+
const parent = fs.mkdtempSync(path.join(os.tmpdir(), 'jev-node-contention-'));
360+
fs.chmodSync(parent, 0o700);
361+
const ledgerPath = path.join(parent, 'ledger.sqlite');
362+
const limits = {max_calls: 2, max_cost: 2};
363+
const identity = 'a'.repeat(64);
364+
const runtime = require.resolve('../jev_integration_evaluator/data/native_js_runtime.cjs');
365+
const child = mode => {
366+
const result = spawnSync(process.execPath, ['-e', `
367+
const assert = require('node:assert/strict');
368+
const {DurableSharedBudget} = require(process.argv[1]);
369+
const args = JSON.parse(process.argv[2]);
370+
if (process.argv[3] === 'contend') {
371+
assert.throws(() => new DurableSharedBudget(args), /locked/);
372+
} else {
373+
const ledger = new DurableSharedBudget(args);
374+
try {
375+
assert.equal(ledger.calls, 1);
376+
assert.equal(ledger.cost, 1);
377+
assert.throws(() => ledger.claimInvocation('one', 'first', 'candidate'),
378+
/effect_replay_denied/);
379+
const reservation = ledger.reserve('two', 1);
380+
ledger.finishReservation(reservation);
381+
assert.throws(() => ledger.reserve('three', 1), /budget_denied/);
382+
assert.equal(ledger.calls, 2);
383+
assert.equal(ledger.cost, 2);
384+
} finally { ledger.close(); }
385+
}
386+
process.stdout.write('verified');
387+
`, runtime, JSON.stringify({limits, ledgerPath, identity}), mode],
388+
{encoding: 'utf8', timeout: 10000, env: {PATH: process.env.PATH}});
389+
assert.equal(result.error, undefined);
390+
assert.equal(result.status, 0, result.stderr);
391+
assert.equal(result.stdout, 'verified');
392+
};
393+
let ledger;
394+
try {
395+
ledger = new DurableSharedBudget({limits, ledgerPath, identity});
396+
const reservation = ledger.reserve('one', 1);
397+
ledger.finishReservation(reservation);
398+
ledger.claimInvocation('one', 'first', 'candidate');
399+
ledger.settleInvocation('one', 'first', 'candidate');
400+
child('contend'); // a real peer process must fail before it can reserve
401+
assert.equal(ledger.calls, 1);
402+
assert.equal(ledger.cost, 1);
403+
ledger.close(); ledger = null;
404+
child('resume'); // release transfers ownership, never a fresh budget
405+
ledger = new DurableSharedBudget({limits, ledgerPath, identity});
406+
assert.equal(ledger.calls, 2);
407+
assert.equal(ledger.cost, 2);
408+
assert.throws(() => ledger.reserve('four', 1), /budget_denied/);
409+
} finally {
410+
if (ledger) ledger.close();
411+
fs.rmSync(parent, {recursive: true, force: true});
412+
}
413+
});
414+
356415
test('connected shadow owner checks independent grant and installed bytes before fake transport',
357416
{skip: process.platform !== 'linux' || process.version !== 'v24.18.0'}, async () => {
358417
const parent = fs.mkdtempSync(path.join(os.tmpdir(), 'jev-connected-shadow-'));

0 commit comments

Comments
 (0)