Skip to content

feat(PRO-401): edge contract 2 on Fastly Compute - #12

Merged
alistairsmith-tech merged 5 commits into
mainfrom
feat/pro-401
Sep 21, 2026
Merged

alistairsmith-tech merged 5 commits into
mainfrom
feat/pro-401

Conversation

@alistairsmith-tech

@alistairsmith-tech alistairsmith-tech commented Sep 20, 2026 •

Copy link
Copy Markdown
Collaborator

The Fastly plugin is now the Compute adapter for edge-core (plan step 2, D9 in monocle-fastly-port-plan.md; tracked by PRO-401, web half PRO-432). Edge-core owns the request pipeline, the /__mcl/* endpoints, decisions, credentials and injection; this package supplies what only the platform can. The previous plugin (MCLVALID cookie, captcha_page.html, own cookie/policy/path modules, PROTECTED_PATHS scoping) is gone.

The adapter

  • config.ts: the deployment from the Config Store — v, id, cv, x and the chunked cfg.<i> DeploymentConfig — validated with edge-core's compileConfig; the origin settings (ORIGIN_HOST, CACHE_RULES, CHAIN_SECRET, CLIENT_IP_HEADER) read best-effort; secrets read lazily, the Policy key only for /__mcl/*.
  • platform.ts: Platform for Compute. The client address is event.client.address. A host the config does not name is forwarded unassessed (the domain is attached to this service either way); plain HTTP is sent to HTTPS, since the cookies are Secure.
  • origin.ts: the origin leg — host override, the client address stamped into X-Forwarded-For, Fastly-Client-IP and the customer's CLIENT_IP_HEADER (PRO-326 folded in; overwritten, never appended), the time-limited chaining signature, cloned cache rules as a CacheOverride, origin backend.
  • html.ts: HTMLRewritingStream — every meta CSP through edge-core's augmentation, the tag appended to body, or to the document when there is none.
  • breaker.ts and crawlers.ts: the breaker state and the crawler snapshot in the POP cache (SimpleCache), the scope Policy reachability has. Nothing schedules on Compute, so the request that finds the snapshot missing refreshes it in waitUntil, once per POP per lease, through static crawler_<host> backends.
  • backends.ts: the named backend every fetch() needs, injected into edge-core's Policy client and feed fetcher.

What js-compute does not have

Found by npm run typecheck, which checks edge-core's sources against js-compute's types: no AES-GCM (cookies use edge-core's HMAC sealer), no AbortSignal (the Policy deadline is the backend's first-byte timeout), no redirect/signal request options, a lenient TextDecoder. edge-core 0.7.1 (spurintel/monocle-edge-core#17) carries the shims, the exclusions and the store-backed breaker this needs.

Verification

  • Typecheck clean; 20 tests run the handler in Node against doubles for the fastly:* modules (test/doubles): unreadable and torn config, challenge and state, mint through the Policy backend then an injected cleared page, refusals and blocks, exclusions, unnamed hosts, HTTP→HTTPS, the origin leg (host, client-address headers, chaining signature, cache rules, 502), lazy crawler refresh without stampede, the breaker surviving between requests, secret reads only on the endpoints.
  • Built to Wasm and run under Viceroy against the live example.com origin: /__mcl/state 200, cold enforced navigation 503 with the interstitial, cookieless action 403 with the challenge header, assessed navigation served with the resident tag injected before </body> by the real HTMLRewritingStream, excluded path proxied, resident script served private, no-store.
  • CI gains the edge-core deploy key (secret set on this repo) and moves to Node 22.

Pinned to v0.7.1-rc.1; repin to v0.7.1 once edge-core #17 merges. Release v2.0.0 when the web side (PRO-432) can write the new store layout.

🤖 Generated with Claude Code

alistairsmith-tech and others added 2 commits September 20, 2026 23:55
Design in monocle-fastly-port-plan.md (workspace); tracked by PRO-401.
The web half is PRO-432 on web feat/pro-432.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Edge-core owns the request pipeline, the /__mcl/* endpoints, decisions,
credentials and injection; this package supplies what only the platform can.
config.ts reads the deployment the dashboard published to the Config Store
(the chunked DeploymentConfig, the chunked route index, exclusions, the origin
settings) and the Secret Store lazily. platform.ts is the Platform for Compute:
the client address, the origin leg with host override, client-address headers,
the chaining signature and cloned cache rules, HTMLRewritingStream for the
injected tag, and the POP cache for the breaker and the crawler snapshot,
refreshed in waitUntil through static crawler_<host> backends. Plain HTTP goes
to HTTPS; a host the config does not name is forwarded unassessed.

js-compute has no AES-GCM and no AbortSignal, so cookies use edge-core's HMAC
sealer and the Policy deadline is the backend's. It runs JavaScript interpreted
inside Wasm, so enforcement is published as lookups: 5,000 entries cost
milliseconds instead of the half second compiling them did.

The previous plugin (MCLVALID cookie, captcha_page.html, own cookie, policy
and path modules, PROTECTED_PATHS) is gone. Tests run the handler in Node
against doubles for the fastly:* modules; the typecheck covers edge-core's
sources against js-compute's types; CI loads the edge-core deploy key and
moves to Node 22.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@alistairsmith-tech
alistairsmith-tech marked this pull request as ready for review September 21, 2026 08:30
alistairsmith-tech and others added 3 commits September 21, 2026 09:32
…al packages

The lockfile predating this branch never recorded @emnapi/core and
@emnapi/runtime, which Linux installs need; npm ci refused it in CI.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The release tag on main, replacing the pre-release.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The repin's `npm install` ran with node_modules present on macOS, which drops
the platform-optional packages (`@emnapi/*`, esbuild's binaries) from the
lockfile again; a local `npm ci` cannot notice, since macOS never needs them,
but the Linux runner refuses the lockfile as out of sync. Regenerated from
scratch, as before.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@alistairsmith-tech
alistairsmith-tech merged commit dfc3936 into main Sep 21, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant