SmolForgeField Notes

Notes on systems,
code, and craft.

All notes

Architecture

We Split Forge’s API Into Five Workers

A blocked docs release exposed one authority-rich hub beside six specialized runtimes.

A docs-only release now touches one secret-free edge Worker because Forge aligned eleven release units with authority, state, failure, and rollback.

A documentation release was blocked before its content mattered. The public/docs artifact also requested email authority, and the connected-provider policy refused it. The policy was right; the release boundary was wrong.

Forge now releases one product as eleven authority-scoped Cloudflare Workers. Five are core platform owners—edge, identity, repository, Deploy control, and notifications. Six are specialized runtimes for CI, Sites delivery, dynamic execution, Wiki generation, AI routing, and Slack.

The immediate result is narrower release authority. A documentation change now selects one edge Worker instead of a seven-Worker pipeline. The docs-serving artifact went from 14 platform bindings in nine categories, ten declared secrets, and twelve scheduled repair jobs to five bindings in three categories, no secrets, and no scheduled jobs. The measured core boundary surface grew because routing and contracts became explicit; the win was aligned authority, ownership, failure behavior, and rollback—not fewer total lines.

Authority—not source layout—was the problem

The split began after a documentation release exposed an email-policy mismatch. Forge's combined cloudforge-api artifact served the application and docs, but it also carried D1, Git and Wiki storage, AI, email, Workflows, a repository Durable Object, provider-control secrets, and bindings to specialized Workers. The provider policy rejected authority that had not been approved. That was correct behavior. A documentation release should not have needed email authority at all.

The old entrypoint made the coupling visible. At the last combined revision, packages/api/src/index.ts was 533 lines with 69 imports. Its Wrangler configuration placed application delivery and product state inside one release envelope. A 682-line production workflow could deploy all seven Workers after any successful commit.

Many responsibilities were already separate source modules. Wiki, runner, Sites, Deploy runtime, AI routing, and Slack also had distinct Worker code. Source modularity had not produced independent trust or rollback boundaries. The artifact graph, not the folder tree, was the architecture smell.

An asset-only edge in front of the old API would have fixed the triggering release. It would also have left credentials, repositories, deployment control, notifications, and provider secrets inside the same failure domain. We chose the larger repair because the incident revealed a production-contract problem, not an asset-routing problem.

Eleven release units contain five core owners

The infographic below follows the blocked docs release through the refactor, then puts the operational and code measurements on the same page. Its primary question is what a documentation release could touch. Open the measured-file explorer beneath it to switch code metrics and inspect individual runtimes.

The architecture refactor, in one view

A docs release exposed one oversized authority envelope

Forge added explicit boundary code, then made releases smaller, safer, and owned.

01

The incident

A content-only change inherited capabilities it did not need.

Docs changeclarify release authority7f2ab47
Combined artifactcloudforge-api
  • Email
  • Secrets
  • Storage
  • AI / models
  • Workflows
  • Scheduled jobs
Policy rejectedEmail authority mismatch
02

The refactor

One release envelope became five core owners. Six specialist runtimes stayed separate.

Forge · one product + monorepo
  1. 01EdgePublic routes + docs
  2. 02IdentityAuthentication
  3. 03RepositoryGit + agents
  4. 04Deploy controlRelease truth
  5. 05NotificationsEmail delivery
Synchronous factsowned boundariesdurable handoffs
Six specialist runtimes retained
  • Runner
  • Sites edge
  • Deploy runtime
  • Wiki
  • AI router
  • Slack agent
03

What changed

The release envelope shrank even though the measured boundary surface grew.

Operational authority
Platform bindings
14
5
Binding categories
9
3
Declared secrets
10
0
Edge scheduled jobs
12
0
Docs release units
7
1
Measured code
Boundary LOC
647
822
Longest core function
133
52
Largest composition file
533
169
Before 72cd968; after code 7feaba7 and release envelope 3c77c17. Code measurements cover the named files in the checked-in snapshot, not the entire repository.
Inspect the measured filesSwitch metrics and select each runtime
Before · 72cd968647 lines
api/src/index.ts483
api/src/routes/users.ts164
After · 7feaba7822 lines across the core boundary surface
Forge · one product and monorepo
synchronous facts · Service Binding/RPCdurable side effects · outbox/reconciliation
Six specialized runtimes retained
SelectedRepository

Git, repositories, Actions coordination, and agents
D1, Git/Wiki R2, AI, Workflows, and repository DO

2 measured files283 logical LOC63 decisions20 functionslongest 45 lines
Measured fileLOCDecisionsLongest
api/src/components/repository.ts1631129
api/src/routes/users/repository.ts1205245
Authority and release envelopeWhat a documentation change was allowed to touch

72cd9683c77c17

Release surfaceBeforeAfterCut
Platform bindingsAssets, AI, D1, R2, Workflows, services, email, metadata, and a Durable Object became assets, metadata, and three Service Bindings.14564%
Binding categoriesThe edge has no database, object storage, AI, Workflow, Durable Object, email, or provider-control category.9367%
Declared secretsA public web and documentation release no longer enters any secret-bearing runtime.100100%
Edge scheduled jobsReconciliation moved behind the owner whose state it repairs.120100%
Docs release unitsA docs-only change now selects the edge instead of traversing the seven-Worker production workflow.7186%
Largest core composition fileComposition code stayed explicit, but no core entrypoint remains the universal assembly point.53316968%

The public/docs release envelope before the split versus the completed edge contract.

Bars normalize within each tier; printed values are exact. Named entry, route, and orchestration files shown here—not the entire repository. Logical LOC excludes blank and comment-only lines. Decision nodes are AST-counted branches, loops, catches, cases, ternaries, and logical operators. Longest function is its physical source span. The checked-in snapshot is rendered directly and performs no live analysis.

The awkward result is also the important one. Across the named core files, logical code grew from 647 to 822 lines and decision nodes grew from 82 to 158. Explicit route ownership, dependency health, and contract plumbing cost code. At the same time, the longest measured core function fell from 133 to 52 lines, and the largest core composition file fell from 533 to 169 lines.

Those measurements do not prove that the product became simpler. They show that complexity moved out of one universal assembly point and into inspectable boundaries. The explorer uses checked-in snapshots at 72cd968 and 7feaba7; its caption defines the file scope and counting method. It is not a live repository scan or a measure of all product code.

The five core owners have different reasons to exist:

  • Edge owns forge.smol.ai, static assets, docs, and deterministic routing. It has Assets, version metadata, and three Service Bindings, but no product database, R2 bucket, email sender, AI, Workflow, Durable Object, or provider-control secret.
  • Identity owns credentials, sessions, users, accounts, organizations, teams, entitlements, and revocation generations. It answers who is present; it does not decide repository access.
  • Repository owns Git, repository APIs, issues, pull requests, Actions dispatch, agents, invitations, and Wiki control. These operations remain together because their authorization depends on repository state.
  • Deploy control owns projects, releases, provider targets, desired state, leases, activation, reconciliation, and the active production pointer. Runners perform work; they do not become deployment truth.
  • Notifications owns invitation delivery state and the only email binding. No public route or repository authority comes with it.

The other six Workers already have distinct provider or execution contracts. A container runner, wildcard Sites edge, Durable Object runtime, AI router, Wiki workflow, and public Slack webhook do not become peers merely because they share a monorepo. “Eleven microservices” would be a less accurate description than “five owners plus six specialized runtimes.”

Authenticate once; authorize beside the resource

Splitting identity from repositories can turn one local decision into a fragile RPC chain. Forge separates two questions instead.

request
  → edge selects one resource owner
  → owner calls IdentityControl.authenticate once
  → identity returns a bounded identity context or fails closed
  → owner loads its local state and decides authorization

Identity owns credential validation, revocation, account context, and authorization generations. Repository decides whether that identity may mutate a repository. Deploy control decides whether it may operate on a deployment. Authentication does not imply access to either resource.

This avoids duplicate token validators and avoids a chain such as edge → identity → account → repository → runner. A resource decision stays beside the state and transaction that make it correct.

The database remains shared initially. Identity, repository, Deploy control, notifications, runner, Sites, Wiki, and AI routing are D1 consumers. D1 does not offer per-table grants, so physical colocation is not isolation. Still, forcing a database split during the runtime cutover would have replaced foreign keys, triggers, and local transactions with a distributed data migration before an independent backup or restore boundary had earned that cost.

The contract is therefore explicit: shared D1 is a migration boundary, not shared ownership. Each component has declared write paths. A domain can move later after cross-domain writes become explicit handoffs and independent storage operations provide real value.

Durable work cannot depend on an RPC nudge

Service Bindings and RPC work for bounded facts: authenticate this credential, route this request, or inspect this exact version. They are insufficient once one owner commits state and another Worker or provider must eventually perform a side effect.

Invitation delivery is the concrete example:

repository transaction
  → commit invitation intent + outbox row
  → send a best-effort notification nudge
  → notifications leases and sends
  → persist provider receipt or explicit ambiguity
  → cron repairs a lost nudge or expired lease

Repository owns invitation intent. Notifications owns delivery transitions and the external email side effect. If execution stops before send, a retry is safe. If it stops after the provider accepted the message but before the receipt was saved, Forge records an ambiguous state for reconciliation instead of automatically risking a duplicate email.

Deployments use the same distinction. Deploy control owns desired state, operation records, and active pointers. Runners and provider adapters execute bounded work. Reconciliation survives the request that initiated it.

Release impact follows contracts

Each production component has a machine-readable dependency entry and an exhaustive binding policy. The release-impact calculator evaluates changed paths, shared contracts, migrations, provider configuration, and release-system inputs. An unmapped release contract fails closed. This is the checked-in component graph behind the proposed Routed Monorepo.

Representative runtime impact before and after the authority split
Changed inputOld fixed pipelineCurrent selection
Web or documentation7 Workersedge
Notification binding contract7 Workersnotifications
Identity-local code7 Workersidentity
Repository-local code7 Workersrepository
D1 migration7 Workers8 D1 consumers
Shared runtime input7 Workersall 11 consumers
Release-system code7 Workersproof rerun; 0 rebuilds

The broad rows matter. Independent release does not mean making every selection small. A D1 migration must reach every actual database consumer, and a shared runtime input must reach all eleven Workers. The target is correct impact, not the smallest possible number.

Release completion is component-specific too. A receipt records the exact source SHA, build and Action result, immutable provider version, binding-policy digest, active traffic pointer, live verification, and rollback target. A green build is not a release. An uploaded Worker is not active traffic.

The completion audit records connected provider and live proof for all eleven units after the cutover at source 3c77c17. It also records a real notifications rollback from abc8ded1… to 7a36a2cc… and back. The public dependency graph observed each exact version with no binding mismatch; no sibling Worker needed to move.

Eleven is a checkpoint, not a target

This is not a claim that microservices won. We kept one product, one origin, one monorepo, tightly related repository operations, and—until a stronger operational boundary emerges—one physical D1 database.

Some components may consolidate. ai-router is a plausible candidate for deploy-runtime if shared AI consumers never appear. Other mergers would recreate expensive authority: combining notifications with repository would put email back into Git releases; combining Deploy control with the runtime would give request-serving code provider activation authority; combining runner with repository would give public repository APIs container and provider credentials.

The component count should change when call volume, failure correlation, operational cost, or ownership evidence changes. Eleven is simply the smallest current map that the checked-in authority contracts can defend.

The durable rule is narrower: put responsibilities in one release unit when they share authority, state ownership, failure behavior, and rollback—not because they share a directory or a product noun. The next test is operational. If these boundaries reduce unrelated release failures without turning normal requests into distributed transactions, they are earning their cost. If they do not, the contracts now make the wrong boundary visible enough to change.

The detailed inventory, options, migration stages, and local-development model live in the platform component architecture. The numbers here compare named files and the docs-serving release envelope at specific revisions; they do not measure total code, total platform capability, or future reliability.