Skip to content
The Fabrica

Under the hood

The long answer.

The homepage says a month of plumbing is already done. This page is the evidence, for the kind of buyer who would rather read it than be told. Everything below is copied out of the repository with the path it came from, so you can check the whole page against the code on your first morning.

recipes
49recipesevery decision, written down
locales
11localeswired, not promised
warnings
0warningsmypy --strict, eslint, in CI
register
Art. 30registera document, not a to-do

What ships

Every part, in detail

The homepage groups these into three. Here they are separately, each with the specific thing about it that took the longest to get right.

auth · accounts

Accounts that already handle the awkward cases

Clerk wired to a Postgres user, webhooks reconciled when they arrive out of order, soft delete with a grace window, and a working data-export endpoint. The parts you discover in week three are here in hour one.

billing · ledger

Billing that survives being audited

Paddle as merchant of record, so VAT calculation, collection and remittance aren't yours to build. Signed checkout, idempotent webhooks, a reconciler that catches what the webhook dropped, and a credit ledger that cannot double-spend.

gdpr · dpa · ropa

The compliance file, written

A DPA template, a Records of Processing register, a DPIA trigger checklist, a live subprocessors page, and cascade deletes so erasure actually erases. Code and paperwork, because only one of them is not enough.

workers · llm

Queues and model calls, hardened

Celery on Redis with a JSON-only serializer (pickle is an RCE vector), per-user LLM rate limits, a Redis response cache, and streaming you can switch off with one setting.

seo · headers · guards

The parts nobody puts in a demo

Sitemap with hreflang, robots, OG images, RSS, llms.txt, a security-header stack, and boot-time guards that refuse to start production with a placeholder secret still in place.

scaffold

A generator, not a snippet to paste

One recipe produces model, migration, schema, repository, router and tests for a new user-owned resource — including a mandatory IDOR regression test, because the ownership filter belongs in SQL and in the suite.

Built with it

Someone already shipped on this

The only claim on this page that is not a file in the repository, and the one that carries the most weight: the rest describe what the codebase contains, this describes what it survived.

Scribora

scribora.ai

Turns scattered notes, recordings and research into a publishable ebook.

Accounts, subscription tiers and metered LLM work — the machinery on this page, taking real payments.

Read the code

An invariant, and the test that guards it

The clearest answer to whether a codebase is engineered or assembled is not a feature list — it is whether it knows which of its own rules are load-bearing.

Every read of a user-owned row goes through here, so the ownership filter lives in SQL, in one place.
bin/templates/resource-scaffold/repository.py.tmpl
async def get_for_user(
    self,
    resource_id: uuid.UUID,
    *,
    user_id: uuid.UUID,
) -> Project | None:
    """Fetch by id WITH ownership check. Returns None for foreign rows.

    This is the ONLY way the router should fetch a single row.
    Calling `session.get(Project, id)` directly skips
    the ownership filter — a classic IDOR slip.
    """
    stmt = select(Project).where(
        Project.id == resource_id,
        Project.user_id == user_id,
    )
    return (await self.session.execute(stmt)).scalar_one_or_none()
And the test that fails the moment somebody deletes that filter. It is generated with every resource, and the template says not to remove it.
bin/templates/resource-scaffold/test.py.tmpl
async def test_get_foreign_row_returns_404(
    authed_client: AsyncClient,
    other_user_client: AsyncClient,
) -> None:
    """Another user's row must look like it doesn't exist.

    REGRESSION GUARD: this is the IDOR test. If someone removes the
    user_id filter in the repository, this test fails immediately
    because the other user's GET would return 200 instead of 404.
    """
    create_resp = await other_user_client.post(
        "/api/v1/projects", json={"title": "Test"},
    )
    foreign_id = create_resp.json()["id"]

    get_resp = await authed_client.get(f"/api/v1/projects/{foreign_id}")

    assert get_resp.status_code == 404, (
        f"IDOR REGRESSION: user got status {get_resp.status_code} "
        f"for foreign row {foreign_id}. Must be 404 (not 403 — don't "
        f"leak existence)."
    )

Adding a resource

Adding a user-owned resource is a documented procedure, not a blank page. Six files come from canonical templates, and four invariants come with them: cascade delete for erasure, the composite index your queries actually need, reads that cannot skip the ownership filter, and an audit row on every state change.

The _domain/ folders are yours. Upstream updates never touch them, so pulling a year of factory changes does not mean re-resolving a year of conflicts in your own code.

  • backend/src/models/_domain/project.pyuser FK, ON DELETE CASCADE
  • backend/alembic/versions/007_add_projects.py(user_id, created_at) index
  • backend/src/api/schemas/_domain/project.pyrequest + response models
  • backend/src/services/repositories_async/_domain/project.pyownership filter, in SQL
  • backend/src/api/routers/_domain/project.pyCRUD, audited
  • backend/tests/_domain/test_project.pyhappy path · IDOR · 401
type errors, strict
0type errors, strictmypy --strict, 85 source files
backend tests
308backend testsacross 44 files, run in CI on every push
lint warnings
0lint warningsruff, and eslint --max-warnings 0
migrations
6migrationsalembic upgrade → downgrade → upgrade, in CI