Pre-publication checklist for the public repository
How the public tree is produced: public-snapshot.md
The product is published as Tabula, in the public repository
tabula-cms/tabula. This private repository, which
carries the first installation’s history, is not switched to public: the public repository starts
from a fresh snapshot of this tree (public-snapshot.md). Work through this
list before the first public push; the PR that prepares the publication ticks what it completes.
Items left open marked (owner) are settings only the repository owner can change.
1. How the history is published
Section titled “1. How the history is published”- Fresh history — one initial commit of the prepared tree, not
git filter-repoover this repository. Decided 2026-09-27: it is the only way to be sure nothing old leaks, and it lets the public tree drop files an installation still relies on by name (migration 0013, see public-snapshot.md) without rewriting anything here. - Every check below is repeated against what will be pushed — the clone in section 8 — not only against this repository’s working tree.
2. Secrets
Section titled “2. Secrets”- Full history scanned with gitleaks (command below):
0 findings, 2026-09-27.
--log-opts="--all"covered every branch and tag. - Nothing to rotate: no secret was ever committed (the scan above). Rotate first if a later scan ever finds one — removing a secret from history does not un-leak it.
-
.envis ignored (.gitignore) and was never committed (git log --all -- .envprints nothing). - GitHub Actions secrets and variables are not copied to the public repository — it deploys nothing. (owner)
gitleaks detect --source . --log-opts="--all" --redact --report-path gitleaks-report.json3. Personal data and one school’s content
Section titled “3. Personal data and one school’s content”Data about minors is treated as sensitive: names, photos, classes of students must not appear anywhere in the public repository or its history.
- Historical content files (
data/*.json, the PDFs underpublic/assets/docs/, the images underpublic/assets/images/of the pre-database site) live only in this repository’s old commits; the fresh history never has them. - Test fixtures (
test/): the first installation’s literals (name, address, map coordinates, album, archive prefix,SITE_SLUG) replaced with invented ones; sample names are obviously invented. - Docs:
docs/audits/anddocs/design/(the first installation’s design hand-off, reviews quoting its content) are excluded from the snapshot;docs/issues/**,docs/dev/,docs/editor/anddocs/install/are scrubbed of its name, domain and city. - Installation-specific text in the tree — removed or neutralised:
README.md(rewritten as Tabula’s README),CLAUDE.md(“What this is”),DEPLOYMENT.md(placeholders instead of the first installation’s values), migration0013-seed-original-site-texts.sql(deleted — the runner ignores an applied file that is gone), the legacy import (src/db/importLegacy.js,src/db/registerFile.js,src/db/pagesSeed.js,scripts/import-legacy.js,npm run import:legacy),public/images/emblema.jpg, the school’s coordinates insrc/lib/mapLinks.jsand the tests. The only remaining mentions are in migrations 0006 and 0009, whose text is frozen by its checksum (editing them would stop every existing installation from starting in production); see public-snapshot.md. Verify with the command below (each word is split in two quoted halves the shell joins back, so that this page does not match itself); it prints onlysrc/db/migrations/0006-menus.sqlandsrc/db/migrations/0009-albums.sql.test/neutralTemplate.test.jsholds the same list and fails if any other deployed file names the first installation. - Commit messages and author e-mails: the fresh history is one commit by the owner.
git grep -il -e 'koro''liov' -e 'Корол''ьов' -e 'Жито''мир' -e 'li''cey' -- ':!docs/audits' ':!docs/design'4. Name and identity
Section titled “4. Name and identity”- Name: Tabula —
package.jsonname, the Docker image title,/opt/tabula,ghcr.io/tabula-cms/tabula. Names an existing installation has on disk (SITE_SLUG, the session cookie, archive names,site.db) are untouched. -
package.jsonauthor,description,repository,homepage,bugsset. -
SECURITY.mdpoints at GitHub private vulnerability reporting (/security/advisories/new) instead of an e-mail address.
5. Licence
Section titled “5. Licence”- The owner has chosen a licence: AGPL-3.0-only, 2026-09-26 (licensing.md).
-
package.json(and the root entry ofpackage-lock.json) set to"license": "AGPL-3.0-only"; README «Ліцензія» section. No SPDX headers in source files — by decision. -
LICENSE— pending diff against https://www.gnu.org/licenses/agpl-3.0.txt (whitespace included); being verified separately. - The font’s licence:
public/fonts/OFL-Commissioner.txt(SIL OFL 1.1, the text published with Commissioner in google/fonts), mentioned in README and licensing.md. - Decide how a running site offers its source to its users (licensing.md → «Still open»).
6. Releases, images and update notifications
Section titled “6. Releases, images and update notifications”- The first release is tagged in the public repository (
vX.Y.Z), so its Release and itsghcr.io/tabula-cms/tabula:X.Y.Zimage exist there. - GHCR package visibility set to public (Package settings → Change visibility). Until then
every other school needs a token to
docker pull, and the update check gets 404. (owner) - Package linked to the public repository (the image’s
org.opencontainers.image.sourcelabel comes from the build argumentSOURCE_URL, i.e.github.repository;urlis fixed to the project page). -
UPDATE_REPOfor installations that follow releases:tabula-cms/tabula(.env.example,docs/install/). - The first installation keeps deploying from this private repository, and its deploy writes
UPDATE_REPOfromgithub.repository— a private repository, so its dashboard stays silent. Decide whether it should announce the public releases instead. - The install guide (
docs/install/) usestabula-cms/tabulain every command and theraw.githubusercontent.com/tabula-cms/tabula/main/scripts/server-setup.shURL. That the URL works for an anonymous user is checked in section 8.
7. Repository settings
Section titled “7. Repository settings”- Default branch chosen (
developormain) and documented inCONTRIBUTING.md. - Branch protection on the default branch: PR required, CI (
ci.yml: jobstest,a11y,docker) required, no force-push, no deletion. (owner) - Tag protection for
v*(only maintainers can create release tags). (owner) - Actions: workflows from forks cannot access secrets (GitHub default — verify). The deploy workflows are not in the public snapshot (public-snapshot.md), so there is nothing to disable.
- Private vulnerability reporting enabled (Security → Private vulnerability reporting), as
SECURITY.mdpromises. (owner) - Issue templates / labels as the project needs; Dependabot (
.github/dependabot.yml) kept.
8. Last look
Section titled “8. Last look”- Clone the public repository into an empty directory as an anonymous user, follow
docs/install/on a throwaway server end to end, and bring up a site through the wizard. (owner) - Re-run gitleaks on that clone.
- On that site, one pass with a screen reader — NVDA with Firefox or Chrome on Windows, and
TalkBack on an Android phone: home, a text page through the menu, a post, the document
library, an album with the lightbox, the contact form (send one message, hear the result),
and the low-vision toggle. The automated check (
npm run test:a11y, docs/dev/testing.md → «Accessibility») cannot tell whether the reading order and the announcements make sense.