Xantham Landscape — Context and Analysis
Single purpose: the terrain the decisions were made in — thesis, reference-project analysis (Farscape/Glutinum), the Cloudflare constellation's shape, and the operating model that follows. Decisions themselves live where they are enforced: scope in cloudflare.pilot.toml, acceptance criteria in GOALS.md, execution steps in PLAN.md, dated state in STATUS.md. Established 2026-07-02.
1. The Thesis (restated for TypeScript)
Xantham is a general TS→F#/Fable binding generator; Cloudflare is the acid test, not the point. The Cloudflare SDK constellation is the first target because it has the most immediate utility (Fidelity.CloudEdge) and because it stresses every axis a general tool must handle: a flat ambient package (workers-types), rich module packages (agents: 31 declared entry points, of which the current crawl used one), a heavy type-program dependency (zod), a multi-package constellation, and a constantly moving surface (workers-types versions by compatibility date — 4.20260630.1 as of this writing). Standing rule: no target-specific knowledge in Xantham core. Mechanisms (multi-entry crawl, ambient roots, policy overlay, drift signal) are package-agnostic; everything Cloudflare-specific lives in one recipe file. If the mechanisms handle this constellation, they handle most of npm.
Farscape's founding thesis: use the declared surface area as ersatz API emulation — the binding should present the same touchpoints the underlying library has. Two difficulties: (1) impedance mismatch between the source language and F#; (2) not everything in a library is of interest.
For TypeScript this thesis holds with one restatement. A .d.ts surface does double duty: it declares the API touchpoints (what a consumer names: functions, classes, member signatures, constructable values) and it contains the type-level program TS uses to compute those touchpoints (conditional types, mapped types, infer chains, internal generic machinery such as zod's ZodTypeInternals). The C world has no evaluator for its headers; the TS world does — tsc itself.
Principle: bind the evaluated API surface, never transliterate the type program. The checker is the evaluator. Xantham's encoder sits on the checker. Where evaluation results are representable in F#, bind them faithfully. Where they are not (true impedance), degrade explicitly with an advisory and hand refinement to a developer-owned layer. Where a type exists only to serve the type program, it is not surface — unless the manifest says otherwise.
The encoder's checker-read-instantiated-members fix (2026-07-02) is the first instance of this principle and is hereby promoted from bug-fix to architecture.
The two difficulties map to the two architectural instruments: 1. Impedance mismatch → Layer 2 conventions + the advisory/Overlay discipline ("a wrong convention is worse than no convention; passthrough is always correct, even if incomplete"). 2. Interest/scope → the manifest (Pilot-equivalent): surface vs dependency is a declared decision, not an accident of crawler reachability.
The manifest is a steering wheel, not a filter. Over-generation happens simply because the mechanics are there — Xantham's own history: the crawler could reach the whole graph, so the whole graph became surface. The Pilot-equivalent exists as the front-end intervention point where the owner steers generation, in both directions at once: - Avoidance — exclude what is unhelpful in the original API surface (type-program internals, dependency machinery, deprecated areas) so it never consumes generation, gating, or review effort. - Curation — shape how the bound output is generated for use in the field: area partitioning and naming, per-area idiom conventions, packaging boundaries, semantic budgets. The same file that says "not this" also says "like this." Every downstream layer consumes the manifest; no layer makes scope or shaping decisions on its own.
What transfers from Farscape, and what does not. The lessons are architectural, not 1:1 mechanical. TypeScript's impedance match with F# is substantially better than C/C++'s — both are managed, closure-native, GC'd, and Fable already bridges runtime semantics — and the declared SDK surface is a far truer map of the desired F# API than C headers ever were (TS types already encode optionality, async, and error shapes that C conventions hid). Consequences:
- Layer 2 is thinner here. Farscape's L2 had to reconstruct semantics C erased (error enums → Result<T,E>, null conventions). Xantham's L2 is targeted idiom policy at declared exceptions — Promise policy, union ergonomics, overload shape — not wholesale wrapping. The default posture is that the declared surface is the API map, with a curated exceptions list.
- The difficulty budget sits at a different layer. Farscape's hard core was marshaling/lowering (e.g. null marshaling as flat closures for the Clef lowering strategy) — a class of problem that does not exist for TS→JS via Fable. Xantham's hard core is type-level fidelity (identity, generics, the evaluated-surface boundary) — which is why the L1 machinery absorbed the project's effort to date, and why the Pilot/steering, layering, advisory, and per-cell signaling patterns transfer while the marshaling machinery does not.
The manifest, gamed out for TypeScript (2026-07-02). Each Farscape mechanism was tested against the question does the C problem it solves exist in TS, and does the ecosystem already answer it? Result: Farscape's disciplines transfer 1:1 (advisory/Overlay, layering, per-cell signaling, phase gates — they are about the generator↔owner relationship, not about C); its mechanisms mostly do not (header lists, transitive_headers, namespace-prefix reconstruction, error conventions — they reconstruct structure C never declared, and TS declares it: package.json exports, explicit import edges, ES module paths, lockfiles). Hand-duplicating what npm already declares would create a parallel artifact that drifts. Therefore the manifest is inverted: not Farscape's reconstruction manifest (declaring structure the source lacks) but a policy overlay — declaring only decisions the source cannot make, defaulting everything else from the ecosystem's own declarations. The owner's genuine decision surface: (1) entry points; (2) per-dependency boundary policy (the avoidance axis — a package-keyed table, not file lists); (3) ambient-package area partitions; (4) synthetic placement (Xantham-invented types have no source home); (5) L2 exception conventions + budgets (the curation axis); (6) packaging — default one nupkg per npm package, overrides only. discover inverts likewise: it reads declared structure and reports only the decisions outstanding (missing policies, ambient partitions needed).
Two ecosystem defaults follow directly: a module package's entry list defaults to its own exports map (agents declares 31; the owner curates down — e.g. ./react is a React-only surface no F# consumer needs — rather than discovering entries by accident), and a moving surface is handled by pin + drift signal, not by chasing: the recipe pins versions (lockfile) and, for workers-types, a compatibility date (its dated snapshot dirs / latest alias). An SDK bump is a deliberate update run: re-crawl → deterministic IR diff → per-area gate deltas → a reviewable surface-drift report. This is where the determinism and gate investment pays out — an upstream release becomes a diff to review, not a mystery regression.
Operating model: this is not a one-time tool. Xantham runs on Cloudflare's release cadence — the update run is the steady-state product, not a migration event. Design consequences, binding on every phase:
- The recipe is durable; runs are cheap. All owner steering (recipe entries, dependency policies, advisory acknowledgments) must survive re-runs — acknowledgments key on stable type identities, never on line numbers or run artifacts, so a re-run never re-asks an answered question.
- The run is hands-off end to end: bump pin → crawl → gates → drift report → regenerate → changelog-gated republish of changed packages only. Human attention goes to the drift report and new advisories, nothing else.
- F# package versions track upstream (e.g. Fidelity.CloudEdge.Workers carries the workers-types version/compat-date it was generated from), so consumers can reason about skew.
- CI-shaped from day one: every gate already exits non-zero; the update run must too, so it can eventually be a scheduled job that opens a review when Cloudflare ships.
- Eventual form: many-to-many — multiple SDKs ingested in one run, multiple namespaced sub-libraries emitted under the Fidelity.CloudEdge umbrella. Near-term form: 1:1 import→export (one npm package → one F# library) — "enough to build something with those APIs" ships first.
The ambient exception — and it is the flagship target. @cloudflare/workers-types/index.d.ts is 164 top-level ambient declare statements, zero exports: one flat global scope, structurally a C header, organized only by naming families (KV*, R2*, D1*, Durable*, …). The "TS declares its own organization" argument holds for agents/zod/MCP and fails for the most important package in the goal. For ambient packages — and only for them — Farscape's namespace-reconstruction table is the literal correct tool. Mechanically, entries therefore come in two kinds: module entries (crawled by import; partition by their own source paths) and ambient roots (added as compilation roots, since ambient files cannot be imported; partitioned by the manifest's area table). Guide attribution: Farscape guides the process; Glutinum guides the L1 representation (Fable attribute idioms, erasure choices, packaging — a mature compiler with settled FFI idiom, unlike Clef/Composer which was co-designed); the type-fidelity core is Xantham's own ground. The Fable-specific risk class that replaces marshaling is silent representation errors (a wrong [<Erase>] produces wrong JavaScript, not a compile error) — which is why the L3 runtime proof (HelloWorker under miniflare) sits in Phase 2, not at the end.