Expand description
cratefield-push-wiring (issue #191): the one place that turns a
venture’s environment into the Push port.
Three adapters serve three transports — cratefield-adapter-apns,
cratefield-adapter-fcm and cratefield-adapter-webpush — and each is
built from a handful of environment variables. Read those names in
serve(), again in fz push send and a third time in fz doctor and
they drift; the drift shows up in production as a notification that
silently never sends. So the names live in exactly one table
(PUSH_ENV), one function assembles the adapters from it
(build_push), and everything else calls that function.
§The partial case is the point
A venture that meant to enable FCM and mistyped one variable must not
boot into a state where every Android send answers
PushOutcome::NotConfigured and nothing
says why. build_push therefore returns a PushWiring report
alongside the router, and the report distinguishes:
- Configured — every variable the transport needs is set and the adapter accepted them.
- Absent — not one of its variables is set. The venture did not wire that transport, which is a choice, not a defect.
- Partial — some but not all. Somebody meant to wire it.
- Invalid — all of them are set and the adapter refused them (a
.p8that is not a PKCS#8 key, anAPNS_HOSTthat is neither host).
Partial and Invalid are an error in production and a warning
everywhere else (PushWiring::severity): a developer wiring APNs one
variable at a time must still be able to boot.
§Nothing that came out of the environment goes into the report
The report is logged whole at cold start, so a single value copied into
it reaches Workers Logs. An adapter’s error is free to quote what it was
handed — VapidError::Subject does — and Vapid::new validates the
subject before the key, so VAPID_SUBJECT and VAPID_PRIVATE_KEY
pasted the wrong way round (the mistake VapidKeys’ own doc warns about)
would have put the private key in the log. So an adapter’s error string
is never copied into TransportWiring::Invalid: every reason is a
fixed phrase naming the variable and what was expected, and
the_report_never_prints_any_variables_value holds every variable in the
table to it.
§Degraded, never broken
A transport that is not configured is simply not routed: the
RoutingPush answers NotConfigured for its recipients, which is the
same contract each adapter’s not_configured() constructor gives. With
nothing configured at all the router is empty, every send is
NotConfigured, and the Worker still boots.
§Call sites
serve()on both runtimes, throughCloudflare::push_from_env()/Native::push_from_env(), which build the router once per isolate or process and log the report once at cold start.fz doctor, throughinspect_push— the same function with an offline HTTP client, because the doctor never sends.fz push sendandfz push doctor, when the CLI lands (issue #184): they callbuild_pushand must not read any of these names themselves.crates/cli-acceptance/tests/push_env_guard.rsfails the build if they do.
§The table is the documentation
docs/PUSH-ENV.md is generated from PUSH_ENV by
cargo run -p cratefield-push-wiring --example push-env-doc, and CI
fails on drift — so the doc cannot describe variables the reader does not
read.
§cratefield-push-wiring
The runtime wiring for the push adapters (issue #191): given a venture’s
environment, which push transports exist, and how do they become the single
Arc<dyn Push> a module receives?
One table of variable names, one function that reads it, and every call site
— serve() on both runtimes, fz push send, fz doctor — calls that
function. Reading the names in three places is how a venture ends up with a
serve() that wires FCM, a doctor that checks a different variable, and an
Android notification that silently never sends.
use cratefield_push_wiring::{build_push, WiringSeverity};
let (push, wiring) = build_push(&config, http, clock);
// One line, names and verdicts only — never a value:
// push wiring: apns=configured fcm=partial (set: …; unset: …) web_push=absent
tracing::info!("{}", wiring.summary());
match wiring.severity(env) {
WiringSeverity::Ok => {}
WiringSeverity::Warning => for p in wiring.problems() { tracing::warn!("{p}") },
WiringSeverity::Error => for p in wiring.problems() { tracing::error!("{p}") },
}
let runtime = Cloudflare::new().push_arc(push);Or let the runtime do it, which is what the example venture does:
let runtime = Cloudflare::new().db("DB").push_from_env(); // feature = "push"§Verdicts
| Verdict | When | Consequence |
|---|---|---|
configured | every required variable is set and the adapter accepted them | routed |
absent | not one of the transport’s variables is set | not routed; NotConfigured. A choice, not a defect |
partial | some are set and some are not | not routed. Error in production, warning elsewhere |
invalid | all are set and the adapter refused them | not routed. Error in production, warning elsewhere |
Partial is the case this crate exists for. A venture that meant to enable FCM
and mistyped one variable must not boot into a state where every Android send
answers NotConfigured and nothing says why.
Nothing is ever broken by a bad environment: an unrouted transport degrades
to PushOutcome::NotConfigured, the same contract each adapter’s
not_configured() constructor gives, so a Worker with nothing configured
still boots and still answers every send.
§The variables
docs/PUSH-ENV.md is generated from this crate’s PUSH_ENV table:
cargo run -p cratefield-push-wiring --example push-env-doc # write
cargo run -p cratefield-push-wiring --example push-env-doc -- --checkNo other Rust source in the workspace may name one of those keys in a string
literal; crates/cli-acceptance/tests/push_env_guard.rs fails the build if
one does.
§The browser’s application server key
vapid_public_key(config) answers the applicationServerKey a browser
passes to pushManager.subscribe(), or None when Web Push is not
configured. It lives here for the same reason build_push does — these are
the same two variables — and it derives the public half from the private
key rather than reading a third variable, so the key a venture serves to
browsers cannot drift from the one its sends are signed with. A venture
hands it to the notifications module, which serves it:
Notifications::new().vapid_public_key(cratefield::push_wiring::vapid_public_key)MIT. Built in the open for Cratefield, a Factory Zero venture.
Structs§
- PushVar
- One environment variable a push transport reads.
- Push
Wiring - Which push transports a venture’s environment configured, which it did not, and which it half did.
Enums§
- PushKey
- One push environment variable, as a type.
- Transport
Wiring - How one transport was configured.
- Wiring
Severity - How loudly a
PushWiringreport should be reported.
Constants§
- PUSH_
ENV - Every environment variable the push adapters read, in one table.
- TRANSPORTS
- The transports, in report order.
Functions§
- build_
push - Assembles the
Pushport from a venture’s environment, and reports what it found. - inspect_
push - The same verdicts as
build_push, for a caller that has no HTTP client and needs none —fz doctor, which reports on an environment rather than sending through it. - notifications_
doc docs/PUSH-ENV.md, generated fromPUSH_ENVso the document cannot describe variables the reader does not read. Written and drift-checked bycargo run -p cratefield-push-wiring --example push-env-doc.- transport_
key - The
RoutingPushbuilder name for a transport —apns,fcm,web_push— which is what a report line and a log say. - transport_
name - The transport’s name in prose (
APNs,FCM,Web Push). - vapid_
public_ key - The
applicationServerKeya browser must pass topushManager.subscribe(), base64url — orNonewhen this venture has not configured Web Push (issue #183). - vars_
for - The variables one transport reads.