sandboxedjs 0.2.11 → 0.2.12
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/README.md +16 -9
- package/assets/logo.png +0 -0
- package/bin/sandboxedjs-egress.mjs +25 -10
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/service-worker.js +3 -2
- package/docs/agent/COMMANDS.md +85 -0
- package/docs/agent/DECISION-TREE.md +84 -0
- package/docs/agent/INVARIANTS.md +40 -0
- package/docs/agent/LAUNCH-PROMPT.md +37 -0
- package/docs/agent/LOOP.md +84 -0
- package/docs/agent/README.md +77 -0
- package/docs/agent/ROADMAP.md +37 -0
- package/docs/agent/STATE.md +93 -0
- package/docs/agent/tasks/00-verify-inherited-work.md +36 -0
- package/docs/agent/tasks/01-authoritative-metadata.md +34 -0
- package/docs/agent/tasks/02-native-dependencies.md +35 -0
- package/docs/agent/tasks/03-reproducible-inputs.md +27 -0
- package/docs/agent/tasks/04-build-frontends.md +27 -0
- package/docs/agent/tasks/05-registry-integration.md +27 -0
- package/docs/agent/tasks/06-package-cohorts.md +42 -0
- package/docs/agent/tasks/07-build-on-miss-boundary.md +31 -0
- package/docs/browser-runtime-architecture.md +142 -0
- package/docs/compatibility-implementation-plan.md +98 -0
- package/docs/developer-tool-packs.md +134 -0
- package/docs/frontend-automation.md +49 -0
- package/docs/fullstack-deployment.md +163 -0
- package/docs/handoff.md +275 -0
- package/docs/original-x64.md +39 -0
- package/docs/platform-hardening.md +49 -0
- package/docs/python/abi.md +97 -0
- package/docs/python/architecture.md +94 -0
- package/docs/python/baseline-inventory.md +54 -0
- package/docs/python/build-on-miss.md +198 -0
- package/docs/python/compatibility.md +206 -0
- package/docs/python/cross-build.md +354 -0
- package/docs/python/extensions.md +282 -0
- package/docs/python/release-gates.md +46 -0
- package/docs/python/virtual-sockets-plan.md +331 -0
- package/docs/runtime-lifecycle-fixes.md +39 -0
- package/docs/server-previews.md +268 -0
- package/docs/virtual-browser.md +120 -0
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
-
<img src="assets/logo.png" alt="SandboxedJS" width="140" />
|
|
3
|
+
<img src="https://cdn.jsdelivr.net/npm/sandboxedjs@latest/assets/logo.png" alt="SandboxedJS" width="140" />
|
|
4
4
|
|
|
5
5
|
# SandboxedJS
|
|
6
6
|
|
|
@@ -117,7 +117,9 @@ terminal.start();
|
|
|
117
117
|
This is the case most people arrive for: a Vite frontend and a Python backend, in one container, in
|
|
118
118
|
a tab — the frontend calling `http://localhost:8000`, the backend calling a real API.
|
|
119
119
|
|
|
120
|
-
|
|
120
|
+
The frontend-to-backend hop and the backend-to-internet hop have different requirements.
|
|
121
|
+
See the [full-stack deployment guide](docs/fullstack-deployment.md) for production asset staging,
|
|
122
|
+
Cloudflare Pages, Vercel, Node/serverless lifetimes, and troubleshooting.
|
|
121
123
|
|
|
122
124
|
### 1. Frontend → backend: nothing to configure
|
|
123
125
|
|
|
@@ -149,10 +151,14 @@ await box.exec("npm install", { cwd: "/app/frontend", timeoutMs: 600_000 });
|
|
|
149
151
|
|
|
150
152
|
box.spawn("fastapi run", { cwd: "/app/backend" });
|
|
151
153
|
box.spawn("npm run dev", { cwd: "/app/frontend" });
|
|
152
|
-
await box.waitForPort(
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
154
|
+
if (!(await box.waitForPort(8000, { timeoutMs: 60_000 })) ||
|
|
155
|
+
!(await box.waitForPort(3000, { timeoutMs: 60_000 }))) {
|
|
156
|
+
throw new Error("Check backend and frontend startup logs");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const preview = await createPreview(box);
|
|
160
|
+
if (!preview) throw new Error("Preview requires a working service worker");
|
|
161
|
+
iframe.src = preview.urlFor(3000);
|
|
156
162
|
```
|
|
157
163
|
|
|
158
164
|
It works between preview pages and from another tab of the same browser while the owner page is
|
|
@@ -172,13 +178,14 @@ browser's rule about _pages_, not a container limit, and the only way through it
|
|
|
172
178
|
somewhere a page is not. So add one to the project you already deploy:
|
|
173
179
|
|
|
174
180
|
```bash
|
|
175
|
-
npx sandboxedjs-egress init --allow ollama.com,api.openai.com
|
|
181
|
+
npx sandboxedjs-egress init --target cloudflare --allow ollama.com,api.openai.com
|
|
176
182
|
```
|
|
177
183
|
|
|
178
184
|
That writes a single function file at the path your host serves — Cloudflare Pages, Vercel and
|
|
179
185
|
Netlify are detected, `--target` names one. Nothing else changes: a container in a browser probes
|
|
180
|
-
its own origin for that proxy before giving up
|
|
181
|
-
|
|
186
|
+
its own origin for that proxy before giving up. The guest project needs no proxy setting;
|
|
187
|
+
the host must deploy the generated function. For a separate relay, set `network.proxy`.
|
|
188
|
+
A host serving only static files cannot relay APIs that reject browser requests.
|
|
182
189
|
|
|
183
190
|
| Where you are | What to do |
|
|
184
191
|
| --------------------------------- | ----------------------------------------------------------------------------------- |
|
package/assets/logo.png
ADDED
|
Binary file
|
|
@@ -45,7 +45,7 @@ const TARGETS = {
|
|
|
45
45
|
${allow}
|
|
46
46
|
/* Cloudflare Pages serves this file at /__sandboxedjs__/egress, which is where
|
|
47
47
|
* a container in this site's pages looks for its way out. */
|
|
48
|
-
export const onRequest = ({ request }) => handleEgressRequest(request, options);
|
|
48
|
+
export const onRequest = ({ request }: { request: Request }) => handleEgressRequest(request, options);
|
|
49
49
|
`,
|
|
50
50
|
},
|
|
51
51
|
vercel: {
|
|
@@ -57,7 +57,7 @@ export const config = { runtime: "edge" };
|
|
|
57
57
|
|
|
58
58
|
/* Vercel serves this file at /api/__sandboxedjs__/egress, which is one of the
|
|
59
59
|
* paths a container in this site's pages probes for its way out. */
|
|
60
|
-
export default (request) => handleEgressRequest(request, options);
|
|
60
|
+
export default (request: Request) => handleEgressRequest(request, options);
|
|
61
61
|
`,
|
|
62
62
|
},
|
|
63
63
|
netlify: {
|
|
@@ -67,7 +67,7 @@ export default (request) => handleEgressRequest(request, options);
|
|
|
67
67
|
${allow}
|
|
68
68
|
export const config = { path: "/.netlify/functions/sandboxedjs-egress" };
|
|
69
69
|
|
|
70
|
-
export default (request) => handleEgressRequest(request, options);
|
|
70
|
+
export default (request: Request) => handleEgressRequest(request, options);
|
|
71
71
|
`,
|
|
72
72
|
},
|
|
73
73
|
};
|
|
@@ -90,13 +90,28 @@ async function detectTarget(root) {
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
async function init(argv) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
93
|
+
let directory;
|
|
94
|
+
let targetName;
|
|
95
|
+
let allowed = null;
|
|
96
|
+
for (let index = 0; index < argv.length; index++) {
|
|
97
|
+
const argument = argv[index];
|
|
98
|
+
if (argument === "--target" || argument === "--allow") {
|
|
99
|
+
const value = argv[++index];
|
|
100
|
+
if (!value || value.startsWith("--")) {
|
|
101
|
+
console.error(`sandboxedjs-egress init: ${argument} requires a value`);
|
|
102
|
+
process.exitCode = 1;
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (argument === "--target") targetName = value;
|
|
106
|
+
else allowed = value.split(",").map((host) => host.trim()).filter(Boolean);
|
|
107
|
+
} else if (argument.startsWith("-") || directory !== undefined) {
|
|
108
|
+
console.error(`sandboxedjs-egress init: unexpected argument ${argument}`);
|
|
109
|
+
process.exitCode = 1;
|
|
110
|
+
return;
|
|
111
|
+
} else directory = argument;
|
|
112
|
+
}
|
|
113
|
+
const root = directory ?? process.cwd();
|
|
114
|
+
const name = targetName ?? await detectTarget(root);
|
|
100
115
|
if (!name || !TARGETS[name]) {
|
|
101
116
|
console.error(
|
|
102
117
|
name
|
package/dist/index.cjs
CHANGED
|
@@ -21103,7 +21103,7 @@ var wheels_default = {
|
|
|
21103
21103
|
|
|
21104
21104
|
// package.json
|
|
21105
21105
|
var package_default = {
|
|
21106
|
-
version: "0.2.
|
|
21106
|
+
version: "0.2.12"};
|
|
21107
21107
|
|
|
21108
21108
|
// src/python/extension-abi.ts
|
|
21109
21109
|
var EXTENSION_ABI = {
|
package/dist/index.js
CHANGED
|
@@ -21086,7 +21086,7 @@ var wheels_default = {
|
|
|
21086
21086
|
|
|
21087
21087
|
// package.json
|
|
21088
21088
|
var package_default = {
|
|
21089
|
-
version: "0.2.
|
|
21089
|
+
version: "0.2.12"};
|
|
21090
21090
|
|
|
21091
21091
|
// src/python/extension-abi.ts
|
|
21092
21092
|
var EXTENSION_ABI = {
|
package/dist/service-worker.js
CHANGED
|
@@ -530,7 +530,8 @@ var PreviewRouter = class {
|
|
|
530
530
|
this.options.onStale?.(clientId);
|
|
531
531
|
}
|
|
532
532
|
const headers = new Headers(result.headers);
|
|
533
|
-
const
|
|
533
|
+
const bodyless = request.method === "HEAD" || [204, 205, 304].includes(result.status);
|
|
534
|
+
const body = bodyless ? null : this.options.injectSockets === false ? result.body : withSockets(result.body, headers);
|
|
534
535
|
headers.set("Cross-Origin-Resource-Policy", "cross-origin");
|
|
535
536
|
headers.set("Cross-Origin-Embedder-Policy", "require-corp");
|
|
536
537
|
return new Response(body, { status: result.status, statusText: result.statusText, headers });
|
|
@@ -656,7 +657,7 @@ worker.addEventListener("fetch", (event) => {
|
|
|
656
657
|
const claimed = router.claimedPort(url.pathname);
|
|
657
658
|
if (claimed !== null && event.request.mode === "navigate") {
|
|
658
659
|
router.bind(event.resultingClientId || event.clientId, claimed);
|
|
659
|
-
event.respondWith(router.fetch(claimed,
|
|
660
|
+
event.respondWith(router.fetch(claimed, router.containerPath(url.pathname, url.search), event.request, event.resultingClientId || event.clientId));
|
|
660
661
|
return;
|
|
661
662
|
}
|
|
662
663
|
const port = router.portForRequest(url.pathname, event.clientId);
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Command catalog
|
|
2
|
+
|
|
3
|
+
Run commands from `/Users/shazi/Projects/SandboxedJs` unless stated otherwise.
|
|
4
|
+
Use focused commands first. Keep long logs out of model context:
|
|
5
|
+
|
|
6
|
+
```sh
|
|
7
|
+
some-command > /tmp/sbx-native-build.log 2>&1
|
|
8
|
+
tail -n 120 /tmp/sbx-native-build.log
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Do not use the redirection form when editing source files; it is for disposable
|
|
12
|
+
logs only.
|
|
13
|
+
|
|
14
|
+
## Cheap inspection
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
git status --short
|
|
18
|
+
git diff --cached --check
|
|
19
|
+
git diff --cached --stat
|
|
20
|
+
python3 -m json.tool python-runtime/abi/extension-abi.json >/dev/null
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Build prerequisites and runtime
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
make -C python-runtime doctor
|
|
27
|
+
make -C python-runtime python PROFILE=dynamic
|
|
28
|
+
make -C python-runtime package PROFILE=dynamic
|
|
29
|
+
npm run build
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Do not rebuild CPython when the runtime and ABI inputs have not changed and the
|
|
33
|
+
required output already exists.
|
|
34
|
+
|
|
35
|
+
## Extension fixtures and index
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
python3 python-runtime/scripts/build_extension.py \
|
|
39
|
+
python-runtime/fixtures/sbx_setuptools_probe/recipe.json
|
|
40
|
+
python3 python-runtime/scripts/build_extension.py \
|
|
41
|
+
python-runtime/fixtures/sbx_cython_probe/recipe.json
|
|
42
|
+
python3 python-runtime/scripts/build_extension.py \
|
|
43
|
+
python-runtime/fixtures/sbx_rust_probe/recipe.json
|
|
44
|
+
python3 python-runtime/scripts/build_index.py
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
If a wheel changes, rebuild `index.json` before runtime tests or its digest will
|
|
48
|
+
correctly be stale.
|
|
49
|
+
|
|
50
|
+
## Focused tests
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
npx vitest run test/python-runtime/extensions.test.ts
|
|
54
|
+
npx vitest run test/python-runtime/resolver.test.ts
|
|
55
|
+
npx vitest run test/python-runtime/extension-abi.test.ts
|
|
56
|
+
npm run typecheck
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Run `npm run check` only at milestone gates. Network-enabled suites are not a
|
|
60
|
+
default native-package check.
|
|
61
|
+
|
|
62
|
+
## Wheel inspection
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
python3 -m zipfile -l path/to/package.whl
|
|
66
|
+
python3 python-runtime/scripts/build_index.py --wheels path/to/wheel-directory
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Prefer adding a small reusable inspection script over repeatedly embedding
|
|
70
|
+
complex Python in shell commands.
|
|
71
|
+
|
|
72
|
+
## Logs
|
|
73
|
+
|
|
74
|
+
Use one log per active failure under `/tmp`, named with the task and package.
|
|
75
|
+
Keep only:
|
|
76
|
+
|
|
77
|
+
- the exact command;
|
|
78
|
+
- toolchain versions when relevant;
|
|
79
|
+
- first causal error;
|
|
80
|
+
- final 80-150 lines;
|
|
81
|
+
- exit code.
|
|
82
|
+
|
|
83
|
+
Warnings are not tasks unless they identify incorrect output or a future
|
|
84
|
+
failure covered by the current milestone.
|
|
85
|
+
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Failure classification and decisions
|
|
2
|
+
|
|
3
|
+
Classify an observed failure by its earliest decisive symptom.
|
|
4
|
+
|
|
5
|
+
## Resolver says no usable distribution
|
|
6
|
+
|
|
7
|
+
1. Inspect candidate filenames and tags.
|
|
8
|
+
2. If a pure wheel exists, fix resolver/tag/metadata behavior; do not compile.
|
|
9
|
+
3. If a matching SandboxedJS wheel exists, inspect index `abiId`, digest, and
|
|
10
|
+
dependency metadata.
|
|
11
|
+
4. If only an sdist or host-native wheels exist, route to the external
|
|
12
|
+
cross-builder. Do not let the runtime installer build source yet.
|
|
13
|
+
|
|
14
|
+
## Build frontend cannot start
|
|
15
|
+
|
|
16
|
+
- Missing host Python module: classify as a build requirement.
|
|
17
|
+
- Unpinned requirement: add an exact version and hash acquisition path.
|
|
18
|
+
- Unsupported backend: add a backend adapter, not a package conditional.
|
|
19
|
+
- Network failure: preserve the command and request network approval; do not
|
|
20
|
+
substitute an unverified archive.
|
|
21
|
+
|
|
22
|
+
## Compiler uses host headers or compiler
|
|
23
|
+
|
|
24
|
+
- Inspect generated cross-sysconfig and command line.
|
|
25
|
+
- Fix host/target environment separation.
|
|
26
|
+
- Never solve this by renaming a produced artifact.
|
|
27
|
+
|
|
28
|
+
## Header or library not found
|
|
29
|
+
|
|
30
|
+
1. Determine whether it is a Python dependency or a native target library.
|
|
31
|
+
2. For a target library, require a declared, pinned native dependency.
|
|
32
|
+
3. Build it into the ABI/profile-specific sysroot.
|
|
33
|
+
4. Expose sysroot include/library paths through the cross environment.
|
|
34
|
+
5. Add a reduced fixture proving the dependency mechanism before retrying a
|
|
35
|
+
large real package.
|
|
36
|
+
|
|
37
|
+
## Configure test compiles, then tries to execute
|
|
38
|
+
|
|
39
|
+
This is a cross-compilation probe problem. Prefer, in order:
|
|
40
|
+
|
|
41
|
+
1. upstream-supported cross-file/cache variables;
|
|
42
|
+
2. a generic build-backend cross configuration;
|
|
43
|
+
3. a visible package patch providing the target fact.
|
|
44
|
+
|
|
45
|
+
Never execute target WebAssembly as if it were a host binary. If executing a
|
|
46
|
+
probe under a controlled runner is proposed, stop for architectural review.
|
|
47
|
+
|
|
48
|
+
## Linker failure
|
|
49
|
+
|
|
50
|
+
- Missing `Py*` symbols can remain unresolved in a side module if supplied by
|
|
51
|
+
the main module; confirm rather than blindly adding `-lpython`.
|
|
52
|
+
- Missing native-library symbols require target dependency/link-order work.
|
|
53
|
+
- Shared-memory, PIC, exception, relocation, or atomics errors are ABI issues;
|
|
54
|
+
inspect `INVARIANTS.md` and escalate before changing flags.
|
|
55
|
+
- Duplicate symbols often indicate a static library linked twice; inspect the
|
|
56
|
+
complete link line.
|
|
57
|
+
|
|
58
|
+
## Wheel builds but validation fails
|
|
59
|
+
|
|
60
|
+
- Not `\0asm`: host compiler leakage.
|
|
61
|
+
- No `dylink`: side-module link mode was lost.
|
|
62
|
+
- Missing `PyInit_*`: module naming/export problem.
|
|
63
|
+
- Wrong tag or ABI: wheel construction problem. Rebuild; never retag.
|
|
64
|
+
|
|
65
|
+
## Install succeeds but import fails
|
|
66
|
+
|
|
67
|
+
- Missing Python wrapper files: wheel assembly/layout defect.
|
|
68
|
+
- Missing dependent shared/static symbols: target dependency closure defect.
|
|
69
|
+
- Init symbol mismatch: derive the module name from installed path.
|
|
70
|
+
- Trap, memory corruption, or exception failure: ABI mismatch until disproven.
|
|
71
|
+
|
|
72
|
+
## Import succeeds but behavior fails
|
|
73
|
+
|
|
74
|
+
Classify whether the package needs an unavailable OS capability, contains a
|
|
75
|
+
32-bit assumption, exceeds memory, or has a package defect. Record an honest
|
|
76
|
+
compatibility limitation when the platform capability is genuinely absent.
|
|
77
|
+
|
|
78
|
+
## Test hangs
|
|
79
|
+
|
|
80
|
+
Do not immediately increase timeouts. Determine whether the process is waiting
|
|
81
|
+
on input, network, a child process, a pthread, or a host ABI response. Capture a
|
|
82
|
+
bounded stack/process snapshot where possible. Kill only the exact process you
|
|
83
|
+
started.
|
|
84
|
+
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Non-negotiable invariants
|
|
2
|
+
|
|
3
|
+
Read this before changing ABI, wheel, resolver, or compiler behavior.
|
|
4
|
+
|
|
5
|
+
1. `python-runtime/abi/extension-abi.json` is the source of truth for target
|
|
6
|
+
identity and compile/link compatibility. Recipes cannot override ABI flags.
|
|
7
|
+
2. A host-native ELF, Mach-O, `.node`, or other binary is never accepted or
|
|
8
|
+
relabeled as WebAssembly.
|
|
9
|
+
3. A native wheel must match both its Python wheel tag and SandboxedJS
|
|
10
|
+
`abiId`.
|
|
11
|
+
4. Build tools run on the build machine. Target code and target libraries are
|
|
12
|
+
compiled for wasm32. Never put target artifacts on the host interpreter's
|
|
13
|
+
import path.
|
|
14
|
+
5. Build, host, Python-runtime, and native-target dependencies are separate
|
|
15
|
+
concepts and must be represented separately.
|
|
16
|
+
6. Sources and build-tool artifacts are version-pinned and hash-verifiable.
|
|
17
|
+
7. Generic behavior lives in generic machinery. Package-specific behavior is
|
|
18
|
+
visible in a recipe and/or patch, never hidden behind `if package == ...`.
|
|
19
|
+
8. The package's own metadata is authoritative. Recipes may constrain or add
|
|
20
|
+
target facts, but must not silently duplicate drifting `Requires-Dist` data.
|
|
21
|
+
9. Installer operations remain transactional. Failed resolution, download, or
|
|
22
|
+
installation must not leave a partial environment.
|
|
23
|
+
10. A build is supported only after the wheel installs and its compiled module
|
|
24
|
+
executes under the owned SandboxedJS CPython runtime.
|
|
25
|
+
11. Browser compatibility remains mandatory. Runtime TypeScript must not gain
|
|
26
|
+
unconditional Node builtin imports.
|
|
27
|
+
12. Unsupported operating-system capabilities fail honestly. Package success
|
|
28
|
+
must not be manufactured by fake sockets, fake processes, or silent skips.
|
|
29
|
+
|
|
30
|
+
## Forbidden shortcuts
|
|
31
|
+
|
|
32
|
+
- Weakening `verify_side_module` or ABI checks to make a fixture pass.
|
|
33
|
+
- Adding arbitrary recipe compiler/link flags.
|
|
34
|
+
- Copying metadata solely from a handwritten recipe when upstream metadata can
|
|
35
|
+
be generated.
|
|
36
|
+
- Installing unpinned build requirements from the network in a release build.
|
|
37
|
+
- Treating "wheel file exists" as an acceptance result.
|
|
38
|
+
- Starting with NumPy-specific branches in the generic pipeline.
|
|
39
|
+
- Running full tests repeatedly when a focused test identifies the defect.
|
|
40
|
+
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Prompt to launch or resume Claude Opus
|
|
2
|
+
|
|
3
|
+
Copy the text below into the coding task. Do not paste the rest of this folder
|
|
4
|
+
into the prompt; the agent should read files on demand.
|
|
5
|
+
|
|
6
|
+
```text
|
|
7
|
+
You are the sole implementation agent for the SandboxedJS native Python
|
|
8
|
+
package platform. The project controller is:
|
|
9
|
+
|
|
10
|
+
agent-projects/python-package-platform/README.md
|
|
11
|
+
|
|
12
|
+
Begin by reading only README.md, STATE.md, and the current task named in
|
|
13
|
+
STATE.md. Follow LOOP.md. Execute commands and make decisions from their actual
|
|
14
|
+
outputs. Continue through tasks while gates pass and context remains healthy.
|
|
15
|
+
Keep STATE.md current so another task can resume without chat history.
|
|
16
|
+
|
|
17
|
+
Preserve all pre-existing and staged work. Do not commit, publish, deploy,
|
|
18
|
+
discard changes, weaken ABI validation, or change the ABI contract unless I
|
|
19
|
+
explicitly authorize it. Use focused tests before broad suites. Keep long build
|
|
20
|
+
logs in /tmp and bring only decisive excerpts into context.
|
|
21
|
+
|
|
22
|
+
If a stop condition in LOOP.md occurs, stop with a compact escalation packet.
|
|
23
|
+
The goal is a general package platform; NumPy is a later acceptance case, not a
|
|
24
|
+
package-specific architecture target.
|
|
25
|
+
|
|
26
|
+
Do not merely review or propose a plan. Work autonomously: inspect, edit,
|
|
27
|
+
execute focused commands, diagnose their outputs, verify exit gates, update
|
|
28
|
+
STATE.md, and advance to the next task. Do not ask for confirmation between
|
|
29
|
+
ordinary in-scope steps. Continue until a documented stop condition requires my
|
|
30
|
+
decision or all roadmap gates are complete.
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Execution policy
|
|
34
|
+
|
|
35
|
+
This workflow assumes one Claude Opus agent. Do not delegate work to other
|
|
36
|
+
models or create subagents. Use the stop conditions and ask the user for one
|
|
37
|
+
concrete decision only when necessary.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Agent execution loop
|
|
2
|
+
|
|
3
|
+
Use this loop exactly. It is designed for a smaller model that can act well
|
|
4
|
+
when each decision is grounded in the previous command's output.
|
|
5
|
+
|
|
6
|
+
## 1. Orient
|
|
7
|
+
|
|
8
|
+
Read `README.md`, `STATE.md`, and the current task only. Inspect `git status
|
|
9
|
+
--short` before editing. Treat all pre-existing changes as user-owned.
|
|
10
|
+
|
|
11
|
+
State the current hypothesis in one sentence. A hypothesis must predict an
|
|
12
|
+
observable result, for example: "the linker cannot find `libxml2` because the
|
|
13
|
+
dynamic sysroot is absent from target link flags."
|
|
14
|
+
|
|
15
|
+
## 2. Choose the cheapest discriminating command
|
|
16
|
+
|
|
17
|
+
Run the smallest command that can disprove the hypothesis. Prefer, in order:
|
|
18
|
+
|
|
19
|
+
1. static inspection of one relevant file;
|
|
20
|
+
2. one unit test or one fixture build;
|
|
21
|
+
3. one runtime import test;
|
|
22
|
+
4. the focused suite;
|
|
23
|
+
5. the full suite only at a milestone gate.
|
|
24
|
+
|
|
25
|
+
Never rerun a long build unchanged. If a command failed, inspect its decisive
|
|
26
|
+
error first and change either the hypothesis or the implementation.
|
|
27
|
+
|
|
28
|
+
## 3. Classify the result
|
|
29
|
+
|
|
30
|
+
Use `DECISION-TREE.md`. Every failure must be classified before code changes.
|
|
31
|
+
If there is not enough evidence, gather more evidence rather than guessing.
|
|
32
|
+
|
|
33
|
+
## 4. Make one coherent change
|
|
34
|
+
|
|
35
|
+
Change the smallest architectural seam that removes the demonstrated failure
|
|
36
|
+
class. Do not add a package-name conditional to the generic builder. A package
|
|
37
|
+
patch belongs in an explicit recipe patch directory, with a comment explaining
|
|
38
|
+
the upstream assumption it replaces.
|
|
39
|
+
|
|
40
|
+
Use `apply_patch` for hand edits. Do not overwrite files with shell redirects.
|
|
41
|
+
Do not alter generated ABI files directly.
|
|
42
|
+
|
|
43
|
+
## 5. Verify in layers
|
|
44
|
+
|
|
45
|
+
Run the focused check that failed. If it passes, run the task's exit gate.
|
|
46
|
+
Compilation is never the final gate for an extension: install and import it in
|
|
47
|
+
the owned CPython runtime and exercise at least one native behavior.
|
|
48
|
+
|
|
49
|
+
## 6. Record and advance
|
|
50
|
+
|
|
51
|
+
Update `STATE.md` with compact evidence. If the task exit gate passes, set its
|
|
52
|
+
checkboxes, point `Current task` to the next task in `ROADMAP.md`, and continue
|
|
53
|
+
if budget remains.
|
|
54
|
+
|
|
55
|
+
## Stop conditions
|
|
56
|
+
|
|
57
|
+
Stop and request a user decision when:
|
|
58
|
+
|
|
59
|
+
- the next action requires credentials, deployment, paid infrastructure, or
|
|
60
|
+
publishing outside the repository;
|
|
61
|
+
- fulfilling the task requires changing the ABI contract rather than fixing a
|
|
62
|
+
consumer of it;
|
|
63
|
+
- an upstream license is unclear or incompatible;
|
|
64
|
+
- a destructive action, history rewrite, or discard of existing work appears
|
|
65
|
+
necessary;
|
|
66
|
+
- the same failure class survives three evidence-driven attempts;
|
|
67
|
+
- a design choice changes public API or materially increases shipped runtime
|
|
68
|
+
size without an already-stated budget.
|
|
69
|
+
|
|
70
|
+
When stopping, preserve logs and state the smallest exact decision needed.
|
|
71
|
+
|
|
72
|
+
## Escalation packet for the user
|
|
73
|
+
|
|
74
|
+
When a stop condition is reached, prepare a compact packet containing:
|
|
75
|
+
|
|
76
|
+
- intended invariant;
|
|
77
|
+
- exact command;
|
|
78
|
+
- final 80-150 relevant log lines;
|
|
79
|
+
- files and functions involved;
|
|
80
|
+
- three attempted hypotheses and what disproved each;
|
|
81
|
+
- the decision required.
|
|
82
|
+
|
|
83
|
+
Do not send entire build logs or the full repository history. Wait for the
|
|
84
|
+
user's decision, record it in `STATE.md`, and then resume this same loop.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# SandboxedJS native Python package platform
|
|
2
|
+
|
|
3
|
+
This folder is an executable work specification for a coding agent. Its goal is
|
|
4
|
+
not to port NumPy. Its goal is to make native Python packages a platform
|
|
5
|
+
capability of SandboxedJS, with NumPy eventually serving as one demanding
|
|
6
|
+
acceptance case.
|
|
7
|
+
|
|
8
|
+
## Start here
|
|
9
|
+
|
|
10
|
+
At the beginning of every turn, read only these files:
|
|
11
|
+
|
|
12
|
+
1. `README.md` (this file).
|
|
13
|
+
2. `STATE.md`.
|
|
14
|
+
3. The one task file named by `STATE.md`.
|
|
15
|
+
|
|
16
|
+
Do not reread every document on every turn. Open `INVARIANTS.md`,
|
|
17
|
+
`DECISION-TREE.md`, or `COMMANDS.md` only when the current task points to them
|
|
18
|
+
or an observed failure requires them. This is intentional: the folder is also
|
|
19
|
+
a context and token budget.
|
|
20
|
+
|
|
21
|
+
Then execute the loop in `LOOP.md`. Continue until the current task's exit gate
|
|
22
|
+
passes or a stop condition requires the user or a stronger model.
|
|
23
|
+
|
|
24
|
+
## Product objective
|
|
25
|
+
|
|
26
|
+
The eventual user experience is:
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
pip install some-package
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The implementation may obtain a pure wheel, install an already-built
|
|
33
|
+
SandboxedJS wheel, or ask an external build system to create and cache a wheel.
|
|
34
|
+
It must never install a host-native artifact by relabeling it, silently omit a
|
|
35
|
+
dependency, or claim support because compilation alone succeeded.
|
|
36
|
+
|
|
37
|
+
## Current baseline
|
|
38
|
+
|
|
39
|
+
The working tree already contains staged, uncommitted work produced in an
|
|
40
|
+
earlier session:
|
|
41
|
+
|
|
42
|
+
- a generic setuptools cross environment;
|
|
43
|
+
- package-owned `setup.py` and `pyproject.toml` builds;
|
|
44
|
+
- C and Cython fixtures;
|
|
45
|
+
- the existing PyO3 path;
|
|
46
|
+
- side-module validation and runtime import tests;
|
|
47
|
+
- `docs/python/cross-build.md`, which records ten known assumptions.
|
|
48
|
+
|
|
49
|
+
Preserve those staged changes. Do not unstage, discard, rewrite wholesale, or
|
|
50
|
+
commit them unless the user explicitly requests it. First verify them and
|
|
51
|
+
record the result in `STATE.md`.
|
|
52
|
+
|
|
53
|
+
## Scope boundary
|
|
54
|
+
|
|
55
|
+
This project owns:
|
|
56
|
+
|
|
57
|
+
- reproducible cross-build inputs;
|
|
58
|
+
- build/host/target dependency separation;
|
|
59
|
+
- package build-backend execution;
|
|
60
|
+
- ABI-valid wheel construction and metadata;
|
|
61
|
+
- wheel indexing, resolution, installation, and runtime verification;
|
|
62
|
+
- explicit recipes and patches for irreducibly package-specific behavior;
|
|
63
|
+
- compatibility evidence.
|
|
64
|
+
|
|
65
|
+
This project does not own, unless the user separately authorizes it:
|
|
66
|
+
|
|
67
|
+
- a hosted public build service;
|
|
68
|
+
- cloud accounts, deployment, billing, or package signing infrastructure;
|
|
69
|
+
- pretending raw sockets, subprocesses, GPU APIs, or unavailable operating
|
|
70
|
+
system capabilities exist;
|
|
71
|
+
- broad unrelated cleanup of SandboxedJS.
|
|
72
|
+
|
|
73
|
+
## Definition of success
|
|
74
|
+
|
|
75
|
+
Success is a general pipeline demonstrated by several independent package
|
|
76
|
+
shapes, not one famous package. The final gate is described in `ROADMAP.md`.
|
|
77
|
+
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Capability roadmap
|
|
2
|
+
|
|
3
|
+
Advance only when the current task's exit gate passes. Each task must leave a
|
|
4
|
+
focused regression test.
|
|
5
|
+
|
|
6
|
+
| Order | Task | Capability produced |
|
|
7
|
+
| --- | --- | --- |
|
|
8
|
+
| 00 | `tasks/00-verify-inherited-work.md` | trusted baseline for the staged generic builder |
|
|
9
|
+
| 01 | `tasks/01-authoritative-metadata.md` | wheel metadata derived from the package |
|
|
10
|
+
| 02 | `tasks/02-native-dependencies.md` | declared target libraries and profile sysroot |
|
|
11
|
+
| 03 | `tasks/03-reproducible-inputs.md` | hashed sources, build tools, patches, provenance |
|
|
12
|
+
| 04 | `tasks/04-build-frontends.md` | explicit backend interface beyond setuptools/PyO3 |
|
|
13
|
+
| 05 | `tasks/05-registry-integration.md` | reproducible build output consumable by `pip` |
|
|
14
|
+
| 06 | `tasks/06-package-cohorts.md` | evidence across independent package shapes |
|
|
15
|
+
| 07 | `tasks/07-build-on-miss-boundary.md` | local protocol/spec for an eventual build service |
|
|
16
|
+
|
|
17
|
+
## Platform completion gate
|
|
18
|
+
|
|
19
|
+
The platform milestone is complete when all of the following are true:
|
|
20
|
+
|
|
21
|
+
- Pure wheels continue to install directly and transactionally.
|
|
22
|
+
- Three native language/build shapes work without package-name branches.
|
|
23
|
+
- At least one extension links a separately built native target library.
|
|
24
|
+
- At least one package needs and cleanly carries a visible target patch.
|
|
25
|
+
- Wheel metadata and dependency resolution come from authoritative package
|
|
26
|
+
metadata plus explicit target constraints.
|
|
27
|
+
- Every indexed artifact is source-pinned, hash-verifiable, ABI-identified,
|
|
28
|
+
installable, importable, and behavior-tested in the owned runtime.
|
|
29
|
+
- A compatibility report distinguishes supported, recipe-required,
|
|
30
|
+
platform-blocked, and not-yet-attempted packages.
|
|
31
|
+
- The external build-on-miss interface is specified without embedding a
|
|
32
|
+
compiler toolchain in the browser runtime.
|
|
33
|
+
|
|
34
|
+
NumPy may be attempted after tasks 00-05. It is evidence for native dependency,
|
|
35
|
+
build-probe, memory, and scientific-stack behavior; it is not itself the
|
|
36
|
+
definition of platform completion.
|
|
37
|
+
|