Skip to Content
Extending itTaking an update

Taking an update

Applies to The Fabrica v0.1.2. Documentation source: 1ee1bc9cb661, including post-release documentation corrections.

What _domain/ protects

Your product code belongs in the reserved _domain/ directories:

backend/src/models/_domain/ backend/src/prompts/_domain/ backend/src/api/routers/_domain/ backend/src/scrapers/_domain/ backend/src/services/_domain/ backend/data/seed/_domain/ backend/src/workers/tasks/_domain/ frontend/src/components/_domain/ frontend/src/app/[locale]/(dashboard)/(_domain)/

Upstream ships only convention files in those directories — a .gitkeep in every one, and a README.md in most. A file you create there has no upstream counterpart, so a merge has nothing to reconcile it against and leaves it exactly as you wrote it.

That is the whole guarantee, and it is a real one: in a test update performed against a scratch clone of this repository, buyer-created files under models/_domain/, api/routers/_domain/ and components/_domain/ came through a conflicted merge byte-for-byte unchanged.

What _domain/ does NOT protect

This is the part that matters, because the protection above is narrower than it sounds.

It reduces the surface a merge can collide with. It does not make updates conflict-free. _domain/ is a naming convention and an ownership boundary — there is no dynamic loading, no manifest, no registry, and nothing that isolates you from Git.

Upstream does write inside _domain/ directories. It owns the README.md and .gitkeep there. In the test update, upstream’s change to models/_domain/README.md merged in cleanly — but if you had edited that README yourself, it would have conflicted like any other file. Your files in those directories are yours; the directories are not.

Every factory file outside _domain/ merges normally. If you edited services/email.py and upstream also changed it, you get an ordinary conflict. Nothing about this repository changes that.

Wiring files are the predictable collision point. Registration here is explicit by design — a model is imported in backend/src/models/__init__.py, a router is mounted in backend/src/api/main.py. That buys a statically analysable import graph, and it costs you a line in a shared file for every resource you add. Those shared files are edited by you and by upstream, so they are where conflicts concentrate.

backend/src/models/__init__.py is worth understanding specifically, because registering one model touches it twice — the import block and the __all__ list. Both are conflict candidates, independently:

In the test update, the buyer and upstream each registered a new model. The import block merged automatically (the two inserts landed at different anchor lines), while __all__ conflicted (both inserts landed immediately after "ApiKey",). Same file, same change, same release: one region merged itself, the other did not.

There is no rule to memorise there. Whether you get a conflict depends on where in the block the two edits landed, which is why this guide will not tell you which releases apply cleanly.

Common failure modes

ruff fails on an import block you never touched. Two independent imports merged into one block, out of order. ruff check --fix handles it. Expect this on any update that adds a model, router or service.

A conflict in models/__init__.py or api/main.py. Expected, by design, and usually “keep both”. Explicit wiring is the trade this codebase makes for a statically analysable import graph.

alembic upgrade head fails with multiple heads. Upstream added a migration and so did you, from the same parent, so the chain has two tips:

FAILED: Multiple head revisions are present for given argument 'head'; please specify a specific target revision, '<branchname>@head' to narrow to a specific head, or 'heads' for all heads

uv run alembic heads shows both. Join them with uv run alembic merge heads -m "merge upstream migrations", then upgrade. That command writes a new revision file under backend/alembic/versions/ — it is source, and it must be committed on the update branch like any other integration fix. Read migrations/safe-migrations.md before doing this against a database with real data in it.

Tests fail on code you did not write. Read the incoming commits: git log HEAD@{1}..HEAD --oneline. An upstream change to a factory invariant — the repository base class, deps.py, the settings — can require a matching change in _domain/ code that depends on it. This is a real cost of updating, and the recipes for the changed files explain what the invariant is protecting.


Provenance. Documentation source: 1ee1bc9cb6619c19f57766731e7884b37f515dc9 (main).

Extracted from docs/upstream-updates.md.

That commit is later than the release these pages describe: it carries documentation corrections made after thefabrica-v0.1.2 went out. Nothing that changes how the product behaves landed between the two, which is what makes naming that version honest.

Last updated on