Expand description
Dev-tier S3 store resolution (R584-T1, partly reversed 2026-09-17).
A workload’s @mesofact/runtime R2Adapter needs a local object store to
resolve against during the dev loop, instead of real Cloudflare R2. There
are two providers, and DevStore::resolve picks between them:
- The camp’s. A running
yah campsupervisesyah-s3-fs(W265) and injects its coordinates into every dev service’s environment (R274-F5,cloud::reconciler::s3_driver). Preferred whenever present: its objects are shared with every other app in the camp and outlive this process, where an embedded store’s are visible to nothing else. - An embedded s3s-fs surface under
.mesofact-dev/s3/, started here.
R584-T1 deleted the embedded arm, on the premise that the camp is always
there. That premise holds inside a camp and nowhere else — mesofact ships to
people who have no yah installed, and mes . on a freshly scaffolded
project is the invocation the scaffold’s own README gives. The standalone
tier of check-mesofact-new.sh runs exactly that, with no camp, and had
been failing on it for six days when this arm was restored (2026-09-17,
blocking the 0.8.40 release wave at release-check).
This is not the fallback-beside-the-real-thing CLAUDE.md warns about: both
arms are load-bearing product behaviour, neither is a compatibility shim for
the other, and a half-set camp injection is still a hard error rather than
a third path — see DevStore::resolve.
@yah:ticket(R584-T1, “Retire mesofact-dev’s private in-process s3s-fs surface in favour of the camp s3 driver”)
@yah:at(2026-09-12T20:41:31Z)
@yah:status(review)
@yah:parent(R584)
@yah:handoff(“Deleted the whole private in-process s3s-fs surface: DevS3::start, build_service, AllowAllAccess, serve_loop, and the TcpListener/hyper_util/s3s/s3s-fs/async-trait code all gone from crates/mesofact-dev/src/s3.rs (was 208 lines, now ~145). DevS3 is now a plain coordinate struct with a from_env() constructor that reads S3_ENDPOINT/S3_BUCKET/S3_ACCESS_KEY_ID/S3_SECRET_ACCESS_KEY and keeps its env_vars() method (still emits R2_ENDPOINT/R2_BUCKET/R2_ACCESS_KEY_ID/R2_SECRET_ACCESS_KEY for workload consumers, unchanged shape/names).”)
@yah:handoff(“Wired from_env() into all six injection sites: cli.rs:410 (was DevS3::start(state_dir.join("s3"), DEV_S3_BUCKET).await?), cli.rs:425/487/508 (.env_vars() call sites unchanged, now sourced from real fields), cli.rs:446-452 (S3Store::new now uses dev_s3.access_key_id/secret_access_key instead of the old hardcoded "dev"/"dev"), app.rs:104 (DevServer::start), app.rs’s export_env loop unchanged (already generic over env_vars()).”)
@yah:handoff(“Deleted DEFAULT_BUCKET/DEV_S3_BUCKET entirely (dead once bucket comes from the camp, not minted) — removed the re-export from lib.rs and all four call sites that passed it as a start() arg.”)
@yah:handoff(“Cargo.toml: dropped s3s, s3s-fs, async-trait, hyper-util, reqwest (all four were only used by the deleted server/round-trip-test code; grepped clean afterward). Kept tower/tower-http/mesofact-publisher — unrelated to this surface.”)
@yah:handoff(“Standalone error (point 4): DevS3::from_env() bails naming exactly which of the four vars is missing plus ‘yah camp’ as the fix — see s3.rs’s error string. Partial-set is a hard error (never fills a default), matching the ticket’s decision.”)
@yah:handoff(“Independent verification (MFT-R584-T1) confirmed DevS3 was a thin env-reader with zero surviving server/spawn behaviour, so it was renamed to CampS3 per CLAUDE.md’s ‘rename to what it is, no aliases’ – every in-crate call site (s3.rs, app.rs, lib.rs, cli.rs) and the three live doc-comment mentions outside the crate (crates/mesofact/src/server.rs, crates/mesofact/src/ssr.rs, crates/mesofact-ssr/src/ssr.rs) updated; historical @yah: annotations describing past DevS3-named state left untouched. Re-verified: build (both feature sets)/test/clippy all still clean after the rename.”)
@yah:handoff(“Test fallout, expected and unavoidable: 5 tests deleted because they drove the now-deleted in-process server end-to-end (s3.rs’s two round-trip tests, lib.rs’s two cross-boundary SSR/deferred-route smokes, app.rs’s handler_reads_r2_from_env test) — none of that integration behavior is testable from this crate anymore since the server moved to yah-s3-fs (out of this crate’s tree). Replaced with 5 new tests covering the actual surface: s3::tests::from_env_reads_camp_injected_coordinates, s3::tests::from_env_errors_naming_missing_vars_and_yah_camp, app::tests::start_reads_camp_coordinates_and_writes_discovery_file (rewritten from start_creates_state_dir_and_discovery_file), app::tests::start_errors_naming_yah_camp_when_nothing_injected (new), app::tests::export_env_publishes_r2_coordinates_from_camp_env (new). Net 28 -> 27 tests.”)
@yah:handoff(“Added lib.rs’s #[cfg(test)] test_support::ENV_LOCK (tokio::sync::Mutex<()>) shared by s3::tests and app::tests since both mutate the same process-wide S3_/R2_ env vars — a lock private to one module doesn’t stop the parallel test runner racing the other module. Used tokio::sync::Mutex specifically (not std::sync::Mutex) because app::tests holds the guard across DevServer::start’s .await; std::sync::Mutex there was clippy::await_holding_lock at baseline-clean crate.”)
@yah:verify(“Baseline (measured before any edit): cargo test -p mesofact-dev = 28 passed, 0 failed (28/0). cargo build -p mesofact-dev clean. cargo clippy -p mesofact-dev –all-targets: zero warnings attributed to mesofact-dev itself (all warnings in that run belong to other workspace crates: mesofact-core, mesofact-build, rnpm).”)
@yah:verify(“After: cargo build -p mesofact-dev clean (both default features and –no-default-features). cargo test -p mesofact-dev = 27 passed, 0 failed (27/0) – net -1 from 5 deletions + 4 additions per the handoff note above (doc-tests unaffected, 0 passed/2 ignored both times). cargo clippy -p mesofact-dev –all-targets: zero warnings attributed to mesofact-dev (same other-crate warnings as baseline, nothing new).”)
@yah:gotcha(“cli.rs and Cargo.toml landed in a camp wip-commit (HEAD 269486ae "sync") partway through this session while s3.rs/app.rs/lib.rs were still uncommitted in the working tree – both states carry the same content (verified by git show 269486ae:oss/mesofact/crates/mesofact-dev/src/cli.rs), so nothing was lost, but don’t be surprised if git status under-reports which files this ticket touched.”)
@yah:gotcha(“Scope-fence held: everything landed inside oss/mesofact/crates/mesofact-dev/. Did NOT touch crates/mesofact/src/cli/new/template-lib/src/bin/PROJECT_NAME-dev.rs (an ignore-tagged doc example referencing DevServer::start(".")) or crates/mesofact/src/ssr.rs’s doc comments mentioning DevS3::env_vars() – both are outside the crate and both are still accurate (DevServer::start and env_vars() keep their old names/signatures precisely so those out-of-crate references don’t need editing).”)
@yah:handoff(“SCOPE EXTRA, deliberate and leader-authorized: DevS3 was renamed to CampS3. The ticket said "delete DevS3", and the implementer’s first pass instead kept the struct as a thin env-reader — correct behaviour, misleading name. The verification pass confirmed zero server/spawn code remained in it and then landed the rename across every in-crate call site plus three live cross-crate doc comments in mesofact / mesofact-ssr that still described the type as minting a store. Per CLAUDE.md’s "rename to what the thing actually is, fix every call site, no aliases" — no alias was left behind. Build / test / clippy were re-run AFTER the rename and are unchanged (27/0, clean, clean); the mesofact and mesofact-ssr compiles in that re-run also cover the touched doc comments. Historical @yah: annotations still say DevS3 on purpose — they are a record of what was there.”)
@yah:verify(“Fallback audit came back negative by grep, which is the finding that actually matters here: no surviving server bind, no s3s reference, no S3Store spawn, and no hardcoded "dev" bucket-or-credential literal used as a RUNTIME default anywhere in the crate. The remaining "dev" literals are test fixtures. Dropped deps confirmed unreferenced including under #[cfg(test)] and behind both feature gates; they are absent from mesofact-dev’s own entry in oss/mesofact/Cargo.lock and survive there only as transitive deps of other crates, with cargo metadata exit 0 plus the four green builds as the consistency evidence.”)
@yah:gotcha(“NOTHING IS COMMITTED. Camp git policy is defer and this is a shared working tree; the edits sit uncommitted in oss/mesofact/crates/mesofact-dev/ (s3.rs, app.rs, cli.rs, lib.rs, Cargo.toml), oss/mesofact/Cargo.lock, and the three renamed doc-comment sites in mesofact / mesofact-ssr. Whoever sweeps git should note that git add takes whole files and this tree carries other sessions’ uncommitted work.”)
@yah:verify(“Standalone-case error was exercised by hand, not just read: the mes binary run with all four of S3_ENDPOINT/S3_BUCKET/S3_ACCESS_KEY_ID/S3_SECRET_ACCESS_KEY unset, and again with only two of four set, both exit 1 with a clear message naming exactly which vars are missing and naming yah camp as the thing that supplies the store — no panic, no backtrace, no hang, and the partial case does not silently guess a default. Test count went 28/0 -> 27/0: five tests that seeded data through the now-deleted in-process server were removed and replaced with env-based ones; the server behaviour they covered did not lose coverage, it MOVED — yah-s3-fs now carries 26 router/store/sigv4/policy tests for it. Two independent passes: an implementing courier and a separate adversarial verification courier that re-ran every command itself rather than reading the first one’s report.”)
@yah:next(“OPEN DESIGN QUESTION the operator raised while this landed, not actioned here: should the dev-tier store be a plugin rather than compiled into mes? Two halves. (a) CAMP ARM — yah-s3-fs is already a supervised out-of-process binary (cloud::reconciler::s3_driver spawns it, kamaji assigns the mesh port, it publishes coords.json), so promoting it to a W232 plugin under app/yah/cli/src/plugin_host.rs would buy a signed manifest, a declared source_ref and an enforced Requires grant set, which the hardcoded best-effort activate has none of. The same argument covers the pg and smtp drivers — one cleanup, not three. (b) STANDALONE ARM — a camp plugin cannot cover this, since a plugin presupposes a camp and mesofact ships to people with no yah installed; the operator’s framing was that mes either carries the store itself (what landed) or grows its OWN plugin system. DevStore::resolve is the seam either would plug into.”)
@yah:handoff(“THE FIX, and what was NOT reverted: CampS3::from_env() became DevStore::resolve(state_dir) (async) in crates/mesofact-dev/src/s3.rs, with a StoreProvenance::{Camp,Embedded} field recording which provider answered. Three cases: all four S3_* set = use the camp’s store (preferred — its objects are shared camp-wide and outlive the process, which is T1’s actual argument and it is still right); NONE set = start the embedded s3s-fs surface under .mesofact-dev/s3/, restored from 5896a06f including AllowAllAccess + SimpleAuth(dev/dev) so SigV4 clients still verify; SOME set = hard error naming both the present and the missing vars, because a half-set injection is a miswired camp and quietly starting a second empty store would hide it behind a dev loop that looks fine until a published object turns up missing. That partial-set refusal is T1’s decision and it is KEPT. Deps s3s 0.13 / s3s-fs 0.13 / async-trait / hyper-util came back to mesofact-dev’s Cargo.toml; reqwest did not (it only served the deleted round-trip tests). CampS3 renamed to DevStore at every call site (lib.rs, app.rs, cli.rs) plus the three cross-crate doc mentions T1 itself had updated (mesofact/src/server.rs, mesofact/src/ssr.rs, mesofact-ssr/src/ssr.rs) — no alias left behind, per CLAUDE.md.”)
@yah:verify(“cargo test -p mesofact-dev = 29 passed / 0 failed (T1’s stated baseline was 27/0). cargo build -p mesofact-dev clean under BOTH default features and –no-default-features. cargo clippy -p mesofact-dev –all-targets: zero warnings attributed to mesofact-dev/src, checked by grepping the log for that path rather than eyeballing a count. THE DECISIVE ONE: bash scripts/check-mesofact-new.sh run end to end = PASSED, 41 passed / 0 failed / 1 skipped, against the same script that had just failed the release. The 1 skip is the library tier declining because the scaffold pins mesofact 0.8.40 and crates.io is still at 0.8.37 — expected pre-publish, not a masked failure. Tests added: s3::tests::resolve_starts_an_embedded_store_when_no_camp_injected_anything (asserts the listener ACCEPTS a TCP connect before resolve returns, not merely that the endpoint string is well-formed), s3::tests::resolve_refuses_a_half_set_injection_instead_of_falling_back, app::tests::start_comes_up_on_an_embedded_store_when_nothing_is_injected (replaces start_errors_naming_yah_camp_when_nothing_injected, which asserted exactly the contract that broke the gate), app::tests::start_errors_on_a_half_set_injection.”)
@yah:gotcha(“PARTLY REVERSED 2026-09-17, operator call, after it broke the release gate for six days. T1’s premise was that a running yah camp always injects S3_*; that holds inside a camp and nowhere else. mes . on a freshly scaffolded project — the invocation the scaffold’s own README gives, and what the standalone tier of oss/mesofact/scripts/check-mesofact-new.sh runs — has no camp, so every such run died on T1’s own error string. mesofact-new-smoke last passed 2026-09-11; T1 landed 2026-09-12; the next run of that gate (2026-09-17, then again inside release wizard runs f848936d and ebf4978a for 0.8.40) failed at check-mesofact-new with mes never served /. It blocked the 0.8.40 wave at release-check, the last reversible point before oss-publish.”)
Structs§
- DevStore
- Coordinates of the dev object store this process talks to, and where they came from. Handed to consumers (build-child env, discovery file, the in-process V8 SSR runtime) so they can point an S3 client at it.
Enums§
- Store
Provenance - Where the coordinates in a
DevStorecame from.
Constants§
- EMBEDDED_
BUCKET - Bucket the embedded surface pre-creates. Workloads point
[sources.r2] buckethere when running outside a camp.