0009 — Public foundation and private consumers
Status: accepted, 2026-09-03. The record is
docs/adr/0009-what-is-public.md;
this page is its map and its summary and states nothing the record does not.
The map
decisions/0009-what-is-public in the recordContext
The foundation needs to build and run without private source or credentials. Commercial capabilities and client configuration have different owners and access requirements, and keeping the shared implementation in one public module does not require publishing those consumers. The record refines the public repository boundary drawn by ADR 0001; migration ownership is defined by ADR 0011, which it cites and does not restate.
Decision
The public repository contains kit/, design/, ui/, the reference
business modules and the reference application; a private catalog supplies
commercial capabilities, and a private application composes the public
foundation, selected catalog capabilities and client modules with its
configuration and assets. The dependency direction is application → catalog →
public foundation: modules cross boundaries through public contracts, not
another module’s implementation, and the public repository never imports or
requires private code. Compatibility with a private consumer is tested in
that consumer’s repository.
Consequences
-
A releasable consumer pins a published version of the public module. During development its
go.modmay select a sibling checkout throughreplace; that dependency must be explicit and available wherever the development build runs, and a successful local build with a replacement does not validate the version named inrequire, so before publishing the consumer tests the selected published dependency without a machine-local path. The consumer’s release procedure owns that check. -
Public documentation describes the consumer seam without naming private repositories, catalog capabilities, clients or infrastructure. Client data, private source, cluster state and credentials do not belong in the public tree, and example tenants and configuration must not expose a customer’s identity or deployment: publishing source makes its history public, and removing it later does not make that disclosure reversible.
-
The source and package ceilings in
loc-budget.jsonandpackages-budget.jsonare the commitment; the record keeps no second table of their values.make check-locandmake check-packagesverify the checked-out source, and a justified increase needs the separate owner reviewCONTRIBUTING.mddefines. -
The foundation and each selected module provide independent migration sources to one runner in composition order; the module name identifies its history owner, versions increase within that owner, there are no global repository ranges, and applied history is immutable, as specified in ADR 0011.
-
The public application’s build, tests and browser checks work without private modules. Consumer compatibility remains a separate test obligation, and a public release does not certify a private product’s deployment or user journeys.
Where it lives
-
RELEASE.md— the release procedure; its last section, "Update consumers", has consumers update their dependency in their own repositories, run their gates and release in dependency order, and warns that a localreplacecan hide a stale version pin, so the module actually selected is verified before a consumer is published. Budget ceilings are reviewed separately from implementation changes. -
loc-budget.json— the line ceilings: onemaxper bucket (kit,modules,ui,apps,tools,go_prod,go_testsupport,go_test,js,design_tooling,design_tooling_test,markdown), each bucket selecting tracked files by path prefix, suffix and package-directory suffix. -
packages-budget.json— the ceiling on first-party packages linked into the reference application, onepackagesnumber. -
tools/locbudget/main.go— counts tracked source lines per bucket overgit ls-files;--checkexits 1 when a bucket is over its maximum,--writeonly ever lowers a maximum, and--write --round 100is the one operation that may raise one, which is why it is a separate flag and an owner’s commit. -
scripts/check_packages.sh— counts thegithub.com/septagon-oss/platformkit/packages thatgo list -deps ./apps/platformkitreaches and fails when the count exceedspackages;--writelowers the ceiling and never raises it. -
Makefile—check-loc(go run ./tools/locbudget --check) andcheck-packages(./scripts/check_packages.sh), both prerequisites ofmake check, which ends with./scripts/check_imports.sh. -
.github/workflows/ci.yml— the "Budgets only ratchet down" step: amaxinloc-budget.jsonor thepackagescount higher than onorigin/mainfails the run, because--writecannot raise a ceiling but a hand edit can; thenmake checkandmake e2e. -
.github/workflows/release.yml— on av*tag, "The tag is on main" refuses a commit main never reached, thenmake checkandmake e2erun on the tagged tree; only then is the image built fromdeploy/Dockerfile, pushed toghcr.io/septagon-oss/platformkit, given an SPDX SBOM by digest, and recorded in a GitHub release with that digest.latestmoves only for a tag with no pre-release part. -
CHANGELOG.md— reviewed when preparing a release; itsv1.0.0entry says what the version promises about size isloc-budget.json. -
go.mod— the public module’s dependency declarations,module github.com/septagon-oss/platformkit, with no privaterequireand noreplace. -
scripts/check_imports.sh— module-boundary enforcement: every cross-module import is acontracts/import andapps/never reaches a module’sinternal/; a consumer calls it from its resolved foundation dependency with its own repository and dependency module paths. -
CONTRIBUTING.md— "Keep budgets honest": remove what a change replaces first, and if the remaining cost is justified, obtain a separate owner budget commit before the implementation.
Evidence
make check-loc # go run ./tools/locbudget --check
make check-packages # ./scripts/check_packages.sh
./scripts/check_imports.sh
make check && make e2e # what release.yml runs on the tagged tree
The record is precise about what it does not claim. A successful local build
with a replace does not validate the version named in require; that check
belongs to the consumer’s release procedure, in the consumer’s repository.
Consumer compatibility remains a separate test obligation, and a public
release does not certify a private product’s deployment or user journeys.