From 2b711dd895741de411307afa13c5bc3d5f707afd Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Sun, 14 Jun 2026 00:47:54 -0700 Subject: [PATCH 01/45] docs: design spec for edgezero #269 (extensible CLI) adaptation, based on main --- ...gezero-extensible-cli-adaptation-design.md | 328 ++++++++++++++++++ 1 file changed, 328 insertions(+) create mode 100644 docs/superpowers/specs/2026-06-11-edgezero-extensible-cli-adaptation-design.md diff --git a/docs/superpowers/specs/2026-06-11-edgezero-extensible-cli-adaptation-design.md b/docs/superpowers/specs/2026-06-11-edgezero-extensible-cli-adaptation-design.md new file mode 100644 index 0000000..630afb8 --- /dev/null +++ b/docs/superpowers/specs/2026-06-11-edgezero-extensible-cli-adaptation-design.md @@ -0,0 +1,328 @@ +# Design: Adapt Mocktioneer to edgezero #269 (extensible CLI) + +- **Date:** 2026-06-11 +- **Branch:** `feature/edgezero-extensible-cli` (off `main`) +- **Upstream:** [stackpop/edgezero#269](https://github.com/stackpop/edgezero/pull/269) + — "EdgeZero CLI Extensions". Head `feature/extensible-cli`, base + `chore/strict-clippy`, **OPEN / unmerged** (re-verified via + `gh pr view 269` on 2026-06-11; re-check at implementation). +- **Scope (approved):** Compat + typed config, with `bid_cpm` consumed at + runtime. Pin deps to `feature/extensible-cli` now; re-pin to `main` after + #269 merges. +- **Revision history:** R1 initial; R2 review (config lifecycle, validators, + scope); **R3 second review** — corrects two errors I made: (a) the **APS + path also uses the fixed CPM** (`build_aps_response` lives in `auction.rs` + and reads `FIXED_BID_CPM`), so `handle_aps_bid` is in scope; (b) Axum binds + an **empty** store when the local file is missing, so `Ok(None)` must + **fall back**, not error. Also: Spin config is KV-backed; `EdgeError`/ + validator signatures corrected; no `.tool-versions` bump needed; gitignore + + check-ci updates added. **R4 base change** — rebased onto `main`: PR #108 + ("Adopt edgezero strict-clippy gate and PR #257 API") merged the prerequisite + strict-clippy/#257 adaptation into `main`, and `main` pins all `edgezero-*` + deps at `branch = "main"`, so the dep repin is now `main` → + `feature/extensible-cli` (no longer stacked on `chore/edgezero-strict-clippy`). + R4 review findings (anyhow-in-core, Dockerfile manifest COPY, docs + edgezero-cli vs mocktioneer-cli story, exact check-ci commands, cli-crate + metadata/lints, docs-formatter/VitePress exclusion) are tracked but not yet + folded into the body below. + +## 1. Problem & context + +Mocktioneer consumes six `edgezero-*` crates from git, pinned (on `main`) to +edgezero's `branch = "main"`. PR #269 is a large, intentionally +backward-incompatible refactor. We adapt to the new APIs ahead of merge and +adopt the typed app-config surface, with `bid_cpm` wired through at request +time. (Base note: `main` already carries the strict-clippy + PR #257 edgezero +adaptation via PR #108, so this branch starts from that merged state.) + +### What #269 changes that actually reaches this repo + +From the sibling checkout `../edgezero` (`feature/extensible-cli`): + +- Mocktioneer has **no `[stores.*]`** today and makes **no + `kv_store`/`config_store`/`secret_store` calls**, so the store breakages are + inert until we opt in. +- `ctx.path()?` (with a `let x: T =` annotation), the `RequestContext(ctx)` + destructure, `RequestContext::new`, `ctx.request()`, and + `edgezero_core::context::RequestContext` **all still compile**. +- `app!` **still accepts the 2-arg custom-name form** → `MocktioneerApp` + retained, **no app rename**. +- `[adapters.*.build]`/`[adapters.*.commands]` remain valid; only legacy + `[stores.*]` and `[adapters.*.stores]` shapes are rejected — neither present. + +Parts that **do** reach this repo: + +1. Every adapter's `run_app` **dropped the `include_str!(manifest)` arg**. +2. **Spin → `spin-sdk ~6.0` / `wasm32-wasip2`** (`#[http_service]`). Fastly + stays `wasm32-wasip1`. +3. New **typed `AppConfig`** + generated `-cli` shape + per-adapter + config-store **lifecycle** (declare → seed → auto-bind on serve → read). + +## 2. Goals / non-goals + +**Goals** + +- Builds, lints (`-D warnings`), tests green against `feature/extensible-cli` + on all targets (native, Fastly `wasm32-wasip1`, Spin `wasm32-wasip2`, + Cloudflare `wasm32-unknown-unknown`). +- Typed config: validated `MocktioneerConfig`, `mocktioneer.toml`, + `mocktioneer-cli`, and a **required** `config validate --strict` CI gate. +- `bid_cpm` consumed at runtime by **both** the OpenRTB auction path + (`handle_openrtb_auction`) **and** the APS path (`handle_aps_bid`), since + both currently emit `FIXED_BID_CPM`, with a defined fallback/error contract. + +**Non-goals** + +- KV or secret stores. +- Re-pinning to `main` (post-merge follow-up). +- Threading any other config field through rendering/business logic (config v1 + is `bid_cpm` only). +- Fixing pre-existing `Uuid::now_v7()` non-determinism in bid IDs (§5; out of + scope). + +## 3. Design + +### 3.1 Dependency pin — `Cargo.toml` + +- Six `edgezero-*` git deps: `branch = "main"` → `branch = "feature/extensible-cli"`. +- `spin-sdk = "5.2"` → `{ version = "~6.0", default-features = false, + features = ["http", "key-value", "variables"] }`. +- Add `clap = { version = "4", features = ["derive"] }` to + `[workspace.dependencies]` (consumed as `clap = { workspace = true }`). +- Regenerate `Cargo.lock`. + +### 3.2 Adapter entrypoints — drop the manifest arg + +| File | After | +| --- | --- | +| `mocktioneer-adapter-axum/src/main.rs` | `run_app::()` | +| `mocktioneer-adapter-cloudflare/src/lib.rs` | `run_app::(req, env, ctx)` | +| `mocktioneer-adapter-fastly/src/main.rs` | `run_app::(req)` | +| `mocktioneer-adapter-spin/src/lib.rs` | `#[http_service]` + `Request` + `run_app::(req)` | + +Spin `lib.rs` swaps `expect(unsafe_code, …)` → `allow(unsafe_code, reason = +"spin's #[http_service] macro generates the unsafe wasm export")`; final +import/`no_main` shape follows the SDK-6 template (compile-driven). + +### 3.3 Spin → `wasm32-wasip2` + +- `crates/mocktioneer-adapter-spin/spin.toml`: `source` path + `build.command` + `wasip1` → `wasip2`. +- `edgezero.toml` `[adapters.spin.build].target`: `wasip1` → `wasip2`. +- `crates/mocktioneer-adapter-spin/src/main.rs` host-stub messages. +- `crates/mocktioneer-adapter-spin/tests/contract.rs` comments + runner + invocation (now a `wasm32-wasip2` component), mirroring edgezero's spin + contract test. +- **Fastly unchanged** (`wasm32-wasip1`). + +**`.tool-versions` — no change needed (verified).** Mocktioneer's +`.tool-versions` has no Spin pin; the only deltas vs edgezero are Fastly +(13.0.0 vs 15.1.0) and Wasmtime (45.0.0 vs 44.0.1). Mocktioneer's +**Wasmtime 45.0.0 already supports wasip2/component-model** (newer than +edgezero's pin), so running wasip2 Spin components needs no bump. Confirm at +implementation that 45.0.0 runs the component; only then revisit. + +**Runner config:** `.cargo/config.toml` defines only `[target.wasm32-wasip1]` +(Viceroy, Fastly). Spin wasip2 contract tests use +`CARGO_TARGET_WASM32_WASIP2_RUNNER` (e.g. `wasmtime run`) set **per-job in +CI** and documented for local runs — **not** a global config entry that would +disturb Fastly's wasip1 Viceroy runner. + +### 3.4 Manifest — `edgezero.toml` + +- Existing `[[triggers.http]]` / `[adapters.*]` tables accepted as-is. +- Add: + ```toml + [stores.config] + ids = ["mocktioneer_config"] + ``` +- Add per-adapter native-backing entries where the new schema requires them + for non-axum adapters (see §3.6; compile/validate-driven). +- `manifest.validate()` runs at compile time inside `app!`. + +### 3.5 Typed config struct + runtime contract + +**Struct** — `crates/mocktioneer-core/src/config.rs`: + +```rust +use serde::{Deserialize, Serialize}; +use validator::{Validate, ValidationError}; + +#[derive(Debug, Deserialize, Serialize, Validate, edgezero_core::AppConfig)] +#[serde(deny_unknown_fields)] +pub struct MocktioneerConfig { + /// Fixed bid CPM in USD. Validated finite and strictly positive. + #[validate(custom(function = "validate_bid_cpm"))] + pub bid_cpm: f64, +} + +// `validator` custom fns take a reference to the field (cf. +// routes.rs `validate_static_asset_size(value: &str)`); `range` does not +// reject NaN / inf for floats, so validate explicitly. +fn validate_bid_cpm(value: &f64) -> Result<(), ValidationError> { + if value.is_finite() && *value > 0.0 { + Ok(()) + } else { + Err(ValidationError::new("bid_cpm_must_be_finite_positive")) + } +} +``` + +- `creative_label` (R1 draft) removed — YAGNI/unused. +- `pub mod config;` in `crates/mocktioneer-core/src/lib.rs`. +- Root **`mocktioneer.toml`** (1:1, no `[config]` wrapper): `bid_cpm = 0.20`. + +**Runtime resolution — shared helper, used by BOTH handlers.** Both +`build_aps_response` (in `auction.rs`, reads `FIXED_BID_CPM` at line ~259) and +the OpenRTB bid builder (`auction.rs` ~116/120) emit the fixed CPM, so both +`handle_openrtb_auction` (already binds `RequestContext(ctx)`) and +`handle_aps_bid` (gains `RequestContext(ctx)`, alongside its existing +`ForwardedHost`/`ValidatedJson` — the multi-extractor pattern +`handle_openrtb_auction` already uses) resolve `cpm` once and thread it in: + +```rust +// routes.rs +async fn resolve_bid_cpm(ctx: &RequestContext) -> Result { + // No config store bound at all → compile-time default. + let Some(store) = ctx.config_store_default() else { + return Ok(auction::FIXED_BID_CPM); + }; + // Store read errors map through `From` (→ 400/503/500); + // do not mask a broken backend as a $0.20 bid. + match store.get("bid_cpm").await.map_err(EdgeError::from)? { + // Declared but unseeded: Axum binds an EMPTY store when + // `.edgezero/local-config-mocktioneer_config.json` is absent + // (dev_server.rs `from_local_file` → `Ok(empty)`), so a missing key + // is the normal "not yet pushed" state → fall back, not error. + None => Ok(auction::FIXED_BID_CPM), + // Present but malformed → real misconfiguration, surface it. + Some(raw) => raw + .parse::() + .ok() + .filter(|v| v.is_finite() && *v > 0.0) + .ok_or_else(|| { + EdgeError::internal(anyhow::anyhow!( + "config store `mocktioneer_config` has malformed bid_cpm: {raw:?}" + )) + }), + } +} +``` + +`auction.rs` bid builders and `build_aps_response` take a `cpm: f64` parameter +(replacing direct `FIXED_BID_CPM` reads); `FIXED_BID_CPM` stays as the +fallback constant and the value existing tests pass explicitly. **Contract +summary:** no store → fallback; bound + key absent → fallback; bound + read +error → propagate; bound + present-but-malformed → error. `EdgeError::internal` +takes `Into` (error.rs:63), hence `anyhow::anyhow!`, not a bare +`String`. **Determinism preserved** on the fallback path (semantic outputs +match `main`). + +### 3.6 Config lifecycle (declare → seed → bind → read) + +Binding on the **serve path is automatic** once the store is declared and the +backing exists — `edgezero-adapter-axum/src/dev_server.rs:333 run_app` → +`build_config_registry(stores.config)` reads +`.edgezero/local-config-.json`; Cloudflare (`request.rs:362`), Fastly +(`request.rs:382`), Spin (`config_store.rs` via `request::build_config_registry`) +have equivalents. The `app!` macro emits `stores().config` from +`[stores.config]`. **Axum binds an empty store when the file is missing** (it +does not skip the id), which is exactly why §3.5's contract falls back on +`Ok(None)`. + +Per-adapter backing + seed step: + +| Adapter | Backing for `mocktioneer_config` | Seed | +| --- | --- | --- | +| **Axum** (primary local/CI path) | `.edgezero/local-config-mocktioneer_config.json` | `mocktioneer-cli config push --adapter axum` writes it. | +| **Cloudflare** | KV namespace (config moved `[vars]`→KV in #269) | `provision --adapter cloudflare` (writes id to `wrangler.toml`) + `config push --adapter cloudflare`. | +| **Fastly** | config store + `[setup]`/`[local_server]` in `fastly.toml` | `provision --adapter fastly` + `config push --adapter fastly`. | +| **Spin** | **KV-backed** (`key_value_stores = ["mocktioneer_config"]` + `[key_value_store.mocktioneer_config]` runtime config) — config is KV-backed in #269; `[variables]` is secrets-only now (`edgezero-adapter-spin/src/config_store.rs`). | `provision --adapter spin` + `config push --adapter spin`. | + +**Pragmatics:** the **axum** path is the one exercised locally and in CI. +Cloud adapters get the manifest declaration + required native-backing tables so +they *build and validate*; push/provision are documented but live cloud stores +are not part of CI. Handler tests wire a `ConfigRegistry` fixture directly (the +app-demo `handlers.rs` test pattern) to cover the seeded-value, empty-store +(fallback), and malformed-value (error) branches without a live backend. + +### 3.7 `mocktioneer-cli` crate + +`crates/mocktioneer-cli/`, mirroring `edgezero-cli/src/templates/cli/`: + +- `Cargo.toml`: `mocktioneer-core`, `edgezero-cli`, `clap = { workspace = true }`, + `log`. +- `src/main.rs`: clap `Args`/`Cmd` flattening + `edgezero_cli::run_{auth,build,deploy,new,provision,serve}` + a typed + `Config` subcommand dispatching `run_config_validate_typed::` + and `run_config_push_typed::`. +- Add `crates/mocktioneer-cli` to root `[workspace].members`. + +### 3.8 Docs, agents, ignore files (verified surface) + +- `wasip1` → `wasip2` for **Spin only** + spin-sdk-6 note, across the files + that reference it (rg-verified): `CLAUDE.md`, `edgezero.toml`, the spin crate + files (§3.3), `.claude/agents/{code-architect,build-validator,verify-app}.md`, + `docs/guide/getting-started.md`, `docs/guide/configuration.md`, + `.cargo/config.toml` (comment), `.github/workflows/test.yml` (§3.9). + **Leave Fastly wasip1 intact.** No `docs/guide/adapters/spin.md` exists; + `check-ci.md`/playwright README have no spin/wasip refs. +- **`.gitignore`:** add `.edgezero/` (currently only `.spin/` is ignored; the + worktree already has an untracked `.edgezero/`). +- **`.claude/commands/check-ci.md`** and **`CLAUDE.md` "CI Gates"**: add the + new `config validate --strict` gate so local CI docs aren't stale. + +### 3.9 CI — `.github/workflows/test.yml` + +- Spin matrix: target `wasip1` → `wasip2`; install the `wasm32-wasip2` rustup + target; set `CARGO_TARGET_WASM32_WASIP2_RUNNER`; keep the pinned Wasmtime + install and confirm it runs wasip2 components. +- `mocktioneer-cli` covered by `--workspace`. +- **Required gate:** `cargo run -p mocktioneer-cli -- config validate --strict`, + plus a **real** `config push --adapter axum` (not dry-run — dry-run does not + seed) followed by an assertion that a seeded `bid_cpm` flows through (an + integration test or a serve smoke), so the seed → bind → read path is + actually exercised, not just validated. +- If the docs Prettier/ESLint gate scopes `docs/**`, exclude + `docs/superpowers/**`. + +## 4. Risks & mitigations + +| Risk | Mitigation | +| --- | --- | +| New parser rejects an existing `[adapters.*]` table | Compile-time `manifest.validate()` surfaces it; fix per upstream error. | +| Spin SDK 6 macro/type churn beyond template | Mirror edgezero's `edgezero-adapter-spin` verbatim; build wasip2. | +| Wasmtime can't run the wasip2 component | Wasmtime 45.0.0 supports it; set `CARGO_TARGET_WASM32_WASIP2_RUNNER`; match edgezero's contract-test config. | +| Fresh dev errors before any push | Resolved: empty/absent → fallback to `FIXED_BID_CPM` (§3.5). | +| Broken/malformed pushed config masked as $0.20 | Read errors propagate; malformed present value errors (§3.5). | +| `bid_cpm` never exercised (store unseeded) | CI does a real `config push --adapter axum` + asserts the value flows; fixtures cover all branches. | +| Pinning to an unmerged branch | Documented; re-pin to `main` post-merge. | +| `.cargo/config.toml.local` patch drift (`edgezero-macros`) | Already lists it; verify it patches cleanly. | + +## 5. Verification + +1. `cargo fmt --all -- --check` +2. `cargo clippy --workspace --all-targets --all-features -- -D warnings` +3. `cargo test --workspace --all-targets` +4. `cargo check --workspace --all-targets --features "fastly cloudflare"` +5. Wasm builds: Fastly (`wasm32-wasip1`), Spin (`wasm32-wasip2`), Cloudflare + (`wasm32-unknown-unknown`). +6. `cargo run -p mocktioneer-cli -- config validate --strict` passes; negative + / non-finite `bid_cpm` fail validation. +7. Spin `contract.rs` green under the wasip2 wasmtime runner. +8. **Semantic** comparison of OpenRTB **and APS** responses vs `main` with no + config store bound: bid/slot `price` (and APS `encoded_price`), sizes, + `cur`, creative markup/URLs, targeting match; volatile fields (IDs from + `Uuid::now_v7()`, auction.rs:51 / mediation.rs:102 / APS `new_id()`) are + excluded — byte equality is impossible even on `main`. +9. Runtime exercise: with `.edgezero/local-config-mocktioneer_config.json` = + `{"bid_cpm":"0.35"}`, OpenRTB and APS emit `0.35`; with the file absent or + an empty object, they emit `0.20` (no error); with `{"bid_cpm":"-1"}` the + handler returns an error. + +## 6. Rollback / follow-ups + +- Rollback = revert the branch; deps return to edgezero `branch = "main"`. +- Follow-up: re-pin all `edgezero-*` deps to `main` once #269 merges. +- Pre-existing tech-debt (not fixed here): bid IDs use `Uuid::now_v7()`, which + is non-deterministic despite the project's determinism guarantee. From 3e6441ff1f72725fb0ad5ccfa3d25ba2ccc8a8b8 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Mon, 15 Jun 2026 21:34:37 -0700 Subject: [PATCH 02/45] docs: fold R5 review into spec body (anyhow, spin runtime-config, fixtures, pricing docs, docker, prettier exclusion, CLI story) --- ...gezero-extensible-cli-adaptation-design.md | 168 ++++++++++++++---- 1 file changed, 129 insertions(+), 39 deletions(-) diff --git a/docs/superpowers/specs/2026-06-11-edgezero-extensible-cli-adaptation-design.md b/docs/superpowers/specs/2026-06-11-edgezero-extensible-cli-adaptation-design.md index 630afb8..3fc0ae1 100644 --- a/docs/superpowers/specs/2026-06-11-edgezero-extensible-cli-adaptation-design.md +++ b/docs/superpowers/specs/2026-06-11-edgezero-extensible-cli-adaptation-design.md @@ -3,9 +3,10 @@ - **Date:** 2026-06-11 - **Branch:** `feature/edgezero-extensible-cli` (off `main`) - **Upstream:** [stackpop/edgezero#269](https://github.com/stackpop/edgezero/pull/269) - — "EdgeZero CLI Extensions". Head `feature/extensible-cli`, base - `chore/strict-clippy`, **OPEN / unmerged** (re-verified via - `gh pr view 269` on 2026-06-11; re-check at implementation). + — "EdgeZero CLI Extensions". Head `feature/extensible-cli`, base **`main`** + (rebased off `chore/strict-clippy`), **OPEN / unmerged** (re-verified via + `gh pr view 269` on 2026-06-15: state OPEN, base `main`, updated + 2026-06-12; re-check at implementation). - **Scope (approved):** Compat + typed config, with `bid_cpm` consumed at runtime. Pin deps to `feature/extensible-cli` now; re-pin to `main` after #269 merges. @@ -21,10 +22,14 @@ strict-clippy/#257 adaptation into `main`, and `main` pins all `edgezero-*` deps at `branch = "main"`, so the dep repin is now `main` → `feature/extensible-cli` (no longer stacked on `chore/edgezero-strict-clippy`). - R4 review findings (anyhow-in-core, Dockerfile manifest COPY, docs - edgezero-cli vs mocktioneer-cli story, exact check-ci commands, cli-crate - metadata/lints, docs-formatter/VitePress exclusion) are tracked but not yet - folded into the body below. + **R5** — folded all prior review findings into the body: anyhow-in-core + (§3.1), Dockerfile manifest COPY (§3.7/§5), cli-crate metadata/lints (§3.7), + mandatory docs-formatter + VitePress exclusion (§3.8), the wider + edgezero-cli→mocktioneer-cli story incl. README/Playwright (§3.8), the public + "$0.20 always" → "$0.20 default" pricing-doc rewrite (§3.8), the Spin + `runtime-config.toml`/`--runtime-config-file` requirement (§3.6), the + malformed-config-FILE-vs-read-error nuance (§3.5), and an explicit seeded + test fixture (§5). ## 1. Problem & context @@ -89,6 +94,9 @@ Parts that **do** reach this repo: features = ["http", "key-value", "variables"] }`. - Add `clap = { version = "4", features = ["derive"] }` to `[workspace.dependencies]` (consumed as `clap = { workspace = true }`). +- Add `anyhow = { workspace = true }` to **`crates/mocktioneer-core/Cargo.toml`** + (the §3.5 helper uses `anyhow::anyhow!`; core does not currently depend on + anyhow — only the workspace table does). `anyhow` is WASM-compatible. - Regenerate `Cargo.lock`. ### 3.2 Adapter entrypoints — drop the manifest arg @@ -211,10 +219,24 @@ async fn resolve_bid_cpm(ctx: &RequestContext) -> Result { `auction.rs` bid builders and `build_aps_response` take a `cpm: f64` parameter (replacing direct `FIXED_BID_CPM` reads); `FIXED_BID_CPM` stays as the -fallback constant and the value existing tests pass explicitly. **Contract -summary:** no store → fallback; bound + key absent → fallback; bound + read -error → propagate; bound + present-but-malformed → error. `EdgeError::internal` -takes `Into` (error.rs:63), hence `anyhow::anyhow!`, not a bare +fallback constant and the value existing tests pass explicitly. + +**Contract summary** (note the malformed-*file* vs malformed-*value* +distinction, which is easy to conflate): + +| Runtime situation | `config_store_default()` / `get` | Result | +| --- | --- | --- | +| No `[stores.config]`, or registry dropped | `None` | fallback `FIXED_BID_CPM` | +| Axum local file **absent** (empty store bound) | `Ok(None)` | fallback | +| File present, **missing** the `bid_cpm` key | `Ok(None)` | fallback | +| File **malformed JSON** | store **dropped at bind** → `config_store_default()` is `None` (Axum `build_config_registry` drops the id, and if it's the default id the whole registry is dropped — it does **not** surface as a `get` error) | fallback | +| Backend **read error** at `get` time (e.g. CF/Fastly KV hiccup) | `Err(ConfigStoreError)` | propagate via `EdgeError::from` (→ 400/503/500) | +| Value present but unparseable / non-finite / ≤ 0 | `Ok(Some(bad))` | **error** (`EdgeError::internal`) | + +So a malformed Axum config *file* degrades to the fallback (the bind-time drop +means handlers never see it), while a malformed *value* in an otherwise-valid +file is a real misconfiguration and errors. `EdgeError::internal` takes +`Into` (error.rs:63), hence `anyhow::anyhow!`, not a bare `String`. **Determinism preserved** on the fallback path (semantic outputs match `main`). @@ -237,40 +259,85 @@ Per-adapter backing + seed step: | **Axum** (primary local/CI path) | `.edgezero/local-config-mocktioneer_config.json` | `mocktioneer-cli config push --adapter axum` writes it. | | **Cloudflare** | KV namespace (config moved `[vars]`→KV in #269) | `provision --adapter cloudflare` (writes id to `wrangler.toml`) + `config push --adapter cloudflare`. | | **Fastly** | config store + `[setup]`/`[local_server]` in `fastly.toml` | `provision --adapter fastly` + `config push --adapter fastly`. | -| **Spin** | **KV-backed** (`key_value_stores = ["mocktioneer_config"]` + `[key_value_store.mocktioneer_config]` runtime config) — config is KV-backed in #269; `[variables]` is secrets-only now (`edgezero-adapter-spin/src/config_store.rs`). | `provision --adapter spin` + `config push --adapter spin`. | +| **Spin** | **KV-backed** (`key_value_stores = ["mocktioneer_config"]` in `spin.toml` + a **`runtime-config.toml`** declaring the `[key_value_store.mocktioneer_config]` backend) — config is KV-backed in #269; `[variables]` is secrets-only now (`edgezero-adapter-spin/src/config_store.rs`). | `provision --adapter spin` + `config push --adapter spin`. | + +**Spin runtime-config gap (must add):** Mocktioneer's Spin adapter has only +`spin.toml` and **no `runtime-config.toml`**, and `edgezero.toml`'s spin +commands (`spin up/build/deploy --from …/spin.toml`) pass **no +`--runtime-config-file`**. KV-backed config needs both: add +`crates/mocktioneer-adapter-spin/runtime-config.toml` with a +`[key_value_store.mocktioneer_config]` entry, and update the +`[adapters.spin.commands]` `serve`/`deploy` lines (and docs) to pass +`--runtime-config-file crates/mocktioneer-adapter-spin/runtime-config.toml`. **Pragmatics:** the **axum** path is the one exercised locally and in CI. -Cloud adapters get the manifest declaration + required native-backing tables so -they *build and validate*; push/provision are documented but live cloud stores -are not part of CI. Handler tests wire a `ConfigRegistry` fixture directly (the -app-demo `handlers.rs` test pattern) to cover the seeded-value, empty-store -(fallback), and malformed-value (error) branches without a live backend. +Cloud/Spin adapters get the manifest declaration + required native-backing +tables (incl. the Spin `runtime-config.toml`) so they *build and validate*; +push/provision are documented but live cloud/Spin stores are not stood up in +CI. Handler tests wire a `ConfigRegistry` fixture directly (the app-demo +`handlers.rs` test pattern) to cover the seeded-value, empty-store (fallback), +and malformed-value (error) branches without a live backend. ### 3.7 `mocktioneer-cli` crate `crates/mocktioneer-cli/`, mirroring `edgezero-cli/src/templates/cli/`: -- `Cargo.toml`: `mocktioneer-core`, `edgezero-cli`, `clap = { workspace = true }`, - `log`. +- `Cargo.toml`: deps `mocktioneer-core`, `edgezero-cli`, + `clap = { workspace = true }`, `log`. **Package metadata matching the other + crates:** `publish = false`, `license.workspace = true`, and + `[lints] workspace = true` (cf. `mocktioneer-core/Cargo.toml`). - `src/main.rs`: clap `Args`/`Cmd` flattening `edgezero_cli::run_{auth,build,deploy,new,provision,serve}` + a typed `Config` subcommand dispatching `run_config_validate_typed::` and `run_config_push_typed::`. - Add `crates/mocktioneer-cli` to root `[workspace].members`. +- **Dockerfile:** the build pre-copies each crate manifest before + `cargo fetch --locked` ([Dockerfile:14-19]) to cache the dependency layer. + Add `COPY crates/mocktioneer-cli/Cargo.toml crates/mocktioneer-cli/Cargo.toml` + alongside the existing crate-manifest COPYs so the workspace `cargo fetch` + resolves with the new member present. (The image still ships only the axum + binary; the CLI crate just needs to be fetch-resolvable.) ### 3.8 Docs, agents, ignore files (verified surface) -- `wasip1` → `wasip2` for **Spin only** + spin-sdk-6 note, across the files - that reference it (rg-verified): `CLAUDE.md`, `edgezero.toml`, the spin crate - files (§3.3), `.claude/agents/{code-architect,build-validator,verify-app}.md`, +- **Spin wasip1 → wasip2** + spin-sdk-6 note, across the files that reference + it (rg-verified): `CLAUDE.md`, `edgezero.toml`, the spin crate files (§3.3), + `.claude/agents/{code-architect,build-validator,verify-app}.md`, `docs/guide/getting-started.md`, `docs/guide/configuration.md`, `.cargo/config.toml` (comment), `.github/workflows/test.yml` (§3.9). - **Leave Fastly wasip1 intact.** No `docs/guide/adapters/spin.md` exists; - `check-ci.md`/playwright README have no spin/wasip refs. + **Leave Fastly wasip1 intact.** No `docs/guide/adapters/spin.md` exists. +- **Pricing docs — "$0.20 always/fixed" → "$0.20 default (configurable via + `bid_cpm`)".** This change makes the fixed CPM a *default*, not an invariant, + so the public claims must be reworded: `docs/guide/what-is-mocktioneer.md`, + `docs/guide/architecture.md`, `docs/integrations/prebidjs.md`, + `docs/integrations/prebid-server.md`, `docs/integrations/index.md`, + `docs/api/openrtb-auction.md`, `docs/api/aps-bid.md`, `docs/api/index.md` + (rg-verified). Mention the config + push path briefly; keep `0.20` as the + shipped default. +- **edgezero-cli → mocktioneer-cli story (wider than VitePress docs).** After + adding the in-repo `mocktioneer-cli`, distinguish the two everywhere they're + referenced: `README.md`, `docs/guide/getting-started.md`, + `docs/guide/adapters/index.md`, `tests/playwright/README.md`, and + `tests/playwright/playwright.config.ts` (the `webServer` command). Rule: + **`config validate`/`config push` are typed and live only in + `mocktioneer-cli`**; `serve`/`build`/`deploy`/`auth`/`provision` work from + either the external `edgezero-cli` or the vendored `mocktioneer-cli`. Update + the "optional, not vendored" framing — `mocktioneer-cli` *is* in-repo. + Playwright's `webServer` should use `cargo run -p mocktioneer-cli -- serve + --adapter …` (no external install needed). - **`.gitignore`:** add `.edgezero/` (currently only `.spin/` is ignored; the worktree already has an untracked `.edgezero/`). - **`.claude/commands/check-ci.md`** and **`CLAUDE.md` "CI Gates"**: add the - new `config validate --strict` gate so local CI docs aren't stale. + new `config validate --strict` gate (exact command in §3.9) so local CI docs + aren't stale. +- **Docs formatter + VitePress exclusion (mandatory — this spec lives under + `docs/`).** `docs/package.json`'s `format` runs `prettier --check .` and the + format CI job runs it; it **fails on this spec file** today, and VitePress + even built it into `docs/.vitepress/dist/…/superpowers/…`. Required: + (a) add `superpowers/` to `docs/.prettierignore`; (b) add + `srcExclude: ['**/superpowers/**']` to the VitePress config so internal specs + aren't published; (c) ensure no built `superpowers` artifact is committed + under `docs/.vitepress/dist/`. ### 3.9 CI — `.github/workflows/test.yml` @@ -278,13 +345,22 @@ app-demo `handlers.rs` test pattern) to cover the seeded-value, empty-store target; set `CARGO_TARGET_WASM32_WASIP2_RUNNER`; keep the pinned Wasmtime install and confirm it runs wasip2 components. - `mocktioneer-cli` covered by `--workspace`. -- **Required gate:** `cargo run -p mocktioneer-cli -- config validate --strict`, - plus a **real** `config push --adapter axum` (not dry-run — dry-run does not - seed) followed by an assertion that a seeded `bid_cpm` flows through (an - integration test or a serve smoke), so the seed → bind → read path is - actually exercised, not just validated. -- If the docs Prettier/ESLint gate scopes `docs/**`, exclude - `docs/superpowers/**`. +- **Required gate** (also mirror these exact commands into + `.claude/commands/check-ci.md` and the `CLAUDE.md` CI-gates list): + ```sh + cargo run -p mocktioneer-cli -- config validate --strict + cargo run -p mocktioneer-cli -- config push --adapter axum # real, not --dry-run + ``` + followed by an assertion that the seeded `bid_cpm` flows through (an + integration test or serve smoke), so the seed → bind → read path is + exercised, not just validated. +- **Docker:** `.github/workflows/docker.yml` builds the image; with + `mocktioneer-cli` added as a workspace member, confirm the Dockerfile + manifest pre-copy (§3.7) keeps `cargo fetch --locked` working. Add a + `docker build` smoke if not already covered. +- **Docs formatter is mandatory, not conditional:** the format CI job already + fails on this spec, so the `docs/.prettierignore` + VitePress `srcExclude` + changes (§3.8) must land in this branch. ## 4. Risks & mitigations @@ -294,9 +370,12 @@ app-demo `handlers.rs` test pattern) to cover the seeded-value, empty-store | Spin SDK 6 macro/type churn beyond template | Mirror edgezero's `edgezero-adapter-spin` verbatim; build wasip2. | | Wasmtime can't run the wasip2 component | Wasmtime 45.0.0 supports it; set `CARGO_TARGET_WASM32_WASIP2_RUNNER`; match edgezero's contract-test config. | | Fresh dev errors before any push | Resolved: empty/absent → fallback to `FIXED_BID_CPM` (§3.5). | -| Broken/malformed pushed config masked as $0.20 | Read errors propagate; malformed present value errors (§3.5). | -| `bid_cpm` never exercised (store unseeded) | CI does a real `config push --adapter axum` + asserts the value flows; fixtures cover all branches. | -| Pinning to an unmerged branch | Documented; re-pin to `main` post-merge. | +| Broken/malformed pushed *value* masked as $0.20 | Read errors propagate; malformed present value errors (§3.5). Note a malformed *file* degrades to fallback (bind-time drop), by design. | +| `bid_cpm` never exercised (store unseeded) | CI does a real `config push --adapter axum` + asserts the value flows; fixtures cover all branches (§5). | +| Spin KV config silently empty (no `runtime-config.toml`) | Add `runtime-config.toml` + `--runtime-config-file` to spin commands (§3.6). | +| New `mocktioneer-cli` breaks Docker dependency layer | Pre-copy its `Cargo.toml` before `cargo fetch` (§3.7); `docker build` smoke (§5). | +| Spec under `docs/` fails the format CI gate | Mandatory `docs/.prettierignore` + VitePress `srcExclude` (§3.8). | +| Pinning to an unmerged branch | Documented; re-pin to edgezero `main` post-merge. | | `.cargo/config.toml.local` patch drift (`edgezero-macros`) | Already lists it; verify it patches cleanly. | ## 5. Verification @@ -315,10 +394,21 @@ app-demo `handlers.rs` test pattern) to cover the seeded-value, empty-store `cur`, creative markup/URLs, targeting match; volatile fields (IDs from `Uuid::now_v7()`, auction.rs:51 / mediation.rs:102 / APS `new_id()`) are excluded — byte equality is impossible even on `main`. -9. Runtime exercise: with `.edgezero/local-config-mocktioneer_config.json` = - `{"bid_cpm":"0.35"}`, OpenRTB and APS emit `0.35`; with the file absent or - an empty object, they emit `0.20` (no error); with `{"bid_cpm":"-1"}` the - handler returns an error. +9. Runtime exercise — **explicit fixtures** (the root `mocktioneer.toml` ships + `bid_cpm = 0.20`, so 0.35 must come from a seeded store, not the default): + - **Unit (preferred, deterministic, no files):** build a `RequestContext` + with a `ConfigRegistry` fixture wrapping an in-memory + `MapConfigStore { "bid_cpm": "0.35" }` (the app-demo `handlers.rs` + pattern); assert OpenRTB and APS emit `0.35`. A no-registry context → `0.20`; + a `{ "bid_cpm": "-1" }` fixture → handler error. + - **Integration (axum seed path):** write a temp config with `bid_cpm = 0.35` + and run `cargo run -p mocktioneer-cli -- config push --adapter axum` (or + write `.edgezero/local-config-mocktioneer_config.json` = + `{"bid_cpm":"0.35"}` directly), serve, and assert the auction returns + `0.35`; remove the file and assert `0.20` with no error. +10. `docs/` formatter passes: `cd docs && npm run format` succeeds (i.e. the + `superpowers/` prettier-ignore is in place). +11. `docker build` succeeds with `mocktioneer-cli` in the workspace. ## 6. Rollback / follow-ups From b89efd38e76c2c8dd7dd95f853baa70b6f3b8d4f Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Mon, 15 Jun 2026 22:09:23 -0700 Subject: [PATCH 03/45] docs: implementation plan for edgezero #269 adaptation; ignore superpowers/ in docs prettier --- docs/.prettierignore | 1 + ...6-15-edgezero-extensible-cli-adaptation.md | 1022 +++++++++++++++++ 2 files changed, 1023 insertions(+) create mode 100644 docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md diff --git a/docs/.prettierignore b/docs/.prettierignore index 94aa6e0..c1f8cd8 100644 --- a/docs/.prettierignore +++ b/docs/.prettierignore @@ -1,3 +1,4 @@ .vitepress/cache .vitepress/dist node_modules +superpowers/ diff --git a/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md b/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md new file mode 100644 index 0000000..01eebff --- /dev/null +++ b/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md @@ -0,0 +1,1022 @@ +# Edgezero #269 (Extensible CLI) Adaptation — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Adapt Mocktioneer to the breaking edgezero #269 API (extensible CLI, dropped `run_app` manifest arg, Spin SDK 6 / wasip2) and adopt typed `AppConfig` so `bid_cpm` is configurable at runtime, defaulting to `FIXED_BID_CPM`. + +**Architecture:** Pin the six `edgezero-*` git deps to `feature/extensible-cli`; fix every adapter entrypoint; migrate the Spin adapter to `spin-sdk ~6.0` / `wasm32-wasip2`; add a `MocktioneerConfig` typed-config struct + `mocktioneer.toml` + a `mocktioneer-cli` crate that mirrors edgezero's generated `-cli`; thread a resolved `cpm` through the OpenRTB and APS bid builders; wire docs/CI/Docker/ignore files. + +**Tech Stack:** Rust (edition 2021, workspace), edgezero framework, `spin-sdk` 6, `clap` 4, `validator`, `anyhow`, VitePress docs, GitHub Actions. + +**Reference spec:** `docs/superpowers/specs/2026-06-11-edgezero-extensible-cli-adaptation-design.md` + +**Branch:** `feature/edgezero-extensible-cli` (already created off `main`). + +**Local iteration tip:** building against the unmerged `feature/extensible-cli` pulls git deps over the network. For fast local iteration you may symlink `.cargo/config.toml.local` (which path-patches `../edgezero`) — but **do not commit** it, and ensure the sibling `../edgezero` checkout is on `feature/extensible-cli`. CI uses the git pin with `--locked`. + +--- + +## File Structure + +| File | Responsibility | Action | +| --- | --- | --- | +| `docs/.prettierignore`, `docs/.vitepress/config.*` | Keep internal specs/plans out of the docs format gate + published site | Modify | +| `Cargo.toml` | Workspace dep pins (`feature/extensible-cli`), `spin-sdk ~6.0`, add `clap`; add `mocktioneer-cli` member | Modify | +| `crates/mocktioneer-core/Cargo.toml` | Add `anyhow` dep | Modify | +| `crates/mocktioneer-adapter-{axum,cloudflare,fastly}/src/*` | Drop `include_str!(manifest)` arg | Modify | +| `crates/mocktioneer-adapter-spin/{src/lib.rs,src/main.rs,Cargo.toml,spin.toml,tests/contract.rs,runtime-config.toml}` | Spin SDK 6 / wasip2 migration + KV config backing | Modify/Create | +| `edgezero.toml` | `[stores.config]`, spin `target = wasip2`, spin commands `--runtime-config-file` | Modify | +| `crates/mocktioneer-core/src/config.rs` | `MocktioneerConfig` typed config | Create | +| `crates/mocktioneer-core/src/lib.rs` | `pub mod config;` | Modify | +| `crates/mocktioneer-core/src/auction.rs` | Thread `cpm: f64` into bid builders | Modify | +| `crates/mocktioneer-core/src/routes.rs` | `resolve_bid_cpm` + `cpm_from_lookup` helpers; wire both handlers | Modify | +| `mocktioneer.toml` | Typed config values (default `bid_cpm = 0.20`) | Create | +| `crates/mocktioneer-cli/{Cargo.toml,src/main.rs}` | Custom CLI mirroring edgezero `-cli` | Create | +| `Dockerfile` | Pre-copy `mocktioneer-cli` manifest before `cargo fetch` | Modify | +| `.gitignore` | Ignore `.edgezero/` | Modify | +| `docs/**`, `CLAUDE.md`, `.claude/...`, `README.md`, `tests/playwright/**` | wasip2 + pricing + CLI-story docs | Modify | +| `.github/workflows/test.yml` | Spin wasip2 matrix + config-validate gate | Modify | + +--- + +## Task 0: Keep `docs/superpowers/**` out of the docs format gate & site + +**Why first:** the format CI job runs `prettier --check .` inside `docs/`, which fails on the spec/plan markdown; VitePress also builds them into the published site. Do this before anything else so the spec and this plan don't break CI. + +**Files:** +- Modify: `docs/.prettierignore` +- Modify: `docs/.vitepress/config.mts` (or `.ts`/`.js` — whichever exists) +- Test: `cd docs && npm run format` + +- [ ] **Step 1: Add `superpowers/` to the docs Prettier ignore** + +Append to `docs/.prettierignore`: + +``` +superpowers/ +``` + +- [ ] **Step 2: Exclude internal specs/plans from the VitePress build** + +Find the config file: `ls docs/.vitepress/config.*`. In its `defineConfig({ ... })` object, add a top-level `srcExclude` key (merge if one already exists): + +```ts + srcExclude: ['**/superpowers/**'], +``` + +- [ ] **Step 3: Remove any committed built artifacts for the spec** + +Run: `git ls-files 'docs/.vitepress/dist/**superpowers**'` +If it lists files, remove them: `git rm -r --cached docs/.vitepress/dist 2>/dev/null; true` only if `dist/` is tracked. Otherwise confirm `docs/.vitepress/dist/` is gitignored: + +Run: `grep -n "dist" docs/.gitignore .gitignore 2>/dev/null` +Expected: `dist` is ignored. If not, add `docs/.vitepress/dist/` to `.gitignore`. + +- [ ] **Step 4: Verify the docs formatter passes** + +Run: `cd docs && npm ci >/dev/null 2>&1; npm run format` +Expected: PASS (no complaint about `superpowers/...`). + +- [ ] **Step 5: Commit** + +```bash +git add docs/.prettierignore docs/.vitepress .gitignore +git commit -m "docs: exclude superpowers specs/plans from prettier + vitepress" +``` + +--- + +## Task 1: Pin deps to `feature/extensible-cli` + workspace deps + +**Files:** +- Modify: `Cargo.toml` (`[workspace.dependencies]`) +- Modify: `crates/mocktioneer-core/Cargo.toml` + +- [ ] **Step 1: Repin the six edgezero deps and bump `spin-sdk`** + +In `Cargo.toml` `[workspace.dependencies]`, change every `branch = "main"` on the `edgezero-*` lines to `branch = "feature/extensible-cli"` (6 lines: adapter-axum, adapter-cloudflare, adapter-fastly, adapter-spin, cli, core). Then replace the `spin-sdk` line: + +```toml +spin-sdk = { version = "~6.0", default-features = false, features = ["http", "key-value", "variables"] } +``` + +- [ ] **Step 2: Add `clap` to workspace deps** + +Add to `[workspace.dependencies]` (alphabetical, near `base64`): + +```toml +clap = { version = "4", features = ["derive"] } +``` + +- [ ] **Step 3: Add `anyhow` to core** + +In `crates/mocktioneer-core/Cargo.toml`, add to `[dependencies]` (alphabetical, before `async-trait`): + +```toml +anyhow = { workspace = true } +``` + +- [ ] **Step 4: Resolve deps & regenerate `Cargo.lock`** + +Run: `cargo check -p mocktioneer-core 2>&1 | tail -30` +Expected: dependency **resolution succeeds** (git deps fetched, `Cargo.lock` rewritten) and `mocktioneer-core` compiles (core doesn't touch `run_app`). If you instead see resolution errors (e.g. branch not found), fix the branch name before continuing. + +- [ ] **Step 5: Commit** + +```bash +git add Cargo.toml crates/mocktioneer-core/Cargo.toml Cargo.lock +git commit -m "build: pin edgezero deps to feature/extensible-cli, bump spin-sdk to 6, add clap/anyhow" +``` + +--- + +## Task 2: Fix axum/cloudflare/fastly entrypoints (drop manifest arg) + +**Files:** +- Modify: `crates/mocktioneer-adapter-axum/src/main.rs:7` +- Modify: `crates/mocktioneer-adapter-cloudflare/src/lib.rs:11-17` +- Modify: `crates/mocktioneer-adapter-fastly/src/main.rs:16` + +- [ ] **Step 1: Axum — remove the manifest arg** + +In `crates/mocktioneer-adapter-axum/src/main.rs`, change: + +```rust + if let Err(err) = run_app::(include_str!("../../../edgezero.toml")) { +``` + +to: + +```rust + if let Err(err) = run_app::() { +``` + +- [ ] **Step 2: Cloudflare — remove the manifest arg** + +In `crates/mocktioneer-adapter-cloudflare/src/lib.rs`, change the call to: + +```rust + edgezero_adapter_cloudflare::run_app::(req, env, ctx).await +``` + +- [ ] **Step 3: Fastly — remove the manifest arg** + +In `crates/mocktioneer-adapter-fastly/src/main.rs`, change: + +```rust + edgezero_adapter_fastly::run_app::(include_str!("../../../edgezero.toml"), req) +``` + +to: + +```rust + edgezero_adapter_fastly::run_app::(req) +``` + +- [ ] **Step 4: Verify native + wasm targets type-check (Spin still broken — that's Task 3)** + +Run: `cargo check -p mocktioneer-adapter-axum` +Expected: PASS. + +Run: `cargo check -p mocktioneer-adapter-fastly --features fastly --target wasm32-wasip1` +Expected: PASS. + +Run: `cargo check -p mocktioneer-adapter-cloudflare --features cloudflare --target wasm32-unknown-unknown` +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add crates/mocktioneer-adapter-axum crates/mocktioneer-adapter-cloudflare crates/mocktioneer-adapter-fastly +git commit -m "feat: drop run_app manifest arg for axum/cloudflare/fastly (edgezero #269)" +``` + +--- + +## Task 3: Migrate the Spin adapter to spin-sdk 6 / wasm32-wasip2 + +**Files:** +- Modify: `crates/mocktioneer-adapter-spin/src/lib.rs` +- Modify: `crates/mocktioneer-adapter-spin/spin.toml` +- Create: `crates/mocktioneer-adapter-spin/runtime-config.toml` +- Modify: `crates/mocktioneer-adapter-spin/src/main.rs` +- Modify: `crates/mocktioneer-adapter-spin/tests/contract.rs` + +- [ ] **Step 1: Rewrite `src/lib.rs` for `#[http_service]` + `Request`** + +Replace the entire file with (mirrors edgezero's `templates/src/lib.rs.hbs`, dropping `no_main` and the manifest arg): + +```rust +#![cfg_attr( + target_arch = "wasm32", + allow( + unsafe_code, + reason = "spin's #[http_service] macro generates the unsafe wasm export" + ) +)] + +#[cfg(target_arch = "wasm32")] +use mocktioneer_core::MocktioneerApp; +#[cfg(target_arch = "wasm32")] +use spin_sdk::http::{IntoResponse, Request}; +#[cfg(target_arch = "wasm32")] +use spin_sdk::http_service; + +#[cfg(target_arch = "wasm32")] +#[http_service] +async fn handle(req: Request) -> anyhow::Result { + edgezero_adapter_spin::run_app::(req).await +} +``` + +- [ ] **Step 2: Point `spin.toml` at wasip2 + declare the config KV store** + +In `crates/mocktioneer-adapter-spin/spin.toml`, replace the `[component.mocktioneer]` and build blocks with: + +```toml +[component.mocktioneer] +source = "../../target/wasm32-wasip2/release/mocktioneer_adapter_spin.wasm" +allowed_outbound_hosts = ["https://*:*"] +key_value_stores = ["mocktioneer_config"] + +[component.mocktioneer.build] +command = "cargo build --target wasm32-wasip2 --release" +watch = ["src/**/*.rs", "Cargo.toml"] +``` + +- [ ] **Step 3: Create `runtime-config.toml`** + +Create `crates/mocktioneer-adapter-spin/runtime-config.toml`: + +```toml +# Spin runtime config: declares the KV labels the component may open. +# The `mocktioneer_config` label backs the EdgeZero `[stores.config]` +# store; the default SQLite backend persists to `.spin/sqlite_key_value.db`. +[key_value_store.mocktioneer_config] +type = "spin" +``` + +- [ ] **Step 4: Update the host-side stub message in `src/main.rs`** + +Replace both `wasm32-wasip1` mentions with `wasm32-wasip2`: + +```rust +#[expect( + clippy::print_stderr, + reason = "host-side stub that exists solely to remind the operator to target wasm32-wasip2" +)] +fn main() { + eprintln!("Run `spin up` or target wasm32-wasip2 to execute mocktioneer-adapter-spin."); +} +``` + +- [ ] **Step 5: Update the contract-test comment to wasip2** + +In `crates/mocktioneer-adapter-spin/tests/contract.rs`, change the doc-comment line `end-to-end under \`wasm32-wasip1\` via the \`wasmtime\` runner` to `wasm32-wasip2`. The `#![cfg(all(feature = "spin", target_arch = "wasm32"))]` gate is unchanged (covers wasip2). + +- [ ] **Step 6: Verify the Spin wasm build + native workspace check** + +Run: `rustup target add wasm32-wasip2 >/dev/null 2>&1; cargo build -p mocktioneer-adapter-spin --features spin --target wasm32-wasip2` +Expected: PASS — produces `target/wasm32-wasip2/release/...` (or debug). If the SDK-6 macro needs a tweak, fix per the compiler. + +Run: `cargo check --workspace` +Expected: PASS (native). + +- [ ] **Step 7: Commit** + +```bash +git add crates/mocktioneer-adapter-spin +git commit -m "feat: migrate spin adapter to spin-sdk 6 / wasm32-wasip2 (edgezero #269)" +``` + +--- + +## Task 4: Manifest — declare config store, spin wasip2 target, runtime-config + +**Files:** +- Modify: `edgezero.toml` + +- [ ] **Step 1: Declare the config store** + +Add a top-level `[stores.config]` section to `edgezero.toml` (place it after `[app]`, before the triggers): + +```toml +[stores.config] +ids = ["mocktioneer_config"] +``` + +- [ ] **Step 2: Spin build target → wasip2** + +In `edgezero.toml`, in `[adapters.spin.build]`, change `target = "wasm32-wasip1"` to: + +```toml +target = "wasm32-wasip2" +``` + +- [ ] **Step 3: Spin serve/deploy commands → pass `--runtime-config-file`** + +In `[adapters.spin.commands]`, change `serve` and `deploy` to reference the runtime config: + +```toml +build = "spin build --from crates/mocktioneer-adapter-spin/spin.toml" +deploy = "spin deploy --from crates/mocktioneer-adapter-spin/spin.toml --runtime-config-file crates/mocktioneer-adapter-spin/runtime-config.toml" +serve = "spin up --from crates/mocktioneer-adapter-spin/spin.toml --runtime-config-file crates/mocktioneer-adapter-spin/runtime-config.toml" +``` + +- [ ] **Step 4: Verify the manifest still compiles (validated at compile time by `app!`)** + +Run: `cargo check -p mocktioneer-core` +Expected: PASS. If `manifest.validate()` rejects a table, fix per its error message. + +- [ ] **Step 5: Commit** + +```bash +git add edgezero.toml +git commit -m "feat: declare [stores.config] + spin wasip2/runtime-config in manifest" +``` + +--- + +## Task 5: `MocktioneerConfig` typed config struct + +**Files:** +- Create: `crates/mocktioneer-core/src/config.rs` +- Modify: `crates/mocktioneer-core/src/lib.rs` + +- [ ] **Step 1: Write the failing validation tests** + +Create `crates/mocktioneer-core/src/config.rs`: + +```rust +//! Typed application config, loaded from `mocktioneer.toml`. The TOML file +//! maps 1:1 onto this struct (no `[config]` wrapper). v1 carries a single +//! `bid_cpm` field; `config validate --strict` enforces the rules below. + +use serde::{Deserialize, Serialize}; +use validator::{Validate, ValidationError}; + +#[derive(Debug, Deserialize, Serialize, Validate, edgezero_core::AppConfig)] +#[serde(deny_unknown_fields)] +pub struct MocktioneerConfig { + /// Fixed bid CPM in USD. Validated finite and strictly positive. + #[validate(custom(function = "validate_bid_cpm"))] + pub bid_cpm: f64, +} + +/// `validator` custom fns take a reference to the field; `range` does not +/// reject NaN / inf for floats, so validate explicitly. +fn validate_bid_cpm(value: &f64) -> Result<(), ValidationError> { + if value.is_finite() && *value > 0.0 { + Ok(()) + } else { + Err(ValidationError::new("bid_cpm_must_be_finite_positive")) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn accepts_positive_finite_cpm() { + let cfg = MocktioneerConfig { bid_cpm: 0.20 }; + assert!(cfg.validate().is_ok()); + } + + #[test] + fn rejects_zero_negative_and_non_finite() { + for bad in [0.0_f64, -1.0, f64::NAN, f64::INFINITY] { + let cfg = MocktioneerConfig { bid_cpm: bad }; + assert!(cfg.validate().is_err(), "expected {bad} to be rejected"); + } + } +} +``` + +- [ ] **Step 2: Wire the module** + +In `crates/mocktioneer-core/src/lib.rs`, add to the top module list (alphabetical, after `aps`): + +```rust +pub mod config; +``` + +- [ ] **Step 3: Run the tests (expect FAIL first if the derive isn't wired, then PASS)** + +Run: `cargo test -p mocktioneer-core config::tests` +Expected: PASS. (If the `edgezero_core::AppConfig` derive path is wrong, the compile error tells you — it is re-exported from `edgezero-core`, no `edgezero-macros` dep needed.) + +- [ ] **Step 4: Commit** + +```bash +git add crates/mocktioneer-core/src/config.rs crates/mocktioneer-core/src/lib.rs +git commit -m "feat: add MocktioneerConfig typed config (bid_cpm, validated)" +``` + +--- + +## Task 6: Thread `cpm` through the bid builders + runtime resolution + +**Files:** +- Modify: `crates/mocktioneer-core/src/auction.rs` (`build_openrtb_response`, `build_aps_response`, tests) +- Modify: `crates/mocktioneer-core/src/routes.rs` (`handle_openrtb_auction`, `handle_aps_bid`, new helpers, imports) + +- [ ] **Step 1: Write the failing pure-helper tests in `routes.rs`** + +In `crates/mocktioneer-core/src/routes.rs`, inside the existing `#[cfg(test)] mod tests { ... }` block, add: + +```rust + #[test] + fn cpm_from_lookup_falls_back_when_absent() { + assert_eq!( + cpm_from_lookup(None).unwrap().to_bits(), + crate::auction::FIXED_BID_CPM.to_bits() + ); + } + + #[test] + fn cpm_from_lookup_parses_valid_value() { + assert!((cpm_from_lookup(Some("0.35".to_owned())).unwrap() - 0.35).abs() < f64::EPSILON); + } + + #[test] + fn cpm_from_lookup_rejects_bad_values() { + for bad in ["-1", "0", "abc", "inf", "NaN", ""] { + assert!( + cpm_from_lookup(Some(bad.to_owned())).is_err(), + "expected {bad:?} to error" + ); + } + } +``` + +- [ ] **Step 2: Run to verify failure** + +Run: `cargo test -p mocktioneer-core routes::tests::cpm_from_lookup 2>&1 | tail -20` +Expected: FAIL — `cannot find function cpm_from_lookup`. + +- [ ] **Step 3: Add the resolution helpers + import** + +In `crates/mocktioneer-core/src/routes.rs`, extend the auction import to include `FIXED_BID_CPM`: + +```rust +use crate::auction::{ + build_aps_response, build_openrtb_response, is_standard_size, standard_sizes, FIXED_BID_CPM, +}; +``` + +Add these two functions near the handlers (module scope, not inside a fn): + +```rust +/// Interpret a config-store lookup for `bid_cpm`. Pure (no `ctx`) so it is +/// unit-testable. `None` (store absent / unseeded / key missing) → default; +/// a present-but-unparseable / non-finite / ≤0 value → error. +fn cpm_from_lookup(found: Option) -> Result { + match found { + None => Ok(FIXED_BID_CPM), + Some(raw) => raw + .parse::() + .ok() + .filter(|value| value.is_finite() && *value > 0.0) + .ok_or_else(|| { + EdgeError::internal(anyhow::anyhow!( + "config store `mocktioneer_config` has malformed bid_cpm: {raw:?}" + )) + }), + } +} + +/// Resolve the effective bid CPM from the bound default config store, falling +/// back to `FIXED_BID_CPM` when no store is bound. A backend read error +/// propagates via `From`. +async fn resolve_bid_cpm(ctx: &RequestContext) -> Result { + let Some(store) = ctx.config_store_default() else { + return Ok(FIXED_BID_CPM); + }; + cpm_from_lookup(store.get("bid_cpm").await.map_err(EdgeError::from)?) +} +``` + +- [ ] **Step 4: Run the pure-helper tests (expect PASS)** + +Run: `cargo test -p mocktioneer-core routes::tests::cpm_from_lookup` +Expected: PASS. + +- [ ] **Step 5: Add `cpm: f64` to the bid builders (write the new builder tests first)** + +In `crates/mocktioneer-core/src/auction.rs`, inside its `#[cfg(test)] mod tests`, add: + +```rust + #[test] + fn openrtb_uses_supplied_cpm() { + let req = OpenRTBRequest { + id: "rc".to_owned(), + imp: vec![OpenrtbImp { + id: "1".to_owned(), + banner: Some(Banner { + width: Some(300), + height: Some(250), + ..Default::default() + }), + ..Default::default() + }], + ..Default::default() + }; + let resp = build_openrtb_response(&req, "host.test", test_signature(), 0.35); + assert!((resp.seatbid[0].bid[0].price - 0.35).abs() < f64::EPSILON); + } + + #[test] + fn aps_uses_supplied_cpm() { + let req = ApsBidRequest { + pub_id: "test".to_owned(), + slots: vec![ApsSlot { + slot_id: "slot1".to_owned(), + sizes: vec![[300, 250]], + slot_name: None, + }], + page_url: None, + user_agent: None, + timeout: None, + }; + let resp = build_aps_response(&req, "mock.test", 0.35); + let slot = &resp.contextual.slots[0]; + let price = decode_aps_price(slot.amznbid.as_ref().unwrap()).unwrap(); + assert!((price - 0.35).abs() < f64::EPSILON); + } +``` + +- [ ] **Step 6: Change the builder signatures + bodies** + +In `build_openrtb_response`, add the param and use it: + +```rust +pub fn build_openrtb_response( + req: &OpenRTBRequest, + base_host: &str, + signature_status: SignatureStatus, + cpm: f64, +) -> OpenRTBResponse { +``` + +Replace the deprecation `log::warn!` argument `FIXED_BID_CPM` with `cpm`, and replace `let price = FIXED_BID_CPM;` (≈line 120) with `let price = cpm;`. + +In `build_aps_response`: + +```rust +pub fn build_aps_response(req: &ApsBidRequest, base_host: &str, cpm: f64) -> ApsBidResponse { +``` + +Replace `let price = FIXED_BID_CPM;` (≈line 259) with `let price = cpm;`. + +- [ ] **Step 7: Update existing auction.rs call sites in tests** + +The two existing tests that build responses now need the `cpm` arg. Update: +- `bid_id_is_hex_like_uuid` and `ext_bid_override_is_ignored`: `build_openrtb_response(&req, "host.test", test_signature(), FIXED_BID_CPM)`. +- `build_aps_response_price_encoding_is_base64`: `build_aps_response(&req, "mock.test", FIXED_BID_CPM)`. + +(They keep asserting against `FIXED_BID_CPM`, which is now the value they pass in.) + +- [ ] **Step 8: Wire the handlers** + +In `crates/mocktioneer-core/src/routes.rs`: + +`handle_openrtb_auction` — resolve cpm and pass it (it already binds `RequestContext(ctx)`): + +```rust + let cpm = resolve_bid_cpm(&ctx).await?; + log::info!("auction id={}, imps={}", req.id, req.imp.len()); + let resp = build_openrtb_response(&req, &host, signature_status, cpm); +``` + +`handle_aps_bid` — add the `RequestContext(ctx)` extractor and resolve cpm: + +```rust +#[action] +pub async fn handle_aps_bid( + RequestContext(ctx): RequestContext, + ForwardedHost(host): ForwardedHost, + ValidatedJson(req): ValidatedJson, +) -> Result { + log::info!( + "APS auction pubId={}, slots={}", + req.pub_id, + req.slots.len() + ); + + let cpm = resolve_bid_cpm(&ctx).await?; + let resp = build_aps_response(&req, &host, cpm); +``` + +- [ ] **Step 9: Run the full core test suite** + +Run: `cargo test -p mocktioneer-core` +Expected: PASS (new builder tests + updated existing tests + config tests). + +- [ ] **Step 10: Commit** + +```bash +git add crates/mocktioneer-core/src/auction.rs crates/mocktioneer-core/src/routes.rs +git commit -m "feat: resolve bid_cpm from config store at runtime (OpenRTB + APS), default FIXED_BID_CPM" +``` + +--- + +## Task 7: `mocktioneer.toml` typed config file + +**Files:** +- Create: `mocktioneer.toml` (repo root, next to `edgezero.toml`) + +- [ ] **Step 1: Create the config file** + +`config validate`/`push` resolve `.toml` next to the manifest from `[app].name = "mocktioneer"`. Create `mocktioneer.toml`: + +```toml +# Typed application config for Mocktioneer (maps 1:1 onto MocktioneerConfig). +# `bid_cpm` is the default; override per-environment via `config push` or the +# `MOCKTIONEER__BID_CPM` env overlay. Default keeps historical $0.20 behavior. +bid_cpm = 0.20 +``` + +- [ ] **Step 2: Commit (validation happens in Task 8 once the CLI exists)** + +```bash +git add mocktioneer.toml +git commit -m "feat: add mocktioneer.toml typed config (bid_cpm default 0.20)" +``` + +--- + +## Task 8: `mocktioneer-cli` crate + +**Files:** +- Create: `crates/mocktioneer-cli/Cargo.toml` +- Create: `crates/mocktioneer-cli/src/main.rs` +- Modify: `Cargo.toml` (`[workspace].members`) + +- [ ] **Step 1: Add the crate to the workspace members** + +In root `Cargo.toml`, add to `[workspace].members` (after the spin adapter line): + +```toml + "crates/mocktioneer-cli", +``` + +- [ ] **Step 2: Create `crates/mocktioneer-cli/Cargo.toml`** + +```toml +[package] +name = "mocktioneer-cli" +version = "0.1.0" +edition = "2021" +publish = false +license.workspace = true + +[dependencies] +mocktioneer-core = { workspace = true } +edgezero-cli = { workspace = true } +clap = { workspace = true } +log = { workspace = true } + +[lints] +workspace = true +``` + +- [ ] **Step 3: Create `crates/mocktioneer-cli/src/main.rs`** + +Instantiates edgezero's `-cli` template (name=`mocktioneer`, struct=`MocktioneerConfig`): + +```rust +//! Mocktioneer CLI — built on the `edgezero-cli` library. +//! +//! Reuses every built-in edgezero command and adds the **typed** `config` +//! arms parameterised over `MocktioneerConfig`, so `validator` rules run on +//! `config validate` / `config push`. + +use clap::{Parser, Subcommand}; +use edgezero_cli::args::{ + AuthArgs, BuildArgs, ConfigPushArgs, ConfigValidateArgs, DeployArgs, NewArgs, ProvisionArgs, + ServeArgs, +}; +use mocktioneer_core::config::MocktioneerConfig; + +#[derive(Parser, Debug)] +#[command(name = "mocktioneer-cli", about = "mocktioneer edge CLI")] +struct Args { + #[command(subcommand)] + cmd: Cmd, +} + +#[derive(Subcommand, Debug)] +enum Cmd { + /// Sign in / out / status against the adapter's native CLI. + Auth(AuthArgs), + /// Build the project for a target edge. + Build(BuildArgs), + /// Inspect or mutate the typed `mocktioneer.toml` app config. + #[command(subcommand)] + Config(MocktioneerConfigCmd), + /// Deploy to a target edge. + Deploy(DeployArgs), + /// Create a new EdgeZero app skeleton. + New(NewArgs), + /// Create the platform resources backing the declared store ids. + Provision(ProvisionArgs), + /// Run a local simulation (adapter-specific). + Serve(ServeArgs), +} + +/// Dispatches `validate`/`push` to the typed entry points over +/// `MocktioneerConfig`. +#[derive(Subcommand, Debug)] +enum MocktioneerConfigCmd { + /// Push `mocktioneer.toml` (flattened) to the adapter's config store. + Push(ConfigPushArgs), + /// Validate `edgezero.toml` + `mocktioneer.toml` against `MocktioneerConfig`. + Validate(ConfigValidateArgs), +} + +fn main() { + use std::process; + + edgezero_cli::init_cli_logger(); + let result = match Args::parse().cmd { + Cmd::Auth(args) => edgezero_cli::run_auth(&args), + Cmd::Build(args) => edgezero_cli::run_build(&args), + Cmd::Config(MocktioneerConfigCmd::Push(args)) => { + edgezero_cli::run_config_push_typed::(&args) + } + Cmd::Config(MocktioneerConfigCmd::Validate(args)) => { + edgezero_cli::run_config_validate_typed::(&args) + } + Cmd::Deploy(args) => edgezero_cli::run_deploy(&args), + Cmd::New(args) => edgezero_cli::run_new(&args), + Cmd::Provision(args) => edgezero_cli::run_provision(&args), + Cmd::Serve(args) => edgezero_cli::run_serve(&args), + }; + if let Err(err) = result { + log::error!("[mocktioneer] {err}"); + process::exit(1); + } +} +``` + +- [ ] **Step 4: Build the CLI** + +Run: `cargo build -p mocktioneer-cli` +Expected: PASS. If `run_auth`/`run_provision` names differ, check `edgezero-cli`'s `lib.rs` re-exports and adjust (the generated template uses exactly these names). + +- [ ] **Step 5: Validate the typed config end to end** + +Run: `cargo run -p mocktioneer-cli -- config validate --strict` +Expected: PASS (manifest + `mocktioneer.toml` validate against `MocktioneerConfig`). + +- [ ] **Step 6: Sanity-check rejection** + +Run: `printf 'bid_cpm = -1\n' > /tmp/bad-mocktioneer.toml && cargo run -p mocktioneer-cli -- config validate --strict --app-config /tmp/bad-mocktioneer.toml; echo "exit=$?"` +Expected: non-zero exit with a validation error about `bid_cpm`. + +- [ ] **Step 7: Commit** + +```bash +git add Cargo.toml Cargo.lock crates/mocktioneer-cli +git commit -m "feat: add mocktioneer-cli with typed config validate/push" +``` + +--- + +## Task 9: Dockerfile — pre-copy the new crate manifest + +**Files:** +- Modify: `Dockerfile` + +- [ ] **Step 1: Add the manifest COPY** + +In `Dockerfile`, after the existing per-crate manifest COPY lines (after the fastly line, before `COPY crates ./crates`), add: + +```dockerfile +COPY crates/mocktioneer-cli/Cargo.toml crates/mocktioneer-cli/Cargo.toml +``` + +- [ ] **Step 2: Verify the build (if Docker is available)** + +Run: `docker build -t mocktioneer:plan-check . 2>&1 | tail -20` +Expected: `cargo fetch --locked` resolves with the new member; build completes. (If Docker isn't available in this environment, note it and rely on CI.) + +- [ ] **Step 3: Commit** + +```bash +git add Dockerfile +git commit -m "build: copy mocktioneer-cli manifest before cargo fetch in Docker" +``` + +--- + +## Task 10: Ignore `.edgezero/` + +**Files:** +- Modify: `.gitignore` + +- [ ] **Step 1: Add the ignore entry** + +Add `.edgezero/` to `.gitignore` (next to the existing `.spin/` line): + +``` +.edgezero/ +``` + +- [ ] **Step 2: Verify the worktree no longer shows it** + +Run: `git status --porcelain | grep edgezero || echo clean` +Expected: `clean`. + +- [ ] **Step 3: Commit** + +```bash +git add .gitignore +git commit -m "chore: gitignore .edgezero/ (local config/kv state)" +``` + +--- + +## Task 11: Docs, agents, CI-command docs + +**Files:** +- Modify (Spin wasip1→wasip2): `CLAUDE.md`, `docs/guide/getting-started.md`, `docs/guide/configuration.md`, `.claude/agents/code-architect.md`, `.claude/agents/build-validator.md`, `.claude/agents/verify-app.md`, `.cargo/config.toml` (comment only) +- Modify (pricing default): `docs/guide/what-is-mocktioneer.md`, `docs/guide/architecture.md`, `docs/integrations/prebidjs.md`, `docs/integrations/prebid-server.md`, `docs/integrations/index.md`, `docs/api/openrtb-auction.md`, `docs/api/aps-bid.md`, `docs/api/index.md` +- Modify (CLI story): `README.md`, `docs/guide/adapters/index.md`, `tests/playwright/README.md`, `tests/playwright/playwright.config.ts` +- Modify (CI gate docs): `.claude/commands/check-ci.md`, `CLAUDE.md` + +- [ ] **Step 1: Spin target references → wasip2** + +In each file listed under "Spin wasip1→wasip2", change Spin-context `wasm32-wasip1` → `wasm32-wasip2`. **Do not** touch Fastly's `wasm32-wasip1` references. Verify scope first: + +Run: `grep -rn "wasip1" CLAUDE.md docs/guide/getting-started.md docs/guide/configuration.md .claude/agents` +For each hit, confirm whether it's Spin (change) or Fastly (leave). In `CLAUDE.md` update the layout comment `Spin / Fermyon bridge (wasm32-wasip1)` and the adapter targets table Spin row to wasip2. + +- [ ] **Step 2: Pricing docs — "$0.20 always/fixed" → "$0.20 default (configurable)"** + +In each pricing doc, reword the fixed-price claim. Example for `docs/api/openrtb-auction.md`: + +> Every bid is priced at a **default** CPM of **$0.20**, configurable via the `bid_cpm` key in `mocktioneer.toml` (pushed to the adapter's config store with `mocktioneer-cli config push`). + +Apply the equivalent one-line edit to each of the 8 files. Verify you caught them: + +Run: `grep -rn "0.20\|fixed price\|always" docs/guide/what-is-mocktioneer.md docs/guide/architecture.md docs/integrations docs/api` + +- [ ] **Step 3: CLI story — distinguish `edgezero-cli` vs `mocktioneer-cli`** + +- `README.md`, `docs/guide/adapters/index.md`: note that `config validate`/`config push` are typed and live in the in-repo `mocktioneer-cli` (`cargo run -p mocktioneer-cli -- …`); `serve`/`build`/`deploy` work from either the external `edgezero-cli` or `mocktioneer-cli`. Drop "optional, not vendored" framing for the config commands. +- `tests/playwright/README.md` and `tests/playwright/playwright.config.ts`: change the `webServer` launch command to `cargo run -p mocktioneer-cli -- serve --adapter cloudflare` (no external install). Confirm the exact current command first: + +Run: `grep -n "edgezero-cli\|webServer\|command" tests/playwright/playwright.config.ts` + +- [ ] **Step 4: Add the config-validate gate to local CI docs** + +In `.claude/commands/check-ci.md`, add a step 5: + +```markdown +5. `cargo run -p mocktioneer-cli -- config validate --strict` +``` + +In `CLAUDE.md` "CI Gates" list, add the same `config validate --strict` gate. + +- [ ] **Step 5: Verify docs formatter still passes** + +Run: `cd docs && npm run format` +Expected: PASS. + +- [ ] **Step 6: Commit** + +```bash +git add CLAUDE.md docs .claude README.md tests/playwright .cargo/config.toml +git commit -m "docs: spin wasip2, bid_cpm-default pricing, edgezero-cli vs mocktioneer-cli, config gate" +``` + +--- + +## Task 12: CI — Spin wasip2 matrix + config-validate gate + +**Files:** +- Modify: `.github/workflows/test.yml` + +- [ ] **Step 1: Inspect the current Spin matrix entry** + +Run: `grep -n "wasip1\|wasip2\|RUNNER\|target:\|spin" .github/workflows/test.yml` +Identify the Spin matrix row (currently `target: wasm32-wasip1`, `runner_env: CARGO_TARGET_WASM32_WASIP1_RUNNER`). + +- [ ] **Step 2: Switch the Spin matrix row to wasip2** + +For the Spin entry only (leave the Fastly entry on wasip1): change `target: wasm32-wasip1` → `target: wasm32-wasip2` and `runner_env: CARGO_TARGET_WASM32_WASIP1_RUNNER` → `CARGO_TARGET_WASM32_WASIP2_RUNNER`. Ensure the rustup target install step uses the matrix `target` (so it adds `wasm32-wasip2`). Keep the pinned Wasmtime install. + +- [ ] **Step 3: Add the typed-config gate job/step** + +Add a step (in the existing native test job, after `cargo test`): + +```yaml + - name: Validate typed app config + run: cargo run -p mocktioneer-cli -- config validate --strict + + - name: Seed + read-back local config (axum) + run: | + cargo run -p mocktioneer-cli -- config push --adapter axum + test -f .edgezero/local-config-mocktioneer_config.json +``` + +- [ ] **Step 4: Lint the workflow locally (if `act`/`actionlint` available) or eyeball YAML** + +Run: `python -c "import yaml,sys; yaml.safe_load(open('.github/workflows/test.yml')); print('yaml-ok')"` +Expected: `yaml-ok`. + +- [ ] **Step 5: Commit** + +```bash +git add .github/workflows/test.yml +git commit -m "ci: spin wasip2 matrix + typed config validate/seed gate" +``` + +--- + +## Task 13: Full verification pass + +**Files:** none (verification only) + +- [ ] **Step 1: Format + lint** + +Run: `cargo fmt --all -- --check` +Expected: PASS. + +Run: `cargo clippy --workspace --all-targets --all-features -- -D warnings` +Expected: PASS. + +- [ ] **Step 2: Tests** + +Run: `cargo test --workspace --all-targets` +Expected: PASS. + +- [ ] **Step 3: Feature/target matrix** + +Run: `cargo check --workspace --all-targets --features "fastly cloudflare"` +Expected: PASS. + +Run: `cargo build -p mocktioneer-adapter-fastly --features fastly --target wasm32-wasip1` +Expected: PASS. + +Run: `cargo build -p mocktioneer-adapter-spin --features spin --target wasm32-wasip2` +Expected: PASS. + +Run: `cargo build -p mocktioneer-adapter-cloudflare --features cloudflare --target wasm32-unknown-unknown` +Expected: PASS. + +- [ ] **Step 4: Typed config + seeded read-back** + +Run: `cargo run -p mocktioneer-cli -- config validate --strict` +Expected: PASS. + +Run: `printf 'bid_cpm = 0.35\n' > /tmp/seed.toml && cargo run -p mocktioneer-cli -- config push --adapter axum --app-config /tmp/seed.toml && cat .edgezero/local-config-mocktioneer_config.json` +Expected: the JSON contains `bid_cpm` = `0.35`. (Then remove it: `rm -f .edgezero/local-config-mocktioneer_config.json`.) + +- [ ] **Step 5: Docs gates** + +Run: `cd docs && npm run format && npm run lint && npm run build` +Expected: PASS (and the spec/plan are excluded from the build). + +- [ ] **Step 6: Semantic parity vs `main` (no store bound)** + +With no `.edgezero/local-config-*` present, hit `/openrtb2/auction` and `/e/dtb/bid` (via `cargo run -p mocktioneer-adapter-axum`) and confirm prices are `0.20` and the responses match `main` on semantic fields (price, sizes, `cur`, creative URLs, targeting) — IDs differ by design (`Uuid::now_v7()`). + +- [ ] **Step 7: Final commit (if any verification fixups were needed)** + +```bash +git add -A +git commit -m "chore: verification fixups for edgezero #269 adaptation" +``` + +--- + +## Self-Review (plan vs spec) + +- **§3.1 deps/anyhow/clap/spin-sdk** → Task 1. ✓ +- **§3.2 adapter entrypoints** → Tasks 2 (axum/cf/fastly) + 3 (spin). ✓ +- **§3.3 Spin wasip2 + runner** → Task 3 (adapter) + Task 12 (CI runner env). ✓ +- **§3.4 manifest `[stores.config]`** → Task 4. ✓ +- **§3.5 typed struct + runtime contract (incl. malformed-file vs read-error)** → Task 5 (struct) + Task 6 (`cpm_from_lookup`/`resolve_bid_cpm`; `None`→fallback covers absent/empty/missing-key; malformed value→error; read error→`From`). ✓ +- **§3.6 config lifecycle (per-adapter backing, spin runtime-config)** → Task 3 (spin runtime-config.toml) + Task 4 (commands) + Task 8 (push) + Task 12 (axum seed). Cloud/Spin provision documented, not CI-stood-up, per spec. ✓ +- **§3.7 mocktioneer-cli (metadata/lints) + Dockerfile** → Task 8 + Task 9. ✓ +- **§3.8 docs (wasip2, pricing default, CLI story, gitignore, check-ci, prettier/VitePress)** → Task 0 (prettier/VitePress) + Task 10 (gitignore) + Task 11 (rest). ✓ +- **§3.9 CI** → Task 12. ✓ +- **§5 verification (incl. explicit seeded fixture, docker, prettier)** → Task 13 (+ unit fixtures in Task 6 via `cpm_from_lookup`, seeded read-back in Tasks 8/12/13). ✓ + +No placeholders; types/functions (`MocktioneerConfig`, `cpm_from_lookup`, `resolve_bid_cpm`, builder signatures with `cpm: f64`) are consistent across tasks. + +--- + +## Execution Handoff + +Plan complete and saved to `docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md`. Two execution options: + +**1. Subagent-Driven (recommended)** — a fresh subagent per task, two-stage review between tasks, fast iteration. + +**2. Inline Execution** — execute tasks in this session with batch checkpoints (executing-plans). + +Which approach? From 63986d8e659bd82066115a12e5ba3520870b1f79 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Mon, 15 Jun 2026 22:15:00 -0700 Subject: [PATCH 04/45] docs: correct plan Task 0 (dist is gitignored, not tracked) --- .../2026-06-15-edgezero-extensible-cli-adaptation.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md b/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md index 01eebff..2c488ad 100644 --- a/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md +++ b/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md @@ -64,13 +64,12 @@ Find the config file: `ls docs/.vitepress/config.*`. In its `defineConfig({ ... srcExclude: ['**/superpowers/**'], ``` -- [ ] **Step 3: Remove any committed built artifacts for the spec** +- [ ] **Step 3: Confirm built artifacts are not tracked** -Run: `git ls-files 'docs/.vitepress/dist/**superpowers**'` -If it lists files, remove them: `git rm -r --cached docs/.vitepress/dist 2>/dev/null; true` only if `dist/` is tracked. Otherwise confirm `docs/.vitepress/dist/` is gitignored: +`docs/.vitepress/dist` is already gitignored and untracked (verified), so any locally-built `superpowers/*.html` is local-only — nothing to remove. Just confirm: -Run: `grep -n "dist" docs/.gitignore .gitignore 2>/dev/null` -Expected: `dist` is ignored. If not, add `docs/.vitepress/dist/` to `.gitignore`. +Run: `git check-ignore docs/.vitepress/dist && echo ignored` +Expected: `ignored`. (If it ever becomes tracked, `git rm -r --cached docs/.vitepress/dist`.) - [ ] **Step 4: Verify the docs formatter passes** From d5bb70aed61361d411aecf93a3e1295736736e78 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Mon, 15 Jun 2026 22:16:37 -0700 Subject: [PATCH 05/45] docs: apply prettier formatting to spec and plan --- ...6-15-edgezero-extensible-cli-adaptation.md | 64 ++++++++------ ...gezero-extensible-cli-adaptation-design.md | 84 +++++++++---------- 2 files changed, 81 insertions(+), 67 deletions(-) diff --git a/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md b/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md index 2c488ad..a9368d8 100644 --- a/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md +++ b/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md @@ -18,24 +18,24 @@ ## File Structure -| File | Responsibility | Action | -| --- | --- | --- | -| `docs/.prettierignore`, `docs/.vitepress/config.*` | Keep internal specs/plans out of the docs format gate + published site | Modify | -| `Cargo.toml` | Workspace dep pins (`feature/extensible-cli`), `spin-sdk ~6.0`, add `clap`; add `mocktioneer-cli` member | Modify | -| `crates/mocktioneer-core/Cargo.toml` | Add `anyhow` dep | Modify | -| `crates/mocktioneer-adapter-{axum,cloudflare,fastly}/src/*` | Drop `include_str!(manifest)` arg | Modify | -| `crates/mocktioneer-adapter-spin/{src/lib.rs,src/main.rs,Cargo.toml,spin.toml,tests/contract.rs,runtime-config.toml}` | Spin SDK 6 / wasip2 migration + KV config backing | Modify/Create | -| `edgezero.toml` | `[stores.config]`, spin `target = wasip2`, spin commands `--runtime-config-file` | Modify | -| `crates/mocktioneer-core/src/config.rs` | `MocktioneerConfig` typed config | Create | -| `crates/mocktioneer-core/src/lib.rs` | `pub mod config;` | Modify | -| `crates/mocktioneer-core/src/auction.rs` | Thread `cpm: f64` into bid builders | Modify | -| `crates/mocktioneer-core/src/routes.rs` | `resolve_bid_cpm` + `cpm_from_lookup` helpers; wire both handlers | Modify | -| `mocktioneer.toml` | Typed config values (default `bid_cpm = 0.20`) | Create | -| `crates/mocktioneer-cli/{Cargo.toml,src/main.rs}` | Custom CLI mirroring edgezero `-cli` | Create | -| `Dockerfile` | Pre-copy `mocktioneer-cli` manifest before `cargo fetch` | Modify | -| `.gitignore` | Ignore `.edgezero/` | Modify | -| `docs/**`, `CLAUDE.md`, `.claude/...`, `README.md`, `tests/playwright/**` | wasip2 + pricing + CLI-story docs | Modify | -| `.github/workflows/test.yml` | Spin wasip2 matrix + config-validate gate | Modify | +| File | Responsibility | Action | +| --------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | ------------- | +| `docs/.prettierignore`, `docs/.vitepress/config.*` | Keep internal specs/plans out of the docs format gate + published site | Modify | +| `Cargo.toml` | Workspace dep pins (`feature/extensible-cli`), `spin-sdk ~6.0`, add `clap`; add `mocktioneer-cli` member | Modify | +| `crates/mocktioneer-core/Cargo.toml` | Add `anyhow` dep | Modify | +| `crates/mocktioneer-adapter-{axum,cloudflare,fastly}/src/*` | Drop `include_str!(manifest)` arg | Modify | +| `crates/mocktioneer-adapter-spin/{src/lib.rs,src/main.rs,Cargo.toml,spin.toml,tests/contract.rs,runtime-config.toml}` | Spin SDK 6 / wasip2 migration + KV config backing | Modify/Create | +| `edgezero.toml` | `[stores.config]`, spin `target = wasip2`, spin commands `--runtime-config-file` | Modify | +| `crates/mocktioneer-core/src/config.rs` | `MocktioneerConfig` typed config | Create | +| `crates/mocktioneer-core/src/lib.rs` | `pub mod config;` | Modify | +| `crates/mocktioneer-core/src/auction.rs` | Thread `cpm: f64` into bid builders | Modify | +| `crates/mocktioneer-core/src/routes.rs` | `resolve_bid_cpm` + `cpm_from_lookup` helpers; wire both handlers | Modify | +| `mocktioneer.toml` | Typed config values (default `bid_cpm = 0.20`) | Create | +| `crates/mocktioneer-cli/{Cargo.toml,src/main.rs}` | Custom CLI mirroring edgezero `-cli` | Create | +| `Dockerfile` | Pre-copy `mocktioneer-cli` manifest before `cargo fetch` | Modify | +| `.gitignore` | Ignore `.edgezero/` | Modify | +| `docs/**`, `CLAUDE.md`, `.claude/...`, `README.md`, `tests/playwright/**` | wasip2 + pricing + CLI-story docs | Modify | +| `.github/workflows/test.yml` | Spin wasip2 matrix + config-validate gate | Modify | --- @@ -44,6 +44,7 @@ **Why first:** the format CI job runs `prettier --check .` inside `docs/`, which fails on the spec/plan markdown; VitePress also builds them into the published site. Do this before anything else so the spec and this plan don't break CI. **Files:** + - Modify: `docs/.prettierignore` - Modify: `docs/.vitepress/config.mts` (or `.ts`/`.js` — whichever exists) - Test: `cd docs && npm run format` @@ -88,6 +89,7 @@ git commit -m "docs: exclude superpowers specs/plans from prettier + vitepress" ## Task 1: Pin deps to `feature/extensible-cli` + workspace deps **Files:** + - Modify: `Cargo.toml` (`[workspace.dependencies]`) - Modify: `crates/mocktioneer-core/Cargo.toml` @@ -132,6 +134,7 @@ git commit -m "build: pin edgezero deps to feature/extensible-cli, bump spin-sdk ## Task 2: Fix axum/cloudflare/fastly entrypoints (drop manifest arg) **Files:** + - Modify: `crates/mocktioneer-adapter-axum/src/main.rs:7` - Modify: `crates/mocktioneer-adapter-cloudflare/src/lib.rs:11-17` - Modify: `crates/mocktioneer-adapter-fastly/src/main.rs:16` @@ -195,6 +198,7 @@ git commit -m "feat: drop run_app manifest arg for axum/cloudflare/fastly (edgez ## Task 3: Migrate the Spin adapter to spin-sdk 6 / wasm32-wasip2 **Files:** + - Modify: `crates/mocktioneer-adapter-spin/src/lib.rs` - Modify: `crates/mocktioneer-adapter-spin/spin.toml` - Create: `crates/mocktioneer-adapter-spin/runtime-config.toml` @@ -271,7 +275,7 @@ fn main() { - [ ] **Step 5: Update the contract-test comment to wasip2** -In `crates/mocktioneer-adapter-spin/tests/contract.rs`, change the doc-comment line `end-to-end under \`wasm32-wasip1\` via the \`wasmtime\` runner` to `wasm32-wasip2`. The `#![cfg(all(feature = "spin", target_arch = "wasm32"))]` gate is unchanged (covers wasip2). +In `crates/mocktioneer-adapter-spin/tests/contract.rs`, change the doc-comment line `end-to-end under \`wasm32-wasip1\` via the \`wasmtime\` runner`to`wasm32-wasip2`. The `#![cfg(all(feature = "spin", target_arch = "wasm32"))]` gate is unchanged (covers wasip2). - [ ] **Step 6: Verify the Spin wasm build + native workspace check** @@ -293,6 +297,7 @@ git commit -m "feat: migrate spin adapter to spin-sdk 6 / wasm32-wasip2 (edgezer ## Task 4: Manifest — declare config store, spin wasip2 target, runtime-config **Files:** + - Modify: `edgezero.toml` - [ ] **Step 1: Declare the config store** @@ -339,6 +344,7 @@ git commit -m "feat: declare [stores.config] + spin wasip2/runtime-config in man ## Task 5: `MocktioneerConfig` typed config struct **Files:** + - Create: `crates/mocktioneer-core/src/config.rs` - Modify: `crates/mocktioneer-core/src/lib.rs` @@ -417,6 +423,7 @@ git commit -m "feat: add MocktioneerConfig typed config (bid_cpm, validated)" ## Task 6: Thread `cpm` through the bid builders + runtime resolution **Files:** + - Modify: `crates/mocktioneer-core/src/auction.rs` (`build_openrtb_response`, `build_aps_response`, tests) - Modify: `crates/mocktioneer-core/src/routes.rs` (`handle_openrtb_auction`, `handle_aps_bid`, new helpers, imports) @@ -571,6 +578,7 @@ Replace `let price = FIXED_BID_CPM;` (≈line 259) with `let price = cpm;`. - [ ] **Step 7: Update existing auction.rs call sites in tests** The two existing tests that build responses now need the `cpm` arg. Update: + - `bid_id_is_hex_like_uuid` and `ext_bid_override_is_ignored`: `build_openrtb_response(&req, "host.test", test_signature(), FIXED_BID_CPM)`. - `build_aps_response_price_encoding_is_base64`: `build_aps_response(&req, "mock.test", FIXED_BID_CPM)`. @@ -624,6 +632,7 @@ git commit -m "feat: resolve bid_cpm from config store at runtime (OpenRTB + APS ## Task 7: `mocktioneer.toml` typed config file **Files:** + - Create: `mocktioneer.toml` (repo root, next to `edgezero.toml`) - [ ] **Step 1: Create the config file** @@ -649,6 +658,7 @@ git commit -m "feat: add mocktioneer.toml typed config (bid_cpm default 0.20)" ## Task 8: `mocktioneer-cli` crate **Files:** + - Create: `crates/mocktioneer-cli/Cargo.toml` - Create: `crates/mocktioneer-cli/src/main.rs` - Modify: `Cargo.toml` (`[workspace].members`) @@ -787,6 +797,7 @@ git commit -m "feat: add mocktioneer-cli with typed config validate/push" ## Task 9: Dockerfile — pre-copy the new crate manifest **Files:** + - Modify: `Dockerfile` - [ ] **Step 1: Add the manifest COPY** @@ -814,6 +825,7 @@ git commit -m "build: copy mocktioneer-cli manifest before cargo fetch in Docker ## Task 10: Ignore `.edgezero/` **Files:** + - Modify: `.gitignore` - [ ] **Step 1: Add the ignore entry** @@ -841,6 +853,7 @@ git commit -m "chore: gitignore .edgezero/ (local config/kv state)" ## Task 11: Docs, agents, CI-command docs **Files:** + - Modify (Spin wasip1→wasip2): `CLAUDE.md`, `docs/guide/getting-started.md`, `docs/guide/configuration.md`, `.claude/agents/code-architect.md`, `.claude/agents/build-validator.md`, `.claude/agents/verify-app.md`, `.cargo/config.toml` (comment only) - Modify (pricing default): `docs/guide/what-is-mocktioneer.md`, `docs/guide/architecture.md`, `docs/integrations/prebidjs.md`, `docs/integrations/prebid-server.md`, `docs/integrations/index.md`, `docs/api/openrtb-auction.md`, `docs/api/aps-bid.md`, `docs/api/index.md` - Modify (CLI story): `README.md`, `docs/guide/adapters/index.md`, `tests/playwright/README.md`, `tests/playwright/playwright.config.ts` @@ -897,6 +910,7 @@ git commit -m "docs: spin wasip2, bid_cpm-default pricing, edgezero-cli vs mockt ## Task 12: CI — Spin wasip2 matrix + config-validate gate **Files:** + - Modify: `.github/workflows/test.yml` - [ ] **Step 1: Inspect the current Spin matrix entry** @@ -913,13 +927,13 @@ For the Spin entry only (leave the Fastly entry on wasip1): change `target: wasm Add a step (in the existing native test job, after `cargo test`): ```yaml - - name: Validate typed app config - run: cargo run -p mocktioneer-cli -- config validate --strict +- name: Validate typed app config + run: cargo run -p mocktioneer-cli -- config validate --strict - - name: Seed + read-back local config (axum) - run: | - cargo run -p mocktioneer-cli -- config push --adapter axum - test -f .edgezero/local-config-mocktioneer_config.json +- name: Seed + read-back local config (axum) + run: | + cargo run -p mocktioneer-cli -- config push --adapter axum + test -f .edgezero/local-config-mocktioneer_config.json ``` - [ ] **Step 4: Lint the workflow locally (if `act`/`actionlint` available) or eyeball YAML** diff --git a/docs/superpowers/specs/2026-06-11-edgezero-extensible-cli-adaptation-design.md b/docs/superpowers/specs/2026-06-11-edgezero-extensible-cli-adaptation-design.md index 3fc0ae1..e71e995 100644 --- a/docs/superpowers/specs/2026-06-11-edgezero-extensible-cli-adaptation-design.md +++ b/docs/superpowers/specs/2026-06-11-edgezero-extensible-cli-adaptation-design.md @@ -91,7 +91,7 @@ Parts that **do** reach this repo: - Six `edgezero-*` git deps: `branch = "main"` → `branch = "feature/extensible-cli"`. - `spin-sdk = "5.2"` → `{ version = "~6.0", default-features = false, - features = ["http", "key-value", "variables"] }`. +features = ["http", "key-value", "variables"] }`. - Add `clap = { version = "4", features = ["derive"] }` to `[workspace.dependencies]` (consumed as `clap = { workspace = true }`). - Add `anyhow = { workspace = true }` to **`crates/mocktioneer-core/Cargo.toml`** @@ -101,12 +101,12 @@ Parts that **do** reach this repo: ### 3.2 Adapter entrypoints — drop the manifest arg -| File | After | -| --- | --- | -| `mocktioneer-adapter-axum/src/main.rs` | `run_app::()` | -| `mocktioneer-adapter-cloudflare/src/lib.rs` | `run_app::(req, env, ctx)` | -| `mocktioneer-adapter-fastly/src/main.rs` | `run_app::(req)` | -| `mocktioneer-adapter-spin/src/lib.rs` | `#[http_service]` + `Request` + `run_app::(req)` | +| File | After | +| ------------------------------------------- | ---------------------------------------------------------------- | +| `mocktioneer-adapter-axum/src/main.rs` | `run_app::()` | +| `mocktioneer-adapter-cloudflare/src/lib.rs` | `run_app::(req, env, ctx)` | +| `mocktioneer-adapter-fastly/src/main.rs` | `run_app::(req)` | +| `mocktioneer-adapter-spin/src/lib.rs` | `#[http_service]` + `Request` + `run_app::(req)` | Spin `lib.rs` swaps `expect(unsafe_code, …)` → `allow(unsafe_code, reason = "spin's #[http_service] macro generates the unsafe wasm export")`; final @@ -221,20 +221,20 @@ async fn resolve_bid_cpm(ctx: &RequestContext) -> Result { (replacing direct `FIXED_BID_CPM` reads); `FIXED_BID_CPM` stays as the fallback constant and the value existing tests pass explicitly. -**Contract summary** (note the malformed-*file* vs malformed-*value* +**Contract summary** (note the malformed-_file_ vs malformed-_value_ distinction, which is easy to conflate): -| Runtime situation | `config_store_default()` / `get` | Result | -| --- | --- | --- | -| No `[stores.config]`, or registry dropped | `None` | fallback `FIXED_BID_CPM` | -| Axum local file **absent** (empty store bound) | `Ok(None)` | fallback | -| File present, **missing** the `bid_cpm` key | `Ok(None)` | fallback | -| File **malformed JSON** | store **dropped at bind** → `config_store_default()` is `None` (Axum `build_config_registry` drops the id, and if it's the default id the whole registry is dropped — it does **not** surface as a `get` error) | fallback | -| Backend **read error** at `get` time (e.g. CF/Fastly KV hiccup) | `Err(ConfigStoreError)` | propagate via `EdgeError::from` (→ 400/503/500) | -| Value present but unparseable / non-finite / ≤ 0 | `Ok(Some(bad))` | **error** (`EdgeError::internal`) | - -So a malformed Axum config *file* degrades to the fallback (the bind-time drop -means handlers never see it), while a malformed *value* in an otherwise-valid +| Runtime situation | `config_store_default()` / `get` | Result | +| --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | +| No `[stores.config]`, or registry dropped | `None` | fallback `FIXED_BID_CPM` | +| Axum local file **absent** (empty store bound) | `Ok(None)` | fallback | +| File present, **missing** the `bid_cpm` key | `Ok(None)` | fallback | +| File **malformed JSON** | store **dropped at bind** → `config_store_default()` is `None` (Axum `build_config_registry` drops the id, and if it's the default id the whole registry is dropped — it does **not** surface as a `get` error) | fallback | +| Backend **read error** at `get` time (e.g. CF/Fastly KV hiccup) | `Err(ConfigStoreError)` | propagate via `EdgeError::from` (→ 400/503/500) | +| Value present but unparseable / non-finite / ≤ 0 | `Ok(Some(bad))` | **error** (`EdgeError::internal`) | + +So a malformed Axum config _file_ degrades to the fallback (the bind-time drop +means handlers never see it), while a malformed _value_ in an otherwise-valid file is a real misconfiguration and errors. `EdgeError::internal` takes `Into` (error.rs:63), hence `anyhow::anyhow!`, not a bare `String`. **Determinism preserved** on the fallback path (semantic outputs @@ -254,12 +254,12 @@ does not skip the id), which is exactly why §3.5's contract falls back on Per-adapter backing + seed step: -| Adapter | Backing for `mocktioneer_config` | Seed | -| --- | --- | --- | -| **Axum** (primary local/CI path) | `.edgezero/local-config-mocktioneer_config.json` | `mocktioneer-cli config push --adapter axum` writes it. | -| **Cloudflare** | KV namespace (config moved `[vars]`→KV in #269) | `provision --adapter cloudflare` (writes id to `wrangler.toml`) + `config push --adapter cloudflare`. | -| **Fastly** | config store + `[setup]`/`[local_server]` in `fastly.toml` | `provision --adapter fastly` + `config push --adapter fastly`. | -| **Spin** | **KV-backed** (`key_value_stores = ["mocktioneer_config"]` in `spin.toml` + a **`runtime-config.toml`** declaring the `[key_value_store.mocktioneer_config]` backend) — config is KV-backed in #269; `[variables]` is secrets-only now (`edgezero-adapter-spin/src/config_store.rs`). | `provision --adapter spin` + `config push --adapter spin`. | +| Adapter | Backing for `mocktioneer_config` | Seed | +| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | +| **Axum** (primary local/CI path) | `.edgezero/local-config-mocktioneer_config.json` | `mocktioneer-cli config push --adapter axum` writes it. | +| **Cloudflare** | KV namespace (config moved `[vars]`→KV in #269) | `provision --adapter cloudflare` (writes id to `wrangler.toml`) + `config push --adapter cloudflare`. | +| **Fastly** | config store + `[setup]`/`[local_server]` in `fastly.toml` | `provision --adapter fastly` + `config push --adapter fastly`. | +| **Spin** | **KV-backed** (`key_value_stores = ["mocktioneer_config"]` in `spin.toml` + a **`runtime-config.toml`** declaring the `[key_value_store.mocktioneer_config]` backend) — config is KV-backed in #269; `[variables]` is secrets-only now (`edgezero-adapter-spin/src/config_store.rs`). | `provision --adapter spin` + `config push --adapter spin`. | **Spin runtime-config gap (must add):** Mocktioneer's Spin adapter has only `spin.toml` and **no `runtime-config.toml`**, and `edgezero.toml`'s spin @@ -272,7 +272,7 @@ commands (`spin up/build/deploy --from …/spin.toml`) pass **no **Pragmatics:** the **axum** path is the one exercised locally and in CI. Cloud/Spin adapters get the manifest declaration + required native-backing -tables (incl. the Spin `runtime-config.toml`) so they *build and validate*; +tables (incl. the Spin `runtime-config.toml`) so they _build and validate_; push/provision are documented but live cloud/Spin stores are not stood up in CI. Handler tests wire a `ConfigRegistry` fixture directly (the app-demo `handlers.rs` test pattern) to cover the seeded-value, empty-store (fallback), @@ -307,7 +307,7 @@ and malformed-value (error) branches without a live backend. `.cargo/config.toml` (comment), `.github/workflows/test.yml` (§3.9). **Leave Fastly wasip1 intact.** No `docs/guide/adapters/spin.md` exists. - **Pricing docs — "$0.20 always/fixed" → "$0.20 default (configurable via - `bid_cpm`)".** This change makes the fixed CPM a *default*, not an invariant, + `bid_cpm`)".** This change makes the fixed CPM a _default_, not an invariant, so the public claims must be reworded: `docs/guide/what-is-mocktioneer.md`, `docs/guide/architecture.md`, `docs/integrations/prebidjs.md`, `docs/integrations/prebid-server.md`, `docs/integrations/index.md`, @@ -322,9 +322,9 @@ and malformed-value (error) branches without a live backend. **`config validate`/`config push` are typed and live only in `mocktioneer-cli`**; `serve`/`build`/`deploy`/`auth`/`provision` work from either the external `edgezero-cli` or the vendored `mocktioneer-cli`. Update - the "optional, not vendored" framing — `mocktioneer-cli` *is* in-repo. + the "optional, not vendored" framing — `mocktioneer-cli` _is_ in-repo. Playwright's `webServer` should use `cargo run -p mocktioneer-cli -- serve - --adapter …` (no external install needed). +--adapter …` (no external install needed). - **`.gitignore`:** add `.edgezero/` (currently only `.spin/` is ignored; the worktree already has an untracked `.edgezero/`). - **`.claude/commands/check-ci.md`** and **`CLAUDE.md` "CI Gates"**: add the @@ -364,19 +364,19 @@ and malformed-value (error) branches without a live backend. ## 4. Risks & mitigations -| Risk | Mitigation | -| --- | --- | -| New parser rejects an existing `[adapters.*]` table | Compile-time `manifest.validate()` surfaces it; fix per upstream error. | -| Spin SDK 6 macro/type churn beyond template | Mirror edgezero's `edgezero-adapter-spin` verbatim; build wasip2. | -| Wasmtime can't run the wasip2 component | Wasmtime 45.0.0 supports it; set `CARGO_TARGET_WASM32_WASIP2_RUNNER`; match edgezero's contract-test config. | -| Fresh dev errors before any push | Resolved: empty/absent → fallback to `FIXED_BID_CPM` (§3.5). | -| Broken/malformed pushed *value* masked as $0.20 | Read errors propagate; malformed present value errors (§3.5). Note a malformed *file* degrades to fallback (bind-time drop), by design. | -| `bid_cpm` never exercised (store unseeded) | CI does a real `config push --adapter axum` + asserts the value flows; fixtures cover all branches (§5). | -| Spin KV config silently empty (no `runtime-config.toml`) | Add `runtime-config.toml` + `--runtime-config-file` to spin commands (§3.6). | -| New `mocktioneer-cli` breaks Docker dependency layer | Pre-copy its `Cargo.toml` before `cargo fetch` (§3.7); `docker build` smoke (§5). | -| Spec under `docs/` fails the format CI gate | Mandatory `docs/.prettierignore` + VitePress `srcExclude` (§3.8). | -| Pinning to an unmerged branch | Documented; re-pin to edgezero `main` post-merge. | -| `.cargo/config.toml.local` patch drift (`edgezero-macros`) | Already lists it; verify it patches cleanly. | +| Risk | Mitigation | +| ---------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | +| New parser rejects an existing `[adapters.*]` table | Compile-time `manifest.validate()` surfaces it; fix per upstream error. | +| Spin SDK 6 macro/type churn beyond template | Mirror edgezero's `edgezero-adapter-spin` verbatim; build wasip2. | +| Wasmtime can't run the wasip2 component | Wasmtime 45.0.0 supports it; set `CARGO_TARGET_WASM32_WASIP2_RUNNER`; match edgezero's contract-test config. | +| Fresh dev errors before any push | Resolved: empty/absent → fallback to `FIXED_BID_CPM` (§3.5). | +| Broken/malformed pushed _value_ masked as $0.20 | Read errors propagate; malformed present value errors (§3.5). Note a malformed _file_ degrades to fallback (bind-time drop), by design. | +| `bid_cpm` never exercised (store unseeded) | CI does a real `config push --adapter axum` + asserts the value flows; fixtures cover all branches (§5). | +| Spin KV config silently empty (no `runtime-config.toml`) | Add `runtime-config.toml` + `--runtime-config-file` to spin commands (§3.6). | +| New `mocktioneer-cli` breaks Docker dependency layer | Pre-copy its `Cargo.toml` before `cargo fetch` (§3.7); `docker build` smoke (§5). | +| Spec under `docs/` fails the format CI gate | Mandatory `docs/.prettierignore` + VitePress `srcExclude` (§3.8). | +| Pinning to an unmerged branch | Documented; re-pin to edgezero `main` post-merge. | +| `.cargo/config.toml.local` patch drift (`edgezero-macros`) | Already lists it; verify it patches cleanly. | ## 5. Verification From 3b1c5ca85b64028afa81e43af98475962f7633fd Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Mon, 15 Jun 2026 22:34:36 -0700 Subject: [PATCH 06/45] docs: address PR #110 review (registry-backed cpm test, non-default CI seed, dockerfile cache-hygiene, docs build-exclusion, broader CLI docs) --- ...6-15-edgezero-extensible-cli-adaptation.md | 165 +++++++++++++++--- ...gezero-extensible-cli-adaptation-design.md | 107 +++++++----- 2 files changed, 204 insertions(+), 68 deletions(-) diff --git a/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md b/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md index a9368d8..7ad73b2 100644 --- a/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md +++ b/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md @@ -65,23 +65,31 @@ Find the config file: `ls docs/.vitepress/config.*`. In its `defineConfig({ ... srcExclude: ['**/superpowers/**'], ``` -- [ ] **Step 3: Confirm built artifacts are not tracked** +- [ ] **Step 3: Ignore the VitePress build temp dir** -`docs/.vitepress/dist` is already gitignored and untracked (verified), so any locally-built `superpowers/*.html` is local-only — nothing to remove. Just confirm: +`docs/.gitignore` currently lists `node_modules`, `.vitepress/dist`, `.vitepress/cache` — but **not** `.vitepress/.temp`, which `npm run build` creates (and which also receives the rendered specs unless excluded). Add to `docs/.gitignore`: -Run: `git check-ignore docs/.vitepress/dist && echo ignored` -Expected: `ignored`. (If it ever becomes tracked, `git rm -r --cached docs/.vitepress/dist`.) +``` +.vitepress/.temp +``` + +`docs/.vitepress/dist` is already gitignored and untracked (verified) — nothing to remove. Confirm: `git check-ignore docs/.vitepress/dist && echo ignored` → `ignored`. + +- [ ] **Step 4: Verify the formatter AND that the build excludes specs/plans** + +Run: `cd docs && npm ci >/dev/null 2>&1 && npm run format && npm run build` +Expected: both PASS. -- [ ] **Step 4: Verify the docs formatter passes** +Then assert no spec/plan leaked into the build output (this is the check the prior plan revision was missing — `srcExclude` must actually drop them): -Run: `cd docs && npm ci >/dev/null 2>&1; npm run format` -Expected: PASS (no complaint about `superpowers/...`). +Run: `find docs/.vitepress/dist docs/.vitepress/.temp -path '*superpowers*' -print -quit` +Expected: **no output**. If anything prints, `srcExclude` is mis-scoped — fix the glob (e.g. `'**/superpowers/**'` relative to the VitePress `srcDir`) and rebuild. - [ ] **Step 5: Commit** ```bash -git add docs/.prettierignore docs/.vitepress .gitignore -git commit -m "docs: exclude superpowers specs/plans from prettier + vitepress" +git add docs/.prettierignore docs/.vitepress docs/.gitignore .gitignore +git commit -m "docs: exclude superpowers specs/plans from prettier + vitepress build" ``` --- @@ -279,8 +287,15 @@ In `crates/mocktioneer-adapter-spin/tests/contract.rs`, change the doc-comment l - [ ] **Step 6: Verify the Spin wasm build + native workspace check** -Run: `rustup target add wasm32-wasip2 >/dev/null 2>&1; cargo build -p mocktioneer-adapter-spin --features spin --target wasm32-wasip2` -Expected: PASS — produces `target/wasm32-wasip2/release/...` (or debug). If the SDK-6 macro needs a tweak, fix per the compiler. +Build `--release` to match the `spin.toml` `source` path (`target/wasm32-wasip2/release/...`): + +Run: `rustup target add wasm32-wasip2 >/dev/null 2>&1; cargo build --release -p mocktioneer-adapter-spin --features spin --target wasm32-wasip2` +Expected: PASS — produces `target/wasm32-wasip2/release/mocktioneer_adapter_spin.wasm`. If the SDK-6 macro needs a tweak, fix per the compiler. + +If the `spin` CLI is installed, also prove the manifest's source path resolves: + +Run: `command -v spin >/dev/null && spin build --from crates/mocktioneer-adapter-spin/spin.toml || echo "spin CLI not installed — skipping"` +Expected: PASS or the skip note. Run: `cargo check --workspace` Expected: PASS (native). @@ -508,6 +523,80 @@ async fn resolve_bid_cpm(ctx: &RequestContext) -> Result { Run: `cargo test -p mocktioneer-core routes::tests::cpm_from_lookup` Expected: PASS. +- [ ] **Step 4b: Add registry-backed `resolve_bid_cpm` tests (spec §5 preferred fixture)** + +Exercises the real `RequestContext` → `ConfigRegistry` → `config_store_default()` → `store.get` path with an in-memory store (the app-demo `config_flow.rs` pattern; all types are public, no `test-utils` feature). Add to the `#[cfg(test)] mod tests` block in `crates/mocktioneer-core/src/routes.rs`: + +```rust + // In-memory ConfigStore for tests (mirrors app-demo's MapConfigStore). + struct MapConfigStore(std::collections::HashMap); + + #[async_trait] + impl edgezero_core::config_store::ConfigStore for MapConfigStore { + async fn get( + &self, + key: &str, + ) -> Result, edgezero_core::config_store::ConfigStoreError> { + Ok(self.0.get(key).cloned()) + } + } + + fn ctx_with_config(pairs: &[(&str, &str)]) -> RequestContext { + use edgezero_core::config_store::ConfigStoreHandle; + use edgezero_core::store_registry::{ConfigRegistry, StoreRegistry}; + use std::collections::{BTreeMap, HashMap}; + use std::sync::Arc; + + let map: HashMap = pairs + .iter() + .map(|(k, v)| ((*k).to_owned(), (*v).to_owned())) + .collect(); + let handle = ConfigStoreHandle::new(Arc::new(MapConfigStore(map))); + let by_id: BTreeMap = + [("mocktioneer_config".to_owned(), handle)].into_iter().collect(); + let registry: ConfigRegistry = + StoreRegistry::new(by_id, "mocktioneer_config".to_owned()); + + let mut request = request_builder() + .method(Method::GET) + .uri("/") + .body(Body::empty()) + .expect("request"); + request.extensions_mut().insert(registry); + RequestContext::new(request, PathParams::new(std::collections::HashMap::new())) + } + + #[test] + fn resolve_bid_cpm_reads_seeded_store() { + let ctx = ctx_with_config(&[("bid_cpm", "0.35")]); + let cpm = futures::executor::block_on(resolve_bid_cpm(&ctx)).unwrap(); + assert!((cpm - 0.35).abs() < f64::EPSILON); + } + + #[test] + fn resolve_bid_cpm_falls_back_without_registry() { + let request = request_builder() + .method(Method::GET) + .uri("/") + .body(Body::empty()) + .expect("request"); + let ctx = RequestContext::new(request, PathParams::new(std::collections::HashMap::new())); + let cpm = futures::executor::block_on(resolve_bid_cpm(&ctx)).unwrap(); + assert_eq!(cpm.to_bits(), FIXED_BID_CPM.to_bits()); + } + + #[test] + fn resolve_bid_cpm_errors_on_malformed_value() { + let ctx = ctx_with_config(&[("bid_cpm", "-1")]); + assert!(futures::executor::block_on(resolve_bid_cpm(&ctx)).is_err()); + } +``` + +Note: `async_trait`, `request_builder`, `Method`, `Body`, `PathParams` are already imported in the test module (confirmed). `futures` is a dev-dependency of `mocktioneer-core`. + +Run: `cargo test -p mocktioneer-core routes::tests::resolve_bid_cpm` +Expected: PASS (after Step 3's helpers exist). + - [ ] **Step 5: Add `cpm: f64` to the bid builders (write the new builder tests first)** In `crates/mocktioneer-core/src/auction.rs`, inside its `#[cfg(test)] mod tests`, add: @@ -575,14 +664,18 @@ pub fn build_aps_response(req: &ApsBidRequest, base_host: &str, cpm: f64) -> Aps Replace `let price = FIXED_BID_CPM;` (≈line 259) with `let price = cpm;`. -- [ ] **Step 7: Update existing auction.rs call sites in tests** +- [ ] **Step 7: Update ALL auction.rs builder call sites in tests** + +There are **many** test call sites, not two. Enumerate them: + +Run: `grep -rn "build_openrtb_response\|build_aps_response" crates/mocktioneer-core/src/auction.rs | grep -v "pub fn "` +Expected: ~9 test call sites (e.g. lines ~344, 368, 389, 409, 436, 455, 488, 510, 563). -The two existing tests that build responses now need the `cpm` arg. Update: +Append `, FIXED_BID_CPM` to every `build_openrtb_response(&req, "host.test", test_signature())` and every `build_aps_response(&req, "mock.test")` call in `auction.rs` tests. They keep asserting against `FIXED_BID_CPM`, which is now the value they pass in. -- `bid_id_is_hex_like_uuid` and `ext_bid_override_is_ignored`: `build_openrtb_response(&req, "host.test", test_signature(), FIXED_BID_CPM)`. -- `build_aps_response_price_encoding_is_base64`: `build_aps_response(&req, "mock.test", FIXED_BID_CPM)`. +**Do NOT touch `crates/mocktioneer-core/src/mediation.rs:183/187`** — that is a _separate, private_ `build_openrtb_response(request.id, request.imp, winning_bids, base_host)` for the mediation path. It is fed by request bids, never `FIXED_BID_CPM`, and is out of scope for `cpm`. -(They keep asserting against `FIXED_BID_CPM`, which is now the value they pass in.) +Run after editing: `cargo build -p mocktioneer-core 2>&1 | grep -c "this function takes" || echo "no arity errors"` to confirm no missed call sites. - [ ] **Step 8: Wire the handlers** @@ -794,24 +887,27 @@ git commit -m "feat: add mocktioneer-cli with typed config validate/push" --- -## Task 9: Dockerfile — pre-copy the new crate manifest +## Task 9: Dockerfile — keep the dependency-cache layer correct + +**Why (accuracy note):** the build already does `COPY crates ./crates` **before** `cargo fetch --locked` (Dockerfile:21/24), so the workspace fetch resolves regardless of the per-crate manifest pre-copies (lines 16-19). Those pre-copies exist only to create a **cache layer** (manifests change rarely → fetch is cached across source edits). That layer is currently already incomplete — it omits the existing **spin** member. So this task is **cache hygiene**, not a correctness fix: bring the pre-copy list in line with the actual member set. **Files:** - Modify: `Dockerfile` -- [ ] **Step 1: Add the manifest COPY** +- [ ] **Step 1: Add the missing member manifests to the cache layer** -In `Dockerfile`, after the existing per-crate manifest COPY lines (after the fastly line, before `COPY crates ./crates`), add: +In `Dockerfile`, after the existing per-crate manifest COPY lines (after the fastly line, before `COPY crates ./crates`), add the two members currently missing from the pre-copy list: ```dockerfile +COPY crates/mocktioneer-adapter-spin/Cargo.toml crates/mocktioneer-adapter-spin/Cargo.toml COPY crates/mocktioneer-cli/Cargo.toml crates/mocktioneer-cli/Cargo.toml ``` - [ ] **Step 2: Verify the build (if Docker is available)** Run: `docker build -t mocktioneer:plan-check . 2>&1 | tail -20` -Expected: `cargo fetch --locked` resolves with the new member; build completes. (If Docker isn't available in this environment, note it and rely on CI.) +Expected: `cargo fetch --locked` resolves and the build completes. (If Docker isn't available, note it and rely on CI; the build is correct either way because `COPY crates` precedes `cargo fetch`.) - [ ] **Step 3: Commit** @@ -856,7 +952,7 @@ git commit -m "chore: gitignore .edgezero/ (local config/kv state)" - Modify (Spin wasip1→wasip2): `CLAUDE.md`, `docs/guide/getting-started.md`, `docs/guide/configuration.md`, `.claude/agents/code-architect.md`, `.claude/agents/build-validator.md`, `.claude/agents/verify-app.md`, `.cargo/config.toml` (comment only) - Modify (pricing default): `docs/guide/what-is-mocktioneer.md`, `docs/guide/architecture.md`, `docs/integrations/prebidjs.md`, `docs/integrations/prebid-server.md`, `docs/integrations/index.md`, `docs/api/openrtb-auction.md`, `docs/api/aps-bid.md`, `docs/api/index.md` -- Modify (CLI story): `README.md`, `docs/guide/adapters/index.md`, `tests/playwright/README.md`, `tests/playwright/playwright.config.ts` +- Modify (CLI story): `README.md`, `docs/guide/adapters/index.md`, `docs/guide/adapters/axum.md`, `docs/guide/adapters/cloudflare.md`, `docs/guide/adapters/fastly.md`, `docs/guide/getting-started.md`, `tests/playwright/README.md`, `tests/playwright/playwright.config.ts` - Modify (CI gate docs): `.claude/commands/check-ci.md`, `CLAUDE.md` - [ ] **Step 1: Spin target references → wasip2** @@ -878,10 +974,14 @@ Run: `grep -rn "0.20\|fixed price\|always" docs/guide/what-is-mocktioneer.md doc - [ ] **Step 3: CLI story — distinguish `edgezero-cli` vs `mocktioneer-cli`** -- `README.md`, `docs/guide/adapters/index.md`: note that `config validate`/`config push` are typed and live in the in-repo `mocktioneer-cli` (`cargo run -p mocktioneer-cli -- …`); `serve`/`build`/`deploy` work from either the external `edgezero-cli` or `mocktioneer-cli`. Drop "optional, not vendored" framing for the config commands. +- `README.md`, `docs/guide/adapters/index.md`, `docs/guide/getting-started.md`: note that `config validate`/`config push` are typed and live in the in-repo `mocktioneer-cli` (`cargo run -p mocktioneer-cli -- …`); `serve`/`build`/`deploy` work from either the external `edgezero-cli` or `mocktioneer-cli`. Drop "optional, not vendored" framing for the config commands. +- **Per-adapter pages** — `docs/guide/adapters/axum.md` (≈L22), `docs/guide/adapters/cloudflare.md` (≈L47), `docs/guide/adapters/fastly.md` (≈L36) each show `edgezero-cli serve/build/deploy` examples. Keep `edgezero-cli` as valid but add a one-line "or, in-repo: `cargo run -p mocktioneer-cli -- `" alongside, so the vendored CLI is mentioned consistently. Find them first: + + Run: `grep -rn "edgezero-cli" docs/guide/adapters/` + - `tests/playwright/README.md` and `tests/playwright/playwright.config.ts`: change the `webServer` launch command to `cargo run -p mocktioneer-cli -- serve --adapter cloudflare` (no external install). Confirm the exact current command first: -Run: `grep -n "edgezero-cli\|webServer\|command" tests/playwright/playwright.config.ts` + Run: `grep -n "edgezero-cli\|webServer\|command" tests/playwright/playwright.config.ts` - [ ] **Step 4: Add the config-validate gate to local CI docs** @@ -930,12 +1030,16 @@ Add a step (in the existing native test job, after `cargo test`): - name: Validate typed app config run: cargo run -p mocktioneer-cli -- config validate --strict -- name: Seed + read-back local config (axum) +- name: Seed a NON-default cpm and assert it round-trips (axum) run: | - cargo run -p mocktioneer-cli -- config push --adapter axum - test -f .edgezero/local-config-mocktioneer_config.json + printf 'bid_cpm = 0.35\n' > /tmp/seed.toml + cargo run -p mocktioneer-cli -- config push --adapter axum --app-config /tmp/seed.toml + test "$(jq -r '.bid_cpm' .edgezero/local-config-mocktioneer_config.json)" = "0.35" + rm -f .edgezero/local-config-mocktioneer_config.json ``` +A **bare** `config push --adapter axum` would silently seed the root `mocktioneer.toml` default (`0.20`) and a `test -f` only proves a file exists — it would pass even if `bid_cpm` were never wired. Seeding `0.35` via `--app-config` and asserting the JSON value with `jq` proves push writes the _configured_ value. The handler → response half (a seeded store yielding `0.35`) is proven deterministically by the registry-backed `resolve_bid_cpm` test (Task 6, Step 4b), so no flaky serve+curl is needed here. (`jq` is preinstalled on GitHub `ubuntu-latest`.) + - [ ] **Step 4: Lint the workflow locally (if `act`/`actionlint` available) or eyeball YAML** Run: `python -c "import yaml,sys; yaml.safe_load(open('.github/workflows/test.yml')); print('yaml-ok')"` @@ -989,10 +1093,13 @@ Expected: PASS. Run: `printf 'bid_cpm = 0.35\n' > /tmp/seed.toml && cargo run -p mocktioneer-cli -- config push --adapter axum --app-config /tmp/seed.toml && cat .edgezero/local-config-mocktioneer_config.json` Expected: the JSON contains `bid_cpm` = `0.35`. (Then remove it: `rm -f .edgezero/local-config-mocktioneer_config.json`.) -- [ ] **Step 5: Docs gates** +- [ ] **Step 5: Docs gates (incl. spec/plan exclusion assertion)** Run: `cd docs && npm run format && npm run lint && npm run build` -Expected: PASS (and the spec/plan are excluded from the build). +Expected: PASS. + +Run: `find docs/.vitepress/dist docs/.vitepress/.temp -path '*superpowers*' -print -quit` +Expected: **no output** (specs/plans excluded from the published build). - [ ] **Step 6: Semantic parity vs `main` (no store bound)** @@ -1018,7 +1125,7 @@ git commit -m "chore: verification fixups for edgezero #269 adaptation" - **§3.7 mocktioneer-cli (metadata/lints) + Dockerfile** → Task 8 + Task 9. ✓ - **§3.8 docs (wasip2, pricing default, CLI story, gitignore, check-ci, prettier/VitePress)** → Task 0 (prettier/VitePress) + Task 10 (gitignore) + Task 11 (rest). ✓ - **§3.9 CI** → Task 12. ✓ -- **§5 verification (incl. explicit seeded fixture, docker, prettier)** → Task 13 (+ unit fixtures in Task 6 via `cpm_from_lookup`, seeded read-back in Tasks 8/12/13). ✓ +- **§5 verification (incl. explicit seeded fixture, docker, prettier)** → Task 13 (+ pure `cpm_from_lookup` tests **and** registry-backed `resolve_bid_cpm` tests in Task 6 Step 4b covering seeded-0.35 / fallback / malformed-error; non-default `0.35` seed + `jq` assert in Tasks 12/13; docs build-exclusion `find` check in Tasks 0/13). ✓ No placeholders; types/functions (`MocktioneerConfig`, `cpm_from_lookup`, `resolve_bid_cpm`, builder signatures with `cpm: f64`) are consistent across tasks. diff --git a/docs/superpowers/specs/2026-06-11-edgezero-extensible-cli-adaptation-design.md b/docs/superpowers/specs/2026-06-11-edgezero-extensible-cli-adaptation-design.md index e71e995..7bc6179 100644 --- a/docs/superpowers/specs/2026-06-11-edgezero-extensible-cli-adaptation-design.md +++ b/docs/superpowers/specs/2026-06-11-edgezero-extensible-cli-adaptation-design.md @@ -29,7 +29,15 @@ "$0.20 always" → "$0.20 default" pricing-doc rewrite (§3.8), the Spin `runtime-config.toml`/`--runtime-config-file` requirement (§3.6), the malformed-config-FILE-vs-read-error nuance (§3.5), and an explicit seeded - test fixture (§5). + test fixture (§5). **R6 (PR #110 review)** — CI seed gate now seeds a + **non-default** `0.35` and `jq`-asserts it (a bare push silently seeds the + `0.20` default); the §5 runtime fixture is a **registry-backed + `resolve_bid_cpm`** test (public `StoreRegistry`/`ConfigStoreHandle`, app-demo + `config_flow.rs` pattern), not just the pure parser; Dockerfile reframed as + cache hygiene (+ add the missing spin manifest); CLI-story widened to the + per-adapter doc pages; docs `srcExclude` made verifiable + `.vitepress/.temp` + ignored; plan call-site note corrected (mediation has a _separate_ + `build_openrtb_response`). ## 1. Problem & context @@ -291,12 +299,16 @@ and malformed-value (error) branches without a live backend. `Config` subcommand dispatching `run_config_validate_typed::` and `run_config_push_typed::`. - Add `crates/mocktioneer-cli` to root `[workspace].members`. -- **Dockerfile:** the build pre-copies each crate manifest before - `cargo fetch --locked` ([Dockerfile:14-19]) to cache the dependency layer. - Add `COPY crates/mocktioneer-cli/Cargo.toml crates/mocktioneer-cli/Cargo.toml` - alongside the existing crate-manifest COPYs so the workspace `cargo fetch` - resolves with the new member present. (The image still ships only the axum - binary; the CLI crate just needs to be fetch-resolvable.) +- **Dockerfile (cache hygiene, not a correctness fix):** the build already does + `COPY crates ./crates` **before** `cargo fetch --locked` (Dockerfile:21/24), + so the workspace fetch resolves regardless of the per-crate manifest + pre-copies (Dockerfile:14-19). Those pre-copies exist only to create a + dependency **cache layer**, and that layer is already incomplete — it omits + the existing **spin** member. Bring it in line by adding the two missing + member manifests: + `COPY crates/mocktioneer-adapter-spin/Cargo.toml …` and + `COPY crates/mocktioneer-cli/Cargo.toml …`. (The image still ships only the + axum binary.) ### 3.8 Docs, agents, ignore files (verified surface) @@ -317,7 +329,11 @@ and malformed-value (error) branches without a live backend. - **edgezero-cli → mocktioneer-cli story (wider than VitePress docs).** After adding the in-repo `mocktioneer-cli`, distinguish the two everywhere they're referenced: `README.md`, `docs/guide/getting-started.md`, - `docs/guide/adapters/index.md`, `tests/playwright/README.md`, and + `docs/guide/adapters/index.md`, the **per-adapter pages** that show + `edgezero-cli` examples — `docs/guide/adapters/axum.md`, + `docs/guide/adapters/cloudflare.md`, `docs/guide/adapters/fastly.md` (keep + `edgezero-cli` valid, add the in-repo `mocktioneer-cli` alternative + alongside) — `tests/playwright/README.md`, and `tests/playwright/playwright.config.ts` (the `webServer` command). Rule: **`config validate`/`config push` are typed and live only in `mocktioneer-cli`**; `serve`/`build`/`deploy`/`auth`/`provision` work from @@ -332,12 +348,16 @@ and malformed-value (error) branches without a live backend. aren't stale. - **Docs formatter + VitePress exclusion (mandatory — this spec lives under `docs/`).** `docs/package.json`'s `format` runs `prettier --check .` and the - format CI job runs it; it **fails on this spec file** today, and VitePress - even built it into `docs/.vitepress/dist/…/superpowers/…`. Required: - (a) add `superpowers/` to `docs/.prettierignore`; (b) add - `srcExclude: ['**/superpowers/**']` to the VitePress config so internal specs - aren't published; (c) ensure no built `superpowers` artifact is committed - under `docs/.vitepress/dist/`. + format CI job runs it; it **fails on this spec file** today, and `npm run + build` renders specs/plans into `docs/.vitepress/dist/…/superpowers/…` (and a + `.vitepress/.temp/`). Required: (a) add `superpowers/` to + `docs/.prettierignore`; (b) add `srcExclude: ['**/superpowers/**']` to the + VitePress config so internal specs aren't published; (c) add + `.vitepress/.temp` to `docs/.gitignore` (currently missing); (d) **verify** + the build actually excludes them — + `find docs/.vitepress/dist docs/.vitepress/.temp -path '*superpowers*' -print + -quit` must produce no output after `npm run build` (a `format`-only check is + insufficient — that was the gap in the prior revision). ### 3.9 CI — `.github/workflows/test.yml` @@ -345,19 +365,24 @@ and malformed-value (error) branches without a live backend. target; set `CARGO_TARGET_WASM32_WASIP2_RUNNER`; keep the pinned Wasmtime install and confirm it runs wasip2 components. - `mocktioneer-cli` covered by `--workspace`. -- **Required gate** (also mirror these exact commands into - `.claude/commands/check-ci.md` and the `CLAUDE.md` CI-gates list): +- **Required gate** (also mirror `config validate --strict` into + `.claude/commands/check-ci.md` and the `CLAUDE.md` CI-gates list). Seed a + **non-default** value so the gate actually proves `bid_cpm` is wired — a bare + `config push --adapter axum` would seed the root default (`0.20`) and a + `test -f` would pass even if nothing were wired: ```sh cargo run -p mocktioneer-cli -- config validate --strict - cargo run -p mocktioneer-cli -- config push --adapter axum # real, not --dry-run + printf 'bid_cpm = 0.35\n' > /tmp/seed.toml + cargo run -p mocktioneer-cli -- config push --adapter axum --app-config /tmp/seed.toml + test "$(jq -r '.bid_cpm' .edgezero/local-config-mocktioneer_config.json)" = "0.35" ``` - followed by an assertion that the seeded `bid_cpm` flows through (an - integration test or serve smoke), so the seed → bind → read path is - exercised, not just validated. -- **Docker:** `.github/workflows/docker.yml` builds the image; with - `mocktioneer-cli` added as a workspace member, confirm the Dockerfile - manifest pre-copy (§3.7) keeps `cargo fetch --locked` working. Add a - `docker build` smoke if not already covered. + The handler → response half (seeded store → `0.35`) is proven deterministically + by the registry-backed `resolve_bid_cpm` test (§5), so no flaky serve+curl is + needed in CI. +- **Docker:** `.github/workflows/docker.yml` builds the image. The Dockerfile + change (§3.7) is cache hygiene only — `COPY crates` already precedes + `cargo fetch`, so the build resolves regardless. A `docker build` smoke is a + nice-to-have, not a gate. - **Docs formatter is mandatory, not conditional:** the format CI job already fails on this spec, so the `docs/.prettierignore` + VitePress `srcExclude` changes (§3.8) must land in this branch. @@ -371,9 +396,9 @@ and malformed-value (error) branches without a live backend. | Wasmtime can't run the wasip2 component | Wasmtime 45.0.0 supports it; set `CARGO_TARGET_WASM32_WASIP2_RUNNER`; match edgezero's contract-test config. | | Fresh dev errors before any push | Resolved: empty/absent → fallback to `FIXED_BID_CPM` (§3.5). | | Broken/malformed pushed _value_ masked as $0.20 | Read errors propagate; malformed present value errors (§3.5). Note a malformed _file_ degrades to fallback (bind-time drop), by design. | -| `bid_cpm` never exercised (store unseeded) | CI does a real `config push --adapter axum` + asserts the value flows; fixtures cover all branches (§5). | +| `bid_cpm` never exercised (store unseeded) | CI seeds a non-default `0.35` via `--app-config` and `jq`-asserts it round-trips; registry-backed `resolve_bid_cpm` test covers the read path (§5). | | Spin KV config silently empty (no `runtime-config.toml`) | Add `runtime-config.toml` + `--runtime-config-file` to spin commands (§3.6). | -| New `mocktioneer-cli` breaks Docker dependency layer | Pre-copy its `Cargo.toml` before `cargo fetch` (§3.7); `docker build` smoke (§5). | +| Docker dependency-cache layer stale/incomplete | Cache hygiene only — `COPY crates` precedes `cargo fetch`; add the missing spin + cli manifests to the pre-copy list (§3.7). | | Spec under `docs/` fails the format CI gate | Mandatory `docs/.prettierignore` + VitePress `srcExclude` (§3.8). | | Pinning to an unmerged branch | Documented; re-pin to edgezero `main` post-merge. | | `.cargo/config.toml.local` patch drift (`edgezero-macros`) | Already lists it; verify it patches cleanly. | @@ -396,19 +421,23 @@ and malformed-value (error) branches without a live backend. excluded — byte equality is impossible even on `main`. 9. Runtime exercise — **explicit fixtures** (the root `mocktioneer.toml` ships `bid_cpm = 0.20`, so 0.35 must come from a seeded store, not the default): - - **Unit (preferred, deterministic, no files):** build a `RequestContext` - with a `ConfigRegistry` fixture wrapping an in-memory - `MapConfigStore { "bid_cpm": "0.35" }` (the app-demo `handlers.rs` - pattern); assert OpenRTB and APS emit `0.35`. A no-registry context → `0.20`; - a `{ "bid_cpm": "-1" }` fixture → handler error. - - **Integration (axum seed path):** write a temp config with `bid_cpm = 0.35` - and run `cargo run -p mocktioneer-cli -- config push --adapter axum` (or - write `.edgezero/local-config-mocktioneer_config.json` = - `{"bid_cpm":"0.35"}` directly), serve, and assert the auction returns - `0.35`; remove the file and assert `0.20` with no error. -10. `docs/` formatter passes: `cd docs && npm run format` succeeds (i.e. the - `superpowers/` prettier-ignore is in place). -11. `docker build` succeeds with `mocktioneer-cli` in the workspace. + - **Registry-backed unit (preferred, deterministic, no files):** build a + `RequestContext` with a `ConfigRegistry` (public `StoreRegistry::new` + + `ConfigStoreHandle` over an in-memory `MapConfigStore`, inserted via + `request.extensions_mut().insert(registry)` — the app-demo + `config_flow.rs` pattern) and assert `resolve_bid_cpm(&ctx)` returns + `0.35`; a no-registry ctx → `0.20`; a `{ "bid_cpm": "-1" }` store → error. + Pair with builder tests that a supplied `cpm` (0.35) flows to the OpenRTB + bid `price` and the APS decoded price. + - **CI seed path (axum):** `printf 'bid_cpm = 0.35\n' > /tmp/seed.toml` then + `config push --adapter axum --app-config /tmp/seed.toml`, and assert + `jq -r '.bid_cpm' .edgezero/local-config-mocktioneer_config.json` == `0.35` + (not a bare push + `test -f`, which would silently seed `0.20`). +10. `docs/` gates pass: `cd docs && npm run format && npm run lint && npm run + build`, **and** `find docs/.vitepress/dist docs/.vitepress/.temp -path + '*superpowers*' -print -quit` produces no output (specs/plans excluded). +11. `docker build` succeeds with `mocktioneer-cli` in the workspace + (nice-to-have; not a gate — the build resolves regardless per §3.7). ## 6. Rollback / follow-ups From 4f5e814f967e55fd0c61cb78c7b37b1d2fca8628 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Mon, 15 Jun 2026 22:41:12 -0700 Subject: [PATCH 07/45] docs: address PR #110 review round 2 (eslint .temp ignore, async_trait(?Send) test impl, spin release build in verify) --- ...6-15-edgezero-extensible-cli-adaptation.md | 42 +++++++++++++------ ...gezero-extensible-cli-adaptation-design.md | 14 ++++--- 2 files changed, 38 insertions(+), 18 deletions(-) diff --git a/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md b/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md index 7ad73b2..ab2180c 100644 --- a/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md +++ b/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md @@ -49,12 +49,13 @@ - Modify: `docs/.vitepress/config.mts` (or `.ts`/`.js` — whichever exists) - Test: `cd docs && npm run format` -- [ ] **Step 1: Add `superpowers/` to the docs Prettier ignore** +- [ ] **Step 1: Add `superpowers/` AND the build temp dir to the docs Prettier ignore** -Append to `docs/.prettierignore`: +`docs/.prettierignore` currently lists `.vitepress/cache`, `.vitepress/dist`, `node_modules`. Append both the specs dir and the VitePress build temp dir (a `git`-ignore does NOT stop Prettier/ESLint from scanning a working-tree dir — the tool ignores must be set explicitly): ``` superpowers/ +.vitepress/.temp ``` - [ ] **Step 2: Exclude internal specs/plans from the VitePress build** @@ -65,22 +66,35 @@ Find the config file: `ls docs/.vitepress/config.*`. In its `defineConfig({ ... srcExclude: ['**/superpowers/**'], ``` -- [ ] **Step 3: Ignore the VitePress build temp dir** +- [ ] **Step 3: Ignore the VitePress build temp dir in ESLint AND git** -`docs/.gitignore` currently lists `node_modules`, `.vitepress/dist`, `.vitepress/cache` — but **not** `.vitepress/.temp`, which `npm run build` creates (and which also receives the rendered specs unless excluded). Add to `docs/.gitignore`: +`npm run build` generates `.vitepress/.temp/` (≈31 JS files) which **ESLint will scan and fail on** (≈700+ errors) unless the flat config ignores it — and the ESLint ignore list is separate from `.prettierignore` and `.gitignore`. In `docs/eslint.config.js`, extend the `ignores` array (currently `['.vitepress/cache/**', '.vitepress/dist/**', 'node_modules/**']`) to add `.vitepress/.temp/**`: + +```js + ignores: [ + '.vitepress/cache/**', + '.vitepress/dist/**', + '.vitepress/.temp/**', + 'node_modules/**', + ], +``` + +Also add `.vitepress/.temp` to `docs/.gitignore` (currently `node_modules`, `.vitepress/dist`, `.vitepress/cache`): ``` .vitepress/.temp ``` -`docs/.vitepress/dist` is already gitignored and untracked (verified) — nothing to remove. Confirm: `git check-ignore docs/.vitepress/dist && echo ignored` → `ignored`. +`docs/.vitepress/dist` is already gitignored and untracked — nothing to remove. Confirm: `git check-ignore docs/.vitepress/dist && echo ignored` → `ignored`. + +- [ ] **Step 4: Build FIRST, then verify format + lint pass with generated files present** -- [ ] **Step 4: Verify the formatter AND that the build excludes specs/plans** +Generate the temp/dist dirs, THEN run the gates (the prior revision only checked format _before_ a build, so it missed the generated-file failures): -Run: `cd docs && npm ci >/dev/null 2>&1 && npm run format && npm run build` -Expected: both PASS. +Run: `cd docs && npm ci >/dev/null 2>&1 && npm run build && npm run format && npm run lint` +Expected: all PASS (the `.temp`/`dist`/`superpowers` ignores hold). -Then assert no spec/plan leaked into the build output (this is the check the prior plan revision was missing — `srcExclude` must actually drop them): +Assert no spec/plan leaked into the build output: Run: `find docs/.vitepress/dist docs/.vitepress/.temp -path '*superpowers*' -print -quit` Expected: **no output**. If anything prints, `srcExclude` is mis-scoped — fix the glob (e.g. `'**/superpowers/**'` relative to the VitePress `srcDir`) and rebuild. @@ -88,8 +102,8 @@ Expected: **no output**. If anything prints, `srcExclude` is mis-scoped — fix - [ ] **Step 5: Commit** ```bash -git add docs/.prettierignore docs/.vitepress docs/.gitignore .gitignore -git commit -m "docs: exclude superpowers specs/plans from prettier + vitepress build" +git add docs/.prettierignore docs/eslint.config.js docs/.vitepress docs/.gitignore .gitignore +git commit -m "docs: exclude superpowers + vitepress build temp from prettier/eslint/vitepress" ``` --- @@ -531,7 +545,9 @@ Exercises the real `RequestContext` → `ConfigRegistry` → `config_store_defau // In-memory ConfigStore for tests (mirrors app-demo's MapConfigStore). struct MapConfigStore(std::collections::HashMap); - #[async_trait] + // `ConfigStore` is declared `#[async_trait(?Send)]` in edgezero-core, so + // the impl MUST use the same `(?Send)` mode or method signatures won't match. + #[async_trait(?Send)] impl edgezero_core::config_store::ConfigStore for MapConfigStore { async fn get( &self, @@ -1079,7 +1095,7 @@ Expected: PASS. Run: `cargo build -p mocktioneer-adapter-fastly --features fastly --target wasm32-wasip1` Expected: PASS. -Run: `cargo build -p mocktioneer-adapter-spin --features spin --target wasm32-wasip2` +Run: `cargo build --release -p mocktioneer-adapter-spin --features spin --target wasm32-wasip2` Expected: PASS. Run: `cargo build -p mocktioneer-adapter-cloudflare --features cloudflare --target wasm32-unknown-unknown` diff --git a/docs/superpowers/specs/2026-06-11-edgezero-extensible-cli-adaptation-design.md b/docs/superpowers/specs/2026-06-11-edgezero-extensible-cli-adaptation-design.md index 7bc6179..434ff7a 100644 --- a/docs/superpowers/specs/2026-06-11-edgezero-extensible-cli-adaptation-design.md +++ b/docs/superpowers/specs/2026-06-11-edgezero-extensible-cli-adaptation-design.md @@ -352,12 +352,16 @@ and malformed-value (error) branches without a live backend. build` renders specs/plans into `docs/.vitepress/dist/…/superpowers/…` (and a `.vitepress/.temp/`). Required: (a) add `superpowers/` to `docs/.prettierignore`; (b) add `srcExclude: ['**/superpowers/**']` to the - VitePress config so internal specs aren't published; (c) add - `.vitepress/.temp` to `docs/.gitignore` (currently missing); (d) **verify** - the build actually excludes them — + VitePress config so internal specs aren't published; (c) ignore the build + temp dir in **all three** tools — `.vitepress/.temp` in `docs/.prettierignore` + AND `.vitepress/.temp/**` in `docs/eslint.config.js` `ignores` (ESLint's flat + config has its own ignore list; a `.gitignore`/`.prettierignore` entry does + not stop ESLint scanning it) AND `.vitepress/.temp` in `docs/.gitignore`; + (d) **verify after a build, not before** — run `npm run build` first, then + `npm run format && npm run lint`, and assert `find docs/.vitepress/dist docs/.vitepress/.temp -path '*superpowers*' -print - -quit` must produce no output after `npm run build` (a `format`-only check is - insufficient — that was the gap in the prior revision). + -quit` yields nothing. (The prior revision only checked `format` pre-build, so + it missed the ~700 ESLint errors generated files produce.) ### 3.9 CI — `.github/workflows/test.yml` From e546a829672cd5c85268ec0ecd47c10255a0d63f Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Mon, 15 Jun 2026 23:07:36 -0700 Subject: [PATCH 08/45] docs: PR #110 review nits (docker commit msg, actionlint over PyYAML) --- ...026-06-15-edgezero-extensible-cli-adaptation.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md b/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md index ab2180c..a9a3323 100644 --- a/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md +++ b/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md @@ -929,7 +929,7 @@ Expected: `cargo fetch --locked` resolves and the build completes. (If Docker is ```bash git add Dockerfile -git commit -m "build: copy mocktioneer-cli manifest before cargo fetch in Docker" +git commit -m "build: copy missing adapter manifests before cargo fetch in Docker" ``` --- @@ -1056,10 +1056,16 @@ Add a step (in the existing native test job, after `cargo test`): A **bare** `config push --adapter axum` would silently seed the root `mocktioneer.toml` default (`0.20`) and a `test -f` only proves a file exists — it would pass even if `bid_cpm` were never wired. Seeding `0.35` via `--app-config` and asserting the JSON value with `jq` proves push writes the _configured_ value. The handler → response half (a seeded store yielding `0.35`) is proven deterministically by the registry-backed `resolve_bid_cpm` test (Task 6, Step 4b), so no flaky serve+curl is needed here. (`jq` is preinstalled on GitHub `ubuntu-latest`.) -- [ ] **Step 4: Lint the workflow locally (if `act`/`actionlint` available) or eyeball YAML** +- [ ] **Step 4: Lint the workflow locally** -Run: `python -c "import yaml,sys; yaml.safe_load(open('.github/workflows/test.yml')); print('yaml-ok')"` -Expected: `yaml-ok`. +Prefer `actionlint` (validates GitHub Actions schema, not just YAML): + +Run: `command -v actionlint >/dev/null && actionlint .github/workflows/test.yml || echo "actionlint not installed"` +Expected: PASS, or the not-installed note. + +Fallback YAML well-formedness check **only if PyYAML is available** (`pip install pyyaml`; it is not in the stdlib, so don't rely on it in a clean env): + +Run: `python -c "import importlib.util,sys; sys.exit(0) if importlib.util.find_spec('yaml') is None else __import__('yaml').safe_load(open('.github/workflows/test.yml'))" && echo "yaml-ok-or-skipped"` - [ ] **Step 5: Commit** From f84db7a3dabd091dd2b7eccfa9e0d1ff3879f2e8 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Mon, 15 Jun 2026 23:14:33 -0700 Subject: [PATCH 09/45] docs: exclude superpowers + vitepress build temp from prettier/eslint/vitepress --- docs/.gitignore | 1 + docs/.prettierignore | 1 + docs/.vitepress/config.mts | 3 +++ docs/eslint.config.js | 7 ++++++- 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/.gitignore b/docs/.gitignore index 57a09c3..097c229 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -1,3 +1,4 @@ node_modules .vitepress/dist .vitepress/cache +.vitepress/.temp diff --git a/docs/.prettierignore b/docs/.prettierignore index c1f8cd8..c43c06b 100644 --- a/docs/.prettierignore +++ b/docs/.prettierignore @@ -1,4 +1,5 @@ .vitepress/cache .vitepress/dist +.vitepress/.temp node_modules superpowers/ diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index b6e18c3..fc4a16c 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -5,6 +5,9 @@ export default defineConfig({ title: 'Mocktioneer', description: 'Deterministic OpenRTB banner bidder for edge platforms', base: '/mocktioneer/', + // Internal design specs/plans live under docs/superpowers/ but are not + // published documentation — keep them out of the built site. + srcExclude: ['**/superpowers/**'], themeConfig: { // https://vitepress.dev/reference/default-theme-config nav: [ diff --git a/docs/eslint.config.js b/docs/eslint.config.js index 50481a7..d991fe3 100644 --- a/docs/eslint.config.js +++ b/docs/eslint.config.js @@ -3,7 +3,12 @@ import tseslint from 'typescript-eslint' export default [ { - ignores: ['.vitepress/cache/**', '.vitepress/dist/**', 'node_modules/**'], + ignores: [ + '.vitepress/cache/**', + '.vitepress/dist/**', + '.vitepress/.temp/**', + 'node_modules/**', + ], }, js.configs.recommended, ...tseslint.configs.recommended, From 4f1a9dd87f977e7e4e3ce3909046ac99bea572e3 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Mon, 15 Jun 2026 23:16:00 -0700 Subject: [PATCH 10/45] build: pin edgezero deps to feature/extensible-cli, bump spin-sdk to 6, add clap/anyhow --- Cargo.lock | 274 ++++++++++++++++++----------- Cargo.toml | 15 +- crates/mocktioneer-core/Cargo.toml | 1 + 3 files changed, 184 insertions(+), 106 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f6e1af6..13c036d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -574,7 +574,7 @@ dependencies = [ [[package]] name = "edgezero-adapter-axum" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=main#7ec2ad1de50477536854c2dc96acb2699b7d0026" +source = "git+https://github.com/stackpop/edgezero.git?branch=feature%2Fextensible-cli#2eeccc9748daba92b9adf6afe4df105e79269ae9" dependencies = [ "anyhow", "async-trait", @@ -587,6 +587,7 @@ dependencies = [ "log", "redb", "reqwest", + "serde_json", "simple_logger", "thiserror 2.0.18", "tokio", @@ -597,7 +598,7 @@ dependencies = [ [[package]] name = "edgezero-adapter-cloudflare" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=main#7ec2ad1de50477536854c2dc96acb2699b7d0026" +source = "git+https://github.com/stackpop/edgezero.git?branch=feature%2Fextensible-cli#2eeccc9748daba92b9adf6afe4df105e79269ae9" dependencies = [ "anyhow", "async-trait", @@ -615,7 +616,7 @@ dependencies = [ [[package]] name = "edgezero-adapter-fastly" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=main#7ec2ad1de50477536854c2dc96acb2699b7d0026" +source = "git+https://github.com/stackpop/edgezero.git?branch=feature%2Fextensible-cli#2eeccc9748daba92b9adf6afe4df105e79269ae9" dependencies = [ "anyhow", "async-stream", @@ -637,7 +638,7 @@ dependencies = [ [[package]] name = "edgezero-adapter-spin" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=main#7ec2ad1de50477536854c2dc96acb2699b7d0026" +source = "git+https://github.com/stackpop/edgezero.git?branch=feature%2Fextensible-cli#2eeccc9748daba92b9adf6afe4df105e79269ae9" dependencies = [ "anyhow", "async-trait", @@ -648,13 +649,17 @@ dependencies = [ "futures", "futures-util", "log", + "serde", + "serde_json", "spin-sdk", + "subtle", + "thiserror 2.0.18", ] [[package]] name = "edgezero-core" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=main#7ec2ad1de50477536854c2dc96acb2699b7d0026" +source = "git+https://github.com/stackpop/edgezero.git?branch=feature%2Fextensible-cli#2eeccc9748daba92b9adf6afe4df105e79269ae9" dependencies = [ "anyhow", "async-compression", @@ -682,7 +687,7 @@ dependencies = [ [[package]] name = "edgezero-macros" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=main#7ec2ad1de50477536854c2dc96acb2699b7d0026" +source = "git+https://github.com/stackpop/edgezero.git?branch=feature%2Fextensible-cli#2eeccc9748daba92b9adf6afe4df105e79269ae9" dependencies = [ "log", "proc-macro2", @@ -830,6 +835,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -952,7 +963,7 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] @@ -980,7 +991,7 @@ dependencies = [ "libc", "r-efi 6.0.0", "wasip2", - "wasip3", + "wasip3 0.4.0+wasi-0.3.0-rc-2026-01-06", ] [[package]] @@ -1005,7 +1016,7 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "foldhash", + "foldhash 0.1.5", ] [[package]] @@ -1013,6 +1024,9 @@ name = "hashbrown" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "foldhash 0.2.0", +] [[package]] name = "heck" @@ -1480,7 +1494,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" dependencies = [ "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "windows-sys 0.61.2", ] @@ -1537,6 +1551,7 @@ dependencies = [ name = "mocktioneer-core" version = "0.1.0" dependencies = [ + "anyhow", "async-trait", "base64", "ed25519-dalek", @@ -1941,7 +1956,9 @@ checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" dependencies = [ "base64", "bytes", + "futures-channel", "futures-core", + "futures-util", "http", "http-body", "http-body-util", @@ -1956,6 +1973,8 @@ dependencies = [ "rustls", "rustls-pki-types", "rustls-platform-verifier", + "serde", + "serde_json", "sync_wrapper", "tokio", "tokio-rustls", @@ -1982,16 +2001,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "routefinder" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0971d3c8943a6267d6bd0d782fdc4afa7593e7381a92a3df950ff58897e066b5" -dependencies = [ - "smartcow", - "smartstring", -] - [[package]] name = "rustc-hash" version = "2.1.2" @@ -2333,26 +2342,6 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" -[[package]] -name = "smartcow" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "656fcb1c1fca8c4655372134ce87d8afdf5ec5949ebabe8d314be0141d8b5da2" -dependencies = [ - "smartstring", -] - -[[package]] -name = "smartstring" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" -dependencies = [ - "autocfg", - "static_assertions", - "version_check", -] - [[package]] name = "socket2" version = "0.6.4" @@ -2363,25 +2352,12 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "spin-executor" -version = "5.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bba409d00af758cd5de128da4a801e891af0545138f66a688f025f6d4e33870b" -dependencies = [ - "futures", - "once_cell", - "wasi 0.13.1+wasi-0.2.0", -] - [[package]] name = "spin-macro" -version = "5.2.0" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f959f16928e3c023468e41da9ebb77442e2ce22315e8dab11508fe76b3567ee1" +checksum = "11e483b94d5bcfac493caf0427fa875063e3e8604d0466a4ab491ec200a42857" dependencies = [ - "anyhow", - "bytes", "proc-macro2", "quote", "syn 1.0.109", @@ -2389,24 +2365,19 @@ dependencies = [ [[package]] name = "spin-sdk" -version = "5.2.0" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8951c7c4ab7f87f332d497789eeed9631c8116988b628b4851eb2fa999ead019" +checksum = "4fd2abac3eb2ee249c2241ab87f7b1287f36172c8cc1ea815c19c85e41ede44d" dependencies = [ "anyhow", - "async-trait", "bytes", - "chrono", - "form_urlencoded", "futures", "http", - "once_cell", - "routefinder", - "spin-executor", + "http-body", + "http-body-util", "spin-macro", "thiserror 2.0.18", - "wasi 0.13.1+wasi-0.2.0", - "wit-bindgen 0.51.0", + "wasip3 0.6.0+wasi-0.3.0-rc-2026-03-15", ] [[package]] @@ -2425,12 +2396,6 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "strsim" version = "0.11.1" @@ -2884,15 +2849,6 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasi" -version = "0.13.1+wasi-0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f43d1c36145feb89a3e61aa0ba3e582d976a8ab77f1474aa0adb80800fe0cf8" -dependencies = [ - "wit-bindgen-rt", -] - [[package]] name = "wasip2" version = "1.0.3+wasi-0.2.9" @@ -2911,6 +2867,19 @@ dependencies = [ "wit-bindgen 0.51.0", ] +[[package]] +name = "wasip3" +version = "0.6.0+wasi-0.3.0-rc-2026-03-15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed83456dd6a0b8581998c0365e4651fa2997e5093b49243b7f35391afaa7a3d9" +dependencies = [ + "bytes", + "http", + "http-body", + "thiserror 2.0.18", + "wit-bindgen 0.57.1", +] + [[package]] name = "wasm-bindgen" version = "0.2.121" @@ -3012,7 +2981,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" dependencies = [ "leb128fmt", - "wasmparser", + "wasmparser 0.244.0", +] + +[[package]] +name = "wasm-encoder" +version = "0.247.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b6733b8b91d010a6ac5b0fb237dc46a19650bc4c67db66857e2e787d437204" +dependencies = [ + "leb128fmt", + "wasmparser 0.247.0", ] [[package]] @@ -3023,8 +3002,20 @@ checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", "indexmap", - "wasm-encoder", - "wasmparser", + "wasm-encoder 0.244.0", + "wasmparser 0.244.0", +] + +[[package]] +name = "wasm-metadata" +version = "0.247.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "665fe59e56cc9b419ca6fcca56673e3421d1a5011e3b65caf6b726fd9e041d10" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder 0.247.0", + "wasmparser 0.247.0", ] [[package]] @@ -3052,6 +3043,18 @@ dependencies = [ "semver", ] +[[package]] +name = "wasmparser" +version = "0.247.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e6fb4c2bee46c5ea4d40f8cdb5c131725cd976718ec56f1c8e82fbde5fa2a80" +dependencies = [ + "bitflags 2.12.1", + "hashbrown 0.17.1", + "indexmap", + "semver", +] + [[package]] name = "web-sys" version = "0.3.98" @@ -3318,7 +3321,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" dependencies = [ "bitflags 2.12.1", - "wit-bindgen-rust-macro", + "wit-bindgen-rust-macro 0.51.0", ] [[package]] @@ -3328,6 +3331,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" dependencies = [ "bitflags 2.12.1", + "futures", + "wit-bindgen-rust-macro 0.57.1", ] [[package]] @@ -3338,16 +3343,18 @@ checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" dependencies = [ "anyhow", "heck", - "wit-parser", + "wit-parser 0.244.0", ] [[package]] -name = "wit-bindgen-rt" -version = "0.24.0" +name = "wit-bindgen-core" +version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0780cf7046630ed70f689a098cd8d56c5c3b22f2a7379bbdb088879963ff96" +checksum = "02dee27a2dc20d1008016c742ec9fc6ea498492994ba3750be7454cbc97ff04c" dependencies = [ - "bitflags 2.12.1", + "anyhow", + "heck", + "wit-parser 0.247.0", ] [[package]] @@ -3361,9 +3368,25 @@ dependencies = [ "indexmap", "prettyplease", "syn 2.0.117", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", + "wasm-metadata 0.244.0", + "wit-bindgen-core 0.51.0", + "wit-component 0.244.0", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5007dae772945b7a5003d69d90a3a4a78929d41f19d004e980c4259a6af4484" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn 2.0.117", + "wasm-metadata 0.247.0", + "wit-bindgen-core 0.57.1", + "wit-component 0.247.0", ] [[package]] @@ -3377,8 +3400,23 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.117", - "wit-bindgen-core", - "wit-bindgen-rust", + "wit-bindgen-core 0.51.0", + "wit-bindgen-rust 0.51.0", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af9237d678e3513ad24e96fe98beacdc0db6405284ba2a2400418cf0d42caa89" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core 0.57.1", + "wit-bindgen-rust 0.57.1", ] [[package]] @@ -3394,10 +3432,29 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", + "wasm-encoder 0.244.0", + "wasm-metadata 0.244.0", + "wasmparser 0.244.0", + "wit-parser 0.244.0", +] + +[[package]] +name = "wit-component" +version = "0.247.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d567162a6b9843080e5e0053f696623ff694bae8ae017c9ec536d1873bbe3d8" +dependencies = [ + "anyhow", + "bitflags 2.12.1", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder 0.247.0", + "wasm-metadata 0.247.0", + "wasmparser 0.247.0", + "wit-parser 0.247.0", ] [[package]] @@ -3415,7 +3472,26 @@ dependencies = [ "serde_derive", "serde_json", "unicode-xid", - "wasmparser", + "wasmparser 0.244.0", +] + +[[package]] +name = "wit-parser" +version = "0.247.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ffe4064318cdf3c08cb99343b44c039fcefe61ccdf58aa9975285f13d74d1fc" +dependencies = [ + "anyhow", + "hashbrown 0.17.1", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser 0.247.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index bb4c1f8..4632aa0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,13 +21,14 @@ anyhow = "1" async-trait = "0.1" axum = "0.8" base64 = "0.22" +clap = { version = "4", features = ["derive"] } ed25519-dalek = "2.1" -edgezero-adapter-axum = { git = "https://github.com/stackpop/edgezero.git", branch = "main", package = "edgezero-adapter-axum", default-features = false } -edgezero-adapter-cloudflare = { git = "https://github.com/stackpop/edgezero.git", branch = "main", package = "edgezero-adapter-cloudflare", default-features = false } -edgezero-adapter-fastly = { git = "https://github.com/stackpop/edgezero.git", branch = "main", package = "edgezero-adapter-fastly", default-features = false } -edgezero-adapter-spin = { git = "https://github.com/stackpop/edgezero.git", branch = "main", package = "edgezero-adapter-spin", default-features = false } -edgezero-cli = { git = "https://github.com/stackpop/edgezero.git", branch = "main", package = "edgezero-cli" } -edgezero-core = { git = "https://github.com/stackpop/edgezero.git", branch = "main", package = "edgezero-core" } +edgezero-adapter-axum = { git = "https://github.com/stackpop/edgezero.git", branch = "feature/extensible-cli", package = "edgezero-adapter-axum", default-features = false } +edgezero-adapter-cloudflare = { git = "https://github.com/stackpop/edgezero.git", branch = "feature/extensible-cli", package = "edgezero-adapter-cloudflare", default-features = false } +edgezero-adapter-fastly = { git = "https://github.com/stackpop/edgezero.git", branch = "feature/extensible-cli", package = "edgezero-adapter-fastly", default-features = false } +edgezero-adapter-spin = { git = "https://github.com/stackpop/edgezero.git", branch = "feature/extensible-cli", package = "edgezero-adapter-spin", default-features = false } +edgezero-cli = { git = "https://github.com/stackpop/edgezero.git", branch = "feature/extensible-cli", package = "edgezero-cli" } +edgezero-core = { git = "https://github.com/stackpop/edgezero.git", branch = "feature/extensible-cli", package = "edgezero-core" } fastly = "0.12.1" futures = { version = "0.3", features = ["std", "executor"] } futures-util = "0.3.32" @@ -40,7 +41,7 @@ serde_json = "1" serde_repr = "0.1" sha2 = "0.10" simple_logger = "5" -spin-sdk = { version = "5.2", default-features = false } +spin-sdk = { version = "~6.0", default-features = false, features = ["http", "key-value", "variables"] } subtle = "2" thiserror = "1.0" tokio = { version = "1", features = ["macros", "rt-multi-thread"] } diff --git a/crates/mocktioneer-core/Cargo.toml b/crates/mocktioneer-core/Cargo.toml index b4a9341..c6405dd 100644 --- a/crates/mocktioneer-core/Cargo.toml +++ b/crates/mocktioneer-core/Cargo.toml @@ -6,6 +6,7 @@ publish = false license.workspace = true [dependencies] +anyhow = { workspace = true } async-trait = { workspace = true } base64 = { workspace = true } ed25519-dalek = { workspace = true } From 8e7aaac0db0a04da1ec6b1f719bc2fdd4cf3ea64 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Mon, 15 Jun 2026 23:16:59 -0700 Subject: [PATCH 11/45] feat: drop run_app manifest arg for axum/cloudflare/fastly (edgezero #269) --- crates/mocktioneer-adapter-axum/src/main.rs | 2 +- crates/mocktioneer-adapter-cloudflare/src/lib.rs | 8 +------- crates/mocktioneer-adapter-fastly/src/main.rs | 2 +- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/crates/mocktioneer-adapter-axum/src/main.rs b/crates/mocktioneer-adapter-axum/src/main.rs index f85b0d5..b094cd7 100644 --- a/crates/mocktioneer-adapter-axum/src/main.rs +++ b/crates/mocktioneer-adapter-axum/src/main.rs @@ -4,7 +4,7 @@ use edgezero_adapter_axum::dev_server::run_app; use mocktioneer_core::MocktioneerApp; fn main() { - if let Err(err) = run_app::(include_str!("../../../edgezero.toml")) { + if let Err(err) = run_app::() { #[expect( clippy::print_stderr, reason = "startup-error path: logger may not be initialised yet" diff --git a/crates/mocktioneer-adapter-cloudflare/src/lib.rs b/crates/mocktioneer-adapter-cloudflare/src/lib.rs index 76e1a8e..9dc67fa 100644 --- a/crates/mocktioneer-adapter-cloudflare/src/lib.rs +++ b/crates/mocktioneer-adapter-cloudflare/src/lib.rs @@ -8,11 +8,5 @@ use worker::*; #[cfg(target_arch = "wasm32")] #[event(fetch)] pub async fn main(req: Request, env: Env, ctx: Context) -> Result { - edgezero_adapter_cloudflare::run_app::( - include_str!("../../../edgezero.toml"), - req, - env, - ctx, - ) - .await + edgezero_adapter_cloudflare::run_app::(req, env, ctx).await } diff --git a/crates/mocktioneer-adapter-fastly/src/main.rs b/crates/mocktioneer-adapter-fastly/src/main.rs index e966745..f1f0170 100644 --- a/crates/mocktioneer-adapter-fastly/src/main.rs +++ b/crates/mocktioneer-adapter-fastly/src/main.rs @@ -13,7 +13,7 @@ use mocktioneer_core::MocktioneerApp; #[cfg(target_arch = "wasm32")] #[fastly::main] pub fn main(req: Request) -> Result { - edgezero_adapter_fastly::run_app::(include_str!("../../../edgezero.toml"), req) + edgezero_adapter_fastly::run_app::(req) } #[cfg(not(target_arch = "wasm32"))] From d38790b66446960c03ed68be2b8092e67eb3ff3b Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Mon, 15 Jun 2026 23:19:47 -0700 Subject: [PATCH 12/45] feat: migrate spin adapter to spin-sdk 6 / wasm32-wasip2 (edgezero #269) --- .../mocktioneer-adapter-spin/runtime-config.toml | 5 +++++ crates/mocktioneer-adapter-spin/spin.toml | 5 +++-- crates/mocktioneer-adapter-spin/src/lib.rs | 16 +++++++--------- crates/mocktioneer-adapter-spin/src/main.rs | 4 ++-- .../mocktioneer-adapter-spin/tests/contract.rs | 4 ++-- 5 files changed, 19 insertions(+), 15 deletions(-) create mode 100644 crates/mocktioneer-adapter-spin/runtime-config.toml diff --git a/crates/mocktioneer-adapter-spin/runtime-config.toml b/crates/mocktioneer-adapter-spin/runtime-config.toml new file mode 100644 index 0000000..90b66e7 --- /dev/null +++ b/crates/mocktioneer-adapter-spin/runtime-config.toml @@ -0,0 +1,5 @@ +# Spin runtime config: declares the KV labels the component may open. +# The `mocktioneer_config` label backs the EdgeZero `[stores.config]` +# store; the default SQLite backend persists to `.spin/sqlite_key_value.db`. +[key_value_store.mocktioneer_config] +type = "spin" diff --git a/crates/mocktioneer-adapter-spin/spin.toml b/crates/mocktioneer-adapter-spin/spin.toml index cec0d60..a606462 100644 --- a/crates/mocktioneer-adapter-spin/spin.toml +++ b/crates/mocktioneer-adapter-spin/spin.toml @@ -10,9 +10,10 @@ route = "/..." component = "mocktioneer" [component.mocktioneer] -source = "../../target/wasm32-wasip1/release/mocktioneer_adapter_spin.wasm" +source = "../../target/wasm32-wasip2/release/mocktioneer_adapter_spin.wasm" allowed_outbound_hosts = ["https://*:*"] +key_value_stores = ["mocktioneer_config"] [component.mocktioneer.build] -command = "cargo build --target wasm32-wasip1 --release" +command = "cargo build --target wasm32-wasip2 --release" watch = ["src/**/*.rs", "Cargo.toml"] diff --git a/crates/mocktioneer-adapter-spin/src/lib.rs b/crates/mocktioneer-adapter-spin/src/lib.rs index e309ba0..020994f 100644 --- a/crates/mocktioneer-adapter-spin/src/lib.rs +++ b/crates/mocktioneer-adapter-spin/src/lib.rs @@ -1,22 +1,20 @@ -#![cfg_attr(target_arch = "wasm32", no_main)] #![cfg_attr( target_arch = "wasm32", - expect( + allow( unsafe_code, - reason = "#[http_component] expands to WASI wit-bindgen code containing an export_name function plus unsafe declarations and blocks; this is the only entry into the Spin runtime" + reason = "spin's #[http_service] macro generates the unsafe wasm export" ) )] #[cfg(target_arch = "wasm32")] use mocktioneer_core::MocktioneerApp; #[cfg(target_arch = "wasm32")] -use spin_sdk::http::{IncomingRequest, IntoResponse}; +use spin_sdk::http::{IntoResponse, Request}; #[cfg(target_arch = "wasm32")] -use spin_sdk::http_component; +use spin_sdk::http_service; #[cfg(target_arch = "wasm32")] -#[http_component] -async fn handle(req: IncomingRequest) -> anyhow::Result { - edgezero_adapter_spin::run_app::(include_str!("../../../edgezero.toml"), req) - .await +#[http_service] +async fn handle(req: Request) -> anyhow::Result { + edgezero_adapter_spin::run_app::(req).await } diff --git a/crates/mocktioneer-adapter-spin/src/main.rs b/crates/mocktioneer-adapter-spin/src/main.rs index 089a78d..f4f6e9d 100644 --- a/crates/mocktioneer-adapter-spin/src/main.rs +++ b/crates/mocktioneer-adapter-spin/src/main.rs @@ -1,7 +1,7 @@ #[expect( clippy::print_stderr, - reason = "host-side stub that exists solely to remind the operator to target wasm32-wasip1" + reason = "host-side stub that exists solely to remind the operator to target wasm32-wasip2" )] fn main() { - eprintln!("Run `spin up` or target wasm32-wasip1 to execute mocktioneer-adapter-spin."); + eprintln!("Run `spin up` or target wasm32-wasip2 to execute mocktioneer-adapter-spin."); } diff --git a/crates/mocktioneer-adapter-spin/tests/contract.rs b/crates/mocktioneer-adapter-spin/tests/contract.rs index 2b16c4a..063c396 100644 --- a/crates/mocktioneer-adapter-spin/tests/contract.rs +++ b/crates/mocktioneer-adapter-spin/tests/contract.rs @@ -1,10 +1,10 @@ //! Spin adapter contract tests. //! -//! Spin's `IncomingRequest` is a WASI handle that can only be constructed by +//! Spin's `Request` is a WASI handle that can only be constructed by //! the Spin runtime, so we cannot exercise the full `dispatch(req)` path the //! way the Fastly and Cloudflare contract tests do. Instead, these tests //! prove that `MocktioneerApp::build_app()` and `router().oneshot(...)` work -//! end-to-end under `wasm32-wasip1` via the `wasmtime` runner — the same +//! end-to-end under `wasm32-wasip2` via the `wasmtime` runner — the same //! integration surface the Spin adapter's `run_app` calls into. #![cfg(all(feature = "spin", target_arch = "wasm32"))] From 63e7fdd85976b9eb997b83dcff9591e300308117 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Mon, 15 Jun 2026 23:20:48 -0700 Subject: [PATCH 13/45] feat: declare [stores.config] + spin wasip2/runtime-config in manifest --- edgezero.toml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/edgezero.toml b/edgezero.toml index e97654d..6660ff4 100644 --- a/edgezero.toml +++ b/edgezero.toml @@ -6,6 +6,9 @@ middleware = [ "mocktioneer_core::routes::Cors" ] +[stores.config] +ids = ["mocktioneer_config"] + [[triggers.http]] id = "root" path = "/" @@ -249,14 +252,14 @@ crate = "crates/mocktioneer-adapter-spin" manifest = "crates/mocktioneer-adapter-spin/spin.toml" [adapters.spin.build] -target = "wasm32-wasip1" +target = "wasm32-wasip2" profile = "release" features = ["spin"] [adapters.spin.commands] build = "spin build --from crates/mocktioneer-adapter-spin/spin.toml" -deploy = "spin deploy --from crates/mocktioneer-adapter-spin/spin.toml" -serve = "spin up --from crates/mocktioneer-adapter-spin/spin.toml" +deploy = "spin deploy --from crates/mocktioneer-adapter-spin/spin.toml --runtime-config-file crates/mocktioneer-adapter-spin/runtime-config.toml" +serve = "spin up --from crates/mocktioneer-adapter-spin/spin.toml --runtime-config-file crates/mocktioneer-adapter-spin/runtime-config.toml" [adapters.spin.logging] level = "info" From a35f7690c64664b590af413c8a05ea39875beebe Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Mon, 15 Jun 2026 23:22:18 -0700 Subject: [PATCH 14/45] feat: add MocktioneerConfig typed config (bid_cpm, validated) --- crates/mocktioneer-core/src/config.rs | 43 +++++++++++++++++++++++++++ crates/mocktioneer-core/src/lib.rs | 1 + 2 files changed, 44 insertions(+) create mode 100644 crates/mocktioneer-core/src/config.rs diff --git a/crates/mocktioneer-core/src/config.rs b/crates/mocktioneer-core/src/config.rs new file mode 100644 index 0000000..fd34b5c --- /dev/null +++ b/crates/mocktioneer-core/src/config.rs @@ -0,0 +1,43 @@ +//! Typed application config, loaded from `mocktioneer.toml`. The TOML file +//! maps 1:1 onto this struct (no `[config]` wrapper). v1 carries a single +//! `bid_cpm` field; `config validate --strict` enforces the rules below. + +use serde::{Deserialize, Serialize}; +use validator::{Validate, ValidationError}; + +#[derive(Debug, Deserialize, Serialize, Validate, edgezero_core::AppConfig)] +#[serde(deny_unknown_fields)] +pub struct MocktioneerConfig { + /// Fixed bid CPM in USD. Validated finite and strictly positive. + #[validate(custom(function = "validate_bid_cpm"))] + pub bid_cpm: f64, +} + +/// `validator` passes `Copy` scalar fields (like `f64`) by value to custom +/// fns; `range` does not reject NaN / inf for floats, so validate explicitly. +fn validate_bid_cpm(value: f64) -> Result<(), ValidationError> { + if value.is_finite() && value > 0.0 { + Ok(()) + } else { + Err(ValidationError::new("bid_cpm_must_be_finite_positive")) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn accepts_positive_finite_cpm() { + let cfg = MocktioneerConfig { bid_cpm: 0.20 }; + assert!(cfg.validate().is_ok()); + } + + #[test] + fn rejects_zero_negative_and_non_finite() { + for bad in [0.0_f64, -1.0, f64::NAN, f64::INFINITY] { + let cfg = MocktioneerConfig { bid_cpm: bad }; + assert!(cfg.validate().is_err(), "expected {bad} to be rejected"); + } + } +} diff --git a/crates/mocktioneer-core/src/lib.rs b/crates/mocktioneer-core/src/lib.rs index a07a836..140de5f 100644 --- a/crates/mocktioneer-core/src/lib.rs +++ b/crates/mocktioneer-core/src/lib.rs @@ -1,5 +1,6 @@ pub mod aps; pub mod auction; +pub mod config; pub mod mediation; pub mod openrtb; pub mod render; From 77016f9b8290bc796d77ec985356945d122ec336 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Mon, 15 Jun 2026 23:35:45 -0700 Subject: [PATCH 15/45] feat: resolve bid_cpm from config store at runtime (OpenRTB + APS), default FIXED_BID_CPM --- crates/mocktioneer-core/src/auction.rs | 67 ++++++++-- crates/mocktioneer-core/src/config.rs | 4 +- crates/mocktioneer-core/src/routes.rs | 120 +++++++++++++++++- .../mocktioneer-core/tests/aps_endpoints.rs | 28 ++-- 4 files changed, 185 insertions(+), 34 deletions(-) diff --git a/crates/mocktioneer-core/src/auction.rs b/crates/mocktioneer-core/src/auction.rs index ff49d0f..2d34c65 100644 --- a/crates/mocktioneer-core/src/auction.rs +++ b/crates/mocktioneer-core/src/auction.rs @@ -93,6 +93,7 @@ pub fn build_openrtb_response( req: &OpenRTBRequest, base_host: &str, signature_status: SignatureStatus, + cpm: f64, ) -> OpenRTBResponse { // Build bids without adm let mut bids: Vec = Vec::new(); @@ -111,13 +112,13 @@ pub fn build_openrtb_response( { log::warn!( "imp[{}].ext.mocktioneer.bid is deprecated and ignored; \ - all bids use fixed price ${}", + all bids use the configured price ${}", imp.id, - FIXED_BID_CPM + cpm ); } - let price = FIXED_BID_CPM; + let price = cpm; bids.push(OpenrtbBid { id: bid_id, @@ -227,7 +228,7 @@ pub fn decode_aps_price(encoded: &str) -> Option { /// - Generates base64-encoded price strings (recoverable in mock, unlike real APS) #[inline] #[must_use] -pub fn build_aps_response(req: &ApsBidRequest, base_host: &str) -> ApsBidResponse { +pub fn build_aps_response(req: &ApsBidRequest, base_host: &str, cpm: f64) -> ApsBidResponse { let mut slots: Vec = Vec::new(); for slot in &req.slots { @@ -256,7 +257,7 @@ pub fn build_aps_response(req: &ApsBidRequest, base_host: &str) -> ApsBidRespons }; // Generate bid components using fixed CPM pricing - let price = FIXED_BID_CPM; + let price = cpm; let impression_id = new_id(); let crid = format!("{}-{}", new_id(), "mocktioneer"); let size_str = format!("{width}x{height}"); @@ -341,7 +342,7 @@ mod tests { }], ..Default::default() }; - let resp = build_openrtb_response(&req, "host.test", test_signature()); + let resp = build_openrtb_response(&req, "host.test", test_signature(), FIXED_BID_CPM); let bid_id = &resp.seatbid[0].bid[0].id; assert_eq!(bid_id.len(), 32); assert!( @@ -365,7 +366,7 @@ mod tests { user_agent: None, timeout: None, }; - let resp = build_aps_response(&req, "mock.test"); + let resp = build_aps_response(&req, "mock.test", FIXED_BID_CPM); let slot = &resp.contextual.slots[0]; // Use decode_aps_price to verify the encoded price @@ -386,7 +387,7 @@ mod tests { user_agent: None, timeout: None, }; - let resp = build_aps_response(&req, "mock.test"); + let resp = build_aps_response(&req, "mock.test", FIXED_BID_CPM); assert_eq!(resp.contextual.slots.len(), 1); let slot = &resp.contextual.slots[0]; @@ -406,7 +407,7 @@ mod tests { user_agent: None, timeout: None, }; - let resp = build_aps_response(&req, "mock.test"); + let resp = build_aps_response(&req, "mock.test", FIXED_BID_CPM); assert_eq!(resp.contextual.status, Some("ok".to_owned())); assert_eq!(resp.contextual.slots.len(), 1); @@ -433,7 +434,7 @@ mod tests { user_agent: None, timeout: None, }; - let resp = build_aps_response(&req, "mock.test"); + let resp = build_aps_response(&req, "mock.test", FIXED_BID_CPM); // Non-standard sizes should be skipped assert!(resp.contextual.slots.is_empty()); @@ -452,7 +453,7 @@ mod tests { user_agent: None, timeout: None, }; - let resp = build_aps_response(&req, "mock.test"); + let resp = build_aps_response(&req, "mock.test", FIXED_BID_CPM); let slot = &resp.contextual.slots[0]; // Verify targeting keys list @@ -485,7 +486,7 @@ mod tests { }], ..Default::default() }; - let resp = build_openrtb_response(&req, "host.test", test_signature()); + let resp = build_openrtb_response(&req, "host.test", test_signature(), FIXED_BID_CPM); let bid = &resp.seatbid[0].bid[0]; // Non-standard should default to 300x250 assert_eq!(bid.width, Some(300)); @@ -507,7 +508,7 @@ mod tests { }], ..Default::default() }; - let resp = build_openrtb_response(&req, "host.test", test_signature()); + let resp = build_openrtb_response(&req, "host.test", test_signature(), FIXED_BID_CPM); assert_eq!(resp.id, "r1"); assert_eq!(resp.cur.as_deref(), Some("USD")); assert_eq!(resp.seatbid.len(), 1); @@ -560,7 +561,7 @@ mod tests { }], ..Default::default() }; - let resp = build_openrtb_response(&req, "host.test", test_signature()); + let resp = build_openrtb_response(&req, "host.test", test_signature(), FIXED_BID_CPM); let bid = &resp.seatbid[0].bid[0]; assert_eq!(bid.price.to_bits(), FIXED_BID_CPM.to_bits()); assert!(bid.ext.is_none()); @@ -623,4 +624,42 @@ mod tests { assert_eq!(standard_or_default((333, 222)), (300, 250)); assert_eq!(standard_or_default((320, 50)), (320, 50)); } + + #[test] + fn openrtb_uses_supplied_cpm() { + let req = OpenRTBRequest { + id: "rc".to_owned(), + imp: vec![OpenrtbImp { + id: "1".to_owned(), + banner: Some(Banner { + width: Some(300), + height: Some(250), + ..Default::default() + }), + ..Default::default() + }], + ..Default::default() + }; + let resp = build_openrtb_response(&req, "host.test", test_signature(), 0.35); + assert!((resp.seatbid[0].bid[0].price - 0.35).abs() < f64::EPSILON); + } + + #[test] + fn aps_uses_supplied_cpm() { + let req = ApsBidRequest { + pub_id: "test".to_owned(), + slots: vec![ApsSlot { + slot_id: "slot1".to_owned(), + sizes: vec![[300, 250]], + slot_name: None, + }], + page_url: None, + user_agent: None, + timeout: None, + }; + let resp = build_aps_response(&req, "mock.test", 0.35); + let slot = &resp.contextual.slots[0]; + let price = decode_aps_price(slot.amznbid.as_ref().unwrap()).unwrap(); + assert!((price - 0.35).abs() < f64::EPSILON); + } } diff --git a/crates/mocktioneer-core/src/config.rs b/crates/mocktioneer-core/src/config.rs index fd34b5c..0cc206b 100644 --- a/crates/mocktioneer-core/src/config.rs +++ b/crates/mocktioneer-core/src/config.rs @@ -30,12 +30,12 @@ mod tests { #[test] fn accepts_positive_finite_cpm() { let cfg = MocktioneerConfig { bid_cpm: 0.20 }; - assert!(cfg.validate().is_ok()); + cfg.validate().unwrap(); } #[test] fn rejects_zero_negative_and_non_finite() { - for bad in [0.0_f64, -1.0, f64::NAN, f64::INFINITY] { + for bad in [0.0_f64, -1.0_f64, f64::NAN, f64::INFINITY] { let cfg = MocktioneerConfig { bid_cpm: bad }; assert!(cfg.validate().is_err(), "expected {bad} to be rejected"); } diff --git a/crates/mocktioneer-core/src/routes.rs b/crates/mocktioneer-core/src/routes.rs index 3f1a27e..cc56fdc 100644 --- a/crates/mocktioneer-core/src/routes.rs +++ b/crates/mocktioneer-core/src/routes.rs @@ -21,7 +21,7 @@ use validator::{Validate, ValidationError}; use crate::aps::ApsBidRequest; use crate::auction::{ - build_aps_response, build_openrtb_response, is_standard_size, standard_sizes, + build_aps_response, build_openrtb_response, is_standard_size, standard_sizes, FIXED_BID_CPM, }; use crate::mediation::{mediate_auction, MediationRequest}; use crate::openrtb::OpenRTBRequest; @@ -311,6 +311,34 @@ pub async fn handle_root(ForwardedHost(host): ForwardedHost) -> Result) -> Result { + match found { + None => Ok(FIXED_BID_CPM), + Some(raw) => raw + .parse::() + .ok() + .filter(|value| value.is_finite() && *value > 0.0_f64) + .ok_or_else(|| { + EdgeError::internal(anyhow::anyhow!( + "config store `mocktioneer_config` has malformed bid_cpm: {raw:?}" + )) + }), + } +} + +/// Resolve the effective bid CPM from the bound default config store, falling +/// back to `FIXED_BID_CPM` when no store is bound. A backend read error +/// propagates via `From`. +async fn resolve_bid_cpm(ctx: &RequestContext) -> Result { + let Some(store) = ctx.config_store_default() else { + return Ok(FIXED_BID_CPM); + }; + cpm_from_lookup(store.get("bid_cpm").await.map_err(EdgeError::from)?) +} + #[action] pub async fn handle_openrtb_auction( RequestContext(ctx): RequestContext, @@ -341,8 +369,9 @@ pub async fn handle_openrtb_auction( log::info!("auction id={}, imps={}", req.id, req.imp.len()); + let cpm = resolve_bid_cpm(&ctx).await?; // Build response with embedded metadata (signature status + request + response preview) - let resp = build_openrtb_response(&req, &host, signature_status); + let resp = build_openrtb_response(&req, &host, signature_status, cpm); let body = Body::json(&resp).map_err(|err| { log::error!("Failed to serialize OpenRTB response: {err}"); EdgeError::internal(err) @@ -468,6 +497,7 @@ pub async fn handle_pixel( #[action] pub async fn handle_aps_bid( + RequestContext(ctx): RequestContext, ForwardedHost(host): ForwardedHost, ValidatedJson(req): ValidatedJson, ) -> Result { @@ -477,7 +507,8 @@ pub async fn handle_aps_bid( req.slots.len() ); - let resp = build_aps_response(&req, &host); + let cpm = resolve_bid_cpm(&ctx).await?; + let resp = build_aps_response(&req, &host, cpm); let body = Body::json(&resp).map_err(|err| { log::error!("Failed to serialize APS response: {err}"); EdgeError::internal(err) @@ -996,13 +1027,28 @@ fn sanitize_for_log(input: &str, max_len: usize) -> String { mod tests { use super::*; use edgezero_core::body::Body; + use edgezero_core::config_store::{ConfigStore, ConfigStoreError, ConfigStoreHandle}; use edgezero_core::context::RequestContext; use edgezero_core::error::EdgeError; use edgezero_core::http::{request_builder, Method, Response, StatusCode}; use edgezero_core::params::PathParams; use edgezero_core::response::IntoResponse as _; + use edgezero_core::store_registry::{ConfigRegistry, StoreRegistry}; use futures::executor::block_on; - use std::collections::HashMap; + use std::collections::{BTreeMap, HashMap}; + use std::sync::Arc; + + /// In-memory `ConfigStore` for tests (mirrors app-demo's `MapConfigStore`). + struct MapConfigStore(HashMap); + + // `ConfigStore` is declared `#[async_trait(?Send)]` in edgezero-core, so + // the impl MUST use the same `(?Send)` mode or method signatures won't match. + #[async_trait(?Send)] + impl ConfigStore for MapConfigStore { + async fn get(&self, key: &str) -> Result, ConfigStoreError> { + Ok(self.0.get(key).cloned()) + } + } fn response_from(result: Result) -> Response { match result { @@ -1030,6 +1076,72 @@ mod tests { RequestContext::new(request, PathParams::new(map)) } + // ---- bid_cpm resolution ---- + + #[test] + fn cpm_from_lookup_falls_back_when_absent() { + assert_eq!(cpm_from_lookup(None).unwrap().to_bits(), FIXED_BID_CPM.to_bits()); + } + + #[test] + fn cpm_from_lookup_parses_valid_value() { + assert!((cpm_from_lookup(Some("0.35".to_owned())).unwrap() - 0.35).abs() < f64::EPSILON); + } + + #[test] + fn cpm_from_lookup_rejects_bad_values() { + for bad in ["-1", "0", "abc", "inf", "NaN", ""] { + assert!( + cpm_from_lookup(Some(bad.to_owned())).is_err(), + "expected {bad:?} to error" + ); + } + } + + fn ctx_with_config(pairs: &[(&str, &str)]) -> RequestContext { + let map: HashMap = pairs + .iter() + .map(|(key, value)| ((*key).to_owned(), (*value).to_owned())) + .collect(); + let handle = ConfigStoreHandle::new(Arc::new(MapConfigStore(map))); + let by_id: BTreeMap = + [("mocktioneer_config".to_owned(), handle)].into_iter().collect(); + let registry: ConfigRegistry = StoreRegistry::new(by_id, "mocktioneer_config".to_owned()); + + let mut request = request_builder() + .method(Method::GET) + .uri("/") + .body(Body::empty()) + .expect("request"); + request.extensions_mut().insert(registry); + RequestContext::new(request, PathParams::new(HashMap::new())) + } + + #[test] + fn resolve_bid_cpm_reads_seeded_store() { + let ctx = ctx_with_config(&[("bid_cpm", "0.35")]); + let cpm = block_on(resolve_bid_cpm(&ctx)).unwrap(); + assert!((cpm - 0.35).abs() < f64::EPSILON); + } + + #[test] + fn resolve_bid_cpm_falls_back_without_registry() { + let request = request_builder() + .method(Method::GET) + .uri("/") + .body(Body::empty()) + .expect("request"); + let ctx = RequestContext::new(request, PathParams::new(HashMap::new())); + let cpm = block_on(resolve_bid_cpm(&ctx)).unwrap(); + assert_eq!(cpm.to_bits(), FIXED_BID_CPM.to_bits()); + } + + #[test] + fn resolve_bid_cpm_errors_on_malformed_value() { + let ctx = ctx_with_config(&[("bid_cpm", "-1")]); + block_on(resolve_bid_cpm(&ctx)).unwrap_err(); + } + #[test] fn parse_size_param_parses_suffix() { assert_eq!(parse_size_param("300x250.svg", ".svg"), Some((300, 250))); diff --git a/crates/mocktioneer-core/tests/aps_endpoints.rs b/crates/mocktioneer-core/tests/aps_endpoints.rs index 0aeff48..2e2ad95 100644 --- a/crates/mocktioneer-core/tests/aps_endpoints.rs +++ b/crates/mocktioneer-core/tests/aps_endpoints.rs @@ -1,9 +1,9 @@ use mocktioneer_core::aps::{ApsBidRequest, ApsSlot}; -use mocktioneer_core::auction::build_aps_response; +use mocktioneer_core::auction::{build_aps_response, FIXED_BID_CPM}; #[cfg(test)] mod tests { - use super::build_aps_response; + use super::{build_aps_response, FIXED_BID_CPM}; use super::{ApsBidRequest, ApsSlot}; #[test] @@ -20,7 +20,7 @@ mod tests { timeout: Some(800), }; - let resp = build_aps_response(&req, "mocktioneer.test"); + let resp = build_aps_response(&req, "mocktioneer.test", FIXED_BID_CPM); assert_eq!(resp.contextual.status, Some("ok".to_owned())); assert_eq!(resp.contextual.slots.len(), 1); @@ -73,7 +73,7 @@ mod tests { timeout: None, }; - let resp = build_aps_response(&req, "mocktioneer.test"); + let resp = build_aps_response(&req, "mocktioneer.test", FIXED_BID_CPM); assert_eq!(resp.contextual.slots.len(), 3); assert_eq!(resp.contextual.slots[0].slot_id, "header"); @@ -103,7 +103,7 @@ mod tests { timeout: None, }; - let resp = build_aps_response(&req, "mocktioneer.test"); + let resp = build_aps_response(&req, "mocktioneer.test", FIXED_BID_CPM); assert_eq!(resp.contextual.slots.len(), 0); } @@ -129,7 +129,7 @@ mod tests { timeout: None, }; - let resp = build_aps_response(&req, "mocktioneer.test"); + let resp = build_aps_response(&req, "mocktioneer.test", FIXED_BID_CPM); assert_eq!(resp.contextual.slots.len(), 1); assert_eq!(resp.contextual.slots[0].slot_id, "standard"); @@ -149,7 +149,7 @@ mod tests { timeout: None, }; - let resp = build_aps_response(&req, "test.host"); + let resp = build_aps_response(&req, "test.host", FIXED_BID_CPM); assert_eq!(resp.contextual.slots.len(), 1); let slot = &resp.contextual.slots[0]; @@ -185,7 +185,7 @@ mod tests { timeout: None, }; - let resp = build_aps_response(&req, "mocktioneer.test"); + let resp = build_aps_response(&req, "mocktioneer.test", FIXED_BID_CPM); assert_eq!(resp.contextual.slots.len(), 1); assert_eq!(resp.contextual.slots[0].size, "970x250"); @@ -205,7 +205,7 @@ mod tests { timeout: None, }; - let resp = build_aps_response(&req, "mocktioneer.test"); + let resp = build_aps_response(&req, "mocktioneer.test", FIXED_BID_CPM); assert_eq!(resp.contextual.slots.len(), 1); assert_eq!(resp.contextual.slots[0].size, "300x250"); @@ -225,7 +225,7 @@ mod tests { timeout: None, }; - let resp = build_aps_response(&req, "mocktioneer.test"); + let resp = build_aps_response(&req, "mocktioneer.test", FIXED_BID_CPM); assert_eq!(resp.contextual.slots.len(), 1); assert_eq!(resp.contextual.slots[0].size, "970x250"); @@ -245,7 +245,7 @@ mod tests { timeout: None, }; - let resp = build_aps_response(&req, "mocktioneer.test"); + let resp = build_aps_response(&req, "mocktioneer.test", FIXED_BID_CPM); assert_eq!(resp.contextual.slots.len(), 0); } @@ -260,7 +260,7 @@ mod tests { timeout: None, }; - let resp = build_aps_response(&req, "mocktioneer.test"); + let resp = build_aps_response(&req, "mocktioneer.test", FIXED_BID_CPM); assert_eq!(resp.contextual.slots.len(), 0); assert_eq!(resp.contextual.status, Some("ok".to_owned())); @@ -280,7 +280,7 @@ mod tests { timeout: None, }; - let resp = build_aps_response(&req, "mocktioneer.test"); + let resp = build_aps_response(&req, "mocktioneer.test", FIXED_BID_CPM); let contextual = &resp.contextual; assert_eq!(contextual.status, Some("ok".to_owned())); @@ -313,7 +313,7 @@ mod tests { timeout: None, }; - let resp = build_aps_response(&req, "mocktioneer.test"); + let resp = build_aps_response(&req, "mocktioneer.test", FIXED_BID_CPM); assert_eq!(resp.contextual.slots.len(), 1); } From bd26219ba1418f1ab3adbd2e1ba321936f964974 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Mon, 15 Jun 2026 23:36:09 -0700 Subject: [PATCH 16/45] feat: add mocktioneer.toml typed config (bid_cpm default 0.20) --- mocktioneer.toml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 mocktioneer.toml diff --git a/mocktioneer.toml b/mocktioneer.toml new file mode 100644 index 0000000..59bf59d --- /dev/null +++ b/mocktioneer.toml @@ -0,0 +1,4 @@ +# Typed application config for Mocktioneer (maps 1:1 onto MocktioneerConfig). +# `bid_cpm` is the default; override per-environment via `config push` or the +# `MOCKTIONEER__BID_CPM` env overlay. Default keeps historical $0.20 behavior. +bid_cpm = 0.20 From 14fa94b5f55dacd2c6ad7abfe48d481923fe8d70 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Mon, 15 Jun 2026 23:39:56 -0700 Subject: [PATCH 17/45] feat: add mocktioneer-cli with typed config validate/push --- Cargo.lock | 351 ++++++++++++++++++++++++++++- Cargo.toml | 1 + crates/mocktioneer-cli/Cargo.toml | 15 ++ crates/mocktioneer-cli/src/main.rs | 72 ++++++ 4 files changed, 436 insertions(+), 3 deletions(-) create mode 100644 crates/mocktioneer-cli/Cargo.toml create mode 100644 crates/mocktioneer-cli/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 13c036d..343b0a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,18 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.4" @@ -41,6 +53,56 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + [[package]] name = "anyhow" version = "1.0.102" @@ -296,6 +358,46 @@ dependencies = [ "windows-link", ] +[[package]] +name = "clap" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + [[package]] name = "cmake" version = "0.1.58" @@ -305,6 +407,12 @@ dependencies = [ "cc", ] +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + [[package]] name = "colored" version = "3.1.1" @@ -392,6 +500,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "ctor" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01334b89b69ff726750c5ce5073fc8bd860e99aa9a8fc5ca11b04730e3aee97a" +dependencies = [ + "link-section", + "linktime-proc-macro", +] + [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -571,6 +689,14 @@ dependencies = [ "zeroize", ] +[[package]] +name = "edgezero-adapter" +version = "0.1.0" +source = "git+https://github.com/stackpop/edgezero.git?branch=feature%2Fextensible-cli#2eeccc9748daba92b9adf6afe4df105e79269ae9" +dependencies = [ + "toml", +] + [[package]] name = "edgezero-adapter-axum" version = "0.1.0" @@ -580,6 +706,8 @@ dependencies = [ "async-trait", "axum", "bytes", + "ctor", + "edgezero-adapter", "edgezero-core", "futures", "futures-util", @@ -591,8 +719,10 @@ dependencies = [ "simple_logger", "thiserror 2.0.18", "tokio", + "toml", "tower", "tracing", + "walkdir", ] [[package]] @@ -604,12 +734,17 @@ dependencies = [ "async-trait", "brotli", "bytes", + "ctor", + "edgezero-adapter", "edgezero-core", "flate2", "futures", "futures-util", "log", "serde_json", + "tempfile", + "toml_edit", + "walkdir", "worker", ] @@ -624,6 +759,8 @@ dependencies = [ "brotli", "bytes", "chrono", + "ctor", + "edgezero-adapter", "edgezero-core", "fastly", "fern", @@ -632,7 +769,10 @@ dependencies = [ "futures-util", "log", "log-fastly", + "serde_json", "thiserror 2.0.18", + "toml_edit", + "walkdir", ] [[package]] @@ -644,16 +784,45 @@ dependencies = [ "async-trait", "brotli", "bytes", + "ctor", + "edgezero-adapter", "edgezero-core", "flate2", "futures", "futures-util", "log", + "rusqlite", "serde", "serde_json", "spin-sdk", "subtle", "thiserror 2.0.18", + "toml", + "toml_edit", + "walkdir", +] + +[[package]] +name = "edgezero-cli" +version = "0.1.0" +source = "git+https://github.com/stackpop/edgezero.git?branch=feature%2Fextensible-cli#2eeccc9748daba92b9adf6afe4df105e79269ae9" +dependencies = [ + "clap", + "edgezero-adapter", + "edgezero-adapter-axum", + "edgezero-adapter-cloudflare", + "edgezero-adapter-fastly", + "edgezero-adapter-spin", + "edgezero-core", + "futures", + "handlebars", + "log", + "serde", + "serde_json", + "simple_logger", + "thiserror 2.0.18", + "toml", + "validator", ] [[package]] @@ -730,6 +899,18 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + [[package]] name = "fastly" version = "0.12.1" @@ -792,6 +973,12 @@ dependencies = [ "wit-bindgen 0.51.0", ] +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + [[package]] name = "fern" version = "0.7.1" @@ -1010,6 +1197,15 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", +] + [[package]] name = "hashbrown" version = "0.15.5" @@ -1028,6 +1224,15 @@ dependencies = [ "foldhash 0.2.0", ] +[[package]] +name = "hashlink" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +dependencies = [ + "hashbrown 0.14.5", +] + [[package]] name = "heck" version = "0.5.0" @@ -1295,6 +1500,12 @@ version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itertools" version = "0.13.0" @@ -1405,6 +1616,35 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" +[[package]] +name = "libsqlite3-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "link-section" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2b1dd6fe32e55c0fc0ea9493aa57459ca3cf4ff3c857c7d0302290150da6e4f" + +[[package]] +name = "linktime-proc-macro" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c7b0a3383c2a1002d11349c92c85a666a5fb679e96c79d782cf0dbe557fd6ee" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + [[package]] name = "litemap" version = "0.8.2" @@ -1547,6 +1787,16 @@ dependencies = [ "spin-sdk", ] +[[package]] +name = "mocktioneer-cli" +version = "0.1.0" +dependencies = [ + "clap", + "edgezero-cli", + "log", + "mocktioneer-core", +] + [[package]] name = "mocktioneer-core" version = "0.1.0" @@ -1627,6 +1877,12 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + [[package]] name = "oorandom" version = "11.1.5" @@ -1730,6 +1986,12 @@ dependencies = [ "spki", ] +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + [[package]] name = "potential_utf" version = "0.1.5" @@ -2001,6 +2263,20 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rusqlite" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e" +dependencies = [ + "bitflags 2.12.1", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + [[package]] name = "rustc-hash" version = "2.1.2" @@ -2016,6 +2292,19 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.12.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + [[package]] name = "rustls" version = "0.23.40" @@ -2471,6 +2760,19 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -2615,10 +2917,19 @@ dependencies = [ "indexmap", "serde_core", "serde_spanned", - "toml_datetime", + "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", "toml_writer", - "winnow", + "winnow 1.0.3", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", ] [[package]] @@ -2630,13 +2941,26 @@ dependencies = [ "serde_core", ] +[[package]] +name = "toml_edit" +version = "0.23.10+spec-1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +dependencies = [ + "indexmap", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 0.7.15", +] + [[package]] name = "toml_parser" version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow", + "winnow 1.0.3", ] [[package]] @@ -2777,6 +3101,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" version = "1.23.2" @@ -2818,6 +3148,12 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" @@ -3308,6 +3644,15 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + [[package]] name = "winnow" version = "1.0.3" diff --git a/Cargo.toml b/Cargo.toml index 4632aa0..1ca1621 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "crates/mocktioneer-adapter-cloudflare", "crates/mocktioneer-adapter-fastly", "crates/mocktioneer-adapter-spin", + "crates/mocktioneer-cli", ] resolver = "2" diff --git a/crates/mocktioneer-cli/Cargo.toml b/crates/mocktioneer-cli/Cargo.toml new file mode 100644 index 0000000..f1d655f --- /dev/null +++ b/crates/mocktioneer-cli/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "mocktioneer-cli" +version = "0.1.0" +edition = "2021" +publish = false +license.workspace = true + +[dependencies] +mocktioneer-core = { workspace = true } +edgezero-cli = { workspace = true } +clap = { workspace = true } +log = { workspace = true } + +[lints] +workspace = true diff --git a/crates/mocktioneer-cli/src/main.rs b/crates/mocktioneer-cli/src/main.rs new file mode 100644 index 0000000..b282a97 --- /dev/null +++ b/crates/mocktioneer-cli/src/main.rs @@ -0,0 +1,72 @@ +//! Mocktioneer CLI — built on the `edgezero-cli` library. +//! +//! Reuses every built-in edgezero command and adds the **typed** `config` +//! arms parameterised over `MocktioneerConfig`, so `validator` rules run on +//! `config validate` / `config push`. + +use clap::{Parser, Subcommand}; +use edgezero_cli::args::{ + AuthArgs, BuildArgs, ConfigPushArgs, ConfigValidateArgs, DeployArgs, NewArgs, ProvisionArgs, + ServeArgs, +}; +use mocktioneer_core::config::MocktioneerConfig; + +#[derive(Parser, Debug)] +#[command(name = "mocktioneer-cli", about = "mocktioneer edge CLI")] +struct Args { + #[command(subcommand)] + cmd: Cmd, +} + +#[derive(Subcommand, Debug)] +enum Cmd { + /// Sign in / out / status against the adapter's native CLI. + Auth(AuthArgs), + /// Build the project for a target edge. + Build(BuildArgs), + /// Inspect or mutate the typed `mocktioneer.toml` app config. + #[command(subcommand)] + Config(MocktioneerConfigCmd), + /// Deploy to a target edge. + Deploy(DeployArgs), + /// Create a new EdgeZero app skeleton. + New(NewArgs), + /// Create the platform resources backing the declared store ids. + Provision(ProvisionArgs), + /// Run a local simulation (adapter-specific). + Serve(ServeArgs), +} + +/// Dispatches `validate`/`push` to the typed entry points over +/// `MocktioneerConfig`. +#[derive(Subcommand, Debug)] +enum MocktioneerConfigCmd { + /// Push `mocktioneer.toml` (flattened) to the adapter's config store. + Push(ConfigPushArgs), + /// Validate `edgezero.toml` + `mocktioneer.toml` against `MocktioneerConfig`. + Validate(ConfigValidateArgs), +} + +fn main() { + use std::process; + + edgezero_cli::init_cli_logger(); + let result = match Args::parse().cmd { + Cmd::Auth(args) => edgezero_cli::run_auth(&args), + Cmd::Build(args) => edgezero_cli::run_build(&args), + Cmd::Config(MocktioneerConfigCmd::Push(args)) => { + edgezero_cli::run_config_push_typed::(&args) + } + Cmd::Config(MocktioneerConfigCmd::Validate(args)) => { + edgezero_cli::run_config_validate_typed::(&args) + } + Cmd::Deploy(args) => edgezero_cli::run_deploy(&args), + Cmd::New(args) => edgezero_cli::run_new(&args), + Cmd::Provision(args) => edgezero_cli::run_provision(&args), + Cmd::Serve(args) => edgezero_cli::run_serve(&args), + }; + if let Err(err) = result { + log::error!("[mocktioneer] {err}"); + process::exit(1); + } +} From 86e731523651198bed176322067394e0b7921dc6 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Mon, 15 Jun 2026 23:40:39 -0700 Subject: [PATCH 18/45] build: copy missing adapter manifests before cargo fetch in Docker --- Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index 7a4847f..182ce31 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,6 +17,8 @@ COPY crates/mocktioneer-core/Cargo.toml crates/mocktioneer-core/Cargo.toml COPY crates/mocktioneer-adapter-axum/Cargo.toml crates/mocktioneer-adapter-axum/Cargo.toml COPY crates/mocktioneer-adapter-cloudflare/Cargo.toml crates/mocktioneer-adapter-cloudflare/Cargo.toml COPY crates/mocktioneer-adapter-fastly/Cargo.toml crates/mocktioneer-adapter-fastly/Cargo.toml +COPY crates/mocktioneer-adapter-spin/Cargo.toml crates/mocktioneer-adapter-spin/Cargo.toml +COPY crates/mocktioneer-cli/Cargo.toml crates/mocktioneer-cli/Cargo.toml COPY crates ./crates COPY edgezero.toml ./edgezero.toml From b8733e06cc84ad08594d632da6b382c0688d3da5 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Mon, 15 Jun 2026 23:41:00 -0700 Subject: [PATCH 19/45] chore: gitignore .edgezero/ (local config/kv state) --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 711cde0..e0f7f2f 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,6 @@ test-results/ # Spin .spin/ + +# EdgeZero local config/kv state (config push --adapter axum, etc.) +.edgezero/ From 6cf54f9c7014802fbafdd46173171d3a21e9036d Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Mon, 15 Jun 2026 23:54:59 -0700 Subject: [PATCH 20/45] docs: spin wasip2, bid_cpm-default pricing, edgezero-cli vs mocktioneer-cli, config gate --- .claude/commands/check-ci.md | 1 + CLAUDE.md | 9 +++++---- README.md | 9 +++++++-- docs/api/index.md | 2 +- docs/api/openrtb-auction.md | 2 +- docs/guide/adapters/axum.md | 4 +++- docs/guide/adapters/cloudflare.md | 2 +- docs/guide/adapters/fastly.md | 2 +- docs/guide/adapters/index.md | 18 ++++++++++++++++++ docs/guide/architecture.md | 2 +- docs/guide/getting-started.md | 8 ++++++++ docs/guide/what-is-mocktioneer.md | 16 ++++++++-------- docs/integrations/index.md | 4 ++-- docs/integrations/prebid-server.md | 4 ++-- docs/integrations/prebidjs.md | 2 +- tests/playwright/README.md | 8 +++----- tests/playwright/playwright.config.ts | 3 ++- 17 files changed, 65 insertions(+), 31 deletions(-) diff --git a/.claude/commands/check-ci.md b/.claude/commands/check-ci.md index 918c0ea..53701a4 100644 --- a/.claude/commands/check-ci.md +++ b/.claude/commands/check-ci.md @@ -4,5 +4,6 @@ Run the full CI gate checks locally before pushing. Run all commands sequentiall 2. `cargo clippy --workspace --all-targets --all-features -- -D warnings` 3. `cargo test --workspace --all-targets` 4. `cargo check --workspace --all-targets --features "fastly cloudflare"` +5. `cargo run -p mocktioneer-cli -- config validate --strict` If any step fails, show the errors and suggest fixes. Do not proceed to the next step until the current one passes. diff --git a/CLAUDE.md b/CLAUDE.md index f2af53c..5ff8c2a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -18,7 +18,7 @@ crates/ mocktioneer-adapter-axum/ # Native Axum HTTP server mocktioneer-adapter-cloudflare/ # Cloudflare Workers bridge (wasm32-unknown-unknown) mocktioneer-adapter-fastly/ # Fastly Compute bridge (wasm32-wasip1) - mocktioneer-adapter-spin/ # Spin / Fermyon bridge (wasm32-wasip1) + mocktioneer-adapter-spin/ # Spin / Fermyon bridge (wasm32-wasip2) docs/ # VitePress documentation site (Node.js) examples/ # curl/shell scripts for endpoint demos tests/playwright/ # Playwright e2e tests (creative visibility, sizes) @@ -70,7 +70,7 @@ faster iteration since nearly all business logic lives there. | ---------- | ------------------------ | ---------------------------------------------------- | | Fastly | `wasm32-wasip1` | Requires Viceroy for local testing | | Cloudflare | `wasm32-unknown-unknown` | Requires `wrangler` for dev/deploy | -| Spin | `wasm32-wasip1` | Requires `spin` for dev/deploy; tests via `wasmtime` | +| Spin | `wasm32-wasip2` | Requires `spin` for dev/deploy; tests via `wasmtime` | | Axum | Native (host triple) | Standard Tokio runtime | ## Coding Conventions @@ -165,8 +165,9 @@ Every PR must pass: 2. `cargo clippy --workspace --all-targets --all-features -- -D warnings` 3. `cargo test --workspace --all-targets` 4. `cargo check --workspace --all-targets --features "fastly cloudflare"` -5. Playwright e2e tests (`tests/playwright/`) -6. ESLint + Prettier on `docs/` +5. `cargo run -p mocktioneer-cli -- config validate --strict` +6. Playwright e2e tests (`tests/playwright/`) +7. ESLint + Prettier on `docs/` Docker image is built and pushed to `ghcr.io/stackpop/mocktioneer` on push to main and on releases. diff --git a/README.md b/README.md index 242c6a8..a197379 100644 --- a/README.md +++ b/README.md @@ -48,8 +48,13 @@ See the [full API reference](https://stackpop.github.io/mocktioneer/api/) for al ```bash cargo test # Run tests cargo run -p mocktioneer-adapter-axum # Local server (Axum) on :8787 -edgezero-cli serve --adapter cloudflare # Local server (Cloudflare) on :8787 -edgezero-cli serve --adapter fastly # Local server (Fastly) on :7676 + +# Serve other adapters via the in-repo CLI (or the external edgezero-cli): +cargo run -p mocktioneer-cli -- serve --adapter cloudflare # Cloudflare on :8787 +cargo run -p mocktioneer-cli -- serve --adapter fastly # Fastly on :7676 + +# Typed app config (validate / push bid_cpm): +cargo run -p mocktioneer-cli -- config validate --strict ``` ## License diff --git a/docs/api/index.md b/docs/api/index.md index 2608ea0..9df9cd2 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -66,7 +66,7 @@ All responses include CORS headers (`Access-Control-Allow-Origin: *`, etc.). See ## Supported Ad Sizes {#supported-sizes} -Mocktioneer supports these standard IAB sizes. All auction bids use a fixed price of `$0.20` CPM. +Mocktioneer supports these standard IAB sizes. All auction bids use a default price of `$0.20` CPM (configurable via `bid_cpm` in mocktioneer.toml). | Size | Name | | ------- | ----------------------------- | diff --git a/docs/api/openrtb-auction.md b/docs/api/openrtb-auction.md index 7401b21..33a6ffc 100644 --- a/docs/api/openrtb-auction.md +++ b/docs/api/openrtb-auction.md @@ -139,7 +139,7 @@ Size is determined in this order: ## Pricing -Mocktioneer returns a fixed bid price of `$0.20` CPM for auction responses. +Mocktioneer returns a default bid price of `$0.20` CPM for auction responses (configurable via `bid_cpm` in mocktioneer.toml). If `imp[].ext.mocktioneer.bid` is present, it is ignored. diff --git a/docs/guide/adapters/axum.md b/docs/guide/adapters/axum.md index b232234..e050238 100644 --- a/docs/guide/adapters/axum.md +++ b/docs/guide/adapters/axum.md @@ -19,10 +19,12 @@ cargo run -p mocktioneer-adapter-axum The server starts at `http://127.0.0.1:8787`. -## Using EdgeZero CLI +## Using the CLI ```bash edgezero-cli serve --adapter axum +# or, in-repo (no external install): +cargo run -p mocktioneer-cli -- serve --adapter axum ``` This executes the command defined in `edgezero.toml`: diff --git a/docs/guide/adapters/cloudflare.md b/docs/guide/adapters/cloudflare.md index 4c7df87..2f3c124 100644 --- a/docs/guide/adapters/cloudflare.md +++ b/docs/guide/adapters/cloudflare.md @@ -44,7 +44,7 @@ The Cloudflare adapter runs Mocktioneer on Cloudflare Workers, providing global Run locally using Wrangler's local mode: ```bash -# Using EdgeZero CLI +# Using the CLI (edgezero-cli, or in-repo `cargo run -p mocktioneer-cli --`) edgezero-cli serve --adapter cloudflare # Or directly diff --git a/docs/guide/adapters/fastly.md b/docs/guide/adapters/fastly.md index 35830e0..8e5e727 100644 --- a/docs/guide/adapters/fastly.md +++ b/docs/guide/adapters/fastly.md @@ -33,7 +33,7 @@ The Fastly adapter runs Mocktioneer on Fastly's Compute platform, providing glob Run locally using Fastly's Viceroy runtime: ```bash -# Using EdgeZero CLI +# Using the CLI (edgezero-cli, or in-repo `cargo run -p mocktioneer-cli --`) edgezero-cli serve --adapter fastly # Or directly diff --git a/docs/guide/adapters/index.md b/docs/guide/adapters/index.md index 1b83e60..38849c5 100644 --- a/docs/guide/adapters/index.md +++ b/docs/guide/adapters/index.md @@ -87,6 +87,24 @@ edgezero-cli build --adapter fastly The CLI reads `edgezero.toml` and executes the appropriate commands for each adapter. If you don't have `edgezero-cli`, use the direct adapter commands on the pages below. +### In-repo `mocktioneer-cli` + +This repository also vendors a thin `mocktioneer-cli` built on the EdgeZero CLI +library — no separate install needed: + +```bash +# serve / build / deploy (same as edgezero-cli) +cargo run -p mocktioneer-cli -- serve --adapter cloudflare + +# typed config (lives only here — validated against MocktioneerConfig) +cargo run -p mocktioneer-cli -- config validate --strict +cargo run -p mocktioneer-cli -- config push --adapter axum +``` + +`serve`/`build`/`deploy`/`auth`/`provision` work from either the external +`edgezero-cli` or `mocktioneer-cli`; the typed `config validate` / `config push` +commands are only in `mocktioneer-cli`. + ## Common Configuration All adapters read from the same `edgezero.toml`: diff --git a/docs/guide/architecture.md b/docs/guide/architecture.md index c2e9043..795f43d 100644 --- a/docs/guide/architecture.md +++ b/docs/guide/architecture.md @@ -168,6 +168,6 @@ Template rendering: ## Supported Sizes -Mocktioneer supports 13 standard IAB ad sizes, each with a fixed CPM price. See the [full size list with pricing](/api/#supported-sizes) in the API reference. +Mocktioneer supports 13 standard IAB ad sizes, each with a default CPM price (configurable via `bid_cpm` in mocktioneer.toml). See the [full size list with pricing](/api/#supported-sizes) in the API reference. Non-standard sizes return 404 for static assets or are coerced to 300x250 for auction responses. Use the [`/_/sizes`](/api/#sizes-endpoint) endpoint to get the current list programmatically. diff --git a/docs/guide/getting-started.md b/docs/guide/getting-started.md index 19fbf71..b1c1ef0 100644 --- a/docs/guide/getting-started.md +++ b/docs/guide/getting-started.md @@ -38,6 +38,14 @@ Or run it from a local EdgeZero checkout: cargo run --manifest-path /path/to/edgezero/Cargo.toml -p edgezero-cli --features cli -- --help ``` +Alternatively, this repo vendors `mocktioneer-cli` (built on the EdgeZero CLI +library) — no external install needed, and it adds the typed config commands: + +```bash +cargo run -p mocktioneer-cli -- --help +cargo run -p mocktioneer-cli -- config validate --strict +``` + ## Running Locally ### Option 1: Native Axum Server (Recommended for Development) diff --git a/docs/guide/what-is-mocktioneer.md b/docs/guide/what-is-mocktioneer.md index a02c7f6..0d272d2 100644 --- a/docs/guide/what-is-mocktioneer.md +++ b/docs/guide/what-is-mocktioneer.md @@ -44,14 +44,14 @@ Mocktioneer provides: ## Key Features -| Feature | Description | -| --------------- | --------------------------------------------- | -| Multi-platform | Runs on Fastly, Cloudflare, and native Axum | -| Manifest-driven | Single `edgezero.toml` configures everything | -| Fixed pricing | Always returns `$0.20` CPM for generated bids | -| Standard sizes | Supports common IAB ad sizes | -| Cookie tracking | Optional pixel tracking with `mtkid` cookie | -| CORS enabled | Works with browser-based clients | +| Feature | Description | +| --------------- | ------------------------------------------------------------------------------------------------- | +| Multi-platform | Runs on Fastly, Cloudflare, and native Axum | +| Manifest-driven | Single `edgezero.toml` configures everything | +| Fixed pricing | Returns a default `$0.20` CPM for generated bids (configurable via `bid_cpm` in mocktioneer.toml) | +| Standard sizes | Supports common IAB ad sizes | +| Cookie tracking | Optional pixel tracking with `mtkid` cookie | +| CORS enabled | Works with browser-based clients | ## How It Works diff --git a/docs/integrations/index.md b/docs/integrations/index.md index fceb906..389dd8c 100644 --- a/docs/integrations/index.md +++ b/docs/integrations/index.md @@ -50,7 +50,7 @@ Mocktioneer acts as a drop-in replacement for real bidders during development an - Same request always produces same response - No flaky tests due to bidder variability -- Fixed $0.20 bids for predictable floor testing +- Default $0.20 bids (configurable via `bid_cpm` in mocktioneer.toml) for predictable floor testing ### No External Dependencies @@ -150,7 +150,7 @@ Test price handling and floor logic: } ``` -Mocktioneer always bids at a fixed `$0.20` CPM and does not evaluate `bidfloor` itself. Use this predictable price to test that your SSP or mediation layer correctly filters bids below the floor threshold. +Mocktioneer bids at a default `$0.20` (configurable via `bid_cpm` in mocktioneer.toml) CPM and does not evaluate `bidfloor` itself. Use this predictable price to test that your SSP or mediation layer correctly filters bids below the floor threshold. ### Identity Pipeline Testing diff --git a/docs/integrations/prebid-server.md b/docs/integrations/prebid-server.md index 73c3ccf..d0e84d4 100644 --- a/docs/integrations/prebid-server.md +++ b/docs/integrations/prebid-server.md @@ -91,7 +91,7 @@ Override the endpoint for specific requests: | ---------- | ------------------ | ------ | ------------------------- | | `endpoint` | `imp[].ext.bidder` | string | Override auction endpoint | -Mocktioneer always returns a fixed bid price of `$0.20` CPM. +Mocktioneer returns a default bid price of `$0.20` CPM (configurable via `bid_cpm` in mocktioneer.toml). ## Response Handling @@ -273,7 +273,7 @@ Include Mocktioneer alongside real bidders: ### Price Floor Testing -Test that your SSP enforces floors correctly (Mocktioneer always bids at a fixed `$0.20` and does not evaluate `bidfloor` itself): +Test that your SSP enforces floors correctly (Mocktioneer bids at a default `$0.20` (configurable via `bid_cpm` in mocktioneer.toml) and does not evaluate `bidfloor` itself): ```json { diff --git a/docs/integrations/prebidjs.md b/docs/integrations/prebidjs.md index 69d30cd..d1e84f4 100644 --- a/docs/integrations/prebidjs.md +++ b/docs/integrations/prebidjs.md @@ -74,7 +74,7 @@ params: { | ---------- | ------ | -------- | --------------------------- | | `endpoint` | string | No | Custom auction endpoint URL | -Mocktioneer always returns a fixed bid price of `$0.20` CPM. +Mocktioneer returns a default bid price of `$0.20` CPM (configurable via `bid_cpm` in mocktioneer.toml). ## Example Page diff --git a/tests/playwright/README.md b/tests/playwright/README.md index cd7a834..39137f1 100644 --- a/tests/playwright/README.md +++ b/tests/playwright/README.md @@ -9,11 +9,9 @@ npm install npx playwright install ``` -**Note:** The Cloudflare adapter requires `edgezero-cli` (not `edgezero`): - -```bash -cargo install --git https://github.com/stackpop/edgezero.git edgezero-cli -``` +**Note:** The Cloudflare web server is launched via the in-repo +`mocktioneer-cli` (`cargo run -p mocktioneer-cli -- serve --adapter cloudflare`), +so no external `edgezero-cli` install is required. ## Running Tests diff --git a/tests/playwright/playwright.config.ts b/tests/playwright/playwright.config.ts index aecfb1f..a7b0140 100644 --- a/tests/playwright/playwright.config.ts +++ b/tests/playwright/playwright.config.ts @@ -8,7 +8,8 @@ const adapter = process.env.ADAPTER || 'axum'; const webServerCommands: Record = { axum: 'cargo run -p mocktioneer-adapter-axum', - cloudflare: 'edgezero-cli serve --adapter cloudflare', + // In-repo CLI — no external `edgezero-cli` install needed. + cloudflare: 'cargo run -p mocktioneer-cli -- serve --adapter cloudflare', }; export default defineConfig({ From ea5d9966db62ea59cf9def2e12a62cd58407167d Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Mon, 15 Jun 2026 23:57:16 -0700 Subject: [PATCH 21/45] ci: spin wasip2 matrix + typed config validate/seed gate --- .github/workflows/test.yml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7bee130..8c40198 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -52,6 +52,16 @@ jobs: - name: Check feature compilation run: cargo check --workspace --all-targets --features "fastly cloudflare" + - name: Validate typed app config + run: cargo run -p mocktioneer-cli -- config validate --strict + + - name: Seed a non-default cpm and assert it round-trips (axum) + run: | + printf 'bid_cpm = 0.35\n' > "${RUNNER_TEMP}/seed.toml" + cargo run -p mocktioneer-cli -- config push --adapter axum --app-config "${RUNNER_TEMP}/seed.toml" + test "$(jq -r '.bid_cpm' .edgezero/local-config-mocktioneer_config.json)" = "0.35" + rm -f .edgezero/local-config-mocktioneer_config.json + adapter-wasm-tests: name: ${{ matrix.adapter }} wasm tests runs-on: ubuntu-latest @@ -68,8 +78,8 @@ jobs: runner_env: CARGO_TARGET_WASM32_WASIP1_RUNNER runner_value: viceroy run - adapter: spin - target: wasm32-wasip1 - runner_env: CARGO_TARGET_WASM32_WASIP1_RUNNER + target: wasm32-wasip2 + runner_env: CARGO_TARGET_WASM32_WASIP2_RUNNER runner_value: wasmtime run steps: - uses: actions/checkout@v6 From a3c26344fb46545e3eeaa0bf9f42276932b95216 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Tue, 16 Jun 2026 00:03:11 -0700 Subject: [PATCH 22/45] feat: drop 'new' subcommand from mocktioneer-cli (nonsensical for a project CLI) --- crates/mocktioneer-cli/src/main.rs | 6 +----- crates/mocktioneer-core/src/routes.rs | 9 +++++++-- .../2026-06-15-edgezero-extensible-cli-adaptation.md | 6 +----- ...26-06-11-edgezero-extensible-cli-adaptation-design.md | 7 +++++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/crates/mocktioneer-cli/src/main.rs b/crates/mocktioneer-cli/src/main.rs index b282a97..a30285d 100644 --- a/crates/mocktioneer-cli/src/main.rs +++ b/crates/mocktioneer-cli/src/main.rs @@ -6,8 +6,7 @@ use clap::{Parser, Subcommand}; use edgezero_cli::args::{ - AuthArgs, BuildArgs, ConfigPushArgs, ConfigValidateArgs, DeployArgs, NewArgs, ProvisionArgs, - ServeArgs, + AuthArgs, BuildArgs, ConfigPushArgs, ConfigValidateArgs, DeployArgs, ProvisionArgs, ServeArgs, }; use mocktioneer_core::config::MocktioneerConfig; @@ -29,8 +28,6 @@ enum Cmd { Config(MocktioneerConfigCmd), /// Deploy to a target edge. Deploy(DeployArgs), - /// Create a new EdgeZero app skeleton. - New(NewArgs), /// Create the platform resources backing the declared store ids. Provision(ProvisionArgs), /// Run a local simulation (adapter-specific). @@ -61,7 +58,6 @@ fn main() { edgezero_cli::run_config_validate_typed::(&args) } Cmd::Deploy(args) => edgezero_cli::run_deploy(&args), - Cmd::New(args) => edgezero_cli::run_new(&args), Cmd::Provision(args) => edgezero_cli::run_provision(&args), Cmd::Serve(args) => edgezero_cli::run_serve(&args), }; diff --git a/crates/mocktioneer-core/src/routes.rs b/crates/mocktioneer-core/src/routes.rs index cc56fdc..5db27c5 100644 --- a/crates/mocktioneer-core/src/routes.rs +++ b/crates/mocktioneer-core/src/routes.rs @@ -1080,7 +1080,10 @@ mod tests { #[test] fn cpm_from_lookup_falls_back_when_absent() { - assert_eq!(cpm_from_lookup(None).unwrap().to_bits(), FIXED_BID_CPM.to_bits()); + assert_eq!( + cpm_from_lookup(None).unwrap().to_bits(), + FIXED_BID_CPM.to_bits() + ); } #[test] @@ -1105,7 +1108,9 @@ mod tests { .collect(); let handle = ConfigStoreHandle::new(Arc::new(MapConfigStore(map))); let by_id: BTreeMap = - [("mocktioneer_config".to_owned(), handle)].into_iter().collect(); + [("mocktioneer_config".to_owned(), handle)] + .into_iter() + .collect(); let registry: ConfigRegistry = StoreRegistry::new(by_id, "mocktioneer_config".to_owned()); let mut request = request_builder() diff --git a/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md b/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md index a9a3323..8f33f7f 100644 --- a/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md +++ b/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md @@ -813,8 +813,7 @@ Instantiates edgezero's `-cli` template (name=`mocktioneer`, struct=`Mockt use clap::{Parser, Subcommand}; use edgezero_cli::args::{ - AuthArgs, BuildArgs, ConfigPushArgs, ConfigValidateArgs, DeployArgs, NewArgs, ProvisionArgs, - ServeArgs, + AuthArgs, BuildArgs, ConfigPushArgs, ConfigValidateArgs, DeployArgs, ProvisionArgs, ServeArgs, }; use mocktioneer_core::config::MocktioneerConfig; @@ -836,8 +835,6 @@ enum Cmd { Config(MocktioneerConfigCmd), /// Deploy to a target edge. Deploy(DeployArgs), - /// Create a new EdgeZero app skeleton. - New(NewArgs), /// Create the platform resources backing the declared store ids. Provision(ProvisionArgs), /// Run a local simulation (adapter-specific). @@ -868,7 +865,6 @@ fn main() { edgezero_cli::run_config_validate_typed::(&args) } Cmd::Deploy(args) => edgezero_cli::run_deploy(&args), - Cmd::New(args) => edgezero_cli::run_new(&args), Cmd::Provision(args) => edgezero_cli::run_provision(&args), Cmd::Serve(args) => edgezero_cli::run_serve(&args), }; diff --git a/docs/superpowers/specs/2026-06-11-edgezero-extensible-cli-adaptation-design.md b/docs/superpowers/specs/2026-06-11-edgezero-extensible-cli-adaptation-design.md index 434ff7a..8281e64 100644 --- a/docs/superpowers/specs/2026-06-11-edgezero-extensible-cli-adaptation-design.md +++ b/docs/superpowers/specs/2026-06-11-edgezero-extensible-cli-adaptation-design.md @@ -37,7 +37,10 @@ cache hygiene (+ add the missing spin manifest); CLI-story widened to the per-adapter doc pages; docs `srcExclude` made verifiable + `.vitepress/.temp` ignored; plan call-site note corrected (mediation has a _separate_ - `build_openrtb_response`). + `build_openrtb_response`). **R7 (during implementation)** — dropped the `new` + subcommand from `mocktioneer-cli`: scaffolding a brand-new EdgeZero app from + within Mocktioneer's own project CLI is nonsensical, so the crate exposes + `auth`/`build`/`deploy`/`provision`/`serve` + typed `config` only. ## 1. Problem & context @@ -295,7 +298,7 @@ and malformed-value (error) branches without a live backend. crates:** `publish = false`, `license.workspace = true`, and `[lints] workspace = true` (cf. `mocktioneer-core/Cargo.toml`). - `src/main.rs`: clap `Args`/`Cmd` flattening - `edgezero_cli::run_{auth,build,deploy,new,provision,serve}` + a typed + `edgezero_cli::run_{auth,build,deploy,provision,serve}` + a typed `Config` subcommand dispatching `run_config_validate_typed::` and `run_config_push_typed::`. - Add `crates/mocktioneer-cli` to root `[workspace].members`. From 697d7e34bed3ae4e0a82db53556cdb24ce48d2ae Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Tue, 16 Jun 2026 08:02:05 -0700 Subject: [PATCH 23/45] test: use FastlyService/CloudflareService::dispatch in contract tests (edgezero #269 removed request::dispatch) --- .../tests/contract.rs | 16 +++++++++------- .../mocktioneer-adapter-fastly/tests/contract.rs | 10 +++------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/crates/mocktioneer-adapter-cloudflare/tests/contract.rs b/crates/mocktioneer-adapter-cloudflare/tests/contract.rs index f6f709b..0c9e86b 100644 --- a/crates/mocktioneer-adapter-cloudflare/tests/contract.rs +++ b/crates/mocktioneer-adapter-cloudflare/tests/contract.rs @@ -1,10 +1,6 @@ #![cfg(all(feature = "cloudflare", target_arch = "wasm32"))] -#![expect( - deprecated, - reason = "exercise the low-level dispatch path while it remains public" -)] -use edgezero_adapter_cloudflare::request::dispatch; +use edgezero_adapter_cloudflare::request::CloudflareService; use mocktioneer_core::build_app; use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure}; use worker::wasm_bindgen::JsCast as _; @@ -41,7 +37,10 @@ async fn root_dispatches_through_cloudflare_adapter() { let req = cf_request(CfMethod::Get, "/"); let (env, ctx) = test_env_ctx(); - let mut response = dispatch(&app, req, env, ctx).await.expect("cf response"); + let mut response = CloudflareService::new(&app) + .dispatch(req, env, ctx) + .await + .expect("cf response"); assert_eq!(response.status_code(), 200); let body = response.bytes().await.expect("body bytes"); @@ -59,7 +58,10 @@ async fn pixel_returns_gif_through_cloudflare_adapter() { let req = cf_request(CfMethod::Get, "/pixel?pid=cloudflare-contract"); let (env, ctx) = test_env_ctx(); - let response = dispatch(&app, req, env, ctx).await.expect("cf response"); + let response = CloudflareService::new(&app) + .dispatch(req, env, ctx) + .await + .expect("cf response"); assert_eq!(response.status_code(), 200); let content_type = response diff --git a/crates/mocktioneer-adapter-fastly/tests/contract.rs b/crates/mocktioneer-adapter-fastly/tests/contract.rs index ff34776..e5cca42 100644 --- a/crates/mocktioneer-adapter-fastly/tests/contract.rs +++ b/crates/mocktioneer-adapter-fastly/tests/contract.rs @@ -1,10 +1,6 @@ #![cfg(all(feature = "fastly", target_arch = "wasm32"))] -#![expect( - deprecated, - reason = "exercise the low-level dispatch path while it remains public" -)] -use edgezero_adapter_fastly::request::dispatch; +use edgezero_adapter_fastly::request::FastlyService; use fastly::http::{Method as FastlyMethod, StatusCode as FastlyStatus}; use fastly::Request as FastlyRequest; use mocktioneer_core::build_app; @@ -20,7 +16,7 @@ fn root_dispatches_through_fastly_adapter() { let app = build_app(); let req = fastly_request(FastlyMethod::GET, "/"); - let mut response = dispatch(&app, req).expect("fastly response"); + let mut response = FastlyService::new(&app).dispatch(req).expect("fastly response"); assert_eq!(response.get_status(), FastlyStatus::OK); let body = response.take_body_bytes(); @@ -37,7 +33,7 @@ fn pixel_returns_gif_through_fastly_adapter() { let app = build_app(); let req = fastly_request(FastlyMethod::GET, "/pixel?pid=fastly-contract"); - let response = dispatch(&app, req).expect("fastly response"); + let response = FastlyService::new(&app).dispatch(req).expect("fastly response"); assert_eq!(response.get_status(), FastlyStatus::OK); let content_type = response From b039c7e12046ee53f09c8bf8efcc67519b269f3b Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Tue, 16 Jun 2026 08:37:18 -0700 Subject: [PATCH 24/45] Formatting --- crates/mocktioneer-adapter-fastly/tests/contract.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/mocktioneer-adapter-fastly/tests/contract.rs b/crates/mocktioneer-adapter-fastly/tests/contract.rs index e5cca42..373d728 100644 --- a/crates/mocktioneer-adapter-fastly/tests/contract.rs +++ b/crates/mocktioneer-adapter-fastly/tests/contract.rs @@ -16,7 +16,9 @@ fn root_dispatches_through_fastly_adapter() { let app = build_app(); let req = fastly_request(FastlyMethod::GET, "/"); - let mut response = FastlyService::new(&app).dispatch(req).expect("fastly response"); + let mut response = FastlyService::new(&app) + .dispatch(req) + .expect("fastly response"); assert_eq!(response.get_status(), FastlyStatus::OK); let body = response.take_body_bytes(); @@ -33,7 +35,9 @@ fn pixel_returns_gif_through_fastly_adapter() { let app = build_app(); let req = fastly_request(FastlyMethod::GET, "/pixel?pid=fastly-contract"); - let response = FastlyService::new(&app).dispatch(req).expect("fastly response"); + let response = FastlyService::new(&app) + .dispatch(req) + .expect("fastly response"); assert_eq!(response.get_status(), FastlyStatus::OK); let content_type = response From 478c3a3461ff3e4167a0cd29fd82655a9da3a39c Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Tue, 16 Jun 2026 16:44:47 -0700 Subject: [PATCH 25/45] docs+ci: quote GITHUB_OUTPUT (actionlint), document stores.config/spin/typed-config, add spin to agent builds, fix stale pricing/cli comments --- .claude/agents/build-validator.md | 1 + .claude/agents/verify-app.md | 1 + .github/workflows/test.yml | 8 ++-- README.md | 1 + crates/mocktioneer-core/src/auction.rs | 5 +- docs/guide/configuration.md | 65 +++++++++++++++++++++++++- tests/playwright/README.md | 8 ++-- 7 files changed, 78 insertions(+), 11 deletions(-) diff --git a/.claude/agents/build-validator.md b/.claude/agents/build-validator.md index 6d95a02..570125f 100644 --- a/.claude/agents/build-validator.md +++ b/.claude/agents/build-validator.md @@ -15,6 +15,7 @@ cargo build --workspace --all-targets --all-features ``` cargo build -p mocktioneer-adapter-fastly --features fastly --target wasm32-wasip1 cargo build -p mocktioneer-adapter-cloudflare --features cloudflare --target wasm32-unknown-unknown +cargo build --release -p mocktioneer-adapter-spin --features spin --target wasm32-wasip2 ``` ## Feature matrix diff --git a/.claude/agents/verify-app.md b/.claude/agents/verify-app.md index eab52ac..524c588 100644 --- a/.claude/agents/verify-app.md +++ b/.claude/agents/verify-app.md @@ -26,6 +26,7 @@ Zero warnings required. Report any clippy lints or format violations. ``` cargo build -p mocktioneer-adapter-fastly --features fastly --target wasm32-wasip1 cargo build -p mocktioneer-adapter-cloudflare --features cloudflare --target wasm32-unknown-unknown +cargo build --release -p mocktioneer-adapter-spin --features spin --target wasm32-wasip2 ``` Both WASM targets must compile. Report any errors with the exact compiler output. diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8c40198..11ad47d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -35,7 +35,7 @@ jobs: - name: Retrieve Rust version id: rust-version - run: echo "rust-version=$(grep '^rust ' .tool-versions | awk '{print $2}')" >> $GITHUB_OUTPUT + run: echo "rust-version=$(grep '^rust ' .tool-versions | awk '{print $2}')" >> "$GITHUB_OUTPUT" shell: bash - name: Set up Rust tool chain @@ -99,7 +99,7 @@ jobs: - name: Retrieve Rust version id: rust-version - run: echo "rust-version=$(grep '^rust ' .tool-versions | awk '{print $2}')" >> $GITHUB_OUTPUT + run: echo "rust-version=$(grep '^rust ' .tool-versions | awk '{print $2}')" >> "$GITHUB_OUTPUT" shell: bash - name: Set up Rust tool chain @@ -193,8 +193,8 @@ jobs: - name: Retrieve tool versions id: tool-versions run: | - echo "rust=$(grep '^rust ' .tool-versions | awk '{print $2}')" >> $GITHUB_OUTPUT - echo "node=$(grep '^nodejs ' .tool-versions | awk '{print $2}')" >> $GITHUB_OUTPUT + echo "rust=$(grep '^rust ' .tool-versions | awk '{print $2}')" >> "$GITHUB_OUTPUT" + echo "node=$(grep '^nodejs ' .tool-versions | awk '{print $2}')" >> "$GITHUB_OUTPUT" shell: bash - name: Set up Rust tool chain diff --git a/README.md b/README.md index a197379..8a2006b 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ cargo run -p mocktioneer-cli -- serve --adapter fastly # Fastly on :7676 # Typed app config (validate / push bid_cpm): cargo run -p mocktioneer-cli -- config validate --strict +cargo run -p mocktioneer-cli -- config push --adapter axum ``` ## License diff --git a/crates/mocktioneer-core/src/auction.rs b/crates/mocktioneer-core/src/auction.rs index 2d34c65..a3bfe52 100644 --- a/crates/mocktioneer-core/src/auction.rs +++ b/crates/mocktioneer-core/src/auction.rs @@ -83,7 +83,8 @@ pub fn standard_or_default((width, height): (i64, i64)) -> (i64, i64) { /// Build an `OpenRTB` bid response for the given request. /// /// - Enforces standard ad sizes (non-standard sizes default to 300x250) -/// - Uses a fixed CPM price ($0.20) +/// - Prices every bid at the supplied `cpm` (callers pass the resolved config +/// value, defaulting to `FIXED_BID_CPM`) /// - Embeds signature verification status, the original request, and a preview /// of the response as HTML comments in each creative /// - The signature badge is rendered inside the creative via the `sig` query param @@ -221,7 +222,7 @@ pub fn decode_aps_price(encoded: &str) -> Option { /// Build APS TAM response from an APS bid request matching real Amazon API format. /// /// This function generates mock bids for all slots with standard sizes: -/// - Fixed bid price of $0.20 CPM +/// - Prices every slot at the supplied `cpm` (defaulting to `FIXED_BID_CPM`) /// - 100% fill rate for standard sizes /// - Returns contextual format matching real Amazon APS API /// - No creative HTML (APS doesn't return adm field) diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index 353aa76..fe95f4e 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -14,6 +14,9 @@ middleware = [ "edgezero_core::middleware::RequestLogger", "mocktioneer_core::routes::Cors" ] + +[stores.config] +ids = ["mocktioneer_config"] ``` ## App Section @@ -26,6 +29,22 @@ The `[app]` section defines the core application: | `entry` | Path to the core crate | | `middleware` | List of middleware to apply to all routes | +## Config Store + +The `[stores.config]` section declares the logical config store(s) backing the +typed app config (`mocktioneer.toml`): + +```toml +[stores.config] +ids = ["mocktioneer_config"] +``` + +The `mocktioneer_config` store holds `bid_cpm` (see the typed `MocktioneerConfig` +struct). Seed it per adapter with `mocktioneer-cli config push --adapter `; +handlers read it at runtime via `ctx.config_store_default()`, falling back to the +compile-time `FIXED_BID_CPM` default when no store is bound. See +[`mocktioneer.toml`](#typed-app-config) below. + ## HTTP Triggers Routes are defined as `[[triggers.http]]` blocks: @@ -36,7 +55,7 @@ id = "openrtb_auction" path = "/openrtb2/auction" methods = ["POST"] handler = "mocktioneer_core::routes::handle_openrtb_auction" -adapters = ["axum", "cloudflare", "fastly"] +adapters = ["axum", "cloudflare", "fastly", "spin"] ``` | Field | Description | @@ -137,6 +156,50 @@ level = "info" echo_stdout = true ``` +### Spin Adapter + +Spin targets `wasm32-wasip2` (spin-sdk 6). Its config store is KV-backed, so the +`serve`/`deploy` commands pass a `--runtime-config-file` declaring the KV label: + +```toml +[adapters.spin.adapter] +crate = "crates/mocktioneer-adapter-spin" +manifest = "crates/mocktioneer-adapter-spin/spin.toml" + +[adapters.spin.build] +target = "wasm32-wasip2" +profile = "release" +features = ["spin"] + +[adapters.spin.commands] +build = "spin build --from crates/mocktioneer-adapter-spin/spin.toml" +serve = "spin up --from crates/mocktioneer-adapter-spin/spin.toml --runtime-config-file crates/mocktioneer-adapter-spin/runtime-config.toml" +deploy = "spin deploy --from crates/mocktioneer-adapter-spin/spin.toml --runtime-config-file crates/mocktioneer-adapter-spin/runtime-config.toml" + +[adapters.spin.logging] +level = "info" +echo_stdout = true +``` + +## Typed App Config + +`mocktioneer.toml` (repo root) maps 1:1 onto the `MocktioneerConfig` struct — +there is no `[config]` wrapper: + +```toml +bid_cpm = 0.20 +``` + +Validate it against the typed contract and push it to an adapter's config store: + +```bash +cargo run -p mocktioneer-cli -- config validate --strict +cargo run -p mocktioneer-cli -- config push --adapter axum +``` + +Any key can be overridden at runtime via the `MOCKTIONEER__` env overlay +(e.g. `MOCKTIONEER__BID_CPM=0.35`). + ## Logging Configuration | Field | Description | diff --git a/tests/playwright/README.md b/tests/playwright/README.md index 39137f1..7a766ee 100644 --- a/tests/playwright/README.md +++ b/tests/playwright/README.md @@ -43,9 +43,9 @@ npx playwright show-report The `ADAPTER` environment variable controls which adapter is tested: -| Value | Command | Description | -| ---------------- | ----------------------------------------- | ------------------------- | -| `axum` (default) | `cargo run -p mocktioneer-adapter-axum` | Native Axum server | -| `cloudflare` | `edgezero-cli serve --adapter cloudflare` | Cloudflare Workers (WASM) | +| Value | Command | Description | +| ---------------- | ------------------------------------------------------ | ------------------------- | +| `axum` (default) | `cargo run -p mocktioneer-adapter-axum` | Native Axum server | +| `cloudflare` | `cargo run -p mocktioneer-cli -- serve --adapter cloudflare` | Cloudflare Workers (WASM) | Both adapters run on `http://127.0.0.1:8787`. From 24fc4a883e1a89986d36524d028cb78b147027af Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Tue, 16 Jun 2026 17:54:46 -0700 Subject: [PATCH 26/45] docs+ci: match config.md commands to manifest, quote GITHUB_OUTPUT repo-wide, update CLAUDE.md crate count/CPM --- .github/workflows/deploy-docs.yml | 4 ++-- .github/workflows/docker.yml | 4 ++-- .github/workflows/format.yml | 4 ++-- CLAUDE.md | 7 +++++-- docs/guide/configuration.md | 8 ++++---- 5 files changed, 15 insertions(+), 12 deletions(-) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index c34d0cc..6aaa883 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -32,9 +32,9 @@ jobs: id: node-version run: | if [ -f .tool-versions ]; then - echo "node-version=$(grep '^nodejs ' .tool-versions | awk '{print $2}')" >> $GITHUB_OUTPUT + echo "node-version=$(grep '^nodejs ' .tool-versions | awk '{print $2}')" >> "$GITHUB_OUTPUT" else - echo "node-version=20" >> $GITHUB_OUTPUT + echo "node-version=20" >> "$GITHUB_OUTPUT" fi - name: Setup Node.js diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index cffa3e7..0162923 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -44,11 +44,11 @@ jobs: - name: Get Rust version from .tool-versions id: rust - run: echo "version=$(grep '^rust' .tool-versions | awk '{print $2}')" >> $GITHUB_OUTPUT + run: echo "version=$(grep '^rust' .tool-versions | awk '{print $2}')" >> "$GITHUB_OUTPUT" - name: Get short SHA id: sha - run: echo "short=${GITHUB_SHA::7}" >> $GITHUB_OUTPUT + run: echo "short=${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT" - name: Log in to GHCR uses: docker/login-action@v3 diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 6360c63..8047458 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -34,7 +34,7 @@ jobs: - name: Retrieve Rust version id: rust-version - run: echo "rust-version=$(grep '^rust ' .tool-versions | awk '{print $2}')" >> $GITHUB_OUTPUT + run: echo "rust-version=$(grep '^rust ' .tool-versions | awk '{print $2}')" >> "$GITHUB_OUTPUT" shell: bash - name: Set up rust toolchain @@ -64,7 +64,7 @@ jobs: - name: Retrieve Node.js version id: node-version working-directory: . - run: echo "node-version=$(grep '^nodejs ' .tool-versions | awk '{print $2}')" >> $GITHUB_OUTPUT + run: echo "node-version=$(grep '^nodejs ' .tool-versions | awk '{print $2}')" >> "$GITHUB_OUTPUT" shell: bash - name: Use Node.js diff --git a/CLAUDE.md b/CLAUDE.md index 5ff8c2a..43d60b7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,7 +6,7 @@ Mocktioneer is a deterministic OpenRTB banner bidder for edge platforms. It lets you test client integrations (Prebid.js, Prebid Server, custom SDKs) without depending on third-party bidders or origin backends. Write once, deploy to Fastly Compute, Cloudflare Workers, or native Axum servers. The codebase is a -Cargo workspace with 4 crates under `crates/`, a VitePress documentation site +Cargo workspace with 6 crates under `crates/`, a VitePress documentation site under `docs/`, Playwright e2e tests under `tests/playwright/`, and CI workflows under `.github/workflows/`. @@ -19,6 +19,7 @@ crates/ mocktioneer-adapter-cloudflare/ # Cloudflare Workers bridge (wasm32-unknown-unknown) mocktioneer-adapter-fastly/ # Fastly Compute bridge (wasm32-wasip1) mocktioneer-adapter-spin/ # Spin / Fermyon bridge (wasm32-wasip2) + mocktioneer-cli/ # Custom CLI: edgezero commands + typed config validate/push docs/ # VitePress documentation site (Node.js) examples/ # curl/shell scripts for endpoint demos tests/playwright/ # Playwright e2e tests (creative visibility, sizes) @@ -154,7 +155,9 @@ through `render.rs`. Do not inline ad markup in handlers. ## Key Constants -- `FIXED_BID_CPM: f64 = 0.20` — fixed price for all Mocktioneer-generated bids +- `FIXED_BID_CPM: f64 = 0.20` — default bid price; the runtime value is + configurable via `bid_cpm` in `mocktioneer.toml` (typed `MocktioneerConfig`), + resolved from the bound config store with this constant as the fallback - `STANDARD_SIZES` — 13 standard IAB sizes as a const array (300x250, 728x90, 320x50, etc.) ## CI Gates diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index fe95f4e..37daad4 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -124,7 +124,7 @@ profile = "release" features = ["fastly"] [adapters.fastly.commands] -build = "cargo build --release --target wasm32-wasip1 -p mocktioneer-adapter-fastly" +build = "fastly compute build -C crates/mocktioneer-adapter-fastly" serve = "fastly compute serve -C crates/mocktioneer-adapter-fastly" deploy = "fastly compute deploy -C crates/mocktioneer-adapter-fastly" @@ -147,9 +147,9 @@ profile = "release" features = ["cloudflare"] [adapters.cloudflare.commands] -build = "cargo build --release --target wasm32-unknown-unknown -p mocktioneer-adapter-cloudflare" -serve = "wrangler dev --config crates/mocktioneer-adapter-cloudflare/wrangler.toml" -deploy = "wrangler publish --config crates/mocktioneer-adapter-cloudflare/wrangler.toml" +build = "wrangler build --cwd crates/mocktioneer-adapter-cloudflare" +serve = "wrangler dev --cwd crates/mocktioneer-adapter-cloudflare" +deploy = "wrangler deploy --cwd crates/mocktioneer-adapter-cloudflare" [adapters.cloudflare.logging] level = "info" From f092affae4a9b9ce79c2080246bb9eecebe0d222 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Tue, 16 Jun 2026 18:53:06 -0700 Subject: [PATCH 27/45] docs: show in-repo mocktioneer-cli alongside edgezero-cli in run examples; fix stale fixed-CPM comments --- crates/mocktioneer-core/src/auction.rs | 2 +- crates/mocktioneer-core/src/routes.rs | 2 +- docs/guide/adapters/cloudflare.md | 4 +++- docs/guide/adapters/fastly.md | 4 +++- docs/guide/getting-started.md | 4 +++- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/crates/mocktioneer-core/src/auction.rs b/crates/mocktioneer-core/src/auction.rs index a3bfe52..26e05b7 100644 --- a/crates/mocktioneer-core/src/auction.rs +++ b/crates/mocktioneer-core/src/auction.rs @@ -257,7 +257,7 @@ pub fn build_aps_response(req: &ApsBidRequest, base_host: &str, cpm: f64) -> Aps continue; }; - // Generate bid components using fixed CPM pricing + // Generate bid components using the resolved CPM let price = cpm; let impression_id = new_id(); let crid = format!("{}-{}", new_id(), "mocktioneer"); diff --git a/crates/mocktioneer-core/src/routes.rs b/crates/mocktioneer-core/src/routes.rs index 5db27c5..0ba3089 100644 --- a/crates/mocktioneer-core/src/routes.rs +++ b/crates/mocktioneer-core/src/routes.rs @@ -1593,7 +1593,7 @@ mod tests { let first = &sizes[0]; assert!(first["width"].is_i64()); assert!(first["height"].is_i64()); - // CPM is no longer included — bid price is fixed at FIXED_BID_CPM + // CPM is not echoed in the response — bid price comes from config (default FIXED_BID_CPM) assert!(first.get("cpm").is_none()); } diff --git a/docs/guide/adapters/cloudflare.md b/docs/guide/adapters/cloudflare.md index 2f3c124..d5e3d47 100644 --- a/docs/guide/adapters/cloudflare.md +++ b/docs/guide/adapters/cloudflare.md @@ -44,8 +44,10 @@ The Cloudflare adapter runs Mocktioneer on Cloudflare Workers, providing global Run locally using Wrangler's local mode: ```bash -# Using the CLI (edgezero-cli, or in-repo `cargo run -p mocktioneer-cli --`) +# Using the CLI edgezero-cli serve --adapter cloudflare +# or, in-repo (no external install): +cargo run -p mocktioneer-cli -- serve --adapter cloudflare # Or directly wrangler dev --config crates/mocktioneer-adapter-cloudflare/wrangler.toml diff --git a/docs/guide/adapters/fastly.md b/docs/guide/adapters/fastly.md index 8e5e727..7e9bb8b 100644 --- a/docs/guide/adapters/fastly.md +++ b/docs/guide/adapters/fastly.md @@ -33,8 +33,10 @@ The Fastly adapter runs Mocktioneer on Fastly's Compute platform, providing glob Run locally using Fastly's Viceroy runtime: ```bash -# Using the CLI (edgezero-cli, or in-repo `cargo run -p mocktioneer-cli --`) +# Using the CLI edgezero-cli serve --adapter fastly +# or, in-repo (no external install): +cargo run -p mocktioneer-cli -- serve --adapter fastly # Or directly fastly compute serve -C crates/mocktioneer-adapter-fastly diff --git a/docs/guide/getting-started.md b/docs/guide/getting-started.md index b1c1ef0..e55f4b2 100644 --- a/docs/guide/getting-started.md +++ b/docs/guide/getting-started.md @@ -58,10 +58,12 @@ cargo run -p mocktioneer-adapter-axum The server starts at `http://127.0.0.1:8787`. -### Option 2: Using EdgeZero CLI +### Option 2: Using the CLI ```bash edgezero-cli serve --adapter axum +# or, in-repo (no external install): +cargo run -p mocktioneer-cli -- serve --adapter axum ``` ### Option 3: Fastly Local Development From 7cf8498032bbf6ff33bb9e196e3dadc1d0d7a696 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Tue, 23 Jun 2026 12:00:01 -0700 Subject: [PATCH 28/45] =?UTF-8?q?feat:=20sync=20to=20edgezero=2089f59266?= =?UTF-8?q?=20=E2=80=94=20blob=20app-config=20(AppConfig=20extractor,=20fa?= =?UTF-8?q?il-loud),=20config=20diff,=20CI/docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test.yml | 7 +- CLAUDE.md | 9 +- Cargo.lock | 29 ++- README.md | 6 +- crates/mocktioneer-cli/src/main.rs | 29 ++- crates/mocktioneer-core/src/routes.rs | 184 ++++++++---------- crates/mocktioneer-core/tests/endpoints.rs | 40 ++++ docs/guide/configuration.md | 19 +- ...gezero-extensible-cli-adaptation-design.md | 14 ++ 9 files changed, 215 insertions(+), 122 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 11ad47d..95f2298 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -58,8 +58,11 @@ jobs: - name: Seed a non-default cpm and assert it round-trips (axum) run: | printf 'bid_cpm = 0.35\n' > "${RUNNER_TEMP}/seed.toml" - cargo run -p mocktioneer-cli -- config push --adapter axum --app-config "${RUNNER_TEMP}/seed.toml" - test "$(jq -r '.bid_cpm' .edgezero/local-config-mocktioneer_config.json)" = "0.35" + cargo run -p mocktioneer-cli -- config push --adapter axum --yes --app-config "${RUNNER_TEMP}/seed.toml" + # edgezero #269 stores the typed config as a single blob envelope under + # the store key; bid_cpm lives at ` | fromjson | .data.bid_cpm`. + got="$(jq -r '.mocktioneer_config | fromjson | .data.bid_cpm' .edgezero/local-config-mocktioneer_config.json)" + test "$got" = "0.35" rm -f .edgezero/local-config-mocktioneer_config.json adapter-wasm-tests: diff --git a/CLAUDE.md b/CLAUDE.md index 43d60b7..8e30bdf 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -155,9 +155,12 @@ through `render.rs`. Do not inline ad markup in handlers. ## Key Constants -- `FIXED_BID_CPM: f64 = 0.20` — default bid price; the runtime value is - configurable via `bid_cpm` in `mocktioneer.toml` (typed `MocktioneerConfig`), - resolved from the bound config store with this constant as the fallback +- `FIXED_BID_CPM: f64 = 0.20` — the auction/APS builders' default `cpm` + argument and the shipped value in `mocktioneer.toml`. At runtime the + OpenRTB/APS handlers read `bid_cpm` from the typed `MocktioneerConfig` blob via + the fail-loud `AppConfig` extractor (edgezero #269 blob model), so a deploy + must `config push` once before those endpoints serve — it is **not** a runtime + fallback - `STANDARD_SIZES` — 13 standard IAB sizes as a const array (300x250, 728x90, 320x50, etc.) ## CI Gates diff --git a/Cargo.lock b/Cargo.lock index 343b0a2..8b6ee09 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -692,7 +692,7 @@ dependencies = [ [[package]] name = "edgezero-adapter" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=feature%2Fextensible-cli#2eeccc9748daba92b9adf6afe4df105e79269ae9" +source = "git+https://github.com/stackpop/edgezero.git?branch=feature%2Fextensible-cli#89f592665a8b386111995da5cbf3fcc068113dfc" dependencies = [ "toml", ] @@ -700,7 +700,7 @@ dependencies = [ [[package]] name = "edgezero-adapter-axum" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=feature%2Fextensible-cli#2eeccc9748daba92b9adf6afe4df105e79269ae9" +source = "git+https://github.com/stackpop/edgezero.git?branch=feature%2Fextensible-cli#89f592665a8b386111995da5cbf3fcc068113dfc" dependencies = [ "anyhow", "async-trait", @@ -728,7 +728,7 @@ dependencies = [ [[package]] name = "edgezero-adapter-cloudflare" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=feature%2Fextensible-cli#2eeccc9748daba92b9adf6afe4df105e79269ae9" +source = "git+https://github.com/stackpop/edgezero.git?branch=feature%2Fextensible-cli#89f592665a8b386111995da5cbf3fcc068113dfc" dependencies = [ "anyhow", "async-trait", @@ -751,7 +751,7 @@ dependencies = [ [[package]] name = "edgezero-adapter-fastly" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=feature%2Fextensible-cli#2eeccc9748daba92b9adf6afe4df105e79269ae9" +source = "git+https://github.com/stackpop/edgezero.git?branch=feature%2Fextensible-cli#89f592665a8b386111995da5cbf3fcc068113dfc" dependencies = [ "anyhow", "async-stream", @@ -769,7 +769,9 @@ dependencies = [ "futures-util", "log", "log-fastly", + "serde", "serde_json", + "sha2 0.10.9", "thiserror 2.0.18", "toml_edit", "walkdir", @@ -778,7 +780,7 @@ dependencies = [ [[package]] name = "edgezero-adapter-spin" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=feature%2Fextensible-cli#2eeccc9748daba92b9adf6afe4df105e79269ae9" +source = "git+https://github.com/stackpop/edgezero.git?branch=feature%2Fextensible-cli#89f592665a8b386111995da5cbf3fcc068113dfc" dependencies = [ "anyhow", "async-trait", @@ -805,8 +807,9 @@ dependencies = [ [[package]] name = "edgezero-cli" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=feature%2Fextensible-cli#2eeccc9748daba92b9adf6afe4df105e79269ae9" +source = "git+https://github.com/stackpop/edgezero.git?branch=feature%2Fextensible-cli#89f592665a8b386111995da5cbf3fcc068113dfc" dependencies = [ + "chrono", "clap", "edgezero-adapter", "edgezero-adapter-axum", @@ -819,6 +822,7 @@ dependencies = [ "log", "serde", "serde_json", + "similar", "simple_logger", "thiserror 2.0.18", "toml", @@ -828,7 +832,7 @@ dependencies = [ [[package]] name = "edgezero-core" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=feature%2Fextensible-cli#2eeccc9748daba92b9adf6afe4df105e79269ae9" +source = "git+https://github.com/stackpop/edgezero.git?branch=feature%2Fextensible-cli#89f592665a8b386111995da5cbf3fcc068113dfc" dependencies = [ "anyhow", "async-compression", @@ -842,9 +846,12 @@ dependencies = [ "http-body", "log", "matchit 0.9.2", + "ryu", "serde", "serde_json", + "serde_path_to_error", "serde_urlencoded", + "sha2 0.10.9", "thiserror 2.0.18", "toml", "tower-service", @@ -856,7 +863,7 @@ dependencies = [ [[package]] name = "edgezero-macros" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=feature%2Fextensible-cli#2eeccc9748daba92b9adf6afe4df105e79269ae9" +source = "git+https://github.com/stackpop/edgezero.git?branch=feature%2Fextensible-cli#89f592665a8b386111995da5cbf3fcc068113dfc" dependencies = [ "log", "proc-macro2", @@ -2607,6 +2614,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" +[[package]] +name = "similar" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" + [[package]] name = "simple_logger" version = "5.2.0" diff --git a/README.md b/README.md index 8a2006b..ff3cb19 100644 --- a/README.md +++ b/README.md @@ -53,9 +53,11 @@ cargo run -p mocktioneer-adapter-axum # Local server (Axum) on :8787 cargo run -p mocktioneer-cli -- serve --adapter cloudflare # Cloudflare on :8787 cargo run -p mocktioneer-cli -- serve --adapter fastly # Fastly on :7676 -# Typed app config (validate / push bid_cpm): +# Typed app config (validate / diff / push bid_cpm as a blob envelope). +# Auction + APS endpoints require a one-time `config push` per deploy. cargo run -p mocktioneer-cli -- config validate --strict -cargo run -p mocktioneer-cli -- config push --adapter axum +cargo run -p mocktioneer-cli -- config diff --adapter axum +cargo run -p mocktioneer-cli -- config push --adapter axum --yes ``` ## License diff --git a/crates/mocktioneer-cli/src/main.rs b/crates/mocktioneer-cli/src/main.rs index a30285d..c6f459f 100644 --- a/crates/mocktioneer-cli/src/main.rs +++ b/crates/mocktioneer-cli/src/main.rs @@ -2,12 +2,14 @@ //! //! Reuses every built-in edgezero command and adds the **typed** `config` //! arms parameterised over `MocktioneerConfig`, so `validator` rules run on -//! `config validate` / `config push`. +//! `config validate` / `config push` / `config diff`. use clap::{Parser, Subcommand}; use edgezero_cli::args::{ - AuthArgs, BuildArgs, ConfigPushArgs, ConfigValidateArgs, DeployArgs, ProvisionArgs, ServeArgs, + AuthArgs, BuildArgs, ConfigDiffArgs, ConfigPushArgs, ConfigValidateArgs, DeployArgs, + ProvisionArgs, ServeArgs, }; +use edgezero_cli::DiffExit; use mocktioneer_core::config::MocktioneerConfig; #[derive(Parser, Debug)] @@ -34,11 +36,14 @@ enum Cmd { Serve(ServeArgs), } -/// Dispatches `validate`/`push` to the typed entry points over +/// Dispatches `validate`/`push`/`diff` to the typed entry points over /// `MocktioneerConfig`. #[derive(Subcommand, Debug)] enum MocktioneerConfigCmd { - /// Push `mocktioneer.toml` (flattened) to the adapter's config store. + /// Diff `mocktioneer.toml` against the live (or local-emulator) config + /// store. Exits 0 (no changes), 1 (changes with `--exit-code`), 2 (error). + Diff(ConfigDiffArgs), + /// Push `mocktioneer.toml` as a blob envelope to the adapter's config store. Push(ConfigPushArgs), /// Validate `edgezero.toml` + `mocktioneer.toml` against `MocktioneerConfig`. Validate(ConfigValidateArgs), @@ -48,9 +53,19 @@ fn main() { use std::process; edgezero_cli::init_cli_logger(); - let result = match Args::parse().cmd { + let result: Result<(), String> = match Args::parse().cmd { Cmd::Auth(args) => edgezero_cli::run_auth(&args), Cmd::Build(args) => edgezero_cli::run_build(&args), + Cmd::Config(MocktioneerConfigCmd::Diff(args)) => { + // `run_config_diff_typed` returns `Result`: a + // non-zero exit code (1 = diff with `--exit-code`; 2 = unsupported) + // exits the process directly, mirroring the generated template. + match edgezero_cli::run_config_diff_typed::(&args) { + Ok(DiffExit { code: 0 }) => Ok(()), + Ok(DiffExit { code }) => process::exit(code), + Err(err) => Err(err), + } + } Cmd::Config(MocktioneerConfigCmd::Push(args)) => { edgezero_cli::run_config_push_typed::(&args) } @@ -63,6 +78,8 @@ fn main() { }; if let Err(err) = result { log::error!("[mocktioneer] {err}"); - process::exit(1); + // Exit 2 for all errors so `config diff` errors satisfy the "errors are + // always ≥ 2" contract; push / validate are not 1-vs-2 sensitive. + process::exit(2); } } diff --git a/crates/mocktioneer-core/src/routes.rs b/crates/mocktioneer-core/src/routes.rs index 0ba3089..835d1b4 100644 --- a/crates/mocktioneer-core/src/routes.rs +++ b/crates/mocktioneer-core/src/routes.rs @@ -7,7 +7,7 @@ use async_trait::async_trait; use edgezero_core::action; use edgezero_core::context::RequestContext; use edgezero_core::extractor::{ - ForwardedHost, FromRequest, Headers, ValidatedJson, ValidatedQuery, + AppConfig, ForwardedHost, FromRequest, Headers, ValidatedJson, ValidatedQuery, }; use edgezero_core::http::{ header, response_builder, HeaderMap, HeaderValue, Method, Response, StatusCode, @@ -21,8 +21,9 @@ use validator::{Validate, ValidationError}; use crate::aps::ApsBidRequest; use crate::auction::{ - build_aps_response, build_openrtb_response, is_standard_size, standard_sizes, FIXED_BID_CPM, + build_aps_response, build_openrtb_response, is_standard_size, standard_sizes, }; +use crate::config::MocktioneerConfig; use crate::mediation::{mediate_auction, MediationRequest}; use crate::openrtb::OpenRTBRequest; use crate::render::{ @@ -311,39 +312,12 @@ pub async fn handle_root(ForwardedHost(host): ForwardedHost) -> Result) -> Result { - match found { - None => Ok(FIXED_BID_CPM), - Some(raw) => raw - .parse::() - .ok() - .filter(|value| value.is_finite() && *value > 0.0_f64) - .ok_or_else(|| { - EdgeError::internal(anyhow::anyhow!( - "config store `mocktioneer_config` has malformed bid_cpm: {raw:?}" - )) - }), - } -} - -/// Resolve the effective bid CPM from the bound default config store, falling -/// back to `FIXED_BID_CPM` when no store is bound. A backend read error -/// propagates via `From`. -async fn resolve_bid_cpm(ctx: &RequestContext) -> Result { - let Some(store) = ctx.config_store_default() else { - return Ok(FIXED_BID_CPM); - }; - cpm_from_lookup(store.get("bid_cpm").await.map_err(EdgeError::from)?) -} - #[action] pub async fn handle_openrtb_auction( RequestContext(ctx): RequestContext, ForwardedHost(host): ForwardedHost, ValidatedJson(req): ValidatedJson, + AppConfig(cfg): AppConfig, ) -> Result { // Capture signature verification status for metadata let signature_status = @@ -369,9 +343,8 @@ pub async fn handle_openrtb_auction( log::info!("auction id={}, imps={}", req.id, req.imp.len()); - let cpm = resolve_bid_cpm(&ctx).await?; // Build response with embedded metadata (signature status + request + response preview) - let resp = build_openrtb_response(&req, &host, signature_status, cpm); + let resp = build_openrtb_response(&req, &host, signature_status, cfg.bid_cpm); let body = Body::json(&resp).map_err(|err| { log::error!("Failed to serialize OpenRTB response: {err}"); EdgeError::internal(err) @@ -497,9 +470,9 @@ pub async fn handle_pixel( #[action] pub async fn handle_aps_bid( - RequestContext(ctx): RequestContext, ForwardedHost(host): ForwardedHost, ValidatedJson(req): ValidatedJson, + AppConfig(cfg): AppConfig, ) -> Result { log::info!( "APS auction pubId={}, slots={}", @@ -507,8 +480,7 @@ pub async fn handle_aps_bid( req.slots.len() ); - let cpm = resolve_bid_cpm(&ctx).await?; - let resp = build_aps_response(&req, &host, cpm); + let resp = build_aps_response(&req, &host, cfg.bid_cpm); let body = Body::json(&resp).map_err(|err| { log::error!("Failed to serialize APS response: {err}"); EdgeError::internal(err) @@ -1026,6 +998,7 @@ fn sanitize_for_log(input: &str, max_len: usize) -> String { #[cfg(test)] mod tests { use super::*; + use edgezero_core::blob_envelope::BlobEnvelope; use edgezero_core::body::Body; use edgezero_core::config_store::{ConfigStore, ConfigStoreError, ConfigStoreHandle}; use edgezero_core::context::RequestContext; @@ -1033,7 +1006,7 @@ mod tests { use edgezero_core::http::{request_builder, Method, Response, StatusCode}; use edgezero_core::params::PathParams; use edgezero_core::response::IntoResponse as _; - use edgezero_core::store_registry::{ConfigRegistry, StoreRegistry}; + use edgezero_core::store_registry::{ConfigRegistry, ConfigStoreBinding, StoreRegistry}; use futures::executor::block_on; use std::collections::{BTreeMap, HashMap}; use std::sync::Arc; @@ -1065,86 +1038,99 @@ mod tests { .to_vec() } - fn ctx(method: Method, uri: &str, body: Body, params: &[(&str, &str)]) -> RequestContext { - let mut builder = request_builder(); - builder = builder.method(method).uri(uri); - let request = builder.body(body).expect("request"); - let map = params - .iter() - .map(|(key, value)| ((*key).to_owned(), (*value).to_owned())) - .collect::>(); - RequestContext::new(request, PathParams::new(map)) - } - - // ---- bid_cpm resolution ---- - - #[test] - fn cpm_from_lookup_falls_back_when_absent() { - assert_eq!( - cpm_from_lookup(None).unwrap().to_bits(), - FIXED_BID_CPM.to_bits() - ); - } - - #[test] - fn cpm_from_lookup_parses_valid_value() { - assert!((cpm_from_lookup(Some("0.35".to_owned())).unwrap() - 0.35).abs() < f64::EPSILON); + /// A valid blob-envelope JSON string wrapping `{ "bid_cpm": }`, + /// matching what `config push` writes for the typed config. + fn config_blob(bid_cpm: f64) -> String { + let data = serde_json::json!({ "bid_cpm": bid_cpm }); + serde_json::to_string(&BlobEnvelope::new(data, "2026-01-01T00:00:00Z".to_owned())) + .expect("serialize envelope") } - #[test] - fn cpm_from_lookup_rejects_bad_values() { - for bad in ["-1", "0", "abc", "inf", "NaN", ""] { - assert!( - cpm_from_lookup(Some(bad.to_owned())).is_err(), - "expected {bad:?} to error" - ); - } - } - - fn ctx_with_config(pairs: &[(&str, &str)]) -> RequestContext { - let map: HashMap = pairs - .iter() - .map(|(key, value)| ((*key).to_owned(), (*value).to_owned())) - .collect(); + fn config_registry(bid_cpm: f64) -> ConfigRegistry { + let map: HashMap = + [("mocktioneer_config".to_owned(), config_blob(bid_cpm))] + .into_iter() + .collect(); let handle = ConfigStoreHandle::new(Arc::new(MapConfigStore(map))); - let by_id: BTreeMap = - [("mocktioneer_config".to_owned(), handle)] + let binding = ConfigStoreBinding { + handle, + default_key: "mocktioneer_config".to_owned(), + }; + let by_id: BTreeMap = + [("mocktioneer_config".to_owned(), binding)] .into_iter() .collect(); - let registry: ConfigRegistry = StoreRegistry::new(by_id, "mocktioneer_config".to_owned()); + StoreRegistry::new(by_id, "mocktioneer_config".to_owned()) + } + fn ctx(method: Method, uri: &str, body: Body, params: &[(&str, &str)]) -> RequestContext { + ctx_with_cpm(method, uri, body, params, 0.20_f64) + } + + /// Like [`ctx`] but seeds the default config store with a `bid_cpm` blob. + /// The auction/APS handlers use the fail-loud `AppConfig` extractor, so a + /// bound, valid blob must be present for them to run. + fn ctx_with_cpm( + method: Method, + uri: &str, + body: Body, + params: &[(&str, &str)], + bid_cpm: f64, + ) -> RequestContext { let mut request = request_builder() - .method(Method::GET) - .uri("/") - .body(Body::empty()) + .method(method) + .uri(uri) + .body(body) .expect("request"); - request.extensions_mut().insert(registry); - RequestContext::new(request, PathParams::new(HashMap::new())) + request.extensions_mut().insert(config_registry(bid_cpm)); + let map = params + .iter() + .map(|(key, value)| ((*key).to_owned(), (*value).to_owned())) + .collect::>(); + RequestContext::new(request, PathParams::new(map)) } + // ---- bid_cpm typed-config wiring ---- + #[test] - fn resolve_bid_cpm_reads_seeded_store() { - let ctx = ctx_with_config(&[("bid_cpm", "0.35")]); - let cpm = block_on(resolve_bid_cpm(&ctx)).unwrap(); - assert!((cpm - 0.35).abs() < f64::EPSILON); + fn auction_uses_seeded_cpm() { + let body = serde_json::json!({ + "id": "rc", + "imp": [{ "id": "1", "banner": { "w": 300_i32, "h": 250_i32 } }] + }); + let ctx = ctx_with_cpm( + Method::POST, + "/openrtb2/auction", + Body::json(&body).expect("json body"), + &[], + 0.35_f64, + ); + let response = response_from(block_on(handle_openrtb_auction(ctx))); + assert_eq!(response.status(), StatusCode::OK); + let payload: serde_json::Value = + serde_json::from_slice(&body_bytes(response)).expect("json"); + let price = payload["seatbid"][0]["bid"][0]["price"] + .as_f64() + .expect("price"); + assert!((price - 0.35).abs() < f64::EPSILON); } #[test] - fn resolve_bid_cpm_falls_back_without_registry() { + fn auction_without_config_errors() { + // Fail-loud: with no config store bound the `AppConfig` extractor errors + // (out-of-the-box requires `config push` before serving auctions). + let body = serde_json::json!({ + "id": "rc", + "imp": [{ "id": "1", "banner": { "w": 300_i32, "h": 250_i32 } }] + }); let request = request_builder() - .method(Method::GET) - .uri("/") - .body(Body::empty()) + .method(Method::POST) + .uri("/openrtb2/auction") + .body(Body::json(&body).expect("json body")) .expect("request"); let ctx = RequestContext::new(request, PathParams::new(HashMap::new())); - let cpm = block_on(resolve_bid_cpm(&ctx)).unwrap(); - assert_eq!(cpm.to_bits(), FIXED_BID_CPM.to_bits()); - } - - #[test] - fn resolve_bid_cpm_errors_on_malformed_value() { - let ctx = ctx_with_config(&[("bid_cpm", "-1")]); - block_on(resolve_bid_cpm(&ctx)).unwrap_err(); + let response = response_from(block_on(handle_openrtb_auction(ctx))); + assert_ne!(response.status(), StatusCode::OK); } #[test] diff --git a/crates/mocktioneer-core/tests/endpoints.rs b/crates/mocktioneer-core/tests/endpoints.rs index 2759e62..5297525 100644 --- a/crates/mocktioneer-core/tests/endpoints.rs +++ b/crates/mocktioneer-core/tests/endpoints.rs @@ -1,16 +1,55 @@ #[cfg(test)] mod tests { + use async_trait::async_trait; use edgezero_core::app::App; + use edgezero_core::blob_envelope::BlobEnvelope; use edgezero_core::body::Body; + use edgezero_core::config_store::{ConfigStore, ConfigStoreError, ConfigStoreHandle}; use edgezero_core::http::{ header, request_builder, HeaderValue, Method, Request, Response, StatusCode, }; + use edgezero_core::store_registry::{ConfigRegistry, ConfigStoreBinding, StoreRegistry}; use futures::executor::block_on; + use std::collections::HashMap; + use std::sync::Arc; + + /// In-memory config store returning a blob envelope by key. + struct MapConfigStore(HashMap); + + #[async_trait(?Send)] + impl ConfigStore for MapConfigStore { + async fn get(&self, key: &str) -> Result, ConfigStoreError> { + Ok(self.0.get(key).cloned()) + } + } fn app() -> App { mocktioneer_core::build_app() } + /// Build a `ConfigRegistry` whose default `mocktioneer_config` store holds a + /// blob envelope for `{ "bid_cpm": }` — what `config push` writes. + /// The auction/APS handlers use the fail-loud `AppConfig` extractor, so the + /// router needs this bound to serve those routes. + fn config_registry(bid_cpm: f64) -> ConfigRegistry { + let data = serde_json::json!({ "bid_cpm": bid_cpm }); + let blob = + serde_json::to_string(&BlobEnvelope::new(data, "2026-01-01T00:00:00Z".to_owned())) + .expect("serialize envelope"); + let store = MapConfigStore( + [("mocktioneer_config".to_owned(), blob)] + .into_iter() + .collect(), + ); + StoreRegistry::single_id( + "mocktioneer_config".to_owned(), + ConfigStoreBinding { + handle: ConfigStoreHandle::new(Arc::new(store)), + default_key: "mocktioneer_config".to_owned(), + }, + ) + } + fn make_request(method: Method, uri: &str, body: Body) -> Request { request_builder() .method(method) @@ -103,6 +142,7 @@ mod tests { request .headers_mut() .insert(header::HOST, HeaderValue::from_static("test.local")); + request.extensions_mut().insert(config_registry(0.20_f64)); let response = dispatch(&app, request); assert_eq!(response.status(), StatusCode::OK); let content_type = response diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index 37daad4..817dedb 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -190,13 +190,28 @@ there is no `[config]` wrapper: bid_cpm = 0.20 ``` -Validate it against the typed contract and push it to an adapter's config store: +Validate it, preview the diff against the live store, then push it: ```bash cargo run -p mocktioneer-cli -- config validate --strict -cargo run -p mocktioneer-cli -- config push --adapter axum +cargo run -p mocktioneer-cli -- config diff --adapter axum +cargo run -p mocktioneer-cli -- config push --adapter axum --yes ``` +`config push` writes the whole struct as a single **blob envelope** (canonical +JSON + a SHA for drift detection) under the store's key — for axum that's +`.edgezero/local-config-mocktioneer_config.json` as +`{ "mocktioneer_config": "" }`. The handlers read it back through the +typed `AppConfig` extractor. + +::: warning Config is required at runtime +The OpenRTB (`/openrtb2/auction`) and APS (`/e/dtb/bid`) endpoints read +`bid_cpm` via the fail-loud `AppConfig` extractor. **A fresh deploy must run +`config push` once** before those endpoints serve bids — until then they return +an error (the static/creative/pixel endpoints are unaffected). `bid_cpm = 0.20` +is the shipped default value, not a runtime fallback. +::: + Any key can be overridden at runtime via the `MOCKTIONEER__` env overlay (e.g. `MOCKTIONEER__BID_CPM=0.35`). diff --git a/docs/superpowers/specs/2026-06-11-edgezero-extensible-cli-adaptation-design.md b/docs/superpowers/specs/2026-06-11-edgezero-extensible-cli-adaptation-design.md index 8281e64..187980d 100644 --- a/docs/superpowers/specs/2026-06-11-edgezero-extensible-cli-adaptation-design.md +++ b/docs/superpowers/specs/2026-06-11-edgezero-extensible-cli-adaptation-design.md @@ -41,6 +41,20 @@ subcommand from `mocktioneer-cli`: scaffolding a brand-new EdgeZero app from within Mocktioneer's own project CLI is nonsensical, so the crate exposes `auth`/`build`/`deploy`/`provision`/`serve` + typed `config` only. + **R8 (sync to edgezero `89f59266`)** — supersedes the §3.5 read model. edgezero + advanced past the pin with a **blob app-config cutover**: the whole typed + config is stored as one canonical-JSON **blob envelope** (SHA-gated) under the + store's key, read via the new **`AppConfig` extractor**, not per-leaf + `get("bid_cpm")`. Per the user's call, the handlers now use the **fail-loud** + bare `AppConfig(cfg): AppConfig` extractor (dropping the + graceful `resolve_bid_cpm` wrapper): OpenRTB/APS **require a `config push`** + before serving (they error otherwise; `FIXED_BID_CPM` is the builder default / + shipped value, no longer a runtime fallback). Also: added the new `config diff` + command (`DiffExit`) to `mocktioneer-cli`; `config push` now needs `--yes` in + CI; the local-config file is `{ "mocktioneer_config": "" }`, so the + CI assertion is `jq -r '.mocktioneer_config | fromjson | .data.bid_cpm'`; and + endpoint/handler tests seed a blob via a `ConfigRegistry` fixture + (`StoreRegistry` + `ConfigStoreBinding` + `BlobEnvelope::new`). ## 1. Problem & context From c2a8ec4f23f96f52e7baa97e137ac794edefaaba Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Tue, 23 Jun 2026 14:52:41 -0700 Subject: [PATCH 29/45] docs: align spec/plan/guides to R8 blob fail-loud model; add config-push prereq to quickstarts; patch spin in local overlay --- .cargo/config.toml.local | 1 + README.md | 8 +- docs/guide/configuration.md | 11 +- docs/guide/getting-started.md | 11 ++ ...6-15-edgezero-extensible-cli-adaptation.md | 36 ++++- ...gezero-extensible-cli-adaptation-design.md | 151 +++++++++--------- 6 files changed, 131 insertions(+), 87 deletions(-) diff --git a/.cargo/config.toml.local b/.cargo/config.toml.local index 767884b..7bec491 100644 --- a/.cargo/config.toml.local +++ b/.cargo/config.toml.local @@ -8,6 +8,7 @@ git-fetch-with-cli = true edgezero-adapter-axum = { path = "../edgezero/crates/edgezero-adapter-axum" } edgezero-adapter-cloudflare = { path = "../edgezero/crates/edgezero-adapter-cloudflare" } edgezero-adapter-fastly = { path = "../edgezero/crates/edgezero-adapter-fastly" } +edgezero-adapter-spin = { path = "../edgezero/crates/edgezero-adapter-spin" } edgezero-cli = { path = "../edgezero/crates/edgezero-cli" } edgezero-core = { path = "../edgezero/crates/edgezero-core" } edgezero-macros = { path = "../edgezero/crates/edgezero-macros" } diff --git a/README.md b/README.md index ff3cb19..cf5215a 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,15 @@ Deterministic OpenRTB banner bidder for edge platforms. Test client integrations ## Quick Start ```bash -# Clone and run locally +# Clone git clone https://github.com/stackpop/mocktioneer.git cd mocktioneer + +# Seed the typed app config once (the auction/APS endpoints read bid_cpm from it +# via the fail-loud AppConfig extractor and error until it is pushed). +cargo run -p mocktioneer-cli -- config push --adapter axum --yes + +# Run locally cargo run -p mocktioneer-adapter-axum # Test the auction endpoint diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index 817dedb..dc38180 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -39,11 +39,12 @@ typed app config (`mocktioneer.toml`): ids = ["mocktioneer_config"] ``` -The `mocktioneer_config` store holds `bid_cpm` (see the typed `MocktioneerConfig` -struct). Seed it per adapter with `mocktioneer-cli config push --adapter `; -handlers read it at runtime via `ctx.config_store_default()`, falling back to the -compile-time `FIXED_BID_CPM` default when no store is bound. See -[`mocktioneer.toml`](#typed-app-config) below. +The `mocktioneer_config` store holds the typed config blob (see the +`MocktioneerConfig` struct). Seed it per adapter with +`mocktioneer-cli config push --adapter `; the OpenRTB/APS handlers read it +at runtime via the **fail-loud `AppConfig` extractor** — a `config push` is +required before those endpoints serve (see [Typed App Config](#typed-app-config) +below). ## HTTP Triggers diff --git a/docs/guide/getting-started.md b/docs/guide/getting-started.md index e55f4b2..8ba6de8 100644 --- a/docs/guide/getting-started.md +++ b/docs/guide/getting-started.md @@ -48,6 +48,17 @@ cargo run -p mocktioneer-cli -- config validate --strict ## Running Locally +> **Seed the app config first.** The OpenRTB (`/openrtb2/auction`) and APS +> (`/e/dtb/bid`) endpoints read `bid_cpm` from the typed config via the +> fail-loud `AppConfig` extractor, so they error until you push the config once +> per adapter: +> +> ```bash +> cargo run -p mocktioneer-cli -- config push --adapter axum --yes +> ``` +> +> The root, `/static/*`, `/pixel`, and `/_/sizes` endpoints work without it. + ### Option 1: Native Axum Server (Recommended for Development) The fastest way to get started: diff --git a/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md b/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md index 8f33f7f..73a07a1 100644 --- a/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md +++ b/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md @@ -2,7 +2,18 @@ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. -**Goal:** Adapt Mocktioneer to the breaking edgezero #269 API (extensible CLI, dropped `run_app` manifest arg, Spin SDK 6 / wasip2) and adopt typed `AppConfig` so `bid_cpm` is configurable at runtime, defaulting to `FIXED_BID_CPM`. +> **⚠️ Superseded in part by spec R8 (blob app-config sync to edgezero `89f59266`).** +> This plan was executed, then edgezero advanced past the pin with the **blob +> app-config cutover**. The runtime-read design below (Task 6: +> `cpm_from_lookup` / `resolve_bid_cpm` / per-leaf `get("bid_cpm")` / graceful +> `None → FIXED_BID_CPM` fallback) is **obsolete**. As implemented, the handlers +> use the **fail-loud `AppConfig` extractor** (config is one +> SHA-gated blob envelope under the store key; a `config push` is required before +> auction/APS serve). The CLI also gained `config diff` (Task 8), and the CI +> assertion reads into the envelope (Task 12). See spec §3.5 (R8) and the source +> for the authoritative behaviour. + +**Goal:** Adapt Mocktioneer to the breaking edgezero #269 API (extensible CLI, dropped `run_app` manifest arg, Spin SDK 6 / wasip2) and adopt typed `AppConfig`. (R8: the typed config is read at runtime via the fail-loud `AppConfig` extractor — `bid_cpm` requires a `config push`; `FIXED_BID_CPM` is the builder default, not a runtime fallback.) **Architecture:** Pin the six `edgezero-*` git deps to `feature/extensible-cli`; fix every adapter entrypoint; migrate the Spin adapter to `spin-sdk ~6.0` / `wasm32-wasip2`; add a `MocktioneerConfig` typed-config struct + `mocktioneer.toml` + a `mocktioneer-cli` crate that mirrors edgezero's generated `-cli`; thread a resolved `cpm` through the OpenRTB and APS bid builders; wire docs/CI/Docker/ignore files. @@ -451,6 +462,14 @@ git commit -m "feat: add MocktioneerConfig typed config (bid_cpm, validated)" ## Task 6: Thread `cpm` through the bid builders + runtime resolution +> **⚠️ Read model below is OBSOLETE (spec R8).** Keep the `cpm: f64` builder +> parameter threading, but the runtime read is NOT `cpm_from_lookup` / +> `resolve_bid_cpm` / `get("bid_cpm")`. As implemented: `handle_openrtb_auction` +> and `handle_aps_bid` take `AppConfig(cfg): AppConfig` and +> pass `cfg.bid_cpm` to the builders — fail-loud (errors with no pushed config). +> Tests seed a **blob envelope** via a `ConfigRegistry` fixture +> (`StoreRegistry::single_id` + `ConfigStoreBinding` + `BlobEnvelope::new`). + **Files:** - Modify: `crates/mocktioneer-core/src/auction.rs` (`build_openrtb_response`, `build_aps_response`, tests) @@ -766,6 +785,12 @@ git commit -m "feat: add mocktioneer.toml typed config (bid_cpm default 0.20)" ## Task 8: `mocktioneer-cli` crate +> **R8 addition:** the crate also wires the new `config diff` command — +> `MocktioneerConfigCmd::Diff(ConfigDiffArgs)` dispatching +> `edgezero_cli::run_config_diff_typed::` (returns `DiffExit`; +> non-zero codes `process::exit`, all errors exit `2`). `new` is intentionally +> omitted (see R7). + **Files:** - Create: `crates/mocktioneer-cli/Cargo.toml` @@ -1045,8 +1070,9 @@ Add a step (in the existing native test job, after `cargo test`): - name: Seed a NON-default cpm and assert it round-trips (axum) run: | printf 'bid_cpm = 0.35\n' > /tmp/seed.toml - cargo run -p mocktioneer-cli -- config push --adapter axum --app-config /tmp/seed.toml - test "$(jq -r '.bid_cpm' .edgezero/local-config-mocktioneer_config.json)" = "0.35" + cargo run -p mocktioneer-cli -- config push --adapter axum --yes --app-config /tmp/seed.toml + # R8 blob model: bid_cpm is inside the envelope under the store key. + test "$(jq -r '.mocktioneer_config | fromjson | .data.bid_cpm' .edgezero/local-config-mocktioneer_config.json)" = "0.35" rm -f .edgezero/local-config-mocktioneer_config.json ``` @@ -1108,8 +1134,8 @@ Expected: PASS. Run: `cargo run -p mocktioneer-cli -- config validate --strict` Expected: PASS. -Run: `printf 'bid_cpm = 0.35\n' > /tmp/seed.toml && cargo run -p mocktioneer-cli -- config push --adapter axum --app-config /tmp/seed.toml && cat .edgezero/local-config-mocktioneer_config.json` -Expected: the JSON contains `bid_cpm` = `0.35`. (Then remove it: `rm -f .edgezero/local-config-mocktioneer_config.json`.) +Run: `printf 'bid_cpm = 0.35\n' > /tmp/seed.toml && cargo run -p mocktioneer-cli -- config push --adapter axum --yes --app-config /tmp/seed.toml && jq -r '.mocktioneer_config | fromjson | .data.bid_cpm' .edgezero/local-config-mocktioneer_config.json` +Expected: prints `0.35` (R8 blob envelope). (Then remove it: `rm -f .edgezero/local-config-mocktioneer_config.json`.) - [ ] **Step 5: Docs gates (incl. spec/plan exclusion assertion)** diff --git a/docs/superpowers/specs/2026-06-11-edgezero-extensible-cli-adaptation-design.md b/docs/superpowers/specs/2026-06-11-edgezero-extensible-cli-adaptation-design.md index 187980d..3aec822 100644 --- a/docs/superpowers/specs/2026-06-11-edgezero-extensible-cli-adaptation-design.md +++ b/docs/superpowers/specs/2026-06-11-edgezero-extensible-cli-adaptation-design.md @@ -99,7 +99,8 @@ Parts that **do** reach this repo: `mocktioneer-cli`, and a **required** `config validate --strict` CI gate. - `bid_cpm` consumed at runtime by **both** the OpenRTB auction path (`handle_openrtb_auction`) **and** the APS path (`handle_aps_bid`), since - both currently emit `FIXED_BID_CPM`, with a defined fallback/error contract. + both currently emit `FIXED_BID_CPM`, read via the typed config (R8: fail-loud + `AppConfig` extractor — a `config push` is required before serving). **Non-goals** @@ -205,65 +206,59 @@ fn validate_bid_cpm(value: &f64) -> Result<(), ValidationError> { - `pub mod config;` in `crates/mocktioneer-core/src/lib.rs`. - Root **`mocktioneer.toml`** (1:1, no `[config]` wrapper): `bid_cpm = 0.20`. -**Runtime resolution — shared helper, used by BOTH handlers.** Both -`build_aps_response` (in `auction.rs`, reads `FIXED_BID_CPM` at line ~259) and -the OpenRTB bid builder (`auction.rs` ~116/120) emit the fixed CPM, so both -`handle_openrtb_auction` (already binds `RequestContext(ctx)`) and -`handle_aps_bid` (gains `RequestContext(ctx)`, alongside its existing -`ForwardedHost`/`ValidatedJson` — the multi-extractor pattern -`handle_openrtb_auction` already uses) resolve `cpm` once and thread it in: +**Runtime resolution — `AppConfig` extractor, fail-loud (R8 / blob model).** +> Superseded the original per-leaf `resolve_bid_cpm`/`get("bid_cpm")` design. +> edgezero `89f59266` stores the whole typed config as one canonical-JSON +> **blob envelope** (SHA-gated) under the store's key, read via the +> `AppConfig` extractor — there is no per-key `get("bid_cpm")`. + +`auction.rs`'s `build_openrtb_response` and `build_aps_response` take a +`cpm: f64` parameter (replacing direct `FIXED_BID_CPM` reads). The two handlers +read the typed config with the **bare, fail-loud extractor** and pass +`cfg.bid_cpm` in: ```rust // routes.rs -async fn resolve_bid_cpm(ctx: &RequestContext) -> Result { - // No config store bound at all → compile-time default. - let Some(store) = ctx.config_store_default() else { - return Ok(auction::FIXED_BID_CPM); - }; - // Store read errors map through `From` (→ 400/503/500); - // do not mask a broken backend as a $0.20 bid. - match store.get("bid_cpm").await.map_err(EdgeError::from)? { - // Declared but unseeded: Axum binds an EMPTY store when - // `.edgezero/local-config-mocktioneer_config.json` is absent - // (dev_server.rs `from_local_file` → `Ok(empty)`), so a missing key - // is the normal "not yet pushed" state → fall back, not error. - None => Ok(auction::FIXED_BID_CPM), - // Present but malformed → real misconfiguration, surface it. - Some(raw) => raw - .parse::() - .ok() - .filter(|v| v.is_finite() && *v > 0.0) - .ok_or_else(|| { - EdgeError::internal(anyhow::anyhow!( - "config store `mocktioneer_config` has malformed bid_cpm: {raw:?}" - )) - }), - } +use edgezero_core::extractor::AppConfig; +use crate::config::MocktioneerConfig; + +#[action] +pub async fn handle_openrtb_auction( + RequestContext(ctx): RequestContext, + ForwardedHost(host): ForwardedHost, + ValidatedJson(req): ValidatedJson, + AppConfig(cfg): AppConfig, +) -> Result { + // ... signature handling ... + let resp = build_openrtb_response(&req, &host, signature_status, cfg.bid_cpm); +} + +#[action] +pub async fn handle_aps_bid( + ForwardedHost(host): ForwardedHost, + ValidatedJson(req): ValidatedJson, + AppConfig(cfg): AppConfig, +) -> Result { + let resp = build_aps_response(&req, &host, cfg.bid_cpm); } ``` -`auction.rs` bid builders and `build_aps_response` take a `cpm: f64` parameter -(replacing direct `FIXED_BID_CPM` reads); `FIXED_BID_CPM` stays as the -fallback constant and the value existing tests pass explicitly. - -**Contract summary** (note the malformed-_file_ vs malformed-_value_ -distinction, which is easy to conflate): - -| Runtime situation | `config_store_default()` / `get` | Result | -| --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | -| No `[stores.config]`, or registry dropped | `None` | fallback `FIXED_BID_CPM` | -| Axum local file **absent** (empty store bound) | `Ok(None)` | fallback | -| File present, **missing** the `bid_cpm` key | `Ok(None)` | fallback | -| File **malformed JSON** | store **dropped at bind** → `config_store_default()` is `None` (Axum `build_config_registry` drops the id, and if it's the default id the whole registry is dropped — it does **not** surface as a `get` error) | fallback | -| Backend **read error** at `get` time (e.g. CF/Fastly KV hiccup) | `Err(ConfigStoreError)` | propagate via `EdgeError::from` (→ 400/503/500) | -| Value present but unparseable / non-finite / ≤ 0 | `Ok(Some(bad))` | **error** (`EdgeError::internal`) | - -So a malformed Axum config _file_ degrades to the fallback (the bind-time drop -means handlers never see it), while a malformed _value_ in an otherwise-valid -file is a real misconfiguration and errors. `EdgeError::internal` takes -`Into` (error.rs:63), hence `anyhow::anyhow!`, not a bare -`String`. **Determinism preserved** on the fallback path (semantic outputs -match `main`). +**Contract (fail-loud — the approved trade-off):** the `AppConfig` extractor +fetches the blob at the bound store's `default_key`, verifies the envelope SHA, +deserialises into `MocktioneerConfig`, and runs `validator`. Outcomes: + +| Runtime situation | Result | +| --- | --- | +| No `[stores.config]` / no config store bound | **error** (`EdgeError::internal` "no default config store registered") | +| Store bound but **no blob pushed** yet | **error** (`config_out_of_date` — "run `config push`") | +| Blob present, valid | typed `cfg.bid_cpm` | +| Blob present, value invalid (`bid_cpm` ≤ 0 / non-finite) | **error** (validation) | + +So OpenRTB/APS **require a `config push` per deploy** before they serve; +`FIXED_BID_CPM` is the builders' default arg + the shipped `mocktioneer.toml` +value, **not** a runtime fallback. The static/creative/pixel endpoints don't +use the extractor and are unaffected. (The earlier graceful-fallback wrapper +`resolve_bid_cpm` was dropped per the user's R8 decision.) ### 3.6 Config lifecycle (declare → seed → bind → read) @@ -273,9 +268,10 @@ backing exists — `edgezero-adapter-axum/src/dev_server.rs:333 run_app` → `.edgezero/local-config-.json`; Cloudflare (`request.rs:362`), Fastly (`request.rs:382`), Spin (`config_store.rs` via `request::build_config_registry`) have equivalents. The `app!` macro emits `stores().config` from -`[stores.config]`. **Axum binds an empty store when the file is missing** (it -does not skip the id), which is exactly why §3.5's contract falls back on -`Ok(None)`. +`[stores.config]`. Axum binds an empty store when the file is missing (it does +not skip the id); under the R8 fail-loud model the `AppConfig` extractor then +errors `config_out_of_date` (blob absent) — so the store must be **seeded with +a `config push`** before the auction/APS routes serve. Per-adapter backing + seed step: @@ -300,7 +296,7 @@ Cloud/Spin adapters get the manifest declaration + required native-backing tables (incl. the Spin `runtime-config.toml`) so they _build and validate_; push/provision are documented but live cloud/Spin stores are not stood up in CI. Handler tests wire a `ConfigRegistry` fixture directly (the app-demo -`handlers.rs` test pattern) to cover the seeded-value, empty-store (fallback), +`handlers.rs` test pattern) to cover the seeded-value, no-config (error), and malformed-value (error) branches without a live backend. ### 3.7 `mocktioneer-cli` crate @@ -313,8 +309,10 @@ and malformed-value (error) branches without a live backend. `[lints] workspace = true` (cf. `mocktioneer-core/Cargo.toml`). - `src/main.rs`: clap `Args`/`Cmd` flattening `edgezero_cli::run_{auth,build,deploy,provision,serve}` + a typed - `Config` subcommand dispatching `run_config_validate_typed::` - and `run_config_push_typed::`. + `Config` subcommand dispatching `run_config_validate_typed`, + `run_config_push_typed`, and (R8) `run_config_diff_typed::` + (the new `config diff` command — returns `DiffExit`; non-zero codes + `process::exit`, all errors exit `2`). - Add `crates/mocktioneer-cli` to root `[workspace].members`. - **Dockerfile (cache hygiene, not a correctness fix):** the build already does `COPY crates ./crates` **before** `cargo fetch --locked` (Dockerfile:21/24), @@ -394,12 +392,13 @@ and malformed-value (error) branches without a live backend. ```sh cargo run -p mocktioneer-cli -- config validate --strict printf 'bid_cpm = 0.35\n' > /tmp/seed.toml - cargo run -p mocktioneer-cli -- config push --adapter axum --app-config /tmp/seed.toml - test "$(jq -r '.bid_cpm' .edgezero/local-config-mocktioneer_config.json)" = "0.35" + cargo run -p mocktioneer-cli -- config push --adapter axum --yes --app-config /tmp/seed.toml + # R8 blob model: bid_cpm lives inside the envelope under the store key. + test "$(jq -r '.mocktioneer_config | fromjson | .data.bid_cpm' .edgezero/local-config-mocktioneer_config.json)" = "0.35" ``` The handler → response half (seeded store → `0.35`) is proven deterministically - by the registry-backed `resolve_bid_cpm` test (§5), so no flaky serve+curl is - needed in CI. + by the registry-backed handler-dispatch test (§5, `auction_uses_seeded_cpm`), + so no flaky serve+curl is needed in CI. - **Docker:** `.github/workflows/docker.yml` builds the image. The Dockerfile change (§3.7) is cache hygiene only — `COPY crates` already precedes `cargo fetch`, so the build resolves regardless. A `docker build` smoke is a @@ -415,9 +414,9 @@ and malformed-value (error) branches without a live backend. | New parser rejects an existing `[adapters.*]` table | Compile-time `manifest.validate()` surfaces it; fix per upstream error. | | Spin SDK 6 macro/type churn beyond template | Mirror edgezero's `edgezero-adapter-spin` verbatim; build wasip2. | | Wasmtime can't run the wasip2 component | Wasmtime 45.0.0 supports it; set `CARGO_TARGET_WASM32_WASIP2_RUNNER`; match edgezero's contract-test config. | -| Fresh dev errors before any push | Resolved: empty/absent → fallback to `FIXED_BID_CPM` (§3.5). | +| Fresh dev errors before any push (R8 fail-loud) | Intended: auction/APS require `config push` once per deploy; documented in `configuration.md`/README (§3.5). | | Broken/malformed pushed _value_ masked as $0.20 | Read errors propagate; malformed present value errors (§3.5). Note a malformed _file_ degrades to fallback (bind-time drop), by design. | -| `bid_cpm` never exercised (store unseeded) | CI seeds a non-default `0.35` via `--app-config` and `jq`-asserts it round-trips; registry-backed `resolve_bid_cpm` test covers the read path (§5). | +| `bid_cpm` never exercised (store unseeded) | CI seeds a non-default `0.35` via `--app-config` and `jq`-asserts it round-trips; registry-backed `auction_uses_seeded_cpm` handler test covers the read path (§5). | | Spin KV config silently empty (no `runtime-config.toml`) | Add `runtime-config.toml` + `--runtime-config-file` to spin commands (§3.6). | | Docker dependency-cache layer stale/incomplete | Cache hygiene only — `COPY crates` precedes `cargo fetch`; add the missing spin + cli manifests to the pre-copy list (§3.7). | | Spec under `docs/` fails the format CI gate | Mandatory `docs/.prettierignore` + VitePress `srcExclude` (§3.8). | @@ -442,18 +441,18 @@ and malformed-value (error) branches without a live backend. excluded — byte equality is impossible even on `main`. 9. Runtime exercise — **explicit fixtures** (the root `mocktioneer.toml` ships `bid_cpm = 0.20`, so 0.35 must come from a seeded store, not the default): - - **Registry-backed unit (preferred, deterministic, no files):** build a - `RequestContext` with a `ConfigRegistry` (public `StoreRegistry::new` + - `ConfigStoreHandle` over an in-memory `MapConfigStore`, inserted via + - **Registry-backed handler unit (R8):** build a `RequestContext` with a + `ConfigRegistry` whose default store holds a **blob envelope** for + `{ "bid_cpm": 0.35 }` (`StoreRegistry::single_id` + `ConfigStoreBinding` + + `BlobEnvelope::new` over an in-memory `MapConfigStore`, inserted via `request.extensions_mut().insert(registry)` — the app-demo - `config_flow.rs` pattern) and assert `resolve_bid_cpm(&ctx)` returns - `0.35`; a no-registry ctx → `0.20`; a `{ "bid_cpm": "-1" }` store → error. - Pair with builder tests that a supplied `cpm` (0.35) flows to the OpenRTB - bid `price` and the APS decoded price. + `config_flow.rs` pattern). Dispatch `handle_openrtb_auction` and assert the + bid `price` is `0.35`; dispatch with **no** registry and assert the handler + **errors** (fail-loud). The `0.20` default `ctx()` seeds the same way. - **CI seed path (axum):** `printf 'bid_cpm = 0.35\n' > /tmp/seed.toml` then - `config push --adapter axum --app-config /tmp/seed.toml`, and assert - `jq -r '.bid_cpm' .edgezero/local-config-mocktioneer_config.json` == `0.35` - (not a bare push + `test -f`, which would silently seed `0.20`). + `config push --adapter axum --yes --app-config /tmp/seed.toml`, and assert + `jq -r '.mocktioneer_config | fromjson | .data.bid_cpm' …` == `0.35` + (the blob envelope; not a bare push + `test -f`). 10. `docs/` gates pass: `cd docs && npm run format && npm run lint && npm run build`, **and** `find docs/.vitepress/dist docs/.vitepress/.temp -path '*superpowers*' -print -quit` produces no output (specs/plans excluded). From 15ae4609f6f7ae97c6469bf99bf63c10d835d99a Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Thu, 25 Jun 2026 17:30:06 -0700 Subject: [PATCH 30/45] fix(docker): seed default config blob into image so OpenRTB/APS serve out-of-the-box (R8 fail-loud); de-obsolete plan Task 6; clarify env-overlay docs --- Dockerfile | 15 +- docs/guide/adapters/axum.md | 14 +- docs/guide/configuration.md | 8 +- ...6-15-edgezero-extensible-cli-adaptation.md | 355 ++++-------------- mocktioneer.toml | 6 +- 5 files changed, 106 insertions(+), 292 deletions(-) diff --git a/Dockerfile b/Dockerfile index 182ce31..582023a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,9 +22,17 @@ COPY crates/mocktioneer-cli/Cargo.toml crates/mocktioneer-cli/Cargo.toml COPY crates ./crates COPY edgezero.toml ./edgezero.toml +COPY mocktioneer.toml ./mocktioneer.toml RUN cargo fetch --locked -RUN cargo build --locked --release -p mocktioneer-adapter-axum +RUN cargo build --locked --release -p mocktioneer-adapter-axum -p mocktioneer-cli + +# Seed the default typed-config blob into `.edgezero/` so the OpenRTB/APS +# endpoints serve out-of-the-box: under edgezero #269 they read `bid_cpm` +# through the fail-loud `AppConfig` extractor and 503 until a blob is pushed. +# (Override at deploy time by mounting your own +# `/app/.edgezero/local-config-mocktioneer_config.json`.) +RUN ./target/release/mocktioneer-cli config push --adapter axum --yes FROM debian:stable-slim AS runtime @@ -35,7 +43,12 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ RUN useradd --create-home --uid 10001 appuser +WORKDIR /app + COPY --from=builder /app/target/release/mocktioneer-adapter-axum /usr/local/bin/mocktioneer-adapter-axum +# The Axum config store reads `./.edgezero/local-config-.json` relative to +# the working directory, so the seeded blob must sit under the runtime WORKDIR. +COPY --from=builder --chown=10001:10001 /app/.edgezero /app/.edgezero USER appuser diff --git a/docs/guide/adapters/axum.md b/docs/guide/adapters/axum.md index e050238..e003b9b 100644 --- a/docs/guide/adapters/axum.md +++ b/docs/guide/adapters/axum.md @@ -67,7 +67,11 @@ Logs are written to stdout. Adjust `level` for more or less verbosity: ## Docker Deployment -This repository does not include a Dockerfile. If you add one, you can containerize the Axum adapter like this: +The repository ships a multi-stage `Dockerfile` (also built and pushed to +`ghcr.io/stackpop/mocktioneer` by CI). The build **seeds the default typed-config +blob** into `/app/.edgezero/`, so the OpenRTB/APS endpoints work out of the box +(they read `bid_cpm` via the fail-loud `AppConfig` extractor and would otherwise +503 until a blob is pushed): ```bash # Build the image @@ -80,6 +84,14 @@ docker run -p 8787:8787 mocktioneer:latest docker run -p 3000:8787 mocktioneer:latest ``` +To override the baked-in config, mount your own blob over the seeded file: + +```bash +docker run -p 8787:8787 \ + -v "$PWD/.edgezero/local-config-mocktioneer_config.json:/app/.edgezero/local-config-mocktioneer_config.json:ro" \ + mocktioneer:latest +``` + ## Development Workflow ### Hot Reloading diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index dc38180..0e2fcb2 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -213,8 +213,12 @@ an error (the static/creative/pixel endpoints are unaffected). `bid_cpm = 0.20` is the shipped default value, not a runtime fallback. ::: -Any key can be overridden at runtime via the `MOCKTIONEER__` env overlay -(e.g. `MOCKTIONEER__BID_CPM=0.35`). +Any key can be overridden via the `MOCKTIONEER__` env overlay +(e.g. `MOCKTIONEER__BID_CPM=0.35`). The overlay is applied **when the CLI loads +`mocktioneer.toml`** (during `config validate` / `diff` / `push`), so it changes +the value pushed into the config-store blob — set it before `config push`. It +does **not** mutate config the running server has already loaded; re-push to +roll out a change. ## Logging Configuration diff --git a/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md b/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md index 73a07a1..8fa154f 100644 --- a/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md +++ b/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md @@ -40,7 +40,7 @@ | `crates/mocktioneer-core/src/config.rs` | `MocktioneerConfig` typed config | Create | | `crates/mocktioneer-core/src/lib.rs` | `pub mod config;` | Modify | | `crates/mocktioneer-core/src/auction.rs` | Thread `cpm: f64` into bid builders | Modify | -| `crates/mocktioneer-core/src/routes.rs` | `resolve_bid_cpm` + `cpm_from_lookup` helpers; wire both handlers | Modify | +| `crates/mocktioneer-core/src/routes.rs` | Fail-loud `AppConfig` extractor on both handlers (R8) | Modify | | `mocktioneer.toml` | Typed config values (default `bid_cpm = 0.20`) | Create | | `crates/mocktioneer-cli/{Cargo.toml,src/main.rs}` | Custom CLI mirroring edgezero `-cli` | Create | | `Dockerfile` | Pre-copy `mocktioneer-cli` manifest before `cargo fetch` | Modify | @@ -475,285 +475,62 @@ git commit -m "feat: add MocktioneerConfig typed config (bid_cpm, validated)" - Modify: `crates/mocktioneer-core/src/auction.rs` (`build_openrtb_response`, `build_aps_response`, tests) - Modify: `crates/mocktioneer-core/src/routes.rs` (`handle_openrtb_auction`, `handle_aps_bid`, new helpers, imports) -- [ ] **Step 1: Write the failing pure-helper tests in `routes.rs`** - -In `crates/mocktioneer-core/src/routes.rs`, inside the existing `#[cfg(test)] mod tests { ... }` block, add: - -```rust - #[test] - fn cpm_from_lookup_falls_back_when_absent() { - assert_eq!( - cpm_from_lookup(None).unwrap().to_bits(), - crate::auction::FIXED_BID_CPM.to_bits() - ); - } - - #[test] - fn cpm_from_lookup_parses_valid_value() { - assert!((cpm_from_lookup(Some("0.35".to_owned())).unwrap() - 0.35).abs() < f64::EPSILON); - } - - #[test] - fn cpm_from_lookup_rejects_bad_values() { - for bad in ["-1", "0", "abc", "inf", "NaN", ""] { - assert!( - cpm_from_lookup(Some(bad.to_owned())).is_err(), - "expected {bad:?} to error" - ); - } - } -``` - -- [ ] **Step 2: Run to verify failure** - -Run: `cargo test -p mocktioneer-core routes::tests::cpm_from_lookup 2>&1 | tail -20` -Expected: FAIL — `cannot find function cpm_from_lookup`. - -- [ ] **Step 3: Add the resolution helpers + import** - -In `crates/mocktioneer-core/src/routes.rs`, extend the auction import to include `FIXED_BID_CPM`: - -```rust -use crate::auction::{ - build_aps_response, build_openrtb_response, is_standard_size, standard_sizes, FIXED_BID_CPM, -}; -``` - -Add these two functions near the handlers (module scope, not inside a fn): - -```rust -/// Interpret a config-store lookup for `bid_cpm`. Pure (no `ctx`) so it is -/// unit-testable. `None` (store absent / unseeded / key missing) → default; -/// a present-but-unparseable / non-finite / ≤0 value → error. -fn cpm_from_lookup(found: Option) -> Result { - match found { - None => Ok(FIXED_BID_CPM), - Some(raw) => raw - .parse::() - .ok() - .filter(|value| value.is_finite() && *value > 0.0) - .ok_or_else(|| { - EdgeError::internal(anyhow::anyhow!( - "config store `mocktioneer_config` has malformed bid_cpm: {raw:?}" - )) - }), - } -} - -/// Resolve the effective bid CPM from the bound default config store, falling -/// back to `FIXED_BID_CPM` when no store is bound. A backend read error -/// propagates via `From`. -async fn resolve_bid_cpm(ctx: &RequestContext) -> Result { - let Some(store) = ctx.config_store_default() else { - return Ok(FIXED_BID_CPM); - }; - cpm_from_lookup(store.get("bid_cpm").await.map_err(EdgeError::from)?) -} -``` - -- [ ] **Step 4: Run the pure-helper tests (expect PASS)** - -Run: `cargo test -p mocktioneer-core routes::tests::cpm_from_lookup` -Expected: PASS. - -- [ ] **Step 4b: Add registry-backed `resolve_bid_cpm` tests (spec §5 preferred fixture)** - -Exercises the real `RequestContext` → `ConfigRegistry` → `config_store_default()` → `store.get` path with an in-memory store (the app-demo `config_flow.rs` pattern; all types are public, no `test-utils` feature). Add to the `#[cfg(test)] mod tests` block in `crates/mocktioneer-core/src/routes.rs`: - -```rust - // In-memory ConfigStore for tests (mirrors app-demo's MapConfigStore). - struct MapConfigStore(std::collections::HashMap); - - // `ConfigStore` is declared `#[async_trait(?Send)]` in edgezero-core, so - // the impl MUST use the same `(?Send)` mode or method signatures won't match. - #[async_trait(?Send)] - impl edgezero_core::config_store::ConfigStore for MapConfigStore { - async fn get( - &self, - key: &str, - ) -> Result, edgezero_core::config_store::ConfigStoreError> { - Ok(self.0.get(key).cloned()) - } - } - - fn ctx_with_config(pairs: &[(&str, &str)]) -> RequestContext { - use edgezero_core::config_store::ConfigStoreHandle; - use edgezero_core::store_registry::{ConfigRegistry, StoreRegistry}; - use std::collections::{BTreeMap, HashMap}; - use std::sync::Arc; - - let map: HashMap = pairs - .iter() - .map(|(k, v)| ((*k).to_owned(), (*v).to_owned())) - .collect(); - let handle = ConfigStoreHandle::new(Arc::new(MapConfigStore(map))); - let by_id: BTreeMap = - [("mocktioneer_config".to_owned(), handle)].into_iter().collect(); - let registry: ConfigRegistry = - StoreRegistry::new(by_id, "mocktioneer_config".to_owned()); - - let mut request = request_builder() - .method(Method::GET) - .uri("/") - .body(Body::empty()) - .expect("request"); - request.extensions_mut().insert(registry); - RequestContext::new(request, PathParams::new(std::collections::HashMap::new())) - } - - #[test] - fn resolve_bid_cpm_reads_seeded_store() { - let ctx = ctx_with_config(&[("bid_cpm", "0.35")]); - let cpm = futures::executor::block_on(resolve_bid_cpm(&ctx)).unwrap(); - assert!((cpm - 0.35).abs() < f64::EPSILON); - } - - #[test] - fn resolve_bid_cpm_falls_back_without_registry() { - let request = request_builder() - .method(Method::GET) - .uri("/") - .body(Body::empty()) - .expect("request"); - let ctx = RequestContext::new(request, PathParams::new(std::collections::HashMap::new())); - let cpm = futures::executor::block_on(resolve_bid_cpm(&ctx)).unwrap(); - assert_eq!(cpm.to_bits(), FIXED_BID_CPM.to_bits()); - } - - #[test] - fn resolve_bid_cpm_errors_on_malformed_value() { - let ctx = ctx_with_config(&[("bid_cpm", "-1")]); - assert!(futures::executor::block_on(resolve_bid_cpm(&ctx)).is_err()); - } -``` - -Note: `async_trait`, `request_builder`, `Method`, `Body`, `PathParams` are already imported in the test module (confirmed). `futures` is a dev-dependency of `mocktioneer-core`. - -Run: `cargo test -p mocktioneer-core routes::tests::resolve_bid_cpm` -Expected: PASS (after Step 3's helpers exist). - -- [ ] **Step 5: Add `cpm: f64` to the bid builders (write the new builder tests first)** - -In `crates/mocktioneer-core/src/auction.rs`, inside its `#[cfg(test)] mod tests`, add: - -```rust - #[test] - fn openrtb_uses_supplied_cpm() { - let req = OpenRTBRequest { - id: "rc".to_owned(), - imp: vec![OpenrtbImp { - id: "1".to_owned(), - banner: Some(Banner { - width: Some(300), - height: Some(250), - ..Default::default() - }), - ..Default::default() - }], - ..Default::default() - }; - let resp = build_openrtb_response(&req, "host.test", test_signature(), 0.35); - assert!((resp.seatbid[0].bid[0].price - 0.35).abs() < f64::EPSILON); - } - - #[test] - fn aps_uses_supplied_cpm() { - let req = ApsBidRequest { - pub_id: "test".to_owned(), - slots: vec![ApsSlot { - slot_id: "slot1".to_owned(), - sizes: vec![[300, 250]], - slot_name: None, - }], - page_url: None, - user_agent: None, - timeout: None, - }; - let resp = build_aps_response(&req, "mock.test", 0.35); - let slot = &resp.contextual.slots[0]; - let price = decode_aps_price(slot.amznbid.as_ref().unwrap()).unwrap(); - assert!((price - 0.35).abs() < f64::EPSILON); - } -``` - -- [ ] **Step 6: Change the builder signatures + bodies** - -In `build_openrtb_response`, add the param and use it: - -```rust -pub fn build_openrtb_response( - req: &OpenRTBRequest, - base_host: &str, - signature_status: SignatureStatus, - cpm: f64, -) -> OpenRTBResponse { -``` - -Replace the deprecation `log::warn!` argument `FIXED_BID_CPM` with `cpm`, and replace `let price = FIXED_BID_CPM;` (≈line 120) with `let price = cpm;`. - -In `build_aps_response`: - -```rust -pub fn build_aps_response(req: &ApsBidRequest, base_host: &str, cpm: f64) -> ApsBidResponse { -``` - -Replace `let price = FIXED_BID_CPM;` (≈line 259) with `let price = cpm;`. - -- [ ] **Step 7: Update ALL auction.rs builder call sites in tests** - -There are **many** test call sites, not two. Enumerate them: - -Run: `grep -rn "build_openrtb_response\|build_aps_response" crates/mocktioneer-core/src/auction.rs | grep -v "pub fn "` -Expected: ~9 test call sites (e.g. lines ~344, 368, 389, 409, 436, 455, 488, 510, 563). - -Append `, FIXED_BID_CPM` to every `build_openrtb_response(&req, "host.test", test_signature())` and every `build_aps_response(&req, "mock.test")` call in `auction.rs` tests. They keep asserting against `FIXED_BID_CPM`, which is now the value they pass in. - -**Do NOT touch `crates/mocktioneer-core/src/mediation.rs:183/187`** — that is a _separate, private_ `build_openrtb_response(request.id, request.imp, winning_bids, base_host)` for the mediation path. It is fed by request bids, never `FIXED_BID_CPM`, and is out of scope for `cpm`. - -Run after editing: `cargo build -p mocktioneer-core 2>&1 | grep -c "this function takes" || echo "no arity errors"` to confirm no missed call sites. - -- [ ] **Step 8: Wire the handlers** - -In `crates/mocktioneer-core/src/routes.rs`: - -`handle_openrtb_auction` — resolve cpm and pass it (it already binds `RequestContext(ctx)`): - -```rust - let cpm = resolve_bid_cpm(&ctx).await?; - log::info!("auction id={}, imps={}", req.id, req.imp.len()); - let resp = build_openrtb_response(&req, &host, signature_status, cpm); -``` - -`handle_aps_bid` — add the `RequestContext(ctx)` extractor and resolve cpm: - -```rust -#[action] -pub async fn handle_aps_bid( - RequestContext(ctx): RequestContext, - ForwardedHost(host): ForwardedHost, - ValidatedJson(req): ValidatedJson, -) -> Result { - log::info!( - "APS auction pubId={}, slots={}", - req.pub_id, - req.slots.len() - ); - - let cpm = resolve_bid_cpm(&ctx).await?; - let resp = build_aps_response(&req, &host, cpm); -``` - -- [ ] **Step 9: Run the full core test suite** - -Run: `cargo test -p mocktioneer-core` -Expected: PASS (new builder tests + updated existing tests + config tests). - -- [ ] **Step 10: Commit** - -```bash -git add crates/mocktioneer-core/src/auction.rs crates/mocktioneer-core/src/routes.rs -git commit -m "feat: resolve bid_cpm from config store at runtime (OpenRTB + APS), default FIXED_BID_CPM" -``` +### As built (R8 fail-loud blob model) + +> The original step-by-step (pure `cpm_from_lookup`, `resolve_bid_cpm`, and a +> `None → FIXED_BID_CPM` fallback) was **removed** — it predates the blob +> cutover and must not be implemented. The behaviour that shipped: + +1. **Builders take `cpm: f64`.** `build_openrtb_response(req, host, sig, cpm)` + and `build_aps_response(req, host, cpm)` replace their direct `FIXED_BID_CPM` + reads with the parameter; `FIXED_BID_CPM` stays the builders' default arg + + the shipped `mocktioneer.toml` value. Every `auction.rs` / + `tests/aps_endpoints.rs` call site passes `FIXED_BID_CPM` explicitly (and do + **not** touch the separate `mediation.rs::build_openrtb_response`). + +2. **Handlers read typed config via the fail-loud `AppConfig` extractor.** Add + `use edgezero_core::extractor::AppConfig;` and + `use crate::config::MocktioneerConfig;`, then: + + ```rust + #[action] + pub async fn handle_openrtb_auction( + RequestContext(ctx): RequestContext, + ForwardedHost(host): ForwardedHost, + ValidatedJson(req): ValidatedJson, + AppConfig(cfg): AppConfig, + ) -> Result { /* … build_openrtb_response(&req, &host, sig, cfg.bid_cpm) */ } + + #[action] + pub async fn handle_aps_bid( + ForwardedHost(host): ForwardedHost, + ValidatedJson(req): ValidatedJson, + AppConfig(cfg): AppConfig, + ) -> Result { /* … build_aps_response(&req, &host, cfg.bid_cpm) */ } + ``` + + With no store bound / no blob pushed, the extractor errors + (`config_out_of_date`) — auction/APS require a `config push`. `FIXED_BID_CPM` + is dropped from `routes.rs` imports (no longer referenced there). + +3. **Tests seed a blob.** The `routes.rs` test `ctx()` helper seeds the default + config store with a blob envelope (`StoreRegistry::single_id` + + `ConfigStoreBinding` + `BlobEnvelope::new` over an in-memory `MapConfigStore` + holding `{ "mocktioneer_config": "" }`), so the + existing handler tests keep exercising their 400/422 paths. Add + `auction_uses_seeded_cpm` (seed `0.35` → assert bid `price == 0.35`) and + `auction_without_config_errors` (no registry → handler errors). The + `tests/endpoints.rs` router auction test inserts the same registry. + +4. **Verify + commit.** + + Run: `cargo test -p mocktioneer-core && cargo clippy -p mocktioneer-core --all-targets --all-features -- -D warnings` + Expected: PASS. + + ```bash + git add crates/mocktioneer-core/src/auction.rs crates/mocktioneer-core/src/routes.rs crates/mocktioneer-core/tests/endpoints.rs + git commit -m "feat: resolve bid_cpm from typed config (fail-loud AppConfig extractor, blob model)" + ``` --- @@ -1076,7 +853,7 @@ Add a step (in the existing native test job, after `cargo test`): rm -f .edgezero/local-config-mocktioneer_config.json ``` -A **bare** `config push --adapter axum` would silently seed the root `mocktioneer.toml` default (`0.20`) and a `test -f` only proves a file exists — it would pass even if `bid_cpm` were never wired. Seeding `0.35` via `--app-config` and asserting the JSON value with `jq` proves push writes the _configured_ value. The handler → response half (a seeded store yielding `0.35`) is proven deterministically by the registry-backed `resolve_bid_cpm` test (Task 6, Step 4b), so no flaky serve+curl is needed here. (`jq` is preinstalled on GitHub `ubuntu-latest`.) +A **bare** `config push --adapter axum` would silently seed the root `mocktioneer.toml` default (`0.20`) and a `test -f` only proves a file exists — it would pass even if `bid_cpm` were never wired. Seeding `0.35` via `--app-config` and asserting the JSON value with `jq` proves push writes the _configured_ value. The handler → response half (a seeded store yielding `0.35`) is proven deterministically by the registry-backed `auction_uses_seeded_cpm` handler test (R8), so no flaky serve+curl is needed here. (`jq` is preinstalled on GitHub `ubuntu-latest`.) - [ ] **Step 4: Lint the workflow locally** @@ -1145,9 +922,15 @@ Expected: PASS. Run: `find docs/.vitepress/dist docs/.vitepress/.temp -path '*superpowers*' -print -quit` Expected: **no output** (specs/plans excluded from the published build). -- [ ] **Step 6: Semantic parity vs `main` (no store bound)** +- [ ] **Step 6: Semantic parity vs `main` (R8: seed config first)** -With no `.edgezero/local-config-*` present, hit `/openrtb2/auction` and `/e/dtb/bid` (via `cargo run -p mocktioneer-adapter-axum`) and confirm prices are `0.20` and the responses match `main` on semantic fields (price, sizes, `cur`, creative URLs, targeting) — IDs differ by design (`Uuid::now_v7()`). +Under R8 the auction/APS endpoints are fail-loud, so seed first: +`cargo run -p mocktioneer-cli -- config push --adapter axum --yes`. Then (via +`cargo run -p mocktioneer-adapter-axum`) hit `/openrtb2/auction` and +`/e/dtb/bid` and confirm prices are `0.20` and the responses match `main` on +semantic fields (price, sizes, `cur`, creative URLs, targeting) — IDs differ by +design (`Uuid::now_v7()`). Without a pushed blob both endpoints return +`config_out_of_date` (expected). - [ ] **Step 7: Final commit (if any verification fixups were needed)** @@ -1164,14 +947,14 @@ git commit -m "chore: verification fixups for edgezero #269 adaptation" - **§3.2 adapter entrypoints** → Tasks 2 (axum/cf/fastly) + 3 (spin). ✓ - **§3.3 Spin wasip2 + runner** → Task 3 (adapter) + Task 12 (CI runner env). ✓ - **§3.4 manifest `[stores.config]`** → Task 4. ✓ -- **§3.5 typed struct + runtime contract (incl. malformed-file vs read-error)** → Task 5 (struct) + Task 6 (`cpm_from_lookup`/`resolve_bid_cpm`; `None`→fallback covers absent/empty/missing-key; malformed value→error; read error→`From`). ✓ +- **§3.5 typed struct + runtime contract** → Task 5 (struct) + Task 6 (R8: fail-loud `AppConfig` extractor; no pushed blob → error; invalid value → validation error). ✓ - **§3.6 config lifecycle (per-adapter backing, spin runtime-config)** → Task 3 (spin runtime-config.toml) + Task 4 (commands) + Task 8 (push) + Task 12 (axum seed). Cloud/Spin provision documented, not CI-stood-up, per spec. ✓ - **§3.7 mocktioneer-cli (metadata/lints) + Dockerfile** → Task 8 + Task 9. ✓ - **§3.8 docs (wasip2, pricing default, CLI story, gitignore, check-ci, prettier/VitePress)** → Task 0 (prettier/VitePress) + Task 10 (gitignore) + Task 11 (rest). ✓ - **§3.9 CI** → Task 12. ✓ -- **§5 verification (incl. explicit seeded fixture, docker, prettier)** → Task 13 (+ pure `cpm_from_lookup` tests **and** registry-backed `resolve_bid_cpm` tests in Task 6 Step 4b covering seeded-0.35 / fallback / malformed-error; non-default `0.35` seed + `jq` assert in Tasks 12/13; docs build-exclusion `find` check in Tasks 0/13). ✓ +- **§5 verification (incl. explicit seeded fixture, docker, prettier)** → Task 13 (+ R8 registry-backed handler tests in Task 6 covering seeded-0.35 and no-config-error; non-default `0.35` seed + `jq` envelope assert in Tasks 12/13; docs build-exclusion `find` check in Tasks 0/13). ✓ -No placeholders; types/functions (`MocktioneerConfig`, `cpm_from_lookup`, `resolve_bid_cpm`, builder signatures with `cpm: f64`) are consistent across tasks. +No placeholders; types/functions (`MocktioneerConfig`, the `AppConfig` extractor, builder signatures with `cpm: f64`) are consistent across tasks (R8). --- diff --git a/mocktioneer.toml b/mocktioneer.toml index 59bf59d..457971e 100644 --- a/mocktioneer.toml +++ b/mocktioneer.toml @@ -1,4 +1,6 @@ # Typed application config for Mocktioneer (maps 1:1 onto MocktioneerConfig). -# `bid_cpm` is the default; override per-environment via `config push` or the -# `MOCKTIONEER__BID_CPM` env overlay. Default keeps historical $0.20 behavior. +# `bid_cpm` is the shipped default. To change it per-environment, set +# `MOCKTIONEER__BID_CPM` (the env overlay is applied when the CLI loads this +# file) and/or edit it, then `config push` — the overlay affects the value the +# CLI pushes into the config-store blob, NOT already-pushed runtime config. bid_cpm = 0.20 From 92b21c30002e22995d4c91acdcfee57b1e91a34f Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Fri, 26 Jun 2026 10:50:30 -0700 Subject: [PATCH 31/45] refactor: validate bid_cpm with range(exclusive_min) instead of custom fn --- crates/mocktioneer-core/src/config.rs | 26 ++++++++----------- ...6-15-edgezero-extensible-cli-adaptation.md | 19 +++++--------- ...gezero-extensible-cli-adaptation-design.md | 20 +++++--------- 3 files changed, 23 insertions(+), 42 deletions(-) diff --git a/crates/mocktioneer-core/src/config.rs b/crates/mocktioneer-core/src/config.rs index 0cc206b..c5873b1 100644 --- a/crates/mocktioneer-core/src/config.rs +++ b/crates/mocktioneer-core/src/config.rs @@ -3,26 +3,19 @@ //! `bid_cpm` field; `config validate --strict` enforces the rules below. use serde::{Deserialize, Serialize}; -use validator::{Validate, ValidationError}; +use validator::Validate; #[derive(Debug, Deserialize, Serialize, Validate, edgezero_core::AppConfig)] #[serde(deny_unknown_fields)] pub struct MocktioneerConfig { - /// Fixed bid CPM in USD. Validated finite and strictly positive. - #[validate(custom(function = "validate_bid_cpm"))] + /// Fixed bid CPM in USD. Must be strictly positive. `exclusive_min` also + /// rejects `0.0`, negatives, and `NaN` (any comparison with `NaN` is + /// false); non-finite floats are additionally rejected by edgezero's typed + /// config loader before this runs. + #[validate(range(exclusive_min = 0.0_f64))] pub bid_cpm: f64, } -/// `validator` passes `Copy` scalar fields (like `f64`) by value to custom -/// fns; `range` does not reject NaN / inf for floats, so validate explicitly. -fn validate_bid_cpm(value: f64) -> Result<(), ValidationError> { - if value.is_finite() && value > 0.0 { - Ok(()) - } else { - Err(ValidationError::new("bid_cpm_must_be_finite_positive")) - } -} - #[cfg(test)] mod tests { use super::*; @@ -34,8 +27,11 @@ mod tests { } #[test] - fn rejects_zero_negative_and_non_finite() { - for bad in [0.0_f64, -1.0_f64, f64::NAN, f64::INFINITY] { + fn rejects_zero_negative_and_nan() { + // `range(exclusive_min = 0.0)` rejects these (NaN fails every + // comparison). `+Inf` passes the range check but is rejected upstream + // by edgezero's non-finite-float loader, so it is not asserted here. + for bad in [0.0_f64, -1.0_f64, f64::NAN] { let cfg = MocktioneerConfig { bid_cpm: bad }; assert!(cfg.validate().is_err(), "expected {bad} to be rejected"); } diff --git a/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md b/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md index 8fa154f..08b1700 100644 --- a/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md +++ b/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md @@ -398,26 +398,19 @@ Create `crates/mocktioneer-core/src/config.rs`: //! `bid_cpm` field; `config validate --strict` enforces the rules below. use serde::{Deserialize, Serialize}; -use validator::{Validate, ValidationError}; +use validator::Validate; #[derive(Debug, Deserialize, Serialize, Validate, edgezero_core::AppConfig)] #[serde(deny_unknown_fields)] pub struct MocktioneerConfig { - /// Fixed bid CPM in USD. Validated finite and strictly positive. - #[validate(custom(function = "validate_bid_cpm"))] + /// Fixed bid CPM in USD. Strictly positive (`exclusive_min` rejects 0.0, + /// negatives, and NaN; non-finite floats also rejected by edgezero's + /// loader). `_f64` suffix satisfies the strict `default_numeric_fallback` + /// clippy lint. + #[validate(range(exclusive_min = 0.0_f64))] pub bid_cpm: f64, } -/// `validator` custom fns take a reference to the field; `range` does not -/// reject NaN / inf for floats, so validate explicitly. -fn validate_bid_cpm(value: &f64) -> Result<(), ValidationError> { - if value.is_finite() && *value > 0.0 { - Ok(()) - } else { - Err(ValidationError::new("bid_cpm_must_be_finite_positive")) - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/docs/superpowers/specs/2026-06-11-edgezero-extensible-cli-adaptation-design.md b/docs/superpowers/specs/2026-06-11-edgezero-extensible-cli-adaptation-design.md index 3aec822..f2317e9 100644 --- a/docs/superpowers/specs/2026-06-11-edgezero-extensible-cli-adaptation-design.md +++ b/docs/superpowers/specs/2026-06-11-edgezero-extensible-cli-adaptation-design.md @@ -180,26 +180,18 @@ disturb Fastly's wasip1 Viceroy runner. ```rust use serde::{Deserialize, Serialize}; -use validator::{Validate, ValidationError}; +use validator::Validate; #[derive(Debug, Deserialize, Serialize, Validate, edgezero_core::AppConfig)] #[serde(deny_unknown_fields)] pub struct MocktioneerConfig { - /// Fixed bid CPM in USD. Validated finite and strictly positive. - #[validate(custom(function = "validate_bid_cpm"))] + /// Fixed bid CPM in USD. Strictly positive — `exclusive_min` rejects + /// `0.0`, negatives, and `NaN`; non-finite floats are also rejected by + /// edgezero's typed loader. (`_f64` suffix satisfies the strict + /// `default_numeric_fallback` clippy lint.) + #[validate(range(exclusive_min = 0.0_f64))] pub bid_cpm: f64, } - -// `validator` custom fns take a reference to the field (cf. -// routes.rs `validate_static_asset_size(value: &str)`); `range` does not -// reject NaN / inf for floats, so validate explicitly. -fn validate_bid_cpm(value: &f64) -> Result<(), ValidationError> { - if value.is_finite() && *value > 0.0 { - Ok(()) - } else { - Err(ValidationError::new("bid_cpm_must_be_finite_positive")) - } -} ``` - `creative_label` (R1 draft) removed — YAGNI/unused. From 30025d2061565b370b628582324dbcdb99adfb18 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Wed, 1 Jul 2026 21:01:59 -0700 Subject: [PATCH 32/45] docs: config-push prerequisite across API/integration/adapter pages; adopt mocktioneer.toml.example (gitignore local, CI/Docker copy from template) --- .github/workflows/test.yml | 7 ++++- .gitignore | 12 +++++++-- Dockerfile | 4 ++- README.md | 3 +++ docs/api/aps-bid.md | 12 +++++++++ docs/api/openrtb-auction.md | 14 ++++++++++ docs/guide/adapters/axum.md | 9 ++++++- docs/guide/adapters/cloudflare.md | 27 ++++++++++++++++++++ docs/guide/adapters/fastly.md | 11 ++++++++ docs/guide/adapters/index.md | 8 +++--- docs/guide/configuration.md | 7 +++++ docs/guide/getting-started.md | 4 ++- docs/integrations/prebid-server.md | 8 ++++++ docs/integrations/prebidjs.md | 8 ++++++ mocktioneer.toml => mocktioneer.toml.example | 0 15 files changed, 125 insertions(+), 9 deletions(-) rename mocktioneer.toml => mocktioneer.toml.example (100%) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 95f2298..85a5a90 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -53,7 +53,12 @@ jobs: run: cargo check --workspace --all-targets --features "fastly cloudflare" - name: Validate typed app config - run: cargo run -p mocktioneer-cli -- config validate --strict + run: | + # `mocktioneer.toml` is gitignored (per-env); the committed template is + # `mocktioneer.toml.example`. Materialise it so `config validate` (which + # reads the `.toml` default path) has a file to check. + cp mocktioneer.toml.example mocktioneer.toml + cargo run -p mocktioneer-cli -- config validate --strict - name: Seed a non-default cpm and assert it round-trips (axum) run: | diff --git a/.gitignore b/.gitignore index e0f7f2f..eb9f3a5 100644 --- a/.gitignore +++ b/.gitignore @@ -33,12 +33,20 @@ node_modules/ playwright-report/ test-results/ +# EdgeZero local config/kv state (config push --adapter axum, etc.) +.edgezero/ + +# Axum +axum.toml + # Cloudflare Workers .wrangler/ +# Fastly +fastly.toml # Spin .spin/ -# EdgeZero local config/kv state (config push --adapter axum, etc.) -.edgezero/ +# Mocktioneer +mocktioneer.toml diff --git a/Dockerfile b/Dockerfile index 582023a..57e62a4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,7 +22,9 @@ COPY crates/mocktioneer-cli/Cargo.toml crates/mocktioneer-cli/Cargo.toml COPY crates ./crates COPY edgezero.toml ./edgezero.toml -COPY mocktioneer.toml ./mocktioneer.toml +# `mocktioneer.toml` is gitignored (per-env); ship the committed template as the +# config the image seeds from. +COPY mocktioneer.toml.example ./mocktioneer.toml RUN cargo fetch --locked RUN cargo build --locked --release -p mocktioneer-adapter-axum -p mocktioneer-cli diff --git a/README.md b/README.md index cf5215a..0b53338 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,9 @@ Deterministic OpenRTB banner bidder for edge platforms. Test client integrations git clone https://github.com/stackpop/mocktioneer.git cd mocktioneer +# Create your local app config from the template (mocktioneer.toml is gitignored) +cp mocktioneer.toml.example mocktioneer.toml + # Seed the typed app config once (the auction/APS endpoints read bid_cpm from it # via the fail-loud AppConfig extractor and error until it is pushed). cargo run -p mocktioneer-cli -- config push --adapter axum --yes diff --git a/docs/api/aps-bid.md b/docs/api/aps-bid.md index a621c61..79b8954 100644 --- a/docs/api/aps-bid.md +++ b/docs/api/aps-bid.md @@ -2,6 +2,18 @@ The `/e/dtb/bid` endpoint accepts Amazon Publisher Services (APS) Transparent Ad Marketplace bid requests and returns bids in APS format. +::: warning Requires a pushed config +Like the OpenRTB endpoint, this reads `bid_cpm` from the typed app config via the +fail-loud `AppConfig` extractor and returns an error until the config blob is +pushed once for the target adapter: + +```bash +cargo run -p mocktioneer-cli -- config push --adapter axum --yes +``` + +See [Configuration › Typed App Config](/guide/configuration#typed-app-config). +::: + ## Endpoint ``` diff --git a/docs/api/openrtb-auction.md b/docs/api/openrtb-auction.md index 33a6ffc..c7bf8d0 100644 --- a/docs/api/openrtb-auction.md +++ b/docs/api/openrtb-auction.md @@ -2,6 +2,20 @@ The `/openrtb2/auction` endpoint accepts OpenRTB 2.x bid requests and returns deterministic bid responses. +::: warning Requires a pushed config +This endpoint reads `bid_cpm` from the typed app config via the fail-loud +`AppConfig` extractor, so it returns an error (`config_out_of_date`) until the +config blob has been pushed once for the target adapter: + +```bash +cargo run -p mocktioneer-cli -- config push --adapter axum --yes +``` + +See [Configuration › Typed App Config](/guide/configuration#typed-app-config) +for per-adapter details. (Static, `/pixel`, and `/_/sizes` endpoints work +without it.) +::: + ## Endpoint ``` diff --git a/docs/guide/adapters/axum.md b/docs/guide/adapters/axum.md index e003b9b..3fefbd0 100644 --- a/docs/guide/adapters/axum.md +++ b/docs/guide/adapters/axum.md @@ -14,10 +14,17 @@ The Axum adapter runs Mocktioneer as a native Rust HTTP server. It's the recomme ## Quick Start ```bash +# One-time: create the local config and push it (auction/APS are fail-loud) +cp mocktioneer.toml.example mocktioneer.toml +cargo run -p mocktioneer-cli -- config push --adapter axum --yes + cargo run -p mocktioneer-adapter-axum ``` -The server starts at `http://127.0.0.1:8787`. +The server starts at `http://127.0.0.1:8787`. Axum reads the pushed blob from +`./.edgezero/local-config-mocktioneer_config.json` **once at startup**, so +re-run `config push` and restart after changing `bid_cpm`. (Static/pixel/sizes +endpoints work without a pushed config.) ## Using the CLI diff --git a/docs/guide/adapters/cloudflare.md b/docs/guide/adapters/cloudflare.md index d5e3d47..2ece695 100644 --- a/docs/guide/adapters/cloudflare.md +++ b/docs/guide/adapters/cloudflare.md @@ -55,6 +55,33 @@ wrangler dev --config crates/mocktioneer-adapter-cloudflare/wrangler.toml This starts a local server that emulates the Workers environment. +::: warning Config is KV-backed on Cloudflare +Unlike Axum (local file) and Fastly, Cloudflare reads `bid_cpm` from a **KV +namespace**, so `/openrtb2/auction` and `/e/dtb/bid` are fail-loud until the +config blob is pushed into that namespace. The KV namespace must be bound in +`wrangler.toml` as `[[kv_namespaces]] binding = "mocktioneer_config"`: + +- **Remote/deploy:** `mocktioneer-cli provision --adapter cloudflare` creates the + namespace and writes the binding into `wrangler.toml`, then + `config push --adapter cloudflare`. +- **Local (`wrangler dev`):** `provision` is remote-only (it calls the Cloudflare + API). For a local-only KV, add the binding to `wrangler.toml` manually with a + placeholder id, then push to local state: + + ```toml + [[kv_namespaces]] + binding = "mocktioneer_config" + id = "local-dev-placeholder" + ``` + + ```bash + cp mocktioneer.toml.example mocktioneer.toml + cargo run -p mocktioneer-cli -- config push --adapter cloudflare --local + ``` + +Static/pixel/sizes endpoints work without any of this. +::: + ## Building ```bash diff --git a/docs/guide/adapters/fastly.md b/docs/guide/adapters/fastly.md index 7e9bb8b..012ee40 100644 --- a/docs/guide/adapters/fastly.md +++ b/docs/guide/adapters/fastly.md @@ -30,6 +30,17 @@ The Fastly adapter runs Mocktioneer on Fastly's Compute platform, providing glob ## Local Development +::: tip Push the config first +`/openrtb2/auction` and `/e/dtb/bid` are fail-loud — push the typed config to +the Fastly config store before serving (static/pixel/sizes work without it): + +```bash +cp mocktioneer.toml.example mocktioneer.toml +cargo run -p mocktioneer-cli -- config push --adapter fastly --local +``` + +::: + Run locally using Fastly's Viceroy runtime: ```bash diff --git a/docs/guide/adapters/index.md b/docs/guide/adapters/index.md index 38849c5..b206360 100644 --- a/docs/guide/adapters/index.md +++ b/docs/guide/adapters/index.md @@ -97,13 +97,15 @@ library — no separate install needed: cargo run -p mocktioneer-cli -- serve --adapter cloudflare # typed config (lives only here — validated against MocktioneerConfig) +cp mocktioneer.toml.example mocktioneer.toml # gitignored per-env copy cargo run -p mocktioneer-cli -- config validate --strict -cargo run -p mocktioneer-cli -- config push --adapter axum +cargo run -p mocktioneer-cli -- config diff --adapter axum +cargo run -p mocktioneer-cli -- config push --adapter axum --yes ``` `serve`/`build`/`deploy`/`auth`/`provision` work from either the external -`edgezero-cli` or `mocktioneer-cli`; the typed `config validate` / `config push` -commands are only in `mocktioneer-cli`. +`edgezero-cli` or `mocktioneer-cli`; the typed `config validate` / `config diff` / +`config push` commands are only in `mocktioneer-cli`. ## Common Configuration diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index 0e2fcb2..a447ce9 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -191,6 +191,13 @@ there is no `[config]` wrapper: bid_cpm = 0.20 ``` +`mocktioneer.toml` is **gitignored** (per-environment); the repo commits +`mocktioneer.toml.example` as the template. Create your local copy first: + +```bash +cp mocktioneer.toml.example mocktioneer.toml # then edit bid_cpm as needed +``` + Validate it, preview the diff against the live store, then push it: ```bash diff --git a/docs/guide/getting-started.md b/docs/guide/getting-started.md index 8ba6de8..b351124 100644 --- a/docs/guide/getting-started.md +++ b/docs/guide/getting-started.md @@ -51,9 +51,11 @@ cargo run -p mocktioneer-cli -- config validate --strict > **Seed the app config first.** The OpenRTB (`/openrtb2/auction`) and APS > (`/e/dtb/bid`) endpoints read `bid_cpm` from the typed config via the > fail-loud `AppConfig` extractor, so they error until you push the config once -> per adapter: +> per adapter. `mocktioneer.toml` is gitignored (per-env); create it from the +> committed template, then push: > > ```bash +> cp mocktioneer.toml.example mocktioneer.toml # then edit bid_cpm if desired > cargo run -p mocktioneer-cli -- config push --adapter axum --yes > ``` > diff --git a/docs/integrations/prebid-server.md b/docs/integrations/prebid-server.md index d0e84d4..453cf4b 100644 --- a/docs/integrations/prebid-server.md +++ b/docs/integrations/prebid-server.md @@ -8,6 +8,14 @@ The Mocktioneer adapter is not yet merged into upstream Prebid Server. Use the S **[github.com/stackpop/prebid-server](https://github.com/stackpop/prebid-server)** ::: +::: tip Push the config first +Mocktioneer's auction endpoint (`/openrtb2/auction`) only returns bids after the +typed config has been pushed once (`mocktioneer-cli config push --adapter `). +Do this before adding Mocktioneer as a bidder, or Prebid Server will see errors +instead of bids. See +[Configuration › Typed App Config](/guide/configuration#typed-app-config). +::: + ## Configuration ### Host Configuration diff --git a/docs/integrations/prebidjs.md b/docs/integrations/prebidjs.md index d1e84f4..406630d 100644 --- a/docs/integrations/prebidjs.md +++ b/docs/integrations/prebidjs.md @@ -8,6 +8,14 @@ The Mocktioneer adapter is not yet merged into upstream Prebid.js. Use the Stack **[github.com/stackpop/Prebid.js](https://github.com/stackpop/Prebid.js)** ::: +::: tip Push the config first +Mocktioneer's auction endpoint (`/openrtb2/auction`) only returns bids after the +typed config has been pushed once (`mocktioneer-cli config push --adapter `). +Do this before pointing Prebid.js at your Mocktioneer instance, or the adapter +will see errors instead of bids. See +[Configuration › Typed App Config](/guide/configuration#typed-app-config). +::: + ## Installation Clone and build from the Stackpop fork: diff --git a/mocktioneer.toml b/mocktioneer.toml.example similarity index 100% rename from mocktioneer.toml rename to mocktioneer.toml.example From 48473b3a3c6dddd570699e00d676b36da1af5c7d Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Wed, 1 Jul 2026 21:06:54 -0700 Subject: [PATCH 33/45] Updated config files --- .gitignore | 8 +- Cargo.lock | 424 ++++-------- Cargo.toml | 12 +- crates/mocktioneer-core/src/routes.rs | 4 +- docs/package-lock.json | 950 +++++++++++++------------- edgezero.toml | 1 + 6 files changed, 644 insertions(+), 755 deletions(-) diff --git a/.gitignore b/.gitignore index eb9f3a5..20304b4 100644 --- a/.gitignore +++ b/.gitignore @@ -36,15 +36,9 @@ test-results/ # EdgeZero local config/kv state (config push --adapter axum, etc.) .edgezero/ -# Axum -axum.toml - -# Cloudflare Workers +# Cloudflare .wrangler/ -# Fastly -fastly.toml - # Spin .spin/ diff --git a/Cargo.lock b/Cargo.lock index 8b6ee09..e8530a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,9 +37,9 @@ checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" [[package]] name = "alloc-stdlib" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" dependencies = [ "alloc-no-stdlib", ] @@ -105,9 +105,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.102" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" [[package]] name = "async-compression" @@ -140,7 +140,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -151,7 +151,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -168,9 +168,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-lc-rs" -version = "1.17.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00" +checksum = "4342d8937fc7e5dd9b1c60292261c0670c882a2cd1719cfc11b1af41731e32ad" dependencies = [ "aws-lc-sys", "zeroize", @@ -178,14 +178,15 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.41.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4" +checksum = "6d9ceb1da931507a12f4fccea479dccd00da1943e1b4ae72d8e502d707361444" dependencies = [ "cc", "cmake", "dunce", "fs_extra", + "pkg-config", ] [[package]] @@ -260,9 +261,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.12.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84d7ced0ae9557296835c32bf1b1e02b44c746701f898460fb000d7eaa84f00a" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" [[package]] name = "block-buffer" @@ -284,9 +285,9 @@ dependencies = [ [[package]] name = "brotli" -version = "8.0.3" +version = "8.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8119e4516436f5708bbc474a9d395bf12f1b5395e93a92a56e647ac3388c8610" +checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -295,9 +296,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "5.0.1" +version = "5.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5962523e1b92ce1b5e793d9169b9943eece10d39f62550bc04bb605d75b94924" +checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -311,9 +312,9 @@ checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "bytes" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" [[package]] name = "cast" @@ -323,9 +324,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.63" +version = "1.2.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" dependencies = [ "find-msvc-tools", "jobserver", @@ -389,7 +390,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -534,7 +535,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -558,7 +559,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -569,7 +570,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -588,7 +589,6 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ - "powerfmt", "serde_core", ] @@ -610,7 +610,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -620,7 +620,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -650,7 +650,7 @@ checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -692,7 +692,7 @@ dependencies = [ [[package]] name = "edgezero-adapter" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=feature%2Fextensible-cli#89f592665a8b386111995da5cbf3fcc068113dfc" +source = "git+https://github.com/stackpop/edgezero.git?branch=main#42843b1c3934fab32f99cc76ddd5881f421cccc7" dependencies = [ "toml", ] @@ -700,7 +700,7 @@ dependencies = [ [[package]] name = "edgezero-adapter-axum" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=feature%2Fextensible-cli#89f592665a8b386111995da5cbf3fcc068113dfc" +source = "git+https://github.com/stackpop/edgezero.git?branch=main#42843b1c3934fab32f99cc76ddd5881f421cccc7" dependencies = [ "anyhow", "async-trait", @@ -728,7 +728,7 @@ dependencies = [ [[package]] name = "edgezero-adapter-cloudflare" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=feature%2Fextensible-cli#89f592665a8b386111995da5cbf3fcc068113dfc" +source = "git+https://github.com/stackpop/edgezero.git?branch=main#42843b1c3934fab32f99cc76ddd5881f421cccc7" dependencies = [ "anyhow", "async-trait", @@ -751,7 +751,7 @@ dependencies = [ [[package]] name = "edgezero-adapter-fastly" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=feature%2Fextensible-cli#89f592665a8b386111995da5cbf3fcc068113dfc" +source = "git+https://github.com/stackpop/edgezero.git?branch=main#42843b1c3934fab32f99cc76ddd5881f421cccc7" dependencies = [ "anyhow", "async-stream", @@ -780,7 +780,7 @@ dependencies = [ [[package]] name = "edgezero-adapter-spin" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=feature%2Fextensible-cli#89f592665a8b386111995da5cbf3fcc068113dfc" +source = "git+https://github.com/stackpop/edgezero.git?branch=main#42843b1c3934fab32f99cc76ddd5881f421cccc7" dependencies = [ "anyhow", "async-trait", @@ -807,7 +807,7 @@ dependencies = [ [[package]] name = "edgezero-cli" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=feature%2Fextensible-cli#89f592665a8b386111995da5cbf3fcc068113dfc" +source = "git+https://github.com/stackpop/edgezero.git?branch=main#42843b1c3934fab32f99cc76ddd5881f421cccc7" dependencies = [ "chrono", "clap", @@ -832,7 +832,7 @@ dependencies = [ [[package]] name = "edgezero-core" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=feature%2Fextensible-cli#89f592665a8b386111995da5cbf3fcc068113dfc" +source = "git+https://github.com/stackpop/edgezero.git?branch=main#42843b1c3934fab32f99cc76ddd5881f421cccc7" dependencies = [ "anyhow", "async-compression", @@ -863,13 +863,13 @@ dependencies = [ [[package]] name = "edgezero-macros" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=feature%2Fextensible-cli#89f592665a8b386111995da5cbf3fcc068113dfc" +source = "git+https://github.com/stackpop/edgezero.git?branch=main#42843b1c3934fab32f99cc76ddd5881f421cccc7" dependencies = [ "log", "proc-macro2", "quote", "serde", - "syn 2.0.117", + "syn 2.0.118", "toml", "validator", ] @@ -1023,12 +1023,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - [[package]] name = "foldhash" version = "0.2.0" @@ -1106,7 +1100,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1177,22 +1171,20 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", - "wasip2", - "wasip3 0.4.0+wasi-0.3.0-rc-2026-01-06", ] [[package]] name = "handlebars" -version = "6.4.1" +version = "6.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d43ccdfe15a81ab0a8af639e90254227c9a46afd9c5f5b6ec7efaa345c4b0f00" +checksum = "f26569a2763497b7bd3fbd19374b774ea6038c5293678771259cd534d49740ff" dependencies = [ "derive_builder", "log", @@ -1213,22 +1205,13 @@ dependencies = [ "ahash", ] -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "foldhash 0.1.5", -] - [[package]] name = "hashbrown" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" dependencies = [ - "foldhash 0.2.0", + "foldhash", ] [[package]] @@ -1248,9 +1231,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "http" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" dependencies = [ "bytes", "itoa", @@ -1555,7 +1538,7 @@ dependencies = [ "quote", "rustc_version", "simd_cesu8", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1574,7 +1557,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" dependencies = [ "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1636,9 +1619,9 @@ dependencies = [ [[package]] name = "link-section" -version = "0.18.2" +version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2b1dd6fe32e55c0fc0ea9493aa57459ca3cf4ff3c857c7d0302290150da6e4f" +checksum = "24670b639492630905459a6c7d47f063d33c2d4fcd5362f6e5827c5613976c9f" [[package]] name = "linktime-proc-macro" @@ -1660,9 +1643,9 @@ checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "log" -version = "0.4.32" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" dependencies = [ "serde_core", ] @@ -1704,9 +1687,9 @@ checksum = "8863b587001c1b9a8a4e36008cebc6b3612cb1226fe2de94858e06092687b608" [[package]] name = "memchr" -version = "2.8.1" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" [[package]] name = "mime" @@ -1846,9 +1829,9 @@ checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-modular" -version = "0.6.1" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17bb261bf36fa7d83f4c294f834e91256769097b3cb505d44831e0a179ac647f" +checksum = "fc41a1374056e9672221567958a66c16be12d0e2c1b408761e14d901c237d5e0" [[package]] name = "num-order" @@ -1944,7 +1927,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1974,7 +1957,7 @@ checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2030,7 +2013,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2052,7 +2035,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2066,9 +2049,9 @@ dependencies = [ [[package]] name = "quinn" -version = "0.11.9" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" dependencies = [ "bytes", "cfg_aliases", @@ -2086,9 +2069,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.14" +version = "0.11.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +checksum = "4fcb935c5bec503c2f0e306bdd3e58bb9029dcb14fa8d9ac76e3a5256ac0763e" dependencies = [ "aws-lc-rs", "bytes", @@ -2122,9 +2105,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.45" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" dependencies = [ "proc-macro2", ] @@ -2190,9 +2173,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.3" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" dependencies = [ "aho-corasick", "memchr", @@ -2213,9 +2196,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "reqwest" @@ -2276,7 +2259,7 @@ version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e" dependencies = [ - "bitflags 2.12.1", + "bitflags 2.13.0", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -2305,7 +2288,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.12.1", + "bitflags 2.13.0", "errno", "libc", "linux-raw-sys", @@ -2314,9 +2297,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.40" +version = "0.23.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" +checksum = "6b92b125634d9b795e7beca796cc790df15a7fb38323bf3196fda83292d06b1f" dependencies = [ "aws-lc-rs", "once_cell", @@ -2340,9 +2323,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046" dependencies = [ "web-time", "zeroize", @@ -2423,7 +2406,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.12.1", + "bitflags 2.13.0", "core-foundation", "core-foundation-sys", "libc", @@ -2484,7 +2467,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2493,6 +2476,7 @@ version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ + "indexmap", "itoa", "memchr", "serde", @@ -2519,7 +2503,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2640,9 +2624,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "socket2" @@ -2679,7 +2663,7 @@ dependencies = [ "http-body-util", "spin-macro", "thiserror 2.0.18", - "wasip3 0.6.0+wasi-0.3.0-rc-2026-03-15", + "wasip3", ] [[package]] @@ -2722,7 +2706,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2744,9 +2728,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.117" +version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" dependencies = [ "proc-macro2", "quote", @@ -2770,7 +2754,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2780,7 +2764,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.2", + "getrandom 0.4.3", "once_cell", "rustix", "windows-sys 0.61.2", @@ -2812,7 +2796,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2823,17 +2807,16 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "time" -version = "0.3.47" +version = "0.3.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50" dependencies = [ "deranged", - "itoa", "libc", "num-conv", "num_threads", @@ -2845,15 +2828,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.27" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f" dependencies = [ "num-conv", "time-core", @@ -2908,7 +2891,7 @@ checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3004,7 +2987,7 @@ version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ - "bitflags 2.12.1", + "bitflags 2.13.0", "bytes", "futures-util", "http", @@ -3048,7 +3031,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3122,11 +3105,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.23.2" +version = "1.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7" +checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53" dependencies = [ - "getrandom 0.4.2", + "getrandom 0.4.3", "js-sys", "wasm-bindgen", ] @@ -3158,7 +3141,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3200,22 +3183,13 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.3+wasi-0.2.9" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ "wit-bindgen 0.57.1", ] -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" -dependencies = [ - "wit-bindgen 0.51.0", -] - [[package]] name = "wasip3" version = "0.6.0+wasi-0.3.0-rc-2026-03-15" @@ -3271,7 +3245,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "wasm-bindgen-shared", ] @@ -3314,7 +3288,7 @@ checksum = "3c81b9fef827e575e0e54431736d1baa0d700315d8c62cfef1f61fa3aad0cbeb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3323,16 +3297,6 @@ version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f4d8ae7ad5440360e9799dfd42857d126454a88441ddf72d288ef83fa47f527" -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser 0.244.0", -] - [[package]] name = "wasm-encoder" version = "0.247.0" @@ -3340,19 +3304,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30b6733b8b91d010a6ac5b0fb237dc46a19650bc4c67db66857e2e787d437204" dependencies = [ "leb128fmt", - "wasmparser 0.247.0", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder 0.244.0", - "wasmparser 0.244.0", + "wasmparser", ] [[package]] @@ -3363,8 +3315,8 @@ checksum = "665fe59e56cc9b419ca6fcca56673e3421d1a5011e3b65caf6b726fd9e041d10" dependencies = [ "anyhow", "indexmap", - "wasm-encoder 0.247.0", - "wasmparser 0.247.0", + "wasm-encoder", + "wasmparser", ] [[package]] @@ -3380,25 +3332,13 @@ dependencies = [ "web-sys", ] -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags 2.12.1", - "hashbrown 0.15.5", - "indexmap", - "semver", -] - [[package]] name = "wasmparser" version = "0.247.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e6fb4c2bee46c5ea4d40f8cdb5c131725cd976718ec56f1c8e82fbde5fa2a80" dependencies = [ - "bitflags 2.12.1", + "bitflags 2.13.0", "hashbrown 0.17.1", "indexmap", "semver", @@ -3426,9 +3366,9 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" +checksum = "0d46a5a140e6f7afeccd8eae97eff335163939eac8b929834875168b29b3d267" dependencies = [ "rustls-pki-types", ] @@ -3463,7 +3403,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3474,7 +3414,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3678,8 +3618,7 @@ version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" dependencies = [ - "bitflags 2.12.1", - "wit-bindgen-rust-macro 0.51.0", + "bitflags 2.13.0", ] [[package]] @@ -3688,20 +3627,9 @@ version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" dependencies = [ - "bitflags 2.12.1", + "bitflags 2.13.0", "futures", - "wit-bindgen-rust-macro 0.57.1", -] - -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck", - "wit-parser 0.244.0", + "wit-bindgen-rust-macro", ] [[package]] @@ -3712,23 +3640,7 @@ checksum = "02dee27a2dc20d1008016c742ec9fc6ea498492994ba3750be7454cbc97ff04c" dependencies = [ "anyhow", "heck", - "wit-parser 0.247.0", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck", - "indexmap", - "prettyplease", - "syn 2.0.117", - "wasm-metadata 0.244.0", - "wit-bindgen-core 0.51.0", - "wit-component 0.244.0", + "wit-parser", ] [[package]] @@ -3741,25 +3653,10 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn 2.0.117", - "wasm-metadata 0.247.0", - "wit-bindgen-core 0.57.1", - "wit-component 0.247.0", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn 2.0.117", - "wit-bindgen-core 0.51.0", - "wit-bindgen-rust 0.51.0", + "syn 2.0.118", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", ] [[package]] @@ -3772,28 +3669,9 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.117", - "wit-bindgen-core 0.57.1", - "wit-bindgen-rust 0.57.1", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags 2.12.1", - "indexmap", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder 0.244.0", - "wasm-metadata 0.244.0", - "wasmparser 0.244.0", - "wit-parser 0.244.0", + "syn 2.0.118", + "wit-bindgen-core", + "wit-bindgen-rust", ] [[package]] @@ -3803,34 +3681,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d567162a6b9843080e5e0053f696623ff694bae8ae017c9ec536d1873bbe3d8" dependencies = [ "anyhow", - "bitflags 2.12.1", + "bitflags 2.13.0", "indexmap", "log", "serde", "serde_derive", "serde_json", - "wasm-encoder 0.247.0", - "wasm-metadata 0.247.0", - "wasmparser 0.247.0", - "wit-parser 0.247.0", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser 0.244.0", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", ] [[package]] @@ -3849,7 +3709,7 @@ dependencies = [ "serde_derive", "serde_json", "unicode-xid", - "wasmparser 0.247.0", + "wasmparser", ] [[package]] @@ -3893,7 +3753,7 @@ dependencies = [ "proc-macro2", "quote", "strum", - "syn 2.0.117", + "syn 2.0.118", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-macro-support", @@ -3937,28 +3797,28 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.50" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.50" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3978,15 +3838,15 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "synstructure", ] [[package]] name = "zeroize" -version = "1.8.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" [[package]] name = "zerotrie" @@ -4018,7 +3878,7 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 1ca1621..19acb66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,12 +24,12 @@ axum = "0.8" base64 = "0.22" clap = { version = "4", features = ["derive"] } ed25519-dalek = "2.1" -edgezero-adapter-axum = { git = "https://github.com/stackpop/edgezero.git", branch = "feature/extensible-cli", package = "edgezero-adapter-axum", default-features = false } -edgezero-adapter-cloudflare = { git = "https://github.com/stackpop/edgezero.git", branch = "feature/extensible-cli", package = "edgezero-adapter-cloudflare", default-features = false } -edgezero-adapter-fastly = { git = "https://github.com/stackpop/edgezero.git", branch = "feature/extensible-cli", package = "edgezero-adapter-fastly", default-features = false } -edgezero-adapter-spin = { git = "https://github.com/stackpop/edgezero.git", branch = "feature/extensible-cli", package = "edgezero-adapter-spin", default-features = false } -edgezero-cli = { git = "https://github.com/stackpop/edgezero.git", branch = "feature/extensible-cli", package = "edgezero-cli" } -edgezero-core = { git = "https://github.com/stackpop/edgezero.git", branch = "feature/extensible-cli", package = "edgezero-core" } +edgezero-adapter-axum = { git = "https://github.com/stackpop/edgezero.git", branch = "main", package = "edgezero-adapter-axum", default-features = false } +edgezero-adapter-cloudflare = { git = "https://github.com/stackpop/edgezero.git", branch = "main", package = "edgezero-adapter-cloudflare", default-features = false } +edgezero-adapter-fastly = { git = "https://github.com/stackpop/edgezero.git", branch = "main", package = "edgezero-adapter-fastly", default-features = false } +edgezero-adapter-spin = { git = "https://github.com/stackpop/edgezero.git", branch = "main", package = "edgezero-adapter-spin", default-features = false } +edgezero-cli = { git = "https://github.com/stackpop/edgezero.git", branch = "main", package = "edgezero-cli" } +edgezero-core = { git = "https://github.com/stackpop/edgezero.git", branch = "main", package = "edgezero-core" } fastly = "0.12.1" futures = { version = "0.3", features = ["std", "executor"] } futures-util = "0.3.32" diff --git a/crates/mocktioneer-core/src/routes.rs b/crates/mocktioneer-core/src/routes.rs index 835d1b4..b9d2993 100644 --- a/crates/mocktioneer-core/src/routes.rs +++ b/crates/mocktioneer-core/src/routes.rs @@ -314,10 +314,10 @@ pub async fn handle_root(ForwardedHost(host): ForwardedHost) -> Result, RequestContext(ctx): RequestContext, ForwardedHost(host): ForwardedHost, ValidatedJson(req): ValidatedJson, - AppConfig(cfg): AppConfig, ) -> Result { // Capture signature verification status for metadata let signature_status = @@ -470,9 +470,9 @@ pub async fn handle_pixel( #[action] pub async fn handle_aps_bid( + AppConfig(cfg): AppConfig, ForwardedHost(host): ForwardedHost, ValidatedJson(req): ValidatedJson, - AppConfig(cfg): AppConfig, ) -> Result { log::info!( "APS auction pubId={}, slots={}", diff --git a/docs/package-lock.json b/docs/package-lock.json index e483ef1..a263d45 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -18,16 +18,16 @@ } }, "node_modules/@algolia/abtesting": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.13.0.tgz", - "integrity": "sha512-Zrqam12iorp3FjiKMXSTpedGYznZ3hTEOAr2oCxI8tbF8bS1kQHClyDYNq/eV0ewMNLyFkgZVWjaS+8spsOYiQ==", + "version": "1.21.1", + "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.21.1.tgz", + "integrity": "sha512-Wia5/mNTfiU0PIUN25UMfAGGdASkkwuCS9nBAdmhqrNPY/ff7U/6MgBVdwFDPsa3sA1msutPtO50gvOzx6MOXA==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.47.0", - "@algolia/requester-browser-xhr": "5.47.0", - "@algolia/requester-fetch": "5.47.0", - "@algolia/requester-node-http": "5.47.0" + "@algolia/client-common": "5.55.1", + "@algolia/requester-browser-xhr": "5.55.1", + "@algolia/requester-fetch": "5.55.1", + "@algolia/requester-node-http": "5.55.1" }, "engines": { "node": ">= 14.0.0" @@ -83,41 +83,41 @@ } }, "node_modules/@algolia/client-abtesting": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.47.0.tgz", - "integrity": "sha512-aOpsdlgS9xTEvz47+nXmw8m0NtUiQbvGWNuSEb7fA46iPL5FxOmOUZkh8PREBJpZ0/H8fclSc7BMJCVr+Dn72w==", + "version": "5.55.1", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.55.1.tgz", + "integrity": "sha512-miW8RzAtBgNiEJ9fGEhsOPgWUpekAe64YcVufqXrlykj0Jjmo5nj0a5f/HAzRVX5ZuU1GAVd7BkzFDx7q50P3A==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.47.0", - "@algolia/requester-browser-xhr": "5.47.0", - "@algolia/requester-fetch": "5.47.0", - "@algolia/requester-node-http": "5.47.0" + "@algolia/client-common": "5.55.1", + "@algolia/requester-browser-xhr": "5.55.1", + "@algolia/requester-fetch": "5.55.1", + "@algolia/requester-node-http": "5.55.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-analytics": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.47.0.tgz", - "integrity": "sha512-EcF4w7IvIk1sowrO7Pdy4Ako7x/S8+nuCgdk6En+u5jsaNQM4rTT09zjBPA+WQphXkA2mLrsMwge96rf6i7Mow==", + "version": "5.55.1", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.55.1.tgz", + "integrity": "sha512-eR3J3kB9JX6DdCvDRi3I4KPfwO6fR9HWYRXhVke2TXIoOQafMKCRAneg33JRmIrb+DnnJ/eWApJLF1O1CLPERg==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.47.0", - "@algolia/requester-browser-xhr": "5.47.0", - "@algolia/requester-fetch": "5.47.0", - "@algolia/requester-node-http": "5.47.0" + "@algolia/client-common": "5.55.1", + "@algolia/requester-browser-xhr": "5.55.1", + "@algolia/requester-fetch": "5.55.1", + "@algolia/requester-node-http": "5.55.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-common": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.47.0.tgz", - "integrity": "sha512-Wzg5Me2FqgRDj0lFuPWFK05UOWccSMsIBL2YqmTmaOzxVlLZ+oUqvKbsUSOE5ud8Fo1JU7JyiLmEXBtgDKzTwg==", + "version": "5.55.1", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.55.1.tgz", + "integrity": "sha512-P5ak7EurwYqgAiDyb95mgA3WRR/Zu8CPMv36lWTISvL2AmlPyqQPy2nX/KEJRTcwaeTWwrk6wJV4/M93GfjOWw==", "dev": true, "license": "MIT", "engines": { @@ -125,160 +125,161 @@ } }, "node_modules/@algolia/client-insights": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.47.0.tgz", - "integrity": "sha512-Ci+cn/FDIsDxSKMRBEiyKrqybblbk8xugo6ujDN1GSTv9RIZxwxqZYuHfdLnLEwLlX7GB8pqVyqrUSlRnR+sJA==", + "version": "5.55.1", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.55.1.tgz", + "integrity": "sha512-OVtj9uA//+pjvKQI5INnzbyLrf3ClNv3XRbWswwJ2kHIStQNHtBfHo+LofNB/WhM9xjuXlW5ANn2aMj65UGx7w==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.47.0", - "@algolia/requester-browser-xhr": "5.47.0", - "@algolia/requester-fetch": "5.47.0", - "@algolia/requester-node-http": "5.47.0" + "@algolia/client-common": "5.55.1", + "@algolia/requester-browser-xhr": "5.55.1", + "@algolia/requester-fetch": "5.55.1", + "@algolia/requester-node-http": "5.55.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-personalization": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.47.0.tgz", - "integrity": "sha512-gsLnHPZmWcX0T3IigkDL2imCNtsQ7dR5xfnwiFsb+uTHCuYQt+IwSNjsd8tok6HLGLzZrliSaXtB5mfGBtYZvQ==", + "version": "5.55.1", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.55.1.tgz", + "integrity": "sha512-oKlVFlp+qbIEe4p7E54zSiP2gEV/vDu972Ykv8VDMFwEvreS7m0YKA3a8hGGHwc7yiBUGGiR3LlwzMLfnJmy6Q==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.47.0", - "@algolia/requester-browser-xhr": "5.47.0", - "@algolia/requester-fetch": "5.47.0", - "@algolia/requester-node-http": "5.47.0" + "@algolia/client-common": "5.55.1", + "@algolia/requester-browser-xhr": "5.55.1", + "@algolia/requester-fetch": "5.55.1", + "@algolia/requester-node-http": "5.55.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-query-suggestions": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.47.0.tgz", - "integrity": "sha512-PDOw0s8WSlR2fWFjPQldEpmm/gAoUgLigvC3k/jCSi/DzigdGX6RdC0Gh1RR1P8Cbk5KOWYDuL3TNzdYwkfDyA==", + "version": "5.55.1", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.55.1.tgz", + "integrity": "sha512-BOVrld6vdtsFmotVDMTVQfYXwrVplJ+DUvy60JFi+tkWV698q2J9NNPKEO3dr5qxtSLKQP4vHF8n+3U5PDWhOQ==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.47.0", - "@algolia/requester-browser-xhr": "5.47.0", - "@algolia/requester-fetch": "5.47.0", - "@algolia/requester-node-http": "5.47.0" + "@algolia/client-common": "5.55.1", + "@algolia/requester-browser-xhr": "5.55.1", + "@algolia/requester-fetch": "5.55.1", + "@algolia/requester-node-http": "5.55.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-search": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.47.0.tgz", - "integrity": "sha512-b5hlU69CuhnS2Rqgsz7uSW0t4VqrLMLTPbUpEl0QVz56rsSwr1Sugyogrjb493sWDA+XU1FU5m9eB8uH7MoI0g==", + "version": "5.55.1", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.55.1.tgz", + "integrity": "sha512-GAqHl9zERhC3bbBfubwUu07G3UXO06gORvOcsiTBZB3et0s3auNUbHlYdYNp4VKa3sUZqH5AcD3OKzU/KDGXjQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@algolia/client-common": "5.47.0", - "@algolia/requester-browser-xhr": "5.47.0", - "@algolia/requester-fetch": "5.47.0", - "@algolia/requester-node-http": "5.47.0" + "@algolia/client-common": "5.55.1", + "@algolia/requester-browser-xhr": "5.55.1", + "@algolia/requester-fetch": "5.55.1", + "@algolia/requester-node-http": "5.55.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/ingestion": { - "version": "1.47.0", - "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.47.0.tgz", - "integrity": "sha512-WvwwXp5+LqIGISK3zHRApLT1xkuEk320/EGeD7uYy+K8WwDd5OjXnhjuXRhYr1685KnkvWkq1rQ/ihCJjOfHpQ==", + "version": "1.55.1", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.55.1.tgz", + "integrity": "sha512-BXZw+C+gsWL7pZvbnhJUnCXASiDLGcQxVV7h55Pyh2DmSzwdZIVccE5xc9RVD2trtrhIqk5smuODTxtaZqd0IA==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.47.0", - "@algolia/requester-browser-xhr": "5.47.0", - "@algolia/requester-fetch": "5.47.0", - "@algolia/requester-node-http": "5.47.0" + "@algolia/client-common": "5.55.1", + "@algolia/requester-browser-xhr": "5.55.1", + "@algolia/requester-fetch": "5.55.1", + "@algolia/requester-node-http": "5.55.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/monitoring": { - "version": "1.47.0", - "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.47.0.tgz", - "integrity": "sha512-j2EUFKAlzM0TE4GRfkDE3IDfkVeJdcbBANWzK16Tb3RHz87WuDfQ9oeEW6XiRE1/bEkq2xf4MvZesvSeQrZRDA==", + "version": "1.55.1", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.55.1.tgz", + "integrity": "sha512-9g/ceZrZTqA62FA3588Xj0onRPjDNfu0pVQqefK0rrHp9H6Wblph/YmzGjZ2g8uqbTh0ZGIvAGCzErU8f7MHpA==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.47.0", - "@algolia/requester-browser-xhr": "5.47.0", - "@algolia/requester-fetch": "5.47.0", - "@algolia/requester-node-http": "5.47.0" + "@algolia/client-common": "5.55.1", + "@algolia/requester-browser-xhr": "5.55.1", + "@algolia/requester-fetch": "5.55.1", + "@algolia/requester-node-http": "5.55.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/recommend": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.47.0.tgz", - "integrity": "sha512-+kTSE4aQ1ARj2feXyN+DMq0CIDHJwZw1kpxIunedkmpWUg8k3TzFwWsMCzJVkF2nu1UcFbl7xsIURz3Q3XwOXA==", + "version": "5.55.1", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.55.1.tgz", + "integrity": "sha512-cZTIrGyAP+W4A6jDVwvWM/JOaoJKQkD/2a5eLUEeNdKAD45jN7BCpsMDONyhZlosLa4UwL8uiINQzj4iFy9nqg==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.47.0", - "@algolia/requester-browser-xhr": "5.47.0", - "@algolia/requester-fetch": "5.47.0", - "@algolia/requester-node-http": "5.47.0" + "@algolia/client-common": "5.55.1", + "@algolia/requester-browser-xhr": "5.55.1", + "@algolia/requester-fetch": "5.55.1", + "@algolia/requester-node-http": "5.55.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-browser-xhr": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.47.0.tgz", - "integrity": "sha512-Ja+zPoeSA2SDowPwCNRbm5Q2mzDvVV8oqxCQ4m6SNmbKmPlCfe30zPfrt9ho3kBHnsg37pGucwOedRIOIklCHw==", + "version": "5.55.1", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.55.1.tgz", + "integrity": "sha512-N6I3leW0UO8Y9Zv90yo2UHgYGuxZO0mjbvzNxDIJDjO0qECEF7Z9XMvSNeUWXQh/iNDA9lr8MfEy3rmZGIcclw==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.47.0" + "@algolia/client-common": "5.55.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-fetch": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.47.0.tgz", - "integrity": "sha512-N6nOvLbaR4Ge+oVm7T4W/ea1PqcSbsHR4O58FJ31XtZjFPtOyxmnhgCmGCzP9hsJI6+x0yxJjkW5BMK/XI8OvA==", + "version": "5.55.1", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.55.1.tgz", + "integrity": "sha512-ukU5zeeFs44rQkzv+TRdYard+d+3lmPGs8lPZhHtWE8rfz+LlBSF6s9kP3VQ7LeOYL8Dz0u6tZfnyTrqrumbHQ==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.47.0" + "@algolia/client-common": "5.55.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-node-http": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.47.0.tgz", - "integrity": "sha512-z1oyLq5/UVkohVXNDEY70mJbT/sv/t6HYtCvCwNrOri6pxBJDomP9R83KOlwcat+xqBQEdJHjbrPh36f1avmZA==", + "version": "5.55.1", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.55.1.tgz", + "integrity": "sha512-lCwXyijwPm3vbYHpBXPRomMcD6mgiptmps27gnMCf4HK+u/AOeFPBnIFh4V3l4A5SnP9VRiKBZqwGBpUH0vaTg==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.47.0" + "@algolia/client-common": "5.55.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "dev": true, "license": "MIT", "engines": { @@ -286,9 +287,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "dev": true, "license": "MIT", "engines": { @@ -296,13 +297,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", - "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.6" + "@babel/types": "^7.29.7" }, "bin": { "parser": "bin/babel-parser.js" @@ -312,14 +313,14 @@ } }, "node_modules/@babel/types": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", - "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -810,15 +811,15 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", - "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", "dev": true, "license": "Apache-2.0", "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", - "minimatch": "^3.1.2" + "minimatch": "^3.1.5" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -851,20 +852,20 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", - "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", "dev": true, "license": "MIT", "dependencies": { - "ajv": "^6.12.4", + "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", - "minimatch": "^3.1.2", + "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" }, "engines": { @@ -875,9 +876,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.39.2", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", - "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", "dev": true, "license": "MIT", "engines": { @@ -912,29 +913,43 @@ } }, "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, "engines": { "node": ">=18.18.0" } }, "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanfs/core": "^0.19.1", + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" }, "engines": { "node": ">=18.18.0" } }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -964,9 +979,9 @@ } }, "node_modules/@iconify-json/simple-icons": { - "version": "1.2.68", - "resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.2.68.tgz", - "integrity": "sha512-bQPl1zuZlX6AnofreA1v7J+hoPncrFMppqGboe/SH54jZO37meiBUGBqNOxEpc0HKfZGxJaVVJwZd4gdMYu3hw==", + "version": "1.2.88", + "resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.2.88.tgz", + "integrity": "sha512-+cvi1qCuvReL29ehi6t62L4fb7GDXe+UlGHFcsJcV7I2l9wtqn9XE2IBKcDr3CI5iGUGS5ISnXv699pSGpyx1Q==", "dev": true, "license": "CC0-1.0", "dependencies": { @@ -988,9 +1003,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", - "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", "cpu": [ "arm" ], @@ -1002,9 +1017,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", - "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", "cpu": [ "arm64" ], @@ -1016,9 +1031,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", - "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", "cpu": [ "arm64" ], @@ -1030,9 +1045,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", - "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", "cpu": [ "x64" ], @@ -1044,9 +1059,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", - "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", "cpu": [ "arm64" ], @@ -1058,9 +1073,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", - "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", "cpu": [ "x64" ], @@ -1072,9 +1087,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", - "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", "cpu": [ "arm" ], @@ -1086,9 +1101,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", - "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", "cpu": [ "arm" ], @@ -1100,9 +1115,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", - "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", "cpu": [ "arm64" ], @@ -1114,9 +1129,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", - "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", "cpu": [ "arm64" ], @@ -1128,9 +1143,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", - "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", "cpu": [ "loong64" ], @@ -1142,9 +1157,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", - "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", "cpu": [ "loong64" ], @@ -1156,9 +1171,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", - "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", "cpu": [ "ppc64" ], @@ -1170,9 +1185,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", - "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", "cpu": [ "ppc64" ], @@ -1184,9 +1199,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", - "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", "cpu": [ "riscv64" ], @@ -1198,9 +1213,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", - "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", "cpu": [ "riscv64" ], @@ -1212,9 +1227,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", - "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", "cpu": [ "s390x" ], @@ -1226,9 +1241,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", - "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", "cpu": [ "x64" ], @@ -1240,9 +1255,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", - "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", "cpu": [ "x64" ], @@ -1254,9 +1269,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", - "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", "cpu": [ "x64" ], @@ -1268,9 +1283,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", - "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", "cpu": [ "arm64" ], @@ -1282,9 +1297,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", - "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", "cpu": [ "arm64" ], @@ -1296,9 +1311,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", - "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", "cpu": [ "ia32" ], @@ -1310,9 +1325,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", - "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", "cpu": [ "x64" ], @@ -1324,9 +1339,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", - "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", "cpu": [ "x64" ], @@ -1425,9 +1440,9 @@ "license": "MIT" }, "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true, "license": "MIT" }, @@ -1484,13 +1499,14 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.10.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.9.tgz", - "integrity": "sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==", + "version": "24.13.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.2.tgz", + "integrity": "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~7.18.0" } }, "node_modules/@types/unist": { @@ -1508,20 +1524,20 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.0.tgz", - "integrity": "sha512-qeu4rTHR3/IaFORbD16gmjq9+rEs9fGKdX0kF6BKSfi+gCuG3RCKLlSBYzn/bGsY9Tj7KE/DAQStbp8AHJGHEQ==", + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.62.1.tgz", + "integrity": "sha512-4EQM77WgVNxj7OkL/5b/D/xZsw00G577+UriYTC7JF5opcF3T2AuoeY7ueLaZgSVjSgCS6yOAJB5bRGLPSJUzA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.57.0", - "@typescript-eslint/type-utils": "8.57.0", - "@typescript-eslint/utils": "8.57.0", - "@typescript-eslint/visitor-keys": "8.57.0", + "@typescript-eslint/scope-manager": "8.62.1", + "@typescript-eslint/type-utils": "8.62.1", + "@typescript-eslint/utils": "8.62.1", + "@typescript-eslint/visitor-keys": "8.62.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.4.0" + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1531,9 +1547,9 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.57.0", + "@typescript-eslint/parser": "^8.62.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { @@ -1547,16 +1563,17 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.57.0.tgz", - "integrity": "sha512-XZzOmihLIr8AD1b9hL9ccNMzEMWt/dE2u7NyTY9jJG6YNiNthaD5XtUHVF2uCXZ15ng+z2hT3MVuxnUYhq6k1g==", + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.62.1.tgz", + "integrity": "sha512-sPhE4iHuJDSvoAiec+Ro8JyXw8f0ql13HFR82P99nCm9GwTEKG0KYLvDe6REk8BCXuit6vJAv/Yxg5ABaNS2rA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.57.0", - "@typescript-eslint/types": "8.57.0", - "@typescript-eslint/typescript-estree": "8.57.0", - "@typescript-eslint/visitor-keys": "8.57.0", + "@typescript-eslint/scope-manager": "8.62.1", + "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/typescript-estree": "8.62.1", + "@typescript-eslint/visitor-keys": "8.62.1", "debug": "^4.4.3" }, "engines": { @@ -1568,18 +1585,18 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.0.tgz", - "integrity": "sha512-pR+dK0BlxCLxtWfaKQWtYr7MhKmzqZxuii+ZjuFlZlIGRZm22HnXFqa2eY+90MUz8/i80YJmzFGDUsi8dMOV5w==", + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.62.1.tgz", + "integrity": "sha512-yQ3RgY5RkSBpsNS1Bx/JQEcA24FOSdfGktoyprAr5u18390UQdtVcfnEv4nIrIshNnavlVyZBKxQwT1fIAE6cg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.57.0", - "@typescript-eslint/types": "^8.57.0", + "@typescript-eslint/tsconfig-utils": "^8.62.1", + "@typescript-eslint/types": "^8.62.1", "debug": "^4.4.3" }, "engines": { @@ -1590,18 +1607,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.0.tgz", - "integrity": "sha512-nvExQqAHF01lUM66MskSaZulpPL5pgy5hI5RfrxviLgzZVffB5yYzw27uK/ft8QnKXI2X0LBrHJFr1TaZtAibw==", + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.62.1.tgz", + "integrity": "sha512-r4d249KbQ1SFdpeStvob8Ih6aPPIzfqllPVOtvhve6ZcpuVcYo5/7zUWckKpHE7StASX4kTKZTLf0WQm/wPkcg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.57.0", - "@typescript-eslint/visitor-keys": "8.57.0" + "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/visitor-keys": "8.62.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1612,9 +1629,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.0.tgz", - "integrity": "sha512-LtXRihc5ytjJIQEH+xqjB0+YgsV4/tW35XKX3GTZHpWtcC8SPkT/d4tqdf1cKtesryHm2bgp6l555NYcT2NLvA==", + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.62.1.tgz", + "integrity": "sha512-xadytJqX9vJVQ2fdQjkcIVigwaOJNWkpjdLt6cEQ+xPnrI1fkp+/jZE/I97k9KUjqtpd25i0HeyZf3T6dutv2g==", "dev": true, "license": "MIT", "engines": { @@ -1625,21 +1642,21 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.0.tgz", - "integrity": "sha512-yjgh7gmDcJ1+TcEg8x3uWQmn8ifvSupnPfjP21twPKrDP/pTHlEQgmKcitzF/rzPSmv7QjJ90vRpN4U+zoUjwQ==", + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.62.1.tgz", + "integrity": "sha512-aXM5xlqXiTxPibXB93cLAURfT3rlizf7uMXISCXy66Isr/9hISJx3yDsKl0L7lKa51b8JpFuNKby0/O0pEm9jg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.57.0", - "@typescript-eslint/typescript-estree": "8.57.0", - "@typescript-eslint/utils": "8.57.0", + "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/typescript-estree": "8.62.1", + "@typescript-eslint/utils": "8.62.1", "debug": "^4.4.3", - "ts-api-utils": "^2.4.0" + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1650,13 +1667,13 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.0.tgz", - "integrity": "sha512-dTLI8PEXhjUC7B9Kre+u0XznO696BhXcTlOn0/6kf1fHaQW8+VjJAVHJ3eTI14ZapTxdkOmc80HblPQLaEeJdg==", + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.62.1.tgz", + "integrity": "sha512-ooCzJFaf+Hg+uG6fA3NRFGuFjlfNlDhBthbv4ZPU/0elCAFUfnyXUvf/WOpHz/jYwSmvU2GkR2LtyUfy1AxZ1Q==", "dev": true, "license": "MIT", "engines": { @@ -1668,21 +1685,21 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.0.tgz", - "integrity": "sha512-m7faHcyVg0BT3VdYTlX8GdJEM7COexXxS6KqGopxdtkQRvBanK377QDHr4W/vIPAR+ah9+B/RclSW5ldVniO1Q==", + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.62.1.tgz", + "integrity": "sha512-xMcW9oP9u7fAMXYs9A65CVmtLQe2r//oXINHfi8HV+oiqhih17sbLdhXr4540YWlgpDKQdY854OL5ZrdCiQsAA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.57.0", - "@typescript-eslint/tsconfig-utils": "8.57.0", - "@typescript-eslint/types": "8.57.0", - "@typescript-eslint/visitor-keys": "8.57.0", + "@typescript-eslint/project-service": "8.62.1", + "@typescript-eslint/tsconfig-utils": "8.62.1", + "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/visitor-keys": "8.62.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.4.0" + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1692,7 +1709,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { @@ -1706,9 +1723,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", + "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", "dev": true, "license": "MIT", "dependencies": { @@ -1719,13 +1736,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^5.0.5" }, "engines": { "node": "18 || 20 || >=22" @@ -1735,16 +1752,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.0.tgz", - "integrity": "sha512-5iIHvpD3CZe06riAsbNxxreP+MuYgVUsV0n4bwLH//VJmgtt54sQeY2GszntJ4BjYCpMzrfVh2SBnUQTtys2lQ==", + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.62.1.tgz", + "integrity": "sha512-sHtbPfuKNZCG+ih8SyjjucqRntSVmp8XgL5u6o9mAhiSn8ds5o/M/XdM0abweme2Tln3szOstOrZ9OXitvPh0g==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.57.0", - "@typescript-eslint/types": "8.57.0", - "@typescript-eslint/typescript-estree": "8.57.0" + "@typescript-eslint/scope-manager": "8.62.1", + "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/typescript-estree": "8.62.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1755,17 +1772,17 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.0.tgz", - "integrity": "sha512-zm6xx8UT/Xy2oSr2ZXD0pZo7Jx2XsCoID2IUh9YSTFRu7z+WdwYTRk6LhUftm1crwqbuoF6I8zAFeCMw0YjwDg==", + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.62.1.tgz", + "integrity": "sha512-4g3BLxfdTMy8iZG0MaBkadnlRrCJ74cQiFbyEVMrkwIoqdyaXXQM22cotDvrl4x28wgIZ9rEJRoM+mmhSJpJ1g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/types": "8.62.1", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -1790,9 +1807,9 @@ } }, "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.2.tgz", + "integrity": "sha512-5jsZFwgR5rTdKwidH9Qmat75RKwqfpKlWWB1frDkljN127mwqBu8K0PYo7/hFpF03IEJpfVPpCQDY/eDx3iHvA==", "dev": true, "license": "ISC" }, @@ -1811,77 +1828,77 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.27.tgz", - "integrity": "sha512-gnSBQjZA+//qDZen+6a2EdHqJ68Z7uybrMf3SPjEGgG4dicklwDVmMC1AeIHxtLVPT7sn6sH1KOO+tS6gwOUeQ==", + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.39.tgz", + "integrity": "sha512-16KBTEXAJCpDr0mwlw+AZyhu8iyC7R3S2vBwsI7QnWJU6X3WKc9VKeNEZpiMdZ569qWhz9574L3vV55qRL0Vtw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.5", - "@vue/shared": "3.5.27", - "entities": "^7.0.0", + "@babel/parser": "^7.29.7", + "@vue/shared": "3.5.39", + "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "node_modules/@vue/compiler-dom": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.27.tgz", - "integrity": "sha512-oAFea8dZgCtVVVTEC7fv3T5CbZW9BxpFzGGxC79xakTr6ooeEqmRuvQydIiDAkglZEAd09LgVf1RoDnL54fu5w==", + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.39.tgz", + "integrity": "sha512-oQPigALqYbNxTNPvNgSOe+czwVExfbVF02lz8jP0S3AXJiu3jxYDygNUiqSep4ezzW8XgnubqH63My2A7JR/vg==", "dev": true, "license": "MIT", "dependencies": { - "@vue/compiler-core": "3.5.27", - "@vue/shared": "3.5.27" + "@vue/compiler-core": "3.5.39", + "@vue/shared": "3.5.39" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.27.tgz", - "integrity": "sha512-sHZu9QyDPeDmN/MRoshhggVOWE5WlGFStKFwu8G52swATgSny27hJRWteKDSUUzUH+wp+bmeNbhJnEAel/auUQ==", + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.39.tgz", + "integrity": "sha512-d0ki86iOyN8LoZPBmk5SJWNwHP19CnDDCfuo//+2WJa2g5Ke0Jay983PIBIcSSzldC68I8DrD5GrHV3OSDfodg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.5", - "@vue/compiler-core": "3.5.27", - "@vue/compiler-dom": "3.5.27", - "@vue/compiler-ssr": "3.5.27", - "@vue/shared": "3.5.27", + "@babel/parser": "^7.29.7", + "@vue/compiler-core": "3.5.39", + "@vue/compiler-dom": "3.5.39", + "@vue/compiler-ssr": "3.5.39", + "@vue/shared": "3.5.39", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", - "postcss": "^8.5.6", + "postcss": "^8.5.15", "source-map-js": "^1.2.1" } }, "node_modules/@vue/compiler-ssr": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.27.tgz", - "integrity": "sha512-Sj7h+JHt512fV1cTxKlYhg7qxBvack+BGncSpH+8vnN+KN95iPIcqB5rsbblX40XorP+ilO7VIKlkuu3Xq2vjw==", + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.39.tgz", + "integrity": "sha512-Ce7/wvwMHai74bdszfXExdazFigYnlF9zgCmEQUcM1j0fOymlouZ7XilTYNo8oUjhlnjYOZbGrcYKuqjz89Ucw==", "dev": true, "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.27", - "@vue/shared": "3.5.27" + "@vue/compiler-dom": "3.5.39", + "@vue/shared": "3.5.39" } }, "node_modules/@vue/devtools-api": { - "version": "7.7.9", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.9.tgz", - "integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==", + "version": "7.7.10", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.10.tgz", + "integrity": "sha512-KxtEpUOOpFz/qOGRrAwA36QF7DqIA+FXgCYit9mk9wjbaZt0sXOFz81ElOZtKA4HbWHUdwNjZHBFsFFyp5BZiA==", "dev": true, "license": "MIT", "dependencies": { - "@vue/devtools-kit": "^7.7.9" + "@vue/devtools-kit": "^7.7.10" } }, "node_modules/@vue/devtools-kit": { - "version": "7.7.9", - "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz", - "integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==", + "version": "7.7.10", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.10.tgz", + "integrity": "sha512-3WNi2Kq4tbpVbmhml7RiphmAt0279oh3fKNeWMQIrltfX8Q91b4i5PL8DtyNKdwmcsGrV4fg+erwWOmD05CLIw==", "dev": true, "license": "MIT", "dependencies": { - "@vue/devtools-shared": "^7.7.9", + "@vue/devtools-shared": "^7.7.10", "birpc": "^2.3.0", "hookable": "^5.5.3", "mitt": "^3.0.1", @@ -1891,9 +1908,9 @@ } }, "node_modules/@vue/devtools-shared": { - "version": "7.7.9", - "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz", - "integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==", + "version": "7.7.10", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.10.tgz", + "integrity": "sha512-wOPslzB8vTvpxwdaOcR2qAbwmuSP0L+rhpoC6Cf56V3Jip+HWb7PQQXOUPgBNQARpXsbQX/+mvi8kKucmBGRwQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1901,57 +1918,57 @@ } }, "node_modules/@vue/reactivity": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.27.tgz", - "integrity": "sha512-vvorxn2KXfJ0nBEnj4GYshSgsyMNFnIQah/wczXlsNXt+ijhugmW+PpJ2cNPe4V6jpnBcs0MhCODKllWG+nvoQ==", + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.39.tgz", + "integrity": "sha512-TpsuBJ9gGlZa5d23XcM2y8EXanz9dZeVDQBXRwzy46ItgvM+rWpzs+UVM0wcRLxGvcav0HE5jz2gNL53xlRAog==", "dev": true, "license": "MIT", "dependencies": { - "@vue/shared": "3.5.27" + "@vue/shared": "3.5.39" } }, "node_modules/@vue/runtime-core": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.27.tgz", - "integrity": "sha512-fxVuX/fzgzeMPn/CLQecWeDIFNt3gQVhxM0rW02Tvp/YmZfXQgcTXlakq7IMutuZ/+Ogbn+K0oct9J3JZfyk3A==", + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.39.tgz", + "integrity": "sha512-9GLtNyRvPAUMbX+7ono0RC2j0guo2LXVi8LvcmAooImACUKm0oFf0jjwbX8/H0AE/t1nxhAkn8RSl9PMCzzxZw==", "dev": true, "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.27", - "@vue/shared": "3.5.27" + "@vue/reactivity": "3.5.39", + "@vue/shared": "3.5.39" } }, "node_modules/@vue/runtime-dom": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.27.tgz", - "integrity": "sha512-/QnLslQgYqSJ5aUmb5F0z0caZPGHRB8LEAQ1s81vHFM5CBfnun63rxhvE/scVb/j3TbBuoZwkJyiLCkBluMpeg==", + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.39.tgz", + "integrity": "sha512-7Y6aAGboKcXAZ3ECuUy7RrS5yy2r47dhTp2SKaJmYxjopImaVFaNa5Ne66NwGovsrxVAl5S5rwc7m22UG7Lmww==", "dev": true, "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.27", - "@vue/runtime-core": "3.5.27", - "@vue/shared": "3.5.27", + "@vue/reactivity": "3.5.39", + "@vue/runtime-core": "3.5.39", + "@vue/shared": "3.5.39", "csstype": "^3.2.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.27.tgz", - "integrity": "sha512-qOz/5thjeP1vAFc4+BY3Nr6wxyLhpeQgAE/8dDtKo6a6xdk+L4W46HDZgNmLOBUDEkFXV3G7pRiUqxjX0/2zWA==", + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.39.tgz", + "integrity": "sha512-yZSakiAGw85rZfG7UM8akMnIF+FmeiNk47uvHf2nVBBSe+dIKUhZuZq9+XgJhbV3nS5Z4ALH23/MpXofW+mbcw==", "dev": true, "license": "MIT", "dependencies": { - "@vue/compiler-ssr": "3.5.27", - "@vue/shared": "3.5.27" + "@vue/compiler-ssr": "3.5.39", + "@vue/shared": "3.5.39" }, "peerDependencies": { - "vue": "3.5.27" + "vue": "3.5.39" } }, "node_modules/@vue/shared": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.27.tgz", - "integrity": "sha512-dXr/3CgqXsJkZ0n9F3I4elY8wM9jMJpP3pvRG52r6m0tu/MsAFIe6JpXVGeNMd/D9F4hQynWT8Rfuj0bdm9kFQ==", + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.39.tgz", + "integrity": "sha512-l1rrBtBfTnmxvtsvdQDXltUUy8S1Y+ZaqdfUzmAnJkTd8Z8rv5v/ytW+TKiqEOWyHPoqtPlNFSs0lhRmYVSHVA==", "dev": true, "license": "MIT" }, @@ -2062,11 +2079,12 @@ } }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2085,9 +2103,9 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { @@ -2102,26 +2120,27 @@ } }, "node_modules/algoliasearch": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.47.0.tgz", - "integrity": "sha512-AGtz2U7zOV4DlsuYV84tLp2tBbA7RPtLA44jbVH4TTpDcc1dIWmULjHSsunlhscbzDydnjuFlNhflR3nV4VJaQ==", + "version": "5.55.1", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.55.1.tgz", + "integrity": "sha512-FyaFnnsbVPtevQwqSj/SdxE3jAsSsY0BEH8IVLf9rXxEBdAhAmT6VKCVSMWoaPIHVN1Eufh/1w8q6k8URpIkWw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@algolia/abtesting": "1.13.0", - "@algolia/client-abtesting": "5.47.0", - "@algolia/client-analytics": "5.47.0", - "@algolia/client-common": "5.47.0", - "@algolia/client-insights": "5.47.0", - "@algolia/client-personalization": "5.47.0", - "@algolia/client-query-suggestions": "5.47.0", - "@algolia/client-search": "5.47.0", - "@algolia/ingestion": "1.47.0", - "@algolia/monitoring": "1.47.0", - "@algolia/recommend": "5.47.0", - "@algolia/requester-browser-xhr": "5.47.0", - "@algolia/requester-fetch": "5.47.0", - "@algolia/requester-node-http": "5.47.0" + "@algolia/abtesting": "1.21.1", + "@algolia/client-abtesting": "5.55.1", + "@algolia/client-analytics": "5.55.1", + "@algolia/client-common": "5.55.1", + "@algolia/client-insights": "5.55.1", + "@algolia/client-personalization": "5.55.1", + "@algolia/client-query-suggestions": "5.55.1", + "@algolia/client-search": "5.55.1", + "@algolia/ingestion": "1.55.1", + "@algolia/monitoring": "1.55.1", + "@algolia/recommend": "5.55.1", + "@algolia/requester-browser-xhr": "5.55.1", + "@algolia/requester-fetch": "5.55.1", + "@algolia/requester-node-http": "5.55.1" }, "engines": { "node": ">= 14.0.0" @@ -2168,9 +2187,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", "dev": true, "license": "MIT", "dependencies": { @@ -2436,25 +2455,26 @@ } }, "node_modules/eslint": { - "version": "9.39.2", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", - "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.1", + "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.2", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", - "ajv": "^6.12.4", + "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", @@ -2473,7 +2493,7 @@ "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", + "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, @@ -2692,6 +2712,7 @@ "integrity": "sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "tabbable": "^6.4.0" } @@ -2884,10 +2905,20 @@ "license": "ISC" }, "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -3132,9 +3163,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.15", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", "dev": true, "funding": [ { @@ -3267,11 +3298,12 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -3280,9 +3312,9 @@ } }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "version": "8.5.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", + "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", "dev": true, "funding": [ { @@ -3300,7 +3332,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -3309,9 +3341,9 @@ } }, "node_modules/preact": { - "version": "10.28.2", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.28.2.tgz", - "integrity": "sha512-lbteaWGzGHdlIuiJ0l2Jq454m6kcpI1zNje6d8MlGAFlYvP2GO4ibnat7P74Esfz4sPTdM6UxtTwh/d3pwM9JA==", + "version": "10.29.3", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.29.3.tgz", + "integrity": "sha512-D9NL1GAnJZhc3RndVs4gDdxEeU9TcHgywMrhhOsnpdlvFjdbx0gAsLUnH6JEhlJH5giL7Tx5biWPUSEXE/HPzw==", "dev": true, "license": "MIT", "funding": { @@ -3330,9 +3362,9 @@ } }, "node_modules/prettier": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", - "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "version": "3.9.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.4.tgz", + "integrity": "sha512-yWG/o/4oJfo036EKAfK6ACAoDOfHeRHx4tuxkfBZiauURiaSmYwlpOr5LQqKtIkRD2z1PLteme2WoxEnj4tHTg==", "dev": true, "license": "MIT", "bin": { @@ -3346,9 +3378,9 @@ } }, "node_modules/property-information": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", - "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz", + "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==", "dev": true, "license": "MIT", "funding": { @@ -3411,13 +3443,13 @@ "license": "MIT" }, "node_modules/rollup": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", - "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.8" + "@types/estree": "1.0.9" }, "bin": { "rollup": "dist/bin/rollup" @@ -3427,31 +3459,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.59.0", - "@rollup/rollup-android-arm64": "4.59.0", - "@rollup/rollup-darwin-arm64": "4.59.0", - "@rollup/rollup-darwin-x64": "4.59.0", - "@rollup/rollup-freebsd-arm64": "4.59.0", - "@rollup/rollup-freebsd-x64": "4.59.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", - "@rollup/rollup-linux-arm-musleabihf": "4.59.0", - "@rollup/rollup-linux-arm64-gnu": "4.59.0", - "@rollup/rollup-linux-arm64-musl": "4.59.0", - "@rollup/rollup-linux-loong64-gnu": "4.59.0", - "@rollup/rollup-linux-loong64-musl": "4.59.0", - "@rollup/rollup-linux-ppc64-gnu": "4.59.0", - "@rollup/rollup-linux-ppc64-musl": "4.59.0", - "@rollup/rollup-linux-riscv64-gnu": "4.59.0", - "@rollup/rollup-linux-riscv64-musl": "4.59.0", - "@rollup/rollup-linux-s390x-gnu": "4.59.0", - "@rollup/rollup-linux-x64-gnu": "4.59.0", - "@rollup/rollup-linux-x64-musl": "4.59.0", - "@rollup/rollup-openbsd-x64": "4.59.0", - "@rollup/rollup-openharmony-arm64": "4.59.0", - "@rollup/rollup-win32-arm64-msvc": "4.59.0", - "@rollup/rollup-win32-ia32-msvc": "4.59.0", - "@rollup/rollup-win32-x64-gnu": "4.59.0", - "@rollup/rollup-win32-x64-msvc": "4.59.0", + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", "fsevents": "~2.3.2" } }, @@ -3464,9 +3496,9 @@ "peer": true }, "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -3602,21 +3634,21 @@ } }, "node_modules/tabbable": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz", - "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.5.0.tgz", + "integrity": "sha512-wieBHXygIm7OyQOu5hQlkk62/WyCFYGlWg7L6/ZCUZwx0o398Zkn4pVmMyfYhfMG8kGrj/Krt8eIk6UKC6VzwA==", "dev": true, "license": "MIT" }, "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", - "picomatch": "^4.0.3" + "picomatch": "^4.0.4" }, "engines": { "node": ">=12.0.0" @@ -3637,9 +3669,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", - "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dev": true, "license": "MIT", "engines": { @@ -3663,9 +3695,9 @@ } }, "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", "peer": true, @@ -3678,16 +3710,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.57.0.tgz", - "integrity": "sha512-W8GcigEMEeB07xEZol8oJ26rigm3+bfPHxHvwbYUlu1fUDsGuQ7Hiskx5xGW/xM4USc9Ephe3jtv7ZYPQntHeA==", + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.62.1.tgz", + "integrity": "sha512-vymnnM5g0AKQDSAyfP12nMIBvgwgA42syg74kkuZ4x1VuTzwQKwc5h9rGxeShCjny5o+zWAb6OEoz7XLgrIkIw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.57.0", - "@typescript-eslint/parser": "8.57.0", - "@typescript-eslint/typescript-estree": "8.57.0", - "@typescript-eslint/utils": "8.57.0" + "@typescript-eslint/eslint-plugin": "8.62.1", + "@typescript-eslint/parser": "8.62.1", + "@typescript-eslint/typescript-estree": "8.62.1", + "@typescript-eslint/utils": "8.62.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3698,13 +3730,13 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "dev": true, "license": "MIT" }, @@ -3827,6 +3859,7 @@ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", @@ -3924,17 +3957,18 @@ } }, "node_modules/vue": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.27.tgz", - "integrity": "sha512-aJ/UtoEyFySPBGarREmN4z6qNKpbEguYHMmXSiOGk69czc+zhs0NF6tEFrY8TZKAl8N/LYAkd4JHVd5E/AsSmw==", + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.39.tgz", + "integrity": "sha512-xmZCYabFGcirU8r0fTuvl/LICc1OU620rnqepaJDL/a141ZigkG7AyaxQLdqJ02ZRYzWe6YPaDHeQx7MfknQfA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@vue/compiler-dom": "3.5.27", - "@vue/compiler-sfc": "3.5.27", - "@vue/runtime-dom": "3.5.27", - "@vue/server-renderer": "3.5.27", - "@vue/shared": "3.5.27" + "@vue/compiler-dom": "3.5.39", + "@vue/compiler-sfc": "3.5.39", + "@vue/runtime-dom": "3.5.39", + "@vue/server-renderer": "3.5.39", + "@vue/shared": "3.5.39" }, "peerDependencies": { "typescript": "*" diff --git a/edgezero.toml b/edgezero.toml index 6660ff4..8f0487e 100644 --- a/edgezero.toml +++ b/edgezero.toml @@ -8,6 +8,7 @@ middleware = [ [stores.config] ids = ["mocktioneer_config"] +default = "mocktioneer_config" [[triggers.http]] id = "root" From e7dc0dbb62f3710550d9cc5e19b501ee5ffdb260 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Wed, 1 Jul 2026 21:12:26 -0700 Subject: [PATCH 34/45] docs: bump eslint 9->10, @types/node 24->26, typescript-eslint (docs devDeps) --- docs/package-lock.json | 462 ++++++++++------------------------------- docs/package.json | 8 +- 2 files changed, 111 insertions(+), 359 deletions(-) diff --git a/docs/package-lock.json b/docs/package-lock.json index a263d45..946ad47 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -9,11 +9,11 @@ "version": "1.0.0", "license": "Apache-2.0", "devDependencies": { - "@eslint/js": "^9.17.0", - "@types/node": "^24.10", - "eslint": "^9.17.0", + "@eslint/js": "^10.0.1", + "@types/node": "^26.1.0", + "eslint": "^10.6.0", "prettier": "^3.4.2", - "typescript-eslint": "^8.57.0", + "typescript-eslint": "^8.62.1", "vitepress": "^1.5.0" } }, @@ -811,105 +811,89 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.21.2", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", - "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.7", + "@eslint/object-schema": "^3.0.5", "debug": "^4.3.1", - "minimatch": "^3.1.5" + "minimatch": "^10.2.4" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/config-helpers": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", - "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz", + "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.17.0" + "@eslint/core": "^1.2.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/core": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", "dev": true, "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", - "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.14.0", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", - "minimatch": "^3.1.5", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/js": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", - "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", "dev": true, "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } } }, "node_modules/@eslint/object-schema": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", "dev": true, "license": "Apache-2.0", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/plugin-kit": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", - "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.17.0", + "@eslint/core": "^1.2.1", "levn": "^0.4.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@humanfs/core": { @@ -1439,6 +1423,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", @@ -1499,14 +1490,14 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.13.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.2.tgz", - "integrity": "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==", + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.0.tgz", + "integrity": "sha512-O0A1G3xPGy4w7AgQdAQYUlQ+BKk2Oovw8eRpofyp5KdBZULnbe+WqaOVNrm705SHphCiG4XHsACrSmPu1f+Kgw==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "undici-types": "~7.18.0" + "undici-types": "~8.3.0" } }, "node_modules/@types/unist": { @@ -1712,45 +1703,6 @@ "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", - "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.5" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@typescript-eslint/utils": { "version": "8.62.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.62.1.tgz", @@ -1793,19 +1745,6 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/@ungap/structured-clone": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.2.tgz", @@ -2146,36 +2085,16 @@ "node": ">= 14.0.0" } }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "18 || 20 || >=22" } }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, "node_modules/birpc": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", @@ -2187,24 +2106,16 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", - "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", + "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", + "balanced-match": "^4.0.2" + }, "engines": { - "node": ">=6" + "node": "18 || 20 || >=22" } }, "node_modules/ccount": { @@ -2218,23 +2129,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/character-entities-html4": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", @@ -2257,26 +2151,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, "node_modules/comma-separated-tokens": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", @@ -2288,13 +2162,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, "node_modules/copy-anything": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz", @@ -2455,34 +2322,34 @@ } }, "node_modules/eslint": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", - "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.6.0.tgz", + "integrity": "sha512-6lVbcqSodALYo+4ELD0heG6lFiFxnLMuLkiMi2qV8LMp54N8tE8FT1GMH+ev4Ti00nFjNze2+Su6DsV5OQW3Dg==", "dev": true, "license": "MIT", "peer": true, + "workspaces": [ + "packages/*" + ], "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.2", - "@eslint/config-helpers": "^0.4.2", - "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.5", - "@eslint/js": "9.39.4", - "@eslint/plugin-kit": "^0.4.1", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.6.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.2", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", - "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "esquery": "^1.5.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", @@ -2492,8 +2359,7 @@ "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.5", + "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, @@ -2501,7 +2367,7 @@ "eslint": "bin/eslint.js" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://eslint.org/donate" @@ -2516,48 +2382,50 @@ } }, "node_modules/eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, "license": "Apache-2.0", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.15.0", + "acorn": "^8.16.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" + "eslint-visitor-keys": "^5.0.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" @@ -2745,29 +2613,6 @@ "node": ">=10.13.0" } }, - "node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/hast-util-to-html": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", @@ -2834,23 +2679,6 @@ "node": ">= 4" } }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -2904,29 +2732,6 @@ "dev": true, "license": "ISC" }, - "node_modules/js-yaml": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", - "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/puzrin" - }, - { - "type": "github", - "url": "https://github.com/sponsors/nodeca" - } - ], - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -2988,13 +2793,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -3129,16 +2927,19 @@ "license": "MIT" }, "node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^5.0.5" }, "engines": { - "node": "*" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minisearch": { @@ -3250,19 +3051,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -3425,16 +3213,6 @@ "dev": true, "license": "MIT" }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/rfdc": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", @@ -3594,19 +3372,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/superjson": { "version": "2.2.6", "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.6.tgz", @@ -3620,19 +3385,6 @@ "node": ">=16" } }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/tabbable": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.5.0.tgz", @@ -3734,9 +3486,9 @@ } }, "node_modules/undici-types": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", - "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", "dev": true, "license": "MIT" }, diff --git a/docs/package.json b/docs/package.json index 75f2842..c26234d 100644 --- a/docs/package.json +++ b/docs/package.json @@ -14,11 +14,11 @@ "lint:fix": "eslint . --fix" }, "devDependencies": { - "@types/node": "^24.10", - "eslint": "^9.17.0", - "@eslint/js": "^9.17.0", - "typescript-eslint": "^8.57.0", + "@eslint/js": "^10.0.1", + "@types/node": "^26.1.0", + "eslint": "^10.6.0", "prettier": "^3.4.2", + "typescript-eslint": "^8.62.1", "vitepress": "^1.5.0" } } From e79b00f39da7a40dd17f4dafe90cf44bd91d7d72 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Wed, 1 Jul 2026 23:29:58 -0700 Subject: [PATCH 35/45] feat: mount framework introspection routes (/_mocktioneer/{manifest,config,routes}); pin edgezero to introspection-routes branch --- Cargo.lock | 17 +++++++++-------- Cargo.toml | 12 ++++++------ README.md | 1 + docs/guide/configuration.md | 24 ++++++++++++++++++++++++ edgezero.toml | 23 +++++++++++++++++++++++ 5 files changed, 63 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e8530a0..8139731 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -692,7 +692,7 @@ dependencies = [ [[package]] name = "edgezero-adapter" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=main#42843b1c3934fab32f99cc76ddd5881f421cccc7" +source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#89996235d122807a1f5b18f34bb3d46c4c3cd7a3" dependencies = [ "toml", ] @@ -700,7 +700,7 @@ dependencies = [ [[package]] name = "edgezero-adapter-axum" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=main#42843b1c3934fab32f99cc76ddd5881f421cccc7" +source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#89996235d122807a1f5b18f34bb3d46c4c3cd7a3" dependencies = [ "anyhow", "async-trait", @@ -728,7 +728,7 @@ dependencies = [ [[package]] name = "edgezero-adapter-cloudflare" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=main#42843b1c3934fab32f99cc76ddd5881f421cccc7" +source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#89996235d122807a1f5b18f34bb3d46c4c3cd7a3" dependencies = [ "anyhow", "async-trait", @@ -751,7 +751,7 @@ dependencies = [ [[package]] name = "edgezero-adapter-fastly" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=main#42843b1c3934fab32f99cc76ddd5881f421cccc7" +source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#89996235d122807a1f5b18f34bb3d46c4c3cd7a3" dependencies = [ "anyhow", "async-stream", @@ -780,7 +780,7 @@ dependencies = [ [[package]] name = "edgezero-adapter-spin" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=main#42843b1c3934fab32f99cc76ddd5881f421cccc7" +source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#89996235d122807a1f5b18f34bb3d46c4c3cd7a3" dependencies = [ "anyhow", "async-trait", @@ -807,7 +807,7 @@ dependencies = [ [[package]] name = "edgezero-cli" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=main#42843b1c3934fab32f99cc76ddd5881f421cccc7" +source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#89996235d122807a1f5b18f34bb3d46c4c3cd7a3" dependencies = [ "chrono", "clap", @@ -832,7 +832,7 @@ dependencies = [ [[package]] name = "edgezero-core" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=main#42843b1c3934fab32f99cc76ddd5881f421cccc7" +source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#89996235d122807a1f5b18f34bb3d46c4c3cd7a3" dependencies = [ "anyhow", "async-compression", @@ -863,12 +863,13 @@ dependencies = [ [[package]] name = "edgezero-macros" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=main#42843b1c3934fab32f99cc76ddd5881f421cccc7" +source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#89996235d122807a1f5b18f34bb3d46c4c3cd7a3" dependencies = [ "log", "proc-macro2", "quote", "serde", + "serde_json", "syn 2.0.118", "toml", "validator", diff --git a/Cargo.toml b/Cargo.toml index 19acb66..efdd85c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,12 +24,12 @@ axum = "0.8" base64 = "0.22" clap = { version = "4", features = ["derive"] } ed25519-dalek = "2.1" -edgezero-adapter-axum = { git = "https://github.com/stackpop/edgezero.git", branch = "main", package = "edgezero-adapter-axum", default-features = false } -edgezero-adapter-cloudflare = { git = "https://github.com/stackpop/edgezero.git", branch = "main", package = "edgezero-adapter-cloudflare", default-features = false } -edgezero-adapter-fastly = { git = "https://github.com/stackpop/edgezero.git", branch = "main", package = "edgezero-adapter-fastly", default-features = false } -edgezero-adapter-spin = { git = "https://github.com/stackpop/edgezero.git", branch = "main", package = "edgezero-adapter-spin", default-features = false } -edgezero-cli = { git = "https://github.com/stackpop/edgezero.git", branch = "main", package = "edgezero-cli" } -edgezero-core = { git = "https://github.com/stackpop/edgezero.git", branch = "main", package = "edgezero-core" } +edgezero-adapter-axum = { git = "https://github.com/stackpop/edgezero.git", branch = "worktree-feature+introspection-routes", package = "edgezero-adapter-axum", default-features = false } +edgezero-adapter-cloudflare = { git = "https://github.com/stackpop/edgezero.git", branch = "worktree-feature+introspection-routes", package = "edgezero-adapter-cloudflare", default-features = false } +edgezero-adapter-fastly = { git = "https://github.com/stackpop/edgezero.git", branch = "worktree-feature+introspection-routes", package = "edgezero-adapter-fastly", default-features = false } +edgezero-adapter-spin = { git = "https://github.com/stackpop/edgezero.git", branch = "worktree-feature+introspection-routes", package = "edgezero-adapter-spin", default-features = false } +edgezero-cli = { git = "https://github.com/stackpop/edgezero.git", branch = "worktree-feature+introspection-routes", package = "edgezero-cli" } +edgezero-core = { git = "https://github.com/stackpop/edgezero.git", branch = "worktree-feature+introspection-routes", package = "edgezero-core" } fastly = "0.12.1" futures = { version = "0.3", features = ["std", "executor"] } futures-util = "0.3.32" diff --git a/README.md b/README.md index 0b53338..f636935 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ Full documentation is available at **[stackpop.github.io/mocktioneer](https://st | `POST /e/dtb/bid` | APS TAM bid request | | `GET /static/creatives/{size}.html` | Creative wrapper | | `GET /_/sizes` | Supported sizes with pricing | +| `GET /_mocktioneer/{manifest,config,routes}` | Introspection (manifest / config / route table) | See the [full API reference](https://stackpop.github.io/mocktioneer/api/) for all endpoints. diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index a447ce9..caf9f94 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -84,9 +84,33 @@ adapters = ["axum", "cloudflare", "fastly", "spin"] | `/sync/start` | GET | `handle_sync_start` | EC pixel sync initiation | | `/sync/done` | GET | `handle_sync_done` | EC pixel sync callback | | `/resolve` | GET | `handle_resolve` | EC pull sync resolution | +| `/_mocktioneer/manifest` | GET | `introspection::manifest` | Full manifest as JSON | +| `/_mocktioneer/config` | GET | `introspection::config` | Effective app config | +| `/_mocktioneer/routes` | GET | `introspection::routes` | Route table as JSON | All routes also have OPTIONS handlers for CORS preflight. +### Introspection Routes + +The `/_mocktioneer/{manifest,config,routes}` endpoints are **framework-supplied** +handlers from `edgezero_core::introspection`, bound like any other route in +`edgezero.toml`: + +- **`manifest`** — the full `edgezero.toml` manifest as JSON (baked at compile + time; `[environment.secrets]` values are redacted). +- **`config`** — the effective app config from the default config store (the + pushed `bid_cpm` blob's `.data`), with any `#[secret]` fields left as + unresolved key-name references (secret-safe). +- **`routes`** — the live route table as `[{ "method", "path" }]`. + +::: warning Unauthenticated +These endpoints are unauthenticated wherever bound — restrict access at the +network/middleware layer before exposing them publicly. `manifest` emits +`[environment.variables]` values verbatim (only `[environment.secrets]` are +redacted), so keep secrets out of `[environment.variables]`. Mocktioneer +declares no `[environment]` section, so nothing sensitive is exposed today. +::: + ## Adapter Configuration Each adapter has its own configuration section: diff --git a/edgezero.toml b/edgezero.toml index 8f0487e..9e17979 100644 --- a/edgezero.toml +++ b/edgezero.toml @@ -10,6 +10,29 @@ middleware = [ ids = ["mocktioneer_config"] default = "mocktioneer_config" +# Introspection routes (framework-supplied by edgezero_core). Unauthenticated — +# they emit the manifest, effective config (secret-safe), and route table. +[[triggers.http]] +id = "introspect_manifest" +path = "/_mocktioneer/manifest" +methods = ["GET"] +handler = "edgezero_core::introspection::manifest" +adapters = ["axum", "cloudflare", "fastly", "spin"] + +[[triggers.http]] +id = "introspect_config" +path = "/_mocktioneer/config" +methods = ["GET"] +handler = "edgezero_core::introspection::config" +adapters = ["axum", "cloudflare", "fastly", "spin"] + +[[triggers.http]] +id = "introspect_routes" +path = "/_mocktioneer/routes" +methods = ["GET"] +handler = "edgezero_core::introspection::routes" +adapters = ["axum", "cloudflare", "fastly", "spin"] + [[triggers.http]] id = "root" path = "/" From ae1689ee7e1db66e8e41ecd1dad65aac84a604dd Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Wed, 1 Jul 2026 23:30:24 -0700 Subject: [PATCH 36/45] Formatting --- ...6-15-edgezero-extensible-cli-adaptation.md | 2 +- ...gezero-extensible-cli-adaptation-design.md | 47 ++++++++++--------- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md b/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md index 08b1700..220487b 100644 --- a/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md +++ b/docs/superpowers/plans/2026-06-15-edgezero-extensible-cli-adaptation.md @@ -40,7 +40,7 @@ | `crates/mocktioneer-core/src/config.rs` | `MocktioneerConfig` typed config | Create | | `crates/mocktioneer-core/src/lib.rs` | `pub mod config;` | Modify | | `crates/mocktioneer-core/src/auction.rs` | Thread `cpm: f64` into bid builders | Modify | -| `crates/mocktioneer-core/src/routes.rs` | Fail-loud `AppConfig` extractor on both handlers (R8) | Modify | +| `crates/mocktioneer-core/src/routes.rs` | Fail-loud `AppConfig` extractor on both handlers (R8) | Modify | | `mocktioneer.toml` | Typed config values (default `bid_cpm = 0.20`) | Create | | `crates/mocktioneer-cli/{Cargo.toml,src/main.rs}` | Custom CLI mirroring edgezero `-cli` | Create | | `Dockerfile` | Pre-copy `mocktioneer-cli` manifest before `cargo fetch` | Modify | diff --git a/docs/superpowers/specs/2026-06-11-edgezero-extensible-cli-adaptation-design.md b/docs/superpowers/specs/2026-06-11-edgezero-extensible-cli-adaptation-design.md index f2317e9..a42e333 100644 --- a/docs/superpowers/specs/2026-06-11-edgezero-extensible-cli-adaptation-design.md +++ b/docs/superpowers/specs/2026-06-11-edgezero-extensible-cli-adaptation-design.md @@ -199,6 +199,7 @@ pub struct MocktioneerConfig { - Root **`mocktioneer.toml`** (1:1, no `[config]` wrapper): `bid_cpm = 0.20`. **Runtime resolution — `AppConfig` extractor, fail-loud (R8 / blob model).** + > Superseded the original per-leaf `resolve_bid_cpm`/`get("bid_cpm")` design. > edgezero `89f59266` stores the whole typed config as one canonical-JSON > **blob envelope** (SHA-gated) under the store's key, read via the @@ -239,12 +240,12 @@ pub async fn handle_aps_bid( fetches the blob at the bound store's `default_key`, verifies the envelope SHA, deserialises into `MocktioneerConfig`, and runs `validator`. Outcomes: -| Runtime situation | Result | -| --- | --- | -| No `[stores.config]` / no config store bound | **error** (`EdgeError::internal` "no default config store registered") | -| Store bound but **no blob pushed** yet | **error** (`config_out_of_date` — "run `config push`") | -| Blob present, valid | typed `cfg.bid_cpm` | -| Blob present, value invalid (`bid_cpm` ≤ 0 / non-finite) | **error** (validation) | +| Runtime situation | Result | +| -------------------------------------------------------- | ---------------------------------------------------------------------- | +| No `[stores.config]` / no config store bound | **error** (`EdgeError::internal` "no default config store registered") | +| Store bound but **no blob pushed** yet | **error** (`config_out_of_date` — "run `config push`") | +| Blob present, valid | typed `cfg.bid_cpm` | +| Blob present, value invalid (`bid_cpm` ≤ 0 / non-finite) | **error** (validation) | So OpenRTB/APS **require a `config push` per deploy** before they serve; `FIXED_BID_CPM` is the builders' default arg + the shipped `mocktioneer.toml` @@ -356,7 +357,7 @@ and malformed-value (error) branches without a live backend. - **Docs formatter + VitePress exclusion (mandatory — this spec lives under `docs/`).** `docs/package.json`'s `format` runs `prettier --check .` and the format CI job runs it; it **fails on this spec file** today, and `npm run - build` renders specs/plans into `docs/.vitepress/dist/…/superpowers/…` (and a +build` renders specs/plans into `docs/.vitepress/dist/…/superpowers/…` (and a `.vitepress/.temp/`). Required: (a) add `superpowers/` to `docs/.prettierignore`; (b) add `srcExclude: ['**/superpowers/**']` to the VitePress config so internal specs aren't published; (c) ignore the build @@ -367,7 +368,7 @@ and malformed-value (error) branches without a live backend. (d) **verify after a build, not before** — run `npm run build` first, then `npm run format && npm run lint`, and assert `find docs/.vitepress/dist docs/.vitepress/.temp -path '*superpowers*' -print - -quit` yields nothing. (The prior revision only checked `format` pre-build, so +-quit` yields nothing. (The prior revision only checked `format` pre-build, so it missed the ~700 ESLint errors generated files produce.) ### 3.9 CI — `.github/workflows/test.yml` @@ -401,19 +402,19 @@ and malformed-value (error) branches without a live backend. ## 4. Risks & mitigations -| Risk | Mitigation | -| ---------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | -| New parser rejects an existing `[adapters.*]` table | Compile-time `manifest.validate()` surfaces it; fix per upstream error. | -| Spin SDK 6 macro/type churn beyond template | Mirror edgezero's `edgezero-adapter-spin` verbatim; build wasip2. | -| Wasmtime can't run the wasip2 component | Wasmtime 45.0.0 supports it; set `CARGO_TARGET_WASM32_WASIP2_RUNNER`; match edgezero's contract-test config. | -| Fresh dev errors before any push (R8 fail-loud) | Intended: auction/APS require `config push` once per deploy; documented in `configuration.md`/README (§3.5). | -| Broken/malformed pushed _value_ masked as $0.20 | Read errors propagate; malformed present value errors (§3.5). Note a malformed _file_ degrades to fallback (bind-time drop), by design. | -| `bid_cpm` never exercised (store unseeded) | CI seeds a non-default `0.35` via `--app-config` and `jq`-asserts it round-trips; registry-backed `auction_uses_seeded_cpm` handler test covers the read path (§5). | -| Spin KV config silently empty (no `runtime-config.toml`) | Add `runtime-config.toml` + `--runtime-config-file` to spin commands (§3.6). | -| Docker dependency-cache layer stale/incomplete | Cache hygiene only — `COPY crates` precedes `cargo fetch`; add the missing spin + cli manifests to the pre-copy list (§3.7). | -| Spec under `docs/` fails the format CI gate | Mandatory `docs/.prettierignore` + VitePress `srcExclude` (§3.8). | -| Pinning to an unmerged branch | Documented; re-pin to edgezero `main` post-merge. | -| `.cargo/config.toml.local` patch drift (`edgezero-macros`) | Already lists it; verify it patches cleanly. | +| Risk | Mitigation | +| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| New parser rejects an existing `[adapters.*]` table | Compile-time `manifest.validate()` surfaces it; fix per upstream error. | +| Spin SDK 6 macro/type churn beyond template | Mirror edgezero's `edgezero-adapter-spin` verbatim; build wasip2. | +| Wasmtime can't run the wasip2 component | Wasmtime 45.0.0 supports it; set `CARGO_TARGET_WASM32_WASIP2_RUNNER`; match edgezero's contract-test config. | +| Fresh dev errors before any push (R8 fail-loud) | Intended: auction/APS require `config push` once per deploy; documented in `configuration.md`/README (§3.5). | +| Broken/malformed pushed _value_ masked as $0.20 | Read errors propagate; malformed present value errors (§3.5). Note a malformed _file_ degrades to fallback (bind-time drop), by design. | +| `bid_cpm` never exercised (store unseeded) | CI seeds a non-default `0.35` via `--app-config` and `jq`-asserts it round-trips; registry-backed `auction_uses_seeded_cpm` handler test covers the read path (§5). | +| Spin KV config silently empty (no `runtime-config.toml`) | Add `runtime-config.toml` + `--runtime-config-file` to spin commands (§3.6). | +| Docker dependency-cache layer stale/incomplete | Cache hygiene only — `COPY crates` precedes `cargo fetch`; add the missing spin + cli manifests to the pre-copy list (§3.7). | +| Spec under `docs/` fails the format CI gate | Mandatory `docs/.prettierignore` + VitePress `srcExclude` (§3.8). | +| Pinning to an unmerged branch | Documented; re-pin to edgezero `main` post-merge. | +| `.cargo/config.toml.local` patch drift (`edgezero-macros`) | Already lists it; verify it patches cleanly. | ## 5. Verification @@ -446,8 +447,8 @@ and malformed-value (error) branches without a live backend. `jq -r '.mocktioneer_config | fromjson | .data.bid_cpm' …` == `0.35` (the blob envelope; not a bare push + `test -f`). 10. `docs/` gates pass: `cd docs && npm run format && npm run lint && npm run - build`, **and** `find docs/.vitepress/dist docs/.vitepress/.temp -path - '*superpowers*' -print -quit` produces no output (specs/plans excluded). +build`, **and** `find docs/.vitepress/dist docs/.vitepress/.temp -path +'*superpowers*' -print -quit` produces no output (specs/plans excluded). 11. `docker build` succeeds with `mocktioneer-cli` in the workspace (nice-to-have; not a gate — the build resolves regardless per §3.7). From 4c03911445a44a66a784e1d2da8a01dd260a78e7 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Thu, 2 Jul 2026 12:36:18 -0700 Subject: [PATCH 37/45] build: update edgezero introspection-routes pin to e68df70b --- Cargo.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8139731..c9a6efc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -692,7 +692,7 @@ dependencies = [ [[package]] name = "edgezero-adapter" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#89996235d122807a1f5b18f34bb3d46c4c3cd7a3" +source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#e68df70b1a856e20c20434472dc22d68769ce18e" dependencies = [ "toml", ] @@ -700,7 +700,7 @@ dependencies = [ [[package]] name = "edgezero-adapter-axum" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#89996235d122807a1f5b18f34bb3d46c4c3cd7a3" +source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#e68df70b1a856e20c20434472dc22d68769ce18e" dependencies = [ "anyhow", "async-trait", @@ -728,7 +728,7 @@ dependencies = [ [[package]] name = "edgezero-adapter-cloudflare" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#89996235d122807a1f5b18f34bb3d46c4c3cd7a3" +source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#e68df70b1a856e20c20434472dc22d68769ce18e" dependencies = [ "anyhow", "async-trait", @@ -751,7 +751,7 @@ dependencies = [ [[package]] name = "edgezero-adapter-fastly" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#89996235d122807a1f5b18f34bb3d46c4c3cd7a3" +source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#e68df70b1a856e20c20434472dc22d68769ce18e" dependencies = [ "anyhow", "async-stream", @@ -780,7 +780,7 @@ dependencies = [ [[package]] name = "edgezero-adapter-spin" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#89996235d122807a1f5b18f34bb3d46c4c3cd7a3" +source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#e68df70b1a856e20c20434472dc22d68769ce18e" dependencies = [ "anyhow", "async-trait", @@ -807,7 +807,7 @@ dependencies = [ [[package]] name = "edgezero-cli" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#89996235d122807a1f5b18f34bb3d46c4c3cd7a3" +source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#e68df70b1a856e20c20434472dc22d68769ce18e" dependencies = [ "chrono", "clap", @@ -832,7 +832,7 @@ dependencies = [ [[package]] name = "edgezero-core" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#89996235d122807a1f5b18f34bb3d46c4c3cd7a3" +source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#e68df70b1a856e20c20434472dc22d68769ce18e" dependencies = [ "anyhow", "async-compression", @@ -863,7 +863,7 @@ dependencies = [ [[package]] name = "edgezero-macros" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#89996235d122807a1f5b18f34bb3d46c4c3cd7a3" +source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#e68df70b1a856e20c20434472dc22d68769ce18e" dependencies = [ "log", "proc-macro2", From 3b4f762b0943979ee4431e624b1ba19f0be4d258 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Thu, 2 Jul 2026 12:47:30 -0700 Subject: [PATCH 38/45] Use latest from edgzero branch --- Cargo.lock | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c9a6efc..4273daa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -692,7 +692,7 @@ dependencies = [ [[package]] name = "edgezero-adapter" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#e68df70b1a856e20c20434472dc22d68769ce18e" +source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#2efa2da69d4bd1d4f20b7d9595440e6bbc416279" dependencies = [ "toml", ] @@ -700,7 +700,7 @@ dependencies = [ [[package]] name = "edgezero-adapter-axum" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#e68df70b1a856e20c20434472dc22d68769ce18e" +source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#2efa2da69d4bd1d4f20b7d9595440e6bbc416279" dependencies = [ "anyhow", "async-trait", @@ -728,7 +728,7 @@ dependencies = [ [[package]] name = "edgezero-adapter-cloudflare" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#e68df70b1a856e20c20434472dc22d68769ce18e" +source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#2efa2da69d4bd1d4f20b7d9595440e6bbc416279" dependencies = [ "anyhow", "async-trait", @@ -751,7 +751,7 @@ dependencies = [ [[package]] name = "edgezero-adapter-fastly" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#e68df70b1a856e20c20434472dc22d68769ce18e" +source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#2efa2da69d4bd1d4f20b7d9595440e6bbc416279" dependencies = [ "anyhow", "async-stream", @@ -780,7 +780,7 @@ dependencies = [ [[package]] name = "edgezero-adapter-spin" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#e68df70b1a856e20c20434472dc22d68769ce18e" +source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#2efa2da69d4bd1d4f20b7d9595440e6bbc416279" dependencies = [ "anyhow", "async-trait", @@ -807,7 +807,7 @@ dependencies = [ [[package]] name = "edgezero-cli" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#e68df70b1a856e20c20434472dc22d68769ce18e" +source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#2efa2da69d4bd1d4f20b7d9595440e6bbc416279" dependencies = [ "chrono", "clap", @@ -832,7 +832,7 @@ dependencies = [ [[package]] name = "edgezero-core" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#e68df70b1a856e20c20434472dc22d68769ce18e" +source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#2efa2da69d4bd1d4f20b7d9595440e6bbc416279" dependencies = [ "anyhow", "async-compression", @@ -863,7 +863,7 @@ dependencies = [ [[package]] name = "edgezero-macros" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#e68df70b1a856e20c20434472dc22d68769ce18e" +source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#2efa2da69d4bd1d4f20b7d9595440e6bbc416279" dependencies = [ "log", "proc-macro2", @@ -2270,9 +2270,9 @@ dependencies = [ [[package]] name = "rustc-hash" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" [[package]] name = "rustc_version" From 6f38760c444dd2183649e285d6855103581fcc47 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Tue, 7 Jul 2026 13:19:27 -0700 Subject: [PATCH 39/45] =?UTF-8?q?fix:=20PR=20#110=20review=20=E2=80=94=20D?= =?UTF-8?q?ocker=20binds=200.0.0.0=20(+=20smoke),=20config-validate=20gate?= =?UTF-8?q?=20uses=20example,=20APS=20route-level=20cpm=20tests,=20documen?= =?UTF-8?q?t=20Spin=20spin-up=20limitation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude/commands/check-ci.md | 2 +- .github/workflows/docker.yml | 33 +++++++++++++++++++++ CLAUDE.md | 10 +++++-- Dockerfile | 6 ++++ crates/mocktioneer-core/src/routes.rs | 42 +++++++++++++++++++++++++++ docs/guide/configuration.md | 10 +++++++ 6 files changed, 100 insertions(+), 3 deletions(-) diff --git a/.claude/commands/check-ci.md b/.claude/commands/check-ci.md index 53701a4..b9c256e 100644 --- a/.claude/commands/check-ci.md +++ b/.claude/commands/check-ci.md @@ -4,6 +4,6 @@ Run the full CI gate checks locally before pushing. Run all commands sequentiall 2. `cargo clippy --workspace --all-targets --all-features -- -D warnings` 3. `cargo test --workspace --all-targets` 4. `cargo check --workspace --all-targets --features "fastly cloudflare"` -5. `cargo run -p mocktioneer-cli -- config validate --strict` +5. `cargo run -p mocktioneer-cli -- config validate --strict --app-config mocktioneer.toml.example` (`mocktioneer.toml` is gitignored; validate the committed template) If any step fails, show the errors and suggest fixes. Do not proceed to the next step until the current one passes. diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 0162923..4774a92 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -88,3 +88,36 @@ jobs: build-args: RUST_VERSION=${{ steps.rust.outputs.version }} cache-from: type=gha cache-to: type=gha,mode=max + + # Load the just-built image locally (reuses the gha cache, so this is + # near-instant). `load` can't be combined with a registry push in one + # step, hence the separate cache-hit build. + - name: Load image for smoke test + uses: docker/build-push-action@v6 + with: + context: . + load: true + push: false + tags: mocktioneer:ci-smoke + platforms: linux/amd64 + build-args: RUST_VERSION=${{ steps.rust.outputs.version }} + cache-from: type=gha + + - name: Smoke test — reachable on mapped port and serves bids + run: | + cid="$(docker run -d -p 8787:8787 mocktioneer:ci-smoke)" + # Wait for the mapped host port — proves the container binds 0.0.0.0, + # not 127.0.0.1 (a loopback bind would make this fail). + for _ in $(seq 1 30); do + curl -fsS http://127.0.0.1:8787/ >/dev/null 2>&1 && break + sleep 2 + done + curl -fsS http://127.0.0.1:8787/ >/dev/null + # The baked-in config blob should let the auction endpoint serve a bid. + code="$(curl -s -o /dev/null -w '%{http_code}' -X POST http://127.0.0.1:8787/openrtb2/auction \ + -H 'content-type: application/json' \ + -d '{"id":"t","imp":[{"id":"1","banner":{"w":300,"h":250}}]}')" + echo "auction status: $code" + docker logs "$cid" 2>&1 | tail -20 || true + docker rm -f "$cid" >/dev/null 2>&1 || true + test "$code" = "200" diff --git a/CLAUDE.md b/CLAUDE.md index 8e30bdf..fa31608 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -53,7 +53,12 @@ cargo run -p mocktioneer-adapter-axum # Run via EdgeZero CLI edgezero-cli serve --adapter cloudflare # Cloudflare on :8787 edgezero-cli serve --adapter fastly # Fastly on :7676 -edgezero-cli serve --adapter spin # Spin on :3000 +edgezero-cli serve --adapter spin # Spin on :3000 — NOTE: `spin up` is + # currently blocked (spin-sdk 6.0.0 + # imports wasi:http@0.3.0-rc, which no + # released Spin runtime provides). + # Build + wasmtime contract tests pass; + # live serve needs an upstream fix. # Playwright e2e tests cd tests/playwright && npm test @@ -171,7 +176,8 @@ Every PR must pass: 2. `cargo clippy --workspace --all-targets --all-features -- -D warnings` 3. `cargo test --workspace --all-targets` 4. `cargo check --workspace --all-targets --features "fastly cloudflare"` -5. `cargo run -p mocktioneer-cli -- config validate --strict` +5. `cargo run -p mocktioneer-cli -- config validate --strict --app-config mocktioneer.toml.example` + (`mocktioneer.toml` is gitignored; CI copies the template first — see `test.yml`) 6. Playwright e2e tests (`tests/playwright/`) 7. ESLint + Prettier on `docs/` diff --git a/Dockerfile b/Dockerfile index 57e62a4..be6d7bd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -54,6 +54,12 @@ COPY --from=builder --chown=10001:10001 /app/.edgezero /app/.edgezero USER appuser +# Bind to all interfaces so `docker run -p :8787` (and k8s) can reach the +# service — EdgeZero's Axum dev server otherwise defaults to 127.0.0.1:8787, +# which is only reachable from inside the container. +ENV EDGEZERO__ADAPTER__HOST=0.0.0.0 \ + EDGEZERO__ADAPTER__PORT=8787 + EXPOSE 8787 HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \ diff --git a/crates/mocktioneer-core/src/routes.rs b/crates/mocktioneer-core/src/routes.rs index b9d2993..84c46fc 100644 --- a/crates/mocktioneer-core/src/routes.rs +++ b/crates/mocktioneer-core/src/routes.rs @@ -998,6 +998,7 @@ fn sanitize_for_log(input: &str, max_len: usize) -> String { #[cfg(test)] mod tests { use super::*; + use crate::auction::decode_aps_price; use edgezero_core::blob_envelope::BlobEnvelope; use edgezero_core::body::Body; use edgezero_core::config_store::{ConfigStore, ConfigStoreError, ConfigStoreHandle}; @@ -1133,6 +1134,47 @@ mod tests { assert_ne!(response.status(), StatusCode::OK); } + #[test] + fn aps_bid_uses_seeded_cpm() { + let body = serde_json::json!({ + "pubId": "5555", + "slots": [{ "slotID": "slot1", "sizes": [[300_i32, 250_i32]] }] + }); + let ctx = ctx_with_cpm( + Method::POST, + "/e/dtb/bid", + Body::json(&body).expect("json body"), + &[], + 0.35_f64, + ); + let response = response_from(block_on(handle_aps_bid(ctx))); + assert_eq!(response.status(), StatusCode::OK); + let payload: serde_json::Value = + serde_json::from_slice(&body_bytes(response)).expect("json"); + let amznbid = payload["contextual"]["slots"][0]["amznbid"] + .as_str() + .expect("amznbid"); + let price = decode_aps_price(amznbid).expect("decode price"); + assert!((price - 0.35).abs() < f64::EPSILON); + } + + #[test] + fn aps_bid_without_config_errors() { + // Fail-loud parity with the OpenRTB path: no config store bound → error. + let body = serde_json::json!({ + "pubId": "5555", + "slots": [{ "slotID": "slot1", "sizes": [[300_i32, 250_i32]] }] + }); + let request = request_builder() + .method(Method::POST) + .uri("/e/dtb/bid") + .body(Body::json(&body).expect("json body")) + .expect("request"); + let ctx = RequestContext::new(request, PathParams::new(HashMap::new())); + let response = response_from(block_on(handle_aps_bid(ctx))); + assert_ne!(response.status(), StatusCode::OK); + } + #[test] fn parse_size_param_parses_suffix() { assert_eq!(parse_size_param("300x250.svg", ".svg"), Some((300, 250))); diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index caf9f94..55501bd 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -186,6 +186,16 @@ echo_stdout = true Spin targets `wasm32-wasip2` (spin-sdk 6). Its config store is KV-backed, so the `serve`/`deploy` commands pass a `--runtime-config-file` declaring the KV label: +::: warning `spin up` currently blocked (upstream) +The Spin adapter **compiles** to a `wasm32-wasip2` component and passes the +router-level contract tests (run under `wasmtime`), but `spin up` on today's +Spin runtimes (e.g. 3.6.3) fails to link: `spin-sdk 6.0.0` pulls +`wasi:http@0.3.0-rc`, which no released Spin provides (they expose +`wasi:http@0.2`). This is a spin-sdk/runtime ABI mismatch to be resolved +upstream in EdgeZero's `spin-sdk` pin — the Fastly / Cloudflare / Axum adapters +are unaffected. +::: + ```toml [adapters.spin.adapter] crate = "crates/mocktioneer-adapter-spin" From 072237e5b1ef847cef7a1b68d61b16ca79df9194 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Tue, 7 Jul 2026 13:36:31 -0700 Subject: [PATCH 40/45] fix: pin runtime base to debian:bookworm-slim to match builder release --- Dockerfile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index be6d7bd..c755af9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -36,7 +36,10 @@ RUN cargo build --locked --release -p mocktioneer-adapter-axum -p mocktioneer-cl # `/app/.edgezero/local-config-mocktioneer_config.json`.) RUN ./target/release/mocktioneer-cli config push --adapter axum --yes -FROM debian:stable-slim AS runtime +# Pin the runtime base to the same Debian release as the builder +# (`rust:1.95.0-slim-bookworm`) so the runtime glibc can't drift out from under +# the build env on a later rebuild. +FROM debian:bookworm-slim AS runtime RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates \ From 699b0612ea88ff2abd14895cd5c1052c1f5cce02 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Thu, 9 Jul 2026 11:04:42 -0700 Subject: [PATCH 41/45] build: pin edgezero deps to released tag v0.0.4 v0.0.4 (9e661ae5) is a superset release containing both the extensible CLI (edgezero #269) and the pluggable introspection routes (#300), replacing the transient worktree branch pin with a stable, durable tag. --- Cargo.lock | 16 ++++++++-------- Cargo.toml | 12 ++++++------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4273daa..df66244 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -692,7 +692,7 @@ dependencies = [ [[package]] name = "edgezero-adapter" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#2efa2da69d4bd1d4f20b7d9595440e6bbc416279" +source = "git+https://github.com/stackpop/edgezero.git?tag=v0.0.4#9e661ae520a8130660f18fd10f42703d7f3e050b" dependencies = [ "toml", ] @@ -700,7 +700,7 @@ dependencies = [ [[package]] name = "edgezero-adapter-axum" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#2efa2da69d4bd1d4f20b7d9595440e6bbc416279" +source = "git+https://github.com/stackpop/edgezero.git?tag=v0.0.4#9e661ae520a8130660f18fd10f42703d7f3e050b" dependencies = [ "anyhow", "async-trait", @@ -728,7 +728,7 @@ dependencies = [ [[package]] name = "edgezero-adapter-cloudflare" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#2efa2da69d4bd1d4f20b7d9595440e6bbc416279" +source = "git+https://github.com/stackpop/edgezero.git?tag=v0.0.4#9e661ae520a8130660f18fd10f42703d7f3e050b" dependencies = [ "anyhow", "async-trait", @@ -751,7 +751,7 @@ dependencies = [ [[package]] name = "edgezero-adapter-fastly" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#2efa2da69d4bd1d4f20b7d9595440e6bbc416279" +source = "git+https://github.com/stackpop/edgezero.git?tag=v0.0.4#9e661ae520a8130660f18fd10f42703d7f3e050b" dependencies = [ "anyhow", "async-stream", @@ -780,7 +780,7 @@ dependencies = [ [[package]] name = "edgezero-adapter-spin" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#2efa2da69d4bd1d4f20b7d9595440e6bbc416279" +source = "git+https://github.com/stackpop/edgezero.git?tag=v0.0.4#9e661ae520a8130660f18fd10f42703d7f3e050b" dependencies = [ "anyhow", "async-trait", @@ -807,7 +807,7 @@ dependencies = [ [[package]] name = "edgezero-cli" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#2efa2da69d4bd1d4f20b7d9595440e6bbc416279" +source = "git+https://github.com/stackpop/edgezero.git?tag=v0.0.4#9e661ae520a8130660f18fd10f42703d7f3e050b" dependencies = [ "chrono", "clap", @@ -832,7 +832,7 @@ dependencies = [ [[package]] name = "edgezero-core" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#2efa2da69d4bd1d4f20b7d9595440e6bbc416279" +source = "git+https://github.com/stackpop/edgezero.git?tag=v0.0.4#9e661ae520a8130660f18fd10f42703d7f3e050b" dependencies = [ "anyhow", "async-compression", @@ -863,7 +863,7 @@ dependencies = [ [[package]] name = "edgezero-macros" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero.git?branch=worktree-feature%2Bintrospection-routes#2efa2da69d4bd1d4f20b7d9595440e6bbc416279" +source = "git+https://github.com/stackpop/edgezero.git?tag=v0.0.4#9e661ae520a8130660f18fd10f42703d7f3e050b" dependencies = [ "log", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index efdd85c..a0a144c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,12 +24,12 @@ axum = "0.8" base64 = "0.22" clap = { version = "4", features = ["derive"] } ed25519-dalek = "2.1" -edgezero-adapter-axum = { git = "https://github.com/stackpop/edgezero.git", branch = "worktree-feature+introspection-routes", package = "edgezero-adapter-axum", default-features = false } -edgezero-adapter-cloudflare = { git = "https://github.com/stackpop/edgezero.git", branch = "worktree-feature+introspection-routes", package = "edgezero-adapter-cloudflare", default-features = false } -edgezero-adapter-fastly = { git = "https://github.com/stackpop/edgezero.git", branch = "worktree-feature+introspection-routes", package = "edgezero-adapter-fastly", default-features = false } -edgezero-adapter-spin = { git = "https://github.com/stackpop/edgezero.git", branch = "worktree-feature+introspection-routes", package = "edgezero-adapter-spin", default-features = false } -edgezero-cli = { git = "https://github.com/stackpop/edgezero.git", branch = "worktree-feature+introspection-routes", package = "edgezero-cli" } -edgezero-core = { git = "https://github.com/stackpop/edgezero.git", branch = "worktree-feature+introspection-routes", package = "edgezero-core" } +edgezero-adapter-axum = { git = "https://github.com/stackpop/edgezero.git", tag = "v0.0.4", package = "edgezero-adapter-axum", default-features = false } +edgezero-adapter-cloudflare = { git = "https://github.com/stackpop/edgezero.git", tag = "v0.0.4", package = "edgezero-adapter-cloudflare", default-features = false } +edgezero-adapter-fastly = { git = "https://github.com/stackpop/edgezero.git", tag = "v0.0.4", package = "edgezero-adapter-fastly", default-features = false } +edgezero-adapter-spin = { git = "https://github.com/stackpop/edgezero.git", tag = "v0.0.4", package = "edgezero-adapter-spin", default-features = false } +edgezero-cli = { git = "https://github.com/stackpop/edgezero.git", tag = "v0.0.4", package = "edgezero-cli" } +edgezero-core = { git = "https://github.com/stackpop/edgezero.git", tag = "v0.0.4", package = "edgezero-core" } fastly = "0.12.1" futures = { version = "0.3", features = ["std", "executor"] } futures-util = "0.3.32" From 0bd3e152322e572719fcc7adcd6bd3f8f961da54 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Thu, 9 Jul 2026 11:34:05 -0700 Subject: [PATCH 42/45] =?UTF-8?q?fix:=20PR=20#110=20review=20round=203=20?= =?UTF-8?q?=E2=80=94=20Dockerfile=20layer-cache,=20spin=20deploy=20flag,?= =?UTF-8?q?=20config=5Fregistry=20helper,=20drop=20dead=20unsafe=5Fcode=20?= =?UTF-8?q?allow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 9 ++++++--- crates/mocktioneer-adapter-spin/src/lib.rs | 8 -------- crates/mocktioneer-core/src/routes.rs | 19 ++++++++----------- docs/guide/configuration.md | 6 ++++-- edgezero.toml | 2 +- 5 files changed, 19 insertions(+), 25 deletions(-) diff --git a/Dockerfile b/Dockerfile index c755af9..3459e9a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,13 +22,16 @@ COPY crates/mocktioneer-cli/Cargo.toml crates/mocktioneer-cli/Cargo.toml COPY crates ./crates COPY edgezero.toml ./edgezero.toml -# `mocktioneer.toml` is gitignored (per-env); ship the committed template as the -# config the image seeds from. -COPY mocktioneer.toml.example ./mocktioneer.toml RUN cargo fetch --locked RUN cargo build --locked --release -p mocktioneer-adapter-axum -p mocktioneer-cli +# `mocktioneer.toml` is gitignored (per-env); ship the committed template as the +# config the image seeds from. Copied here (after the build layers) so that +# editing `bid_cpm` in the template doesn't invalidate the dependency-fetch and +# compile caches — only the cheap `config push` layer below re-runs. +COPY mocktioneer.toml.example ./mocktioneer.toml + # Seed the default typed-config blob into `.edgezero/` so the OpenRTB/APS # endpoints serve out-of-the-box: under edgezero #269 they read `bid_cpm` # through the fail-loud `AppConfig` extractor and 503 until a blob is pushed. diff --git a/crates/mocktioneer-adapter-spin/src/lib.rs b/crates/mocktioneer-adapter-spin/src/lib.rs index 020994f..d0cae24 100644 --- a/crates/mocktioneer-adapter-spin/src/lib.rs +++ b/crates/mocktioneer-adapter-spin/src/lib.rs @@ -1,11 +1,3 @@ -#![cfg_attr( - target_arch = "wasm32", - allow( - unsafe_code, - reason = "spin's #[http_service] macro generates the unsafe wasm export" - ) -)] - #[cfg(target_arch = "wasm32")] use mocktioneer_core::MocktioneerApp; #[cfg(target_arch = "wasm32")] diff --git a/crates/mocktioneer-core/src/routes.rs b/crates/mocktioneer-core/src/routes.rs index 84c46fc..57271c3 100644 --- a/crates/mocktioneer-core/src/routes.rs +++ b/crates/mocktioneer-core/src/routes.rs @@ -1009,7 +1009,7 @@ mod tests { use edgezero_core::response::IntoResponse as _; use edgezero_core::store_registry::{ConfigRegistry, ConfigStoreBinding, StoreRegistry}; use futures::executor::block_on; - use std::collections::{BTreeMap, HashMap}; + use std::collections::HashMap; use std::sync::Arc; /// In-memory `ConfigStore` for tests (mirrors app-demo's `MapConfigStore`). @@ -1052,16 +1052,13 @@ mod tests { [("mocktioneer_config".to_owned(), config_blob(bid_cpm))] .into_iter() .collect(); - let handle = ConfigStoreHandle::new(Arc::new(MapConfigStore(map))); - let binding = ConfigStoreBinding { - handle, - default_key: "mocktioneer_config".to_owned(), - }; - let by_id: BTreeMap = - [("mocktioneer_config".to_owned(), binding)] - .into_iter() - .collect(); - StoreRegistry::new(by_id, "mocktioneer_config".to_owned()) + StoreRegistry::single_id( + "mocktioneer_config".to_owned(), + ConfigStoreBinding { + handle: ConfigStoreHandle::new(Arc::new(MapConfigStore(map))), + default_key: "mocktioneer_config".to_owned(), + }, + ) } fn ctx(method: Method, uri: &str, body: Body, params: &[(&str, &str)]) -> RequestContext { diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index 55501bd..ff6e214 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -184,7 +184,9 @@ echo_stdout = true ### Spin Adapter Spin targets `wasm32-wasip2` (spin-sdk 6). Its config store is KV-backed, so the -`serve`/`deploy` commands pass a `--runtime-config-file` declaring the KV label: +`serve` command passes a `--runtime-config-file` declaring the KV label +(`spin deploy` is plugin-mediated and provisions KV itself, so it takes no +runtime-config flag): ::: warning `spin up` currently blocked (upstream) The Spin adapter **compiles** to a `wasm32-wasip2` component and passes the @@ -209,7 +211,7 @@ features = ["spin"] [adapters.spin.commands] build = "spin build --from crates/mocktioneer-adapter-spin/spin.toml" serve = "spin up --from crates/mocktioneer-adapter-spin/spin.toml --runtime-config-file crates/mocktioneer-adapter-spin/runtime-config.toml" -deploy = "spin deploy --from crates/mocktioneer-adapter-spin/spin.toml --runtime-config-file crates/mocktioneer-adapter-spin/runtime-config.toml" +deploy = "spin deploy --from crates/mocktioneer-adapter-spin/spin.toml" [adapters.spin.logging] level = "info" diff --git a/edgezero.toml b/edgezero.toml index 9e17979..29d1a39 100644 --- a/edgezero.toml +++ b/edgezero.toml @@ -282,7 +282,7 @@ features = ["spin"] [adapters.spin.commands] build = "spin build --from crates/mocktioneer-adapter-spin/spin.toml" -deploy = "spin deploy --from crates/mocktioneer-adapter-spin/spin.toml --runtime-config-file crates/mocktioneer-adapter-spin/runtime-config.toml" +deploy = "spin deploy --from crates/mocktioneer-adapter-spin/spin.toml" serve = "spin up --from crates/mocktioneer-adapter-spin/spin.toml --runtime-config-file crates/mocktioneer-adapter-spin/runtime-config.toml" [adapters.spin.logging] From 889fba05430e53067e74dea742d8972eb1d9331b Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Fri, 10 Jul 2026 21:57:45 -0700 Subject: [PATCH 43/45] =?UTF-8?q?fix:=20PR=20#110=20review=20round=204=20?= =?UTF-8?q?=E2=80=94=20gate=20Docker=20push=20on=20smoke=20test,=20assert?= =?UTF-8?q?=20exact=20503=20config=5Fout=5Fof=5Fdate=20contract,=20fresh-c?= =?UTF-8?q?lone=20getting-started=20validate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/docker.yml | 35 +++++++------- crates/mocktioneer-core/src/routes.rs | 66 +++++++++++++++++++++------ docs/guide/getting-started.md | 4 +- 3 files changed, 73 insertions(+), 32 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 4774a92..7b061a3 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -77,22 +77,11 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Build and push - uses: docker/build-push-action@v6 - with: - context: . - push: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - platforms: linux/amd64 - build-args: RUST_VERSION=${{ steps.rust.outputs.version }} - cache-from: type=gha - cache-to: type=gha,mode=max - - # Load the just-built image locally (reuses the gha cache, so this is - # near-instant). `load` can't be combined with a registry push in one - # step, hence the separate cache-hit build. - - name: Load image for smoke test + # Build once and load locally (no registry push yet) so the smoke test + # can gate publication — a broken image must never reach its final tag. + # `cache-to` is populated here so the publish step below is a near-instant + # cache hit of the exact same image. + - name: Build and load for smoke test uses: docker/build-push-action@v6 with: context: . @@ -102,6 +91,7 @@ jobs: platforms: linux/amd64 build-args: RUST_VERSION=${{ steps.rust.outputs.version }} cache-from: type=gha + cache-to: type=gha,mode=max - name: Smoke test — reachable on mapped port and serves bids run: | @@ -121,3 +111,16 @@ jobs: docker logs "$cid" 2>&1 | tail -20 || true docker rm -f "$cid" >/dev/null 2>&1 || true test "$code" = "200" + + # Only now publish — reuses the gha cache from the build above, so this + # pushes the exact image the smoke test just validated (not a rebuild). + - name: Push validated image + uses: docker/build-push-action@v6 + with: + context: . + push: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + platforms: linux/amd64 + build-args: RUST_VERSION=${{ steps.rust.outputs.version }} + cache-from: type=gha diff --git a/crates/mocktioneer-core/src/routes.rs b/crates/mocktioneer-core/src/routes.rs index 57271c3..1f4c556 100644 --- a/crates/mocktioneer-core/src/routes.rs +++ b/crates/mocktioneer-core/src/routes.rs @@ -1039,6 +1039,23 @@ mod tests { .to_vec() } + /// Assert the fail-loud compatibility contract for a bid route served with + /// no config store bound: `503 Service Unavailable`, a `Retry-After` header, + /// and a JSON body whose `error.kind` is `config_out_of_date`. + fn assert_config_out_of_date(response: Response) { + assert_eq!(response.status(), StatusCode::SERVICE_UNAVAILABLE); + assert_eq!( + response + .headers() + .get("retry-after") + .and_then(|value| value.to_str().ok()), + Some("60"), + ); + let payload: serde_json::Value = + serde_json::from_slice(&body_bytes(response)).expect("json error body"); + assert_eq!(payload["error"]["kind"], "config_out_of_date"); + } + /// A valid blob-envelope JSON string wrapping `{ "bid_cpm": }`, /// matching what `config push` writes for the typed config. fn config_blob(bid_cpm: f64) -> String { @@ -1065,6 +1082,27 @@ mod tests { ctx_with_cpm(method, uri, body, params, 0.20_f64) } + /// A `RequestContext` whose default config store is bound but empty — i.e. a + /// deploy that declared `[stores.config]` but never ran `config push`. The + /// `AppConfig` extractor then fails loud with `503 config_out_of_date`, the + /// documented "must push before serving bids" compatibility contract. + fn ctx_without_pushed_config(method: Method, uri: &str, body: Body) -> RequestContext { + let mut request = request_builder() + .method(method) + .uri(uri) + .body(body) + .expect("request"); + let empty = StoreRegistry::single_id( + "mocktioneer_config".to_owned(), + ConfigStoreBinding { + handle: ConfigStoreHandle::new(Arc::new(MapConfigStore(HashMap::new()))), + default_key: "mocktioneer_config".to_owned(), + }, + ); + request.extensions_mut().insert(empty); + RequestContext::new(request, PathParams::new(HashMap::new())) + } + /// Like [`ctx`] but seeds the default config store with a `bid_cpm` blob. /// The auction/APS handlers use the fail-loud `AppConfig` extractor, so a /// bound, valid blob must be present for them to run. @@ -1121,14 +1159,13 @@ mod tests { "id": "rc", "imp": [{ "id": "1", "banner": { "w": 300_i32, "h": 250_i32 } }] }); - let request = request_builder() - .method(Method::POST) - .uri("/openrtb2/auction") - .body(Body::json(&body).expect("json body")) - .expect("request"); - let ctx = RequestContext::new(request, PathParams::new(HashMap::new())); + let ctx = ctx_without_pushed_config( + Method::POST, + "/openrtb2/auction", + Body::json(&body).expect("json body"), + ); let response = response_from(block_on(handle_openrtb_auction(ctx))); - assert_ne!(response.status(), StatusCode::OK); + assert_config_out_of_date(response); } #[test] @@ -1157,19 +1194,18 @@ mod tests { #[test] fn aps_bid_without_config_errors() { - // Fail-loud parity with the OpenRTB path: no config store bound → error. + // Fail-loud parity with the OpenRTB path: store bound but not pushed. let body = serde_json::json!({ "pubId": "5555", "slots": [{ "slotID": "slot1", "sizes": [[300_i32, 250_i32]] }] }); - let request = request_builder() - .method(Method::POST) - .uri("/e/dtb/bid") - .body(Body::json(&body).expect("json body")) - .expect("request"); - let ctx = RequestContext::new(request, PathParams::new(HashMap::new())); + let ctx = ctx_without_pushed_config( + Method::POST, + "/e/dtb/bid", + Body::json(&body).expect("json body"), + ); let response = response_from(block_on(handle_aps_bid(ctx))); - assert_ne!(response.status(), StatusCode::OK); + assert_config_out_of_date(response); } #[test] diff --git a/docs/guide/getting-started.md b/docs/guide/getting-started.md index b351124..10515a7 100644 --- a/docs/guide/getting-started.md +++ b/docs/guide/getting-started.md @@ -43,7 +43,9 @@ library) — no external install needed, and it adds the typed config commands: ```bash cargo run -p mocktioneer-cli -- --help -cargo run -p mocktioneer-cli -- config validate --strict +# `mocktioneer.toml` is gitignored (created in Running Locally below); validate +# the committed template so this works on a fresh checkout. +cargo run -p mocktioneer-cli -- config validate --strict --app-config mocktioneer.toml.example ``` ## Running Locally From 079d28abfa69b595569d7339702b75ddf7eaef34 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Sat, 11 Jul 2026 01:20:46 -0700 Subject: [PATCH 44/45] test: exercise config-seeded auction through Fastly and Cloudflare adapters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a contract test to each adapter's existing wasm test binary that seeds a non-default bid_cpm (0.35) in an in-process ConfigStore, injects it via the service builder's with_config_handle, and dispatches POST /openrtb2/auction — covering request translation, the config-store binding, the AppConfig extractor, the auction handler, and response translation under the real wasm runtime (Viceroy / wasm-bindgen). Runs in the existing adapter-wasm-tests CI matrix; no new emulator infra. Spin equivalent waits on the upstream wasi:http ABI fix. --- Cargo.lock | 4 + .../mocktioneer-adapter-cloudflare/Cargo.toml | 3 + .../tests/contract.rs | 76 ++++++++++++++++++- crates/mocktioneer-adapter-fastly/Cargo.toml | 5 ++ .../tests/contract.rs | 58 ++++++++++++++ 5 files changed, 145 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index df66244..6ec0674 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1748,9 +1748,11 @@ dependencies = [ name = "mocktioneer-adapter-cloudflare" version = "0.1.0" dependencies = [ + "async-trait", "edgezero-adapter-cloudflare", "edgezero-core", "mocktioneer-core", + "serde_json", "wasm-bindgen-test", "worker", ] @@ -1759,11 +1761,13 @@ dependencies = [ name = "mocktioneer-adapter-fastly" version = "0.1.0" dependencies = [ + "async-trait", "edgezero-adapter-fastly", "edgezero-core", "fastly", "log", "mocktioneer-core", + "serde_json", ] [[package]] diff --git a/crates/mocktioneer-adapter-cloudflare/Cargo.toml b/crates/mocktioneer-adapter-cloudflare/Cargo.toml index ae422fb..91cec95 100644 --- a/crates/mocktioneer-adapter-cloudflare/Cargo.toml +++ b/crates/mocktioneer-adapter-cloudflare/Cargo.toml @@ -29,6 +29,9 @@ worker = { workspace = true } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] wasm-bindgen-test = "=0.3.71" +# Contract tests seed the typed-config blob in-process (no miniflare/KV fixture). +async-trait = { workspace = true } +serde_json = { workspace = true } [lints] workspace = true diff --git a/crates/mocktioneer-adapter-cloudflare/tests/contract.rs b/crates/mocktioneer-adapter-cloudflare/tests/contract.rs index 0c9e86b..cc75efb 100644 --- a/crates/mocktioneer-adapter-cloudflare/tests/contract.rs +++ b/crates/mocktioneer-adapter-cloudflare/tests/contract.rs @@ -1,11 +1,38 @@ #![cfg(all(feature = "cloudflare", target_arch = "wasm32"))] +use async_trait::async_trait; use edgezero_adapter_cloudflare::request::CloudflareService; +use edgezero_core::blob_envelope::BlobEnvelope; +use edgezero_core::config_store::{ConfigStore, ConfigStoreError, ConfigStoreHandle}; use mocktioneer_core::build_app; +use std::collections::HashMap; +use std::sync::Arc; use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure}; -use worker::wasm_bindgen::JsCast as _; +use worker::wasm_bindgen::{JsCast as _, JsValue}; use worker::{Context, Env, Method as CfMethod, Request as CfRequest, RequestInit}; +/// In-memory `ConfigStore` so the auction path can be seeded in-process, with no +/// miniflare/workerd KV fixture. Mirrors the core-crate test double. +struct MapConfigStore(HashMap); + +#[async_trait(?Send)] +impl ConfigStore for MapConfigStore { + async fn get(&self, key: &str) -> Result, ConfigStoreError> { + Ok(self.0.get(key).cloned()) + } +} + +/// A config handle holding the typed-config blob for `{ "bid_cpm": }`. +/// An injected handle is bound under default_key `"default"` by the Cloudflare +/// service builder (`synthesise_store_registries`), so the blob lives there. +fn seeded_config_handle(bid_cpm: f64) -> ConfigStoreHandle { + let data = serde_json::json!({ "bid_cpm": bid_cpm }); + let blob = serde_json::to_string(&BlobEnvelope::new(data, "2026-01-01T00:00:00Z".to_owned())) + .expect("serialize envelope"); + let store = MapConfigStore([("default".to_owned(), blob)].into_iter().collect()); + ConfigStoreHandle::new(Arc::new(store)) +} + // `run_in_browser` selects the wasm-bindgen browser harness. In CI this runs // headless in Firefox (via the geckodriver that ships on the ubuntu-latest // runners); locally it picks up whichever browser driver is on PATH. @@ -71,3 +98,50 @@ async fn pixel_returns_gif_through_cloudflare_adapter() { .expect("content-type header"); assert_eq!(content_type, "image/gif"); } + +fn cf_post_json(path: &str, body: &str) -> CfRequest { + let mut init = RequestInit::new(); + init.with_method(CfMethod::Post); + + let headers = worker::Headers::new(); + headers.set("host", "test.local").expect("host header"); + headers + .set("content-type", "application/json") + .expect("content-type header"); + init.with_headers(headers); + init.with_body(Some(JsValue::from_str(body))); + + let url = format!("https://test.local{path}"); + CfRequest::new_with_init(&url, &init).expect("cf request") +} + +#[wasm_bindgen_test] +async fn auction_uses_seeded_cpm_through_cloudflare_adapter() { + let app = build_app(); + let body = serde_json::json!({ + "id": "rc", + "imp": [{ "id": "1", "banner": { "w": 300_i32, "h": 250_i32 } }] + }) + .to_string(); + let req = cf_post_json("/openrtb2/auction", &body); + let (env, ctx) = test_env_ctx(); + + // Full path through the adapter: request translation -> config-store bind -> + // `AppConfig` extractor -> auction handler -> response translation. + let mut response = CloudflareService::new(&app) + .with_config_handle(seeded_config_handle(0.35_f64)) + .dispatch(req, env, ctx) + .await + .expect("cf response"); + + assert_eq!(response.status_code(), 200); + let payload: serde_json::Value = + serde_json::from_slice(&response.bytes().await.expect("body bytes")).expect("json body"); + let price = payload["seatbid"][0]["bid"][0]["price"] + .as_f64() + .expect("bid price"); + assert!( + (price - 0.35_f64).abs() < f64::EPSILON, + "expected seeded cpm 0.35, got {price}", + ); +} diff --git a/crates/mocktioneer-adapter-fastly/Cargo.toml b/crates/mocktioneer-adapter-fastly/Cargo.toml index 6e7a74d..c0ee5a5 100644 --- a/crates/mocktioneer-adapter-fastly/Cargo.toml +++ b/crates/mocktioneer-adapter-fastly/Cargo.toml @@ -23,5 +23,10 @@ edgezero-adapter-fastly = { workspace = true, features = ["fastly"] } edgezero-core = { workspace = true } fastly = { workspace = true } +# Contract tests seed the typed-config blob in-process (no Viceroy fixture). +[target.'cfg(target_arch = "wasm32")'.dev-dependencies] +async-trait = { workspace = true } +serde_json = { workspace = true } + [lints] workspace = true diff --git a/crates/mocktioneer-adapter-fastly/tests/contract.rs b/crates/mocktioneer-adapter-fastly/tests/contract.rs index 373d728..0e1eb60 100644 --- a/crates/mocktioneer-adapter-fastly/tests/contract.rs +++ b/crates/mocktioneer-adapter-fastly/tests/contract.rs @@ -1,9 +1,36 @@ #![cfg(all(feature = "fastly", target_arch = "wasm32"))] +use async_trait::async_trait; use edgezero_adapter_fastly::request::FastlyService; +use edgezero_core::blob_envelope::BlobEnvelope; +use edgezero_core::config_store::{ConfigStore, ConfigStoreError, ConfigStoreHandle}; use fastly::http::{Method as FastlyMethod, StatusCode as FastlyStatus}; use fastly::Request as FastlyRequest; use mocktioneer_core::build_app; +use std::collections::HashMap; +use std::sync::Arc; + +/// In-memory `ConfigStore` so the auction path can be seeded in-process, with no +/// Viceroy config-store fixture. Mirrors the core-crate test double. +struct MapConfigStore(HashMap); + +#[async_trait(?Send)] +impl ConfigStore for MapConfigStore { + async fn get(&self, key: &str) -> Result, ConfigStoreError> { + Ok(self.0.get(key).cloned()) + } +} + +/// A config handle holding the typed-config blob for `{ "bid_cpm": }`. +/// An injected handle is bound under default_key `"default"` by the Fastly +/// service builder (`synthesise_store_registries`), so the blob lives there. +fn seeded_config_handle(bid_cpm: f64) -> ConfigStoreHandle { + let data = serde_json::json!({ "bid_cpm": bid_cpm }); + let blob = serde_json::to_string(&BlobEnvelope::new(data, "2026-01-01T00:00:00Z".to_owned())) + .expect("serialize envelope"); + let store = MapConfigStore([("default".to_owned(), blob)].into_iter().collect()); + ConfigStoreHandle::new(Arc::new(store)) +} fn fastly_request(method: FastlyMethod, path: &str) -> FastlyRequest { let mut req = FastlyRequest::new(method, format!("http://test.local{path}")); @@ -46,3 +73,34 @@ fn pixel_returns_gif_through_fastly_adapter() { .expect("content-type"); assert_eq!(content_type, "image/gif"); } + +#[test] +fn auction_uses_seeded_cpm_through_fastly_adapter() { + let app = build_app(); + let body = serde_json::json!({ + "id": "rc", + "imp": [{ "id": "1", "banner": { "w": 300_i32, "h": 250_i32 } }] + }) + .to_string(); + let req = fastly_request(FastlyMethod::POST, "/openrtb2/auction") + .with_header("content-type", "application/json") + .with_body(body); + + let mut response = FastlyService::new(&app) + .with_config_handle(seeded_config_handle(0.35_f64)) + .dispatch(req) + .expect("fastly response"); + + // Full path through the adapter: request translation -> config-store bind -> + // `AppConfig` extractor -> auction handler -> response translation. + assert_eq!(response.get_status(), FastlyStatus::OK); + let payload: serde_json::Value = + serde_json::from_slice(&response.take_body_bytes()).expect("json body"); + let price = payload["seatbid"][0]["bid"][0]["price"] + .as_f64() + .expect("bid price"); + assert!( + (price - 0.35_f64).abs() < f64::EPSILON, + "expected seeded cpm 0.35, got {price}", + ); +} From 04235ff25323d051e1cca8fc5b44b1d08e7a3b2c Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Mon, 13 Jul 2026 16:58:21 -0700 Subject: [PATCH 45/45] docs: document remote Fastly config-store setup (provision -> push -> 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. --- docs/guide/adapters/fastly.md | 43 +++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/docs/guide/adapters/fastly.md b/docs/guide/adapters/fastly.md index 012ee40..686eb9c 100644 --- a/docs/guide/adapters/fastly.md +++ b/docs/guide/adapters/fastly.md @@ -75,16 +75,45 @@ target/wasm32-wasip1/release/mocktioneer-adapter-fastly.wasm ### First-Time Setup +`/openrtb2/auction` and `/e/dtb/bid` read `bid_cpm` from the **remote Fastly +config store** via the fail-loud `AppConfig` extractor. Deploying alone is not +enough — without the config store created, seeded and linked, those endpoints +return `503 config_out_of_date`. Run the three steps in this order: + ```bash -edgezero-cli deploy --adapter fastly +# 1. Create the remote config store and append [setup.config_stores.*] to fastly.toml +cargo run -p mocktioneer-cli -- provision --adapter fastly + +# 2. Push the typed config blob into the store just created +cp mocktioneer.toml.example mocktioneer.toml # if you haven't already +cargo run -p mocktioneer-cli -- config push --adapter fastly --yes + +# 3. Deploy — creating the service consumes [setup] and links the store to it +cargo run -p mocktioneer-cli -- deploy --adapter fastly ``` -The CLI will prompt you to: +The deploy will prompt you to: 1. Create a new service or select existing 2. Configure the domain 3. Deploy the WASM bundle +::: warning Already-deployed services skip `[setup]` +Fastly consumes `[setup.config_stores.*]` **only when `deploy` creates a new +service**. If `fastly.toml` already declares a `service_id`, the store is created +in your account but is **not linked** to the service, so the runtime cannot open +it. `provision` detects this and prints the exact one-shot command to finish the +job — look up the store id with `fastly config-store list --json`, then: + +```bash +fastly resource-link create --service-id= --resource-id= \ + --version=latest --autoclone --name=mocktioneer_config +``` + +The link clones the active version, so live traffic is unaffected until you run +`fastly service-version activate`. +::: + ### Subsequent Deployments ```bash @@ -95,6 +124,16 @@ edgezero-cli deploy --adapter fastly fastly compute deploy -C crates/mocktioneer-adapter-fastly ``` +### Updating the Config + +`bid_cpm` lives in the config store, not the WASM bundle — after editing +`mocktioneer.toml`, **re-push**; no redeploy is needed: + +```bash +cargo run -p mocktioneer-cli -- config diff --adapter fastly # preview +cargo run -p mocktioneer-cli -- config push --adapter fastly --yes +``` + ## Configuration ### Build Settings