You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Found while starting #86. Blocks the local-emulation approach agreed for Phase 6, and needs a
decision rather than a workaround.
What happens
Phase 6 needs a Worker with D1 and R2 bindings to develop against. The intended route is
Miniflare, added as a dev dependency, so no Cloudflare account is needed and nothing reaches
a published package.
It works. Verified on 2026-08-06:
wrangler --version reports 4.119.0.
A Miniflare Worker with live d1Databases and r2Buckets bindings round-trips both: {"d1":"from-d1","r2":"from-r2"}.
Each D1 database gets isolated storage, which is what makes testing a candidate projection
meaningful.
workerd ships a post-install script, and declining it costs nothing: the binary arrives
through @cloudflare/workerd-<platform> optional dependencies. allowBuilds: workerd: false
in pnpm-workspace.yaml, exactly as esbuild already is, and everything above still works.
Then pnpm verify fails:
check:no-native failed. The core install must never compile or download binaries.
installed tree contains sharp (node_modules/.pnpm/node_modules/sharp)
wrangler pulls in sharp, a native image library, transitively.
Why this is a decision and not a workaround
check:no-native exists to keep invariant 7 true: no Python, Docker, compiler toolchain, native
add-on, model download, API key or account. Ever. It is one of the few guards that protects the
product's central promise, and the tempting fix, teaching it to ignore this case, weakens exactly
the thing it is for.
The honest tension: invariant 7 constrains what a user installs. sharp here is in a
contributor's node_modules, never in a published package, so a user installing Lorepack is
genuinely unaffected. But check:no-native currently asserts something stronger, about the whole
installed tree, and that stronger statement has value: it is what makes "no native code" checkable
rather than a claim about intent.
The options
Scope the check to what ships. Assert no native code in the published packages and their
production dependency closure, rather than in the whole installed tree. Keeps the promise that
matters and lets dev tooling be dev tooling. The work is defining "published closure" precisely
enough that the check stays meaningful.
Allowlist, narrowly. Let the check carry named exceptions with a written reason each, the
way allowBuilds already does. Cheapest, and the risk is that an allowlist is a slope.
Avoid Miniflare. Test Worker handlers as plain functions with fake bindings. No native
code, and it cannot prove the Worker starts or that the bindings work, which is most of what Cloudflare Worker runtime with D1/R2-backed storage ports #86 is for.
A separate install. Keep the emulator out of the workspace entirely and run it from a
throwaway directory in CI only. Keeps the tree clean at the cost of a second install path
nobody sees locally.
Option 1 is the one that makes the guard more honest rather than less. It is also the most work,
and it changes a check every phase has relied on, so it should not be decided in passing.
Acceptance criteria
The decision is recorded here before check:no-native is changed.
Whichever is chosen, "a user's install compiles and downloads nothing" is still asserted by
a test, not merely intended.
If the check narrows, a native dependency added to a published package still fails it.
That is the mutation worth proving.
docs/architecture/dependencies.md records the rationale for the emulator, including that workerd's post-install is declined and why that is safe.
Reproducing
pnpm add -Dw wrangler@4.119.0
# set `workerd: false` under allowBuilds in pnpm-workspace.yaml
pnpm verify # check:no-native fails on sharp
The Miniflare probe used above is two tests and is worth keeping when this unblocks: a Worker
with both bindings, and two D1 databases proving isolated storage.
Found while starting #86. Blocks the local-emulation approach agreed for Phase 6, and needs a
decision rather than a workaround.
What happens
Phase 6 needs a Worker with D1 and R2 bindings to develop against. The intended route is
Miniflare, added as a dev dependency, so no Cloudflare account is needed and nothing reaches
a published package.
It works. Verified on 2026-08-06:
wrangler --versionreports 4.119.0.d1Databasesandr2Bucketsbindings round-trips both:{"d1":"from-d1","r2":"from-r2"}.meaningful.
workerdships a post-install script, and declining it costs nothing: the binary arrivesthrough
@cloudflare/workerd-<platform>optional dependencies.allowBuilds: workerd: falsein
pnpm-workspace.yaml, exactly as esbuild already is, and everything above still works.Then
pnpm verifyfails:wranglerpulls insharp, a native image library, transitively.Why this is a decision and not a workaround
check:no-nativeexists to keep invariant 7 true: no Python, Docker, compiler toolchain, nativeadd-on, model download, API key or account. Ever. It is one of the few guards that protects the
product's central promise, and the tempting fix, teaching it to ignore this case, weakens exactly
the thing it is for.
The honest tension: invariant 7 constrains what a user installs.
sharphere is in acontributor's
node_modules, never in a published package, so a user installing Lorepack isgenuinely unaffected. But
check:no-nativecurrently asserts something stronger, about the wholeinstalled tree, and that stronger statement has value: it is what makes "no native code" checkable
rather than a claim about intent.
The options
production dependency closure, rather than in the whole installed tree. Keeps the promise that
matters and lets dev tooling be dev tooling. The work is defining "published closure" precisely
enough that the check stays meaningful.
way
allowBuildsalready does. Cheapest, and the risk is that an allowlist is a slope.code, and it cannot prove the Worker starts or that the bindings work, which is most of what
Cloudflare Worker runtime with D1/R2-backed storage ports #86 is for.
throwaway directory in CI only. Keeps the tree clean at the cost of a second install path
nobody sees locally.
Option 1 is the one that makes the guard more honest rather than less. It is also the most work,
and it changes a check every phase has relied on, so it should not be decided in passing.
Acceptance criteria
check:no-nativeis changed.a test, not merely intended.
That is the mutation worth proving.
docs/architecture/dependencies.mdrecords the rationale for the emulator, including thatworkerd's post-install is declined and why that is safe.Reproducing
The Miniflare probe used above is two tests and is worth keeping when this unblocks: a Worker
with both bindings, and two D1 databases proving isolated storage.