Adapt Mocktioneer to edgezero v0.0.7 (extensible CLI, typed config, introspection, deploy lifecycle) - #110
Adapt Mocktioneer to edgezero v0.0.7 (extensible CLI, typed config, introspection, deploy lifecycle)#110aram356 wants to merge 51 commits into
Conversation
…tures, pricing docs, docker, prettier exclusion, CLI story)
…owers/ in docs prettier
…I seed, dockerfile cache-hygiene, docs build-exclusion, broader CLI docs)
…t(?Send) test impl, spin release build in verify)
…6, add clap/anyhow
…efault FIXED_BID_CPM
…er-cli, config gate
… (edgezero #269 removed request::dispatch)
…n/typed-config, add spin to agent builds, fix stale pricing/cli comments
…po-wide, update CLAUDE.md crate count/CPM
…ples; fix stale fixed-CPM comments
…r, fail-loud), config diff, CI/docs
…ush prereq to quickstarts; patch spin in local overlay
…dition 2024 The Spin description claimed wasm32-wasip2, but on main the adapter builds wasip1: spin-sdk is 5.2, edgezero.toml declares wasm32-wasip1, and the CI matrix runs the spin contract tests under wasip1 via wasmtime. wasip2 arrives with the spin-sdk 6 migration in #110, which adds its own wasip2 CI entry. Also update CLAUDE.md, which still declared Edition 2021.
… deploy) The Fastly deployment guide only seeded Viceroy's local state; following the production flow deployed fail-loud OpenRTB/APS endpoints with no config blob. Adds the first-deploy sequence, the already-deployed resource-link caveat (Fastly consumes [setup] only when deploy creates a new service), and a re-push-after-config-change note.
prk-Jr
left a comment
There was a problem hiding this comment.
PR Review
Summary
Adapts Mocktioneer to edgezero v0.0.4 (extensible CLI, blob-model typed config, pluggable introspection routes). The change is well-scoped, thoroughly tested, and has clearly been through several review rounds. No blocking findings. Verdict is COMMENT — a handful of non-blocking questions, one build-cache cleanup, a design-level footgun to keep in view, and a stale doc line. Inline comments carry the file-specific items.
Findings
♻️ Refactor
- Dockerfile redundant manifest COPYs — six per-crate
Cargo.tomlcopies are superseded byCOPY crates ./cratesbeforecargo fetch, so they add no layer caching. Inline atDockerfile:16.
❓ Questions
anyhowin core with nosrc/usage — likely required by theAppConfigderive; please confirm, else drop. Inline atcrates/mocktioneer-core/Cargo.toml:9.bid_cpmaccepts+Inf—exclusive_min = 0.0passes infinity; upstream non-finite rejection isn't pinned by a test in this repo. Inline atcrates/mocktioneer-core/src/config.rs:15.
🌱 Seeds / 📌 Out of scope
- Unauthenticated introspection on prod adapters —
/_mocktioneer/{manifest,config,routes}publicly enumerable on cloudflare/fastly. Documented and safe today; footgun once anything sensitive enters the manifest. Inline atedgezero.toml:15. - Breaking runtime change —
/openrtb2/auctionand/e/dtb/bidnow return503 config_out_of_dateuntil an operator runsconfig push. This is the intended fail-loud behavior and is well documented, but existing deploys that upgrade will start erroring until they push. Please make sure this lands prominently in the release notes / CHANGELOG so it isn't a surprise on rollout.
⛏ Nitpicks
- Stale doc —
GET /_/sizesstill described "with pricing" though cpm is no longer echoed. Inline atREADME.md:51.
👍 Praise
- Fail-loud contract is genuinely well tested:
assert_config_out_of_date(503 +Retry-After+error.kind = config_out_of_date) exercised at the route level for both OpenRTB and APS, plus config-seeded auction contract tests through the real Fastly (Viceroy) and Cloudflare (wasm-bindgen) runtimes, and a Docker smoke test that gates image publication on an actual live bid. Strong end-to-end coverage of the new config-store path. - Security hygiene from the base is preserved throughout: constant-time token comparison, hostname / path-injection validation on
ts_domain, log sanitization, and#[serde(deny_unknown_fields)]on the typed config.
CI Status
Verified locally on the branch head:
- fmt: PASS
- clippy (
--workspace --all-targets --all-features -D warnings): PASS - tests (
--workspace --all-targets, host targets): PASS
Full remote CI (wasm adapter matrix, Playwright, Docker build + smoke, docs ESLint/Prettier) reported green.
…d_cpm guard, trim redundant Dockerfile COPYs, correct README - anyhow was added to mocktioneer-core by this PR but never referenced; the AppConfig derive does not need it. Full suite + clippy pass without it. - Add a test pinning that edgezero's loader rejects bid_cpm = inf/-inf/nan with InvalidValue. range(exclusive_min = 0.0) alone lets inf through, so this guarantee was relied on in a doc comment but untested here. - The six per-crate Cargo.toml COPYs were superseded by COPY crates ./crates before cargo fetch, buying zero cache reuse. Verified image still builds and the smoke test serves a bid. - /_/sizes no longer echoes cpm; drop "with pricing".
ChristianPavilonis
left a comment
There was a problem hiding this comment.
Summary
Reviewed PR #110 against main. All reported CI checks pass, including Rust, adapter WASM, Docker smoke, Playwright, docs, and CodeQL. I found two non-blocking P2 issues; the CORS issue is inline.
The typed-config runtime wiring otherwise looks coherent, and I found no P0/P1 issues.
Additional P2 finding (not inline)
Trusted Server full-flow guide starts Axum without seeding typed config — docs/integrations/trusted-server.md:242-243
This standalone flow starts Axum directly, but it never copies mocktioneer.toml.example or runs config push. On a fresh checkout, the OpenRTB request in the later bidstream step now returns 503 config_out_of_date, so the documented flow cannot reach its verification step. Please add the config-copy and cargo run -p mocktioneer-cli -- config push --adapter axum --yes steps before starting the server. This was folded into the review body because the documentation file is unchanged in this PR and GitHub rejected an inline position for it.
…spection routes
The three /_mocktioneer/{manifest,config,routes} GET routes had no matching
OPTIONS triggers, so preflight returned 405 while the docs state every route
carries an OPTIONS handler. Adds the three OPTIONS triggers (shared
handle_options) and extends the router-level preflight test to cover them.
prk-Jr
left a comment
There was a problem hiding this comment.
PR Review
Summary
Adapts Mocktioneer to edgezero v0.0.4: manifest arg dropped from every run_app, Spin moved to spin-sdk 6 / wasm32-wasip2, bid_cpm lifted out of a compile-time constant into a validated MocktioneerConfig blob read through the fail-loud AppConfig extractor, a new mocktioneer-cli for typed config validate/diff/push, and framework introspection routes. 60 files, +3731/−1161.
I read every changed file and re-ran the gates locally. No correctness defect found in the Rust changes. The fail-loud migration is the risky part of this PR and it is handled carefully: the contract is asserted at the core-router level and driven end-to-end through the real Fastly and Cloudflare dispatch paths, the Docker image seeds the blob so the container still works out of the box, and the "push before you serve" prerequisite is documented on the README, both API pages, both integration pages and every adapter page.
Everything below is non-blocking. Two questions worth answering, a handful of gaps worth closing, some nits.
Findings
❓ Questions
- Config-store read on the auction hot path —
AppConfigcallsConfigStore::getper request with no caching in the extractor; the Cloudflare backend is a barestore.get(key).text()with nocacheTtl. Each auction is now a billable KV read plus cold-read latency where it used to be a constant. Axum (loads once at startup) and Fastly (edge-resident config store) are unaffected. Inline onroutes.rs. - Docker publish step rebuilds rather than repushing — the step comment claims it publishes the exact smoke-tested image; labels are added only in that step and the guarantee depends on a gha cache hit. Inline on
docker.yml.
🤔 Thoughts
- Retag instead of rebuild to make the publish gate literal — inline on
docker.yml. - Introspection is on by default, on all four adapters, behind
Access-Control-Allow-Origin: *— nothing sensitive today, butMOCKTIONEER_PULL_TOKENbecomes world-readable the day someone files it under[environment.variables]. Inline onedgezero.toml.
♻️ Refactor / test coverage
examples/openrtb_request.shandexamples/aps_request.shwere missed. Both POST to now-fail-loud endpoints and default tohttp://127.0.0.1:7676(Fastly), so on a fresh checkout they return503 config_out_of_datewith no hint why. Every other surface in the repo got the "push the config first" note — these two didn't. A comment near the top pointing atconfig push --adapter fastly --local, or a preflight that checks the status and prints the fix, would finish the sweep. (Not in the diff, so no inline anchor.)- Introspection
GETroutes have no test — only theOPTIONSpreflight is asserted. Inline onendpoints.rs. mocktioneer.toml.exampleisn't pinned toFIXED_BID_CPM— inline on the template.mocktioneer-clihas no tests —Args::command().debug_assert()is one line. Inline onmain.rs.
⛏ Nitpicks
@types/node ^26vs Node24.12.0in.tool-versions; eslint9 → 10is unrelated scope. Inline ondocs/package.json.CLAUDE.mdgate 5 documents a different invocation thantest.ymlruns. Inline.what-is-mocktioneer.mdrow still labeled "Fixed pricing". Inline.config.rsnon-finite test uses fixed-name files in the shared temp dir. Inline.
📌 Out of scope
spin upis blocked upstream (spin-sdk 6.0.0importswasi:http@0.3.0-rc, no released Spin provides it). The adapter builds and passes the wasmtime contract tests, and the limitation is documented in three places — butedgezero.tomlstill ships aservecommand that cannot work on any current runtime. Worth a tracking issue so it doesn't quietly become permanent.
👍 Praise
assert_config_out_of_dateasserts status andRetry-Afteranderror.kind— the compatibility contract, not a status code that could match by accident. Inline.loader_rejects_non_finite_bid_cpmpins an upstream guarantee thebid_cpmdoc comment leans on.inf > 0.0is true and TOML can expressbid_cpm = inf, so without edgezero's loader guard an operator could serve infinite-priced bids. Testing someone else's invariant is usually a smell; here it's the right call, and the comment says why. Inline.- The Fastly and Cloudflare contract tests drive a seeded blob through the real
dispatchpath (FastlyService/CloudflareService+with_config_handle), not just the core router — so the config-store binding at the adapter boundary is covered, which is exactly where this migration could have silently broken. - The Docker smoke test both gates publication and proves the
0.0.0.0bind by hitting the mapped host port — a loopback bind would fail it. Nice touch, and it catches the class of bug that only shows up in a container.
CI Status
Verified locally against 2e697d1:
cargo fmt --all -- --check— PASScargo clippy --workspace --all-targets --all-features -- -D warnings— PASScargo test --workspace --all-targets— PASS (170 tests: 150 + 12 + 8, 0 failed)
All GitHub checks green: cargo fmt, cargo test, fastly/cloudflare/spin wasm tests, playwright, format-docs, Docker build & push, CodeQL.
…ound 6 edgezero v0.0.5: - Repin the six edgezero-* deps to tag v0.0.5. Mocktioneer implements no Adapter trait, so the breaking provision-trait change does not apply; full workspace + wasm targets build unchanged. - Wire the new untyped `config gc` subcommand (run_config_gc) into mocktioneer-cli and document it in configuration.md. PR #110 review (prk-Jr, all non-blocking): - config.rs: switch the non-finite-cpm test to tempfile::tempdir() (unique dir, auto-cleanup); add example_template_matches_shipped_default pinning the shipped template against FIXED_BID_CPM. - mocktioneer-cli: add Args::command().debug_assert() clap-validity test. - endpoints.rs: add introspection_routes_serve_json covering the GET {routes,manifest,config} responses, not just the OPTIONS preflight. - docker.yml: publish by re-tagging the smoke-tested image and pushing it, instead of a cache-dependent rebuild; labels move to the loaded build. - CLAUDE.md: match the exact test.yml config-validate invocation. - docs: relabel 'Fixed pricing' -> 'Deterministic pricing'; pin @types/node to ^24 to match the Node 24 toolchain.
…ition (#112) * chore: inherit crate metadata from workspace and move to Rust 2024 edition Add authors, edition, publish, and version to [workspace.package] and have every crate inherit them, so metadata lives in one place instead of being re-declared per crate. Each [package] block is now name, description, then the inherited keys. Descriptions are new. Bump the workspace edition from 2021 to 2024. The upgrade compiled clean; the only source churn is mechanical: the 2024 style edition re-sorts imports, and clippy's collapsible_if now collapses nested if-let pairs into let-chains (stable in 2024). * Updated authors * fix: PR #112 review — Spin adapter targets wasm32-wasip1, CLAUDE.md edition 2024 The Spin description claimed wasm32-wasip2, but on main the adapter builds wasip1: spin-sdk is 5.2, edgezero.toml declares wasm32-wasip1, and the CI matrix runs the spin contract tests under wasip1 via wasmtime. wasip2 arrives with the spin-sdk 6 migration in #110, which adds its own wasip2 CI entry. Also update CLAUDE.md, which still declared Edition 2021.
…nsible-cli # Conflicts: # crates/mocktioneer-adapter-fastly/tests/contract.rs # crates/mocktioneer-core/src/routes.rs
…lback CLI commands edgezero v0.0.6 introduces three top-level staging-lifecycle commands (run_active_version / run_healthcheck / run_rollback). Repin the six edgezero-* deps to tag v0.0.6 and wire all three into mocktioneer-cli, plus document the deploy -> verify -> roll-back loop in the Fastly adapter guide. No breaking changes reached mocktioneer (it implements no Adapter trait); full workspace + all wasm targets build and the suite passes.
v0.0.7 is a small release (Fastly runtime_env_config exposure #351, rollback hint fix #352, dependency bumps) — no new CLI commands and no breaking changes reach mocktioneer. Full workspace + all wasm targets build; suite, clippy, and the config-validate gate pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Closes #115
What this PR does
Adapts Mocktioneer to the released edgezero
v0.0.7, which lands the extensible CLI (#269), pluggable introspection routes (#300), and the config-GC + deploy-lifecycle CLI commands. The design spec and implementation plan that opened this PR live underdocs/superpowers/; the implementation follows them end to end.Merged with
main(PR #112: Rust 2024 edition + workspace metadata inheritance) — the workspace is now edition 2024.Changes
edgezero-*git deps to the immutable tagv0.0.7; drop theinclude_str!(manifest)arg from every adapterrun_app; migrate the Spin adapter tospin-sdk ~6.0/wasm32-wasip2(Fastly stayswasm32-wasip1). Mocktioneer implements noAdaptertrait, so v0.0.5's breakingAdapter::provisionchange does not apply.MocktioneerConfig(bid_cpm,range(exclusive_min = 0.0)), a gitignoredmocktioneer.tomlwith a committedmocktioneer.toml.exampletemplate.mocktioneer-cli): mirrors edgezero's generated<name>-cli. Typedconfig validate/diff/pushoverMocktioneerConfig, plus every built-in command adopted as edgezero added them:config gc— reclaim orphaned config-store chunks (v0.0.5; untyped, dry-run by default).active-version/healthcheck/rollback— Fastly staging deploy-lifecycle (v0.0.6).auth/build/deploy/provision/serve(newintentionally dropped for an in-repo CLI)./openrtb2/auction) and APS (/e/dtb/bid) handlers readbid_cpmfrom the bound config store via edgezero's typedAppConfigextractor. A fresh deploy mustconfig pushonce before those endpoints serve bids — until then they return503 config_out_of_date.FIXED_BID_CPM = 0.20is the shipped default, not a runtime fallback. Static/creative/pixel endpoints are unaffected.config pushwrites the whole struct as a single canonical-JSON blob envelope (SHA-gated) under the store's key, per adapter (axum local file / cloudflare KV / fastly config store / spin KV)./_mocktioneer/{manifest,config,routes}routes fromedgezero_core::introspection(unauthenticated — restrict at the network/middleware layer;[environment.secrets]redacted). Hardening tracked in Gate unauthenticated introspection routes before sensitive config lands in the manifest #116.bid_cpmguard and template↔FIXED_BID_CPMdrift pinned; aclapCLI-definition assertion.[stores.config]in the manifest + Spinruntime-config.toml; Dockerfile seeds the default config blob, pinsdebian:bookworm-slim, and binds0.0.0.0; docs cover wasip2, fail-loud typed config, introspection, the remote Fastlyprovision → push → deployflow, and the new CLI commands; aconfig validate --strictCI gate + a Docker build/smoke gate that gates the registry push on the smoke test.Notes
v0.0.7, so no post-merge re-pin is needed.spin upis blocked upstream (tracked in Spin adapter cannot serve: spin-sdk 6 imports wasi:http@0.3.0-rc, unsupported by released Spin runtimes #113): the adapter compiles towasm32-wasip2and passes thewasmtimerouter contract tests, butspin upon current Spin runtimes fails to link becausespin-sdk 6.0.0pullswasi:http@0.3.0-rc, which no released Spin provides. Fastly / Cloudflare / Axum are unaffected.CI
fmt·clippy -D warnings·test --workspace·check --features "fastly cloudflare"·config validate --strict· Docker build + smoke · Playwright · docs ESLint/Prettier — all green.🤖 Generated with Claude Code