Infrastructure
The seams in the core sample are the platform migrations
Every product built in the period this site covers ran in production. The strata chart on the home page draws that period as four seams: Azure and SQL Server, the pivot to Cloudflare and Postgres, six products on one shared template, then a shared substrate and a deliberate teardown. This page is what that chart doesn't say out loud: where things actually ran, what moving them cost, and how they were turned off.
Fifteen products, one person, at once
Fifteen of the seventeen products on this site had hosted services. Floriva ran on-device and the decision-making skill has no hosted service at all, so neither appears below. Everything here happened alongside a full-time job, and most of it with other products also in flight. That is the constraint that shaped the rest of this page: hosted CI runner minutes are billed per project, and with fifteen projects running at once the bill adds up, so the verification gate that /methoddescribes ran locally instead. The cost and the choice are the same fact.
The same instinct shows up six months earlier, in the Azure era. GatherGrove's backend build pipeline has never triggered on a push or a pull request. Its second line reads:
# Manual trigger only to conserve build minutes
That is .azure/pipelines/backend-build.yml, line 2, from the first products built here. The reasoning that later kept CI off GitHub's runners had already been used once, on a different platform, for the same reason.
Azure, then Railway, then Cloudflare
Three platforms carried the whole estate in sequence. The three .NET products built first, GatherGrove, SkillLedger and GeoLeap, committed the same SQL Server to PostgreSQL cutover on the same calendar day, 2026-02-17, each in its own repository. Three to four weeks later, Railway backend configuration and Cloudflare Workers frontend configuration landed in all three within a three-day window, 2026-03-15 to 2026-03-17. Independent codebases moving in lockstep is itself a platform decision.
What ran on Azure, and for how long. GatherGrove ran on Azure App Service, Linux containers on DOTNETCORE|9.0, with staging and production apps deployed by an Azure DevOps pipeline that curled /api/v1/health after every release and failed the deploy on a non-200. Its frontend Content-Security-Policy still lists the live staging and production hostnames in its connect-src, left in the tree after the product moved on. Azure Key Vault held its secrets until the vault itself was deleted while the code still depended on it at startup. Every production request hung until the Key Vault client was ripped out and replaced with plain environment variables, the same week the product moved to Railway: infrastructure outside the code's control disappeared out from under a running service and took production down with it.
SkillLedger priced Azure without paying for it: a complete ARM template specifies an App Service Plan, a Key Vault, and an Azure SQL Database at the S3 tier with geo-redundant backup, and Key Vault client code exists and is tested. No Azure hostname appears anywhere in the shipped configuration, and its own architecture document describes the path as configured, then abandoned. SkillLedger ran on Railway and Cloudflare Workers instead, from the start.
GeoLeap carries two incompatible Azure strategies, written at different times: a PaaS script that provisions App Service, Azure SQL and Azure Cache for Redis from scratch, and an Azure virtual machine that ran Postgres and Redis as Docker containers on the VM itself rather than as billed Azure data services, still being hardened five months after the rest of the estate had moved to Railway. The VM path is what was actually used. Every tier chosen for it, B2 compute, S0 database, Basic Redis, is the cheapest paid option at its layer. A separate Bicep template describes a production-grade version of the same system with autoscaling compute and premium Redis, priced but never deployed: the same shape as SkillLedger's Key Vault.
Railway was rented compute for intermittent traffic. CapVeri's own infrastructure notes call this out directly: the backend ran on Railway as three always-billed services, a FastAPI API, a Celery worker and a Redis broker, for roughly six months, configured almost entirely through Railway's own dashboard rather than the repository. The only trace left in version control is an environment variable pinned after a cold build failed to find a precompiled Python interpreter. The written migration plan states its goal in one line: eliminate the Railway bill. The record of the cutover states the outcome the same way: zero dollars a month, going forward.
The sharpest single detail in the whole estate is in Lextract's history. One day before its Railway backend was retired, a commit titled "stop idle database wakeups" deleted the only entry in Celery's beat schedule: a maintenance task that recovered stalled extraction jobs, run automatically every six hours. That six-hour cadence, running with no user active, was enough on its own to wake a serverless Postgres tier that billed for compute time, on a completely different platform: a cost control on one system defeating a cost control on another. The fix removed the scheduled entry, left the task callable by hand, and deleted the matching Railway service file outright, the day before the whole backend moved off Railway for good.
What replaced all of it: eleven repositories with Cloudflare configuration, 29 wrangler.jsonc and wrangler.toml files between them, counted file by file with git ls-files and reconciled against itself below. A twelfth product from the same period, the Floriva iOS app, has zero network call sites and zero Cloudflare footprint by design: it is local-first and on-device, outside this estate entirely.
What eleven products actually used
Counted directly against each repository's committed Cloudflare configuration: what a product wired up, which is a different question from what it served in production. A primitive at zero prints alongside the rest, on equal footing with everything else in the inventory.
Scrolls sideways. The notes are in the last column.
| Primitive | Products | Notes |
|---|---|---|
| Workers | 11 / 11 | Ventora Platform alone deploys 5 |
| Static Assets | 8 / 11 | the pattern for a Worker-served SPA |
| Pages | 1 / 11 | Floriva Web |
| D1 | 8 / 11 | |
| R2 | 10 / 11 | Sequencer has 4 buckets, 2 shared with other products |
| KV | 3 / 11 | |
| Hyperdrive | 5 / 11 | a 6th, PebbleDesk, is documented outside the committed config |
| Queues | 4 / 11 | |
| Dead-letter queues | 3 / 11 | a subset of the 4 above |
| Durable Objects | 6 / 11 | a 7th is documented outside the committed config |
| Workflows | 1 / 11 | Lextract, extraction and export |
| Cron Triggers | 7 / 11 | |
| Browser Rendering | 1 / 11 | GrantPipe, production environment |
| Send Email binding | 1 / 11 | Floriva Web, outbound. No product used inbound Email Routing |
| Turnstile | 8 / 11 | |
| Access (Zero Trust) | 9 / 11 | mostly service-token consumers rather than protected apps |
| Images | 0 / 11 | |
| Vectorize | 0 / 11 | |
| AI Gateway | 0 / 11 | every LLM call goes to OpenRouter directly |
Three primitives sit at zero across all eleven products: Images, Vectorize and AI Gateway. The AI Gateway row carries the reason: every LLM call in the estate goes to OpenRouter directly instead.
Reconciling the header count: 3 + 3 + 3 + 3 + 2 + 1 + 3 + 3 + 1 + 2 + 5 wrangler files across the eleven repositories sums to 29, which is the figure quoted above. Counted with git ls-files against each repository's own committed files.
A handful of things in the estate are unusual enough to name. Ventora Platform runs a private npm-compatible package registry on a Worker, backed by R2: it implements the registry's own HTTP protocol, a publish endpoint, tarball downloads, and a 15-minute publish lock to keep concurrent publishes from colliding. A second Worker does the same thing for Python packages. A third renders React Email templates and serves them over an HMAC-signed HTTP endpoint so Python-authored products can send the same branded email a TypeScript product would, without either language importing the other's code. A codegen script reads one JSON schema and emits both a TypeScript union type and a PythonLiteral, with a --check mode that fails the build the moment the two drift apart. Gavelhouse's deploy tooling statically scans everypackage.json in the repository for a script that calls wrangler deploy directly, outside its own orchestrator, and fails the check if it finds one. Sequencer carries a test dedicated to failing the build if a real Cloudflare resource ID is ever committed in place of a placeholder. And Gavelhouse's own Worker still answers on boardstack.app, the product's retired former name, bound as a route rather than a custom domain so the old brand can 301 to the current one without touching the zone's own DNS.
Scrolls sideways. Row-level security is the last column.
| Product | Store | Migrations, per the README | Row-level security |
|---|---|---|---|
| GrantPipe | Postgres via Hyperdrive, plus D1 | 115 tables, 95 migrations | |
| Kaiplan | Postgres, plus D1 | 20 PG tables / 25 migrations; 9 D1 tables / 14 migrations | |
| PebbleDesk | Neon via Hyperdrive | 44 tables, 68 migrations | |
| Gavelhouse | Postgres | 42 tables, 27 migrations | |
| PHIGuard | Postgres, Hyperdrive-bound | 62 migrations | |
| Sequencer | D1 | 21 tables, 33 migrations | |
| Ventora CRM | D1 | 14 tables, 17 triggers, 20 indexes; no migration total stated | |
| CapVeri | Supabase Postgres | 142 migrations, 63 tables | 394 RLS policy statements |
| Lextract | Neon via Hyperdrive | 17 migrations | every table; policies carry ancestry from a prior Supabase phase whose migrations stayed separate |
| Floriva Web | D1 | 15 migrations, 12 tables |
Gavelhouse's own README says "Postgres via Hyperdrive" in three places, but its committed wrangler.toml has no hyperdrive block, and its dev environment uses a raw connection string. Whether production carried the binding and the snapshot stripped it, or the README describes a state that stayed on paper, is unknown from the repository alone.
The week of 2026-06-11
Five products moved in three days. Gavelhouse, Kaiplan and PebbleDesk were all retired on 2026-06-11, the same calendar date across three independent repositories, each with its own dated record of the shutdown. CapVeri and Lextract left Railway for Cloudflare Workers on 2026-06-12 and 2026-06-13. Five codebases, one decision, carried out in sequence. PHIGuard's frontmatter carries the same end date but sits outside this count: its repository holds no dated decommissioning record, and it kept receiving commits after it.
Lextract's backend was rewritten in a single day. 29 commits, all dated 2026-06-12, against a 1,091-line written plan that named the goal up front: build a new TypeScript Worker backend that serves the existing API contract, and prove contract compatibility with tests before removing the Railway path. The plan explicitly required route contract tests alongside unit tests, and it held off deleting the Railway configuration until the Worker had already been verified against that contract. The Celery beat scheduler deletion described above happened the day before this, as preparation for it.
CapVeri moved in two separately planned steps, two days apart. The frontend and marketing site left Vercel for Cloudflare on 2026-06-12: the React app became a Workers Static Assets deployment, and the Next.js marketing site became an OpenNext Worker. The backend left Railway for Cloudflare on 2026-06-12 into 2026-06-13, and this one required a full port rather than a lift and shift, since Workers run code directly without a container: the FastAPI application was ported to TypeScript, module by module, roughly 43 commits, each carrying its own tests. The porting rule was to preserve behaviour exactly, down to language semantics: a Decimal clone at CPython's own rounding precision, and line-level ports of fuzzy-matching and date-arithmetic libraries the original relied on. Two places broke that rule on purpose and both are recorded as decisions rather than left quiet: a statistical anomaly detector that resisted faithful reproduction inside a Worker was retired from both implementations rather than shipped as an approximation, and a webhook signature check was rewritten to match the third party's actual specification instead of matching the old code's specification, which the old code had gotten wrong.
Before anything was deleted, the job queue was queried directly for anything still processing, retrying or queued, and all three came back empty. With that confirmed, the Railway project was deleted in one command, API, worker, Redis and volume together.
How a change reached production
No two products deploy the same way, but every path that has one runs a verification chain before it touches anything live.
Ventora CRM's deploy script is defined entirely in terms of what has to pass first: it calls verify:rollout, a chain of 14 steps, lint, typecheck, test coverage, a local migration check, the actual migration and seed, a product-retirement step, an origins check, a firewall check, a media check, an end-to-end suite, a deploy dry run, and an audit of production dependencies, before it will run the staged rollout at all.
Gavelhouse deploys through a Node orchestrator that checks for uncommitted changes, injects the build commit into a generated config file, runs each project's own build steps, uploads to R2 when the build needs it, and finishes with a mandatory post-deploy smoke check against the live URL. A separate guard statically scans everypackage.json in the repository and fails the check if anything calls wrangler deploy outside that orchestrator, so the verification step stays impossible to route around.
Sequencer's deploy script checks who is authenticated, diffs the sequence definitions it is about to ship against what is already live, dry-runs the deploy, and checks readiness before and after. It explicitly disables its own migration command with an error message pointing at a documented expand-deploy-contract strategy: applying a production migration ahead of a compatible Worker can break writes that are still in flight, so the script refuses to let that order happen by accident.
PHIGuard's deploy redeploys just the targets whose watched paths actually changed, then runs a production smoke check that hits two different health endpoints on purpose: a database-free liveness check and a separate readiness check that does touch the database, kept apart so a routine liveness ping avoids waking a database that bills for being awake. It is the same instinct as the Celery-beat deletion, in a different shape, on a different product.
GrantPipe and Kaiplan's deploy paths are thinner: a direct deploy script, with Kaiplan's web deploy chaining a D1 migration and a backfill ahead of wrangler deploy itself.
Rollback: the estate's practice is to move forward behind a verification gate rather than to reverse after the fact. Sequencer refuses to run a migration ahead of a compatible Worker, and Ventora CRM stages its D1 migrations in two phases around the Worker deploy, so an incompatible migration is blocked from landing against a Worker still serving the old shape. Both gates exist to make a rollback unnecessary rather than to provide one.
Four ways to turn something off
Shutting these down meant something different each time. Each product was retired in a way suited to how it was built, and the differences are the point. Four of those ways are worth setting out.
PebbleDesk has the fullest record. A single dated document walks through verified Cloudflare state after the fact: the API Worker deleted, then a post-deletion inventory across Workers, Pages, R2, D1, Hyperdrive, KV, queues and email routing, all returning zero results for the product's name, plus five live hostnames confirmed dead by request. Its four deploy scripts were rewritten down to three lines each: set strict mode, stop on error, then throw a message refusing to deploy the retired Worker. All three of its wrangler.jsonc files were reduced to a decommission notice with no Worker name, entry point or bindings left in them, so a raw wrangler deploy would fail before it could recreate anything. The document closes with a section titled "External checks still requiring dashboard confirmation," naming Stripe, Sentry, Resend, PostHog, the OAuth provider, the accounting integration and the domain registrar as things it declines to claim are finished.
Gavelhouse was switched off in place. Three independent kill switches were layered on top of the running product: a build-time flag forced into the frontend so the deployed bundle renders a closure notice in place of the product, a runtime flag checked into both Workers' committed configuration, and the API Worker returning HTTP 410 for every request regardless of what code is actually deployed. The deploy orchestrator itself was taught the shutdown state, reading the flag at deploy time to skip the object-storage upload step entirely. Local development leaves the flag off, so the real application still boots and runs for anyone reading the code. The switch holds for anything that reaches production, and the product's design stays visible in the code.
Kaiplan was retired, then restored for publication. The retirement commit had replaced its three Worker entry points with 410 stubs, and they were reactivated in the published snapshot so the project runs as something a reader can look at. Every resource identifier in its Cloudflare configuration is a placeholder, standing in for the real one.
GrantPipe's teardown was scoped to one integration. What got retired was the accounting-sync feature rather than the product around it. Its accounting-sync routes return 410, six tables that integration used are left in the schema but unused, and a contract test exists for the sole purpose of catching a future change that accidentally brings the integration back to life.
CapVeri's own README states plainly that its hosted Railway services are gone, but the repository holds no dated decommissioning document for it, unlike PebbleDesk's. Whether one exists elsewhere is unknown from what is here.