Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
0f924d7
provision --local implementation: tracking branch
aram356 Jun 29, 2026
1ffe5a9
Add ProvisionMode + ProvisionArgs.local for provision --local
aram356 Jun 29, 2026
feb607e
Add neutral ProvisionOutcome + AdapterDeployedState types + crate-roo…
aram356 Jun 29, 2026
5896a8e
Thread ProvisionMode + ProvisionOutcome through Adapter::provision
aram356 Jun 30, 2026
63f9a4d
Add Adapter::provision_typed trait method with default no-op
aram356 Jun 30, 2026
5121603
Extract run_typed_preflight; route validate/push/diff through it
aram356 Jun 30, 2026
a8e54e1
Task 5 fix: promote resolve_app_config_path_primitive to pub(crate) p…
aram356 Jun 30, 2026
d184c8f
Add path_safety module with containment helper
aram356 Jun 30, 2026
a347422
Wire path_safety into config push --local
aram356 Jul 1, 2026
d8d6622
Wire path_safety + ProvisionMode into run_provision
aram356 Jul 1, 2026
7074ac7
Add Adapter::synthesise_baseline_manifest + CLI bootstrap before vali…
aram356 Jul 1, 2026
a0bec7a
Add copy_tree helper; promote tempfile + toml_edit to CLI runtime deps
aram356 Jul 1, 2026
3fa8c22
Merge branch 'main' into feature/provision-local-impl
aram356 Jul 1, 2026
ba3f891
Move config push --local containment guard above run_shared_checks
aram356 Jul 1, 2026
619541a
Merge remote-tracking branch 'origin/feature/provision-local-impl' in…
aram356 Jul 1, 2026
1617bb8
Make provision_local accept-tests actually exercise their code paths
aram356 Jul 2, 2026
12d7129
Add run_with_staging tempdir helper for dry-run staging
aram356 Jul 2, 2026
42c198e
Wire mode x dry-run dispatch matrix into run_provision
aram356 Jul 2, 2026
cd87c24
Add dry-run allow-list diff + would-write status rewriting
aram356 Jul 2, 2026
66ab458
Add provision_local_dry_run worktree-clean + no-tempdir-leak test (ig…
aram356 Jul 2, 2026
2b3e700
Add ManifestAdapterDeployed struct + deployed field on ManifestAdapter
aram356 Jul 2, 2026
5531f72
Fix local dry-run to dispatch adapters with dry_run = false
aram356 Jul 2, 2026
6033e72
Add adapter-owned deployed_fields + Manifest-level cross-check
aram356 Jul 2, 2026
af4baa9
Task 15 followup: refresh #[expect] reasons + document case-handling
aram356 Jul 2, 2026
b3466c1
Add toml_edit-based [adapters.<name>.deployed] writeback
aram356 Jul 2, 2026
277f614
Cloudflare: cloud provision returns created namespace ids via deployed
aram356 Jul 2, 2026
d7d6b1a
Add edgezero_adapter::env_file::append_lines_dedup (used by Section 5)
aram356 Jul 2, 2026
9c21d15
Wire deployed_state_for translator + validate_deployed_field_ownershi…
aram356 Jul 2, 2026
fac82ab
Clean up misleading adapter-named tests in provision.rs
aram356 Jul 2, 2026
1e1e206
Fix three review findings: dry-run leak, baseline containment, deploy…
aram356 Jul 2, 2026
c640931
Cloudflare: primitive synthesiser for wrangler.toml + bootstrap override
aram356 Jul 2, 2026
91205a6
Cloudflare: local-mode provision emits [[kv_namespaces]] bindings (de…
aram356 Jul 2, 2026
da4a2b9
Cloudflare: local-mode provision writes .dev.vars __NAME / __KEY lines
aram356 Jul 2, 2026
140728e
Cloudflare: provision_typed appends secret placeholders to .dev.vars
aram356 Jul 2, 2026
84f8908
Fastly: primitive synthesiser for fastly.toml + bootstrap override
aram356 Jul 2, 2026
706d2cd
Fastly: local-mode provision writes [local_server.*] + edgezero_runti…
aram356 Jul 2, 2026
efd0be3
Fastly: provision_typed writes [[local_server.secret_stores.*]] entries
aram356 Jul 2, 2026
6642598
Spin: primitive synthesiser for spin.toml + runtime-config.toml + boo…
aram356 Jul 2, 2026
8f6776c
Spin: local-mode provision writes bindings + runtime-config + .env __…
aram356 Jul 2, 2026
00157fe
Spin: provision_typed writes lowercased [variables] + SPIN_VARIABLE_ …
aram356 Jul 2, 2026
84bfea9
Axum: local-mode provision creates .edgezero/ and writes .env (no axu…
aram356 Jul 2, 2026
dad90a2
Axum: provision_typed appends secret placeholders to .edgezero/.env
aram356 Jul 2, 2026
8f68180
Re-enable provision_local_dry_run worktree-clean test (Section 5 lock…
aram356 Jul 2, 2026
324c106
Fix two Section 5 contract gaps: Spin silent success + missing header
aram356 Jul 3, 2026
b00440c
Fastly: additive merge for edgezero_runtime_env on re-provision
aram356 Jul 3, 2026
0778ec9
Add run_provision_typed<C> public entry point
aram356 Jul 3, 2026
c729d93
Route Cmd::Provision through run_provision_typed (scaffold + app-demo)
aram356 Jul 3, 2026
50455b4
Scaffold: loop run_provision over every selected adapter
aram356 Jul 3, 2026
7e458aa
Scaffold: extend .gitignore with provision-owned manifests + local state
aram356 Jul 3, 2026
1a631ad
Gitignore Cloudflare/Fastly/Spin manifests; regenerate via provision …
aram356 Jul 3, 2026
9a5eb49
run_serve: adapter-scoped env-file load (axum vs spin); existing env …
aram356 Jul 3, 2026
3ad53c0
Smoke: warm-up via provision --local; drop obsolete backup_in_tree calls
aram356 Jul 3, 2026
d980113
CI: gate fastly/spin/wrangler/runtime-config.toml + .dev.vars (axum.t…
aram356 Jul 3, 2026
d0199dc
Docs: update for provision --local + gitignored adapter manifests
aram356 Jul 3, 2026
6361d87
Axum: per-adapter provision_local_ contract tests (axum.toml untouched)
aram356 Jul 3, 2026
20970d2
Fastly: per-adapter provision_local_ contract tests
aram356 Jul 3, 2026
afc6fbe
Cloudflare: per-adapter provision_local_ contract tests
aram356 Jul 3, 2026
31ca168
Spin: per-adapter provision_local_ contract tests + env-label alignment
aram356 Jul 3, 2026
492f774
Axum: split cli.rs into cli/{mod,provision_local,run}.rs
aram356 Jul 3, 2026
637e6a6
Cloudflare: split cli.rs into cli/{mod,provision_local,provision_clou…
aram356 Jul 3, 2026
9ea12d5
Fastly: split cli.rs into cli/{mod,provision_local,provision_cloud,pu…
aram356 Jul 3, 2026
09fcb85
Spin: split cli.rs; rename push_sqlite→push_local; fold housekeeping
aram356 Jul 3, 2026
20c29b7
Adapters: hoist path_mutation_guard to cli/mod.rs to fix cross-module…
aram356 Jul 3, 2026
e3657b7
Cloudflare: fix .dev.vars commented KEY placeholder value
aram356 Jul 3, 2026
d3c4b81
Fastly: fix service_id root-key positioning + populate cloud deployed…
aram356 Jul 4, 2026
2e7b0c2
CLI: merge outcome.deployed from run_provision_typed real-write path
aram356 Jul 4, 2026
ed5bf83
Deployed schema: colocate scalar+sub-table field arrays with the struct
aram356 Jul 4, 2026
6bb5c7e
Docs: correct axum.toml claim + Spin dotenv recipe + gitignore list
aram356 Jul 4, 2026
153fa7c
Fastly: template no longer seeds empty service_id; docs match cloud/l…
aram356 Jul 5, 2026
0483c00
Deep review batch 1: API hardening + PATH-guard unification + env-ove…
aram356 Jul 5, 2026
a72ff10
Deep review P1-b: cross-process advisory lock for provision + config …
aram356 Jul 5, 2026
24f2b20
Deep review batch 2: template hygiene walker + Fastly reparse asserts…
aram356 Jul 5, 2026
b766f75
Deep review batch 3: env-file hardening + case-insensitive test + Clo…
aram356 Jul 5, 2026
4a31c78
Provision status lines: name the file each mutation lands in
aram356 Jul 5, 2026
99b122c
Axum: axum.toml joins the provision-generated set
aram356 Jul 5, 2026
b9cdbd8
Provision: emit 'wrote baseline <path>' line for every synthesised ad…
aram356 Jul 5, 2026
ba9479f
Cloud path safety + provision lock in .edgezero/ + config push lock t…
aram356 Jul 5, 2026
6da249f
Axum dry-run: preview generated axum.toml + sanitise typed dry-run er…
aram356 Jul 5, 2026
10da44d
config.rs: remove all non-test #[expect] via code restructure
aram356 Jul 5, 2026
d584a23
Docs: spec + plan align on axum.toml provision-generated model
aram356 Jul 6, 2026
19c2180
Non-test #[expect] cleanup: stream module + scaffold Result + syn hel…
aram356 Jul 6, 2026
22964f5
args.rs: hoist no_diff+no_env into ConfigPushSuppressions sub-struct
aram356 Jul 7, 2026
1435d58
Remove two non-test expects in fastly + spin adapter crates
aram356 Jul 7, 2026
9cd1357
Remove missing_trait_methods expects across adapters + core manifest
aram356 Jul 7, 2026
1bddba5
Remove clippy::panic expect from canonical_form::write_number
aram356 Jul 7, 2026
9085a2e
app-demo-cli: track ConfigPushArgs.suppress hoist
aram356 Jul 7, 2026
0703a3f
Provision-local review findings: single-source axum.toml + docs + cop…
aram356 Jul 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,19 @@ jobs:
- name: Nested AppConfig audit
run: cargo run -q --bin check_no_nested_app_config --features nested-app-config-check -- examples/app-demo crates/edgezero-cli/src/templates

# Enforce spec §"Adapter manifests are gitignored" + §"Migration for
# downstream projects". All five generated adapter manifests
# (`axum.toml`, `wrangler.toml`, `fastly.toml`, `spin.toml`,
# `runtime-config.toml`) and Cloudflare's `.dev.vars` MUST NOT be
# tracked -- teammates regenerate them locally via
# `provision --local`.
- name: Enforce adapter manifests and .dev.vars are not tracked
run: |
if git ls-files | grep -E '(^|/)(axum|fastly|spin|wrangler|runtime-config)\.toml$|(^|/)\.dev\.vars$'; then
echo "::error::These adapter manifests AND Cloudflare's .dev.vars must be gitignored (spec §'Adapter manifests are gitignored' + §'Migration for downstream projects'). .dev.vars carries operator secret values and must NEVER be committed."
exit 1
fi

- name: Run workspace tests
run: cargo test --workspace --all-targets

Expand Down
56 changes: 48 additions & 8 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# node
# --- Node ---
node_modules/

# compiled output
# --- Cargo / Rust ---
bin/
# `bin/` above is overly broad — it also blocks Cargo's `src/bin/`
# directories, which are LEGITIMATE source (one file per binary
Expand All @@ -10,20 +10,61 @@ bin/
!**/src/bin/**
pkg/
target/
*.rlib

# --- EdgeZero core (shared across adapters) ---
# Runtime dir the CLI creates for adapter-local state:
# - `.edgezero/.env` — Axum's env-line file
# - `.edgezero/local-config-<id>.json` — Axum's local config store
# - `.edgezero/provision.lock` — cross-process advisory lock
# (`provision` + `config push`)
# The whole `.edgezero/` directory is per-machine and never committed.
.edgezero/

# --- Cloudflare adapter ---
# `wrangler.toml` is regenerated by `provision --local`; teammates
# must not commit each other's namespace ids / platform binding
# names. `.dev.vars` holds operator-filled runtime secrets and
# `.wrangler/` is `wrangler dev`'s local state directory.
wrangler.toml
.dev.vars
.wrangler/

# --- Fastly adapter ---
# `fastly.toml` is regenerated by `provision --local`. It carries
# the compute manifest + `[local_server]` Viceroy state; per-machine
# secret entries and setup blocks must not be shared via git.
fastly.toml

# --- Spin adapter ---
# `spin.toml` (component / variables) + `runtime-config.toml` (KV
# backend paths) are regenerated by `provision --local`.
# `.spin/` is Spin's local state directory (SQLite KV, log tail).
spin.toml
runtime-config.toml
.spin/
.edgezero/

# env
# --- Axum adapter ---
# `axum.toml` is regenerated by `provision --local` (host / port /
# crate name defaults; operator edits are preserved because
# provision's merge path never re-writes the file after the first
# synthesis).
axum.toml

# --- Shared per-crate `.env` ---
# Spin's `<spin_crate>/.env` and any future adapter's crate-local
# `.env` land here. `.edgezero/.env` also matches via the top-level
# `.env` pattern; both are provision-owned + operator-filled and
# must never be committed.
.env

# OS
# --- OS ---
.DS_Store

# Worktrees
# --- Worktrees ---
.worktrees/

# Editors
# --- Editors ---
.claude/*
!.claude/settings.json
!.claude/commands/
Expand All @@ -35,4 +76,3 @@ target/
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.rlib
104 changes: 104 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Breaking changes

- **`edgezero-adapter::Adapter::provision` trait method changed shape.** Was
`fn provision(&self, root, adapter_manifest, component, stores, dry_run) -> Result<Vec<String>, String>`
with an `Ok(Vec::new())` default. Is now
`fn provision(&self, root, adapter_manifest, component, stores, deployed: Option<&AdapterDeployedState>, mode: ProvisionMode, dry_run) -> Result<ProvisionOutcome, String>`
with **no default** — every `impl Adapter` must supply it. Any
out-of-tree adapter written against the previous shape will fail to
compile with two errors: the method's arity and its return type. To
migrate:
1. Add a `mode: ProvisionMode` match arm and a `deployed: Option<&AdapterDeployedState>` parameter.
2. Return `ProvisionOutcome::from_status_lines(lines)` (or
`::with_deployed(lines, deployed_state)` when the cloud arm has
an id to write back) instead of `Ok(vec![...])`.
3. Add a fall-through arm `other => Err(...)` on the `match mode` —
`ProvisionMode` is `#[non_exhaustive]` and may gain variants.

- **`edgezero-adapter::AdapterDeployedState`, `ProvisionOutcome`,
and `ProvisionMode` are now `#[non_exhaustive]`.** Struct-literal
construction from a downstream crate (e.g.
`ProvisionOutcome { status_lines, deployed }`) no longer compiles.
Use the new constructors:
- `ProvisionOutcome::from_status_lines(status_lines)` for local mode
(which returns `deployed: None`).
- `ProvisionOutcome::with_deployed(status_lines, deployed_state)`
for cloud mode that populates the writeback.
- `AdapterDeployedState::default()` + `.fields.insert(...)` /
`.sub_tables.insert(...)` for the deployed state.

### Added

- **`edgezero provision` / `edgezero config push` cross-process advisory
lock** (`.edgezero-provision.lock` alongside `edgezero.toml`).
Serialises concurrent invocations against the same tree so
read-modify-write on `.env` / `.dev.vars` / `edgezero.toml` no
longer silently drops a competing writer's edits. Dry-run skips
the lock. Auto-released on process exit; the sentinel file itself
is git-ignored per-machine and safe to delete when no invocation
is running.

### Fixed

- Fastly `service_id` no longer lands under `[local_server]` on
re-provision; the merged `fastly.toml` correctly carries it at the
TOML root so `fastly compute deploy` picks it up.
- Fastly cloud `provision` populates `ProvisionOutcome.deployed.service_id`
from `fastly.toml`; the writeback to `[adapters.fastly.deployed]`
in `edgezero.toml` no longer silently drops.
- Cloudflare `.dev.vars` commented `__KEY` placeholder now uses
`<logical>_staging` per spec Task 19 (was
`<placeholder-<logical>-key>`).
- Cross-adapter `path_mutation_guard` unification (`edgezero-cli`
test binary): scaffold + push-shim tests share the same mutex, no
more intermittent CI flakes from PATH-restore races.
- Cloud `config push` now honours the spec's path-containment MUST
(absolute path + `..` traversal rejection). The strict-local
"manifest inside adapter crate" check stays `--local`-gated so
existing cloud fixtures with root-level manifest paths keep
working.
- Provision `.dev.vars` / `.env` written 0600 on Unix so operator-
filled secret values are not world-readable.
- Provision line-oriented files reject values containing `\n` or
`\r` — a malicious env override can no longer split into a second
`KEY=VALUE` line and inject an unintended env-var.
- Dry-run report emits a **unified diff with 2-line context radius**
instead of the full pre-image; `.dev.vars` / `.env` operator
values no longer stream into CI logs.
- Adapter error paths inside `run_local_dry_run` sanitise raw
`/var/folders/.../edgezero-staging-*` tempdir paths back to the
project-relative form before surfacing.

### Test coverage

- End-to-end env-overlay: `EDGEZERO__STORES__<KIND>__<LOGICAL>__NAME`
now has an integration test that drives it from process env
through `EnvConfig::store_name()` into the emitted `.edgezero/.env`.
- Case-insensitive adapter arg: `--adapter AXUM` against
`[adapters.axum]` lowercase now covered (previously only the
reverse direction was locked).
- Cloudflare `wrangler.toml` schema header preserved at line 1 after
provision merge into an operator-authored doc.
- `*.toml.hbs` scaffold templates walker asserts no `KEY = ""`
placeholder leaks past `write_baseline_to_disk`.
- Fastly root-scalar assertions (`service_id`, `[[local_server.kv_stores.sessions]]`
stub row) now reparse-then-index instead of substring-match, so
the shipped `service_id`-under-`[local_server]` bug's regression
class is locked.

### Deprecated / renamed

- Three tests named `provision_local_push_after_provision_preserves_*`
were renamed to `provision_typed_local_re_run_preserves_*` —
their bodies never invoked `push_config_entries` and the prior
name misled readers looking for real push→provision coverage.
Real push→provision integration coverage is still an open gap.
21 changes: 21 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 26 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ edgezero-cli = { path = "crates/edgezero-cli", default-features = false }
fastly = "0.12"
fern = "0.7"
flate2 = { version = "1", features = ["rust_backend"] }
# Cross-platform advisory file locks (flock on Unix, LockFileEx on
# Windows). Used by edgezero-cli to serialise concurrent `provision`
# / `config push` invocations against the same project tree so
# read-modify-write on `.env` / `.dev.vars` / `edgezero.toml`
# doesn't silently drop a competing writer's changes.
fs4 = { version = "0.13", default-features = false, features = ["sync"] }
futures = { version = "0.3", features = ["std", "executor"] }
futures-util = { version = "0.3", features = ["alloc", "io"] }
handlebars = "6"
Expand Down Expand Up @@ -165,5 +171,25 @@ exhaustive_enums = "allow"
std_instead_of_alloc = "allow"
std_instead_of_core = "allow"

# --- Layout / ordering restrictions that fight standard Rust conventions ---
#
# `mod_module_files` (deny) requires `cli.rs` sibling to `cli/` (2018
# layout). We use the `cli/mod.rs` form because it groups the module + its
# submodules under one directory — more grep-friendly and matches the
# codebase's four adapter crates. `self_named_module_files` (the opposite
# lint) would fire the other way. This workspace picks the `mod.rs` form
# and allows the lint globally with one line here rather than sprinkling
# a `#![expect]` in every adapter's `cli/mod.rs`.
mod_module_files = "allow"
#
# `arbitrary_source_item_ordering` (deny) enforces a strict order among 5
# item kinds (module/struct/enum/trait/impl) and its config API can't be
# tuned for the common patterns this workspace uses: cfg-gated `use` in
# the middle of a file for test-only helpers, the "declare struct then
# immediately impl it" convention, and interleaved trait impls between
# module declarations and statics. Reordering the ~15k lines across four
# adapter crates to satisfy the lint would be pure churn.
arbitrary_source_item_ordering = "allow"

[workspace.lints.rust]
unsafe_code = "deny"
Loading