Evidence: 0 file:line witnesses as of 2026-08-18. Confidence: derived, written from the seams and the estate's failures rather than from citations; treat each rule as a design call and argue with it. What changes it: the first module built against this doc (
../PLAN.mdforge-1) and its instance breakdown.
Running a module on a laptop with nothing provisioned, running it inside an app's process, and answering "it works on my computer but not on prod" without guessing. The owner named this as a large, recurring source of friction; the direct-API and agent path is "the debugging / building side", and it is a contract concern, not a convenience.
Audience: anyone running a module outside prod, or wiring one into an app in-process.
What the instances do#
| versable-runner | speedway | walmart-mvp | |
|---|---|---|---|
| in-process mode | RUNNER_DISPATCH=local: in-process semaphore, no queue | Cloud Tasks vars unset means setImmediate in-process | storage_backend=local, stub_feed_submit=True fakes the vendor round trip |
| zero-env import | yes: every config var has a default (overrides/lib/config/__init__.py) | ADC against real Firestore/GCS; expired ADC makes every page 500 | .env + pydantic settings |
| local storage | GCS still | GCS + Firestore still (shared dev project) | local filesystem |
| local auth | password empty means IAM-locked mode | real sessions against dev data | JWT against local Postgres |
| build canary | none | /build-info reports "dev" when unstamped | /api/build-info |
versable-runner is closest: it imports with zero env and can dispatch
in-process. It still needs a bucket. speedway's local mode talks to a real
shared dev project, which is why expired ADC 500s every page and why its
CLAUDE.md says to check auth before debugging anything.
The rule#
Every module runs in-process with zero environment, against local
storage, with a local verifier, and every environment answers the same
/health/deep so two environments can be diffed.
In-process is a first-class adapter, not a dev hack#
The queue port has an in-process adapter (04-dispatch-and-workers.md), the
storage port has a local-directory adapter (05-storage-and-persistence.md),
the verifier has local (02-identity-and-tenancy.md), tracing is a no-op
without keys, and usage events go to a local ledger file. Selecting all of
them is what env: local means, and it is the default when nothing is set,
so python -m module (or the language's equivalent) serves the full surface
on a port with no cloud at all. The owner's draft names this directly: a
stage "can be a python module, a shell script, even a separate service ...
we only care about the input / output"; in-process is how a module is a
python module.
Same code path, same handler, same outcome shapes as prod. The only things
that differ are adapters and the env value.
Embedded in an app#
An app that wants a capability without a network hop (the console in local
development, a test suite, a batch script) imports the module and calls the
same runner with the in-process adapters, submitting the same job envelope
and reading the same outcomes. State lives wherever the app's adapters put
it. Nothing about the payload knows the difference; that is the seam doing
its job (01-runner-and-payload.md).
Local auth that cannot leak#
The local verifier accepts one static token from config and refuses to
start if the module believes it is on a public URL (a configured service
URL that is not localhost, or a platform env var that says Cloud Run or
Render). Zero-env means "runs on a laptop", never "runs unauthenticated on
the internet".
Diffing environments#
/health/deep returns the effective non-secret config, the adapters in use,
the reference-data versions, and the verifier, in the same shape in every
environment. "Works locally, not on prod" starts with two curls and a
diff, and the answer is usually in the diff: a different reference snapshot,
a different variant default, a queue ceiling, a missing secret reported as
absent. Secrets are never in the echo, but their presence or absence is.
Re-running one item is a call, not a shell session#
The debug path for a failed item in prod is a single-item child job with
parent_job_id and log_level: debug, read back through
GET /jobs/{job_id}/outcomes/{item_id}/log (07-observability.md). No shell,
no bucket browsing, callable from an app, from curl, or from an agent
reading the /guide prose.
Local against dev cloud, when needed#
Sometimes a payload needs the real vendor or the real reference bucket. The
module then runs with env: dev credentials for exactly those adapters and
local for the rest, and /health/deep says so. speedway's shape (everything
against a shared dev project) is the thing to avoid as the default, because
one expired credential breaks all of local.
Do-nots#
- Do not require a bucket, a queue, a database, or a credential to run a module on a laptop.
- Do not let the in-process mode take a different code path from prod. Same handler, different adapters.
- Do not let the
localverifier start on a public URL. - Do not make "debug one prod item" require shell access.
- Do not point local at a shared cloud project by default. (speedway)
- Do not answer
/health/deepdifferently in shape across environments.