DevPay Local Control Plane
The local control plane is a development tool, not an application API. It uses a separate SQLite database, Git worktrees, and an isolated local validation ref. It does not deploy the application, access D1, publish to a remote, or touch payments.
Initialize and create a task
Run from nougat-devpay/. Pin the immutable specification revision in the initializer; generated worktrees retain the same Gem root and commit.
python3 -m tools.devpay_control.cli --state .devpay-control init \
--repo .. --gem-root /path/to/nougat-gems \
--gem-revision 69b0707633a4afc0888ba1ce5092375386b630cd \
--base feat/pg-magician2
3
4
The isolated integration ref defaults to refs/devpay/validated/local, bootstrapped from the supplied Base. It never pushes remotely. After the local Gate 1/2 evidence passes, a local-only fast-forward to the target branch may be performed explicitly. All source changes are restricted to nougat-devpay/; branch creation, staging, and commits remain explicit.
python3 -m tools.devpay_control.cli --state .devpay-control create product-work --owner SELLER_FE
python3 -m tools.devpay_control.cli --state .devpay-control submit <task-id>
python3 -m tools.devpay_control.cli --state .devpay-control gate1 <attempt-id>
python3 -m tools.devpay_control.cli --state .devpay-control integrate <attempt-id>
python3 -m tools.devpay_control.cli --state .devpay-control report --task <task-id>
python3 -m tools.devpay_control.cli --state .devpay-control cancel <unsubmitted-task-id> --owner BACKEND_INFRA2
3
4
5
6
The read-only viewer observes tasks, attempts, gates, events, runtime leases, a memory snapshot, and stored artifacts without any write API. It opens control.sqlite with mode=ro, accepts only GET/HEAD, binds loopback only (external exposure would require TLS and authentication, which this local tool does not provide), and projects records through a field allow-list that drops filesystem paths. Artifacts are re-hashed on read and served as JSON; runtime logs are served as text/plain with nosniff from inside the state root only.
python3 -m tools.devpay_control.cli --state .devpay-control viewer --port 8090Gate 1 checks the submitted SHA in a detached worktree. Gate 2 merges that exact SHA onto the current local validated ref, runs the candidate profile again, and advances the ref with git update-ref <new> <expected-old>. Candidate failure, Gate 1 failure, merge conflict, and stale Base leave the validated ref unchanged. A retry creates a new Attempt. cancel is restricted to an owned, clean, unsubmitted Task at its original Base; it refuses Tasks with Attempts or active runtimes. Remote publication is intentionally not implemented.
Profiles and runtimes
Diff classification is conservative. Submit accepts paths under nougat-devpay/** plus the exact repository-root path .github/workflows/yetdapay.yml; all other root paths (including other workflow files) are rejected. Empty, non-canonical, and traversal-containing paths are rejected as well. DevPay documentation-only changes run git diff --check; control-plane code runs its unittest suite; Axum code requires cargo test; Worker code requires a WASM build; frontend code requires typecheck plus browser verification. The exact workflow path runs git diff --check, the control-plane unittest suite, and an offline Python/PyYAML structural check of .github/workflows/yetdapay.yml (root/jobs mappings and required jobs); it does not select frontend or browser checks by itself. PyYAML must already be available locally; the check never installs dependencies or uses the network. This structural check is not a substitute for actual GitHub Actions validation, which must pass separately. Unknown paths escalate to all profiles. Missing executables, dependencies, coverage, or isolated runtime are BLOCKED, never PASS.
Axum startup remains non-seeding by default. Tests needing demo rows call db::seed_test_fixture explicitly; that helper rejects APP_ENV=production and databases outside the OS temporary directory. The full local Axum test suite passes with one PostgreSQL-only test ignored when no server is configured.
Task-owned Linux GUI sessions may be managed with runtime-start, runtime-status, and runtime-stop. Each allocates a separate Xvfb parent and Xephyr display, browser profile, home, and artifact directory. Host DISPLAY and XAUTHORITY are not inherited. The Gate browser runner starts its own equivalent Gate-owned runtime. Both use headed Chromium and a local temporary Axum SQLite fixture. Rust/API/DB-only checks do not start a GUI.
The browser gate reuses a locked FE dependency tree only when its package.json and pnpm-lock.yaml fingerprints match the Gate worktree; it copies that tree into the Gate worktree and never runs package lifecycle scripts or a network install. UI ports use the two loopback origins allowed by the existing Axum CORS policy. Browser external requests are blocked. A lock mismatch, unavailable cache, Xephyr failure, or failed startup blocks the Gate.
Gate children keep HOME, CARGO_TARGET_DIR, the SQLite fixture, and the browser profile inside the Gate runtime, and the build stays offline (--offline, CARGO_NET_OFFLINE=true). Three host caches are passed in as read-only shared inputs, because an isolated HOME has neither a toolchain, a registry cache, nor an installed browser: CARGO_HOME/RUSTUP_HOME ($CARGO_HOME/$RUSTUP_HOME, else ~/.cargo/~/.rustup), PLAYWRIGHT_BROWSERS_PATH ($PLAYWRIGHT_BROWSERS_PATH, else ~/.cache/ms-playwright), and the FE dependency tree taken from $DEVPAY_FE_DEPS_SOURCE (else the canonical checkout's nougat-devpay/fe/node_modules) after its fingerprints match the Gate worktree. Nothing is written back to a host cache, so a fresh Gate worktree needs no preinstalled node_modules, and build output still lands in the task-isolated CARGO_TARGET_DIR. A missing toolchain, browser cache, or dependency cache, an unverified or lock-mismatched dependency tree, and a non-absolute DEVPAY_FE_DEPS_SOURCE all block the Gate instead of skipping checks.
The seller product seller.product.create and seller.product.update Actions are versioned and payload-validated. Their Receipts separate API command result, server read-after-write, and rendered detail. The browser driver independently verifies the UI and server row. Both Actions pass Gate 1/2 with exact Task/Attempt/Commit/Scenario evidence. Only after all required evidence matches that context can the Evidence Bridge report verified. The bridge only reads and reconciles evidence; it has no Git or queue methods.
State and trust limits
SQLite stores task, attempt, queue, gate, event, runtime-lease, and immutable artifact-digest records. Gate artifacts contain status, check IDs, duration, SHA, runtime identity, and allow-listed action receipt evidence; command stdout/stderr and raw product payloads are discarded. The viewer is read-only.
The implementation is single-host Linux: flock leases and Git CAS protect local concurrency, but this is not a distributed lease/fencing service. It does not isolate mutually hostile processes sharing the same OS account. Recovery must verify actual Git/runtime state; interrupted records are retained. Remote publish, cross-host scheduling, notifications, native OS input, and production deployment remain out of scope.