Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/smoke-node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
}
},
"dependencies": {
"@blue-quickjs/deterministic-bundler": "workspace:*",
"@blue-quickjs/dv": "workspace:*",
"@blue-quickjs/quickjs-runtime": "workspace:*",
"@blue-quickjs/test-harness": "workspace:*",
Expand Down
41 changes: 41 additions & 0 deletions apps/smoke-node/src/lib/chess-library-reuse.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { bundleDeterministicProgram } from '@blue-quickjs/deterministic-bundler';
import path from 'node:path';
import {
CHESS_E2E6_EXPECTED_LEGAL,
CHESS_LIBRARY_ENTRY_PATH,
CHESS_LIBRARY_GAS_LIMIT,
CHESS_LIBRARY_INPUT,
CHESS_LIBRARY_MANIFEST,
CHESS_LIBRARY_PROGRAM_BASE,
createDeterminismHost,
} from '@blue-quickjs/test-harness';
import { evaluate } from '@blue-quickjs/quickjs-runtime';

describe('smoke-node chess.js reuse', () => {
it('bundles chess.js and checks e2e6 legality', async () => {
const workspaceRoot = path.resolve(process.cwd(), '../..');
const bundled = await bundleDeterministicProgram({
absWorkingDir: workspaceRoot,
entryPath: CHESS_LIBRARY_ENTRY_PATH,
profile: 'compat-regexp-v1',
});

const host = createDeterminismHost();
const result = await evaluate({
program: {
...CHESS_LIBRARY_PROGRAM_BASE,
code: bundled.code,
},
input: CHESS_LIBRARY_INPUT,
gasLimit: CHESS_LIBRARY_GAS_LIMIT,
manifest: CHESS_LIBRARY_MANIFEST,
handlers: host.handlers,
});

expect(result.ok).toBe(true);
if (!result.ok) {
throw new Error(result.message);
}
expect(result.value).toBe(CHESS_E2E6_EXPECTED_LEGAL);
});
});
3 changes: 3 additions & 0 deletions apps/smoke-node/tsconfig.lib.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
},
{
"path": "../../libs/dv/tsconfig.lib.json"
},
{
"path": "../../libs/deterministic-bundler/tsconfig.lib.json"
}
],
"exclude": [
Expand Down
12 changes: 12 additions & 0 deletions apps/smoke-web/chess-library-reuse.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Chess library reuse</title>
</head>
<body>
<main data-app>Loading chess library reuse fixture…</main>
<script type="module" src="/src/chess-library-reuse.ts"></script>
</body>
</html>
3 changes: 2 additions & 1 deletion apps/smoke-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
"type": "module",
"private": true,
"dependencies": {
"@blue-quickjs/deterministic-bundler": "workspace:*",
"@blue-quickjs/dv": "workspace:*",
"@blue-quickjs/quickjs-wasm": "workspace:*",
"@blue-quickjs/quickjs-runtime": "workspace:*",
"@blue-quickjs/quickjs-wasm": "workspace:*",
"@blue-quickjs/test-harness": "workspace:*"
},
"nx": {
Expand Down
109 changes: 109 additions & 0 deletions apps/smoke-web/src/chess-library-reuse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { evaluate } from '@blue-quickjs/quickjs-runtime';
import {
CHESS_E2E6_EXPECTED_LEGAL,
CHESS_LIBRARY_GAS_LIMIT,
CHESS_LIBRARY_INPUT,
CHESS_LIBRARY_MANIFEST,
CHESS_LIBRARY_PROGRAM_BASE,
createDeterminismHost,
} from '@blue-quickjs/test-harness';

type ChessReuseResult = {
ok: boolean;
value: boolean | null;
gasUsed: string;
gasRemaining: string;
errorCode: string | null;
errorTag: string | null;
};

declare global {
interface Window {
__CHESS_BUNDLED_CODE__?: string;
__CHESS_LIBRARY_REUSE_RESULT__?: ChessReuseResult;
}
}

renderShell();
void run();

function renderShell(): void {
const app = document.querySelector<HTMLElement>('[data-app]');
if (!app) {
return;
}
app.innerHTML = `
<h1>Chess.js deterministic reuse</h1>
<p>Expected e2e6 legal: <strong>${String(CHESS_E2E6_EXPECTED_LEGAL)}</strong></p>
<p data-runstate="running">Running…</p>
<pre data-result>waiting…</pre>
`;
}

async function run(): Promise<void> {
const runstate = document.querySelector<HTMLElement>('[data-runstate]');
const resultEl = document.querySelector<HTMLElement>('[data-result]');

try {
const code = window.__CHESS_BUNDLED_CODE__;
if (typeof code !== 'string' || code.length === 0) {
throw new Error('Missing bundled chess code in window.__CHESS_BUNDLED_CODE__');
}

const host = createDeterminismHost();
const result = await evaluate({
program: {
...CHESS_LIBRARY_PROGRAM_BASE,
code,
},
input: CHESS_LIBRARY_INPUT,
gasLimit: CHESS_LIBRARY_GAS_LIMIT,
manifest: CHESS_LIBRARY_MANIFEST,
handlers: host.handlers,
});

const payload: ChessReuseResult = result.ok
? {
ok: true,
value: result.value as boolean,
gasUsed: result.gasUsed.toString(),
gasRemaining: result.gasRemaining.toString(),
errorCode: null,
errorTag: null,
}
: {
ok: false,
value: null,
gasUsed: result.gasUsed.toString(),
gasRemaining: result.gasRemaining.toString(),
errorCode: result.error.code,
errorTag: 'tag' in result.error ? result.error.tag : null,
};

window.__CHESS_LIBRARY_REUSE_RESULT__ = payload;
if (runstate) {
runstate.dataset.runstate = 'done';
runstate.textContent = payload.ok ? 'Done' : 'Failed';
}
if (resultEl) {
resultEl.textContent = JSON.stringify(payload, null, 2);
}
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
window.__CHESS_LIBRARY_REUSE_RESULT__ = {
ok: false,
value: null,
gasUsed: '0',
gasRemaining: '0',
errorCode: 'BOOT_FAILURE',
errorTag: null,
};
if (runstate) {
runstate.dataset.runstate = 'error';
runstate.textContent = `Error: ${message}`;
}
if (resultEl) {
resultEl.textContent = message;
}
}
}
85 changes: 85 additions & 0 deletions apps/smoke-web/tests/chess-library-reuse.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { bundleDeterministicProgram } from '@blue-quickjs/deterministic-bundler';
import { evaluate } from '@blue-quickjs/quickjs-runtime';
import {
CHESS_E2E6_EXPECTED_LEGAL,
CHESS_LIBRARY_ENTRY_PATH,
CHESS_LIBRARY_GAS_LIMIT,
CHESS_LIBRARY_INPUT,
CHESS_LIBRARY_MANIFEST,
CHESS_LIBRARY_PROGRAM_BASE,
createDeterminismHost,
} from '@blue-quickjs/test-harness';
import { expect, test } from '@playwright/test';

type ChessReuseResult = {
ok: boolean;
value: boolean | null;
gasUsed: string;
gasRemaining: string;
errorCode: string | null;
errorTag: string | null;
};

test('browser matches node for bundled chess.js e2e6 legality', async ({
page,
}) => {
const bundled = await bundleDeterministicProgram({
absWorkingDir: process.cwd(),
entryPath: CHESS_LIBRARY_ENTRY_PATH,
profile: 'compat-regexp-v1',
});

const nodeResult = await runNodeFixture(bundled.code);

await page.addInitScript((code) => {
(window as Window & { __CHESS_BUNDLED_CODE__?: string }).__CHESS_BUNDLED_CODE__ =
code;
}, bundled.code);

await page.goto('/chess-library-reuse.html');
await page.waitForSelector('[data-runstate="done"]', { timeout: 30000 });

const browserResult = (await page.evaluate(() =>
(window as Window & { __CHESS_LIBRARY_REUSE_RESULT__?: ChessReuseResult })
.__CHESS_LIBRARY_REUSE_RESULT__,
)) as ChessReuseResult | undefined;

expect(browserResult).toBeTruthy();
expect(browserResult).toEqual(nodeResult);
expect(browserResult?.ok).toBe(true);
expect(browserResult?.value).toBe(CHESS_E2E6_EXPECTED_LEGAL);
});

async function runNodeFixture(code: string): Promise<ChessReuseResult> {
const host = createDeterminismHost();
const result = await evaluate({
program: {
...CHESS_LIBRARY_PROGRAM_BASE,
code,
},
input: CHESS_LIBRARY_INPUT,
gasLimit: CHESS_LIBRARY_GAS_LIMIT,
manifest: CHESS_LIBRARY_MANIFEST,
handlers: host.handlers,
});

if (result.ok) {
return {
ok: true,
value: result.value as boolean,
gasUsed: result.gasUsed.toString(),
gasRemaining: result.gasRemaining.toString(),
errorCode: null,
errorTag: null,
};
}

return {
ok: false,
value: null,
gasUsed: result.gasUsed.toString(),
gasRemaining: result.gasRemaining.toString(),
errorCode: result.error.code,
errorTag: 'tag' in result.error ? result.error.tag : null,
};
}
7 changes: 5 additions & 2 deletions apps/smoke-web/tsconfig.app.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,16 @@
"path": "../../libs/test-harness/tsconfig.lib.json"
},
{
"path": "../../libs/quickjs-runtime/tsconfig.lib.json"
"path": "../../libs/quickjs-wasm/tsconfig.lib.json"
},
{
"path": "../../libs/quickjs-wasm/tsconfig.lib.json"
"path": "../../libs/quickjs-runtime/tsconfig.lib.json"
},
{
"path": "../../libs/dv/tsconfig.lib.json"
},
{
"path": "../../libs/deterministic-bundler/tsconfig.lib.json"
}
]
}
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ Most people end up reading some docs and then jumping into these locations:
- Wasm entrypoints: `vendor/quickjs/quickjs-wasm-entry.c`

- **TypeScript libraries**
- Deterministic library bundling + compatibility scan: `libs/deterministic-bundler/`
- DV reference implementation: `libs/dv/`
- Manifest schema + canonical encoding/hashing: `libs/abi-manifest/`
- Wasm constants + metadata types: `libs/quickjs-wasm-constants/`
Expand Down
4 changes: 4 additions & 0 deletions docs/baseline-1.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ Determinism depends on a strict JS surface:

The exact list and the required deterministic error messages are specified in `docs/determinism-profile.md`.

Compatibility profiles may opt in to narrowly scoped additional surfaces (for
example regexp support), but baseline behavior remains the default contract and
must stay unchanged unless explicitly selected by the program artifact.

## 4. Canonical gas (normative)

### 4.1 Canonical gas is **inside QuickJS**
Expand Down
12 changes: 11 additions & 1 deletion docs/determinism-profile.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ Scope: capture the deterministic VM configuration required by Baseline #1 for bo
- optionally copies a context blob (max 5 MiB) and installs ergonomic globals
- sets the gas limit to `options.gas_limit`

## Execution profiles

`program.executionProfile` controls deterministic feature flags:

- `baseline-v1` (default): canonical baseline restrictions.
- `compat-regexp-v1`: baseline + deterministic RegExp compatibility.

`compat-regexp-v1` only re-enables RegExp support. All other disabled surfaces
remain disabled unless explicitly documented otherwise.

## Enabled intrinsics

The deterministic init only loads these intrinsic sets:
Expand All @@ -32,7 +42,7 @@ The following globals or methods exist but throw the exact TypeError shown:
- `eval(...)` -> `TypeError: eval is disabled in deterministic mode`
- `Function(...)` -> `TypeError: Function is disabled in deterministic mode`
- Function constructor paths (`Function.prototype.constructor`, arrow/generator constructors) -> `TypeError: Function constructor is disabled in deterministic mode`
- `RegExp` and regex literals -> `TypeError: RegExp is disabled in deterministic mode`
- `RegExp` and regex literals -> `TypeError: RegExp is disabled in deterministic mode` (**baseline-v1 only**)
- `Proxy` -> `TypeError: Proxy is disabled in deterministic mode`
- `Promise` and statics (`resolve`, `reject`, `all`, `race`, `any`, `allSettled`) -> `TypeError: Promise is disabled in deterministic mode`
- `Math.random()` -> `TypeError: Math.random is disabled in deterministic mode`
Expand Down
Loading
Loading