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.
Files changed (43) hide show
  1. package/README.md +16 -9
  2. package/assets/logo.png +0 -0
  3. package/bin/sandboxedjs-egress.mjs +25 -10
  4. package/dist/index.cjs +1 -1
  5. package/dist/index.js +1 -1
  6. package/dist/service-worker.js +3 -2
  7. package/docs/agent/COMMANDS.md +85 -0
  8. package/docs/agent/DECISION-TREE.md +84 -0
  9. package/docs/agent/INVARIANTS.md +40 -0
  10. package/docs/agent/LAUNCH-PROMPT.md +37 -0
  11. package/docs/agent/LOOP.md +84 -0
  12. package/docs/agent/README.md +77 -0
  13. package/docs/agent/ROADMAP.md +37 -0
  14. package/docs/agent/STATE.md +93 -0
  15. package/docs/agent/tasks/00-verify-inherited-work.md +36 -0
  16. package/docs/agent/tasks/01-authoritative-metadata.md +34 -0
  17. package/docs/agent/tasks/02-native-dependencies.md +35 -0
  18. package/docs/agent/tasks/03-reproducible-inputs.md +27 -0
  19. package/docs/agent/tasks/04-build-frontends.md +27 -0
  20. package/docs/agent/tasks/05-registry-integration.md +27 -0
  21. package/docs/agent/tasks/06-package-cohorts.md +42 -0
  22. package/docs/agent/tasks/07-build-on-miss-boundary.md +31 -0
  23. package/docs/browser-runtime-architecture.md +142 -0
  24. package/docs/compatibility-implementation-plan.md +98 -0
  25. package/docs/developer-tool-packs.md +134 -0
  26. package/docs/frontend-automation.md +49 -0
  27. package/docs/fullstack-deployment.md +163 -0
  28. package/docs/handoff.md +275 -0
  29. package/docs/original-x64.md +39 -0
  30. package/docs/platform-hardening.md +49 -0
  31. package/docs/python/abi.md +97 -0
  32. package/docs/python/architecture.md +94 -0
  33. package/docs/python/baseline-inventory.md +54 -0
  34. package/docs/python/build-on-miss.md +198 -0
  35. package/docs/python/compatibility.md +206 -0
  36. package/docs/python/cross-build.md +354 -0
  37. package/docs/python/extensions.md +282 -0
  38. package/docs/python/release-gates.md +46 -0
  39. package/docs/python/virtual-sockets-plan.md +331 -0
  40. package/docs/runtime-lifecycle-fixes.md +39 -0
  41. package/docs/server-previews.md +268 -0
  42. package/docs/virtual-browser.md +120 -0
  43. 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
- Two different problems hide in that sentence, and SandboxedJS solves them in two different ways.
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(3000, { timeoutMs: 60_000 });
153
-
154
- const preview = await createPreview(box); // null where service workers are unavailable
155
- iframe.src = preview!.urlFor(3000);
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, so **no project passes a `proxy` option and no
181
- project is configured twice.**
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
  | --------------------------------- | ----------------------------------------------------------------------------------- |
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
- const root = argv.find((a) => !a.startsWith("-")) ?? process.cwd();
94
- const at = argv.indexOf("--target");
95
- const allowAt = argv.indexOf("--allow");
96
- const allowed = allowAt === -1
97
- ? null
98
- : (argv[allowAt + 1] ?? "").split(",").map((h) => h.trim()).filter(Boolean);
99
- const name = at === -1 ? await detectTarget(root) : argv[at + 1];
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.11"};
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.11"};
21089
+ version: "0.2.12"};
21090
21090
 
21091
21091
  // src/python/extension-abi.ts
21092
21092
  var EXTENSION_ABI = {
@@ -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 body = this.options.injectSockets === false ? result.body : withSockets(result.body, headers);
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, "/", event.request, event.resultingClientId || event.clientId));
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
+