The standalone, multi-tenant ad-lander server. Deployed as the Cloudflare Worker flipper-landers, live at landers.appolis.app.
Single source of truth for this app. Updated every time the engine changes.
Written 2026-07-26 (note_1082 audit): the engine had shipped since 2026-07-13 with no docs, no README, no package.json and no version string anywhere โ its only documentation was the header comment in worker.js, so there was no baseline to measure drift against.
cd "F:\Claude Code\lander-engine" && npx wrangler deploybeaconDims built its URL from String(p.get('q')||'').slice(0, 400) โ a bare character cut that lands inside whatever value straddles character 400. On flip-8 that was the Meta ad id: ad_id=120255012631920321 arrived as ad_id=12, and 45 of 364 sessions over two days were attributed to ads that do not exist, with a phantom bucket named 12 outranking nine real ads. Views were never affected (they parse the real request URL), which is why the signature was rows with sessions and zero views. New exported qcut(s, max) drops the trailing partial pair whole and returns '' when not one pair fits โ an unusable fragment is worse than an honest blank. Ceiling raised to 1200 and demoted to a hostile-input guard, because the page now sends an allowlisted, per-value-capped payload (mission-control lander-templates.js, same to-do). โ ๏ธ Landers published before this keep sending the old truncated form until each is republished; those fragments are expected, not a regression. Tests: test/rollups.mjs 101 โ 112.POST /e/{slug}/click โ clicks:{tenant}:{slug} counter (same trust level as views; pages fire it via sendBeacon on CTA taps, skipped inside builder previews). ?stats=1 now returns {views, clicks, published, title, at} โ enough for the Kosmos builder's CTR + staleness actionables. DELETE /publish/โฆ also purges the views/clicks counters. KV layout gains clicks:{tenant}:{slug}.VERSION/VERSION_DATE in worker.js, serves them at GET /__version, and documents the engine for the first time. Behaviour unchanged from the deployed build except the version route and the CORS header noted in ยง4.โ ๏ธ THIS CHANGELOG HAS A HOLE: v1.2.0 โ v1.8.0 WERE NEVER RECORDED HERE. Noticed 2026-08-17 while
adding the line above. Seven builds โ including the whole analytics and session-measurement programme
(sessions/engagement/channel in v1.6.0, the four faults an adversarial audit found live in v1.7.0, and
section timing in v1.8.0) โ shipped against the rule three lines up in worker.js that says to add a line
here on every material change. The jump from v1.1.0 straight to v1.8.1 below is that gap, left visible on
purpose rather than back-filled from memory: a reconstructed changelog reads exactly like a real one and
is the same class of confident-wrong-answer this engine keeps being bitten by. Back-fill it from the git
log (git log --oneline), where the real record is, before trusting any date in this list.
A deliberately dumb, fast serving layer for advertising landing pages. Pages are authored elsewhere and pushed here once at publish time; the engine only stores and serves them. That split is the whole design: authoring can be as clever as it likes and the thing on the ad's critical path stays a KV read.
It is fully isolated from every other app โ no service bindings, no shared state, its own KV namespace.
Who writes to it: the Kosmos Lander Builder (kosmos.appolis.app/lander-builder) is the authoring surface. Kosmos holds a direct binding to this engine's KV namespace and writes page: / cfg: / media: keys itself; see Kosmos APP_BREAKDOWN.md v7.08. The PUT /publish API below is the door for anything that does not have that binding.
Namespace LANDERS (id 60e0b588f2b54cf0a30c5d2352c34ccd).
| Key | Holds | Metadata |
|---|---|---|
page:{tenant}:{slug} | the rendered HTML | {ct, title, at} |
media:{tenant}:{slug} | creative bytes โ images, fonts, video | {ct} |
views:{tenant}:{slug} | best-effort view counter | โ |
cfg:{tenant}:{slug} | the lander's config (written/read by the Kosmos builder; the engine itself never reads it) | โ |
views: is eventually consistent and read-modify-write, so counts are advisory โ concurrent hits can lose increments. Exact analytics would need Analytics Engine; this is a rough signal, not a metric.
GET /__version โ open. {engine, version, date}. Identify the live build without credentials.
PUT /publish โ bearer-gated on PUBLISH_KEY.
Body {tenant, slug, html, title?, media_b64?, media_ct?}. The slug is lowercased and stripped to [a-z0-9-], capped at 60 chars. Returns {ok, slug, url}.
DELETE /publish/{tenant}/{slug} โ bearer-gated. Deletes the page and its media. Does not delete cfg: โ unpublishing takes the page down while leaving it editable in the builder.
GET /{slug} โ serves the page. Cache-Control: public, max-age=300, stale-while-revalidate=3600. Bumps views:. Missing page โ 404 This page has moved or expired.
GET /{slug}/media โ serves the creative. Immutable one-week cache, Access-Control-Allow-Origin: *.
GET /{slug}?stats=1 โ bearer-gated. {views}.
Anything else โ 200 lander engine (a liveness string).
/media is openAdded 2026-07-26. The Kosmos Lander Builder renders its live preview in an iframe on kosmos.appolis.app while the brand fonts and pool images are served from landers.appolis.app. Fonts are subject to CORS even when images are not, so without Access-Control-Allow-Origin: * the preview silently fell back to system fonts and misrepresented the page it was about to publish.
Media is public brand material served from a public URL either way, so the header grants nothing that fetching the URL directly did not.
Tenancy is hostname-scoped:
const HOST_TENANTS = { 'lp.flipmylifenow.com': 't_verdant' };
const tenantOf = (host) => HOST_TENANTS[host] || 't_verdant';
A client brand CNAMEs its own subdomain at this Worker (Cloudflare for SaaS) and gets its own tenant partition. Every unmapped host โ including landers.appolis.app itself โ falls back to t_verdant.
โ ๏ธ This map is hardcoded, so the engine is effectively single-tenant today. Onboarding a second brand means editing this file and redeploying. Productizing the lander pipeline for other Kosmos/Agora users requires replacing it with per-store config; that is tracked on the Kosmos side (note_990).
There is no authentication on page serving. GET /{slug} reads KV and returns the bytes. Only PUT /publish, DELETE /publish/* and ?stats=1 are gated.
So a "private" lander means an unguessable slug โ the same model as an unlisted hub link. It is not indexed and not linked, but anyone holding the URL can open it.
If real gating is ever needed (a client reviewing unreleased creative), the engine needs a view-token or an Appolis ID check added. Until then, do not describe a lander as access-controlled.
wrangler.jsonc: name flipper-landers, main worker.js, compat date 2026-06-01, custom domain route landers.appolis.app, KV binding LANDERS, observability on.
PUBLISH_KEY is a secret (npx wrangler secret put PUBLISH_KEY) โ never a vars entry. It is the only credential the engine has.
npx wrangler kv key delete "page:t_verdant:{slug}" --namespace-id 60e0b588f2b54cf0a30c5d2352c34ccd --remotemedia: keys instead brought the same page to 11KB. Prefer /{slug}/media.HOST_TENANTS map with config so a second brand does not require a code edit (blocks productizing landers for other users).views: loses concurrent increments; Analytics Engine if the number ever has to be trusted.package.json โ there is none; the Worker has no dependencies, but its absence is why nothing had a version to read.