DocsReference
#ADR 0013: One repository — modules, ventures and the control plane live here
Status: accepted, 2026-09-08. Supersedes the distribution half of 0005 and the private-crate paragraph of 0011. The naming decisions in both stand unchanged.
#Context
The harness had grown six sibling repositories:
| Repository | Held |
|---|---|
Factory-Zero/auth | 9 login crates and the auth Worker |
Factory-Zero/harness-private | fz-module-linkedin |
Cratefield/control-plane | 10 crates for the managed service |
Cratefield/waitlist-backend | the api.cratefield.com venture |
Factory-Zero/factory0-backend | a README and a banner, no code |
Factory-Zero/venture-backend-template | a README and a banner, no code |
ADR 0005 split them for a real reason: crates.io has no private crates, so anything unpublished had to be a git dependency, and a git dependency is a pin. The cost of that pin came due within two days of the first release:
harness-privatepinnedfactory0-coreat a revision from before the ADR 0011 rename. It could not build againstmainat all.waitlist-backendpinned the same dead names. The venture servingapi.cratefield.comcould not be rebuilt from its own repository.control-planepinned revb085800and had no CI whatsoever, so nothing noticed.- Three of the four had a
cratefield-corea different age from the harness they were built against. Twocratefield_coreversions in one graph is not a version skew, it is a type error:SqliteDatabasedoes not implementcratefield_core::Databasewhen the trait comes from a different copy of the crate.
Each pin was individually defensible and collectively the modules drifted out of the harness. A pin only protects you if somebody moves it.
#Decision
One repository. Cratefield/harness holds every crate — public and private, library and venture — as workspace members with path dependencies.
- Publication is decided per crate by
publish = false, not by which repository the crate sits in. All 20 crates that moved carry it already. The 18 publishedcratefield-*crates are unaffected. - Private-by-repository is replaced by public-by-default. The control plane and the LinkedIn module are now readable source. That is a deliberate trade: their value is the running service and the Page connection, not the source text, and it is worth less than the drift.
- Venture-side
fzbinaries stay excluded from the workspace (crates/auth-fz,crates/control-plane-fz,crates/control-plane-dev) for the reason their own manifests already gave: they are native builds and theirclap/rusqlitedependencies must never reach a wasm graph. - Spikes stay detached with an empty
[workspace]table, asspikes/cryptoalready was. - Package names do not change.
factory0-auth-*is still a Factory Zero service andfz-module-linkedinis still a Factory Zero module; ADR 0011's reasoning for their names survives the move, and neither is published, so a rename would buy nothing. - ADRs are numbered in blocks so two repositories can never claim one number again, which is exactly what happened to 0102: | Block | Subject | |---|---| | 0000–0099 | the harness | | 0100–0199 | cross-cutting harness concerns (crypto, 0102) | | 0200–0299 | the auth service | | 0300–0399 | the control plane | The auth ADRs move 0100–0104 → 0200–0204.
0102-crypto-crate.mdkeeps its number: it is referenced from published crates.
#Consequences
- The two crates that could not build against
mainbuild against it now, because "againstmain" is the only thing they can build against. fz-module-linkedinruns the conformance kit in this repo's own CI instead of through a cross-repository reusable workflow call. The reusableconformance.ymlstays for anyone building modules out of tree.- One
Cargo.lock, onedocs/COMPATIBILITY.md(39 crates), one clippy configuration. The mergeddoc-valid-identslist is the union of the three that existed;control-planehad no clippy config and no CI, and its fourstd::sync::Mutextest fakes needed the annotation ADR 0007's ban has always required. - CI gets slower and touches everything. That is the price of the guarantee and it is the right way round: a slow check that runs beats a fast one that was never wired up.
p256is duplicated: 0.13.2 forcratefield-adapter-apns, 0.14.0 for the four auth crates that pin it directly. Unifying it means a major bump on a published crate and is deliberately not part of this move.- Anyone who cloned one of the six repositories now clones this one. The originals are archived read-only, not deleted, so their issue history and every link into it still resolve.
Amended 2026-10-05 (issue #777). Two bullets above no longer hold and are kept only as the historical record. "Package names do not change": the auth crates were renamed from
factory0-auth-*tocratefield-auth-*(see the ADR 0011 amendment of the same date), because the auth service is now deployed as one branded instance per app rather than as one Factory Zero service. Problem-type URIs are no longer fixed tofactory0.ventures: issue #557 takes the base from the venture (itspublic_url, orabout:blankwhen it has none).fz-module-linkedinand thefactory0example venture are unaffected.
Mirrored from docs/adr/0013-one-repository.md at b6f50cd. The harness is the source of truth: a correction goes there first.