Status: active component architecture and database-decomposition runbook, 2026-08-08.
The requirement-to-evidence index and remaining connected release gates are in
platform-restructure-completion-audit.md.
Forge remains one product and one monorepo, but it is released as twelve
authority-scoped Workers. cloudforge-edge is the only public origin for the
Forge application. It owns static web and documentation delivery and routes
requests to identity, content control, repository, and Deploy control over Service Bindings. It
has no database, object storage, email, AI, provider-control secret, Workflow,
or Durable Object binding.
The five responsibilities extracted from the former cloudforge-api are:
| Component | Product responsibility | Authority and state it owns |
|---|---|---|
edge |
forge.smol.ai, web, docs, route selection |
Assets and four HTTP Service Bindings only; no product state |
identity |
Login, sessions, credentials, users, accounts, organizations, teams, entitlements, identity broker, Slack installation records | Identity/account tables in D1; credential validation RPC; identity and OAuth secrets |
content-control |
Gists, character skins, product social cards, stateful HTML metadata | Content tables in D1, character-skin objects in Git R2, identity RPC |
repository |
Git, repository APIs, issues, PRs, Actions dispatch, agents, repository invitations, Wiki control, repository-owned Site configuration | Repository/Git/Actions/invitation tables, Git and Wiki R2 objects, repository DO and Workflows, repo-scoped authorization |
deploy-control |
Deploy projects, releases, provider targets, Sites projects/domains, desired state, activation, reconciliation | Deploy and Sites-control tables, Site asset control access, runner/runtime handoff and provider-control secrets |
notifications |
Transactional invitation delivery | Invitation delivery outbox state and the only send_email binding |
The shared D1 database is a temporary physical migration boundary, not a
shared-ownership license. config/forge-database-domains.json assigns every
canonical table to identity, repository, automation, Deploy, release
authority, notifications, content, Wiki, or AI control; rate_limits is
explicitly partitioned by service-prefixed key. npm run test:database-domains
reconstructs the canonical schema and fails on an unowned or multiply-owned
table.
The initial executable inventory contains 198 tables and 131 cross-domain
foreign keys. Those edges are migration blockers, not permission to retain a
permanent shared database. Each blocker must become an owner-local reference,
immutable external identifier, Service Binding query, or transactional outbox
before that domain binding changes. New domain D1 databases are created as
additive shadow targets. A cutover requires a source Time Travel bookmark,
deterministic replay, schema and row parity, stable content digests, dual-read
comparison where practical, and an independently verified rollback binding.
Production cloudforge-next is never recreated or destructively transformed
by this decomposition.
Identity, content control, repository, Deploy control, and notifications now have independent
npm workspace entrypoints and Wrangler files. Their first extraction delegates
to the existing packages/api implementation kernel so the provider bundle
and public behavior remain stable. Source directories move only after their
imports, tests, and database authority can follow the same owner; the kernel is
deleted when the last feature leaves it.
The old cloudforge-api artifact served the SPA and docs and also carried D1,
Git/Site/Wiki R2, AI, email, two Workflows, the repository Durable Object,
runner/runtime/Wiki Service Bindings, OAuth and signing secrets, Git and
repository routes, account and identity routes, Deploy/Sites routes, invitation
delivery, operator endpoints, and a one-minute reconciliation cron. A content
change therefore rebuilt and reauthorized this complete authority set.
Responsibilities such as Wiki generation, CI runner, Sites delivery, Deploy runtime, AI routing, and Slack execution were already source modules and Workers, but the release workflow redeployed every Worker after every main commit. The main API also remained their authority-rich control-plane hub, so source modularity had not produced independent failure or release domains.
| Worker | Public route | Bound platform authority | Calls |
|---|---|---|---|
cloudforge-edge |
custom domain forge.smol.ai |
Assets | identity, content-control, repository, deploy-control |
cloudforge-api (identity) |
none; reached through edge/RPC | shared D1; identity/OAuth secrets | no general downstream binding; exports IdentityControl, ConnectedForgeIdentity, and SlackIntegrationControl |
cloudforge-content-control |
none; reached through edge | shared D1, Git R2 | identity RPC |
cloudforge-repository |
none; reached through edge | shared D1, Git R2, Wiki R2, AI, RepoLock, last-commit and path-index Workflows, repository secrets |
identity RPC, runner, Wiki, notifications |
cloudforge-deploy-control |
none; reached through edge | shared D1, Site R2, Deploy/identity signing secrets | identity RPC, runner, Deploy runtime |
cloudforge-notifications |
none; Service Binding only | shared D1, invitation-token key, email sender | none |
cloudforge-ci-runner |
none | shared D1, cache/Git/Site R2, Sandbox DO, CI/pack/full-clone Workflows, provider token, release policy | Deploy runtime and Cloudflare provider APIs permitted by per-repository policy |
cloudforge-sites |
*.sites.smol.ai/* |
shared D1, Site R2, analytics, dispatch namespace, request-signing secrets | identity RPC and Deploy runtime |
cloudforge-deploy-runtime |
none | application shard and quota DOs, context and AI-router secrets | AI router; health includes the exact bound router version |
cloudforge-wiki |
none | shared D1, Git/Wiki R2, AI, Wiki Workflow and Wiki secrets | AI Gateway/providers; exact release health is exposed through repository's composed probe |
cloudforge-ai-router |
none | shared D1, Workers AI and provider credentials | configured AI providers |
cloudforge-slack-agent |
custom domain slack.forge.smol.ai |
separate Slack-agent D1, Slack Workflow and signing secret | identity's narrow Slack RPC |
The Cloudflare zone also has an unrelated api.smol.ai/* route to
smol-endpoint; it is not a Forge release unit. Edge additionally exposes
cloudforge-edge.swyxio.workers.dev and immutable
VERSION-cloudforge-edge.swyxio.workers.dev preview origins because connected
Deploy must verify the exact uploaded version before activation. They are
noncanonical verification origins; forge.smol.ai is the product origin. The
private split components have workers_dev disabled.
Static paths and documentation terminate at edge assets. /api/auth, account,
organization membership/team, user-profile, identity and Slack-integration
paths route to identity. Organization repository operations, profile README and
R2-backed avatar paths, Repository, Git Smart HTTP, Actions, agent,
Wiki-control, gist and invitation paths route to repository. Deploy
project/release/provider and Sites-control paths route to deploy-control. Route
ownership is deterministic and contract tested; /internal/* is denied at the
public edge.
IdentityControl.authenticate RPC once, fail closed
if it is unavailable, and then apply resource-local authorization using the
returned identity context. They do not copy token validation logic or build
a synchronous call chain.config/forge-releases/*.json is the exhaustive binding inventory. Each
binding name declares its provider type; configured or live bindings outside
that allowlist fail release verification. This is also the auditable inventory
for D1, R2, DO, Workflow, AI, email, Assets, secrets, variables, analytics,
dispatch namespaces, version metadata, and Service Bindings. The identity
contract's prunableBindings records obsolete secrets that may be removed by
the narrow release operation but can never be reintroduced as allowed
authority.
| Component | Scheduled/durable behavior | Completion authority |
|---|---|---|
| identity | one-minute account authorization and lifecycle repair | identity/account generation and operation rows |
| repository | repository metadata, Actions, agent, Git snapshot and invitation-intent handoffs; Workflows and RepoLock |
repository refs/objects, run records, outbox receipts |
| notifications | one-minute invitation delivery lease scan | provider receipt or explicit ambiguous/terminal delivery state |
| deploy-control | one-minute provider activation, delivery and desired-state reconciliation | deploy release/operation row plus active provider pointer |
| runner | one-minute dispatch/cache/canary reconciliation; CI, pack and full-clone Workflows | immutable run/step evidence and external operation receipt |
| wiki-worker | Wiki Workflow and durable build stages | Wiki revision/build receipt |
| slack-agent | Slack Workflow with leased job polling | Slack job/transport receipt |
| Sites/runtime/AI/edge | request or DO-driven; no database repair cron | owner-signed context, DO state, invocation receipt, or active edge version |
This is the smallest extraction and would have prevented web/docs changes from requiring email approval. It leaves identity, Git, Deploy, provider control, and notification authority in one failure and rollback domain, so it treats the incident as an asset-routing issue instead of the broader coupling signal.
Separate Workers for accounts, organizations, credentials, repositories, issues, Actions, agents, Sites, deployments, invitations, and Wiki control would maximize nominal deploy units. In the current relational model it would also create synchronous call chains and cross-service transaction problems, duplicate repository authorization, and make local debugging materially harder. Source directories are not trust boundaries.
Edge, identity, repository, Deploy control, and notifications are separate; tightly coupled repository operations remain colocated; existing execution Workers remain specialized. This removes the most dangerous bindings from unrelated releases while retaining local decisions where correctness depends on one transaction or one repository authorization check. It also permits a gradual future database split without making that split a prerequisite.
Use synchronous Service Binding/RPC calls for bounded request/response facts: credential validation, route forwarding, a current health observation, and a single repository or deployment operation. Do not synchronously traverse edge -> identity -> account -> repository -> runner.
Use a durable outbox, Workflow, or leased reconciliation record when the caller has committed state and another component or provider must eventually perform a side effect. The database record is authoritative; a Service Binding nudge is only latency optimization. All consumers require a stable idempotency key, bounded attempts, visible terminal/ambiguous state, and a repair scan. Cloudflare Queues may replace polling where throughput warrants it, but their at-least-once delivery still requires the same idempotency and state model.
config/forge-components.json is the machine-readable component graph.
scripts/forge-release-impact.mjs evaluates changed paths, shared contracts,
migrations, provider configuration, and release-system inputs.
forge-release-provider-impact.mjs compares against every component's own
active exact-SHA baseline, and verify-component-source-impact.mjs checks the
graph against Wrangler's actual bundle metafiles. Local component code affects
only the components that compile it; web/docs affect only edge; and a migration
affects the D1 consumers. Root lockfile impact follows the signed runtime
workspace closure. Shared subpaths retain explicit consumer ownership; an
unresolved shared dependency fails closed rather than silently becoming a
permanent all-Workers rule.
Dependents are included only when their compiled/runtime contract changes, not
merely because another component has a new implementation. Each component
declares its own releaseContract path: a binding-policy edit selects only
that owner, while an unmapped contract file fails impact calculation closed.
Each component's runtimeBindings file lives in its own workspace and is
checked against the corresponding release contract. A new component therefore
does not change an aggregate runtime binding map. Release-system changes rerun
proof without rebuilding any runtime by default.
The merge queue first consumes already-signed evidence for the exact candidate and base. If matching CI is still running, it joins that run instead of dispatching a duplicate queue build. The controller promotes independent managed Workers concurrently inside dependency-safe waves, persists each provider receipt separately, and starts a dependent wave only after its selected predecessors have succeeded. A cancelled workflow is terminal and rolls the release back; it cannot leave the queue permanently running.
When signed evidence introduces a component unknown to the active controller, the plan retains that component and bootstraps deploy-control first. The exact-source controller then resumes the same fenced release with the new manifest. This avoids both silent omission and an out-of-band feature-task release handoff.
Every component release writes forge-component-release/v1 evidence with:
An upload is not completion. Completion is an active immutable version whose annotation or connected managed binding identifies the exact SHA, whose provider bindings exactly match the checked-in typed allowlist, and whose health or public route proves the intended version. A verification failure deploys the recorded prior version back to 100% when one exists.
Private release health is composed without creating new public origins. Edge observes identity, repository and Deploy control; repository observes notifications, runner and Wiki; Deploy control observes runner and runtime; runtime observes AI routing. Sites and Slack use their existing public health hosts. The release coordinator selects the observation for the component being released and requires its exact provider version, release contract, empty missing-binding list and healthy state. An unrelated degraded sibling remains visible in the aggregate graph but does not block that component's independent release.
The connected generalized Deploy contract in forgeBuild.ts describes only
the public edge artifact. Its provider policy permits only the three Service
Bindings and no email, D1, R2, AI, Workflow, Durable Object, or provider
credential. The platform components behind that edge use the trusted,
dependency-aware release coordinator rather than smuggling their authority into the connected
application build.
Each stage below is independently releasable even when executed in one planned cutover for this pre-production system.
forge.smol.ai, assets and docs to edge; enable path-derived releases.workers_dev is disabled.workers.dev preview is verification-only;
private Workers keep workers_dev disabled.Run npm run dev:platform after providing non-production values in each
runtime workspace's .dev.vars; the checked-in scoped .dev.vars.example
files are safe for disposable development. Wrangler starts the six split configs in one local
multi-Worker session with one persisted D1/R2 directory and connected Service
Bindings. Add -- --migrate to apply D1 migrations to that local persistence
directory before startup. Each workspace file contains only that runtime's
secrets, so identity cannot receive repository, notification, or Deploy
secrets and vice versa. Add -- --with-runner (and the runner's local
example secrets) for the container-backed production Git-ingest path. The
integration harness creates disposable dev-var files from the examples only
when no developer-owned file exists and deletes only the files it created.
Edge is the listening Worker at http://localhost:8787.
Trigger a component cron with /cdn-cgi/handler/scheduled. Do not use real
provider secrets locally; AI bindings are remote and should be avoided unless
the test explicitly needs them.
The former combined API entrypoint and its all-authority integration Wrangler configuration are gone. Local integration tests traverse edge and the real Service Bindings, so a passing smoke cannot validate a topology that production does not use.
Unit and contract tests own the fast path. Tests that depend on Cloudflare Service Binding/RPC behavior, version overrides, routes, email-binding policy, or provider activation use disposable real Workers and isolated names. They must record their exact versions and delete only the resources they created.
For production debugging, begin at edge /health/dependencies, follow the
reported component version IDs, then inspect the owning component's structured
operation/outbox/reconciliation record. Do not repair product state with raw D1
commands. Re-drive the owning narrow API or reconciliation job. Provider state
is compared to durable desired state, and the release evidence supplies the
exact rollback target.
The default-branch workflow definitions are likewise Git-owned. A push reads
.github/workflows from the exact Git object SHA, validates Forge's small
workflow contract, and synchronizes source metadata before dispatch. Local
tests use disposable D1/R2 substitutes for object and transaction behavior;
Service Binding/RPC, version routing, email policy, and provider activation
tests use disposable real Workers when Miniflare cannot reproduce the
Cloudflare contract.