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 headsuv 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.